Skip to content
Browse files

added support for writing on separate lines

  • Loading branch information...
1 parent 378707b commit f023b5c21631f39b52ab42e67b70379be5e572c9 @fmw fmw committed May 2, 2012
Showing with 96 additions and 31 deletions.
  1. +3 −2 README.md
  2. +63 −20 src/camelot/core.clj
  3. +30 −9 test/camelot/test/core.clj
View
5 README.md
@@ -23,8 +23,9 @@ Start using the library!
``` clojure
;; Create PDF files with some text.
(-> {:font "Helvetica-Bold"
- :size 12
- :text "Hello World!"
+ :page-size :A6
+ :lines [[{:font-face "Times-Roman" :font-size 25} "Hello, world!"]
+ [{} "Here be dragons!"]]
:metadata {:author "Joe Bloggs"
:title "Hello World"
:keywords ["test" "hello" "world"]}}
View
83 src/camelot/core.clj
@@ -1,6 +1,7 @@
(ns camelot.core
- (:require [clojure.string :as str])
- (:import (org.apache.pdfbox.pdmodel PDDocument PDDocumentInformation PDPage)
+ (:import (org.apache.pdfbox.pdmodel PDDocument
+ PDDocumentInformation
+ PDPage)
(org.apache.pdfbox.pdmodel.edit PDPageContentStream)
(org.apache.pdfbox.pdmodel.font PDType1Font)
(org.apache.pdfbox.util PDFMergerUtility)
@@ -23,32 +24,74 @@
"Symbol" PDType1Font/SYMBOL
"ZapfDingbats" PDType1Font/ZAPF_DINGBATS})
+(defonce page-sizes
+ {:A0 PDPage/PAGE_SIZE_A0
+ :A1 PDPage/PAGE_SIZE_A1
+ :A2 PDPage/PAGE_SIZE_A2
+ :A3 PDPage/PAGE_SIZE_A3
+ :A4 PDPage/PAGE_SIZE_A4
+ :A5 PDPage/PAGE_SIZE_A5
+ :A6 PDPage/PAGE_SIZE_A6
+ :letter PDPage/PAGE_SIZE_LETTER})
+
(defn font
"Given a string representing a font, returns the equivalent ENUM."
[^String name]
(font-map name))
+(defn draw-text-lines-for-page
+ "Given PDocument, PDPage, PDRectangle and sequence of lines
+ (e.g. [[{:font-size 10 :font-face \"Helvetica-Bold\"} \"Hello, World!\"}]),
+ produces a PDocument with the provided text."
+ [doc page page-size lines]
+ (let [start-x 35
+ start-y (- (.getUpperRightY page-size) 75)]
+
+ (do
+ (.addPage doc page))
+
+ (with-open [content (PDPageContentStream. doc page)]
+ (loop [lines lines
+ start-y start-y]
+ (when-let [[line-meta line-content] (first lines)]
+ (let [font-face (font (or (:font-face line-meta) "Helvetica-Bold"))
+ font-size (or (:font-size line-meta) 10)]
+ (doto content
+ (.setFont font-face font-size)
+ (.beginText)
+ (.moveTextPositionByAmount start-x start-y)
+ (.drawString line-content)
+ (.endText))
+ (let [new-y (- start-y (* font-size 1.2))]
+ (if (.contains page-size start-x (- new-y 30))
+ (recur (rest lines) new-y)
+ (do
+ (.close content)
+ (draw-text-lines-for-page doc
+ (PDPage. page-size)
+ page-size
+ (rest lines))))))))))
+
+ doc)
+
(defn save-as
- "Given a map representing the document to be built, and a filename, saves the PDF."
+ "Given a map representing the document to be built, and a filename,
+ saves the PDF."
[doc-map filename]
{:pre [(and (map? doc-map)
(string? filename))]}
- (let [page (PDPage.)
- doc (doto (PDDocument.)
- (.addPage page))
- content (PDPageContentStream. doc page)]
- (try
- (.beginText content)
- (.setFont content (font (doc-map :font)) (doc-map :size))
- (.moveTextPositionByAmount content 100 700)
- (.drawString content (doc-map :text))
- (.endText content)
- (.close content)
- (when (contains? doc-map :metadata)
- (set-metadata doc (doc-map :metadata)))
- (.save doc filename)
- (finally (if (not (nil? doc))
- (.close doc))))
+ (with-open [doc (PDDocument.)]
+ (let [page-size (get page-sizes (or (:page-size doc-map) :A4))]
+ (draw-text-lines-for-page doc
+ (PDPage. page-size)
+ page-size
+ (:lines doc-map)))
+
+ (when (contains? doc-map :metadata)
+ (set-metadata doc (:metadata doc-map)))
+
+ (.save doc filename)
+
doc))
(defn merge-pdfs
@@ -80,4 +123,4 @@
(.save start-doc filename)
(finally (if (not (nil? start-doc))
(.close start-doc))))
- start-doc))
+ start-doc))
View
39 test/camelot/test/core.clj
@@ -1,9 +1,10 @@
(ns camelot.test.core
- (:use [camelot.core]
- [camelot.test.helpers])
- (:use [clojure.test])
- (:import (org.apache.pdfbox.pdmodel PDDocument)
- (org.apache.pdfbox.pdmodel.font PDType1Font)))
+ (:use [camelot.core] :reload
+ [camelot.test.helpers]
+ [clojure.test])
+ (:import (org.apache.pdfbox.pdmodel PDDocument PDPage)
+ (org.apache.pdfbox.pdmodel.font PDType1Font)
+ (org.apache.pdfbox.util PDFTextStripper)))
(deftest converts-font-string-to-enum
(is (= PDType1Font/TIMES_ROMAN (font "Times-Roman")))
@@ -24,7 +25,7 @@
(deftest save-as-bad-input-throws-assertion-error
(let [doc-map {:font "Helvetica-Bold"
:size 12
- :text "Hello World"}
+ :lines [[{} "Hello World"]]}
filename (temp-pdf-filename)]
(is (thrown? AssertionError (save-as [2 3] filename)))
(is (thrown? AssertionError (save-as doc-map 23)))))
@@ -33,17 +34,37 @@
(let [filename (temp-pdf-filename)
doc (-> {:font "Helvetica-Bold"
:size 12
- :text "Hello World"}
+ :lines [[{} "Hello World"]]}
(save-as filename))]
(is (instance? PDDocument doc))
(is (= "file" (file-kind filename)))
(is (= 1 (.getPageCount doc)))))
+(deftest test-draw-text-lines-for-page
+ (let [dummy-lines (map #(str (first %) " " (last %))
+ (partition
+ 2
+ (interleave (range 100)
+ (repeat "here be dragons!"))))]
+ (with-open [doc (PDDocument.)]
+ (testing "make sure the text content is as expected"
+ (is (= (.getText (PDFTextStripper.)
+ (draw-text-lines-for-page
+ doc
+ (PDPage. (:A4 page-sizes))
+ (:A4 page-sizes)
+ (map #(vec [{:font-size 10} %]) dummy-lines)))
+ (apply str (interleave dummy-lines (repeat "\n"))))))
+
+
+ (testing "make sure the text is spread out over two pages"
+ (is (= (.getPageCount doc) 2))))))
+
(deftest save-as-builds-a-basic-pdf-file-with-metadata
(let [filename (temp-pdf-filename)
doc (-> {:font "Helvetica-Bold"
:size 12
- :text "Hello World"
+ :lines [[{} "Hello World"]]
:metadata {:author "Joe Bloggs"
:title "Hello World"
:keywords ["test" "hello" "world"]}}
@@ -77,4 +98,4 @@
(is (= "file" (file-kind name-b)))
(is (= 1 (.getPageCount doc-a)))
(is (= 1 (.getPageCount doc-b)))
- (is (= 2 (.getPageCount merged)))))
+ (is (= 2 (.getPageCount merged)))))

0 comments on commit f023b5c

Please sign in to comment.
Something went wrong with that request. Please try again.