ef97da444a9b09c39353d1f40a89fe8b5bf63fab
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 Geom::Matrix i2doc = sp_item_i2doc_affine(item);
339 push_transform(i2doc);
341 gchar *str = sp_te_get_string_multiline(item);
342 Geom::Point pos = textobj->attributes.firstXY() * transform();
343 gchar *alignment = "lb";
345 // get rotation
346 Geom::Matrix wotransl = i2doc.without_translation();
347 double degrees = -180/M_PI * Geom::atan2(wotransl.xAxis());
349 pop_transform();
351 // write to LaTeX
352 Inkscape::SVGOStringStream os;
354 // os << "\\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){\\makebox(0,0)[" << alignment << "]{\\strut{}" << str << "}}%%\n";
355 os << "\\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){";
356 if (!Geom::are_near(degrees,0.)) {
357 os << "\\rotatebox{" << degrees << "}{";
358 }
359 os << str;
360 if (!Geom::are_near(degrees,0.)) {
361 os << "}";
362 }
363 os << "}%%\n";
365 fprintf(_stream, "%s", os.str().c_str());
366 }
368 void
369 PDFLaTeXRenderer::sp_flowtext_render(SPItem *item)
370 {
371 /* SPFlowtext *group = SP_FLOWTEXT(item);
373 // write to LaTeX
374 Inkscape::SVGOStringStream os;
376 os << " \\begin{picture}(" << _width << "," << _height << ")%%\n";
377 os << " \\gplgaddtomacro\\gplbacktext{%%\n";
378 os << " \\csname LTb\\endcsname%%\n";
379 os << "\\put(0,0){\\makebox(0,0)[lb]{\\strut{}Position}}%%\n";
381 fprintf(_stream, "%s", os.str().c_str());
382 */
383 }
385 void
386 PDFLaTeXRenderer::sp_root_render(SPItem *item)
387 {
388 SPRoot *root = SP_ROOT(item);
390 // ctx->pushState();
391 // setStateForItem(ctx, item);
392 Geom::Matrix tempmat (root->c2p);
393 push_transform(tempmat);
394 sp_group_render(item);
395 pop_transform();
396 }
398 void
399 PDFLaTeXRenderer::sp_item_invoke_render(SPItem *item)
400 {
401 // Check item's visibility
402 if (item->isHidden()) {
403 return;
404 }
406 g_message("hier?");
407 if (SP_IS_ROOT(item)) {
408 TRACE(("root\n"));
409 return sp_root_render(item);
410 } else if (SP_IS_GROUP(item)) {
411 TRACE(("group\n"));
412 return sp_group_render(item);
413 } else if (SP_IS_USE(item)) {
414 TRACE(("use begin---\n"));
415 sp_use_render(item);
416 TRACE(("---use end\n"));
417 } else if (SP_IS_TEXT(item)) {
418 TRACE(("text\n"));
419 return sp_text_render(item);
420 } else if (SP_IS_FLOWTEXT(item)) {
421 TRACE(("flowtext\n"));
422 return sp_flowtext_render(item);
423 }
424 // We are not interested in writing the other SPItem types to LaTeX
425 }
427 void
428 PDFLaTeXRenderer::setStateForItem(SPItem const *item)
429 {
430 /*
431 SPStyle const *style = SP_OBJECT_STYLE(item);
432 ctx->setStateForStyle(style);
434 CairoRenderState *state = ctx->getCurrentState();
435 state->clip_path = item->clip_ref->getObject();
436 state->mask = item->mask_ref->getObject();
437 state->item_transform = Geom::Matrix (item->transform);
439 // If parent_has_userspace is true the parent state's transform
440 // has to be used for the mask's/clippath's context.
441 // This is so because we use the image's/(flow)text's transform for positioning
442 // instead of explicitly specifying it and letting the renderer do the
443 // transformation before rendering the item.
444 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item) || SP_IS_IMAGE(item))
445 state->parent_has_userspace = TRUE;
446 TRACE(("setStateForItem opacity: %f\n", state->opacity));
447 */
448 }
450 void
451 PDFLaTeXRenderer::renderItem(SPItem *item)
452 {
453 // ctx->pushState();
454 // setStateForItem(ctx, item);
456 // CairoRenderState *state = ctx->getCurrentState();
457 // state->need_layer = ( state->mask || state->clip_path || state->opacity != 1.0 );
459 // Draw item on a temporary surface so a mask, clip path, or opacity can be applied to it.
460 // if (state->need_layer) {
461 // state->merge_opacity = FALSE;
462 // ctx->pushLayer();
463 // }
464 Geom::Matrix tempmat (item->transform);
465 // ctx->transform(&tempmat);
466 sp_item_invoke_render(item);
468 // if (state->need_layer)
469 // ctx->popLayer();
471 // ctx->popState();
472 }
474 bool
475 PDFLaTeXRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, SPItem *base)
476 {
477 // The boundingbox calculation here should be exactly the same as the one by CairoRenderer::setupDocument !
479 if (!base)
480 base = SP_ITEM(sp_document_root(doc));
482 NRRect d;
483 if (pageBoundingBox) {
484 d.x0 = d.y0 = 0;
485 d.x1 = ceil(sp_document_width(doc));
486 d.y1 = ceil(sp_document_height(doc));
487 } else {
488 sp_item_invoke_bbox(base, &d, sp_item_i2d_affine(base), TRUE, SPItem::RENDERING_BBOX);
489 }
491 // convert from px to pt
492 push_transform( Geom::Scale(PT_PER_PX, PT_PER_PX) );
494 if (!pageBoundingBox)
495 {
496 double high = sp_document_height(doc);
498 push_transform( Geom::Translate( -d.x0,
499 -d.y0 ) );
500 }
502 // flip y-axis
503 push_transform( Geom::Scale(1,-1) * Geom::Translate(0, sp_document_height(doc)) );
505 _width = (d.x1-d.x0) * PT_PER_PX;
506 _height = (d.y1-d.y0) * PT_PER_PX;
508 // write the info to LaTeX
509 Inkscape::SVGOStringStream os;
511 os << " \\begin{picture}(" << _width << "," << _height << ")%%\n";
512 os << " \\gplgaddtomacro\\gplbacktext{%%\n";
513 os << " \\csname LTb\\endcsname%%\n";
515 fprintf(_stream, "%s", os.str().c_str());
517 return true;
518 }
520 Geom::Matrix const &
521 PDFLaTeXRenderer::transform()
522 {
523 return _transform_stack.top();
524 }
526 void
527 PDFLaTeXRenderer::push_transform(Geom::Matrix const &tr)
528 {
529 if(_transform_stack.size()){
530 Geom::Matrix tr_top = _transform_stack.top();
531 _transform_stack.push(tr * tr_top);
532 } else {
533 _transform_stack.push(tr);
534 }
535 }
537 void
538 PDFLaTeXRenderer::pop_transform()
539 {
540 _transform_stack.pop();
541 }
543 /*
544 #include "macros.h" // SP_PRINT_*
546 // Apply an SVG clip path
547 void
548 PDFLaTeXRenderer::applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp)
549 {
550 g_assert( ctx != NULL && ctx->_is_valid );
552 if (cp == NULL)
553 return;
555 CairoRenderContext::CairoRenderMode saved_mode = ctx->getRenderMode();
556 ctx->setRenderMode(CairoRenderContext::RENDER_MODE_CLIP);
558 Geom::Matrix saved_ctm;
559 if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) {
560 //SP_PRINT_DRECT("clipd", cp->display->bbox);
561 NRRect clip_bbox(cp->display->bbox);
562 Geom::Matrix t(Geom::Scale(clip_bbox.x1 - clip_bbox.x0, clip_bbox.y1 - clip_bbox.y0));
563 t[4] = clip_bbox.x0;
564 t[5] = clip_bbox.y0;
565 t *= ctx->getCurrentState()->transform;
566 ctx->getTransform(&saved_ctm);
567 ctx->setTransform(&t);
568 }
570 TRACE(("BEGIN clip\n"));
571 SPObject *co = SP_OBJECT(cp);
572 for (SPObject *child = sp_object_first_child(co) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
573 if (SP_IS_ITEM(child)) {
574 SPItem *item = SP_ITEM(child);
576 // combine transform of the item in clippath and the item using clippath:
577 Geom::Matrix tempmat (item->transform);
578 tempmat = tempmat * (ctx->getCurrentState()->item_transform);
580 // render this item in clippath
581 ctx->pushState();
582 ctx->transform(&tempmat);
583 setStateForItem(ctx, item);
584 sp_item_invoke_render(item, ctx);
585 ctx->popState();
586 }
587 }
588 TRACE(("END clip\n"));
590 // do clipping only if this was the first call to applyClipPath
591 if (ctx->getClipMode() == CairoRenderContext::CLIP_MODE_PATH
592 && saved_mode == CairoRenderContext::RENDER_MODE_NORMAL)
593 cairo_clip(ctx->_cr);
595 if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX)
596 ctx->setTransform(&saved_ctm);
598 ctx->setRenderMode(saved_mode);
599 }
601 // Apply an SVG mask
602 void
603 PDFLaTeXRenderer::applyMask(CairoRenderContext *ctx, SPMask const *mask)
604 {
605 g_assert( ctx != NULL && ctx->_is_valid );
607 if (mask == NULL)
608 return;
610 //SP_PRINT_DRECT("maskd", &mask->display->bbox);
611 NRRect mask_bbox(mask->display->bbox);
612 // TODO: should the bbox be transformed if maskUnits != userSpaceOnUse ?
613 if (mask->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) {
614 Geom::Matrix t(Geom::Scale(mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0));
615 t[4] = mask_bbox.x0;
616 t[5] = mask_bbox.y0;
617 t *= ctx->getCurrentState()->transform;
618 ctx->setTransform(&t);
619 }
621 // Clip mask contents... but...
622 // The mask's bounding box is the "geometric bounding box" which doesn't allow for
623 // filters which extend outside the bounding box. So don't clip.
624 // ctx->addClippingRect(mask_bbox.x0, mask_bbox.y0, mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0);
626 ctx->pushState();
628 TRACE(("BEGIN mask\n"));
629 SPObject *co = SP_OBJECT(mask);
630 for (SPObject *child = sp_object_first_child(co) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
631 if (SP_IS_ITEM(child)) {
632 SPItem *item = SP_ITEM(child);
633 renderItem(ctx, item);
634 }
635 }
636 TRACE(("END mask\n"));
638 ctx->popState();
639 }
640 */
642 } /* namespace Internal */
643 } /* namespace Extension */
644 } /* namespace Inkscape */
646 #undef TRACE
648 /* End of GNU GPL code */
650 /*
651 Local Variables:
652 mode:c++
653 c-file-style:"stroustrup"
654 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
655 indent-tabs-mode:nil
656 fill-column:99
657 End:
658 */
659 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :