1 #define EXTENSION_INTERNAL_PDF_LATEX_RENDERER_CPP
3 /** \file
4 * Rendering LaTeX file (pdf+latex output)
5 */
6 /*
7 * Authors:
8 * Johan Engelen <goejendaagh@zonnet.nl>
9 * Miklos Erdelyi <erdelyim@gmail.com>
10 *
11 * Copyright (C) 2006-2010 Authors
12 *
13 * Most of the pre- and postamble is copied from GNUPlot's epslatex terminal output :-)
14 *
15 * Licensed under GNU GPL
16 */
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #ifndef PANGO_ENABLE_BACKEND
23 #define PANGO_ENABLE_BACKEND
24 #endif
26 #ifndef PANGO_ENABLE_ENGINE
27 #define PANGO_ENABLE_ENGINE
28 #endif
31 #include <signal.h>
32 #include <errno.h>
34 #include "libnr/nr-rect.h"
35 #include "libnrtype/Layout-TNG.h"
36 #include <2geom/transforms.h>
37 #include <2geom/pathvector.h>
39 #include <glib/gmem.h>
41 #include <glibmm/i18n.h>
42 #include "display/nr-arena.h"
43 #include "display/nr-arena-item.h"
44 #include "display/nr-arena-group.h"
45 #include "display/curve.h"
46 #include "display/canvas-bpath.h"
47 #include "sp-item.h"
48 #include "sp-item-group.h"
49 #include "style.h"
50 #include "marker.h"
51 #include "sp-linear-gradient.h"
52 #include "sp-radial-gradient.h"
53 #include "sp-root.h"
54 #include "sp-use.h"
55 #include "sp-text.h"
56 #include "sp-flowtext.h"
57 #include "sp-mask.h"
58 #include "sp-clippath.h"
59 #include "text-editing.h"
61 #include <unit-constants.h>
62 #include "helper/png-write.h"
63 #include "helper/pixbuf-ops.h"
65 #include "pdflatex-renderer.h"
66 #include "extension/system.h"
68 #include "io/sys.h"
70 #include <cairo.h>
72 // include support for only the compiled-in surface types
73 #ifdef CAIRO_HAS_PDF_SURFACE
74 #include <cairo-pdf.h>
75 #endif
76 #ifdef CAIRO_HAS_PS_SURFACE
77 #include <cairo-ps.h>
78 #endif
80 //#define TRACE(_args) g_message(_args)
81 #define TRACE(_args)
82 //#define TEST(_args) _args
83 #define TEST(_args)
85 // FIXME: expose these from sp-clippath/mask.cpp
86 struct SPClipPathView {
87 SPClipPathView *next;
88 unsigned int key;
89 NRArenaItem *arenaitem;
90 NRRect bbox;
91 };
93 struct SPMaskView {
94 SPMaskView *next;
95 unsigned int key;
96 NRArenaItem *arenaitem;
97 NRRect bbox;
98 };
100 namespace Inkscape {
101 namespace Extension {
102 namespace Internal {
105 PDFLaTeXRenderer::PDFLaTeXRenderer(void)
106 : _stream(NULL),
107 _filename(NULL),
108 _width(0),
109 _height(0)
110 {
111 push_transform(Geom::identity());
112 }
114 PDFLaTeXRenderer::~PDFLaTeXRenderer(void)
115 {
116 if (_stream) {
117 writePostamble();
119 fclose(_stream);
120 }
122 /* restore default signal handling for SIGPIPE */
123 #if !defined(_WIN32) && !defined(__WIN32__)
124 (void) signal(SIGPIPE, SIG_DFL);
125 #endif
127 if (_filename) {
128 g_free(_filename);
129 }
131 return;
132 }
134 /** This should create the output LaTeX file, and assign it to _stream.
135 * @return Returns true when succesfull
136 */
137 bool
138 PDFLaTeXRenderer::setTargetFile(gchar const *filename) {
139 if (filename != NULL) {
140 while (isspace(*filename)) filename += 1;
142 _filename = g_strdup(filename);
144 gchar *filename_ext = g_strdup_printf("%s.tex", filename);
145 Inkscape::IO::dump_fopen_call(filename_ext, "K");
146 FILE *osf = Inkscape::IO::fopen_utf8name(filename_ext, "w+");
147 if (!osf) {
148 fprintf(stderr, "inkscape: fopen(%s): %s\n",
149 filename_ext, strerror(errno));
150 return false;
151 }
152 _stream = osf;
153 g_free(filename_ext);
154 }
156 if (_stream) {
157 /* fixme: this is kinda icky */
158 #if !defined(_WIN32) && !defined(__WIN32__)
159 (void) signal(SIGPIPE, SIG_IGN);
160 #endif
161 }
163 fprintf(_stream, "%%%% Creator: Inkscape %s, www.inkscape.org\n", PACKAGE_STRING);
164 fprintf(_stream, "%%%% PDF + LaTeX output extension by Johan Engelen, 2010\n");
165 fprintf(_stream, "%%%% Accompanies %s.pdf\n", _filename);
166 /* flush this to test output stream as early as possible */
167 if (fflush(_stream)) {
168 if (ferror(_stream)) {
169 g_print("Error %d on LaTeX file output stream: %s\n", errno,
170 g_strerror(errno));
171 }
172 g_print("Output to LaTeX file failed\n");
173 /* fixme: should use pclose() for pipes */
174 fclose(_stream);
175 _stream = NULL;
176 fflush(stdout);
177 return false;
178 }
180 writePreamble();
182 return true;
183 }
185 /* Most of this preamble is copied from GNUPlot's epslatex terminal output :-) */
186 static char const preamble[] =
187 "\\begingroup \n"
188 " \\makeatletter \n"
189 " \\providecommand\\color[2][]{%% \n"
190 " \\GenericError{(gnuplot) \\space\\space\\space\\@spaces}{%% \n"
191 " Package color not loaded in conjunction with \n"
192 " terminal option `colourtext'%% \n"
193 " }{See the gnuplot documentation for explanation.%% \n"
194 " }{Either use 'blacktext' in gnuplot or load the package \n"
195 " color.sty in LaTeX.}%% \n"
196 " \\renewcommand\\color[2][]{}%% \n"
197 " }%% \n"
198 " \\providecommand\\includegraphics[2][]{%% \n"
199 " \\GenericError{(gnuplot) \\space\\space\\space\\@spaces}{%% \n"
200 " Package graphicx or graphics not loaded%% \n"
201 " }{See the gnuplot documentation for explanation.%% \n"
202 " }{The gnuplot epslatex terminal needs graphicx.sty or graphics.sty.}%% \n"
203 " \\renewcommand\\includegraphics[2][]{}%% \n"
204 " }%% \n"
205 " \\providecommand\\rotatebox[2]{#2}%% \n"
206 " \\@ifundefined{ifGPcolor}{%% \n"
207 " \\newif\\ifGPcolor \n"
208 " \\GPcolorfalse \n"
209 " }{}%% \n"
210 " \\@ifundefined{ifGPblacktext}{%% \n"
211 " \\newif\\ifGPblacktext \n"
212 " \\GPblacktexttrue \n"
213 " }{}%% \n"
214 " %% define a \\g@addto@macro without @ in the name: \n"
215 " \\let\\gplgaddtomacro\\g@addto@macro \n"
216 " %% define empty templates for all commands taking text: \n"
217 " \\gdef\\gplbacktext{}%% \n"
218 " \\gdef\\gplfronttext{}%% \n"
219 " \\makeatother \n"
220 " \\ifGPblacktext \n"
221 " %% no textcolor at all \n"
222 " \\def\\colorrgb#1{}%% \n"
223 " \\def\\colorgray#1{}%% \n"
224 " \\else \n"
225 " %% gray or color? \n"
226 " \\ifGPcolor \n"
227 " \\def\\colorrgb#1{\\color[rgb]{#1}}%% \n"
228 " \\def\\colorgray#1{\\color[gray]{#1}}%% \n"
229 " \\expandafter\\def\\csname LTw\\endcsname{\\color{white}}%% \n"
230 " \\expandafter\\def\\csname LTb\\endcsname{\\color{black}}%% \n"
231 " \\expandafter\\def\\csname LTa\\endcsname{\\color{black}}%% \n"
232 " \\expandafter\\def\\csname LT0\\endcsname{\\color[rgb]{1,0,0}}%% \n"
233 " \\expandafter\\def\\csname LT1\\endcsname{\\color[rgb]{0,1,0}}%% \n"
234 " \\expandafter\\def\\csname LT2\\endcsname{\\color[rgb]{0,0,1}}%% \n"
235 " \\expandafter\\def\\csname LT3\\endcsname{\\color[rgb]{1,0,1}}%% \n"
236 " \\expandafter\\def\\csname LT4\\endcsname{\\color[rgb]{0,1,1}}%% \n"
237 " \\expandafter\\def\\csname LT5\\endcsname{\\color[rgb]{1,1,0}}%% \n"
238 " \\expandafter\\def\\csname LT6\\endcsname{\\color[rgb]{0,0,0}}%% \n"
239 " \\expandafter\\def\\csname LT7\\endcsname{\\color[rgb]{1,0.3,0}}%% \n"
240 " \\expandafter\\def\\csname LT8\\endcsname{\\color[rgb]{0.5,0.5,0.5}}%% \n"
241 " \\else \n"
242 " %% gray \n"
243 " \\def\\colorrgb#1{\\color{black}}%% \n"
244 " \\def\\colorgray#1{\\color[gray]{#1}}%% \n"
245 " \\expandafter\\def\\csname LTw\\endcsname{\\color{white}}% \n"
246 " \\expandafter\\def\\csname LTb\\endcsname{\\color{black}}% \n"
247 " \\expandafter\\def\\csname LTa\\endcsname{\\color{black}}% \n"
248 " \\expandafter\\def\\csname LT0\\endcsname{\\color{black}}% \n"
249 " \\expandafter\\def\\csname LT1\\endcsname{\\color{black}}% \n"
250 " \\expandafter\\def\\csname LT2\\endcsname{\\color{black}}% \n"
251 " \\expandafter\\def\\csname LT3\\endcsname{\\color{black}}% \n"
252 " \\expandafter\\def\\csname LT4\\endcsname{\\color{black}}% \n"
253 " \\expandafter\\def\\csname LT5\\endcsname{\\color{black}}% \n"
254 " \\expandafter\\def\\csname LT6\\endcsname{\\color{black}}% \n"
255 " \\expandafter\\def\\csname LT7\\endcsname{\\color{black}}% \n"
256 " \\expandafter\\def\\csname LT8\\endcsname{\\color{black}}% \n"
257 " \\fi \n"
258 " \\fi \n"
259 " \\setlength{\\unitlength}{1pt}% \n";
261 static char const postamble1[] =
262 " }%% \n"
263 " \\gplgaddtomacro\\gplfronttext{% \n"
264 " }%% \n"
265 " \\gplbacktext \n";
267 static char const postamble2[] =
268 " \\gplfronttext \n"
269 " \\end{picture}% \n"
270 "\\endgroup \n";
272 void
273 PDFLaTeXRenderer::writePreamble()
274 {
275 fprintf(_stream, "%s", preamble);
276 }
277 void
278 PDFLaTeXRenderer::writePostamble()
279 {
280 fprintf(_stream, "%s", postamble1);
282 // strip pathname on windows, as it is probably desired. It is not possible to work without paths on windows yet. (bug)
283 #ifdef WIN32
284 gchar *figurefile = g_path_get_basename(_filename);
285 #else
286 gchar *figurefile = g_strdup(_filename);
287 #endif
288 fprintf(_stream, " \\put(0,0){\\includegraphics{%s.pdf}}%%\n", figurefile);
289 g_free(figurefile);
291 fprintf(_stream, "%s", postamble2);
292 }
294 void
295 PDFLaTeXRenderer::sp_group_render(SPItem *item)
296 {
297 SPGroup *group = SP_GROUP(item);
299 GSList *l = g_slist_reverse(group->childList(false));
300 while (l) {
301 SPObject *o = SP_OBJECT (l->data);
302 if (SP_IS_ITEM(o)) {
303 renderItem (SP_ITEM (o));
304 }
305 l = g_slist_remove (l, o);
306 }
307 }
309 void
310 PDFLaTeXRenderer::sp_use_render(SPItem *item)
311 {
312 /*
313 bool translated = false;
314 SPUse *use = SP_USE(item);
316 if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) {
317 Geom::Matrix tp(Geom::Translate(use->x.computed, use->y.computed));
318 ctx->pushState();
319 ctx->transform(&tp);
320 translated = true;
321 }
323 if (use->child && SP_IS_ITEM(use->child)) {
324 renderItem(SP_ITEM(use->child));
325 }
327 if (translated) {
328 ctx->popState();
329 }
330 */
331 }
333 void
334 PDFLaTeXRenderer::sp_text_render(SPItem *item)
335 {
336 SPText *textobj = SP_TEXT (item);
338 push_transform(sp_item_i2doc_affine(item));
340 gchar *str = sp_te_get_string_multiline(item);
341 Geom::Point pos = textobj->attributes.firstXY() * transform();
342 gchar *alignment = "lb";
344 pop_transform();
346 // write to LaTeX
347 Inkscape::SVGOStringStream os;
349 os << "\\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){\\makebox(0,0)[" << alignment << "]{\\strut{}" << str << "}}%%\n";
351 fprintf(_stream, "%s", os.str().c_str());
352 }
354 void
355 PDFLaTeXRenderer::sp_flowtext_render(SPItem *item)
356 {
357 /* SPFlowtext *group = SP_FLOWTEXT(item);
359 // write to LaTeX
360 Inkscape::SVGOStringStream os;
362 os << " \\begin{picture}(" << _width << "," << _height << ")%%\n";
363 os << " \\gplgaddtomacro\\gplbacktext{%%\n";
364 os << " \\csname LTb\\endcsname%%\n";
365 os << "\\put(0,0){\\makebox(0,0)[lb]{\\strut{}Position}}%%\n";
367 fprintf(_stream, "%s", os.str().c_str());
368 */
369 }
371 void
372 PDFLaTeXRenderer::sp_root_render(SPItem *item)
373 {
374 SPRoot *root = SP_ROOT(item);
376 // ctx->pushState();
377 // setStateForItem(ctx, item);
378 Geom::Matrix tempmat (root->c2p);
379 push_transform(tempmat);
380 sp_group_render(item);
381 pop_transform();
382 }
384 void
385 PDFLaTeXRenderer::sp_item_invoke_render(SPItem *item)
386 {
387 // Check item's visibility
388 if (item->isHidden()) {
389 return;
390 }
392 g_message("hier?");
393 if (SP_IS_ROOT(item)) {
394 TRACE(("root\n"));
395 return sp_root_render(item);
396 } else if (SP_IS_GROUP(item)) {
397 TRACE(("group\n"));
398 return sp_group_render(item);
399 } else if (SP_IS_USE(item)) {
400 TRACE(("use begin---\n"));
401 sp_use_render(item);
402 TRACE(("---use end\n"));
403 } else if (SP_IS_TEXT(item)) {
404 TRACE(("text\n"));
405 return sp_text_render(item);
406 } else if (SP_IS_FLOWTEXT(item)) {
407 TRACE(("flowtext\n"));
408 return sp_flowtext_render(item);
409 }
410 // We are not interested in writing the other SPItem types to LaTeX
411 }
413 void
414 PDFLaTeXRenderer::setStateForItem(SPItem const *item)
415 {
416 /*
417 SPStyle const *style = SP_OBJECT_STYLE(item);
418 ctx->setStateForStyle(style);
420 CairoRenderState *state = ctx->getCurrentState();
421 state->clip_path = item->clip_ref->getObject();
422 state->mask = item->mask_ref->getObject();
423 state->item_transform = Geom::Matrix (item->transform);
425 // If parent_has_userspace is true the parent state's transform
426 // has to be used for the mask's/clippath's context.
427 // This is so because we use the image's/(flow)text's transform for positioning
428 // instead of explicitly specifying it and letting the renderer do the
429 // transformation before rendering the item.
430 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item) || SP_IS_IMAGE(item))
431 state->parent_has_userspace = TRUE;
432 TRACE(("setStateForItem opacity: %f\n", state->opacity));
433 */
434 }
436 void
437 PDFLaTeXRenderer::renderItem(SPItem *item)
438 {
439 // ctx->pushState();
440 // setStateForItem(ctx, item);
442 // CairoRenderState *state = ctx->getCurrentState();
443 // state->need_layer = ( state->mask || state->clip_path || state->opacity != 1.0 );
445 // Draw item on a temporary surface so a mask, clip path, or opacity can be applied to it.
446 // if (state->need_layer) {
447 // state->merge_opacity = FALSE;
448 // ctx->pushLayer();
449 // }
450 Geom::Matrix tempmat (item->transform);
451 // ctx->transform(&tempmat);
452 sp_item_invoke_render(item);
454 // if (state->need_layer)
455 // ctx->popLayer();
457 // ctx->popState();
458 }
460 bool
461 PDFLaTeXRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, SPItem *base)
462 {
463 // The boundingbox calculation here should be exactly the same as the one by CairoRenderer::setupDocument !
465 if (!base)
466 base = SP_ITEM(sp_document_root(doc));
468 NRRect d;
469 if (pageBoundingBox) {
470 d.x0 = d.y0 = 0;
471 d.x1 = ceil(sp_document_width(doc));
472 d.y1 = ceil(sp_document_height(doc));
473 } else {
474 sp_item_invoke_bbox(base, &d, sp_item_i2d_affine(base), TRUE, SPItem::RENDERING_BBOX);
475 }
477 // convert from px to pt
478 push_transform( Geom::Scale(PT_PER_PX, PT_PER_PX) );
480 if (!pageBoundingBox)
481 {
482 double high = sp_document_height(doc);
484 push_transform( Geom::Translate( -d.x0,
485 -d.y0 ) );
486 }
488 // flip y-axis
489 push_transform( Geom::Scale(1,-1) * Geom::Translate(0, sp_document_height(doc)) );
491 _width = (d.x1-d.x0) * PT_PER_PX;
492 _height = (d.y1-d.y0) * PT_PER_PX;
494 // write the info to LaTeX
495 Inkscape::SVGOStringStream os;
497 os << " \\begin{picture}(" << _width << "," << _height << ")%%\n";
498 os << " \\gplgaddtomacro\\gplbacktext{%%\n";
499 os << " \\csname LTb\\endcsname%%\n";
500 os << "\\put(0,0){\\makebox(0,0)[lb]{\\strut{}0,0}}%%\n";
502 fprintf(_stream, "%s", os.str().c_str());
504 return true;
505 }
507 Geom::Matrix const &
508 PDFLaTeXRenderer::transform()
509 {
510 return _transform_stack.top();
511 }
513 void
514 PDFLaTeXRenderer::push_transform(Geom::Matrix const &tr)
515 {
516 if(_transform_stack.size()){
517 Geom::Matrix tr_top = _transform_stack.top();
518 _transform_stack.push(tr * tr_top);
519 } else {
520 _transform_stack.push(tr);
521 }
522 }
524 void
525 PDFLaTeXRenderer::pop_transform()
526 {
527 _transform_stack.pop();
528 }
530 /*
531 #include "macros.h" // SP_PRINT_*
533 // Apply an SVG clip path
534 void
535 PDFLaTeXRenderer::applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp)
536 {
537 g_assert( ctx != NULL && ctx->_is_valid );
539 if (cp == NULL)
540 return;
542 CairoRenderContext::CairoRenderMode saved_mode = ctx->getRenderMode();
543 ctx->setRenderMode(CairoRenderContext::RENDER_MODE_CLIP);
545 Geom::Matrix saved_ctm;
546 if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) {
547 //SP_PRINT_DRECT("clipd", cp->display->bbox);
548 NRRect clip_bbox(cp->display->bbox);
549 Geom::Matrix t(Geom::Scale(clip_bbox.x1 - clip_bbox.x0, clip_bbox.y1 - clip_bbox.y0));
550 t[4] = clip_bbox.x0;
551 t[5] = clip_bbox.y0;
552 t *= ctx->getCurrentState()->transform;
553 ctx->getTransform(&saved_ctm);
554 ctx->setTransform(&t);
555 }
557 TRACE(("BEGIN clip\n"));
558 SPObject *co = SP_OBJECT(cp);
559 for (SPObject *child = sp_object_first_child(co) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
560 if (SP_IS_ITEM(child)) {
561 SPItem *item = SP_ITEM(child);
563 // combine transform of the item in clippath and the item using clippath:
564 Geom::Matrix tempmat (item->transform);
565 tempmat = tempmat * (ctx->getCurrentState()->item_transform);
567 // render this item in clippath
568 ctx->pushState();
569 ctx->transform(&tempmat);
570 setStateForItem(ctx, item);
571 sp_item_invoke_render(item, ctx);
572 ctx->popState();
573 }
574 }
575 TRACE(("END clip\n"));
577 // do clipping only if this was the first call to applyClipPath
578 if (ctx->getClipMode() == CairoRenderContext::CLIP_MODE_PATH
579 && saved_mode == CairoRenderContext::RENDER_MODE_NORMAL)
580 cairo_clip(ctx->_cr);
582 if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX)
583 ctx->setTransform(&saved_ctm);
585 ctx->setRenderMode(saved_mode);
586 }
588 // Apply an SVG mask
589 void
590 PDFLaTeXRenderer::applyMask(CairoRenderContext *ctx, SPMask const *mask)
591 {
592 g_assert( ctx != NULL && ctx->_is_valid );
594 if (mask == NULL)
595 return;
597 //SP_PRINT_DRECT("maskd", &mask->display->bbox);
598 NRRect mask_bbox(mask->display->bbox);
599 // TODO: should the bbox be transformed if maskUnits != userSpaceOnUse ?
600 if (mask->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) {
601 Geom::Matrix t(Geom::Scale(mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0));
602 t[4] = mask_bbox.x0;
603 t[5] = mask_bbox.y0;
604 t *= ctx->getCurrentState()->transform;
605 ctx->setTransform(&t);
606 }
608 // Clip mask contents... but...
609 // The mask's bounding box is the "geometric bounding box" which doesn't allow for
610 // filters which extend outside the bounding box. So don't clip.
611 // ctx->addClippingRect(mask_bbox.x0, mask_bbox.y0, mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0);
613 ctx->pushState();
615 TRACE(("BEGIN mask\n"));
616 SPObject *co = SP_OBJECT(mask);
617 for (SPObject *child = sp_object_first_child(co) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
618 if (SP_IS_ITEM(child)) {
619 SPItem *item = SP_ITEM(child);
620 renderItem(ctx, item);
621 }
622 }
623 TRACE(("END mask\n"));
625 ctx->popState();
626 }
627 */
629 } /* namespace Internal */
630 } /* namespace Extension */
631 } /* namespace Inkscape */
633 #undef TRACE
635 /* End of GNU GPL code */
637 /*
638 Local Variables:
639 mode:c++
640 c-file-style:"stroustrup"
641 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
642 indent-tabs-mode:nil
643 fill-column:99
644 End:
645 */
646 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :