[cl-pdf-devel] Using zpb-ttf to load Unicode ttf files

Peter Heslin pj at heslin.eclipse.co.uk
Thu Mar 2 22:33:02 UTC 2006


A couple of weeks ago, Zach Beane announced the release of his zpb-ttf
library for reading TrueType Unicode font files.  cl-pdf already has
support for using these fonts, but the metrics have to be read in via
a "ufm" file, which has to be generated with a hacked version of
ttf2pt1 -- a less than ideal situation.

So I tried to get zpb-ttf to load the ttf font metrics.  I'll attach
the file I came up with, which now allows a pure Lisp solution to
using truetype fonts in cl-pdf.  I've tested it on just a few fonts,
but it seems to work fine.

Health warning: I don't have a deep understanding of all of the parts
of this code, much of which was munged around and adapted from various
parts of font-metrics.lisp.  It would be good if someone else who
understands the issues were to review it.

One thing I did notice is that zpb-ttf reports a different value for
the font's descender, as compared to the ufm file generated by
ttf2pt1.  Also, I used zpb-ttf's line-gap for (leading font-metric),
but that's a guess.

-- 
Peter Heslin (http://www.dur.ac.uk/p.j.heslin)
-------------- next part --------------
;;; Use the zpb-ttf library to load Unicode TrueType fonts into cl-pdf
;;; Copyright (c) 2006 Peter Heslin, All Rights Reserved

(in-package :pdf)

;; Missing: weight, char-width, version, mapping-scheme, esc-char,
;; character-set, vvector, fixed-v-p, cap-height, x-height.

;; Value of descender is different from that obtained via ufm file.
;; For leading should we use line-gap or the formula used for afm files?

;; Usage: (load-ttf-file "times.ttf")

(defun read-ttf-metrics (file)
  (zpb-ttf:with-font-loader (loader file)
    (let* ((font-metrics (make-instance 'ttu-font-metrics))
           (characters (characters font-metrics))
           (kernings (kernings font-metrics))
           (units (/ 1000 (zpb-ttf:units/em loader)))
           (italic-angle (* units (zpb-ttf:italic-angle loader)))
           (italic-sin (sin (/ (* pi italic-angle) -180)))
           (font-bbox  (map 'vector (lambda (x) (* 0.001 units x))
                            (zpb-ttf:bounding-box loader)))
           (min-code #xfffe)
           (max-code 0)
           (void-char (make-instance 'char-metrics :code -1 :name "VoidChar" :index 0 
                                     :width 0 :bbox #(0 0 0 0) :spacing 0))
           encoding-vector pdf-widths)

      (setf (gethash "VoidCharacter" characters) void-char)
    
      (dotimes (gid (zpb-ttf:glyph-count loader))
        (let ((g (zpb-ttf:index-glyph gid loader)))
          (when g
            (let* ((code (or (zpb-ttf:code-point g) -1))
                   (name (zpb-ttf:postscript-name g))
                   (width (* 0.001 units (zpb-ttf:advance-width g)))
                   (char-bbox (zpb-ttf:bounding-box g))
                   (bbox (if char-bbox
                             (map 'vector (lambda (x) (* units 0.001 x)) char-bbox)
                             font-bbox))
                   (urx (aref bbox 2))
                   (stroke-width (if bbox (if (zerop urx) width (* 0.001 urx)) 0))
                   (char-metrics
                    (make-instance 'char-metrics :code code :name name
                                   :index gid :width  width
                                   :bbox bbox :spacing (- width stroke-width)
                                   :left-italic-correction (if bbox (* italic-sin (aref bbox 3)) 0)
                                   :right-italic-correction (if bbox (* italic-sin (aref bbox 1)) 0))))
              (when name
                (setf (gethash name characters) char-metrics))
              
              ;; Code taken from read-ufm-file
              (when (and (<= 0 code #xfffe))
                (when (> code max-code) (setf max-code code))
                (when (< code min-code) (setf min-code code))
                (setf (aref (c2g font-metrics) (* 2 code))
                      (code-char (ldb (byte 8 8) gid))
                      (aref (c2g font-metrics) (+ (* 2 code) 1))
                      (code-char (ldb (byte 8 0) gid)))
                (vector-push-extend code (cid-widths font-metrics))
                (vector-push-extend (vector (round (* 1000 (width char-metrics))))
                                    (cid-widths font-metrics)))))))

      (flet ((register-kern-pair (name1 name2 dx dy)
               (let* ((char1 (gethash name1 characters))
                      (char2 (when char1 (gethash name2 characters))))
                 (when char2
                   (setf (gethash (cons char1 char2) kernings) (cons (* 0.001 dx) (* 0.001 dy)))))))
        (dolist (kern (zpb-ttf:all-kerning-pairs loader))
          (register-kern-pair (zpb-ttf:postscript-name (first kern))
                              (zpb-ttf:postscript-name (second kern))
                              (third kern) 0)))
        
      (setf (font-name font-metrics) (zpb-ttf:postscript-name loader)
            (full-name font-metrics) (zpb-ttf:full-name loader)
            (family-name font-metrics) (zpb-ttf:family-name loader)
            (underline-position font-metrics) (* 0.001 units (zpb-ttf:underline-position loader))
            (underline-thickness font-metrics) (* units 0.001 (zpb-ttf:underline-thickness loader))
            (italic-angle font-metrics) italic-angle
            (italic-sin font-metrics) italic-sin
            (fixed-pitch-p font-metrics) (zpb-ttf:fixed-pitch-p loader)
            (font-bbox font-metrics) font-bbox
            (notice font-metrics) (zpb-ttf:name-entry-value :copyright-notice loader)
            (ascender font-metrics) (* 0.001 units (zpb-ttf:ascender loader))
            (descender font-metrics) (* 0.001 (zpb-ttf:descender loader))
;            (leading font-metrics) (- 1 (descender font-metrics))
            (leading font-metrics) (zpb-ttf:line-gap loader)
            (encoding-scheme font-metrics) :unicode-encoding
            (characters font-metrics) characters
            (kernings font-metrics) kernings)

      ;; From read-ufm-file
      (setf encoding-vector (make-array (1+ max-code) :initial-element void-char)
            pdf-widths (make-array (1+ max-code) :initial-element 0))
      (iter (for (name char-metrics) in-hashtable (characters font-metrics))
            (for code = (code char-metrics))
            (when (<= min-code code max-code)
              (setf (aref encoding-vector code) char-metrics
                    (aref pdf-widths code) (round (* 1000 (width char-metrics))))))
      (setf (min-code font-metrics) min-code
            (max-code font-metrics) max-code
            (encoding-vector font-metrics) encoding-vector
            (pdf-widths font-metrics) pdf-widths
            (encoding-scheme font-metrics) :unicode-encoding
            (gethash (string-downcase (font-name font-metrics)) *font-metrics*) font-metrics)
      font-metrics)))

(defun load-ttf-file (ttf-file)
  (let ((ttffm (read-ttf-metrics ttf-file)))
    (with-open-file (in ttf-file :direction :input :element-type '(unsigned-byte 8))
      (setf (length1 ttffm)
            (file-length in)
            (binary-data ttffm)
            (make-array (length1 ttffm) :element-type '(unsigned-byte 8) :initial-element 0))
      (read-sequence (binary-data ttffm) in))
    ttffm))



More information about the cl-pdf-devel mailing list