;; see blog post for documentation of what follows:
;;   http://nklein.com/2009/12/rendering-text-with-cl-opengl-and-zpb-ttf

(defun draw-quad (bx1 by1 bx2 by2)
  (gl:with-primitives :quads
    (gl:vertex bx1 by1)
    (gl:vertex bx2 by1)
    (gl:vertex bx2 by2)
    (gl:vertex bx1 by2)))

(defun make-interpolator (ss cc ee)
  (let ((xx (+ ss (* -2 cc) ee))
	(yy (* 2 (- cc ss))) 
	(zz ss))
    #'(lambda (tt)
	(+ (* xx tt tt) (* yy tt) zz))))

(defun interpolate (sx sy ex ey int-x int-y &optional (st 0) (et 1))
  (let ((mx (/ (+ sx ex) 2.0))
	(my (/ (+ sy ey) 2.0))
	(mt (/ (+ st et) 2.0)))
    (let ((nx (funcall int-x mt))
	  (ny (funcall int-y mt)))
      (let ((dx (- mx nx))
	    (dy (- my ny)))
	(when (< 1 (+ (* dx dx) (* dy dy)))
	  (interpolate sx sy nx ny int-x int-y st mt)
	  (gl:vertex nx ny)
	  (interpolate nx ny ex ey int-x int-y mt et))))))

(defun render-glyph (glyph mode)
  (zpb-ttf:do-contours (contour glyph)
    (gl:with-primitives mode
      (zpb-ttf:do-contour-segments (start ctrl end) contour
	(let ((sx (zpb-ttf:x start))
	      (sy (zpb-ttf:y start))
	      (cx (when ctrl (zpb-ttf:x ctrl)))
	      (cy (when ctrl (zpb-ttf:y ctrl)))
	      (ex (zpb-ttf:x end))
	      (ey (zpb-ttf:y end)))
	  (gl:vertex sx sy)
	  (when ctrl
	    (let ((int-x (make-interpolator sx cx ex))
		  (int-y (make-interpolator sy cy ey)))
	      (interpolate sx sy ex ey int-x int-y)))
	  (gl:vertex ex ey))))))

(defun render-string (string font-loader fill)
  (loop :for pos :from 0 :below (length string)
     :for cur = (zpb-ttf:find-glyph (aref string pos) font-loader)
     :for prev = nil :then cur
     :do (when prev
	   (gl:translate (- (zpb-ttf:kerning-offset prev cur font-loader)
			    (zpb-ttf:left-side-bearing cur))
			 0 0))
	 (render-glyph cur (if fill :polygon :line-strip))
         (gl:translate (zpb-ttf:advance-width cur) 0 0)))

(defun draw-string (font-loader string &key (size 48) (filled t))
  (gl:with-pushed-matrix
    (let* ((box (zpb-ttf:string-bounding-box string font-loader :kerning t))
	   (bx1 (aref box 0))
	   (by1 (aref box 1))
	   (bx2 (aref box 2))
	   (by2 (aref box 3)))

      (let ((ss (/ size (zpb-ttf:units/em font-loader))))
	(gl:scale ss ss 1))

      (gl:translate (/ (- bx1 bx2) 2) (/ (- by1 by2) 2) 0)

      (gl:with-pushed-attrib (:current-bit :color-buffer-bit :line-bit
					   :hint-bit :stencil-buffer-bit)
	;; antialias lines
      (gl:enable :blend)
      (gl:blend-func :src-alpha :one-minus-src-alpha)
      (gl:enable :line-smooth)
      (gl:hint :line-smooth-hint :nicest)
      (gl:with-pushed-matrix
	  (render-string string font-loader nil))

      (when filled
	;; fill stencil buffer with filled-in-glyph
	(gl:color-mask nil nil nil nil)
	(gl:enable :stencil-test)
	(gl:stencil-mask 1)
	(gl:clear-stencil 0)
	(gl:clear :stencil-buffer-bit)
	(gl:stencil-func :always 1 1)
	(gl:stencil-op :invert :invert :invert)
	(gl:with-pushed-matrix
	    (render-string string font-loader t))

	;; fill in area subject to stencil
	(gl:color-mask t t t t)
	(gl:stencil-func :equal 1 1)
	(draw-quad bx1 by1 bx2 by2))))))

