123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #include "config.h"
- #include <assert.h>
- #include <errno.h>
- #include <limits.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
- #include <common/const.h>
- #include <gvc/gvplugin_render.h>
- #include <common/utils.h>
- #include <gvc/gvplugin_device.h>
- #include <gvc/gvio.h>
- #include <math.h>
- #include <util/agxbuf.h>
- #include "gvplugin_pango.h"
- #include <pango/pangocairo.h>
- typedef enum {
- FORMAT_CAIRO,
- FORMAT_PNG,
- FORMAT_PS,
- FORMAT_PDF,
- FORMAT_SVG,
- FORMAT_EPS,
- } format_type;
- #define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
- static double dashed[] = {6.};
- static int dashed_len = ARRAY_SIZE(dashed);
- static double dotted[] = {2., 6.};
- static int dotted_len = ARRAY_SIZE(dotted);
- #ifdef CAIRO_HAS_PS_SURFACE
- #include <cairo-ps.h>
- #endif
- #ifdef CAIRO_HAS_PDF_SURFACE
- #include <cairo-pdf.h>
- #endif
- #ifdef CAIRO_HAS_SVG_SURFACE
- #include <cairo-svg.h>
- #endif
- static void cairogen_polyline(GVJ_t *job, pointf *A, size_t n);
- static void cairogen_set_color(cairo_t * cr, gvcolor_t * color)
- {
- cairo_set_source_rgba(cr, color->u.RGBA[0], color->u.RGBA[1],
- color->u.RGBA[2], color->u.RGBA[3]);
- }
- static void cairogen_add_color_stop_rgba(cairo_pattern_t *pat, double stop , gvcolor_t * color)
- {
- cairo_pattern_add_color_stop_rgba (pat, stop,color->u.RGBA[0], color->u.RGBA[1],
- color->u.RGBA[2], color->u.RGBA[3]);
- }
- static cairo_status_t
- writer (void *closure, const unsigned char *data, unsigned int length)
- {
- if (length == gvwrite(closure, (const char*)data, length))
- return CAIRO_STATUS_SUCCESS;
- return CAIRO_STATUS_WRITE_ERROR;
- }
- static void cairogen_begin_job(GVJ_t * job)
- {
- if (job->external_context && job->context)
- cairo_save(job->context);
- }
- static void cairogen_end_job(GVJ_t * job)
- {
- cairo_t *cr = job->context;
- if (job->external_context)
- cairo_restore(cr);
- else {
- cairo_destroy(cr);
- job->context = NULL;
- }
- }
- static const double CAIRO_XMAX = 32767;
- static const double CAIRO_YMAX = 32767;
- static void cairogen_begin_page(GVJ_t * job)
- {
- cairo_t *cr = job->context;
- cairo_surface_t *surface;
- cairo_status_t status;
- if (cr == NULL) {
- switch (job->render.id) {
- case FORMAT_PS:
- case FORMAT_EPS:
- #ifdef CAIRO_HAS_PS_SURFACE
- surface = cairo_ps_surface_create_for_stream (writer,
- job, job->width, job->height);
- if (job->render.id == FORMAT_EPS)
- cairo_ps_surface_set_eps (surface, TRUE);
- #endif
- break;
- case FORMAT_PDF:
- #ifdef CAIRO_HAS_PDF_SURFACE
- surface = cairo_pdf_surface_create_for_stream (writer,
- job, job->width, job->height);
- {
- const char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
- if (source_date_epoch != NULL) {
- char *end = NULL;
- errno = 0;
- long epoch = strtol(source_date_epoch, &end, 10);
- // from https://reproducible-builds.org/specs/source-date-epoch/
- //
- // If the value is malformed, the build process SHOULD
- // exit with a non-zero error code.
- if ((epoch == LONG_MAX && errno != 0) || epoch < 0
- || *end != '\0') {
- fprintf(stderr,
- "malformed value %s for $SOURCE_DATE_EPOCH\n",
- source_date_epoch);
- exit(EXIT_FAILURE);
- }
- time_t tepoch = (time_t)epoch;
- struct tm *tm = gmtime(&tepoch);
- if (tm == NULL) {
- fprintf(stderr,
- "malformed value %s for $SOURCE_DATE_EPOCH\n",
- source_date_epoch);
- exit(EXIT_FAILURE);
- }
- #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
- char iso8601[sizeof("YYYY-MM-DDThh:mm:ss")] = {0};
- (void)strftime(iso8601, sizeof(iso8601), "%Y-%m-%dT%H:%M:%SZ", tm);
- cairo_pdf_surface_set_metadata(surface,
- CAIRO_PDF_METADATA_CREATE_DATE,
- iso8601);
- cairo_pdf_surface_set_metadata(surface,
- CAIRO_PDF_METADATA_MOD_DATE,
- iso8601);
- #endif
- }
- }
- #endif
- break;
- case FORMAT_SVG:
- #ifdef CAIRO_HAS_SVG_SURFACE
- surface = cairo_svg_surface_create_for_stream (writer,
- job, job->width, job->height);
- #endif
- break;
- case FORMAT_CAIRO:
- case FORMAT_PNG:
- default:
- if (job->width >= CAIRO_XMAX || job->height >= CAIRO_YMAX) {
- double scale = fmin(CAIRO_XMAX / job->width, CAIRO_YMAX / job->height);
- assert(job->width * scale <= UINT_MAX);
- job->width = (unsigned)(job->width * scale);
- assert(job->height * scale <= UINT_MAX);
- job->height = (unsigned)(job->height * scale);
- job->scale.x *= scale;
- job->scale.y *= scale;
- fprintf(stderr,
- "%s: graph is too large for cairo-renderer bitmaps. Scaling by %g to fit\n",
- job->common->cmdname, scale);
- }
- assert(job->width <= INT_MAX);
- assert(job->height <= INT_MAX);
- surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
- (int)job->width, (int)job->height);
- if (job->common->verbose)
- fprintf(stderr,
- "%s: allocating a %.0fK cairo image surface (%d x %d pixels)\n",
- job->common->cmdname,
- round(job->width * job->height * 4 / 1024.),
- job->width, job->height);
- break;
- }
- status = cairo_surface_status(surface);
- if (status != CAIRO_STATUS_SUCCESS) {
- fprintf(stderr, "%s: failure to create cairo surface: %s\n",
- job->common->cmdname,
- cairo_status_to_string(status));
- cairo_surface_destroy (surface);
- return;
- }
- cr = cairo_create(surface);
- cairo_surface_destroy (surface);
- job->context = cr;
- }
- cairo_scale(cr, job->scale.x, job->scale.y);
- cairo_rotate(cr, -job->rotation * M_PI / 180.);
- cairo_translate(cr, job->translation.x, -job->translation.y);
- cairo_rectangle(cr, job->clip.LL.x, - job->clip.LL.y,
- job->clip.UR.x - job->clip.LL.x, - (job->clip.UR.y - job->clip.LL.y));
- cairo_clip(cr);
- }
- static void cairogen_end_page(GVJ_t * job)
- {
- cairo_t *cr = job->context;
- cairo_surface_t *surface;
- cairo_status_t status;
- switch (job->render.id) {
- #ifdef CAIRO_HAS_PNG_FUNCTIONS
- case FORMAT_PNG:
- surface = cairo_get_target(cr);
- cairo_surface_write_to_png_stream(surface, writer, job);
- break;
- #endif
- case FORMAT_PS:
- case FORMAT_PDF:
- case FORMAT_SVG:
- cairo_show_page(cr);
- surface = cairo_surface_reference(cairo_get_target(cr));
- cairo_surface_finish(surface);
- status = cairo_surface_status(surface);
- cairo_surface_destroy(surface);
- if (status != CAIRO_STATUS_SUCCESS)
- fprintf(stderr, "cairo: %s\n", cairo_status_to_string(status));
- break;
- case FORMAT_CAIRO:
- default:
- surface = cairo_get_target(cr);
- if (cairo_image_surface_get_width(surface) == 0 || cairo_image_surface_get_height(surface) == 0) {
- /* apparently cairo never allocates a surface if nothing was ever written to it */
- /* but suppress this error message since a zero area surface seems to happen during normal operations, particular in -Tx11
- fprintf(stderr, "ERROR: cairo surface has zero area, this may indicate some problem during rendering shapes.\n");
- - jce */
- }
- job->imagedata = (char *)(cairo_image_surface_get_data(surface));
- break;
- /* formatting will be done by gvdevice_format() */
- }
- }
- static void cairogen_begin_anchor(GVJ_t *job, char *url, char *tooltip, char *target, char *id)
- {
- obj_state_t *obj = job->obj;
- cairo_t *cr = job->context;
- double p0x, p0y, p1x, p1y;
- // suppress unused parameter warnings
- (void)tooltip;
- (void)target;
- (void)id;
- if (url && obj->url_map_p) {
- p0x = obj->url_map_p[0].x;
- p0y = -obj->url_map_p[0].y;
- cairo_user_to_device (cr, &p0x, &p0y);
- p1x = obj->url_map_p[1].x;
- p1y = -obj->url_map_p[1].y;
- cairo_user_to_device (cr, &p1x, &p1y);
- agxbuf buf = {0};
- agxbprint(&buf, "rect=[%f %f %f %f] uri='%s'", p0x, p0y, p1x - p0x,
- p1y - p0y, url);
- #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
- cairo_tag_begin(cr, CAIRO_TAG_LINK, agxbuse(&buf));
- cairo_tag_end (cr, CAIRO_TAG_LINK);
- #endif
- agxbfree(&buf);
- }
- }
- static void cairogen_textspan(GVJ_t * job, pointf p, textspan_t * span)
- {
- obj_state_t *obj = job->obj;
- cairo_t *cr = job->context;
- pointf A[2];
- cairo_set_dash (cr, dashed, 0, 0.0); /* clear any dashing */
- cairogen_set_color(cr, &obj->pencolor);
- switch (span->just) {
- case 'r':
- p.x -= span->size.x;
- break;
- case 'l':
- p.x -= 0.0;
- break;
- case 'n':
- default:
- p.x -= span->size.x / 2.0;
- break;
- }
- p.y += span->yoffset_centerline + span->yoffset_layout;
- cairo_move_to (cr, p.x, -p.y);
- cairo_save(cr);
- cairo_scale(cr, POINTS_PER_INCH / FONT_DPI, POINTS_PER_INCH / FONT_DPI);
- pango_cairo_show_layout(cr, (PangoLayout*)span->layout);
- cairo_restore(cr);
- if (span->font && (span->font->flags & HTML_OL)) {
- A[0].x = p.x;
- A[1].x = p.x + span->size.x;
- A[1].y = A[0].y = p.y;
- cairogen_polyline(job, A, 2);
- }
- }
- static void cairogen_set_penstyle(GVJ_t *job, cairo_t *cr)
- {
- obj_state_t *obj = job->obj;
- if (obj->pen == PEN_DASHED) {
- cairo_set_dash (cr, dashed, dashed_len, 0.0);
- } else if (obj->pen == PEN_DOTTED) {
- cairo_set_dash (cr, dotted, dotted_len, 0.0);
- } else {
- cairo_set_dash (cr, dashed, 0, 0.0);
- }
- cairo_set_line_width (cr, obj->penwidth);
- }
- static void cairo_gradient_fill(cairo_t *cr, obj_state_t *obj, int filled,
- pointf *A, size_t n) {
- cairo_pattern_t* pat;
- double angle = obj->gradient_angle * M_PI / 180;
- pointf G[2],c1;
- if (filled == GRADIENT) {
- get_gradient_points(A, G, n, angle, 0);
- pat = cairo_pattern_create_linear (G[0].x,G[0].y,G[1].x,G[1].y);
- }
- else {
- get_gradient_points(A, G, n, 0, 1);
- //r1 is inner radius, r2 is outer radius
- const double r1 = G[1].x; // set a r2 ÷ 4 in get_gradient_points
- const double r2 = G[1].y;
- if (obj->gradient_angle == 0) {
- c1.x = G[0].x;
- c1.y = G[0].y;
- }
- else {
- c1.x = G[0].x + r1 * cos(angle);
- c1.y = G[0].y - r1 * sin(angle);
- }
- pat = cairo_pattern_create_radial(c1.x,c1.y,r1,G[0].x,G[0].y,r2);
- }
- if (obj->gradient_frac > 0) {
- cairogen_add_color_stop_rgba(pat,obj->gradient_frac - 0.001,&(obj->fillcolor));
- cairogen_add_color_stop_rgba(pat,obj->gradient_frac,&(obj->stopcolor));
- }
- else {
- cairogen_add_color_stop_rgba(pat,0,&(obj->fillcolor));
- cairogen_add_color_stop_rgba(pat,1,&(obj->stopcolor));
- }
- cairo_set_source (cr, pat);
- cairo_fill_preserve (cr);
- cairo_pattern_destroy (pat);
- }
- static void cairogen_ellipse(GVJ_t * job, pointf * A, int filled)
- {
- obj_state_t *obj = job->obj;
- cairo_t *cr = job->context;
- cairo_matrix_t matrix;
- double rx, ry;
- cairogen_set_penstyle(job, cr);
- cairo_get_matrix(cr, &matrix);
- rx = A[1].x - A[0].x;
- ry = A[1].y - A[0].y;
- #define RMIN 0.01
- rx = fmax(rx, RMIN);
- ry = fmax(ry, RMIN);
- cairo_translate(cr, A[0].x, -A[0].y);
- cairo_scale(cr, rx, ry);
- cairo_move_to(cr, 1., 0.);
- cairo_arc(cr, 0., 0., 1., 0., 2 * M_PI);
- cairo_set_matrix(cr, &matrix);
- if (filled == GRADIENT || filled == RGRADIENT) {
- cairo_gradient_fill (cr, obj, filled, A, 2);
- }
- else if (filled) {
- cairogen_set_color(cr, &obj->fillcolor);
- cairo_fill_preserve(cr);
- }
- cairogen_set_color(cr, &obj->pencolor);
- cairo_stroke(cr);
- }
- static void cairogen_polygon(GVJ_t *job, pointf *A, size_t n, int filled) {
- obj_state_t *obj = job->obj;
- cairo_t *cr = job->context;
- cairogen_set_penstyle(job, cr);
- cairo_move_to(cr, A[0].x, -A[0].y);
- for (size_t i = 1; i < n; i++)
- cairo_line_to(cr, A[i].x, -A[i].y);
- cairo_close_path(cr);
- if (filled == GRADIENT || filled == RGRADIENT) {
- cairo_gradient_fill(cr, obj, filled, A, n);
- }
- else if (filled) {
- cairogen_set_color(cr, &obj->fillcolor);
- cairo_fill_preserve(cr);
- }
- cairogen_set_color(cr, &obj->pencolor);
- cairo_stroke(cr);
- }
- static void cairogen_bezier(GVJ_t *job, pointf *A, size_t n, int filled) {
- obj_state_t *obj = job->obj;
- cairo_t *cr = job->context;
- cairogen_set_penstyle(job, cr);
- cairo_move_to(cr, A[0].x, -A[0].y);
- for (size_t i = 1; i < n; i += 3)
- cairo_curve_to(cr, A[i].x, -A[i].y, A[i + 1].x, -A[i + 1].y,
- A[i + 2].x, -A[i + 2].y);
- if (filled == GRADIENT || filled == RGRADIENT) {
- cairo_gradient_fill(cr, obj, filled, A, n);
- }
- else if (filled) {
- cairogen_set_color(cr, &obj->fillcolor);
- cairo_fill_preserve(cr);
- }
- cairogen_set_color(cr, &obj->pencolor);
- cairo_stroke(cr);
- }
- static void cairogen_polyline(GVJ_t *job, pointf *A, size_t n) {
- obj_state_t *obj = job->obj;
- cairo_t *cr = job->context;
- cairogen_set_penstyle(job, cr);
- cairo_move_to(cr, A[0].x, -A[0].y);
- for (size_t i = 1; i < n; i++)
- cairo_line_to(cr, A[i].x, -A[i].y);
- cairogen_set_color(cr, &obj->pencolor);
- cairo_stroke(cr);
- }
- static gvrender_engine_t cairogen_engine = {
- cairogen_begin_job,
- cairogen_end_job,
- 0, /* cairogen_begin_graph */
- 0, /* cairogen_end_graph */
- 0, /* cairogen_begin_layer */
- 0, /* cairogen_end_layer */
- cairogen_begin_page,
- cairogen_end_page,
- 0, /* cairogen_begin_cluster */
- 0, /* cairogen_end_cluster */
- 0, /* cairogen_begin_nodes */
- 0, /* cairogen_end_nodes */
- 0, /* cairogen_begin_edges */
- 0, /* cairogen_end_edges */
- 0, /* cairogen_begin_node */
- 0, /* cairogen_end_node */
- 0, /* cairogen_begin_edge */
- 0, /* cairogen_end_edge */
- cairogen_begin_anchor, /* cairogen_begin_anchor */
- 0, /* cairogen_end_anchor */
- 0, /* cairogen_begin_label */
- 0, /* cairogen_end_label */
- cairogen_textspan,
- 0, /* cairogen_resolve_color */
- cairogen_ellipse,
- cairogen_polygon,
- cairogen_bezier,
- cairogen_polyline,
- 0, /* cairogen_comment */
- 0, /* cairogen_library_shape */
- };
- static gvrender_features_t render_features_cairo = {
- GVRENDER_Y_GOES_DOWN
- | GVRENDER_DOES_TRANSFORM, /* flags */
- 4., /* default pad - graph units */
- 0, /* knowncolors */
- 0, /* sizeof knowncolors */
- RGBA_DOUBLE, /* color_type */
- };
- static gvdevice_features_t device_features_png = {
- GVDEVICE_BINARY_FORMAT
- | GVDEVICE_DOES_TRUECOLOR,/* flags */
- {0.,0.}, /* default margin - points */
- {0.,0.}, /* default page width, height - points */
- {96.,96.}, /* typical monitor dpi */
- };
- static gvdevice_features_t device_features_ps = {
- GVRENDER_NO_WHITE_BG
- | GVDEVICE_DOES_TRUECOLOR, /* flags */
- {36.,36.}, /* default margin - points */
- {0.,0.}, /* default page width, height - points */
- {72.,72.}, /* postscript 72 dpi */
- };
- static gvdevice_features_t device_features_eps = {
- GVRENDER_NO_WHITE_BG
- | GVDEVICE_DOES_TRUECOLOR, /* flags */
- {36.,36.}, /* default margin - points */
- {0.,0.}, /* default page width, height - points */
- {72.,72.}, /* postscript 72 dpi */
- };
- static gvdevice_features_t device_features_pdf = {
- GVDEVICE_BINARY_FORMAT
- | GVRENDER_NO_WHITE_BG
- | GVRENDER_DOES_MAPS
- | GVRENDER_DOES_MAP_RECTANGLE
- | GVDEVICE_DOES_TRUECOLOR,/* flags */
- {36.,36.}, /* default margin - points */
- {0.,0.}, /* default page width, height - points */
- {72.,72.}, /* postscript 72 dpi */
- };
- static gvdevice_features_t device_features_svg = {
- GVRENDER_NO_WHITE_BG
- | GVDEVICE_DOES_TRUECOLOR, /* flags */
- {0.,0.}, /* default margin - points */
- {0.,0.}, /* default page width, height - points */
- {72.,72.}, /* svg 72 dpi */
- };
- gvplugin_installed_t gvrender_pango_types[] = {
- {FORMAT_CAIRO, "cairo", 10, &cairogen_engine, &render_features_cairo},
- {0, NULL, 0, NULL, NULL}
- };
- gvplugin_installed_t gvdevice_pango_types[] = {
- #ifdef CAIRO_HAS_PNG_FUNCTIONS
- {FORMAT_PNG, "png:cairo", 10, NULL, &device_features_png},
- #endif
- #ifdef CAIRO_HAS_PS_SURFACE
- {FORMAT_PS, "ps:cairo", -10, NULL, &device_features_ps},
- {FORMAT_EPS, "eps:cairo", -10, NULL, &device_features_eps},
- #endif
- #ifdef CAIRO_HAS_PDF_SURFACE
- {FORMAT_PDF, "pdf:cairo", 10, NULL, &device_features_pdf},
- #endif
- #ifdef CAIRO_HAS_SVG_SURFACE
- {FORMAT_SVG, "svg:cairo", -10, NULL, &device_features_svg},
- #endif
- {0, NULL, 0, NULL, NULL}
- };
|