A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/*
2 * drawing.c: Intermediary between the drawing interface as
3 * presented to the back end, and that implemented by the front
4 * end.
5 *
6 * Mostly just looks up calls in a vtable and passes them through
7 * unchanged. However, on the printing side it tracks print colours
8 * so the front end API doesn't have to.
9 *
10 * FIXME:
11 *
12 * - I'd _like_ to do automatic draw_updates, but it's a pain for
13 * draw_text in particular. I'd have to invent a front end API
14 * which retrieved the text bounds.
15 * + that might allow me to do the alignment centrally as well?
16 * * perhaps not, because PS can't return this information,
17 * so there would have to be a special case for it.
18 * + however, that at least doesn't stand in the way of using
19 * the text bounds for draw_update, because PS doesn't need
20 * draw_update since it's printing-only. Any _interactive_
21 * drawing API couldn't get away with refusing to tell you
22 * what parts of the screen a text draw had covered, because
23 * you would inevitably need to erase it later on.
24 */
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <assert.h>
30#ifdef NO_TGMATH_H
31# include <math.h>
32#else
33# include <tgmath.h>
34#endif
35
36#include "puzzles.h"
37
38struct print_colour {
39 int hatch;
40 int hatch_when; /* 0=never 1=only-in-b&w 2=always */
41 float r, g, b;
42 float grey;
43};
44
45typedef struct drawing_internal {
46 /* we implement data hiding by casting `struct drawing*` pointers
47 * to `struct drawing_internal*` */
48 struct drawing pub;
49
50 /* private data */
51 struct print_colour *colours;
52 int ncolours, coloursize;
53 float scale;
54 /* `me' is only used in status_bar(), so print-oriented instances of
55 * this may set it to NULL. */
56 midend *me;
57 char *laststatus;
58} drawing_internal;
59
60#define PRIVATE_CAST(dr) ((drawing_internal*)(dr))
61#define PUBLIC_CAST(dri) ((drawing*)(dri))
62
63/* See puzzles.h for a description of the version number. */
64#define DRAWING_API_VERSION 1
65
66drawing *drawing_new(const drawing_api *api, midend *me, void *handle)
67{
68 if(api->version != DRAWING_API_VERSION) {
69 fatal("Drawing API version mismatch: expected: %d, actual: %d\n", DRAWING_API_VERSION, api->version);
70 /* shouldn't get here */
71 return NULL;
72 }
73
74 drawing_internal *dri = snew(drawing_internal);
75 dri->pub.api = api;
76 dri->pub.handle = handle;
77 dri->colours = NULL;
78 dri->ncolours = dri->coloursize = 0;
79 dri->scale = 1.0F;
80 dri->me = me;
81 dri->laststatus = NULL;
82 return PUBLIC_CAST(dri);
83}
84
85void drawing_free(drawing *dr)
86{
87 drawing_internal *dri = PRIVATE_CAST(dr);
88 sfree(dri->laststatus);
89 sfree(dri->colours);
90 sfree(dri);
91}
92
93void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize,
94 int align, int colour, const char *text)
95{
96 drawing_internal *dri = PRIVATE_CAST(dr);
97 dri->pub.api->draw_text(dr, x, y, fonttype, fontsize, align,
98 colour, text);
99}
100
101void draw_rect(drawing *dr, int x, int y, int w, int h, int colour)
102{
103 drawing_internal *dri = PRIVATE_CAST(dr);
104 dri->pub.api->draw_rect(dr, x, y, w, h, colour);
105}
106
107void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour)
108{
109 drawing_internal *dri = PRIVATE_CAST(dr);
110 dri->pub.api->draw_line(dr, x1, y1, x2, y2, colour);
111}
112
113void draw_thick_line(drawing *dr, float thickness,
114 float x1, float y1, float x2, float y2, int colour)
115{
116 drawing_internal *dri = PRIVATE_CAST(dr);
117 if (thickness < 1.0F)
118 thickness = 1.0F;
119 if (dri->pub.api->draw_thick_line) {
120 dri->pub.api->draw_thick_line(dr, thickness,
121 x1, y1, x2, y2, colour);
122 } else {
123 /* We'll fake it up with a filled polygon. The tweak to the
124 * thickness empirically compensates for rounding errors, because
125 * polygon rendering uses integer coordinates.
126 */
127 float len = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
128 float tvhatx = (x2 - x1)/len * (thickness/2 - 0.2F);
129 float tvhaty = (y2 - y1)/len * (thickness/2 - 0.2F);
130 int p[8];
131
132 p[0] = x1 - tvhaty;
133 p[1] = y1 + tvhatx;
134 p[2] = x2 - tvhaty;
135 p[3] = y2 + tvhatx;
136 p[4] = x2 + tvhaty;
137 p[5] = y2 - tvhatx;
138 p[6] = x1 + tvhaty;
139 p[7] = y1 - tvhatx;
140 dri->pub.api->draw_polygon(dr, p, 4, colour, colour);
141 }
142}
143
144void draw_polygon(drawing *dr, const int *coords, int npoints,
145 int fillcolour, int outlinecolour)
146{
147 drawing_internal *dri = PRIVATE_CAST(dr);
148 dri->pub.api->draw_polygon(dr, coords, npoints, fillcolour,
149 outlinecolour);
150}
151
152void draw_circle(drawing *dr, int cx, int cy, int radius,
153 int fillcolour, int outlinecolour)
154{
155 drawing_internal *dri = PRIVATE_CAST(dr);
156 dri->pub.api->draw_circle(dr, cx, cy, radius, fillcolour,
157 outlinecolour);
158}
159
160void draw_update(drawing *dr, int x, int y, int w, int h)
161{
162 drawing_internal *dri = PRIVATE_CAST(dr);
163 if (dri->pub.api->draw_update)
164 dri->pub.api->draw_update(dr, x, y, w, h);
165}
166
167void clip(drawing *dr, int x, int y, int w, int h)
168{
169 drawing_internal *dri = PRIVATE_CAST(dr);
170 dri->pub.api->clip(dr, x, y, w, h);
171}
172
173void unclip(drawing *dr)
174{
175 drawing_internal *dri = PRIVATE_CAST(dr);
176 dri->pub.api->unclip(dr);
177}
178
179void start_draw(drawing *dr)
180{
181 drawing_internal *dri = PRIVATE_CAST(dr);
182 dri->pub.api->start_draw(dr);
183}
184
185void end_draw(drawing *dr)
186{
187 drawing_internal *dri = PRIVATE_CAST(dr);
188 dri->pub.api->end_draw(dr);
189}
190
191char *text_fallback(drawing *dr, const char *const *strings, int nstrings)
192{
193 drawing_internal *dri = PRIVATE_CAST(dr);
194 int i;
195
196 /*
197 * If the drawing implementation provides one of these, use it.
198 */
199 if (dr && dri->pub.api->text_fallback)
200 return dri->pub.api->text_fallback(dr, strings, nstrings);
201
202 /*
203 * Otherwise, do the simple thing and just pick the first string
204 * that fits in plain ASCII. It will then need no translation
205 * out of UTF-8.
206 */
207 for (i = 0; i < nstrings; i++) {
208 const char *p;
209
210 for (p = strings[i]; *p; p++)
211 if (*p & 0x80)
212 break;
213 if (!*p)
214 return dupstr(strings[i]);
215 }
216
217 /*
218 * The caller was responsible for making sure _some_ string in
219 * the list was in plain ASCII.
220 */
221 assert(!"Should never get here");
222 return NULL; /* placate optimiser */
223}
224
225void status_bar(drawing *dr, const char *text)
226{
227 drawing_internal *dri = PRIVATE_CAST(dr);
228 char *rewritten;
229
230 if (!dri->pub.api->status_bar)
231 return;
232
233 assert(dri->me);
234
235 rewritten = midend_rewrite_statusbar(dri->me, text);
236 if (!dri->laststatus || strcmp(rewritten, dri->laststatus)) {
237 dri->pub.api->status_bar(dr, rewritten);
238 sfree(dri->laststatus);
239 dri->laststatus = rewritten;
240 } else {
241 sfree(rewritten);
242 }
243}
244
245blitter *blitter_new(drawing *dr, int w, int h)
246{
247 drawing_internal *dri = PRIVATE_CAST(dr);
248 return dri->pub.api->blitter_new(dr, w, h);
249}
250
251void blitter_free(drawing *dr, blitter *bl)
252{
253 drawing_internal *dri = PRIVATE_CAST(dr);
254 dri->pub.api->blitter_free(dr, bl);
255}
256
257void blitter_save(drawing *dr, blitter *bl, int x, int y)
258{
259 drawing_internal *dri = PRIVATE_CAST(dr);
260 dri->pub.api->blitter_save(dr, bl, x, y);
261}
262
263void blitter_load(drawing *dr, blitter *bl, int x, int y)
264{
265 drawing_internal *dri = PRIVATE_CAST(dr);
266 dri->pub.api->blitter_load(dr, bl, x, y);
267}
268
269void print_begin_doc(drawing *dr, int pages)
270{
271 drawing_internal *dri = PRIVATE_CAST(dr);
272 dri->pub.api->begin_doc(dr, pages);
273}
274
275void print_begin_page(drawing *dr, int number)
276{
277 drawing_internal *dri = PRIVATE_CAST(dr);
278 dri->pub.api->begin_page(dr, number);
279}
280
281void print_begin_puzzle(drawing *dr, float xm, float xc,
282 float ym, float yc, int pw, int ph, float wmm,
283 float scale)
284{
285 drawing_internal *dri = PRIVATE_CAST(dr);
286 dri->scale = scale;
287 dri->ncolours = 0;
288 dri->pub.api->begin_puzzle(dr, xm, xc, ym, yc, pw, ph, wmm);
289}
290
291void print_end_puzzle(drawing *dr)
292{
293 drawing_internal *dri = PRIVATE_CAST(dr);
294 dri->pub.api->end_puzzle(dr);
295 dri->scale = 1.0F;
296}
297
298void print_end_page(drawing *dr, int number)
299{
300 drawing_internal *dri = PRIVATE_CAST(dr);
301 dri->pub.api->end_page(dr, number);
302}
303
304void print_end_doc(drawing *dr)
305{
306 drawing_internal *dri = PRIVATE_CAST(dr);
307 dri->pub.api->end_doc(dr);
308}
309
310void print_get_colour(drawing *dr, int colour, bool printing_in_colour,
311 int *hatch, float *r, float *g, float *b)
312{
313 drawing_internal *dri = PRIVATE_CAST(dr);
314 assert(colour >= 0 && colour < dri->ncolours);
315 if (dri->colours[colour].hatch_when == 2 ||
316 (dri->colours[colour].hatch_when == 1 && !printing_in_colour)) {
317 *hatch = dri->colours[colour].hatch;
318 } else {
319 *hatch = -1;
320 if (printing_in_colour) {
321 *r = dri->colours[colour].r;
322 *g = dri->colours[colour].g;
323 *b = dri->colours[colour].b;
324 } else {
325 *r = *g = *b = dri->colours[colour].grey;
326 }
327 }
328}
329
330static int print_generic_colour(drawing *dr, float r, float g, float b,
331 float grey, int hatch, int hatch_when)
332{
333 drawing_internal *dri = PRIVATE_CAST(dr);
334 if (dri->ncolours >= dri->coloursize) {
335 dri->coloursize = dri->ncolours + 16;
336 dri->colours = sresize(dri->colours, dri->coloursize,
337 struct print_colour);
338 }
339 dri->colours[dri->ncolours].hatch = hatch;
340 dri->colours[dri->ncolours].hatch_when = hatch_when;
341 dri->colours[dri->ncolours].r = r;
342 dri->colours[dri->ncolours].g = g;
343 dri->colours[dri->ncolours].b = b;
344 dri->colours[dri->ncolours].grey = grey;
345 return dri->ncolours++;
346}
347
348int print_mono_colour(drawing *dr, int grey)
349{
350 return print_generic_colour(dr, grey, grey, grey, grey, -1, 0);
351}
352
353int print_grey_colour(drawing *dr, float grey)
354{
355 return print_generic_colour(dr, grey, grey, grey, grey, -1, 0);
356}
357
358int print_hatched_colour(drawing *dr, int hatch)
359{
360 return print_generic_colour(dr, 0, 0, 0, 0, hatch, 2);
361}
362
363int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int grey)
364{
365 return print_generic_colour(dr, r, g, b, grey, -1, 0);
366}
367
368int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey)
369{
370 return print_generic_colour(dr, r, g, b, grey, -1, 0);
371}
372
373int print_rgb_hatched_colour(drawing *dr, float r, float g, float b, int hatch)
374{
375 return print_generic_colour(dr, r, g, b, 0, hatch, 1);
376}
377
378void print_line_width(drawing *dr, int width)
379{
380 drawing_internal *dri = PRIVATE_CAST(dr);
381
382 /*
383 * I don't think it's entirely sensible to have line widths be
384 * entirely relative to the puzzle size; there is a point
385 * beyond which lines are just _stupidly_ thick. On the other
386 * hand, absolute line widths aren't particularly nice either
387 * because they start to feel a bit feeble at really large
388 * scales.
389 *
390 * My experimental answer is to scale line widths as the
391 * _square root_ of the main puzzle scale. Double the puzzle
392 * size, and the line width multiplies by 1.4.
393 */
394 dri->pub.api->line_width(dr, (float)sqrt(dri->scale) * width);
395}
396
397void print_line_dotted(drawing *dr, bool dotted)
398{
399 drawing_internal *dri = PRIVATE_CAST(dr);
400 dri->pub.api->line_dotted(dr, dotted);
401}