root/trunk/thirdparty/cl-pdf/pdf-template.lisp

Revision 2636, 12.0 kB (checked in by hans, 10 months ago)

add cl-pdf for pixel->pdf converter

Line 
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
Note: See TracBrowser for help on using the browser.