Root/eeshow/gfx/cro.c

1/*
2 * gfx/cro.c - Cairo graphics back-end
3 *
4 * Written 2016 by Werner Almesberger
5 * Copyright 2016 by Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13
14#include <stddef.h>
15#include <stdbool.h>
16#include <stdint.h>
17#include <stdlib.h>
18#include <stdint.h>
19#include <unistd.h>
20#include <math.h>
21
22#include <cairo/cairo.h>
23#include <cairo/cairo-pdf.h>
24
25#include "misc/util.h"
26#include "misc/diag.h"
27#include "gfx/style.h"
28#include "gfx/text.h"
29#include "gfx/gfx.h"
30#include "gfx/record.h"
31#include "gfx/pdftoc.h"
32#include "main.h"
33#include "gfx/cro.h"
34
35
36/*
37 * FIG works with 1/1200 in
38 * KiCad works with mil
39 * 1 point = 1/72 in
40 */
41
42#define DEFAULT_SCALE (72.0 / 1200)
43
44
45struct cro_ctx {
46    struct record record; /* must be first */
47
48    int xo, yo; /* offset in target (e.g., canvas) coord */
49    float scale;
50
51    cairo_t *cr;
52    cairo_surface_t *s;
53
54    struct record *sheets; /* for PDF */
55    unsigned n_sheets;
56
57    const char *output_name;
58
59    bool add_toc;
60    struct pdftoc *toc;
61
62    int color_override; /* FIG color, COLOR_NONE if no override */
63};
64
65
66static inline int cd(const struct cro_ctx *cc, int x)
67{
68    return x * cc->scale;
69}
70
71
72static inline int dc(const struct cro_ctx *cc, int x)
73{
74    return x / cc->scale;
75}
76
77
78static inline int cx(const struct cro_ctx *cc, int x)
79{
80    return cc->xo + x * cc->scale;
81}
82
83
84static inline int cy(const struct cro_ctx *cc, int y)
85{
86    return cc->yo + y * cc->scale;
87}
88
89
90static inline float pt(const struct cro_ctx *cc, int x)
91{
92    return cd(cc, x) * 72 * 1.5 / 1200.0;
93}
94
95
96static void set_color(struct cro_ctx *cc, int color)
97{
98    uint32_t c;
99
100    if (cc->color_override != COLOR_NONE)
101        color = cc->color_override;
102    if (color < 0)
103        return;
104    c = color_rgb[color];
105    cairo_set_source_rgb(cc->cr,
106        (c >> 16) / 255.0, ((c >> 8) & 255) / 255.0, (c & 255) / 255.0);
107}
108
109
110static void paint(struct cro_ctx *cc, int color, int fill_color)
111{
112    if (fill_color != COLOR_NONE) {
113        set_color(cc, fill_color);
114        if (color == COLOR_NONE)
115            cairo_fill(cc->cr);
116        else
117            cairo_fill_preserve(cc->cr);
118    }
119    if (color != COLOR_NONE) {
120        set_color(cc, color);
121        cairo_stroke(cc->cr);
122    }
123}
124
125
126/* ----- General items ----------------------------------------------------- */
127
128
129static void cr_line(void *ctx, int sx, int sy, int ex, int ey,
130    int color, unsigned layer)
131{
132    struct cro_ctx *cc = ctx;
133    static const double dashes[] = { 2, 4 };
134
135    cairo_new_path(cc->cr);
136    cairo_move_to(cc->cr, cx(cc, sx), cy(cc, sy));
137    cairo_line_to(cc->cr, cx(cc, ex), cy(cc, ey));
138    cairo_set_dash(cc->cr, dashes, ARRAY_ELEMENTS(dashes), 0);
139    paint(cc, color, COLOR_NONE);
140    cairo_set_dash(cc->cr, NULL, 0, 0);
141}
142
143
144static void cr_poly(void *ctx,
145    int points, const int x[points], const int y[points],
146    int color, int fill_color, unsigned layer)
147{
148    struct cro_ctx *cc = ctx;
149    bool closed;
150    int i;
151
152    if (points < 2)
153        return;
154    closed = x[0] == x[points - 1] && y[0] == y[points - 1];
155
156    cairo_new_path(cc->cr);
157    cairo_move_to(cc->cr, cx(cc, x[0]), cy(cc, y[0]));
158
159    for (i = 1; i != points - closed; i++)
160        cairo_line_to(cc->cr, cx(cc, x[i]), cy(cc, y[i]));
161    if (closed)
162        cairo_close_path(cc->cr);
163
164    paint(cc, color, fill_color);
165}
166
167
168static void cr_circ(void *ctx, int x, int y, int r,
169    int color, int fill_color, unsigned layer)
170{
171    struct cro_ctx *cc = ctx;
172
173    cairo_new_path(cc->cr);
174    cairo_arc(cc->cr, cx(cc, x), cy(cc, y), cd(cc, r), 0, 2 * M_PI);
175    paint(cc, color, fill_color);
176}
177
178
179static void cr_arc(void *ctx, int x, int y, int r, int sa, int ea,
180    int color, int fill_color, unsigned layer)
181{
182    struct cro_ctx *cc = ctx;
183
184    cairo_new_path(cc->cr);
185    cairo_arc(cc->cr, cx(cc, x), cy(cc, y), cd(cc, r),
186        -ea / 180.0 * M_PI, -sa / 180.0 * M_PI);
187    if (fill_color != COLOR_NONE)
188        cairo_close_path(cc->cr);
189    paint(cc, color, fill_color);
190}
191
192
193#define TEXT_STRETCH 1.3
194
195
196static void cr_text(void *ctx, int x, int y, const char *s, unsigned size,
197    enum text_align align, int rot, unsigned color, unsigned layer)
198{
199    struct cro_ctx *cc = ctx;
200    cairo_text_extents_t ext;
201    cairo_matrix_t m;
202
203    cairo_set_font_size(cc->cr, cd(cc, size) * TEXT_STRETCH);
204    cairo_text_extents(cc->cr, s, &ext);
205
206    set_color(cc, color);
207
208    cairo_move_to(cc->cr, cx(cc, x), cy(cc, y));
209
210    cairo_get_matrix(cc->cr, &m);
211    cairo_rotate(cc->cr, -rot / 180.0 * M_PI);
212
213    switch (align) {
214    case text_min:
215        break;
216    case text_mid:
217        cairo_rel_move_to(cc->cr, -ext.width / 2.0, 0);
218        break;
219    case text_max:
220        cairo_rel_move_to(cc->cr, -ext.width, 0);
221        break;
222    default:
223        abort();
224    }
225
226    cairo_show_text(cc->cr, s);
227    cairo_set_matrix(cc->cr, &m);
228}
229
230
231static unsigned cr_text_width(void *ctx, const char *s, unsigned size)
232{
233    struct cro_ctx *cc = ctx;
234    cairo_text_extents_t ext;
235
236    cairo_set_font_size(cc->cr, cd(cc, size) * TEXT_STRETCH);
237    cairo_text_extents(cc->cr, s, &ext);
238    return dc(cc, ext.width) * 1.05; /* @@@ Cairo seems to underestimate */
239}
240
241
242/* ----- Color override ---------------------------------------------------- */
243
244
245void cro_color_override(struct cro_ctx *cc, int color)
246{
247    cc->color_override = color;
248}
249
250
251/* ----- Initialization and termination ------------------------------------ */
252
253
254static const struct gfx_ops real_cro_ops = {
255    .name = "cairo",
256    .line = cr_line,
257    .poly = cr_poly,
258    .circ = cr_circ,
259    .arc = cr_arc,
260    .text = cr_text,
261    .text_width = cr_text_width,
262};
263
264
265static struct cro_ctx *new_cc(void)
266{
267    struct cro_ctx *cc;
268
269    cc = alloc_type(struct cro_ctx);
270    cc->xo = cc->yo = 0;
271    cc->scale = DEFAULT_SCALE;
272
273    cc->sheets = NULL;
274    cc->n_sheets = 0;
275
276    cc->color_override = COLOR_NONE;
277
278    cc->output_name = NULL;
279
280    cc->add_toc = 1;
281    cc->toc = NULL;
282    /*
283     * record_init does not perform allocations or such, so it's safe to
284     * call it here even if we don't use this facility.
285     */
286    record_init(&cc->record, &real_cro_ops, cc);
287
288    return cc;
289}
290
291
292static struct cro_ctx *init_common(int argc, char *const *argv)
293{
294    struct cro_ctx *cc = new_cc();
295    char c;
296
297    while ((c = getopt(argc, argv, "o:s:T")) != EOF)
298        switch (c) {
299        case 'o':
300            cc->output_name = optarg;
301            break;
302        case 's':
303            cc->scale = atof(optarg) * DEFAULT_SCALE;
304            break;
305        case 'T':
306            cc->add_toc = 0;
307            break;
308        default:
309            usage(*argv);
310        }
311
312    return cc;
313}
314
315
316void cro_get_size(const struct cro_ctx *cc, int *w, int *h, int *x, int *y)
317{
318    int xmin, ymin;
319
320    record_bbox(&cc->record, &xmin, &ymin, w, h);
321
322// fprintf(stderr, "%dx%d%+d%+d\n", *w, *h, xmin, ymin);
323    *x = xmin;
324    *y = ymin;
325    *w = cd(cc, *w);
326    *h = cd(cc, *h);
327// fprintf(stderr, "%dx%d%+d%+d\n", *w, *h, xmin, ymin);
328}
329
330
331static void end_common(struct cro_ctx *cc, int *w, int *h, int *x, int *y)
332{
333    int xmin, ymin;
334
335    cairo_surface_destroy(cc->s);
336    cairo_destroy(cc->cr);
337
338    cro_get_size(cc, w, h, &xmin, &ymin);
339    cc->xo = -cd(cc, xmin);
340    cc->yo = -cd(cc, ymin);
341
342    if (x)
343        *x = xmin;
344    if (y)
345        *y = ymin;
346}
347
348
349static cairo_status_t stream_to_stdout(void *closure,
350    const unsigned char *data, unsigned length)
351{
352    ssize_t wrote;
353
354    wrote = write(1, data, length);
355    if (wrote == (ssize_t) length)
356        return CAIRO_STATUS_SUCCESS;
357    diag_perror("stdout");
358    return CAIRO_STATUS_WRITE_ERROR;
359}
360
361
362/* ----- PDF (auto-sizing, using redraw) ----------------------------------- */
363
364
365static cairo_status_t stream_to_pdftoc(void *closure,
366    const unsigned char *data, unsigned length)
367{
368    struct cro_ctx *cc = closure;
369
370    return pdftoc_write(cc->toc, data, length) ?
371        CAIRO_STATUS_SUCCESS : CAIRO_STATUS_WRITE_ERROR;
372}
373
374
375static void *cr_pdf_init(int argc, char *const *argv)
376{
377    struct cro_ctx *cc;
378
379    cc = init_common(argc, argv);
380
381    /* cr_text_width needs *something* to work with */
382
383    cc->s = cairo_pdf_surface_create(NULL, 16, 16);
384    cc->cr = cairo_create(cc->s);
385
386    if (cc->add_toc)
387        cc->toc = pdftoc_begin(cc->output_name);
388
389    return cc;
390}
391
392
393static void cr_pdf_sheet_name(void *ctx, const char *name)
394{
395    struct cro_ctx *cc = ctx;
396
397    if (cc->toc)
398        pdftoc_title(cc->toc, name ? name : "???");
399}
400
401
402static void cr_pdf_new_sheet(void *ctx)
403{
404    struct cro_ctx *cc = ctx;
405
406    cc->n_sheets++;
407    cc->sheets = realloc_type_n(cc->sheets, struct record, cc->n_sheets);
408    cc->sheets[cc->n_sheets - 1] = cc->record;
409    record_wipe(&cc->record);
410}
411
412
413static void cr_pdf_end(void *ctx)
414{
415    struct cro_ctx *cc = ctx;
416    int w, h;
417    unsigned i;
418
419    end_common(cc, &w, &h, NULL, NULL);
420
421    if (cc->toc)
422        cc->s = cairo_pdf_surface_create_for_stream(stream_to_pdftoc,
423            cc, w, h);
424    else if (cc->output_name)
425        cc->s = cairo_pdf_surface_create(cc->output_name, w, h);
426    else
427        cc->s = cairo_pdf_surface_create_for_stream(stream_to_stdout,
428            NULL, w, h);
429    cc->cr = cairo_create(cc->s);
430
431    cairo_select_font_face(cc->cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL,
432        CAIRO_FONT_WEIGHT_BOLD);
433    cairo_set_line_width(cc->cr, 0.5 * cc->scale);
434    /* @@@ CAIRO_LINE_CAP_ROUND makes all non-dashed lines disappear */
435    cairo_set_line_cap(cc->cr, CAIRO_LINE_CAP_SQUARE);
436
437    for (i = 0; i != cc->n_sheets; i++) {
438        set_color(cc, COLOR_WHITE);
439        cairo_paint(cc->cr);
440
441        record_replay(cc->sheets + i);
442        record_destroy(cc->sheets + i);
443
444        cairo_show_page(cc->cr);
445    }
446
447    record_replay(&cc->record);
448    record_destroy(&cc->record);
449
450    cairo_show_page(cc->cr);
451
452    cairo_surface_destroy(cc->s);
453    cairo_destroy(cc->cr);
454
455    if (cc->toc)
456        pdftoc_end(cc->toc);
457
458}
459
460
461/* ----- PNG (auto-sizing, using redraw) ----------------------------------- */
462
463
464static void *cr_png_init(int argc, char *const *argv)
465{
466    struct cro_ctx *cc;
467
468    cc = init_common(argc, argv);
469
470    /* cr_text_width needs *something* to work with */
471
472    cc->s = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 16, 16);
473    cc->cr = cairo_create(cc->s);
474
475    return cc;
476}
477
478
479static void cr_png_end(void *ctx)
480{
481    struct cro_ctx *cc = ctx;
482    int w, h, stride;
483
484    cro_img_end(cc, &w, &h, &stride);
485    cro_img_write(cc, cc->output_name);
486}
487
488
489/* ----- Images (auto-sizing, using redraw) -------------------------------- */
490
491
492uint32_t *cro_img_end(struct cro_ctx *cc, int *w, int *h, int *stride)
493{
494    uint32_t *data;
495
496    end_common(cc, w, h, NULL, NULL);
497
498    *stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, *w);
499    data = alloc_size(*stride * *h);
500
501    cc->s = cairo_image_surface_create_for_data((unsigned char *) data,
502        CAIRO_FORMAT_RGB24, *w, *h, *stride);
503    cc->cr = cairo_create(cc->s);
504
505    set_color(cc, COLOR_WHITE);
506    cairo_paint(cc->cr);
507
508    cairo_select_font_face(cc->cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL,
509        CAIRO_FONT_WEIGHT_BOLD);
510    cairo_set_line_width(cc->cr, 2);
511    cairo_set_line_cap(cc->cr, CAIRO_LINE_CAP_ROUND);
512
513    record_replay(&cc->record);
514    record_destroy(&cc->record);
515
516    cairo_surface_flush(cc->s);
517
518    return data;
519}
520
521
522void cro_img_write(struct cro_ctx *cc, const char *name)
523{
524    if (name)
525        cairo_surface_write_to_png(cc->s, name);
526    else
527        cairo_surface_write_to_png_stream(cc->s, stream_to_stdout,
528            NULL);
529}
530
531
532/* ----- Canvas (using redraw) --------------------------------------------- */
533
534
535void cro_canvas_end(struct cro_ctx *cc, int *w, int *h, int *xmin, int *ymin)
536{
537    end_common(cc, w, h, xmin, ymin);
538    *w /= cc->scale;
539    *h /= cc->scale;
540}
541
542
543void cro_canvas_prepare(cairo_t *cr)
544{
545    cairo_set_source_rgb(cr, 1, 1, 1);
546    cairo_paint(cr);
547
548    cairo_select_font_face(cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL,
549        CAIRO_FONT_WEIGHT_BOLD);
550
551    cairo_set_line_width(cr, 2);
552    cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
553}
554
555
556void cro_canvas_draw(struct cro_ctx *cc, cairo_t *cr, int xo, int yo,
557    float scale)
558{
559    cc->cr = cr;
560
561    cc->scale = scale;
562    cc->xo = xo;
563    cc->yo = yo;
564    record_replay(&cc->record);
565}
566
567
568/* ----- Image for external use (simplified API) --------------------------- */
569
570
571uint32_t *cro_img(struct cro_ctx *ctx, int xo, int yo, int w, int h,
572    float scale, cairo_t **res_cr, int *res_stride)
573{
574    int stride;
575    uint32_t *data;
576    cairo_t *cr;
577    cairo_surface_t *s;
578
579    stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
580    data = alloc_size(stride * h);
581
582    s = cairo_image_surface_create_for_data((unsigned char *) data,
583        CAIRO_FORMAT_RGB24, w, h, stride);
584    cr = cairo_create(s);
585
586    cairo_set_source_rgb(cr, 1, 1, 1);
587    cairo_paint(cr);
588
589    cairo_select_font_face(cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL,
590        CAIRO_FONT_WEIGHT_BOLD);
591    cairo_set_line_width(cr, 2);
592    cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
593
594    ctx->cr = cr;
595    ctx->s = s;
596    ctx->xo = xo;
597    ctx->yo = yo;
598    ctx->scale = scale;
599    ctx->color_override = COLOR_NONE;
600
601    record_replay(&ctx->record);
602
603    if (res_cr)
604        *res_cr = cr;
605    if (res_stride)
606        *res_stride = stride;
607
608    return data;
609}
610
611
612/* ----- Operations -------------------------------------------------------- */
613
614
615const struct gfx_ops cro_png_ops = {
616    .name = "png",
617    .line = record_line,
618    .poly = record_poly,
619    .circ = record_circ,
620    .arc = record_arc,
621    .text = record_text,
622    .text_width = cr_text_width,
623    .init = cr_png_init,
624    .end = cr_png_end,
625};
626
627const struct gfx_ops cro_pdf_ops = {
628    .name = "pdf",
629    .line = record_line,
630    .poly = record_poly,
631    .circ = record_circ,
632    .arc = record_arc,
633    .text = record_text,
634    .text_width = cr_text_width,
635    .init = cr_pdf_init,
636    .sheet_name = cr_pdf_sheet_name,
637    .new_sheet = cr_pdf_new_sheet,
638    .end = cr_pdf_end,
639};
640

Archive Download this file

Branches:
master



interactive