emacs-pdf.el (16502B)
1 ;;; -*- lexical-binding: t -*- 2 ;;; 3 ;;; emacs-pdf.el 4 ;;; 5 ;;; Print buffer to PDF file. 6 ;;; 7 ;;; Copyright (C) 2020 Tomas Hlavaty <tom at logand dot com> 8 ;;; 9 ;;; License: GPLv3 or later 10 ;;; 11 ;;; Download: git clone https://logand.com/git/emacs-pdf.git 12 ;;; 13 ;;; Example configuration: 14 ;;; 15 ;;; (add-to-list 'load-path "~/git/emacs-pdf") 16 ;;; (require 'emacs-pdf) 17 ;;; 18 ;;; Example usage: 19 ;;; 20 ;;; M-x pdf-buffer or pdf-region in example.txt will create 21 ;;; example.txt.pdf file. 22 ;;; 23 ;;; Some variables can be customized in pdf and ps groups. 24 25 (require 'cl-lib) 26 (require 'printing) 27 28 (defcustom pdf-line-height-factor 1.2 29 "Specify the line height factor (line-height = factor * font-size)." 30 :type 'number 31 :group 'pdf) 32 33 (defcustom pdf-base-font '/Courier 34 "PDF base font." 35 :type '(choice 36 (const /Courier) 37 (const /Courier-Bold) 38 (const /Courier-Oblique) 39 (const /Courier-BoldOblique) 40 (const /Helvetica) 41 (const /Helvetica-Bold) 42 (const /Helvetica-Oblique) 43 (const /Helvetica-BoldOblique) 44 (const /Times-Roman) 45 (const /Times-Bold) 46 (const /Times-Italic) 47 (const /Times-BoldItalic)) 48 :group 'pdf) 49 50 (defcustom pdf-header 51 (list 'pdf-page-number "/" 'pdf-number-of-pages " " 'pdf-nondirpart) 52 "The items to display in the page header. 53 54 The value should be a list of strings and symbols. Strings are 55 inserted unchanged. For symbols with bound functions, the 56 function is called and should return a string to be inserted. 57 For symbols with bound values, the value should be a string to be 58 inserted. If symbols are unbounded, they are silently ignored. 59 60 Useful functions: 61 - pdf-dirpart 62 - pdf-iso8601-date 63 - pdf-nondirpart 64 - pdf-number-of-pages 65 - pdf-page-number 66 " 67 :type '(repeat (choice :menu-tag "Header" 68 :tag "Header" 69 string symbol)) 70 :group 'pdf) 71 72 (defcustom pdf-footer 73 (list 'pdf-iso8601-date " " 'pdf-dirpart) 74 "The items to display in the page footer. 75 76 The value should be a list of strings and symbols. Strings are 77 inserted unchanged. For symbols with bound functions, the 78 function is called and should return a string to be inserted. 79 For symbols with bound values, the value should be a string to be 80 inserted. If symbols are unbounded, they are silently ignored. 81 82 Useful functions: 83 - pdf-dirpart 84 - pdf-iso8601-date 85 - pdf-nondirpart 86 - pdf-number-of-pages 87 - pdf-page-number 88 " 89 :type '(repeat (choice :menu-tag "Footer" 90 :tag "Footer" 91 string symbol)) 92 :group 'pdf) 93 94 (defcustom pdf-end-regexp "^Local Variables:" 95 "Specify regexp which ends the printable part of the file. 96 97 As an example, it may be set to \"^Local Variables:\", in order to leave out 98 some special printing instructions from the actual print. Special printing 99 instructions may be appended to the end of the file just like any other 100 buffer-local variables. See section \"Local Variables in Files\" on Emacs 101 manual for more information: 102 103 (info \"(emacs) File Variables\") 104 105 It controls what actually gets printed and may be set to nil in which case 106 the end of the file ends the printable region." 107 :type '(choice (const :tag "No Delimiter" nil) 108 (regexp :tag "Delimiter Regexp")) 109 :group 'pdf) 110 111 ;; for eldoc 112 (defun pdf (&rest rest) `(pdf ,@rest)) 113 (defun pdf-xref (&rest kv) `(pdf-xref ,@kv)) 114 (defun pdf-stream (dic &rest rest) `(pdf-stream ,dic ,@rest)) 115 (defun pdf-obj (num ver one) `(pdf-obj ,num ,ver ,one)) 116 (defun pdf-dic (&rest kv) `(pdf-dic ,@kv)) 117 (defun pdf-vec (&rest rest) `(pdf-vec ,@rest)) 118 (defun pdf-ref (num ver) `(pdf-ref ,num ,ver)) 119 120 (defun insert-pdf (x) 121 "Serialize cons tree x as PDF octets into the current buffer." 122 (let (objs xrefpos) 123 (cl-labels ((rec (x) 124 (cl-etypecase x 125 (integer 126 (insert (format "\n%d" x))) 127 (symbol 128 (insert (format "\n%s" x))) 129 (string 130 (insert "\n(") 131 (cl-loop 132 for c across x 133 do (cl-case c 134 (?\\ (insert "\\\\")) 135 (?\( (insert "\\(")) 136 (?\) (insert "\\)")) 137 (t (insert c)))) 138 (insert ")")) 139 (real 140 (insert (format "\n%f" x))) 141 (cons 142 (cl-ecase (car x) 143 (pdf 144 (insert "%PDF-1.4") 145 (insert 146 (format "\n%%%c%c%c%c%c%c%c%c" 147 150 151 152 153 255 254 253 252)) 148 (mapc #'rec (cdr x)) 149 (insert "\nstartxref") 150 (insert (format "\n%d" xrefpos)) 151 (insert "\n%%EOF\n")) 152 (pdf-xref 153 (setq xrefpos (point)) 154 (insert "\nxref") 155 (insert (format "\n%d %d" 0 (1+ (length objs)))) 156 (insert "\n0000000000 65535 f\r") 157 (dolist (obj (setq objs (nreverse objs))) 158 (cl-destructuring-bind (a b pos &rest c) obj 159 (ignore a) 160 (insert (format "\n%010d %05d n\r" pos b)))) 161 (insert "\ntrailer") 162 (rec 163 `(pdf-dic /Size ,(1+ (length objs)) ,@(cdr x)))) 164 (pdf-stream 165 (cl-destructuring-bind (a &rest b) (cdr x) 166 (let ((x (with-temp-buffer 167 (set-buffer-multibyte nil) 168 (mapc #'rec b) 169 (buffer-string)))) 170 (rec (if a 171 `(/Length ,(length x) ,@a) 172 `(pdf-dic /Length ,(length x)))) 173 (insert "\nstream") 174 (insert x) 175 (insert "\nendstream")))) 176 (pdf-obj 177 (cl-destructuring-bind (a b &rest c) (cdr x) 178 (push `(,a ,b ,(point)) objs) 179 (insert (format "\n%d %d obj" a b)) 180 (mapc #'rec c) 181 (insert "\nendobj"))) 182 (pdf-dic 183 (insert "\n<<") 184 (mapc #'rec (cdr x)) 185 (insert "\n>>")) 186 (pdf-vec 187 (insert "\n[") 188 (mapc #'rec (cdr x)) 189 (insert "\n]")) 190 (pdf-ref 191 (cl-destructuring-bind (a b) (cdr x) 192 (insert (format "\n%d %d R" a b))))))))) 193 (set-buffer-multibyte nil) 194 (rec x)))) 195 196 (defun pdf-brook-collect (brook) 197 "Collect all values pulled from brook." 198 (cl-loop 199 with z = nil 200 while (setq z (funcall brook)) 201 collect z)) 202 203 (defun pdf-brook-appending (brook) 204 "Append all values pulled from brook." 205 (cl-loop 206 with z = nil 207 while (setq z (funcall brook)) 208 appending z)) 209 210 (defun pdf-brook-count (brook) 211 "Count all values in brook." 212 (cl-loop 213 with z = nil 214 while (setq z (funcall brook)) 215 count z)) 216 217 (defun pdf-brook (x) 218 "Make new brook from x." 219 (cl-etypecase x 220 (list 221 (lambda () 222 (pop x))))) 223 224 ;;(pdf-brook-collect (pdf-brook '(1 2 3 4))) 225 ;;(pdf-brook-count (pdf-brook '(1 2 3 4))) 226 227 (defun pdf-flat-brook (&rest brooks) 228 "Compose brooks left to right, depth-first." 229 (lambda () 230 (cl-block loop 231 (while brooks 232 (let ((z (funcall (car brooks)))) 233 (cond 234 ((functionp z) (push z brooks)) 235 (z (cl-return-from loop z)) 236 (t (pop brooks)))))))) 237 238 ;;(pdf-brook-collect (pdf-flat-brook (pdf-brook '(1 2 3)) (pdf-brook '(4 5 6)))) 239 240 (defun pdf-source-line-brook (string) 241 "Make brook from string. Elements are string per line or 'pagebreak." 242 (pdf-flat-brook 243 (let ((pages (pdf-brook (split-string string "")))) 244 (lambda () 245 (let ((page (funcall pages))) 246 (when page 247 (pdf-brook (list (pdf-brook (split-string page "\n")) 248 (pdf-brook '(pagebreak)))))))))) 249 250 ;;(pdf-brook-collect (pdf-source-line-brook "1\n23\n4\n5")) 251 252 (defun pdf-source-page-brook (brook) 253 "Make brook for single page from brook." 254 (lambda () 255 (let ((z (funcall brook))) 256 (when z 257 (unless (eq 'pagebreak z) 258 z))))) 259 260 ;;(pdf-brook-collect (pdf-source-page-brook (pdf-source-line-brook "1\n23\n4\n5"))) 261 262 (defvar *pdf-page-number*) 263 (defun pdf-page-number () 264 "Return current page number as string. Useful in document 265 header or footer." 266 (format "%s" *pdf-page-number*)) 267 268 (defvar *pdf-number-of-pages*) 269 (defun pdf-number-of-pages () 270 "Return number of pages as string. Useful in document header 271 or footer." 272 (format "%s" *pdf-number-of-pages*)) 273 274 (defvar *pdf-file-name*) 275 (defun pdf-dirpart () 276 "Return directory part as string. Useful in document header or 277 footer." 278 (and *pdf-file-name* (file-name-directory *pdf-file-name*))) 279 (defun pdf-nondirpart () 280 "Return non-directory part as string. Useful in document header or 281 footer." 282 (and *pdf-file-name* (file-name-nondirectory *pdf-file-name*))) 283 284 (defun pdf-iso8601-date () 285 "Return current data as ISO8601 string. Useful in document 286 header or footer." 287 (format-time-string "%Y-%m-%d")) 288 289 (defun pdf-header-or-footer-text (i n file-name list) 290 "Insert document header or footer specified in list." 291 (with-temp-buffer 292 (let ((*pdf-page-number* i) 293 (*pdf-number-of-pages* n) 294 (*pdf-file-name* file-name)) 295 (dolist (x list) 296 (when x 297 (insert 298 (cl-etypecase x 299 (function (or (funcall x) "")) 300 (symbol (or (symbol-value x) "")) 301 (string x)))))) 302 (buffer-string))) 303 304 (defun pdf-header-text (i n file-name) 305 "Insert document header." 306 (pdf-header-or-footer-text i n file-name pdf-header)) 307 308 (defun pdf-footer-text (i n file-name) 309 "Insert document footer." 310 (pdf-header-or-footer-text i n file-name pdf-footer)) 311 312 (defun pdf-line (x y line) 313 "Represent PDF line as list of PDF drawing primitives." 314 `(1 0 0 1 ,x ,y Tm ,line Tj)) 315 316 (defun pdf-page-text (lines x0 y0 line-height header footer) 317 "Represent PDF page as list of PDF drawing primitives." 318 (pdf-brook-appending 319 (let* ((page (pdf-source-page-brook lines)) 320 (bottom-margin (+ ps-bottom-margin 321 (if footer (+ line-height ps-footer-offset) 0))) 322 (y y0)) 323 (lambda () 324 (when (<= bottom-margin (cl-decf y line-height)) 325 (let ((line (funcall page))) 326 (when line 327 `(,@(when header 328 (prog1 (pdf-line x0 y header) 329 (setq header nil) 330 (cl-decf y (+ line-height ps-header-offset)))) 331 ,@(when footer 332 (prog1 (pdf-line x0 ps-bottom-margin footer) 333 (setq footer nil))) 334 ,@(pdf-line x0 y line))))))))) 335 336 (defun pdf-page-dimensions () 337 "Return values of page width and height depending on 338 ps-paper-type and ps-landscape-mode." 339 (let ((x (cdr (assq ps-paper-type ps-page-dimensions-database)))) 340 (if ps-landscape-mode 341 (cl-values (cadr x) (car x)) 342 (cl-values (car x) (cadr x))))) 343 344 ;;(pdf-page-dimensions) 345 346 (defun pdf-pages-brook (substring x0 y0 line-height font-size 347 npages file-name parent oid !ref) 348 "Make brook of PDF objects per page." 349 (let ((lines (pdf-source-line-brook substring)) 350 (i 0)) 351 (lambda () 352 (cl-incf i) 353 (let ((text (pdf-page-text lines x0 y0 line-height 354 (when ps-print-header 355 (pdf-header-text i npages file-name)) 356 (when ps-print-footer 357 (pdf-footer-text i npages file-name))))) 358 (when text 359 (let ((oid1 (funcall oid)) 360 (oid2 (funcall oid))) 361 (funcall !ref `(pdf-ref ,oid2 0)) 362 `((pdf-obj ,oid1 363 0 364 (pdf-stream nil 365 BT 366 /F1 ,font-size Tf 367 1 0 0 1 ,x0 ,y0 Tm 368 ,@text 369 ET)) 370 (pdf-obj ,oid2 371 0 372 (pdf-dic /Type /Page 373 /Parent ,parent 374 /Resources (pdf-ref 2 0) 375 /Contents (pdf-ref ,oid1 0)))))))))) 376 377 (defun pdf-npages (substring x0 y0 line-height font-size file-name) 378 "Count number of pages in the document." 379 (pdf-brook-count 380 (pdf-pages-brook substring x0 y0 line-height font-size 0 file-name nil 381 (lambda () 0) 382 (lambda (x) (ignore x))))) 383 384 (defun pdf-region (from to &optional file-name) 385 "Save region to PDF file." 386 (interactive "r") 387 (cl-multiple-value-bind (page-width page-height) (pdf-page-dimensions) 388 (let* ((coding-system-for-write 'raw-text-unix) 389 (source-file-name (or file-name 390 (buffer-file-name) 391 (buffer-name))) 392 (file-name (concat source-file-name ".pdf")) 393 (x0 ps-left-margin) 394 (y0 (- page-height ps-top-margin)) 395 (font-size (cl-etypecase ps-font-size 396 (number ps-font-size) 397 (cons (if ps-landscape-mode 398 (car ps-font-size) 399 (cdr ps-font-size))))) 400 (line-height (* pdf-line-height-factor font-size)) 401 (substring (buffer-substring-no-properties 402 from 403 (or (when pdf-end-regexp 404 (save-excursion 405 (goto-char (point-min)) 406 (when (re-search-forward pdf-end-regexp to 'noerror) 407 (min to (match-beginning 0))))) 408 to))) 409 (npages (when (or (when ps-print-header 410 (member 'pdf-number-of-pages pdf-header)) 411 (when ps-print-footer 412 (member 'pdf-number-of-pages pdf-footer))) 413 (pdf-npages substring x0 y0 line-height font-size 414 source-file-name)))) 415 (with-temp-buffer 416 (insert-pdf 417 (let ((oid 0) 418 %pages 419 (%parent `(pdf-ref -1 0))) 420 `(pdf 421 (pdf-obj ,(cl-incf oid) 422 0 423 (pdf-dic /Type /Font 424 /Subtype /Type1 425 /Name /F1 426 /BaseFont ,pdf-base-font)) 427 (pdf-obj ,(cl-incf oid) 428 0 429 (pdf-dic /ProcSet (pdf-vec /PDF /Text) 430 /Font (pdf-dic /F1 (pdf-ref ,(1- oid) 0)))) 431 ,@(pdf-brook-appending 432 (pdf-pages-brook substring x0 y0 line-height font-size npages 433 source-file-name 434 %parent 435 (lambda () (cl-incf oid)) 436 (lambda (x) (push x %pages)))) 437 (pdf-obj ,(setf (cadr %parent) (cl-incf oid)) 438 0 439 (pdf-dic /Type /Pages 440 /Count ,(length %pages) 441 /Kids (pdf-vec ,@(nreverse %pages)) 442 /MediaBox (pdf-vec 0 0 ,page-width ,page-height))) 443 (pdf-obj ,(cl-incf oid) 444 0 445 (pdf-dic /Type /Catalog 446 /Pages (pdf-ref ,(1- oid) 0))) 447 (pdf-xref /Root (pdf-ref ,oid 0))))) 448 (write-region (point-min) (point-max) file-name))))) 449 450 (defun pdf-buffer (&optional file-name) 451 "Save buffer to PDF file." 452 (interactive) 453 (pdf-region (point-min) (point-max) file-name)) 454 455 (provide 'emacs-pdf)