mirror of
https://github.com/goose121/clx-truetype.git
synced 2026-09-10 07:16:25 -04:00
706 lines
34 KiB
Common Lisp
706 lines
34 KiB
Common Lisp
;;;; clx-truetype.lisp
|
|
|
|
(in-package #:clx-truetype)
|
|
|
|
(defclass font ()
|
|
((family :type string :initarg :family :accessor font-family :documentation "Font family.")
|
|
(subfamily :type string :initarg :subfamily :accessor font-subfamily :documentation "Font subfamily. For e.g. regular, italic, bold, bold italib.")
|
|
(size :type real :initarg :size :accessor font-size :initform 12 :documentation "Font size in points.")
|
|
(underline :type boolean :initarg :underline :initform nil :accessor font-underline :documentation "Draw line under text string.")
|
|
(strikethrough :type boolean :initarg :strikethrough :initform nil :accessor font-strikethrough :documentation "Draw strike through text string.")
|
|
(overline :type boolean :initarg :overline :initform nil :accessor font-overline :documentation "Draw line over text string.")
|
|
(background :initarg :background :initform nil :accessor font-background :documentation "Background color.")
|
|
(foreground :initarg :foreground :initform nil :accessor font-foreground :documentation "Foreground color.")
|
|
(overwrite-gcontext :type boolean :initarg :overwrite-gcontext :initform nil
|
|
:accessor font-overwrite-gcontext :documentation "Use font values for background and foreground colors.")
|
|
(antialias :type boolean :initarg :antialias :initform t :accessor font-antialias :documentation "Antialias text string.")
|
|
(string-bboxes :type cacle:cache :accessor font-string-bboxes
|
|
:documentation "Cache for text bboxes")
|
|
(string-line-bboxes :type cacle:cache :accessor font-string-line-bboxes
|
|
:documentation "Cache for text line bboxes")
|
|
(string-alpha-maps :type cacle:cache :accessor font-string-alpha-maps
|
|
:documentation "Cache for text alpha maps")
|
|
(string-line-alpha-maps :type cacle:cache :accessor font-string-line-alpha-maps
|
|
:documentation "Cache for text line alpha maps"))
|
|
(:documentation "Class for representing font information."))
|
|
|
|
(defun check-valid-font-families (family subfamily)
|
|
(when (or (null (gethash family *font-cache*))
|
|
(null (gethash subfamily (gethash family *font-cache*))))
|
|
(error "Font is not found: ~A ~A" family subfamily)))
|
|
|
|
(defmethod initialize-instance :before
|
|
((instance font) &rest initargs &key family subfamily &allow-other-keys)
|
|
(declare (ignorable initargs))
|
|
(check-valid-font-families family subfamily))
|
|
|
|
(defun make-font-cache (font dpi-cache-size string-cache-size inner-provider)
|
|
(flet ((outer-provider (dpi-cons)
|
|
(values
|
|
(cacle:make-cache
|
|
string-cache-size
|
|
(lambda (string)
|
|
(values
|
|
(funcall inner-provider (car dpi-cons) (cdr dpi-cons) font string)
|
|
(length string)))
|
|
:test #'equal
|
|
:policy :lfu)
|
|
1)))
|
|
(cacle:make-cache
|
|
dpi-cache-size
|
|
#'outer-provider
|
|
:test #'equal
|
|
:policy :lfu)))
|
|
|
|
(defun font-cache-fetch (cache dpi string)
|
|
(cacle:cache-fetch (cacle:cache-fetch cache dpi) string))
|
|
|
|
(defmethod initialize-instance :after
|
|
((font font) &key (dpi-cache-size 10) (string-cache-size 1000) &allow-other-keys)
|
|
(setf
|
|
(font-string-bboxes font)
|
|
(make-font-cache font dpi-cache-size string-cache-size 'text-bounding-box-provider)
|
|
(font-string-line-bboxes font)
|
|
(make-font-cache font dpi-cache-size string-cache-size 'text-line-bounding-box-provider)
|
|
(font-string-alpha-maps font)
|
|
(make-font-cache font dpi-cache-size string-cache-size 'text-pixarray-provider)
|
|
(font-string-line-alpha-maps font)
|
|
(make-font-cache font dpi-cache-size string-cache-size 'text-line-pixarray-provider)))
|
|
|
|
(defmethod (setf font-family) :before
|
|
(family (instance font))
|
|
(check-valid-font-families family (font-subfamily instance)))
|
|
|
|
(defmethod (setf font-subfamily) :before
|
|
(subfamily (instance font))
|
|
(check-valid-font-families (font-family instance) subfamily))
|
|
|
|
(defmethod (setf font-family) :after
|
|
(family (font font))
|
|
(cacle:cache-flush (font-string-bboxes font))
|
|
(cacle:cache-flush (font-string-line-bboxes font)))
|
|
|
|
(defmethod (setf font-subfamily) :after
|
|
(subfamily (font font))
|
|
(cacle:cache-flush (font-string-bboxes font))
|
|
(cacle:cache-flush (font-string-line-bboxes font)))
|
|
|
|
(defmethod (setf font-size) :after (value (font font))
|
|
(cacle:cache-flush (font-string-bboxes font))
|
|
(cacle:cache-flush (font-string-line-bboxes font)))
|
|
|
|
(defmethod (setf font-underline) :after (value (font font))
|
|
(cacle:cache-flush (font-string-bboxes font)))
|
|
|
|
(defmethod (setf font-overline) :after (value (font font))
|
|
(cacle:cache-flush (font-string-bboxes font)))
|
|
|
|
(defgeneric font-equal (font1 font2)
|
|
(:documentation "Returns t if two font objects are equal, else returns nil.")
|
|
(:method ((font1 font) (font2 font))
|
|
(and (string-equal (font-family font1)
|
|
(font-family font2))
|
|
(string-equal (font-subfamily font1)
|
|
(font-subfamily font2))
|
|
(= (font-size font1) (font-size font2))
|
|
(eql (font-underline font1) (font-underline font2))
|
|
(eql (font-strikethrough font1) (font-strikethrough font2))
|
|
(eql (font-overline font1) (font-overline font2))
|
|
(equal (font-background font1) (font-background font2))
|
|
(equal (font-foreground font1) (font-foreground font2))
|
|
(eql (font-overwrite-gcontext font1) (font-overwrite-gcontext font2))
|
|
(eql (font-antialias font1) (font-antialias font2)))))
|
|
|
|
(defmethod print-object ((instance font) stream)
|
|
"Pretty printing font object"
|
|
(with-slots (family subfamily underline strikethrough
|
|
overline background foreground overwrite-gcontext
|
|
antialias)
|
|
instance
|
|
(if *print-readably*
|
|
(format stream
|
|
"#.(~S '~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S)"
|
|
'cl:make-instance 'font
|
|
:family family :subfamily subfamily :underline underline
|
|
:strikethrough strikethrough
|
|
:overline overline :background background :foreground foreground
|
|
:overwrite-gcontext overwrite-gcontext
|
|
:antialias antialias)
|
|
(format stream
|
|
"#<'~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S>"
|
|
'font
|
|
:family family :subfamily subfamily :underline underline
|
|
:strikethrough strikethrough
|
|
:overline overline :background background :foreground foreground
|
|
:overwrite-gcontext overwrite-gcontext
|
|
:antialias antialias))))
|
|
|
|
;;; ZPB-TTF font objects cache
|
|
(defun get-font-pathname (font)
|
|
(gethash (font-subfamily font) (gethash (font-family font) *font-cache*)))
|
|
|
|
(defvar *font-loader-cache* (make-hash-table :test 'equal))
|
|
|
|
(defmacro with-font-loader ((loader font) &body body)
|
|
(let ((exists-p (gensym))
|
|
(font-path (gensym)))
|
|
`(let ((,font-path (get-font-pathname ,font)))
|
|
(multiple-value-bind (,loader ,exists-p)
|
|
(gethash ,font-path *font-loader-cache*)
|
|
(unless ,exists-p
|
|
(setf ,loader (setf (gethash ,font-path *font-loader-cache*)
|
|
(zpb-ttf:open-font-loader ,font-path))))
|
|
,@body))))
|
|
|
|
;;; Screen DPI
|
|
(defun screen-default-dpi (screen)
|
|
"Returns default dpi for @var{screen}. pixel width * 25.4/millimeters width"
|
|
(values (floor (* (xlib:screen-width screen) 25.4)
|
|
(xlib:screen-width-in-millimeters screen))
|
|
(floor (* (xlib:screen-height screen) 25.4)
|
|
(xlib:screen-height-in-millimeters screen))))
|
|
|
|
(defun screen-dpi (screen)
|
|
"Returns current dpi for @var{screen}."
|
|
(values (getf (xlib:screen-plist screen) :dpi-x
|
|
(floor (* (xlib:screen-width screen) 25.4)
|
|
(xlib:screen-width-in-millimeters screen)))
|
|
(getf (xlib:screen-plist screen) :dpi-y
|
|
(floor (* (xlib:screen-height screen) 25.4)
|
|
(xlib:screen-height-in-millimeters screen)))))
|
|
|
|
(defun (setf screen-dpi) (value screen)
|
|
"Sets current dpi for @var{screen}."
|
|
(setf (getf (xlib:screen-plist screen) :dpi-x) value
|
|
(getf (xlib:screen-plist screen) :dpi-y) value))
|
|
|
|
;;; Font metrics
|
|
(defun font-units->pixels-x (dpi-x font)
|
|
"px = funits*coeff. Function returns coeff."
|
|
(with-font-loader (loader font)
|
|
(with-slots (size) font
|
|
(let* ((units/em (zpb-ttf:units/em loader))
|
|
(pixel-size-x (* size (/ dpi-x 72))))
|
|
(* pixel-size-x (/ units/em))))))
|
|
|
|
(defun font-units->pixels-y (dpi-y font)
|
|
"px = funits*coeff. Function returns coeff."
|
|
(with-font-loader (loader font)
|
|
(with-slots (size) font
|
|
(let* ((units/em (zpb-ttf:units/em loader))
|
|
(pixel-size-y (* size (/ dpi-y 72))))
|
|
(* pixel-size-y (/ units/em))))))
|
|
|
|
(defun font-ascent-for-dpi (dpi-y font)
|
|
(with-font-loader (loader font)
|
|
(ceiling (* (font-units->pixels-y dpi-y font) (zpb-ttf:ascender loader)))))
|
|
|
|
(defun font-descent-for-dpi (dpi-y font)
|
|
(with-font-loader (loader font)
|
|
(floor (* (font-units->pixels-y dpi-y font) (zpb-ttf:descender loader)))))
|
|
|
|
(defun font-ascent (drawable font)
|
|
"Returns ascent of @var{font}. @var{drawable} must be window, pixmap or screen."
|
|
(font-ascent-for-dpi (nth-value 1 (screen-dpi (drawable-screen drawable))) font))
|
|
|
|
(defun font-descent (drawable font)
|
|
"Returns descent of @var{font}. @var{drawable} must be window, pixmap or screen."
|
|
(font-descent-for-dpi (nth-value 1 (screen-dpi (drawable-screen drawable))) font))
|
|
|
|
(defun font-line-gap (drawable font)
|
|
"Returns line gap of @var{font}. @var{drawable} must be window, pixmap or screen."
|
|
(with-font-loader (loader font)
|
|
(ceiling (* (font-units->pixels-y drawable font) (zpb-ttf:line-gap loader)))))
|
|
|
|
;;; baseline-to-baseline = ascent - descent + line gap
|
|
(defun baseline-to-baseline (drawable font)
|
|
"Returns distance between baselines of @var{font}. @var{drawable} must be window, pixmap or screen. ascent - descent + line gap"
|
|
(+ (font-ascent drawable font) (- (font-descent drawable font))
|
|
(font-line-gap drawable font)))
|
|
|
|
(defun text-bounding-box-provider (dpi-x dpi-y font string)
|
|
(with-font-loader (loader font)
|
|
(let* ((bbox
|
|
(zpb-ttf:string-bounding-box string loader))
|
|
(units->pixels-x (font-units->pixels-x dpi-x font))
|
|
(units->pixels-y (font-units->pixels-y dpi-y font))
|
|
(xmin (zpb-ttf:xmin bbox))
|
|
(ymin (zpb-ttf:ymin bbox))
|
|
(xmax (zpb-ttf:xmax bbox))
|
|
(ymax (zpb-ttf:ymax bbox)))
|
|
(when (font-underline font)
|
|
(setf ymin (min ymin (- (zpb-ttf:underline-position loader)
|
|
(zpb-ttf:underline-thickness loader)))))
|
|
(when (font-overline font)
|
|
(setf ymax (max ymax (+ (zpb-ttf:ascender loader)
|
|
(zpb-ttf:underline-position loader)
|
|
(+ (zpb-ttf:underline-thickness loader))))))
|
|
(vector (floor (* xmin
|
|
units->pixels-x))
|
|
(floor (* ymin
|
|
units->pixels-y))
|
|
(ceiling (* xmax
|
|
units->pixels-x))
|
|
(ceiling (* ymax
|
|
units->pixels-y))))))
|
|
|
|
(defun text-bounding-box (drawable font string &key start end)
|
|
"Returns text bounding box. @var{drawable} must be window, pixmap or screen. Text bounding box is only for contours. Bounding box for space (#x20) is zero."
|
|
(when (and start end)
|
|
(setf string (subseq string start end)))
|
|
(font-cache-fetch (font-string-bboxes font)
|
|
(multiple-value-call #'cons (screen-dpi (drawable-screen drawable)))
|
|
string))
|
|
|
|
(defun text-width (drawable font string &key start end)
|
|
"Returns width of text bounding box. @var{drawable} must be window, pixmap or screen."
|
|
(when (and start end)
|
|
(setf string (subseq string start end)))
|
|
(let ((bbox (text-bounding-box drawable font string)))
|
|
(- (xmax bbox) (xmin bbox))))
|
|
|
|
(defun text-height (drawable font string &key start end)
|
|
"Returns height of text bounding box. @var{drawable} must be window, pixmap or screen."
|
|
(when (and start end)
|
|
(setf string (subseq string start end)))
|
|
(let ((bbox (text-bounding-box drawable font string)))
|
|
(- (ymax bbox) (ymin bbox))))
|
|
|
|
(defvar *allow-fixed-pitch-p* t
|
|
"Allow shortcutting fixed-pitch fonts. Somewhat faster, but breaks CJK
|
|
in fixed-pitch fonts.")
|
|
|
|
(defun text-line-bounding-box-provider (dpi-x dpi-y font string)
|
|
(with-font-loader (loader font)
|
|
(let* ((units->pixels-x (font-units->pixels-x dpi-x font))
|
|
(xmin 0)
|
|
(ymin (font-descent-for-dpi dpi-y font))
|
|
(ymax (font-ascent-for-dpi dpi-y font))
|
|
(string-length (length string))
|
|
(xmax (if (> string-length 0)
|
|
(zpb-ttf:advance-width (zpb-ttf:find-glyph (elt string 0) loader))
|
|
0)))
|
|
(if (and *allow-fixed-pitch-p* (zpb-ttf:fixed-pitch-p loader))
|
|
(setf xmax (* xmax string-length))
|
|
(do ((i 1 (1+ i)))
|
|
((>= i string-length))
|
|
(incf xmax
|
|
(+ (zpb-ttf:advance-width (zpb-ttf:find-glyph (elt string i) loader))
|
|
(zpb-ttf:kerning-offset (elt string (1- i)) (elt string i) loader)))))
|
|
(vector (floor (* xmin units->pixels-x))
|
|
ymin
|
|
(ceiling (* xmax
|
|
units->pixels-x))
|
|
ymax))))
|
|
|
|
(defun text-line-bounding-box (drawable font string &key start end)
|
|
"Returns text line bounding box. @var{drawable} must be window, pixmap or screen. Text line bounding box is bigger than text bounding box. It's height is ascent + descent, width is sum of advance widths minus sum of kernings."
|
|
(when (and start end)
|
|
(setf string (subseq string start end)))
|
|
(font-cache-fetch (font-string-line-bboxes font)
|
|
(multiple-value-call #'cons (screen-dpi (drawable-screen drawable)))
|
|
string))
|
|
|
|
(defun text-line-width (drawable font string &key start end)
|
|
"Returns width of text line bounding box. @var{drawable} must be window, pixmap or screen. It is sum of advance widths minus sum of kernings."
|
|
(when (and start end)
|
|
(setf string (subseq string start end)))
|
|
(let ((bbox (text-line-bounding-box drawable font string)))
|
|
(- (xmax bbox) (xmin bbox))))
|
|
|
|
(defun text-line-height (drawable font string &key start end)
|
|
"Returns height of text line bounding box. @var{drawable} must be window, pixmap or screen."
|
|
(when (and start end)
|
|
(setf string (subseq string start end)))
|
|
(let ((bbox (text-line-bounding-box drawable font string)))
|
|
(- (ymax bbox) (ymin bbox))))
|
|
|
|
(defun xmin (bounding-box)
|
|
"Returns left side x of @var{bounding-box}"
|
|
(typecase bounding-box
|
|
(vector (elt bounding-box 0))))
|
|
|
|
(defun ymin (bounding-box)
|
|
"Returns bottom side y of @var{bounding-box}"
|
|
(typecase bounding-box
|
|
(vector (elt bounding-box 1))))
|
|
|
|
(defun xmax (bounding-box)
|
|
"Returns right side x of @var{bounding-box}"
|
|
(typecase bounding-box
|
|
(vector (elt bounding-box 2))))
|
|
|
|
(defun ymax (bounding-box)
|
|
"Returns top side y of @var{bounding-box}"
|
|
(typecase bounding-box
|
|
(vector (elt bounding-box 3))))
|
|
|
|
;;; Font rendering
|
|
(defun clamp (value min max)
|
|
"Clamps the value 'value' into the range [min,max]."
|
|
(max min (min max value)))
|
|
|
|
(defun make-state (font)
|
|
"Wrapper around antialising and not antialiasing renderers."
|
|
(if (font-antialias font)
|
|
(aa:make-state)
|
|
(aa-bin:make-state)))
|
|
|
|
(defun aa-bin/update-state (state paths)
|
|
"Update state for not antialiasing renderer."
|
|
(if (listp paths)
|
|
(dolist (path paths)
|
|
(aa-bin/update-state state path))
|
|
(let ((iterator (paths:path-iterator-segmented paths)))
|
|
(multiple-value-bind (i1 k1 e1) (paths:path-iterator-next iterator)
|
|
(declare (ignore i1))
|
|
(when (and k1 (not e1))
|
|
;; at least 2 knots
|
|
(let ((first-knot k1))
|
|
(loop
|
|
(multiple-value-bind (i2 k2 e2) (paths:path-iterator-next iterator)
|
|
(declare (ignore i2))
|
|
(aa-bin:line-f state
|
|
(paths:point-x k1) (paths:point-y k1)
|
|
(paths:point-x k2) (paths:point-y k2))
|
|
(setf k1 k2)
|
|
(when e2
|
|
(return))))
|
|
(aa-bin:line-f state
|
|
(paths:point-x k1) (paths:point-y k1)
|
|
(paths:point-x first-knot) (paths:point-y first-knot)))))))
|
|
state)
|
|
|
|
(defun update-state (font state paths)
|
|
"Wrapper around antialising and not antialiasing renderers."
|
|
(if (font-antialias font)
|
|
(vectors:update-state state paths)
|
|
(aa-bin/update-state state paths)))
|
|
|
|
(defun cells-sweep (font state function &optional function-span)
|
|
"Wrapper around antialising and not antialiasig renderers."
|
|
(if (font-antialias font)
|
|
(aa:cells-sweep state function function-span)
|
|
(aa-bin:cells-sweep state function function-span)))
|
|
|
|
(defun text-pixarray-provider (dpi-x dpi-y font string)
|
|
(with-font-loader (font-loader font)
|
|
(let* ((bbox (font-cache-fetch (font-string-bboxes font) (cons dpi-x dpi-y) string))
|
|
(min-x (xmin bbox))
|
|
(min-y (ymin bbox))
|
|
(max-x (xmax bbox))
|
|
(max-y (ymax bbox))
|
|
(width (- max-x min-x))
|
|
(height (- max-y min-y)))
|
|
(if (or (= 0 width) (= 0 height))
|
|
(list nil 0 0 0 0)
|
|
(let* ((units->pixels-x (font-units->pixels-x dpi-x font))
|
|
(units->pixels-y (font-units->pixels-y dpi-y font))
|
|
(array (make-array (list height width)
|
|
:initial-element 0
|
|
:element-type '(unsigned-byte 8)))
|
|
(state (make-state font))
|
|
(paths (paths-ttf:paths-from-string font-loader string
|
|
:offset (paths:make-point (- min-x)
|
|
max-y)
|
|
:scale-x units->pixels-x
|
|
:scale-y (- units->pixels-y))))
|
|
|
|
(when (font-underline font)
|
|
(let* ((thickness (* units->pixels-y (zpb-ttf:underline-thickness font-loader)))
|
|
(underline-offset (* units->pixels-y (zpb-ttf:underline-position font-loader)))
|
|
(underline-path (paths:make-rectangle-path 0 (+ max-y (- underline-offset))
|
|
max-x (+ max-y (- underline-offset) thickness))))
|
|
(push underline-path paths)))
|
|
(when (font-strikethrough font)
|
|
(let* ((thickness (* units->pixels-y (zpb-ttf:underline-thickness font-loader)))
|
|
(underline-offset (* 2 units->pixels-y (zpb-ttf:underline-position font-loader)))
|
|
(line-path (paths:make-rectangle-path 0 (+ max-y underline-offset) max-x (+ max-y underline-offset thickness))))
|
|
(push line-path paths)))
|
|
(when (font-overline font)
|
|
(let* ((thickness (* units->pixels-y (zpb-ttf:underline-thickness font-loader)))
|
|
(underline-offset (* units->pixels-y (zpb-ttf:underline-position font-loader)))
|
|
(ascend (* units->pixels-y (zpb-ttf:ascender font-loader)))
|
|
(overline-path (paths:make-rectangle-path 0 (- max-y ascend underline-offset)
|
|
max-x
|
|
(- max-y ascend underline-offset thickness))))
|
|
(push overline-path paths)))
|
|
(update-state font state paths)
|
|
(cells-sweep font state
|
|
(lambda (x y alpha)
|
|
(when (and (<= 0 x (1- width))
|
|
(<= 0 y (1- height)))
|
|
(setf alpha (min 255 (abs alpha))
|
|
(aref array y x) (clamp
|
|
(floor (+ (* (- 256 alpha) (aref array y x))
|
|
(* alpha 255))
|
|
256)
|
|
0 255)))))
|
|
(list array
|
|
min-x
|
|
max-y
|
|
width
|
|
height))))))
|
|
|
|
(defun text-pixarray (drawable font string)
|
|
"Render a text string of 'face', returning a 2D (unsigned-byte 8) array
|
|
suitable as an alpha mask, and dimensions. This function returns five
|
|
values: alpha mask byte array, x-origin, y-origin (subtracted from
|
|
position before rendering), horizontal and vertical advances.
|
|
@var{drawable} must be window or pixmap."
|
|
(values-list
|
|
(font-cache-fetch
|
|
(font-string-alpha-maps font)
|
|
(multiple-value-call #'cons (screen-dpi (drawable-screen drawable)))
|
|
string)))
|
|
|
|
(defun text-line-pixarray-provider (dpi-x dpi-y font string)
|
|
(with-font-loader (font-loader font)
|
|
(let* ((bbox (font-cache-fetch (font-string-line-bboxes font) (cons dpi-x dpi-y) string))
|
|
(min-x (xmin bbox))
|
|
(min-y (ymin bbox))
|
|
(max-x (xmax bbox))
|
|
(max-y (ymax bbox))
|
|
(width (- max-x min-x))
|
|
(height (- max-y min-y)))
|
|
(if (or (= 0 width) (= 0 height))
|
|
(list nil 0 0 0 0)
|
|
(let* ((units->pixels-x (font-units->pixels-x dpi-x font))
|
|
(units->pixels-y (font-units->pixels-y dpi-y font))
|
|
(array (make-array (list height width)
|
|
:initial-element 0
|
|
:element-type '(unsigned-byte 8)))
|
|
(state (make-state font))
|
|
(paths (paths-ttf:paths-from-string font-loader string
|
|
:offset (paths:make-point (- min-x)
|
|
max-y)
|
|
:scale-x units->pixels-x
|
|
:scale-y (- units->pixels-y))))
|
|
(when (font-underline font)
|
|
(let* ((thickness (* units->pixels-y (zpb-ttf:underline-thickness font-loader)))
|
|
(underline-offset (* units->pixels-y (zpb-ttf:underline-position font-loader)))
|
|
(underline-path (paths:make-rectangle-path 0 (+ max-y (- underline-offset))
|
|
max-x (+ max-y (- underline-offset) thickness))))
|
|
(push underline-path paths)))
|
|
(when (font-strikethrough font)
|
|
(let* ((thickness (* units->pixels-y (zpb-ttf:underline-thickness font-loader)))
|
|
(underline-offset (* 2 units->pixels-y (zpb-ttf:underline-position font-loader)))
|
|
(line-path (paths:make-rectangle-path 0 (+ max-y underline-offset) max-x (+ max-y underline-offset thickness))))
|
|
(push line-path paths)))
|
|
(when (font-overline font)
|
|
(let* ((thickness (* units->pixels-y (zpb-ttf:underline-thickness font-loader)))
|
|
(underline-offset (* units->pixels-y (zpb-ttf:underline-position font-loader)))
|
|
(ascend (* units->pixels-y (zpb-ttf:ascender font-loader)))
|
|
(overline-path (paths:make-rectangle-path 0 (- max-y ascend underline-offset)
|
|
max-x
|
|
(- max-y ascend underline-offset thickness))))
|
|
(push overline-path paths)))
|
|
(update-state font state paths)
|
|
(cells-sweep font state
|
|
(lambda (x y alpha)
|
|
(when (and (<= 0 x (1- width))
|
|
(<= 0 y (1- height)))
|
|
(setf alpha (min 255 (abs alpha))
|
|
(aref array y x) (clamp
|
|
(floor (+ (* (- 256 alpha) (aref array y x))
|
|
(* alpha 255))
|
|
256)
|
|
0 255)))))
|
|
(list array
|
|
min-x
|
|
max-y
|
|
width
|
|
height))))))
|
|
|
|
(defun text-line-pixarray (drawable font string)
|
|
"Render a text line of 'face', returning a 2D (unsigned-byte 8) array
|
|
suitable as an alpha mask, and dimensions. This function returns five
|
|
values: alpha mask byte array, x-origin, y-origin (subtracted from
|
|
position before rendering), horizontal and vertical advances.
|
|
@var{drawable} must be window or pixmap."
|
|
(values-list
|
|
(font-cache-fetch
|
|
(font-string-line-alpha-maps font)
|
|
(multiple-value-call #'cons (screen-dpi (drawable-screen drawable)))
|
|
string)))
|
|
|
|
(defun update-foreground (drawable gcontext font)
|
|
"Lazy updates foreground for drawable. @var{drawable} must be window or pixmap."
|
|
(let ((pixmap (or (getf (xlib:drawable-plist drawable) :ttf-pen-surface)
|
|
(setf (getf (xlib:drawable-plist drawable) :ttf-pen-surface)
|
|
(xlib:create-pixmap
|
|
:drawable drawable
|
|
:depth (xlib:drawable-depth drawable)
|
|
:width 1 :height 1)))))
|
|
(let ((color (the xlib:card32
|
|
(if (font-overwrite-gcontext font)
|
|
(font-foreground font)
|
|
(xlib:gcontext-foreground gcontext)))))
|
|
(when (or (null (getf (xlib:drawable-plist drawable) :ttf-foreground))
|
|
(/= (getf (xlib:drawable-plist drawable) :ttf-foreground)
|
|
color))
|
|
(let ((previous-color (xlib:gcontext-foreground gcontext)))
|
|
(setf (xlib:gcontext-foreground gcontext) color)
|
|
(xlib:draw-point pixmap gcontext 0 0)
|
|
(setf (xlib:gcontext-foreground gcontext) previous-color)
|
|
(setf (getf (xlib:drawable-plist drawable) :ttf-foreground) color))))))
|
|
|
|
(defun update-background (drawable gcontext font x y width height)
|
|
"Lazy updates background for drawable. @var{drawable} must be window or pixmap."
|
|
(let ((previous-color (xlib:gcontext-foreground gcontext))
|
|
(color (the xlib:card32
|
|
(if (font-overwrite-gcontext font)
|
|
(font-background font)
|
|
(xlib:gcontext-background gcontext)))))
|
|
(setf (xlib:gcontext-foreground gcontext) color)
|
|
(xlib:draw-rectangle drawable gcontext x y width height t)
|
|
(setf (xlib:gcontext-foreground gcontext) previous-color)))
|
|
|
|
;;; Caching X11 objects
|
|
(defun get-drawable-picture (drawable)
|
|
(or (getf (xlib:drawable-plist drawable) :ttf-surface)
|
|
(setf (getf (xlib:drawable-plist drawable) :ttf-surface)
|
|
(xlib:render-create-picture
|
|
drawable
|
|
:format (first (xlib::find-matching-picture-formats (xlib:drawable-display drawable)
|
|
:depth (xlib:drawable-depth drawable)))))))
|
|
|
|
(defun get-drawable-pen-picture (drawable)
|
|
(or (getf (xlib:drawable-plist drawable) :ttf-pen)
|
|
(setf (getf (xlib:drawable-plist drawable) :ttf-pen)
|
|
(xlib:render-create-picture
|
|
(or (getf (xlib:drawable-plist drawable) :ttf-pen-surface)
|
|
(setf (getf (xlib:drawable-plist drawable) :ttf-pen-surface)
|
|
(xlib:create-pixmap
|
|
:drawable drawable
|
|
:depth (xlib:drawable-depth drawable)
|
|
:width 1 :height 1)))
|
|
:format (first (xlib::find-matching-picture-formats (xlib:drawable-display drawable)
|
|
:depth (xlib:drawable-depth drawable)))
|
|
:repeat :on))))
|
|
|
|
(defun display-alpha-picture-format (display)
|
|
(or (getf (xlib:display-plist display) :ttf-alpha-format)
|
|
(setf (getf (xlib:display-plist display) :ttf-alpha-format)
|
|
(first
|
|
(xlib:find-matching-picture-formats
|
|
display
|
|
:depth 8 :alpha 8 :red 0 :blue 0 :green 0)))))
|
|
|
|
;;; Drawing text
|
|
|
|
(defun draw-text (drawable gcontext font string x y &key start end
|
|
draw-background-p)
|
|
"Draws text string using @var{font} on @var{drawable} with graphic context @var{gcontext}. @var{x}, @var{y} are the left point of base line. @var{start} and @var{end} are used for substring rendering.
|
|
If @var{gcontext} has background color, text bounding box will be filled with it. Text line bounding box is bigger than text bounding box. @var{drawable} must be window or pixmap."
|
|
(when (and start end)
|
|
(when (>= start end)
|
|
(return-from draw-text))
|
|
(setf string (subseq string start end)))
|
|
(multiple-value-bind (alpha-data min-x max-y width height)
|
|
(text-pixarray drawable font string)
|
|
(when (or (= 0 width) (= 0 height))
|
|
(return-from draw-text))
|
|
(let* ((display (xlib:drawable-display drawable))
|
|
(image (xlib:create-image :width width :height height :depth 8 :data alpha-data))
|
|
(alpha-pixmap (xlib:create-pixmap :width width :height height :depth 8 :drawable drawable))
|
|
(alpha-gc (xlib:create-gcontext :drawable alpha-pixmap))
|
|
(alpha-picture
|
|
(progn
|
|
(xlib:put-image alpha-pixmap alpha-gc image :x 0 :y 0)
|
|
(xlib:render-create-picture alpha-pixmap
|
|
:format (display-alpha-picture-format display))))
|
|
(source-picture (get-drawable-pen-picture drawable))
|
|
(destination-picture (get-drawable-picture drawable)))
|
|
|
|
(xlib:free-gcontext alpha-gc)
|
|
|
|
(update-foreground drawable gcontext font)
|
|
(when draw-background-p
|
|
(update-background drawable gcontext font (+ x min-x) (- y max-y) width height))
|
|
;; Sync the destination picture with the gcontext
|
|
(setf (xlib:picture-clip-x-origin destination-picture) (xlib:gcontext-clip-x gcontext))
|
|
(setf (xlib:picture-clip-y-origin destination-picture) (xlib:gcontext-clip-y gcontext))
|
|
(setf (xlib:picture-subwindow-mode destination-picture) (xlib:gcontext-subwindow-mode gcontext))
|
|
(setf (xlib::picture-clip-mask destination-picture)
|
|
(xlib::gcontext-clip-mask gcontext))
|
|
(xlib:render-composite :over source-picture alpha-picture destination-picture 0 0 0 0 (+ x min-x) (- y max-y) width height)
|
|
|
|
(xlib:render-free-picture alpha-picture)
|
|
(xlib:free-pixmap alpha-pixmap)
|
|
nil)))
|
|
|
|
(defun draw-text-line (drawable gcontext font string x y &key start end draw-background-p)
|
|
"Draws text string using @var{font} on @var{drawable} with graphic context @var{gcontext}. @var{x}, @var{y} are the left point of base line. @var{start} and @var{end} are used for substring rendering.
|
|
If @var{gcontext} has background color, text line bounding box will be filled with it. Text line bounding box is bigger than text bounding box. @var{drawable} must be window or pixmap."
|
|
(when (and start end)
|
|
(when (>= start end)
|
|
(return-from draw-text-line))
|
|
(setf string (subseq string start end)))
|
|
(multiple-value-bind (alpha-data min-x max-y width height)
|
|
(text-line-pixarray drawable font string)
|
|
(when (or (= 0 width) (= 0 height))
|
|
(return-from draw-text-line))
|
|
(let* ((display (xlib:drawable-display drawable))
|
|
(image (xlib:create-image :width width :height height :depth 8 :data alpha-data))
|
|
|
|
(alpha-pixmap (xlib:create-pixmap :width width :height height :depth 8 :drawable drawable))
|
|
(alpha-gc (xlib:create-gcontext :drawable alpha-pixmap))
|
|
(alpha-picture
|
|
(progn
|
|
(xlib:put-image alpha-pixmap alpha-gc image :x 0 :y 0)
|
|
(xlib:render-create-picture alpha-pixmap
|
|
:format (display-alpha-picture-format display))))
|
|
(source-picture (get-drawable-pen-picture drawable))
|
|
(destination-picture (get-drawable-picture drawable)))
|
|
|
|
(xlib:free-gcontext alpha-gc)
|
|
|
|
(update-foreground drawable gcontext font)
|
|
(when draw-background-p
|
|
(update-background drawable gcontext font (+ x min-x) (- y max-y) width height))
|
|
;; Sync the destination picture with the gcontext
|
|
(setf (xlib:picture-clip-x-origin destination-picture) (xlib:gcontext-clip-x gcontext))
|
|
(setf (xlib:picture-clip-y-origin destination-picture) (xlib:gcontext-clip-y gcontext))
|
|
(setf (xlib:picture-subwindow-mode destination-picture) (xlib:gcontext-subwindow-mode gcontext))
|
|
(setf (xlib::picture-clip-mask destination-picture)
|
|
(xlib::gcontext-clip-mask gcontext))
|
|
(xlib:render-composite :over source-picture alpha-picture destination-picture 0 0 0 0 (+ x min-x) (- y max-y) width height)
|
|
|
|
(xlib:render-free-picture alpha-picture)
|
|
(xlib:free-pixmap alpha-pixmap)
|
|
nil)))
|
|
|
|
;;; Test utils
|
|
|
|
(defun trgrey (i)
|
|
"Visualize alpha mask using graphic characters"
|
|
(cond
|
|
((> i 200) "██")
|
|
((> i 150) "▓▓")
|
|
((> i 100) "▒▒")
|
|
((> i 50) "░░")
|
|
(t " ")))
|
|
|
|
(defun print-pixarray (array)
|
|
"Print 2d array of alpha mask using graphic characters."
|
|
(do ((i 0 (1+ i)))
|
|
((>= i (array-dimension array 0)) nil)
|
|
(do ((j 0 (1+ j)))
|
|
((>= j (array-dimension array 1)) nil)
|
|
(format t "~A" (trgrey (aref array i j))))
|
|
(format t "~%")))
|
|
|
|
(defun font-lines-height (drawable font lines-count)
|
|
"Returns text lines height in pixels. For one line height is ascender+descender. For more than one line height is ascender+descender+linegap."
|
|
(if (> lines-count 0)
|
|
(+ (+ (xft:font-ascent drawable font)
|
|
(- (xft:font-descent drawable font)))
|
|
(* (1- lines-count) (+ (xft:font-ascent drawable font)
|
|
(- (xft:font-descent drawable font))
|
|
(xft:font-line-gap drawable font))))
|
|
0))
|
|
|
|
;;; "clx-truetype" goes here. Hacks and glory await!
|
|
|