-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgg-backend-libplot.scm
More file actions
360 lines (305 loc) · 12.7 KB
/
gg-backend-libplot.scm
File metadata and controls
360 lines (305 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
;; gg-backend-libplot.scm
;; libplot wrapper backend for the Grammar of Graphics library.
;;
;; Provides a gg-backend-protocol-compliant object wrapping the existing
;; CHICKEN `plot` egg (GNU libplot bindings). This exists to allow the
;; rendering pipeline in gg-primitives.scm and gg-plot.scm to be migrated
;; to the backend abstraction without breaking the libplot code path.
;;
;; Known limitations:
;;
;; - No transparency: alpha components of colors are silently ignored.
;;
;; - Font rendering quality depends on how the `plot` egg was compiled.
;; Standard Debian/Ubuntu packages only provide
;; Hershey vector fonts rather than FreeType-rendered system fonts.
;;
;; - Clipping (backend/set-clip-rect!) is not supported; calls are no-ops.
;;
;; - backend/set-dash! is not implemented; calls are no-ops.
;;
;; - libplot's savestate/restorestate does NOT save the fspace coordinate
;; transform. We re-apply (fspace) after every pop-state! call.
;;
;; - backend/text-extents returns heuristic estimates (not measured).
;;
;; - backend/get-width / backend/get-height return the values supplied
;; at construction time; libplot does not expose them at runtime.
(module gg-backend-libplot
(make-libplot-png-backend
make-libplot-svg-backend
make-libplot-ps-backend
make-libplot-generic-backend)
(import scheme (chicken base) (chicken string)
yasos
gg-backend
plot) ;; GNU libplot bindings
;;; ================================================================
;;; Color conversion: `color` -> libplot integer RGB (0–65535)
;;; ================================================================
(define (color->pl-rgb col)
"Returns (values r16 g16 b16). Alpha is discarded."
(call-with-values
(lambda () (color->rgba-values col))
(lambda (r g b _a)
(values (inexact->exact (round (* r 65535.0)))
(inexact->exact (round (* g 65535.0)))
(inexact->exact (round (* b 65535.0)))))))
;;; ================================================================
;;; libplot viewport record
;;; We store the fspace parameters and reapply after pop-state!,
;;; because libplot savestate does NOT save the coordinate transform.
;;; ================================================================
(define-record-type <pl-viewport>
(make-pl-vp x0 y0 x1 y1)
pl-vp?
(x0 pl-vp-x0) (y0 pl-vp-y0)
(x1 pl-vp-x1) (y1 pl-vp-y1))
(define (pl-vp-translate vp dx dy)
(make-pl-vp (+ (pl-vp-x0 vp) dx) (+ (pl-vp-y0 vp) dy)
(+ (pl-vp-x1 vp) dx) (+ (pl-vp-y1 vp) dy)))
;;; Apply viewport to plotter via (fspace).
(define (pl-apply-vp! plotter vp)
(fspace plotter
(pl-vp-x0 vp) (pl-vp-y0 vp)
(pl-vp-x1 vp) (pl-vp-y1 vp)))
;;; ================================================================
;;; Draw-state record
;;; ================================================================
(define-record-type <pl-draw-state>
(%make-pl-ds viewport pen fill font-name font-size)
pl-draw-state?
(viewport pl-ds-viewport pl-ds-viewport-set!)
(pen pl-ds-pen pl-ds-pen-set!)
(fill pl-ds-fill pl-ds-fill-set!)
(font-name pl-ds-font-name pl-ds-font-name-set!)
(font-size pl-ds-font-size pl-ds-font-size-set!))
(define (copy-pl-ds ds)
(%make-pl-ds (pl-ds-viewport ds) (pl-ds-pen ds)
(pl-ds-fill ds) (pl-ds-font-name ds)
(pl-ds-font-size ds)))
(define (make-default-pl-ds)
(%make-pl-ds (make-pl-vp 0.0 0.0 1000.0 1000.0)
color-black color-white
"HersheySerif" 12.0))
;;; ================================================================
;;; Backend object
;;; ================================================================
(define (make-libplot-generic-backend make-plotter-thunk width height)
"Low-level constructor. make-plotter-thunk is a zero-argument
procedure that returns an un-opened libplot plotter."
(let ((plotter #f)
(ds #f)
(stack '()) ;; list of saved pl-draw-state
(w* (exact->inexact width))
(h* (exact->inexact height)))
;; Helpers
(define (apply-pen! col)
(call-with-values (lambda () (color->pl-rgb col))
(lambda (r g b) (pencolor plotter r g b))))
(define (apply-fill! col)
(call-with-values (lambda () (color->pl-rgb col))
(lambda (r g b) (fillcolor plotter r g b))))
(object
((graphics-backend? self) #t)
;; Lifecycle
((backend/open! self)
(set! plotter (make-plotter-thunk))
(openpl plotter)
(set! ds (make-default-pl-ds))
(set! stack '())
(pl-apply-vp! plotter (pl-ds-viewport ds))
(apply-pen! (pl-ds-pen ds))
(apply-fill! (pl-ds-fill ds)))
((backend/close! self)
(when plotter
(closepl plotter)
(delete-plotter plotter)
(set! plotter #f)
(set! ds #f)))
;; State stack
;;
;; libplot savestate saves drawing attributes (colors, line width,
;; font, fill type) but NOT the fspace coordinate transform.
;; We save both and re-apply fspace after restorestate.
((backend/push-state! self)
(savestate plotter)
(set! stack (cons (copy-pl-ds ds) stack)))
((backend/pop-state! self)
(when (null? stack)
(error "backend/pop-state!: state stack underflow"))
(restorestate plotter)
(set! ds (car stack))
(set! stack (cdr stack))
;; Re-establish coordinate space (not saved by libplot)
(pl-apply-vp! plotter (pl-ds-viewport ds)))
;; Viewport
((backend/set-viewport! self x0 y0 x1 y1)
(let ((vp (make-pl-vp (exact->inexact x0) (exact->inexact y0)
(exact->inexact x1) (exact->inexact y1))))
(pl-ds-viewport-set! ds vp)
(pl-apply-vp! plotter vp)))
((backend/translate! self dx dy)
(let ((vp (pl-vp-translate (pl-ds-viewport ds)
(exact->inexact dx)
(exact->inexact dy))))
(pl-ds-viewport-set! ds vp)
(pl-apply-vp! plotter vp)))
;; Clipping
;; libplot has no explicit clip rectangle API; treat as no-op.
((backend/set-clip-rect! self x y w h) (void))
((backend/reset-clip! self) (void))
;; Style
((backend/set-pen-color! self col)
(let ((c (parse-color col)))
(pl-ds-pen-set! ds c)
(apply-pen! c)))
((backend/set-fill-color! self col)
(let ((c (parse-color col)))
(pl-ds-fill-set! ds c)
(apply-fill! c)))
((backend/set-line-width! self w)
(linewidth plotter (exact->inexact w)))
;; libplot dash support is limited; no-op for now.
((backend/set-dash! self dashes offset) (void))
;; libplot font: slant and weight are encoded in the font name.
;; We store the family for text-extents estimation and use fontname.
((backend/set-font! self family size slant weight)
(let ((name (match (list slant weight)
(('italic 'bold) (string-append family "-BoldItalic"))
(('italic _) (string-append family "-Italic"))
((_ 'bold) (string-append family "-Bold"))
(_ family))))
(pl-ds-font-name-set! ds name)
(pl-ds-font-size-set! ds (exact->inexact size))
(fontname plotter name)
(fontsize plotter (exact->inexact size))))
;; Stroked primitives
((backend/draw-line! self x1 y1 x2 y2)
(apply-pen! (pl-ds-pen ds))
(fline plotter
(exact->inexact x1) (exact->inexact y1)
(exact->inexact x2) (exact->inexact y2)))
((backend/draw-polyline! self pts)
(when (pair? pts)
(apply-pen! (pl-ds-pen ds))
(fmove plotter (exact->inexact (caar pts)) (exact->inexact (cdar pts)))
(for-each
(lambda (pt)
(fcont plotter (exact->inexact (car pt)) (exact->inexact (cdr pt))))
(cdr pts))
(endpath plotter)))
((backend/draw-rect! self x y w h)
(apply-pen! (pl-ds-pen ds))
(filltype plotter 0)
;; libplot fbox takes two corners, not (x y w h)
(fbox plotter
(exact->inexact x) (exact->inexact y)
(exact->inexact (+ x w)) (exact->inexact (+ y h))))
((backend/draw-circle! self cx cy r)
(apply-pen! (pl-ds-pen ds))
(filltype plotter 0)
(fcircle plotter
(exact->inexact cx) (exact->inexact cy) (exact->inexact r)))
((backend/draw-polygon! self pts)
(when (pair? pts)
(apply-pen! (pl-ds-pen ds))
(filltype plotter 0)
(fmove plotter (exact->inexact (caar pts)) (exact->inexact (cdar pts)))
(for-each
(lambda (pt)
(fcont plotter (exact->inexact (car pt)) (exact->inexact (cdr pt))))
(cdr pts))
(endpath plotter)))
;; ── Filled primitives ────────────────────────────────────
((backend/draw-filled-rect! self x y w h)
;; Fill pass
(apply-fill! (pl-ds-fill ds))
(filltype plotter 1)
(fbox plotter
(exact->inexact x) (exact->inexact y)
(exact->inexact (+ x w)) (exact->inexact (+ y h)))
;; Stroke pass
(apply-pen! (pl-ds-pen ds))
(filltype plotter 0)
(fbox plotter
(exact->inexact x) (exact->inexact y)
(exact->inexact (+ x w)) (exact->inexact (+ y h))))
((backend/draw-filled-circle! self cx cy r)
(apply-fill! (pl-ds-fill ds))
(filltype plotter 1)
(fcircle plotter
(exact->inexact cx) (exact->inexact cy) (exact->inexact r)))
((backend/draw-filled-polygon! self pts)
(when (pair? pts)
(apply-fill! (pl-ds-fill ds))
(filltype plotter 1)
(fmove plotter (exact->inexact (caar pts)) (exact->inexact (cdar pts)))
(for-each
(lambda (pt)
(fcont plotter (exact->inexact (car pt)) (exact->inexact (cdr pt))))
(cdr pts))
(endpath plotter)))
;; ── General path ─────────────────────────────────────────
;; libplot paths use fmove/fcont/endpath.
;; path:curve-to and path:arc are not natively supported;
;; they are silently skipped. Upgrade to Cairo for these.
((backend/draw-path! self cmds filled?)
(when filled? (apply-fill! (pl-ds-fill ds)) (filltype plotter 1))
(apply-pen! (pl-ds-pen ds))
(let loop ((cmds cmds) (open? #f))
(if (null? cmds)
(when open? (endpath plotter))
(cases path-cmd (car cmds)
((path:move-to x y)
(when open? (endpath plotter))
(fmove plotter (exact->inexact x) (exact->inexact y))
(loop (cdr cmds) #t))
((path:line-to x y)
(fcont plotter (exact->inexact x) (exact->inexact y))
(loop (cdr cmds) #t))
((path:close)
(endpath plotter)
(loop (cdr cmds) #f))
(else
;; curve-to / arc: not supported in libplot; skip
(loop (cdr cmds) open?))))))
;; Text
;; libplot places text at the current pen position via (label).
;; Horizontal alignment is approximate; (textangle) supports rotation.
;; Vertical alignment below baseline is not available.
((backend/draw-text! self x y text halign valign)
(apply-pen! (pl-ds-pen ds))
(fmove plotter (exact->inexact x) (exact->inexact y))
(label plotter text))
;; text-extents: heuristic only (libplot has no query API)
((backend/text-extents self text)
(let* ((size (pl-ds-font-size ds))
(w (* (string-length text) size 0.60))
(h (* size 1.2))
(asc (* size 0.8))
(dsc (* size 0.2)))
(values w h asc dsc)))
;; Dimensions
((backend/get-width self) w*)
((backend/get-height self) h*))))
;;; Convenience constructors
(define (make-libplot-png-backend port width height)
(make-libplot-generic-backend
(lambda ()
(make-plotter (PNG) port
(list (BITMAPSIZE
(string-append
(number->string (inexact->exact (round width)))
"x"
(number->string (inexact->exact (round height))))))))
width height))
(define (make-libplot-svg-backend port width height)
(make-libplot-generic-backend
(lambda () (make-plotter (SVG) port '()))
width height))
(define (make-libplot-ps-backend port width height)
(make-libplot-generic-backend
(lambda () (make-plotter (PS) port '()))
width height))
) ;; end module gg-backend-libplot