;;; cl-pdf copyright 2002-2003 Marc Battyani see license.txt for the details
;;; You can reach me at marc.battyani@fractalconcept.com or marc@battyani.net
;;; The homepage of cl-pdf is here: http://www.fractalconcept.com/asp/html/cl-pdf.html

;;;; Touched: <18-Nov-03 09:23:16 IST, madhu>
;;; pk file parsing by Suresh Madhu

;;;; vim: ft=lisp
;;;; Touched: <26-Sep-03 03:42:39 IST, madhu>
;;;;
;;;; PK files are well described in `web2c/pktype.web' in TeX sources.
;;;; This is a blind translation in CL of that idiomatic pascal program
;;;; and produces the exact same output as `pktype'. In addition, it also
;;;; computes raster info for encoding the font as Type 3, in pdf dicts
;;;;
;;;; Translated 2003 Madhu. Bugs-To: <enometh@net.meer>

(defpackage "PK"
  (:use "CL")
  (:export "PKTYPE"))
(in-package "PK")

(export '(GET-PKFILE PK-FONT-SIZE PK-FONT-SCALE PK-CHAR-WIDTH
                     PK-FONT-NAME PK-CHAR-DETAILS))

(defvar *pk-file-stream* nil) ; stream of (unsigned-byte 8) elements

(define-symbol-macro pk-byte (read-byte *pk-file-stream*))

(define-symbol-macro pk-loc (file-position *pk-file-stream*))

(define-symbol-macro get-16 ; 2 byte value
  (let ((a pk-byte) (b pk-byte))
     (declare (type (unsigned-byte 8) a b))
     (+ (* 256 a) b)))

(define-symbol-macro get-32 ; signed 4 byte value
  (let ((a get-16) (b get-16))
    (declare (type (unsigned-byte 16) a b))
    (+ (* 65536 (if (> a 32767) (- a 65536) a)) b)))

(defconstant +pk-id+ 	89 "version of .PK file described")

(defconstant +pk-pre+	247 "preamble (i[1] k[1] x[k] ds[4] cs[5] hppp[4]")

(defconstant +pk-xxx+ 	'(240 241 242 243))

(defconstant +pk-xxx1+	240 "\special commands (k[1] x[k])") ;k+2 byte no-op

;(defconstant +pk-xxx2+ 241 "\special commands (k[2] x[k])") ;0<=k<=65536

;(defconstant +pk-xxx3+ 242 "\special commands (k[3] x[k])") ;0<=k<2^{24}

;(defconstant +pk-xxx4+ 243 "\special commands (k[4] x[k])") ;k huge.

(defconstant +pk-yyy+	244 "\numspecial commands (y[4])")

(defconstant +pk-post+	245 "postamble") ;padded to 4-byte boundary with no-ops

(defconstant +pk-no-op+	246 "no operation")

;(defconstant +pk-undefined-ops+ '(248 249 250 251 252 253 254 255))

(defun say (&rest args) (apply #'format t args))

;; this speeds things up
#-nil
(defmacro say (&rest args) (declare (ignore args)))

(defvar *%font-dsize* nil)

(defvar *%resolution* nil)

(defun pk-read-preamble ()
  (unless (= pk-byte +pk-pre+) (error "Bad PK file: pre command missing!"))
  (unless (= pk-byte +pk-id+) (error "Wrong version of PK file!"))
  (let ((comment (loop with comment-length = pk-byte
		       repeat comment-length collect pk-byte)))
    (declare (ignorable comment))
    (say "~&'~a'~&" (map 'string #'code-char comment)))
  (let ((design-size get-32)) ; in 1/2^{20} points
    (declare (ignorable design-size)) ;XXX
    (setf *%font-dsize* (/ design-size 1048576.0))
    (say "Design size = ~a~&" design-size))
  (let ((check-sum get-32)) 
    (declare (ignorable check-sum))
    (say "Checksum = ~a~&" check-sum))
  (let ((hppp get-32) ; horizontal resolution, points per inch, and
	(vppp get-32)) ; vertical resolution; multiplied by 2^16
    (unless (= hppp vppp) (warn "aspect ratio not 1:1"))
    (setf *%resolution* (/ hppp 65535.0))
    (say "Resolution: horizontal = ~a  vertical = ~a  (~a dpi)~&"
	 hppp vppp (round (/ (* 72.27 hppp) 65535))))
  (values))

(defun pk-read-command (flagbyte) ; skip-specials
  (cond ((= flagbyte +pk-no-op+)
	 (say "~a:  No op~&" (1- pk-loc)))
	((= flagbyte +pk-yyy+)
	 (let ((c get-32)) 
	   (declare (ignorable c))
	   (say "~a:  Num Special: ~a~&" (1- pk-loc) c)));bug
	((member flagbyte +pk-xxx+ :test #'eql)
	 (say "~a:  Special: " (1- pk-loc))
	 (let ((k 0))
	   (loop for j from +pk-xxx1+ to flagbyte do
		 (setf k (+ (* k 256) pk-byte)))
	   (loop for j below k collecting pk-byte into bytes
		 finally (say "'~a'~&" (map 'string #'code-char bytes)))))
	(t (error "got ~a. expected one of ~a" flagbyte
		  (adjoin +pk-no-op+ (adjoin +pk-yyy+ +pk-xxx+))))))

(defun pk-read-postamble (flagbyte)
  (say "~a:  Postamble~&" (1- pk-loc)) (assert (= flagbyte +pk-post+))
  (handler-bind ((end-of-file (lambda (c) (declare (ignore c))
				(return-from pk-read-postamble :EOF))))
    (loop for i = pk-byte do
	  (if (= i +pk-no-op+)
	    (say "~a:  No op~&" (1- pk-loc))
	    (error "bad byte at end of file: ~a" i)))))

(defvar *bit-weight* 0 "weight of current bit")

(defvar *input-byte* 0 "byte currently being read")

(defvar *nybble* 0 "current nybble")

(define-symbol-macro get-nyb
  (progn
    (when (zerop *bit-weight*)
      (setf *input-byte* pk-byte)
      (setf *bit-weight* 16))
    (multiple-value-bind (n r) (floor *input-byte* *bit-weight*)
      (setf *input-byte* r)
      (setf *bit-weight* (floor (/ *bit-weight* 16)))
      n)))

(define-symbol-macro get-bit
  (progn
     (setf *bit-weight* (floor *bit-weight* 2))
     (when (= *bit-weight* 0)
       (setf *input-byte* pk-byte)
       (setf *bit-weight* 128))
     (let ((r (>= *input-byte* *bit-weight*)))
       (when r (decf *input-byte* *bit-weight*))
       r)))

(defclass chardesc () ; bastard struct. todo class-allocate the `local-vars'
  (car dx dy x-off y-off width height tfm-width
       turn-on flag-byte dyn-f
       packet-length end-of-packet
       raster rastersize p-bit p b rw))

(defun  read-long-character-preamble (ch)
  (with-slots (car dx dy x-off y-off width height tfm-width packet-length end-of-packet) ch
    (setf packet-length get-32)
    (setf car get-32) ; character code
    (setf end-of-packet (+ pk-loc packet-length))
    (incf packet-length 9)
    (setf tfm-width get-32)
    (setf dx get-32)
    (setf dy get-32)
    (setf width get-32)
    (setf height get-32)
    (setf x-off get-32)
    (setf y-off get-32)))

(defun read-extended-short-character-preamble (ch)
  (with-slots (car dx dy x-off y-off width height tfm-width packet-length end-of-packet flag-byte) ch
    (setf packet-length (+ (* 65536 (- flag-byte 4)) get-16))
    (setf car pk-byte)
    (setf end-of-packet (+ pk-loc packet-length))
    (incf packet-length 4)
    (let ((i pk-byte)) (setf tfm-width (+ (* i 65536) get-16)))
    (setf dx (* 65536 get-16))
    (setf dy 0)
    (setf width get-16)
    (setf height get-16)
    (setf x-off get-16)
    (setf y-off get-16)
    (if (> x-off 32767) (decf x-off 65536))
    (if (> y-off 32767) (decf y-off 65536))))

(defun read-short-char-preamble (ch)
  (with-slots (car dx dy x-off y-off width height tfm-width packet-length end-of-packet flag-byte) ch
    (setf packet-length (+ (* 256 flag-byte) pk-byte))
    (setf car pk-byte)
    (setf end-of-packet (+ pk-loc packet-length))
    (incf packet-length 3)
    (let ((i pk-byte)) (setf tfm-width (+ (* 65536 i) get-16)))
    (setf dx (* 65536 pk-byte))
    (setf dy 0)
    (setf width pk-byte)
    (setf height pk-byte)
    (setf x-off pk-byte)
    (setf y-off pk-byte)
    (if (> x-off 127) (decf x-off 256))
    (if (> y-off 127) (decf y-off 256))))

(defun %init-raster (ch)
  (with-slots (width height raster rastersize p-bit p b rw) ch
    (setf rw (floor (+ width 7) 8)) ; width (of `width') in bytes
    (setf rastersize (* height rw))
    (when (<= rastersize 0)
      (warn "hmm: rastersize=~a <=1" rastersize)
      (setf rastersize 1))
    (setf p-bit 7)  ; position of current bit in byte (for +power+)
    (setf p 0)      ; current index into `raster'
    (setf b 0)      ; current byte
    (setf raster (make-array rastersize :element-type '(unsigned-byte 16)))))

(defun %debug-raster (ch)
  (with-slots (height width raster p rw) ch
    (setf p 0)
    (format t "~&")
    (loop repeat height do
	  (loop repeat rw do
		(format t "~8,'0b" (aref raster p))
		(incf p))
	  (format t "~&"))))

(defconstant +power+ #(0 1 3 7 15 31 63 127 255))

(defmacro power (weight) `(aref +power+ ,weight))

(defun get-raster-by-bits (ch) ; not tested yet
  (with-slots (height width raster p-bit p b rastersize) ch
    (assert (zerop p))
    (loop repeat height do
	  (say " ")
	  (setf p-bit 7 b 0)
	  (loop repeat width for on = get-bit do
		(cond (on (incf b (1+ (power p-bit)))
			  (say "*"))
		      (t (say ".")))
		(decf p-bit)
		(when (zerop p-bit)
		  (setf (aref raster p) b) (incf p)
		  (setf p-bit 7 b 0)))
	  (unless (= p-bit 7)
	    (setf (aref raster p) b)) ; loop resets rest
	  (say "~%"))
    (assert (= p rastersize))))

(defvar *term-pos*)

(defun send-out (repeat-count-p value ch)
  (with-slots (turn-on) ch
    (let ((i 10) (len 1))
      (loop while (>= value i) do (incf len) (setf i (* i 10)))
      (when (or repeat-count-p (not turn-on))
	(incf len 2))
      (cond ((> (+ *term-pos* len) 78)
	     (setf *term-pos* (+ len 2))
	     (say " ~%  "))
	    (t (incf *term-pos* len)))
      (cond (repeat-count-p (say "[~D]" value))
	    (turn-on (say "~D" value))
	    (t (say "(~D)" value))))))

(defun pk-packed-num (ch)
  (with-slots (dyn-f turn-on) ch
    (let ((i get-nyb) j)
      (declare (special repeat-count))
      (cond ((zerop i)
	     (loop do (setf j get-nyb) (incf i) until (/= j 0))
	     (loop while (> i 0) do (setf j (+ (* j 16) get-nyb)) (decf i))
	     (+ j -15 (* (- 13 dyn-f) 16) dyn-f))
	    ((<= i dyn-f) i)
	    ((< i 14) (+ (* (- i dyn-f 1) 16) get-nyb dyn-f 1))
	    (t (unless (zerop repeat-count)
		 (error "Second repeat count for this row"))
	       (setf repeat-count 1)
	       (if (= i 14) (setf repeat-count (pk-packed-num ch)))
	       (send-out t repeat-count ch)
	       (pk-packed-num ch))))))

(defun  create-normally-packed-raster (ch)
  (setf *term-pos* 2)
  (say "  ")
  (with-slots (height width turn-on dyn-f turn-on raster p-bit p b rw) ch
    (let ((rows-left height) (h-bit width) (repeat-count 0))
      (declare (special repeat-count))
      (assert (= 0 b))
      (setf p-bit 8 b 0 p 0)
      (loop
	while (> rows-left 0) do
	(let ((count (pk-packed-num ch)))
	  (send-out nil count ch)
	  (loop while (> count 0) do
	      (cond ((and (< count p-bit) (< count h-bit))
		     (if turn-on (incf b (- (power p-bit) (power (- p-bit count)))))
		     (decf h-bit count)
		     (decf p-bit count)
		     (setf count 0))
		    ((and (>= count h-bit) (<= h-bit p-bit))
		     (if turn-on
		       (incf b (- (power p-bit) (power (- p-bit h-bit)))))
		     (setf (aref raster p) b) (incf p) (setf b 0)
		     (loop repeat repeat-count do
			   (loop repeat rw do
				 (setf (aref raster p) (aref raster (- p rw)))
				 (incf p)))
		     (decf rows-left (1+ repeat-count))
		     (setf repeat-count 0)
		     (decf count h-bit)
		     (setf p-bit 8)
		     (setf h-bit width))
		    (t
		      (if turn-on (incf b  (power p-bit)))
		      (setf (aref raster p) b) (incf p) (setf b 0)
		      (decf count p-bit)
		      (decf h-bit p-bit)
		      (setf p-bit 8))))
	  (setf turn-on (not turn-on))))
      (say " ~%")
      (unless (and (zerop rows-left) (= width h-bit))
	(error "Bad PK file: More bits than required!")))))

(defun read-and-translate-raster-description (ch)
  (setf *bit-weight* 0)
  (%init-raster ch)
  (with-slots (dyn-f) ch
    (if (= dyn-f 14)
      (get-raster-by-bits ch)
      (create-normally-packed-raster ch))))

(defvar *%chars* (make-array 256)) ; array of ALL chars (last call to PKTYPE)

(defun pk-read-char (flagbyte) ; unpack-and-write-character
  (say "~a:  Flag byte = ~a" (1- pk-loc) flagbyte)
  (let ((ch (make-instance 'chardesc)))
    (with-slots (turn-on flag-byte dyn-f) ch
      (multiple-value-bind (n r) (floor flagbyte 16)
	(setf dyn-f n) (setf flag-byte r))
      (when (setf turn-on (>= flag-byte 8))
	(decf flag-byte 8))
      (cond ((= flag-byte 7) (read-long-character-preamble ch))
	    ((> flag-byte 3) (read-extended-short-character-preamble ch))
	    (t (read-short-char-preamble ch))))
    (with-slots (packet-length dyn-f tfm-width dx dy height width x-off y-off car) ch
      (setf (aref *%chars* car) ch)
      ;; ^ this imposes limit on car code range (0 255)
      (say "  Character = ~a  Packet length = ~a~&" car packet-length)
      (say "  Dynamic packing variable = ~a~&" dyn-f)
      (say "  TFM width = ~a  dx = ~a ~@[  dy = ~a~]~&" tfm-width dx
	   (unless (zerop dy) dy))
      (say "  Height = ~a  Width = ~a  X-offset = ~a  Y-offset = ~a~&"
	   height width x-off y-off))
    (read-and-translate-raster-description ch)
    (with-slots (end-of-packet) ch
      (unless (= pk-loc end-of-packet)
	(error "Bad PK File: Bad packet length")))))

(defun pktype (file)
  (with-open-file (*pk-file-stream* file :direction :input :element-type '(unsigned-byte 8))
    (pk-read-preamble)
    (loop for flagbyte = pk-byte until (= flagbyte +pk-post+) do
	  (if (<= flagbyte 239)
	    (pk-read-char flagbyte) ; flagbyte introduced a char
	    (pk-read-command flagbyte)) ; flagbyte introduced a command
	  finally (pk-read-postamble flagbyte))))

#+nil
(pk:pktype  #p"skt/sktbs10.864pk")

;;; 
;;;
;;; afterthought: caching wrapper around pktype

(defclass pkfile() 
  ((font-size :accessor pk-font-size :initarg font-size)
   (font-name :accessor pk-font-name :initarg font-name)
   (resolution :accessor pk-resolution :initarg resolution) ;; points/inch
   (font-scale :accessor pk-font-scale)
   (chars :accessor chars :initarg chars)))

(defmethod initialize-instance :after ((pkfile pkfile) &rest initargs &key &allow-other-keys)
  (declare (ignore initargs))
  (setf (slot-value pkfile 'font-scale)
        (/ .01 (/ (pk-resolution pkfile) (pk-font-size pkfile)))))
                               
(defmethod pk-char-width ((pkfile pkfile) (car integer)) ; points/inch
  (check-type car (integer 0 256))
  (let ((tfm-width (/ (slot-value (aref (chars pkfile) car) 'tfm-width)
                      1048576.0)))
    (/ tfm-width (pk-font-scale pkfile))))
(let ((pkhash (make-hash-table :test #'equal))) ; XXX cache
(defmethod get-pkfile ((pkfile pathname)) 
  (let ((p (gethash (truename pkfile) pkhash)))
    (cond (p (warn "load-pk-font: ~s already loaded" pkfile) p)
          (t (let ((*%chars* (make-array 256)) *%font-dsize* *%resolution*)
               (pk:pktype pkfile)
               (setf (gethash (truename pkfile) pkhash)
                     (make-instance 'pkfile ; ugh! infer font name from file
                                    'font-name (pathname-name pkfile) ;XXX
                                    'font-size *%font-dsize*
                                    'resolution *%resolution*
                                    'chars *%chars*)))))))
); end closure over pkhash 

(defmethod pk-char-details ((pkfile pkfile) (car integer))
  ;; XXX return a list: specifically for destructuring
  (check-type car (integer 0 256))
  (with-slots (x-off y-off width height raster rastersize rw)
      (aref (chars pkfile) car)
    (list x-off y-off width height raster rastersize rw)))

