[cl-pdf-devel] Named destinations proposal and small amendments

Dmitri Ivanov divanov at aha.ru
Wed Oct 27 08:03:23 UTC 2004


Hello,

Below are:
1) Sketch of code implementing named destinations,
2) Suggestion of the single add-link function replacing multiple
add-<whatever>-link functions,
3) An example.

;=================================================

(defun pdf-name (obj &optional (prefix #\/))
;;; Helper (akin to pdf-string) to escape non-alphanumeric characters in
PDF names
  ;; by writing 2-digit hexadecimal code, preceded by the number sign
character (#).
  ;; CAUTION: PDF names are case-sensitive!
  (let ((string (if (stringp obj)
                    (if (and prefix (char= (schar obj 0) prefix))
                        (return-from pdf-name obj) ; PDF-ready
                        obj)
                    (princ-to-string obj))))
    (with-output-to-string (stream nil #-cmu :element-type #-cmu
(array-element-type string))
      (when prefix
        (write-char prefix stream))
      (dotimes (i (length string))
        (let ((char (schar string i)))
          (if (or (alphanumericp char)
                  (find char "-_." :test #'char=)) ; often used regular
chars
              (write-char char stream)
              (format stream "#~2,'0x" (char-code char))))))))

(defmacro enforce-/ (&rest names)
;;; Verify and prefix each name by / unless it is PDF-ready.
  `(setf ,@(loop for name in names collect name collect `(pdf-name ,name))))


(defun register-destination (destination &optional (display "/Fit") &rest
args)
;;; Register named destination as named object or string.
  (cond ((symbolp destination)
         ;; PDF 1.1: Include into the Dest entry of in the document's
Catalog dictionary.
         ;;      Limited by maxumim dictionary size.
         (let ((entry (pdf-name destination))
               (value (apply #'vector *page* display args))
               (dests (get-dict-value (content (catalog *document*))
"/Dests")))
           (if dests
               (add-dict-value dests entry value)
               (add-dict-value (content (catalog *document*)) "/Dests"
                               (setf dests (make-instance 'dictionary
:dict-values
                                                   `((,entry .
,value))))))))
        ((stringp destination)
         ;; PDF 1.2: Dests entry in the document's Names dictionary, located
via
         ;;      the Names entry in the document's Catalog dictionary.
         (error "Name trees are not implemeted!"))))

(defun add-link (x y dx dy &key reference destination uri filename
                                (border #(0 0 0)) (display "/Fit") args)
;;; "Generic" link adder.
  ;; Args: reference Named reference inside the current document
  ;;    destinaion Named destination in the current document or the remote filename:
  ;;   - either symbol designating named object;
  ;;   - or string through the name tree (not implemented);
  ;;   - or integer interpreted as a page number (for remote only?)
  ;; Other types of links are welcome.
  (let ((annotation (make-instance 'annotation
                :rect (vector x y (+ x dx) (+ y dy))
                 :type "/Link" :border border))
(action (cond (uri
                       (make-instance 'dictionary :dict-values '(("/S" .
"/URI"))))
                      (filename
                       (make-instance 'dictionary :dict-values '(("/S" .
"/GoToR")))))))
    (if action
        (add-dict-value (content annotation) "/A" action)
        ;; Named reference or destination in the current document
        (push (cons "/Dest" (cond (reference
                                   (get-named-reference reference))
                                  ((symbolp destination)
                                   (pdf-name destination))
                                  (t (error "not implemeted"))))
              (dict-values (content annotation))))
    (cond (uri
           (add-dict-value action "/URI" (pdf-string uri)))
          (filename
           (add-dict-value action "/F" (pdf-string filename))
           (add-dict-value action "/D"
             (cond ((numberp destination) ; page number
                       (apply #'vector destination display args))
                      ((symbolp destination)
                        (pdf-name destination))
                      (t (error "not implemeted"))))) )
    annotation))

;===================== Example ========================

(defun ex-dest (&optional (file #P"/tmp/ex-dest.pdf"))
  (pdf:with-document ()
   (let* ((helvetica (pdf:get-font "Helvetica")))
    (loop for i from 1 to 7
   do
          (pdf:with-page ()
            (pdf:with-outline-level ((format nil "Page ~d"
i)(pdf:register-page-reference))
              (pdf::register-destination (make-symbol (format nil
"top-of-page~d" i))
                                         "/FitH" 810)
              (pdf:in-text-mode
                (pdf:set-font helvetica 10)
                (pdf:move-text 50 800)
                (pdf:draw-text (format nil "go to next page ~d" (1+ i))))
              (pdf:add-link 50 800 80 10
                  :destination (make-symbol (format nil "top-of-page~d" (1+
i))))
              (pdf:in-text-mode
                (pdf:set-font helvetica 12)
                (pdf:move-text 400 800)
                (pdf:draw-text "cl-pdf: Named destiantions"))
              (pdf:in-text-mode
                (pdf:set-font helvetica 36)
                (pdf:move-text 100 600)
                (pdf:draw-text (format nil "Hello world - page ~d" i)))
              (pdf:in-text-mode
                (pdf:set-font helvetica 10)
                (pdf:move-text 50 50)
                (pdf:draw-text (format nil "go to previous page ~d" (1-
i))))
              (pdf:add-link 50 50 80 10
                  :destination (make-symbol (format nil "bottom-of-page~d"
(1- i))))
              (pdf::register-destination (make-symbol (format nil
"bottom-of-page~d" i))
                                         )))
          ))
    (pdf:write-document file)))

(ex-dest (current-pathname "ex-dest.pdf"))

;======================= End of Example =====================

Named destinations could benefit when using Acrobat Interapplication
Communication.
For example, in LWW, you can automate AcroReader via DDE as follows:

(setq fullpath "E:\\Projects\\cl-pdf\\examples\\ex-dest.pdf")
(win32:dde-execute-command* 'acroview 'control "DocOpen"
        `(,fullpath))
;; The first page is numbered 0.
(win32:dde-execute-command* 'acroview 'control "DocGoTo"
        `(,fullpath 0))
(win32:dde-execute-command* 'acroview 'control "DocGoToNameDest"
        `(,fullpath "top-of-page3"))
(win32:dde-execute-command* 'acroview 'control "DocGoToNameDest"
        `(,fullpath "bottom-of-page6"))
(win32:dde-execute-command* 'acroview 'control "DocClose"
        `(,fullpath))
--
Sincerely,
Dmitri Ivanov
lisp.ystok.ru





More information about the cl-pdf-devel mailing list