diff --git a/src/clj_htmltopdf/core.clj b/src/clj_htmltopdf/core.clj index 2fda5a7..e11a5b7 100644 --- a/src/clj_htmltopdf/core.clj +++ b/src/clj_htmltopdf/core.clj @@ -1,13 +1,18 @@ (ns clj-htmltopdf.core (:require [clojure.java.io :as io] - [hiccup.page :as h]) + [clojure.string :as string] + [hiccup.page :as h] + [clj-htmltopdf.css :as css] + [clj-htmltopdf.options :as o]) (:import [java.io OutputStream] [com.openhtmltopdf DOMBuilder] [com.openhtmltopdf.pdfboxout PdfRendererBuilder] [com.openhtmltopdf.util XRLog] - [org.jsoup Jsoup])) + [org.jsoup Jsoup] + [org.jsoup.nodes Document Element] + [org.jsoup.parser Tag])) (defn- read-html [in] @@ -29,35 +34,36 @@ (catch Exception ex (throw (Exception. "Error preparing an OutputStream from output given." ex))))) -(defn- parse-html5 +(defn- parse-jsoup-html [^String html] (try - (let [parsed-doc (Jsoup/parse html)] - (DOMBuilder/jsoup2DOM parsed-doc)) + (Jsoup/parse html) (catch Exception ex - (throw (Exception. "Error parsing input as HTML5." ex))))) + (throw (Exception. "Error parsing input HTML to Jsoup Document." ex))))) -(def default-options - {:logging? false - :base-uri "" - :include-base-css? true - :page {:size :letter - :orientation :portrait}}) +(defn- set-jsoup-html-doc + [^PdfRendererBuilder builder jsoup-doc base-uri] + (try + (let [doc (DOMBuilder/jsoup2DOM jsoup-doc)] + (.withW3cDocument builder doc base-uri)) + (catch Exception ex + (throw (Exception. "Error setting org.w3c.dom.Document HTML." ex))))) (defn ->pdf [in out & [options]] - (let [options (merge default-options options) + (let [options (merge o/default-options options) builder (PdfRendererBuilder.) - base-uri (str (:base-uri options)) html (read-html in) - html-doc (parse-html5 html) + html-doc (parse-jsoup-html html) + html-doc (o/inject-options-into-html html-doc options) output (->output-stream out)] + html-doc (if (:logging? options) (let [logger (:logger options)] (if logger (XRLog/setLoggerImpl logger)) (XRLog/setLoggingEnabled true)) (XRLog/setLoggingEnabled false)) - (.withW3cDocument builder html-doc base-uri) + (set-jsoup-html-doc builder html-doc (o/->base-uri options)) (with-open [os output] (.toStream builder os) (try diff --git a/src/clj_htmltopdf/options.clj b/src/clj_htmltopdf/options.clj new file mode 100644 index 0000000..65e71c6 --- /dev/null +++ b/src/clj_htmltopdf/options.clj @@ -0,0 +1,66 @@ +(ns clj-htmltopdf.options + (:require + [clojure.java.io :as io] + [clojure.string :as string] + [clj-htmltopdf.css :as css]) + (:import + [org.jsoup.nodes Document Element] + [org.jsoup.parser Tag])) + +(def default-options + {:logging? false + :base-uri "" + :include-base-css? true + :page {:size :letter + :orientation :portrait + :margin "1.0in"}}) + +(defn ->base-uri + [options] + (str (:base-uri options))) + +(defn ->page-size-css + [{:keys [size orientation] :as page-options}] + (if (or size orientation) + (string/trim + (str + (cond + (keyword? size) (name size) + (sequential? size) (string/join " " size) + :else size) + " " + (if (keyword? orientation) + (name orientation) + orientation))))) + +(defn page-options->css + [page-options] + (let [styles (->> [[:size (->page-size-css page-options)]] + (into []) + (remove #(nil? (second %))) + (reduce #(assoc %1 (first %2) (second %2)) {}))] + [["@page" styles]])) + +(defn append-page-options-style-tag + ^Element [^Element parent options] + (let [styles (-> (:page options) + (page-options->css) + (css/css->str)) + element (.appendElement parent "style")] + (.attr element "type" "text/css") + (.text element styles))) + +(defn append-base-css-link-tag + ^Element [^Element parent] + (let [element (.appendElement parent "link")] + (.attr element "type" "text/css") + (.attr element "rel" "stylesheet") + (.attr element "href" (str (io/resource "htmltopdf-base.css"))))) + +(defn inject-options-into-html + [^Document doc options] + (let [base-uri (->base-uri options) + head-tag (-> doc (.select "head") (.first))] + (append-page-options-style-tag head-tag options) + (if (:include-base-css? options) (append-base-css-link-tag head-tag)) + doc))