| 1 |
;;; cl-pdf copyright 2002-2005 Marc Battyani see license.txt for the details |
|---|
| 2 |
;;; You can reach me at marc.battyani@fractalconcept.com or marc@battyani.net |
|---|
| 3 |
;;; The homepage of cl-pdf is here: http://www.fractalconcept.com/asp/html/cl-pdf.html |
|---|
| 4 |
|
|---|
| 5 |
;;;# PDF Templates by Tim Daly jr |
|---|
| 6 |
;;; |
|---|
| 7 |
;;; Copyright (c) 2005 Tim Daly Jr. All rights reserved. |
|---|
| 8 |
;;; |
|---|
| 9 |
;;; Redistribution and use in source and binary forms, with or without |
|---|
| 10 |
;;; modification, are permitted provided that the following conditions |
|---|
| 11 |
;;; are met: |
|---|
| 12 |
;;; |
|---|
| 13 |
;;; 1. Redistributions of source code must retain the above copyright |
|---|
| 14 |
;;; notice, this list of conditions and the following disclaimer. |
|---|
| 15 |
;;; |
|---|
| 16 |
;;; 2. Redistributions in binary form must reproduce the above copyright |
|---|
| 17 |
;;; notice, this list of conditions and the following disclaimer in |
|---|
| 18 |
;;; the documentation and/or other materials provided with the |
|---|
| 19 |
;;; distribution. |
|---|
| 20 |
;;; |
|---|
| 21 |
;;; THIS SOFTWARE IS PROVIDED BY TIM DALY JR. ``AS IS'' AND ANY |
|---|
| 22 |
;;; EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|---|
| 23 |
;;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|---|
| 24 |
;;; PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TIM DALY JR. OR |
|---|
| 25 |
;;; HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|---|
| 26 |
;;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
|---|
| 27 |
;;; NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|---|
| 28 |
;;; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|---|
| 29 |
;;; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
|---|
| 30 |
;;; STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|---|
| 31 |
;;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
|---|
| 32 |
;;; OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 33 |
|
|---|
| 34 |
;;; Converting a page of a PDF document into a template allows you to |
|---|
| 35 |
;;; draw it just like an image or some text. This is useful for |
|---|
| 36 |
;;; watermarking, merging documents, and creating templates that can |
|---|
| 37 |
;;; be filled in. |
|---|
| 38 |
|
|---|
| 39 |
;;; This code was developed using Adobe's PDF Reference, Third |
|---|
| 40 |
;;; Edition, Version 1.4, which is currently available here: |
|---|
| 41 |
|
|---|
| 42 |
;;; http://partners.adobe.com/public/developer/en/pdf/PDFReference.pdf |
|---|
| 43 |
|
|---|
| 44 |
;;; We refer to this document as "PDF-REF <Section-ID>" below. |
|---|
| 45 |
|
|---|
| 46 |
;;;## Package definition |
|---|
| 47 |
|
|---|
| 48 |
(in-package #:pdf) |
|---|
| 49 |
|
|---|
| 50 |
;;;## Template Objects |
|---|
| 51 |
|
|---|
| 52 |
(defparameter *default-template-size* #(0 0 595 841) |
|---|
| 53 |
;; A4 page size (same default as page object) |
|---|
| 54 |
;; the initform of the bounds slot in the pdf::page class. |
|---|
| 55 |
"Default size of a template's bounding box.") |
|---|
| 56 |
|
|---|
| 57 |
;;; A template is implemented as a Form XObject, see PDF-REF 4.9. |
|---|
| 58 |
(defclass template (indirect-object) |
|---|
| 59 |
((name :accessor name |
|---|
| 60 |
;; I'm assuming that you can use any name you want to. |
|---|
| 61 |
:initform (gen-name "/CLXOBJ") :initarg :name) |
|---|
| 62 |
(bounds :accessor bounds |
|---|
| 63 |
:initform *default-template-size* :initarg :bounds) |
|---|
| 64 |
(resources :accessor resources |
|---|
| 65 |
:initform (make-instance 'dictionary)))) |
|---|
| 66 |
|
|---|
| 67 |
(defmethod initialize-instance :after ((template template) &rest init-options |
|---|
| 68 |
&key no-compression |
|---|
| 69 |
&allow-other-keys) |
|---|
| 70 |
(declare (ignore init-options)) |
|---|
| 71 |
;; ProcSet is not really necessary, see PDF-REF 9.1. |
|---|
| 72 |
(add-dict-value (resources template) "/ProcSet" "[ /PDF /Text ]") |
|---|
| 73 |
(setf (content template) |
|---|
| 74 |
(make-instance 'pdf-stream |
|---|
| 75 |
:no-compression no-compression |
|---|
| 76 |
:dict-values `(("/Type" . "/Xobject") |
|---|
| 77 |
("/Subtype" . "/Form") |
|---|
| 78 |
("/FormType" . "1") |
|---|
| 79 |
("/BBox" . ,#'(lambda () (bounds template))) |
|---|
| 80 |
("/Resources" . ,(resources template)))))) |
|---|
| 81 |
|
|---|
| 82 |
(defun draw-template (template) |
|---|
| 83 |
"Cause a template to be drawn on the current page." |
|---|
| 84 |
;; See PDF-REF 4.7. |
|---|
| 85 |
(format *page-stream* "~a Do~%" (name template))) |
|---|
| 86 |
|
|---|
| 87 |
|
|---|
| 88 |
(defun add-templates-to-page (&rest templates) |
|---|
| 89 |
"Add TEMPLATES to the resources dictionary of the current page, so |
|---|
| 90 |
that they can be drawn by e.g., DRAW-TEMPLATE." |
|---|
| 91 |
(dolist (template templates) |
|---|
| 92 |
(add-dict-value (xobjects *page*) (name template) template))) |
|---|
| 93 |
|
|---|
| 94 |
(defconstant +identity-matrix+ |
|---|
| 95 |
(if (boundp '+identity-matrix+) |
|---|
| 96 |
(symbol-value '+identity-matrix+) |
|---|
| 97 |
#(1 0 0 1 0 0))) |
|---|
| 98 |
|
|---|
| 99 |
(defun make-template-from-page (old-page &key scale rotate |
|---|
| 100 |
(translate-x 0) (translate-y 0)) |
|---|
| 101 |
(when (typep old-page 'indirect-object) |
|---|
| 102 |
(setf old-page (content old-page))) |
|---|
| 103 |
(let ((template (make-instance 'template :bounds |
|---|
| 104 |
(or (resolve-page-dict-value old-page "/MediaBox") |
|---|
| 105 |
*default-template-size*)))) |
|---|
| 106 |
(let ((old-content-stream (resolve-dict-value old-page "/Contents")) |
|---|
| 107 |
(new-content-stream (content template))) |
|---|
| 108 |
;; copy the old content stream |
|---|
| 109 |
(setf (content new-content-stream) |
|---|
| 110 |
(content old-content-stream)) |
|---|
| 111 |
;; copy the other dict entries, e.g. filter, length, from the old stream |
|---|
| 112 |
(loop for ((name . value)) on (dict-values old-content-stream) do |
|---|
| 113 |
(change-dict-value new-content-stream name (copy-pdf-structure value)))) |
|---|
| 114 |
|
|---|
| 115 |
(let ((form-matrix +identity-matrix+)) |
|---|
| 116 |
;; rotate the template |
|---|
| 117 |
(when (and rotate (not (zerop rotate))) |
|---|
| 118 |
(setf form-matrix |
|---|
| 119 |
(let ((translate-to-origin (translation-matrix |
|---|
| 120 |
(- (/ (template-width template) 2)) |
|---|
| 121 |
(- (/ (template-height template) 2)))) |
|---|
| 122 |
(translate-back (translation-matrix |
|---|
| 123 |
(/ (template-width template) 2) |
|---|
| 124 |
(/ (template-height template) 2)))) |
|---|
| 125 |
(multiply-tranformation-matrices |
|---|
| 126 |
(multiply-tranformation-matrices translate-to-origin |
|---|
| 127 |
(rotation-matrix rotate)) |
|---|
| 128 |
translate-back)))) |
|---|
| 129 |
;; scale the template |
|---|
| 130 |
(when scale |
|---|
| 131 |
(setf form-matrix |
|---|
| 132 |
(multiply-tranformation-matrices |
|---|
| 133 |
form-matrix |
|---|
| 134 |
(scale-matrix scale scale)))) |
|---|
| 135 |
|
|---|
| 136 |
;;translate the template |
|---|
| 137 |
(setf form-matrix |
|---|
| 138 |
(multiply-tranformation-matrices |
|---|
| 139 |
form-matrix |
|---|
| 140 |
(translation-matrix translate-x translate-y))) |
|---|
| 141 |
|
|---|
| 142 |
;; set the template's form matrix, see PDF-REF Table 4.41 |
|---|
| 143 |
(change-dict-value (content template) "/Matrix" form-matrix)) |
|---|
| 144 |
|
|---|
| 145 |
;; copy any metadata associated with the page. Not sure if this |
|---|
| 146 |
;; is what we want. |
|---|
| 147 |
(when (get-dict-value old-page "/Metadata") |
|---|
| 148 |
(add-dict-value (content template) "/Metadata" |
|---|
| 149 |
(copy-pdf-structure (get-dict-value old-page "/Metadata")))) |
|---|
| 150 |
|
|---|
| 151 |
;; copy the page's resources, such as images and fonts. |
|---|
| 152 |
(let ((old-resources (resolve-page-dict-value old-page "/Resources"))) |
|---|
| 153 |
(loop for ((name . value)) on (dict-values old-resources) do |
|---|
| 154 |
(change-dict-value (resources template) name (copy-pdf-structure value)))) |
|---|
| 155 |
|
|---|
| 156 |
template)) |
|---|
| 157 |
|
|---|
| 158 |
(defun template-width (template) |
|---|
| 159 |
(svref (bounds template) 2)) |
|---|
| 160 |
|
|---|
| 161 |
(defun template-height (template) |
|---|
| 162 |
(svref (bounds template) 3)) |
|---|
| 163 |
|
|---|
| 164 |
;;;## Transformation Matrices (See PDF-REF 4.2.3) |
|---|
| 165 |
(defun rotation-matrix (deg) |
|---|
| 166 |
(let* ((angle (/ (* pi deg) 180)) |
|---|
| 167 |
(s (sin angle)) |
|---|
| 168 |
(c (cos angle))) |
|---|
| 169 |
(vector c s (- s) c 0 0))) |
|---|
| 170 |
|
|---|
| 171 |
(defun translation-matrix (x y) |
|---|
| 172 |
(vector 1 0 0 1 x y)) |
|---|
| 173 |
|
|---|
| 174 |
(defun scale-matrix (x y) |
|---|
| 175 |
(vector x 0 0 y 0 0)) |
|---|
| 176 |
|
|---|
| 177 |
(defun multiply-tranformation-matrices (a b) |
|---|
| 178 |
"C_ik = A_ij B_jk, where a matrix like this: [ a b 0 ] |
|---|
| 179 |
[ c d 0 ] |
|---|
| 180 |
[ e f 1 ] |
|---|
| 181 |
|
|---|
| 182 |
is stored liked this: [ a b c d e f ]." |
|---|
| 183 |
(let ((a_11 (svref a 0)) |
|---|
| 184 |
(a_12 (svref a 1)) |
|---|
| 185 |
(a_21 (svref a 2)) |
|---|
| 186 |
(a_22 (svref a 3)) |
|---|
| 187 |
(a_31 (svref a 4)) |
|---|
| 188 |
(a_32 (svref a 5)) |
|---|
| 189 |
|
|---|
| 190 |
(b_11 (svref b 0)) |
|---|
| 191 |
(b_12 (svref b 1)) |
|---|
| 192 |
(b_21 (svref b 2)) |
|---|
| 193 |
(b_22 (svref b 3)) |
|---|
| 194 |
(b_31 (svref b 4)) |
|---|
| 195 |
(b_32 (svref b 5))) |
|---|
| 196 |
(vector (+ (* a_11 b_11) (* a_12 b_21)) |
|---|
| 197 |
(+ (* a_11 b_12) (* a_12 b_22)) |
|---|
| 198 |
(+ (* a_21 b_11) (* a_22 b_21)) |
|---|
| 199 |
(+ (* a_21 b_12) (* a_22 b_22)) |
|---|
| 200 |
(+ (* a_31 b_11) (* a_32 b_21) b_31) |
|---|
| 201 |
(+ (* a_31 b_12) (* a_32 b_22) b_32)))) |
|---|
| 202 |
|
|---|
| 203 |
;;;## Copying PDF Structure |
|---|
| 204 |
|
|---|
| 205 |
(defgeneric copy-pdf-structure (arg) |
|---|
| 206 |
(:documentation "Copy some piece of a PDF, creating fresh indirect |
|---|
| 207 |
objects.")) |
|---|
| 208 |
|
|---|
| 209 |
(defmethod copy-pdf-structure ((dict dictionary)) |
|---|
| 210 |
(let ((new-dict (make-instance 'dictionary))) |
|---|
| 211 |
(loop for ((name . value)) on (dict-values dict) do |
|---|
| 212 |
(change-dict-value new-dict name (copy-pdf-structure value))) |
|---|
| 213 |
new-dict)) |
|---|
| 214 |
|
|---|
| 215 |
(defmethod copy-pdf-structure ((obj indirect-object)) |
|---|
| 216 |
(make-instance 'indirect-object |
|---|
| 217 |
:content (copy-pdf-structure (content obj)))) |
|---|
| 218 |
|
|---|
| 219 |
(defmethod copy-pdf-structure ((obj pdf-stream)) |
|---|
| 220 |
(let ((new-stream (make-instance 'pdf-stream :empty t))) |
|---|
| 221 |
(setf (content new-stream) |
|---|
| 222 |
(copy-pdf-structure (content obj))) |
|---|
| 223 |
;; I think this suppresses compressing the stream again..? |
|---|
| 224 |
(setf (no-compression new-stream) t) |
|---|
| 225 |
(loop for ((name . value)) on (dict-values obj) do |
|---|
| 226 |
(change-dict-value new-stream name (copy-pdf-structure value))) |
|---|
| 227 |
|
|---|
| 228 |
new-stream)) |
|---|
| 229 |
|
|---|
| 230 |
(defmethod copy-pdf-structure ((obj sequence)) |
|---|
| 231 |
(map (type-of obj) |
|---|
| 232 |
(lambda (o) |
|---|
| 233 |
(copy-pdf-structure o)) |
|---|
| 234 |
obj)) |
|---|
| 235 |
|
|---|
| 236 |
(defmethod copy-pdf-structure ((obj t)) |
|---|
| 237 |
;; This can be useful for debugging: |
|---|
| 238 |
;; (format t "warning, not copying ~A~%" (type-of obj)) |
|---|
| 239 |
obj) |
|---|
| 240 |
|
|---|
| 241 |
;;;## A couple of utility functions. |
|---|
| 242 |
|
|---|
| 243 |
(defun resolve-dict-value (obj key) |
|---|
| 244 |
"If a dictionary value is an indirect object, return the indirect |
|---|
| 245 |
object's content." |
|---|
| 246 |
(do ((value (get-dict-value obj key) (content value))) |
|---|
| 247 |
((not (typep value 'indirect-object)) value))) |
|---|
| 248 |
|
|---|
| 249 |
|
|---|
| 250 |
(defun resolve-page-dict-value (page key) |
|---|
| 251 |
"Look KEY up in the page dictionary. If it is not found, look it up |
|---|
| 252 |
in the parent page dictionary." |
|---|
| 253 |
;; Some values in a page dictionary can be inherited from the parent |
|---|
| 254 |
;; page's dictionary, such as the MediaBox. This is a handy way to |
|---|
| 255 |
;; look them up. See PDF-REF Table 3.18. |
|---|
| 256 |
(let ((value (resolve-dict-value page key))) |
|---|
| 257 |
(or value |
|---|
| 258 |
(let ((parent-page (resolve-dict-value page "/Parent"))) |
|---|
| 259 |
(and parent-page |
|---|
| 260 |
(resolve-page-dict-value parent-page key)))))) |
|---|
| 261 |
|
|---|
| 262 |
|
|---|
| 263 |
|
|---|
| 264 |
;;;## Example Usage |
|---|
| 265 |
|
|---|
| 266 |
;;; Try something like this after loading pdf-template.lisp: |
|---|
| 267 |
;;; |
|---|
| 268 |
;;; (pdf:test-template "/tmp/ex7.pdf" 1 "/tmp/template.pdf") |
|---|
| 269 |
#+nil |
|---|
| 270 |
(defun test-template (in-file page-number out-file) |
|---|
| 271 |
"Create a new PDF with the given file and page number drawn several |
|---|
| 272 |
times. This test requires pdf-parser to be loaded." |
|---|
| 273 |
(let* ((old-doc (read-pdf-file in-file)) |
|---|
| 274 |
(old-root (root-page old-doc)) |
|---|
| 275 |
(old-page (aref (pages old-root) page-number)) |
|---|
| 276 |
(old-page-bounds (or (resolve-page-dict-value (content old-page) "/MediaBox") |
|---|
| 277 |
*default-template-size*)) |
|---|
| 278 |
(width (svref old-page-bounds 2)) |
|---|
| 279 |
(height (svref old-page-bounds 3))) |
|---|
| 280 |
(with-document () |
|---|
| 281 |
(with-page (:bounds old-page-bounds) |
|---|
| 282 |
(let ((top-template (make-template-from-page |
|---|
| 283 |
old-page :scale 2/5 :translate-x (* 3/10 width) |
|---|
| 284 |
:translate-y (* 3/5 height))) |
|---|
| 285 |
(right-template (make-template-from-page |
|---|
| 286 |
old-page :scale 2/5 :rotate 90 |
|---|
| 287 |
:translate-x (* 3/5 width) :translate-y (* 3/10 height))) |
|---|
| 288 |
(left-template (make-template-from-page |
|---|
| 289 |
old-page :scale 2/5 :rotate -90 |
|---|
| 290 |
:translate-y (* 3/10 height ))) |
|---|
| 291 |
(bottom-template (make-template-from-page |
|---|
| 292 |
old-page :scale 2/5 :rotate 180 |
|---|
| 293 |
:translate-x (* 3/10 width)))) |
|---|
| 294 |
(add-templates-to-page top-template right-template |
|---|
| 295 |
left-template bottom-template) |
|---|
| 296 |
(draw-template top-template) |
|---|
| 297 |
(draw-template bottom-template) |
|---|
| 298 |
(draw-template left-template) |
|---|
| 299 |
(draw-template right-template))) |
|---|
| 300 |
(write-document out-file)))) |
|---|
| 301 |
|
|---|