123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- /*************************************************************************
- * 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 <cstddef>
- #include <iostream>
- #include <fstream>
- #include <stdexcept>
- #include <string>
- #include <LASi.h>
- #include "config.h"
- #include <gvc/gvplugin_render.h>
- #include <gvc/gvplugin_device.h>
- #include <gvc/gvio.h>
- #include <gvc/gvcint.h>
- #include <common/const.h>
- #include <common/utils.h>
- #include <util/agxbuf.h>
- #include <util/prisize_t.h>
- #include <util/unreachable.h>
- #include "../core/ps.h"
- using namespace LASi;
- using namespace std;
- // J$: added `pdfmark' URL embedding. PostScript rendered from
- // dot files with URL attributes will get active PDF links
- // from Adobe's Distiller.
- #define PDFMAX 14400 ///< Maximum size of PDF page
- typedef enum { FORMAT_PS, FORMAT_PS2, FORMAT_EPS } format_type;
- struct Context {
- using write_fn = size_t(*)(GVJ_t*, const char*, size_t);
- Context(write_fn fn): save_write_fn(fn) { }
- PostscriptDocument doc;
- write_fn save_write_fn;
- };
- static size_t lasi_head_writer(GVJ_t * job, const char *s, size_t len)
- {
- Context *ctxt = reinterpret_cast<Context*>(job->context);
- ctxt->doc.osHeader() << s;
- return len;
- }
- static size_t lasi_body_writer(GVJ_t * job, const char *s, size_t len)
- {
- Context *ctxt = reinterpret_cast<Context*>(job->context);
- ctxt->doc.osBody() << s;
- return len;
- }
- static size_t lasi_footer_writer(GVJ_t * job, const char *s, size_t len)
- {
- Context *ctxt = reinterpret_cast<Context*>(job->context);
- ctxt->doc.osFooter() << s;
- return len;
- }
- static void lasi_begin_job(GVJ_t * job)
- {
- job->context = new Context(job->gvc->write_fn);
- job->gvc->write_fn = lasi_head_writer;
- gvprintf(job, "%%%%Creator: %s version %s (%s)\n",
- job->common->info[0], job->common->info[1], job->common->info[2]);
- }
- static void lasi_end_job(GVJ_t * job)
- {
- job->gvc->write_fn = lasi_footer_writer;
- if (job->render.id != FORMAT_EPS)
- gvprintf(job, "%%%%Pages: %d\n", job->common->viewNum);
- if (job->common->show_boxes == nullptr)
- if (job->render.id != FORMAT_EPS)
- gvprintf(job, "%%%%BoundingBox: %d %d %d %d\n",
- job->boundingBox.LL.x, job->boundingBox.LL.y,
- job->boundingBox.UR.x, job->boundingBox.UR.y);
- gvputs(job, "end\nrestore\n");
- {
- // create the new stream to "redirect" cout's output to
- ostringstream output;
-
- // smart class that will swap streambufs and replace them
- // when object goes out of scope.
- class StreamBuf_Swapper
- {
- public:
- StreamBuf_Swapper(ostream & orig, ostream & replacement)
- : buf_(orig.rdbuf()), str_(orig)
- {
- orig.rdbuf(replacement.rdbuf());
- }
- ~StreamBuf_Swapper()
- {
- str_.rdbuf(buf_);
- }
- private:
- std::streambuf * buf_;
- std::ostream & str_;
- } swapper(cout, output);
-
- Context *ctxt = reinterpret_cast<Context*>(job->context);
- ctxt->doc.write(cout);
-
- job->gvc->write_fn = ctxt->save_write_fn;
- gvputs(job, output.str().c_str());
- delete ctxt;
- }
- }
- static void lasi_begin_graph(GVJ_t * job)
- {
- obj_state_t *obj = job->obj;
- job->gvc->write_fn = lasi_body_writer;
- if (job->common->viewNum == 0) {
- gvprintf(job, "%%%%Title: %s\n", agnameof(obj->u.g));
- if (job->render.id != FORMAT_EPS)
- gvputs(job, "%%Pages: (atend)\n");
- else
- gvputs(job, "%%Pages: 1\n");
- if (job->common->show_boxes == nullptr) {
- if (job->render.id != FORMAT_EPS)
- gvputs(job, "%%BoundingBox: (atend)\n");
- else
- gvprintf(job, "%%%%BoundingBox: %d %d %d %d\n",
- job->pageBoundingBox.LL.x, job->pageBoundingBox.LL.y,
- job->pageBoundingBox.UR.x, job->pageBoundingBox.UR.y);
- }
- gvputs(job, "%%EndComments\nsave\n");
- // include shape library
- cat_libfile(job, job->common->lib, ps_txt);
- // include epsf
- epsf_define(job);
- if (job->common->show_boxes) {
- const char* args[2];
- args[0] = job->common->show_boxes[0];
- args[1] = nullptr;
- cat_libfile(job, nullptr, args);
- }
- }
- // Set base URL for relative links (for Distiller ≥ 3.0)
- if (obj->url)
- gvprintf(job, "[ {Catalog} << /URI << /Base %s >> >>\n"
- "/PUT pdfmark\n", ps_string(obj->url, CHAR_UTF8));
- }
- static void lasi_begin_layer(GVJ_t * job, char*, int layerNum, int numLayers)
- {
- gvprintf(job, "%d %d setlayer\n", layerNum, numLayers);
- }
- static void lasi_begin_page(GVJ_t * job)
- {
- box pbr = job->pageBoundingBox;
- gvprintf(job, "%%%%Page: %d %d\n",
- job->common->viewNum + 1, job->common->viewNum + 1);
- if (job->common->show_boxes == nullptr)
- gvprintf(job, "%%%%PageBoundingBox: %d %d %d %d\n",
- pbr.LL.x, pbr.LL.y, pbr.UR.x, pbr.UR.y);
- gvprintf(job, "%%%%PageOrientation: %s\n",
- (job->rotation ? "Landscape" : "Portrait"));
- if (job->render.id == FORMAT_PS2)
- gvprintf(job, "<< /PageSize [%d %d] >> setpagedevice\n",
- pbr.UR.x, pbr.UR.y);
- gvprintf(job, "%d %d %d beginpage\n",
- job->pagesArrayElem.x, job->pagesArrayElem.y, job->numPages);
- if (job->common->show_boxes == nullptr)
- gvprintf(job, "gsave\n%d %d %d %d boxprim clip newpath\n",
- pbr.LL.x, pbr.LL.y, pbr.UR.x-pbr.LL.x, pbr.UR.y-pbr.LL.y);
- gvprintf(job, "%g %g set_scale %d rotate %g %g translate\n",
- job->scale.x, job->scale.y,
- job->rotation,
- job->translation.x, job->translation.y);
- // Define the size of the PS canvas
- if (job->render.id == FORMAT_PS2) {
- if (pbr.UR.x >= PDFMAX || pbr.UR.y >= PDFMAX)
- job->common->errorfn("canvas size (%d,%d) exceeds PDF limit (%d)\n"
- "\t(suggest setting a bounding box size, see dot(1))\n",
- pbr.UR.x, pbr.UR.y, PDFMAX);
- gvprintf(job, "[ /CropBox [%d %d %d %d] /PAGES pdfmark\n",
- pbr.LL.x, pbr.LL.y, pbr.UR.x, pbr.UR.y);
- }
- }
- static void lasi_end_page(GVJ_t * job)
- {
- if (job->common->show_boxes) {
- gvputs(job, "0 0 0 edgecolor\n");
- cat_libfile(job, nullptr, job->common->show_boxes + 1);
- }
- // the showpage is really a no-op, but at least one PS processor
- // out there needs to see this literal token. endpage does the real work.
- gvputs(job, "endpage\nshowpage\ngrestore\n");
- gvputs(job, "%%PageTrailer\n");
- gvprintf(job, "%%%%EndPage: %d\n", job->common->viewNum);
- }
- static void lasi_begin_cluster(GVJ_t * job)
- {
- obj_state_t *obj = job->obj;
- gvprintf(job, "%% %s\n", agnameof(obj->u.sg));
- gvputs(job, "gsave\n");
- }
- static void lasi_end_cluster(GVJ_t * job)
- {
- gvputs(job, "grestore\n");
- }
- static void lasi_begin_node(GVJ_t * job)
- {
- gvputs(job, "gsave\n");
- }
- static void lasi_end_node(GVJ_t * job)
- {
- gvputs(job, "grestore\n");
- }
- static void
- lasi_begin_edge(GVJ_t * job)
- {
- gvputs(job, "gsave\n");
- }
- static void lasi_end_edge(GVJ_t * job)
- {
- gvputs(job, "grestore\n");
- }
- static void lasi_begin_anchor(GVJ_t *job, char *url, char*, char*, char*)
- {
- obj_state_t *obj = job->obj;
- if (url && obj->url_map_p) {
- gvputs(job, "[ /Rect [ ");
- gvprintpointflist(job, obj->url_map_p, 2);
- gvputs(job, " ]\n");
- gvprintf(job, " /Border [ 0 0 0 ]\n"
- " /Action << /Subtype /URI /URI %s >>\n"
- " /Subtype /Link\n"
- "/ANN pdfmark\n",
- ps_string(url, CHAR_UTF8));
- }
- }
- static void ps_set_pen_style(GVJ_t *job)
- {
- double penwidth = job->obj->penwidth;
- char *p, *line, **s = job->obj->rawstyle;
- gvprintdouble(job, penwidth);
- gvputs(job," setlinewidth\n");
- while (s && (p = line = *s++)) {
- if (line == std::string{"setlinewidth"})
- continue;
- while (*p)
- p++;
- p++;
- while (*p) {
- gvprintf(job,"%s ", p);
- while (*p)
- p++;
- p++;
- }
- if (line == std::string{"invis"})
- job->obj->penwidth = 0;
- gvprintf(job, "%s\n", line);
- }
- }
- static void ps_set_color(GVJ_t *job, gvcolor_t *color)
- {
- const char *objtype;
- if (color) {
- switch (job->obj->type) {
- case ROOTGRAPH_OBJTYPE:
- case CLUSTER_OBJTYPE:
- objtype = "graph";
- break;
- case NODE_OBJTYPE:
- objtype = "node";
- break;
- case EDGE_OBJTYPE:
- objtype = "edge";
- break;
- default:
- objtype = "sethsb";
- break;
- }
- gvprintf(job, "%.3g %.3g %.3g %scolor\n",
- color->u.HSVA[0], color->u.HSVA[1], color->u.HSVA[2], objtype);
- }
- }
- static void lasi_textspan(GVJ_t * job, pointf p, textspan_t * span)
- {
- const char *font;
- const PangoFontDescription *pango_font;
- FontStretch stretch;
- FontStyle style;
- FontVariant variant;
- FontWeight weight;
- PostscriptAlias *pA;
- if (job->obj->pencolor.u.HSVA[3] < .5)
- return; // skip transparent text
- if (span->layout) {
- pango_font = pango_layout_get_font_description((PangoLayout*)(span->layout));
- font = pango_font_description_get_family(pango_font);
- switch (pango_font_description_get_stretch(pango_font)) {
- case PANGO_STRETCH_ULTRA_CONDENSED: stretch = ULTRACONDENSED; break;
- case PANGO_STRETCH_EXTRA_CONDENSED: stretch = EXTRACONDENSED; break;
- case PANGO_STRETCH_CONDENSED: stretch = CONDENSED; break;
- case PANGO_STRETCH_SEMI_CONDENSED: stretch = SEMICONDENSED; break;
- case PANGO_STRETCH_NORMAL: stretch = NORMAL_STRETCH; break;
- case PANGO_STRETCH_SEMI_EXPANDED: stretch = SEMIEXPANDED; break;
- case PANGO_STRETCH_EXPANDED: stretch = EXPANDED; break;
- case PANGO_STRETCH_EXTRA_EXPANDED: stretch = EXTRAEXPANDED; break;
- case PANGO_STRETCH_ULTRA_EXPANDED: stretch = ULTRAEXPANDED; break;
- default:
- UNREACHABLE();
- }
- switch (pango_font_description_get_style(pango_font)) {
- case PANGO_STYLE_NORMAL: style = NORMAL_STYLE; break;
- case PANGO_STYLE_OBLIQUE: style = OBLIQUE; break;
- case PANGO_STYLE_ITALIC: style = ITALIC; break;
- default:
- UNREACHABLE();
- }
- switch (pango_font_description_get_variant(pango_font)) {
- case PANGO_VARIANT_NORMAL: variant = NORMAL_VARIANT; break;
- case PANGO_VARIANT_SMALL_CAPS: variant = SMALLCAPS; break;
- #if PANGO_VERSION_CHECK(1, 50, 0)
- case PANGO_VARIANT_ALL_SMALL_CAPS: variant = SMALLCAPS; break;
- case PANGO_VARIANT_PETITE_CAPS: variant = SMALLCAPS; break;
- case PANGO_VARIANT_ALL_PETITE_CAPS: variant = SMALLCAPS; break;
- case PANGO_VARIANT_UNICASE: variant = SMALLCAPS; break;
- case PANGO_VARIANT_TITLE_CAPS: variant = SMALLCAPS; break;
- #endif
- default:
- UNREACHABLE();
- }
- switch (pango_font_description_get_weight(pango_font)) {
- #if PANGO_VERSION_CHECK(1, 24, 0)
- case PANGO_WEIGHT_THIN: weight = ULTRALIGHT; break; // no exact match in LASi
- #endif
- case PANGO_WEIGHT_ULTRALIGHT: weight = ULTRALIGHT; break;
- case PANGO_WEIGHT_LIGHT: weight = LIGHT; break;
- #if PANGO_VERSION_CHECK(1, 36, 7)
- case PANGO_WEIGHT_SEMILIGHT: weight = LIGHT; break; // no exact match in LASi
- #endif
- #if PANGO_VERSION_CHECK(1, 24, 0)
- case PANGO_WEIGHT_BOOK: weight = NORMAL_WEIGHT; break; // no exact match in LASi
- #endif
- case PANGO_WEIGHT_NORMAL: weight = NORMAL_WEIGHT; break;
- #if PANGO_VERSION_CHECK(1, 24, 0)
- case PANGO_WEIGHT_MEDIUM: weight = NORMAL_WEIGHT; break; // no exact match in LASi
- #endif
- case PANGO_WEIGHT_SEMIBOLD: weight = BOLD; break; /* no exact match in lasi */
- case PANGO_WEIGHT_BOLD: weight = BOLD; break;
- case PANGO_WEIGHT_ULTRABOLD: weight = ULTRABOLD; break;
- case PANGO_WEIGHT_HEAVY: weight = HEAVY; break;
- #if PANGO_VERSION_CHECK(1, 24, 0)
- case PANGO_WEIGHT_ULTRAHEAVY: weight = HEAVY; break; // no exact match in LASi
- #endif
- default:
- UNREACHABLE();
- }
- }
- else {
- pA = span->font->postscript_alias;
- font = pA->svg_font_family;
- stretch = NORMAL_STRETCH;
- if (pA->svg_font_style && pA->svg_font_style == std::string{"italic"})
- style = ITALIC;
- else
- style = NORMAL_STYLE;
- variant = NORMAL_VARIANT;
- if (pA->svg_font_weight && pA->svg_font_weight == std::string{"bold"})
- weight = BOLD;
- else
- weight = NORMAL_WEIGHT;
- }
- ps_set_color(job, &(job->obj->pencolor));
- Context *ctxt = reinterpret_cast<Context*>(job->context);
- ctxt->doc.osBody() << setFont(font, style, weight, variant, stretch) << setFontSize(span->font->size) << "\n";
- 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;
- gvprintpointf(job, p);
- gvputs(job, " moveto ");
- ctxt->doc.osBody() << show(span->str) << "\n";
- }
- static void lasi_ellipse(GVJ_t * job, pointf * A, int filled)
- {
- // A[] contains 2 points: the center and corner.
- pointf AA[2];
- AA[0] = A[0];
- AA[1].x = A[1].x - A[0].x;
- AA[1].y = A[1].y - A[0].y;
- if (filled && job->obj->fillcolor.u.HSVA[3] > .5) {
- ps_set_color(job, &(job->obj->fillcolor));
- gvprintpointflist(job, AA, 2);
- gvputs(job, " ellipse_path fill\n");
- }
- if (job->obj->pencolor.u.HSVA[3] > .5) {
- ps_set_pen_style(job);
- ps_set_color(job, &(job->obj->pencolor));
- gvprintpointflist(job, AA, 2);
- gvputs(job, " ellipse_path stroke\n");
- }
- }
- static void lasi_bezier(GVJ_t *job, pointf *A, size_t n, int filled) {
- if (filled && job->obj->fillcolor.u.HSVA[3] > .5) {
- ps_set_color(job, &(job->obj->fillcolor));
- gvputs(job, "newpath ");
- gvprintpointf(job, A[0]);
- gvputs(job, " moveto\n");
- for (size_t j = 1; j < n; j += 3) {
- gvprintpointflist(job, &A[j], 3);
- gvputs(job, " curveto\n");
- }
- gvputs(job, "closepath fill\n");
- }
- if (job->obj->pencolor.u.HSVA[3] > .5) {
- ps_set_pen_style(job);
- ps_set_color(job, &(job->obj->pencolor));
- gvputs(job, "newpath ");
- gvprintpointf(job, A[0]);
- gvputs(job, " moveto\n");
- for (size_t j = 1; j < n; j += 3) {
- gvprintpointflist(job, &A[j], 3);
- gvputs(job, " curveto\n");
- }
- gvputs(job, "stroke\n");
- }
- }
- static void lasi_polygon(GVJ_t *job, pointf *A, size_t n, int filled) {
- if (filled && job->obj->fillcolor.u.HSVA[3] > .5) {
- ps_set_color(job, &(job->obj->fillcolor));
- gvputs(job, "newpath ");
- gvprintpointf(job, A[0]);
- gvputs(job, " moveto\n");
- for (size_t j = 1; j < n; j++) {
- gvprintpointf(job, A[j]);
- gvputs(job, " lineto\n");
- }
- gvputs(job, "closepath fill\n");
- }
- if (job->obj->pencolor.u.HSVA[3] > .5) {
- ps_set_pen_style(job);
- ps_set_color(job, &(job->obj->pencolor));
- gvputs(job, "newpath ");
- gvprintpointf(job, A[0]);
- gvputs(job, " moveto\n");
- for (size_t j = 1; j < n; j++) {
- gvprintpointf(job, A[j]);
- gvputs(job, " lineto\n");
- }
- gvputs(job, "closepath stroke\n");
- }
- }
- static void lasi_polyline(GVJ_t *job, pointf *A, size_t n) {
- if (job->obj->pencolor.u.HSVA[3] > .5) {
- ps_set_pen_style(job);
- ps_set_color(job, &(job->obj->pencolor));
- gvputs(job, "newpath ");
- gvprintpointf(job, A[0]);
- gvputs(job, " moveto\n");
- for (size_t j = 1; j < n; j++) {
- gvprintpointf(job, A[j]);
- gvputs(job, " lineto\n");
- }
- gvputs(job, "stroke\n");
- }
- }
- static void lasi_comment(GVJ_t * job, char *str)
- {
- gvputs(job, "% ");
- gvputs(job, str);
- gvputs(job, "\n");
- }
- static void lasi_library_shape(GVJ_t *job, char *name, pointf *A, size_t n,
- int filled) {
- if (filled && job->obj->fillcolor.u.HSVA[3] > .5) {
- ps_set_color(job, &(job->obj->fillcolor));
- gvputs(job, "[ ");
- gvprintpointflist(job, A, n);
- gvputs(job, " ");
- gvprintpointf(job, A[0]);
- gvprintf(job, " ] %" PRISIZE_T " true %s\n", n, name);
- }
- if (job->obj->pencolor.u.HSVA[3] > .5) {
- ps_set_pen_style(job);
- ps_set_color(job, &(job->obj->pencolor));
- gvputs(job, "[ ");
- gvprintpointflist(job, A, n);
- gvputs(job, " ");
- gvprintpointf(job, A[0]);
- gvprintf(job, " ] %" PRISIZE_T " false %s\n", n, name);
- }
- }
- static gvrender_engine_t lasi_engine = {
- lasi_begin_job,
- lasi_end_job,
- lasi_begin_graph,
- 0, // lasi_end_graph
- lasi_begin_layer,
- 0, // lasi_end_layer
- lasi_begin_page,
- lasi_end_page,
- lasi_begin_cluster,
- lasi_end_cluster,
- 0, // lasi_begin_nodes
- 0, // lasi_end_nodes
- 0, // lasi_begin_edges
- 0, // lasi_end_edges
- lasi_begin_node,
- lasi_end_node,
- lasi_begin_edge,
- lasi_end_edge,
- lasi_begin_anchor,
- 0, // lasi_end_anchor
- 0, // lasi_begin_label
- 0, // lasi_end_label
- lasi_textspan,
- 0, // lasi_resolve_color
- lasi_ellipse,
- lasi_polygon,
- lasi_bezier,
- lasi_polyline,
- lasi_comment,
- lasi_library_shape,
- };
- static gvrender_features_t render_features_lasi = {
- GVRENDER_DOES_TRANSFORM
- | GVRENDER_DOES_MAPS
- | GVRENDER_NO_WHITE_BG
- | GVRENDER_DOES_MAP_RECTANGLE,
- 4., // default pad - graph units
- nullptr, // knowncolors
- 0, // sizeof knowncolors
- HSVA_DOUBLE, // color_type
- };
- static gvdevice_features_t device_features_ps = {
- GVDEVICE_DOES_PAGES
- | GVDEVICE_DOES_LAYERS, // flags
- {36.,36.}, // default margin - points
- {612.,792.}, // default page width, height - points
- {72.,72.}, // default dpi
- };
- static gvdevice_features_t device_features_eps = {
- 0, // flags
- {36.,36.}, // default margin - points
- {612.,792.}, // default page width, height - points
- {72.,72.}, // default dpi
- };
- gvplugin_installed_t gvrender_lasi_types[] = {
- {FORMAT_PS, "lasi", -5, &lasi_engine, &render_features_lasi},
- {0, nullptr, 0, nullptr, nullptr}
- };
- gvplugin_installed_t gvdevice_lasi_types[] = {
- {FORMAT_PS, "ps:lasi", -5, nullptr, &device_features_ps},
- {FORMAT_PS2, "ps2:lasi", -5, nullptr, &device_features_ps},
- {FORMAT_EPS, "eps:lasi", -5, nullptr, &device_features_eps},
- {0, nullptr, 0, nullptr, nullptr}
- };
|