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

Revision 4148, 14.3 kB (checked in by hans, 2 weeks ago)

Fix some small bugs.

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 (in-package #:pdf)
6
7 ;;;text functions
8
9 (defmacro in-text-mode (&body body)
10   `(unwind-protect
11      (let ((*font* nil))
12        (write-line "BT" *page-stream*)
13        ,@body)
14     (write-line "ET" *page-stream*)))
15
16 (defun set-font (font size)
17   (setf *font* font)
18   (setf *font-size* size)
19   (format *page-stream* "~a ~,2f Tf~%" (name (add-font-to-page font)) size))
20
21 (defun set-gstate (&rest gstate)
22   (format *page-stream* "~a gs~%" (name (apply #'add-gstate-to-page gstate))))
23
24 (defmacro def-pdf-op (name (&rest args) format)
25   (if args
26     `(defun ,name ,args (format *page-stream* ,format ,@args))
27     `(defun ,name () (write-line ,format *page-stream*))))
28
29 (def-pdf-op move-text (dx dy) "~8f ~8f Td~%")
30
31 ;;; String output
32
33 (defgeneric write-to-page (object encoding &optional escape)
34  (:documentation
35   "Write object (only text for now) into current *page-stream* in text mode"))
36
37 ;;; show-text writes string as is, draw-text escapes dangerous characters.
38 ;;; Args: text String or a single character
39
40 (defun show-text (text)
41   (if *font*
42       (progn (write-to-page text (if *font* (encoding *font*)))
43         (write-line "Tj" *page-stream*))
44       (format *page-stream* "(~a) Tj~%" text)))
45
46 (defun draw-text (text)
47   (if *font*
48       (progn (write-to-page text (if *font* (encoding *font*)) t)
49         (write-line "Tj" *page-stream*))
50       (format *page-stream* "(~a) Tj~%" text)))
51
52 (defun show-text-on-next-line (string)
53   (write-to-page string (if *font* (encoding *font*)))
54   (write-line "'" *page-stream*))
55
56 (defun draw-text-on-next-line (string)
57   (write-to-page string (if *font* (encoding *font*)) t)
58   (write-line "'" *page-stream*))
59
60 (defun show-spaced-strings (strings)
61   (write-string "[ " *page-stream*)
62   (let ((encoding (if *font* (encoding *font*))))
63     (dolist (item strings)
64       (if (numberp item)
65           (format *page-stream* "~a " item)
66           (write-to-page item encoding))))
67   (write-line "] TJ" *page-stream*))
68
69 (defun draw-spaced-strings (strings)
70   (write-string "[ " *page-stream*)
71   (let ((encoding (if *font* (encoding *font*))))
72     (dolist (item strings)
73       (if (numberp item)
74           (format *page-stream* "~a " item)
75           (write-to-page item encoding t))))
76   (write-line "] TJ" *page-stream*))
77
78 (defmethod write-to-page ((string string) encoding &optional escape)
79   (declare (ignore encoding))
80   (write-char #\( *page-stream*)
81   (if escape
82       (loop for char across string
83             do (case char
84                  ((#\( #\) #\\)
85                   (write-char #\\ *page-stream*)
86                   (write-char char *page-stream*))
87                  (otherwise
88                   (write-char char *page-stream*))))
89     (write-string string *page-stream*))
90   (write-string ") " *page-stream*))
91
92 (defmethod write-to-page ((string string) (encoding single-byte-encoding) &optional escape)
93   ;; There is no point to interpret \n and others in a special way
94   ;; as they are not control characters within content stream
95   (write-char #\( *page-stream*)
96   (if (or escape
97           #+lispworks (lw:text-string-p string)         ; may include unicode
98           #+allegro t)
99       (loop with charset = (charset encoding)
100             for char across string
101             do (case char
102                  ((#\( #\) #\\)
103                   (write-char #\\ *page-stream*)
104                   (write-char char *page-stream*))
105                  ;(#\Newline
106                  ; (write-string "\\n" *page-stream*))
107                  ;(#\Return
108                  ; (write-string "\\r" *page-stream*))
109                  ;(#\Tab
110                  ; (write-string "\\t" *page-stream*))
111                  (otherwise
112                   (write-char (if #+lispworks (lw:base-char-p char)
113                                   #+(or allegro sbcl) (standard-char-p char)
114                                   #-(or lispworks allegro sbcl) t
115                                   char
116                                   (code-char (char-external-code char charset)))
117                               *page-stream*))))
118       (write-string string *page-stream*))
119   (write-string ") " *page-stream*))
120
121 (defmethod write-to-page ((string string) (encoding unicode-encoding) &optional escape)
122   (declare (ignore escape))
123   (write-char #\< *page-stream*)
124   (loop for char across string do
125         (format *page-stream* "~4,'0x" (char-code char)))
126   (write-string "> " *page-stream*))
127
128 ;;; Single character output
129
130 (defun show-char (char)
131  ;;; Deprecated in favor of show-text or draw-text
132   (write-to-page char (if *font* (encoding *font*)) t)
133   (write-line "Tj" *page-stream*))
134
135 (defmethod write-to-page ((char character) encoding &optional escape)
136  ;;; This default method is only needed for deprecated and legacy code,
137   ;; e.g. bar-codes.lisp: draw-chars (*font* is nil) -> draw-char
138   (declare (ignore encoding))
139   (write-char #\( *page-stream*)
140   (when escape (case char
141                  ((#\( #\) #\\) (write-char #\\ *page-stream*))))
142   (write-char char *page-stream*)
143   (write-char #\) *page-stream*))
144
145 (defmethod write-to-page ((char character)(encoding single-byte-encoding) &optional escape)
146   (write-char #\( *page-stream*)
147   (when escape (case char
148                  ((#\( #\) #\\) (write-char #\\ *page-stream*))))
149   (write-char (if #+lispworks (lw:base-char-p char)
150                   #+(or allegro sbcl) (standard-char-p char)
151                   #-(or lispworks allegro sbcl) t
152                   char
153                   (code-char (char-external-code char (charset encoding))))
154               *page-stream*)
155   (write-char #\) *page-stream*))
156
157 (defmethod write-to-page ((char character) (encoding unicode-encoding) &optional escape)
158   (declare (ignore escape))
159   (write-char #\< *page-stream*)
160   (format *page-stream* "~4,'0x" (char-code char))
161   (write-char #\> *page-stream*))
162
163 (def-pdf-op set-text-rendering-mode (mode) "~d Tr~%")
164
165 (def-pdf-op set-char-spacing (space) "~8f Tc~%")
166
167 (def-pdf-op set-text-x-scale (scale) "~8f Tz~%")
168
169 (def-pdf-op set-text-leading (space) "~8f TL~%")
170
171 (def-pdf-op set-text-rise (rise) "~8f Ts~%")
172
173 (def-pdf-op move-to-next-line () " T*")
174
175 (def-pdf-op set-text-matrix (a b c d e f) "~10f ~10f ~10f ~10f ~10f ~10f Tm~%")
176
177 ;;; graphic functions
178 (defconstant +deg-to-rad+ #.(/ pi 180))
179
180 (defmacro with-saved-state (&body body)
181   `(unwind-protect
182      (progn (write-line "q" *page-stream*)
183             ,@body)
184     (write-line "Q" *page-stream*)))
185
186 (def-pdf-op set-transform-matrix (a b c d e f) "~8f ~8f ~8f ~8f ~8f ~8f cm~%")
187
188 (def-pdf-op translate (dx dy) "1.0 0.0 0.0 1.0 ~8f ~8f cm~%")
189
190 (defun rotate (deg)
191   (let* ((angle (* +deg-to-rad+ deg))
192          (s (sin angle))
193          (c (cos angle)))
194     (format *page-stream* "~10f ~10f ~10f ~10f 0.0 0.0 cm~%" c s (- s) c)))
195
196 (defun rotate* (radians)
197   (let* ((s (sin radians))
198          (c (cos radians)))
199     (format *page-stream* "~10f ~10f ~10f ~10f 0.0 0.0 cm~%" c s (- s) c)))
200
201 (def-pdf-op scale (sx sy) " ~8f 0.0 0.0 ~8f 0.0 0.0 cm~%")
202
203 (defun skew (x-deg y-deg)
204   (format *page-stream* " 1.0 ~10f ~10f 1.0 0.0 0.0 cm~%"
205           (tan (* +deg-to-rad+ x-deg))(tan (* +deg-to-rad+ y-deg))))
206
207 (defun skew* (x-radians y-radians)
208   (set-transform-matrix 1.0 (tan x-radians) (tan y-radians) 1.0 0.0 0.0))
209
210 (def-pdf-op set-line-width (width) "~8f w~%")
211
212 (def-pdf-op set-line-cap (mode) "~d J~%")
213
214 (def-pdf-op set-line-join (mode) "~d j~%")
215
216 (def-pdf-op set-dash-pattern (dash-array phase) "[~{~d~^ ~}] ~d d~%")
217
218 (def-pdf-op set-miter-limit (limit) "~8f M~%")
219
220 (def-pdf-op move-to (x y) "~8f ~8f m~%")
221
222 (def-pdf-op line-to (x y) "~8f ~8f l~%")
223
224 (def-pdf-op bezier-to (x1 y1 x2 y2 x3 y3) "~8f ~8f ~8f ~8f ~8f ~8f c~%")
225
226 (def-pdf-op bezier2-to (x2 y2 x3 y3) "~8f ~8f ~8f ~8f v~%")
227
228 (def-pdf-op bezier3-to (x1 y1 x3 y3) "~8f ~8f ~8f ~8f y~%")
229
230 (def-pdf-op close-path () " h")
231
232 (def-pdf-op basic-rect (x y dx dy) "~8f ~8f ~8f ~8f re~%")
233
234 (defun paint-image (image)
235   (format *page-stream* "~a Do~%" (name image)))
236
237 (def-pdf-op stroke () " S")
238
239 (def-pdf-op close-and-stroke () " s")
240
241 (def-pdf-op fill-path () " f")
242
243 (def-pdf-op close-and-fill () " h f")
244
245 (def-pdf-op even-odd-fill () " f*")
246
247 (def-pdf-op fill-and-stroke () " B")
248
249 (def-pdf-op even-odd-fill-and-stroke () " B*")
250
251 (def-pdf-op close-fill-and-stroke () " b")
252
253 (def-pdf-op close-even-odd-fill-and-stroke () " b*")
254
255 (def-pdf-op end-path-no-op  () " n")
256
257 (def-pdf-op clip-path () " W")
258
259 (def-pdf-op even-odd-clip-path () " W*")
260
261 (def-pdf-op set-gray-stroke (gray) "~5f G~%")
262
263 (def-pdf-op set-gray-fill (gray) "~5f g~%")
264
265 (def-pdf-op set-rgb-stroke (r g b) "~5f ~5f ~5f RG~%")
266
267 (defgeneric get-rgb (color)
268  (:method ((color list)) 
269   (values (first color)(second color)(third color)))
270
271  (:method ((color vector))
272   #+lispworks
273   (if (numberp (aref color 0))
274       (values (aref color 0)(aref color 1)(aref color 2))
275       (case (aref color 0)              ; convert from (color:make-rgb ...) or other model
276         (:RGB   (values (aref color 1)(aref color 2)(aref color 3)))
277         (:GRAY  (values (aref color 1)(aref color 1)(aref color 1)))))
278   #-lispworks
279   (values (aref color 0)(aref color 1)(aref color 2)))
280
281  (:method ((color string))      ; takes a CSS color string like "#CCBBFF"
282   (if (eql #\# (aref color 0))
283       (values (/ (parse-integer color :start 1 :end 3 :radix 16) 255.0)
284               (/ (parse-integer color :start 3 :end 5 :radix 16) 255.0)
285               (/ (parse-integer color :start 5 :end 7 :radix 16) 255.0))
286       (find-color-from-string color)))
287
288  (:method ((color integer))     ; a la CSS but specified as a Lisp number like #xCCBBFF
289   (values (/ (ldb (byte 8 16) color) 255.0)
290           (/ (ldb (byte 8 8) color) 255.0)
291           (/ (ldb (byte 8 0) color) 255.0)))
292
293  (:method ((color symbol))      ; :blue, :darkgreen, or win32:color_3dface
294    (find-color-from-symbol color)))
295
296 (defun set-color-stroke (color)
297   (multiple-value-call #'set-rgb-stroke (get-rgb color)))
298
299 (defun set-color-fill (color)
300   (multiple-value-call #'set-rgb-fill (get-rgb color)))
301
302 (def-pdf-op set-rgb-fill (r g b) "~5f ~5f ~5f rg~%")
303
304 (def-pdf-op set-cymk-stroke (c y m k) "~5f ~5f ~5f ~5f K~%")
305
306 (def-pdf-op set-cymk-fill (c y m k) "~5f ~5f ~5f ~5f k~%")
307
308 (defun draw-image (image x y dx dy rotation &optional keep-aspect-ratio)
309   (when keep-aspect-ratio
310     (let ((r1 (/ dy dx))
311           (r2 (/ (height image)(width image))))
312       (if (> r1 r2)
313         (setf dy (* dx r2)))
314         (when (< r1 r2)(setf dx (/ dy r2)))))
315   (with-saved-state
316       (translate x y)
317       (rotate rotation)
318       (scale dx dy)
319       (paint-image image)))
320
321 (defun add-link (x y dx dy ref-name &key (border #(0 0 0)))
322   (let ((annotation (make-instance 'annotation :rect (vector x y (+ x dx) (+ y dy))
323                                    :type "/Link" :border border)))
324     (push (cons "/Dest" (get-named-reference ref-name)) (dict-values (content annotation)))
325     annotation))
326
327 (defun add-URI-link (x y dx dy uri &key (border #(0 0 0)))
328   (let ((annotation (make-instance 'annotation :rect (vector x y (+ x dx) (+ y dy))
329                                    :type "/Link" :border border ))
330         (action (make-instance 'dictionary :dict-values '(("/S" . "/URI")))))
331     (add-dict-value (content annotation) "/A" action)
332     (add-dict-value action "/URI" (concatenate 'string "(" uri ")"))
333     annotation))
334
335 (defun add-external-link (x y dx dy filename page-nb &key (border #(0 0 0)))
336   (let ((annotation (make-instance 'annotation :rect (vector x y (+ x dx) (+ y dy))
337                                    :type "/Link" :border border ))
338         (action (make-instance 'dictionary :dict-values '(("/S" . "/GoToR")))))
339     (add-dict-value (content annotation) "/A" action)
340     (add-dict-value action "/F" (concatenate 'string "(" filename ")"))
341     (add-dict-value action "/D" (vector page-nb "/Fit"))
342     annotation))
343
344 (defparameter +jpeg-color-spaces+ #("?" "/DeviceGray" "?" "/DeviceRGB" "/DeviceCMYK"))
345
346 (defclass bitmap-image ()
347   ((width  :accessor width :initarg :width)
348    (height :accessor height :initarg :height)
349    (nb-components :accessor nb-components :initarg :nb-components)
350    (data   :accessor data :initarg :data)))
351
352 (defclass jpeg-image (bitmap-image)
353   ())
354
355 (defun %read-jpeg-file% (filename)
356   (with-open-file (s filename :direction :input :element-type '(unsigned-byte 8))
357     (loop with width and height and nb-components and data
358           for marker = (read-byte s)
359           if (= marker #xFF) do
360               (setf marker (read-byte s))
361               (cond
362                 ((member marker '(#xC0 #xC1 #xC2));SOF markers
363                  (read-byte s)(read-byte s) ;size
364                  (when (/= (read-byte s) 8) (error "JPEG must have 8bits per component"))
365                  (setf height (+ (ash (read-byte s) 8)(read-byte s)))
366                  (setf width (+ (ash (read-byte s) 8)(read-byte s)))
367                  (setf nb-components (read-byte s))
368                  (file-position s :start)
369                  (setf data (make-array (file-length s) :element-type '(unsigned-byte 8)))
370                  (read-sequence data s)
371                  (return (values nb-components width height data)))
372                 ((member marker '(#xC3 #xC5 #xC6 #xC7 #xC8 #xC9 #xCA #xCB #xCD #xCE #xCF)) ;unsupported markers
373                  (error "Unsupported JPEG format"))
374                 ((not (member marker '(#xD0 #xD1 #xD2 #xD3 #xD4 #xD5 #xD6 #xD7 #xD8 #x01))) ;no param markers
375                  (file-position s (+ (file-position s)(ash (read-byte s) 8)(read-byte s) -2)))))))
376
377 (defun read-jpeg-file (filename)
378   (multiple-value-bind (nb-components width height data) (%read-jpeg-file% filename)
379     (when nb-components
380       (make-instance 'jpeg-image :nb-components nb-components
381                      :width width :height height :data data))))
382
383 (defmethod make-jpeg-image (jpeg)
384   (make-image jpeg))
385
386 (defmethod make-jpeg-image ((pathname pathname))
387   (make-jpeg-image (read-jpeg-file pathname)))
388
389 (defmethod make-jpeg-image ((string string))
390   (make-jpeg-image (read-jpeg-file string)))
391
392 (defgeneric make-image (object &key type &allow-other-keys)
393  (:documentation "Returns more than just one pdf:image object when mask is supplied"))
394
395 (defmethod make-image ((object pathname) &rest args &key (type (pathname-type object)))
396   (cond ((member type '("jpeg" "jpg") :test #'equalp)
397          (apply 'make-image (read-jpeg-file object) args))
398         ((equalp type "png")
399          (apply 'make-image (read-png-file object) args))
400         (t (error "Unsupported image file type ~s." type))))
401
402 (defmethod make-image ((object string) &rest args &key type)
403   (apply #'make-image (merge-pathnames object (make-pathname :type type))
404          args))
405
406 (defmethod make-image ((jpeg jpeg-image) &key &allow-other-keys)
407   (make-instance 'pdf:image
408          :bits (data jpeg)
409          :width (width jpeg) :height (height jpeg)
410          :filter "/DCTDecode"
411          :color-space (aref +jpeg-color-spaces+ (nb-components jpeg))
412          :no-compression t))
Note: See TracBrowser for help on using the browser.