racket/draw: fix font metrics versus transformations

For a font with 'aligned hinting, the font map caches metric
information that depends on the destination transformation,
at least on Windows. Make the font-map cache sensitive to the
destination's current transformation.

This bug was exposed by support for DPI-aware GUIs on Windows,
but the problem was more general.
This commit is contained in:
Matthew Flatt 2014-09-23 16:46:40 -06:00
parent ed92e271e8
commit 9ee2bd9b60
5 changed files with 102 additions and 16 deletions

View File

@ -32,6 +32,12 @@
(cairo_scale cr s s))) (cairo_scale cr s s)))
(super init-cr-matrix cr)) (super init-cr-matrix cr))
(define/override (init-effective-matrix mx)
(when bm
(define s (send bm get-cairo-device-scale))
(unless (= s 1)
(cairo_matrix_scale mx s s))))
(define/override (ok?) (and c #t)) (define/override (ok?) (and c #t))
(define/private (do-set-bitmap v reset?) (define/private (do-set-bitmap v reset?)

View File

@ -76,11 +76,16 @@
;; been called before any call to flush. ;; been called before any call to flush.
flush-cr flush-cr
;; method init-cr-matrix : -> void ;; method init-cr-matrix : cr -> void
;; ;;
;; Initializes/resets the transformation matrix ;; Initializes/resets the transformation matrix
init-cr-matrix init-cr-matrix
;; method init-effective-matrix : matrix -> void
;;
;; Like init-cr-matrix, but given a matrix
init-effective-matrix
;; method reset-clip : cr -> void ;; method reset-clip : cr -> void
;; ;;
;; Resets the clipping region ;; Resets the clipping region
@ -170,6 +175,7 @@
(define/public (flush-cr) (void)) (define/public (flush-cr) (void))
(define/public (init-cr-matrix cr) (void)) (define/public (init-cr-matrix cr) (void))
(define/public (init-effective-matrix mx) (void))
(define/public (reset-clip cr) (define/public (reset-clip cr)
(cairo_reset_clip cr)) (cairo_reset_clip cr))
@ -234,13 +240,20 @@
;; at least for the Quartz and Win32 back-ends. ;; at least for the Quartz and Win32 back-ends.
;; (But we create the font maps on demand.) ;; (But we create the font maps on demand.)
;; We fold hinting in, too, as an extra factor of 2. ;; We fold hinting in, too, as an extra factor of 2.
;; In the case of aligned hinting, at least, the font map
;; might further depend on the transformation, at least
;; for Windows, so each element of `font-maps` is
;; (vector font-map xform (hash xform -> font-map))
;; where the first two elements of the vector act as
;; a cache for the hash-table lookup.
(define font-maps (make-vector 8 #f)) (define font-maps (make-vector 8 #f))
(define (dc-mixin backend%) (define (dc-mixin backend%)
(defclass* dc% backend% (dc<%>) (defclass* dc% backend% (dc<%>)
(super-new) (super-new)
(inherit flush-cr get-cr release-cr end-cr init-cr-matrix get-pango (inherit flush-cr get-cr release-cr end-cr init-cr-matrix init-effective-matrix
get-pango
install-color dc-adjust-smoothing get-hairline-width dc-adjust-cap-shape install-color dc-adjust-smoothing get-hairline-width dc-adjust-cap-shape
reset-clip reset-clip
collapse-bitmap-b&w? collapse-bitmap-b&w?
@ -360,7 +373,9 @@
(set-effective-scale-font-cached?!) (set-effective-scale-font-cached?!)
(set! effective-origin-x 0.0) (set! effective-origin-x 0.0)
(set! effective-origin-y 0.0) (set! effective-origin-y 0.0)
(set! current-xform (vector 1.0 0.0 0.0 1.0 0.0 0.0)) (let* ([mx (make-cairo_matrix_t 1 0 0 1 0 0)])
(init-effective-matrix mx)
(set! current-xform (matrix->vector mx)))
(set! pen (send the-pen-list find-or-create-pen "black" 1 'solid)) (set! pen (send the-pen-list find-or-create-pen "black" 1 'solid))
(set! brush (send the-brush-list find-or-create-brush "white" 'solid)) (set! brush (send the-brush-list find-or-create-brush "white" 'solid))
(set! font (send the-font-list find-or-create-font 12 'default)) (set! font (send the-font-list find-or-create-font 12 'default))
@ -380,6 +395,7 @@
(define/private (reset-effective!) (define/private (reset-effective!)
(let* ([mx (make-cairo_matrix_t 1 0 0 1 0 0)]) (let* ([mx (make-cairo_matrix_t 1 0 0 1 0 0)])
(init-effective-matrix mx)
(cairo_matrix_multiply mx mx matrix) (cairo_matrix_multiply mx mx matrix)
(cairo_matrix_translate mx origin-x origin-y) (cairo_matrix_translate mx origin-x origin-y)
(cairo_matrix_scale mx scale-x scale-y) (cairo_matrix_scale mx scale-x scale-y)
@ -394,15 +410,18 @@
(set-effective-scale-font-cached?!) (set-effective-scale-font-cached?!)
(set! effective-origin-x (cairo_matrix_t-x0 mx)) (set! effective-origin-x (cairo_matrix_t-x0 mx))
(set! effective-origin-y (cairo_matrix_t-y0 mx)) (set! effective-origin-y (cairo_matrix_t-y0 mx))
(let ([v (vector (cairo_matrix_t-xx mx) (let ([v (matrix->vector mx)])
(cairo_matrix_t-yx mx)
(cairo_matrix_t-xy mx)
(cairo_matrix_t-yy mx)
(cairo_matrix_t-x0 mx)
(cairo_matrix_t-y0 mx))])
(unless (equal? v current-xform) (unless (equal? v current-xform)
(set! current-xform v))))) (set! current-xform v)))))
(define/private (matrix->vector mx)
(vector (cairo_matrix_t-xx mx)
(cairo_matrix_t-yx mx)
(cairo_matrix_t-xy mx)
(cairo_matrix_t-yy mx)
(cairo_matrix_t-x0 mx)
(cairo_matrix_t-y0 mx)))
(define/override (set-auto-scroll dx dy) (define/override (set-auto-scroll dx dy)
(unless (and (= scroll-dx (- dx)) (unless (and (= scroll-dx (- dx))
(= scroll-dy (- dy))) (= scroll-dy (- dy)))
@ -1302,11 +1321,7 @@
(vector-set! c 1 xform))) (vector-set! c 1 xform)))
(vector-ref c 0))) (vector-ref c 0)))
(let ([c (pango_font_map_create_context (let ([c (pango_font_map_create_context
(let ([fm (vector-ref font-maps smoothing-index)]) (get-font-map smoothing-index xform))])
(or fm
(let ([fm (pango_cairo_font_map_new)])
(vector-set! font-maps smoothing-index fm)
fm))))])
(pango_cairo_update_context cr c) (pango_cairo_update_context cr c)
(vector-set! contexts smoothing-index (vector c xform)) (vector-set! contexts smoothing-index (vector c xform))
(set-font-antialias c (set-font-antialias c
@ -1314,6 +1329,28 @@
(send font get-hinting)) (send font get-hinting))
c))) c)))
(define/private (get-font-map smoothing-index xform)
(define old-fmv (vector-ref font-maps smoothing-index))
(define fmv (or old-fmv (vector #f #f #hash())))
(unless old-fmv
(vector-set! font-maps smoothing-index fmv))
(or (and (equal? xform (vector-ref fmv 1))
(vector-ref fmv 0))
(let* ([fm (hash-ref (vector-ref fmv 2) xform #f)]
[new-fm (or fm
(pango_cairo_font_map_new))])
(vector-set! fmv 0 new-fm)
(vector-set! fmv 1 xform)
(unless fm
(define ht (vector-ref fmv 2))
(define new-ht
;; Limit the number of font maps that we cache:
(if ((hash-count ht) . < . 8)
ht
#hash()))
(vector-set! fmv 2 (hash-set new-ht xform new-fm)))
new-fm)))
(define/private (do-text cr draw-mode s x y font combine? offset angle) (define/private (do-text cr draw-mode s x y font combine? offset angle)
(let* ([s (if (zero? offset) (let* ([s (if (zero? offset)
s s
@ -1943,7 +1980,7 @@
[attrs (send font get-pango-attrs)] [attrs (send font get-pango-attrs)]
[index (get-smoothing-index font)] [index (get-smoothing-index font)]
[context (get-context cr index font current-xform)] [context (get-context cr index font current-xform)]
[fontmap (vector-ref font-maps index)] [fontmap (get-font-map index current-xform)]
[font (pango_font_map_load_font fontmap context desc)]) [font (pango_font_map_load_font fontmap context desc)])
(and font ;; else font match failed (and font ;; else font match failed
(let ([metrics (pango_font_get_metrics font (pango_language_get_default))]) (let ([metrics (pango_font_get_metrics font (pango_language_get_default))])

View File

@ -47,6 +47,7 @@
reset-cr reset-cr
flush-cr flush-cr
init-cr-matrix init-cr-matrix
init-effective-matrix
get-font-metrics-key get-font-metrics-key
install-color install-color
dc-adjust-smoothing dc-adjust-smoothing

View File

@ -183,9 +183,14 @@
(define/override (init-cr-matrix cr) (define/override (init-cr-matrix cr)
(unless (= backing-scale 1.0) (unless (= backing-scale 1.0)
(cairo_scale cr backing-scale backing-scale)) (cairo_scale cr backing-scale backing-scale))
(super init-cr-matrix cr)) (super init-cr-matrix cr))
(define/override (init-effective-matrix mx)
(unless (= backing-scale 1.0)
(cairo_matrix_scale mx backing-scale backing-scale))
(super init-effective-matrix mx))
(define/override (reset-config s) (define/override (reset-config s)
(set! backing-scale s) (set! backing-scale s)
(super reset-config)))) (super reset-config))))

View File

@ -0,0 +1,37 @@
#lang racket
(require racket/draw)
;; Check for pollution of font metrics from differently
;; scaled contexts.
(define font (make-font #:face "Times"))
;; Running `go` might affect the result of `go2`
(define (go)
(define bm (make-bitmap 1 1))
(send (send bm make-dc) get-text-extent
"Extra regexp"
font
#t))
;; `go2` is like `go`, but for a different scale
(define (go2)
(define bm2 (make-platform-bitmap 1 1))
(define dc (send bm2 make-dc))
(send dc scale 1.25 1.25)
(send dc get-text-extent
"Extra regexp"
font
#t))
;; Running `go2` again in a separate place might produce
;; results unaffected by `go`:
(define (go2/p)
(place pch (place-channel-put pch (call-with-values go2 list))))
(module+ test
(call-with-values go void)
(define l1 (call-with-values go2 list))
(define l2 (sync (go2/p)))
(unless (equal? l1 l2)
(error 'different "~s ~s" l1 l2)))