add hinting configuration to `font%'

The default is that hiniting is enabled, which causes some text
metrics (notably width) to be rounded to integer values, which makes
spacing more consistent. This default is backward-compatible. The
non-default 'unaligned mode refrains from rounding, which makes metric
information scale correctly and improves output for PS/PDF (such as
Redex output).

The `text' function from `slideshow/pict' defaults to
disabling hinting --- which is consistent with its default to combine
text instead of drawing character-by-character -- so slides and Redex
inherit the improvement.
This commit is contained in:
Matthew Flatt 2012-03-27 07:07:16 -06:00
parent 2eeff9d1fc
commit b3002cfab0
8 changed files with 154 additions and 82 deletions

View File

@ -237,7 +237,8 @@
;; time a font is used in a given map seems to stick,
;; at least for the Quartz and Win32 back-ends.
;; (But we create the font maps on demand.)
(define font-maps (make-vector 4 #f))
;; We fold hinting in, too, as an extra factor of 2.
(define font-maps (make-vector 8 #f))
(define (dc-mixin backend%)
(defclass* dc% backend% (dc<%>)
@ -546,7 +547,7 @@
(define current-smoothing #f)
(define (set-font-antialias context smoothing)
(define (set-font-antialias context smoothing hinting)
(let ([o (pango_cairo_context_get_font_options context)]
[o2 (cairo_font_options_create)])
(when o
@ -560,8 +561,13 @@
[(unsmoothed) CAIRO_ANTIALIAS_NONE]
[(partly-smoothed) CAIRO_ANTIALIAS_GRAY]
[(smoothed) CAIRO_ANTIALIAS_SUBPIXEL]))
(cairo_font_options_set_hint_metrics o2 CAIRO_HINT_METRICS_OFF)
;; good idea?: (cairo_font_options_set_hint_style o2 CAIRO_HINT_STYLE_NONE)
(case hinting
[(aligned)
(cairo_font_options_set_hint_metrics o2 CAIRO_HINT_METRICS_ON)
(cairo_font_options_set_hint_style o2 CAIRO_HINT_STYLE_DEFAULT)]
[(unaligned)
(cairo_font_options_set_hint_metrics o2 CAIRO_HINT_METRICS_OFF)
(cairo_font_options_set_hint_style o2 CAIRO_HINT_STYLE_NONE)])
(pango_cairo_context_set_font_options context o2)
(cairo_font_options_destroy o2)))
@ -1220,11 +1226,14 @@
(do-text cr #f s 0 0 use-font combine? offset 0.0))))))
(define/private (get-smoothing-index)
(case (dc-adjust-smoothing (send font get-smoothing))
[(default) 0]
[(unsmoothed) 1]
[(partly-smoothed) 2]
[(smoothed) 3]))
(+ (case (dc-adjust-smoothing (send font get-smoothing))
[(default) 0]
[(unsmoothed) 1]
[(partly-smoothed) 2]
[(smoothed) 3])
(case (send font get-hinting)
[(aligned) 0]
[(unaligned) 4])))
(define/private (get-context cr smoothing-index)
(or (vector-ref contexts smoothing-index)
@ -1236,7 +1245,9 @@
fm))))])
(pango_cairo_update_context cr c)
(vector-set! contexts smoothing-index c)
(set-font-antialias c (dc-adjust-smoothing (send font get-smoothing)))
(set-font-antialias c
(dc-adjust-smoothing (send font get-smoothing))
(send font get-hinting))
c)))
(define/private (do-text cr draw-mode s x y font combine? offset angle)
@ -1271,7 +1282,9 @@
(cairo_rotate cr (- angle)))
(let ([desc (get-pango font)]
[attrs (send font get-pango-attrs)]
[integral round]
[force-hinting (case (send font get-hinting)
[(aligned) round]
[else values])]
[x (if rotate? 0.0 (exact->inexact x))]
[y (if rotate? 0.0 (exact->inexact y))])
;; We have two ways to draw text:
@ -1352,7 +1365,8 @@
[else
(let ([nw (if blank?
0.0
(integral (/ (PangoRectangle-width logical) (exact->inexact PANGO_SCALE))))]
(force-hinting
(/ (PangoRectangle-width logical) (exact->inexact PANGO_SCALE))))]
[na 0.0])
(loop next-s draw-mode measured? (+ w nw) (max h nh) (max d nd) (max a na)))])))])))]))
;; This is character-by-character mode. It uses a cached per-character+font layout
@ -1445,10 +1459,10 @@
(memcpy glyph-infos i glyphs 1 _PangoGlyphInfo)
;; Every glyph is is own cluster:
(ptr-set! log-clusters _int i i)
;; Adjust width to be consistent with integral widths
;; Adjust width to be consistent with measured widths
;; used when drawing individual characters.
;; This is `set-PangoGlyphInfo-width!', but without
;; computing a
;; computing an intermediate pointer:
(ptr-set! glyph-infos _uint32 'abs (+ (* i pgi-size) 4) (vector-ref v 5))
(loop (add1 i))))))
;; If we get here, we can use the fast way:
@ -1482,10 +1496,9 @@
(pango_layout_get_extents layout #f logical)
(let ([baseline (pango_layout_get_baseline layout)]
[orig-h (PangoRectangle-height logical)])
;; We keep integer width & height to pixel-align each individual character,
;; but we keep non-integral lh & ld to pixel-align the baseline.
(let ([lw (integral (/ (PangoRectangle-width logical)
(exact->inexact PANGO_SCALE)))]
(let ([lw (force-hinting
(/ (PangoRectangle-width logical)
(exact->inexact PANGO_SCALE)))]
[flh (/ orig-h (exact->inexact PANGO_SCALE))]
[ld (/ (- orig-h baseline) (exact->inexact PANGO_SCALE))]
[la 0.0])

View File

@ -1,6 +1,7 @@
#lang scheme/base
(provide family-symbol? style-symbol? weight-symbol? smoothing-symbol?)
(provide family-symbol? style-symbol? weight-symbol?
smoothing-symbol? hinting-symbol?)
(define (family-symbol? s)
(memq s '(default decorative roman script
@ -15,3 +16,5 @@
(define (smoothing-symbol? s)
(memq s '(default smoothed unsmoothed partly-smoothed)))
(define (hinting-symbol? s)
(memq s '(aligned unaligned)))

View File

@ -13,7 +13,7 @@
(provide font%
font-list% the-font-list
make-font
family-symbol? style-symbol? weight-symbol? smoothing-symbol?
family-symbol? style-symbol? weight-symbol? smoothing-symbol? hinting-symbol?
get-pango-attrs
get-face-list
(protect-out substitute-fonts?
@ -186,6 +186,9 @@
(define smoothing 'default)
(def/public (get-smoothing) smoothing)
(define hinting 'aligned)
(def/public (get-hinting) hinting)
(define style 'normal)
(def/public (get-style) style)
@ -213,14 +216,16 @@
[weight-symbol? [_weight 'normal]]
[any? [_underlined? #f]]
[smoothing-symbol? [_smoothing 'default]]
[any? [_size-in-pixels? #f]])
[any? [_size-in-pixels? #f]]
[hinting-symbol? [_hinting 'aligned]])
(set! size _size)
(set! family _family)
(set! style _style)
(set! weight _weight)
(set! underlined? _underlined?)
(set! smoothing _smoothing)
(set! size-in-pixels? _size-in-pixels?)]
(set! size-in-pixels? _size-in-pixels?)
(set! hinting _hinting)]
[([size? _size]
[(make-or-false string?) _face]
[family-symbol? _family]
@ -228,7 +233,8 @@
[weight-symbol? [_weight 'normal]]
[any? [_underlined? #f]]
[smoothing-symbol? [_smoothing 'default]]
[any? [_size-in-pixels? #f]])
[any? [_size-in-pixels? #f]]
[hinting-symbol? [_hinting 'aligned]])
(set! size _size)
(set! face (and _face (string->immutable-string _face)))
(set! family _family)
@ -236,7 +242,8 @@
(set! weight _weight)
(set! underlined? _underlined?)
(set! smoothing _smoothing)
(set! size-in-pixels? _size-in-pixels?)]
(set! size-in-pixels? _size-in-pixels?)
(set! hinting _hinting)]
(init-name 'font%))
(define id
@ -244,7 +251,7 @@
(send the-font-name-directory find-or-create-font-id face family)
(send the-font-name-directory find-family-default-font-id family)))
(define key
(let ([key (vector id size style weight underlined? smoothing size-in-pixels?)])
(let ([key (vector id size style weight underlined? smoothing size-in-pixels? hinting)])
(let ([old-key (atomically (hash-ref keys key #f))])
(if old-key
(weak-box-value old-key)
@ -267,8 +274,9 @@
[weight-symbol? [weight 'normal]]
[any? [underlined? #f]]
[smoothing-symbol? [smoothing 'default]]
[any? [size-in-pixels? #f]])
(vector size family style weight underlined? smoothing size-in-pixels?)]
[any? [size-in-pixels? #f]]
[hinting-symbol? [hinting 'aligned]])
(vector size family style weight underlined? smoothing size-in-pixels? hinting)]
[([size? size]
[(make-or-false string?) face]
[family-symbol? family]
@ -276,10 +284,11 @@
[weight-symbol? [weight 'normal]]
[any? [underlined? #f]]
[smoothing-symbol? [smoothing 'default]]
[any? [size-in-pixels? #f]])
[any? [size-in-pixels? #f]]
[hinting-symbol? [hinting 'aligned]])
(vector size (and face (string->immutable-string face)) family
style weight underlined? smoothing size-in-pixels?)]
(method-name 'find-or-create-font font-list%))])
style weight underlined? smoothing size-in-pixels? hinting)]
(method-name 'find-or-create-font 'font-list%))])
(atomically
(let ([e (hash-ref fonts key #f)])
(or (and e
@ -320,11 +329,13 @@
#:weight [weight 'normal]
#:underlined? [underlined? #f]
#:smoothing [smoothing 'default]
#:size-in-pixels? [size-in-pixels? #f])
#:size-in-pixels? [size-in-pixels? #f]
#:hinting [hinting 'aligned])
(unless (size? size) (raise-type-error 'make-font "exact integer in [1, 1024]" size))
(unless (or (not face) (string? face)) (raise-type-error 'make-font "string or #f" face))
(unless (family-symbol? family) (raise-type-error 'make-font "family-symbol" family))
(unless (style-symbol? style) (raise-type-error 'make-font "style-symbol" style))
(unless (weight-symbol? weight) (raise-type-error 'make-font "weight-symbol" weight))
(unless (smoothing-symbol? smoothing) (raise-type-error 'make-font "smoothing-symbol" smoothing))
(make-object font% size face family style weight underlined? smoothing size-in-pixels?))
(unless (hinting-symbol? hinting) (raise-type-error 'make-font "hinting-symbol" hinting))
(make-object font% size face family style weight underlined? smoothing size-in-pixels? hinting))

View File

@ -11,7 +11,7 @@ A parameter that determines the current PostScript configuration
settings. See @racket[post-script-dc%] and @racket[printer-dc%].}
@defproc[(get-face-list [kind (one-of/c 'mono 'all) 'all]
@defproc[(get-face-list [kind (or/c 'mono 'all) 'all]
[#:all-variants? all-variants? any/c #f])
(listof string?)]{
@ -26,8 +26,8 @@ If @racket[all-variants?] is @racket[#f] (the default), then the
includes a string for each available face in the family.}
@defproc[(get-family-builtin-face [family (one-of/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)])
@defproc[(get-family-builtin-face [family (or/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)])
string?]{
Returns the built-in default face mapping for a particular font
@ -50,16 +50,17 @@ See also @racket[make-platform-bitmap] and @secref["Portability"].
@defproc[(make-font [#:size size (integer-in 1 1024) 12]
[#:face face (or/c string? #f) #f]
[#:family family (one-of/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)
[#:family family (or/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)
'default]
[#:style style (one-of/c 'normal 'italic 'slant) 'normal]
[#:weight weight (one-of/c 'normal 'bold 'light) 'normal]
[#:style style (or/c 'normal 'italic 'slant) 'normal]
[#:weight weight (or/c 'normal 'bold 'light) 'normal]
[#:underlined? underlined? any/c #f]
[#:smoothing smoothing (one-of/c 'default 'partly-smoothed
'smoothed 'unsmoothed)
[#:smoothing smoothing (or/c 'default 'partly-smoothed
'smoothed 'unsmoothed)
'default]
[#:size-in-pixels? size-in-pixels? any/c #f])
[#:size-in-pixels? size-in-pixels? any/c #f]
[#:hinting hinting (or/c 'aligned 'unaligned) 'aligned])
(is-a?/c font%)]{
Creates a @racket[font%] instance. This procedure provides an
@ -90,12 +91,12 @@ on Windows and Mac OS X. See @secref["Portability"] for more information.}
@defproc[(read-bitmap [in (or path-string? input-port?)]
[kind (one-of/c 'unknown 'unknown/mask 'unknown/alpha
'gif 'gif/mask 'gif/alpha
'jpeg 'jpeg/alpha
'png 'png/mask 'png/alpha
'xbm 'xbm/alpha 'xpm 'xpm/alpha
'bmp 'bmp/alpha)
[kind (or/c 'unknown 'unknown/mask 'unknown/alpha
'gif 'gif/mask 'gif/alpha
'jpeg 'jpeg/alpha
'png 'png/mask 'png/alpha
'xbm 'xbm/alpha 'xpm 'xpm/alpha
'bmp 'bmp/alpha)
'unknown/alpha]
[bg-color (or/c (is-a?/c color%) false/c) #f]
[complain-on-failure? any/c #t])

View File

@ -78,6 +78,14 @@ A @defterm{font} is an object which determines the appearance of text,
bitmap drawing context), @racket[#f] if the size of the font is in
points (which can depend on screen resolution).}
@item{hinting --- Whether font metrics should be rounded to integers:
@itemize[
@item{@indexed-racket['aligned] (the default) --- rounds to integers
to improve the consistency of letter spacing for pixel-based
targets, but at the expense of making metrics unscalable}
@item{@indexed-racket['unaligned] --- disables rounding}
]}
]
To avoid creating multiple fonts with the same characteristics, use
@ -90,33 +98,37 @@ See also
@defconstructor*/make[(()
([size (integer-in 1 1024)]
[family (one-of/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)]
[style (one-of/c 'normal 'italic 'slant) 'normal]
[weight (one-of/c 'normal 'bold 'light) 'normal]
[family (or/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)]
[style (or/c 'normal 'italic 'slant) 'normal]
[weight (or/c 'normal 'bold 'light) 'normal]
[underline? any/c #f]
[smoothing (one-of/c 'default 'partly-smoothed
[smoothing (or/c 'default 'partly-smoothed
'smoothed 'unsmoothed)
'default]
[size-in-pixels? any/c #f])
[size-in-pixels? any/c #f]
[hinting (or/c 'aligned 'unaligned) 'aligned])
([size (integer-in 1 1024)]
[face string?]
[family (one-of/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)]
[style (one-of/c 'normal 'italic 'slant) 'normal]
[weight (one-of/c 'normal 'bold 'light) 'normal]
[family (or/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)]
[style (or/c 'normal 'italic 'slant) 'normal]
[weight (or/c 'normal 'bold 'light) 'normal]
[underline? any/c #f]
[smoothing (one-of/c 'default 'partly-smoothed
'smoothed 'unsmoothed)
[smoothing (or/c 'default 'partly-smoothed
'smoothed 'unsmoothed)
'default]
[size-in-pixels? any/c #f]))]{
[size-in-pixels? any/c #f]
[hinting (or/c 'aligned 'unaligned) 'aligned]))]{
When no arguments are provided, creates an instance of the default
font. If no face name is provided, the font is created without a face
name.
See @racket[font%] for information about @racket[family],
@racket[style], and @racket[weight]. @racket[font-name-directory<%>].
@racket[style], @racket[weight], @racket[smoothing],
@racket[size-in-pixels?], and @racket[hinting].
@racket[font-name-directory<%>].
See also @racket[make-font].
@ -130,8 +142,8 @@ Gets the font's face name, or @racket[#f] if none is specified.
}
@defmethod[(get-family)
(one-of/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)]{
(or/c 'default 'decorative 'roman 'script
'swiss 'modern 'symbol 'system)]{
Gets the font's family. See @racket[font%] for information about
families.
@ -147,6 +159,14 @@ face and family specifications, only.
}
@defmethod[(get-hinting)
(or/c 'aligned 'unaligned)]{
Gets the font's hinting. See @racket[font%] for information about
hinting.
}
@defmethod[(get-point-size)
(integer-in 1 1024)]{
@ -172,7 +192,7 @@ For a size in points and a screen or bitmap drawing context, the
}
@defmethod[(get-smoothing)
(one-of/c 'default 'partly-smoothed 'smoothed 'unsmoothed)]{
(or/c 'default 'partly-smoothed 'smoothed 'unsmoothed)]{
Gets the font's anti-alias smoothing mode. See @racket[font%] for
information about smoothing.
@ -180,7 +200,7 @@ Gets the font's anti-alias smoothing mode. See @racket[font%] for
}
@defmethod[(get-style)
(one-of/c 'normal 'italic 'slant)]{
(or/c 'normal 'italic 'slant)]{
Gets the font's slant style. See @racket[font%] for information about
styles.
@ -196,7 +216,7 @@ otherwise.
}
@defmethod[(get-weight)
(one-of/c 'normal 'bold 'light)]{
(or/c 'normal 'bold 'light)]{
Gets the font's weight. See @racket[font%] for information about
weights.

View File

@ -22,7 +22,8 @@ Creates an empty font list.
[weight (one-of/c 'normal 'bold 'light)]
[underline? any/c #f]
[smoothing (one-of/c 'default 'partly-smoothed 'smoothed 'unsmoothed) 'default]
[size-in-pixels? any/c #f])
[size-in-pixels? any/c #f]
[hinting (or/c 'aligned 'unaligned) 'aligned])
(is-a?/c font%)]
[(find-or-create-font [size (integer-in 1 255)]
[face string?]
@ -32,7 +33,8 @@ Creates an empty font list.
[weight (one-of/c 'normal 'bold 'light)]
[underline any/c #f]
[smoothing (one-of/c 'default 'partly-smoothed 'smoothed 'unsmoothed) 'default]
[size-in-pixels? any/c #f])
[size-in-pixels? any/c #f]
[hinting (or/c 'aligned 'unaligned) 'aligned])
(is-a?/c font%)])]{
Finds an existing font in the list or creates a new one (that is

View File

@ -189,11 +189,20 @@ The @racket[style] argument must be one of the following:
(the default, unless the @racket['modern] family is specified)}
@item{@racket[(cons 'no-combine style)] --- renders characters individually}
@item{@racket[(cons 'aligned style)] --- enables hinting, which
rounds metrics to integers}
@item{@racket[(cons 'unaligned style)] --- disables hinting (which is
the default), so that metrics are scalable}
]
If both @racket['combine] and @racket['no-combine] are specified, the
first one takes precedence. If caps is specified, the angle must be
zero.
first one in @racket[style] takes precedence. Similarly, if both
@racket['aligned] and @racket['unaligned] are specified, the first one
in !racket[style] takes precedence. If @racket['caps] is specified,
the @racket[angle] must be zero.
The given @racket[size] is in pixels, but it is ignored if a
@racket[font%] object is provided in the text-style.

View File

@ -90,7 +90,7 @@
(memq* a (cdr l)))
#f))
(define (extend-font font size style weight)
(define (extend-font font size style weight hinting)
(if (send font get-face)
(send the-font-list find-or-create-font
size
@ -100,7 +100,8 @@
weight
#f
'default
#t)
#t
hinting)
(send the-font-list find-or-create-font
size
(send font get-family)
@ -108,7 +109,8 @@
weight
#f
'default
#t)))
#t
hinting)))
(define text
(case-lambda
@ -142,25 +144,26 @@
(cond
[(null? style)
(send the-font-list find-or-create-font
size 'default 'normal 'normal #f 'default #t)]
size 'default 'normal 'normal #f 'default #t 'unaligned)]
[(is-a? style font%)
style]
[(memq style families)
(send the-font-list find-or-create-font
size style 'normal 'normal #f 'default #t)]
size style 'normal 'normal #f 'default #t 'unaligned)]
[(string? style)
(send the-font-list find-or-create-font
size style 'default 'normal 'normal #f 'default #t)]
size style 'default 'normal 'normal #f 'default #t 'unaligned)]
[(and (pair? style)
(string? (car style))
(memq (cdr style) families))
(send the-font-list find-or-create-font
size (car style) (cdr style) 'normal 'normal #f 'default #t)]
size (car style) (cdr style) 'normal 'normal #f 'default #t 'unaligned)]
[(and (pair? style)
(memq (car style)
'(superscript
subscript
bold italic)))
bold italic
aligned unaligned)))
(let ([font (loop (cdr style))]
[style (car style)])
(cond
@ -168,12 +171,21 @@
(extend-font font
(send font get-point-size)
(send font get-style)
'bold)]
'bold
(send font get-hinting))]
[(eq? style 'italic)
(extend-font font
(send font get-point-size)
'italic
(send font get-weight))]
(send font get-weight)
(send font get-hinting))]
[(or (eq? style 'aligned)
(eq? style 'unaligned))
(extend-font font
(send font get-point-size)
(send font get-style)
(send font get-weight)
style)]
[else font]))]
[(and (pair? style)
(memq (car style) '(combine no-combine)))
@ -194,7 +206,8 @@
(extend-font font
(floor (* 6/10 (send font get-point-size)))
(send font get-style)
(send font get-weight))
(send font get-weight)
(send font get-hinting))
font)]
[dc (dc-for-text-size)])
(unless dc