racket/draw: add text-outline' to dc-path%'

This commit is contained in:
Matthew Flatt 2011-12-25 18:16:49 -06:00
parent 9bef0204bb
commit 6c5c170565
7 changed files with 155 additions and 21 deletions

View File

@ -7,12 +7,14 @@
"fmod.rkt"
"point.rkt"
"transform.rkt"
"font.rkt"
(only-in scheme/base
[append s:append]
[reverse s:reverse]))
(provide dc-path%
do-path)
(protect-out do-path
set-text-to-path!))
(define-local-member-name
get-closed-points
@ -22,6 +24,9 @@
(define 2pi (* 2.0 pi))
(define pi/2 (/ pi 2.0))
(define text-to-path #f)
(define (set-text-to-path! proc) (set! text-to-path proc))
(define dc-path%
(class object%
;; A path is a list of pairs and vectors:
@ -277,6 +282,19 @@
(do-arc x y w h 0 2pi #f)
(close))
(def/public (text-outline [font% font] [string? str] [real? x] [real? y] [any? [combine? #f]])
(when (open?) (close))
(let ([p (text-to-path font str x y combine?)])
(for ([a (in-list p)])
(case (car a)
[(move) (move-to (cadr a) (caddr a))]
[(line) (line-to (cadr a) (caddr a))]
[(curve) (curve-to (cadr a) (caddr a)
(list-ref a 2) (list-ref a 3)
(list-ref a 4) (list-ref a 5))]
[(close) (close)])))
(close))
(def/public (scale [real? x][real? y])
(unless (and (= x 1.0) (= y 1.0))
(flatten-open!)

View File

@ -32,7 +32,8 @@
(define-local-member-name
do-set-pen!
do-set-brush!)
do-set-brush!
text-path)
(define 2pi (* 2 pi))
@ -1161,9 +1162,16 @@
(with-cr
(check-ok 'draw-text)
cr
(do-text cr #t s x y font combine? offset angle)
(do-text cr 'draw s x y font combine? offset angle)
(flush-cr)))
(define/public (text-path s x y combine?)
(with-cr
(check-ok 'draw-text)
cr
(do-text cr 'path s x y font combine? 0 0.0)
(cairo_copy_path cr)))
(define size-cache (make-weak-hasheq))
(define/private (get-size-cache desc)
@ -1231,12 +1239,12 @@
(set-font-antialias c (dc-adjust-smoothing (send font get-smoothing)))
c)))
(define/private (do-text cr draw? 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)
s
(substring s offset))]
[blank? (string=? s "")]
[s (if (and (not draw?) blank?) " " s)]
[s (if (and (not draw-mode) blank?) " " s)]
[s (if (for/or ([c (in-string s)])
(or (eqv? c #\uFFFE) (eqv? c #\uFFFF)))
;; Since \uFFFE and \uFFFF are not supposed to be in any
@ -1244,10 +1252,10 @@
;; string to Pango:
(regexp-replace* #rx"[\uFFFE\uFFFF]" s "\uFFFD")
s)]
[rotate? (and draw? (not (zero? angle)))]
[rotate? (and draw-mode (not (zero? angle)))]
[smoothing-index (get-smoothing-index)]
[context (get-context cr smoothing-index)])
(when draw?
(when draw-mode
(when (eq? text-mode 'solid)
(unless rotate?
(let-values ([(w h d a) (do-text cr #f s 0 0 font combine? 0 0.0)])
@ -1276,7 +1284,7 @@
;; This is combine mode. It has to be a little complicated, after all,
;; because we may need to implement font substitution ourselves, which
;; breaks the string into multiple layouts.
(let loop ([s s] [draw? draw?] [measured? #f] [w 0.0] [h 0.0] [d 0.0] [a 0.0])
(let loop ([s s] [draw-mode draw-mode] [measured? #f] [w 0.0] [h 0.0] [d 0.0] [a 0.0])
(cond
[(not s)
(when rotate? (cairo_restore cr))
@ -1313,11 +1321,11 @@
(install-alternate-face (string-ref s 0) layout font desc attrs context))
(substring s (max 1 ok-count))))])
(cond
[(and draw? next-s (not measured?))
[(and draw-mode next-s (not measured?))
;; It's going to take multiple layouts, so first gather measurements.
(let-values ([(w2 h d a) (loop s #f #f w h d a)])
;; draw again, supplying `h', `d', and `a' for the whole line
(loop s #t #t w h d a))]
(loop s draw-mode #t w h d a))]
[else
(let ([logical (make-PangoRectangle 0 0 0 0)])
(pango_layout_get_extents layout #f logical)
@ -1325,16 +1333,19 @@
[nd (/ (- (PangoRectangle-height logical)
(pango_layout_get_baseline layout))
(exact->inexact PANGO_SCALE))])
(when draw?
(when draw-mode
(let ([bl (if measured? (- h d) (- nh nd))])
(pango_layout_get_extents layout #f logical)
(cairo_move_to cr
(text-align-x/delta (+ x w) 0)
(text-align-y/delta (+ y bl) 0))
;; Draw the text:
(pango_cairo_show_layout_line cr (pango_layout_get_line_readonly layout 0))))
(let ([line (pango_layout_get_line_readonly layout 0)])
(if (eq? draw-mode 'draw)
(pango_cairo_show_layout_line cr line)
(pango_cairo_layout_line_path cr line)))))
(cond
[(and draw? (not next-s))
[(and draw-mode (not next-s))
(g_object_unref layout)
(when rotate? (cairo_restore cr))]
[else
@ -1342,7 +1353,7 @@
0.0
(integral (/ (PangoRectangle-width logical) (exact->inexact PANGO_SCALE))))]
[na 0.0])
(loop next-s measured? draw? (+ w nw) (max h nh) (max d nd) (max a na)))])))])))]))
(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
;; object.
(let ([cache (if (or combine?
@ -1397,7 +1408,7 @@
;; character or if we're just measuring text.
(begin0
(unless (and
draw?
(eq? draw-mode 'draw)
cache
(not attrs) ; fast path doesn't handle underline
((string-length s) . > . 1)
@ -1489,13 +1500,16 @@
;; unrounded height, for slow-path alignment
flh)))
(values lw lh ld la flh)))))))])
(when draw?
(when draw-mode
(cairo_move_to cr
(text-align-x/delta (+ x w) 0)
(let ([bl (- flh ld)])
(text-align-y/delta (+ y bl) 0)))
;; Here's the draw command, which uses most of the time in this mode:
(pango_cairo_show_layout_line cr (pango_layout_get_line_readonly layout 0)))
(let ([line (pango_layout_get_line_readonly layout 0)])
(if (eq? draw-mode 'draw)
(pango_cairo_show_layout_line cr line)
(pango_cairo_layout_line_path cr line))))
(values (if blank? 0.0 (+ w lw)) (max h lh) (max d ld) (max a la))))))
(when rotate? (cairo_restore cr))))))))
@ -1518,7 +1532,7 @@
(vector-set! vec 2 #f)
(vector-set! vec 3 #f)
(vector-set! vec 4 #f)))))
(def/public (start-doc [string? desc])
(check-ok 'start-doc))
(def/public (end-doc)
@ -1819,3 +1833,14 @@
(void))
dc%)
(set-text-to-path!
(lambda (font str x y combine?)
(define tmp-bm (make-object bitmap% 10 10))
(define tmp-dc (make-object -bitmap-dc% tmp-bm))
(send tmp-dc set-font font)
(define path (send tmp-dc text-path str x y combine?))
(begin0
(cairo-path->list path)
(cairo_path_destroy path))))

View File

@ -428,3 +428,59 @@
CAIRO_FILTER_GAUSSIAN)
(define/provide CAIRO_CONTENT_COLOR_ALPHA #x3000)
;; ----------------------------------------
(define-cstruct _cairo_path_data_t_header ([type _int]
[length _int]))
(define-cstruct _cairo_path_data_t_point ([x _double]
[y _double]))
(define _cairo_path_data_t (_union
_cairo_path_data_t_header
_cairo_path_data_t_point))
(define-cstruct _cairo_path_t ([status _int]
[data _pointer]
[num_data _int]))
(define-cairo cairo_path_destroy (_fun _cairo_path_t-pointer -> _void)
#:wrap (deallocator))
(define-cairo cairo_copy_path (_fun _cairo_t -> _cairo_path_t-pointer)
#:wrap (allocator cairo_path_destroy))
(define-enum 0
CAIRO_PATH_MOVE_TO
CAIRO_PATH_LINE_TO
CAIRO_PATH_CURVE_TO
CAIRO_PATH_CLOSE_PATH)
(provide cairo-path->list)
(define (cairo-path->list path)
(define len (cairo_path_t-num_data path))
(define data (cairo_path_t-data path))
(let loop ([i 0])
(if (= i len)
null
(let ([h (union-ref (ptr-ref data _cairo_path_data_t i) 0)])
(cons (let ([t (cairo_path_data_t_header-type h)])
(cond
[(or (= t CAIRO_PATH_MOVE_TO)
(= t CAIRO_PATH_LINE_TO))
(define a (union-ref (ptr-ref data _cairo_path_data_t (add1 i)) 1))
(list (if (= t CAIRO_PATH_MOVE_TO) 'move 'line)
(cairo_path_data_t_point-x a)
(cairo_path_data_t_point-y a))]
[(= t CAIRO_PATH_CURVE_TO)
(define a (union-ref (ptr-ref data _cairo_path_data_t (+ 1 i)) 1))
(define b (union-ref (ptr-ref data _cairo_path_data_t (+ 2 i)) 1))
(define c (union-ref (ptr-ref data _cairo_path_data_t (+ 3 i)) 1))
(list 'curve
(cairo_path_data_t_point-x a) (cairo_path_data_t_point-y a)
(cairo_path_data_t_point-x b) (cairo_path_data_t_point-y b)
(cairo_path_data_t_point-x c) (cairo_path_data_t_point-y c))]
[(= t CAIRO_PATH_CLOSE_PATH)
'(close)]))
(loop (+ i (cairo_path_data_t_header-length h))))))))

View File

@ -185,6 +185,7 @@
(define-pangocairo pango_cairo_show_layout (_pfun _cairo_t PangoLayout -> _void))
(define-pangocairo pango_cairo_show_layout_line (_pfun _cairo_t PangoLayoutLine -> _void))
(define-pangocairo pango_cairo_show_glyph_string (_pfun _cairo_t PangoFont _PangoGlyphString-pointer -> _void))
(define-pangocairo pango_cairo_layout_line_path (_pfun _cairo_t PangoLayoutLine -> _void))
(define-pango pango_layout_iter_free (_pfun PangoLayoutIter -> _void)
#:wrap (deallocator))

View File

@ -264,6 +264,21 @@ If @racket[radius] is less than @racket[-0.5] or more than half of
}
@defmethod[(text-outline [font (is-a?/c font%)]
[str string?]
[x real?]
[y real?]
[combine? any/c #f])
void?]{
Closes the @tech{open sub-path}, if any, and adds a @tech{closed
sub-path} to outline @racket[str] using @racket[font]. The
top left of the text is positioned at @racket[x] and @racket[y]. The
@racket[combine?] argument enables kerning and character combinations
as for @xmethod[dc<%> draw-text].
}
@defmethod[(translate [x real?]
[y real?])
void?]{

View File

@ -659,6 +659,17 @@
(loop (cdr fam) (cdr stl) (cdr wgt) (cdr sze) x (+ y h) #f)))))
(send dc set-pen save-pen)))
;; Text paths:
(let ([p (make-object dc-path%)]
[old-pen (send dc get-pen)]
[old-brush (send dc get-brush)])
(send p text-outline (make-font #:size 32) "A" 360 190)
(send dc set-pen "black" 1 'solid)
(send dc set-brush "pink" 'solid)
(send dc draw-path p)
(send dc set-pen old-pen)
(send dc set-brush old-brush))
; Bitmap copying:
(when (and (not no-bitmaps?) last?)
(let ([x 5] [y 165])
@ -1080,6 +1091,11 @@
[(lam) (let ([r (make-object region% clip-dc)])
(send r set-path lambda-path)
(send dc set-clipping-region r))]
[(A) (let ([p (new dc-path%)]
[r (make-object region% clip-dc)])
(send p text-outline (make-font #:size 256) "A" 10 10)
(send r set-path p)
(send dc set-clipping-region r))]
[(rect+poly) (let ([r (mk-poly 'winding)])
(send r union (mk-rect))
(send dc set-clipping-region r))]
@ -1161,7 +1177,8 @@
(let*-values ([(x y w h) (send r get-bounding-box)]
[(l) (list x y w h)]
[(=~) (lambda (x y)
(<= (- x 2) y (+ x 2)))])
(or (not y)
(<= (- x 2) y (+ x 2))))])
(unless (andmap =~ l
(let ([l
(case clip
@ -1170,6 +1187,7 @@
[(poly circle poly-rect) '(0. 60. 180. 180.)]
[(wedge) '(26. 60. 128. 90.)]
[(lam) '(58. 10. 202. 281.)]
[(A) '(#f #f #f #f)]
[(rect+poly rect+circle poly^rect) '(0. -25. 180. 400.)]
[(poly&rect) '(100. 60. 10. 180.)]
[(roundrect) '(80. 200. 125. 40.)]
@ -1293,14 +1311,14 @@
(send canvas set-kern (send self get-value))))
(make-object choice% "Clip"
'("None" "Rectangle" "Rectangle2" "Octagon"
"Circle" "Wedge" "Round Rectangle" "Lambda"
"Circle" "Wedge" "Round Rectangle" "Lambda" "A"
"Rectangle + Octagon" "Rectangle + Circle"
"Octagon - Rectangle" "Rectangle & Octagon" "Rectangle ^ Octagon" "Polka"
"Empty")
hp3
(lambda (self event)
(set! clip (list-ref
'(none rect rect2 poly circle wedge roundrect lam
'(none rect rect2 poly circle wedge roundrect lam A
rect+poly rect+circle poly-rect poly&rect poly^rect
polka empty)
(send self get-selection)))

View File

@ -1,5 +1,6 @@
Version 5.2.0.7
Intern strings, etc., only in read-syntax mode, not read mode
racket/draw: add text-outline to dc-path%
Version 5.2.0.6
Added pseudo-random-generator-vector?