119 lines
5.7 KiB
Clojure
119 lines
5.7 KiB
Clojure
(ns clj-htmltopdf.watermark
|
|
(:require
|
|
[clojure.java.io :as io])
|
|
(:import
|
|
[java.io InputStream OutputStream]
|
|
[org.apache.pdfbox.pdmodel PDDocument PDPage PDPageContentStream PDPageContentStream$AppendMode]
|
|
[org.apache.pdfbox.pdmodel.font PDType1Font PDFont]
|
|
[org.apache.pdfbox.pdmodel.graphics.image PDImageXObject]
|
|
[org.apache.pdfbox.pdmodel.graphics.state PDExtendedGraphicsState]
|
|
[org.apache.pdfbox.util Matrix]))
|
|
|
|
(declare ^:dynamic *pdf-images*)
|
|
|
|
(defn create-pdf-image
|
|
"Creates an XObject image from an image file. Caches this XObject for this particular PDF document so that if the
|
|
image is rendered into the PDF multiple times, the PDF will reference the same image instead of creating duplicates
|
|
(which would potentially bump up the file size drastically).
|
|
This function must only be called from within code invoked by write-watermark!."
|
|
^PDImageXObject [file ^PDDocument doc]
|
|
(if-not *pdf-images*
|
|
(throw (ex-info "Could not create PDF XObject image. Image cache has not been initialized." {:file file})))
|
|
(let [image-file (io/file file)
|
|
path (.getPath image-file)]
|
|
(if-let [existing-image (get @*pdf-images* path)]
|
|
existing-image
|
|
(let [image (PDImageXObject/createFromFileByContent image-file doc)]
|
|
(swap! *pdf-images* assoc path image)
|
|
image))))
|
|
|
|
; TODO: this is a temporary measure to allow at least _some_ font customizability for watermarks
|
|
; until something more comprehensive can be implemented such as allowing loading of external
|
|
; TTF fonts via PDTrueTypeFont.loadTTF()
|
|
(def watermark-fonts
|
|
{"times-roman" PDType1Font/TIMES_ROMAN
|
|
"times-bold" PDType1Font/TIMES_BOLD
|
|
"times-italic" PDType1Font/TIMES_ITALIC
|
|
"times-bolditalic" PDType1Font/TIMES_BOLD_ITALIC
|
|
"helvetica" PDType1Font/HELVETICA
|
|
"helvetica-bold" PDType1Font/HELVETICA_BOLD
|
|
"helvetica-oblique" PDType1Font/HELVETICA_OBLIQUE
|
|
"helvetica-boldoblique" PDType1Font/HELVETICA_BOLD_OBLIQUE
|
|
"courier" PDType1Font/COURIER
|
|
"courier-bold" PDType1Font/COURIER_BOLD
|
|
"courier-oblique" PDType1Font/COURIER_OBLIQUE
|
|
"courier-boldoblique" PDType1Font/COURIER_BOLD_OBLIQUE})
|
|
|
|
(defn render-text-watermark!
|
|
[^PDDocument doc ^PDPage page ^PDPageContentStream cs options]
|
|
(let [font (or (get watermark-fonts (:font options))
|
|
(get watermark-fonts "helvetica-bold"))
|
|
font-size (float (or (:font-size options) 36.0))
|
|
font-color (or (:color options) [0 0 0])
|
|
text (:text options)
|
|
text-width (/ (* (.getStringWidth font text) font-size) 1000.0)
|
|
text-height (/ (* (.getHeight (.getFontBoundingBox (.getFontDescriptor ^PDFont font))) font-size) 1000.0)
|
|
rotation (float (or (:rotation options) 0))
|
|
page-size (.getMediaBox page)
|
|
page-width (.getWidth page-size)
|
|
page-height (.getHeight page-size)
|
|
x (if (= :center (:x options)) (/ page-width 2) (float (:x options)))
|
|
y (if (= :center (:y options)) (/ page-height 2) (float (:y options)))
|
|
transform (doto (Matrix.)
|
|
(.translate x y)
|
|
(.rotate (Math/toRadians rotation))
|
|
(.translate (- (/ text-width 2)) (- (/ text-height 2))))]
|
|
(when (:opacity options)
|
|
(let [r0 (PDExtendedGraphicsState.)]
|
|
(.setNonStrokingAlphaConstant r0 (float (:opacity options)))
|
|
(.setGraphicsStateParameters cs r0)))
|
|
(.beginText cs)
|
|
(.setFont cs font font-size)
|
|
(.setNonStrokingColor cs (int (nth font-color 0)) (int (nth font-color 1)) (int (nth font-color 2)))
|
|
(.setTextMatrix cs transform)
|
|
(.showText cs text)
|
|
(.endText cs)))
|
|
|
|
(defn render-image-watermark!
|
|
[^PDDocument doc ^PDPage page ^PDPageContentStream cs options]
|
|
(let [image (create-pdf-image (:image options) doc)
|
|
image-width (.getWidth image)
|
|
image-height (.getHeight image)
|
|
rotation (float (or (:rotation options) 0))
|
|
page-size (.getMediaBox page)
|
|
page-width (.getWidth page-size)
|
|
page-height (.getHeight page-size)
|
|
x (if (= :center (:x options)) (/ page-width 2) (float (:x options)))
|
|
y (if (= :center (:y options)) (/ page-height 2) (float (:y options)))
|
|
transform (doto (Matrix.)
|
|
(.translate x y)
|
|
(.scale (or (:scale-x options) 1.0) (or (:scale-y options) 1.0))
|
|
(.rotate (Math/toRadians rotation))
|
|
(.translate (- (/ image-width 2)) (- (/ image-height 2))))]
|
|
(when (:opacity options)
|
|
(let [r0 (PDExtendedGraphicsState.)]
|
|
(.setNonStrokingAlphaConstant r0 (float (:opacity options)))
|
|
(.setGraphicsStateParameters cs r0)))
|
|
(.transform cs transform)
|
|
(.drawImage cs image (float 0) (float 0))))
|
|
|
|
(defn render-watermark!
|
|
[^PDDocument doc ^PDPage page ^PDPageContentStream cs options]
|
|
(cond
|
|
(:text options) (render-text-watermark! doc page cs options)
|
|
(:image options) (render-image-watermark! doc page cs options)
|
|
:else (throw (Exception. "Unknown type of watermark. Either :text or :image should be specified."))))
|
|
|
|
(defn write-watermark!
|
|
[^InputStream pdf ^OutputStream out {:keys [watermark] :as options}]
|
|
(with-open [doc (PDDocument/load pdf)]
|
|
(binding [*pdf-images* (atom {})]
|
|
(doseq [^PDPage page (.getPages doc)]
|
|
(let [cs (PDPageContentStream. doc page PDPageContentStream$AppendMode/APPEND true true)]
|
|
(with-open [cs cs]
|
|
(if (map? watermark)
|
|
(render-watermark! doc page cs watermark)
|
|
(watermark doc page cs))))))
|
|
(.save doc out)
|
|
out))
|