83bb7c52dbf2e46c992ea80b48120ccdd89ca8b2
1 #define EXTENSION_INTERNAL_PDF_LATEX_RENDERER_CPP
3 /** \file
4 * Rendering LaTeX file (pdf+latex output)
5 *
6 * The idea stems from GNUPlot's epslatex terminal output :-)
7 */
8 /*
9 * Authors:
10 * Johan Engelen <goejendaagh@zonnet.nl>
11 * Miklos Erdelyi <erdelyim@gmail.com>
12 *
13 * Copyright (C) 2006-2010 Authors
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 "latex-text-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 };
101 namespace Inkscape {
102 namespace Extension {
103 namespace Internal {
105 bool
106 latex_render_document_text_to_file( SPDocument *doc, gchar const *filename,
107 const gchar * const exportId, bool exportDrawing, bool exportCanvas)
108 {
109 sp_document_ensure_up_to_date(doc);
111 SPItem *base = NULL;
113 bool pageBoundingBox = true;
114 if (exportId && strcmp(exportId, "")) {
115 // we want to export the given item only
116 base = SP_ITEM(doc->getObjectById(exportId));
117 pageBoundingBox = exportCanvas;
118 }
119 else {
120 // we want to export the entire document from root
121 base = SP_ITEM(sp_document_root(doc));
122 pageBoundingBox = !exportDrawing;
123 }
125 if (!base)
126 return false;
128 /* Create renderer */
129 LaTeXTextRenderer *renderer = new LaTeXTextRenderer();
131 bool ret = renderer->setTargetFile(filename);
132 if (ret) {
133 /* Render document */
134 bool ret = renderer->setupDocument(doc, pageBoundingBox, base);
135 if (ret) {
136 renderer->renderItem(base);
137 }
138 }
140 delete renderer;
142 return ret;
143 }
145 LaTeXTextRenderer::LaTeXTextRenderer(void)
146 : _stream(NULL),
147 _filename(NULL),
148 _width(0),
149 _height(0)
150 {
151 push_transform(Geom::identity());
152 }
154 LaTeXTextRenderer::~LaTeXTextRenderer(void)
155 {
156 if (_stream) {
157 writePostamble();
159 fclose(_stream);
160 }
162 /* restore default signal handling for SIGPIPE */
163 #if !defined(_WIN32) && !defined(__WIN32__)
164 (void) signal(SIGPIPE, SIG_DFL);
165 #endif
167 if (_filename) {
168 g_free(_filename);
169 }
171 return;
172 }
174 /** This should create the output LaTeX file, and assign it to _stream.
175 * @return Returns true when succesfull
176 */
177 bool
178 LaTeXTextRenderer::setTargetFile(gchar const *filename) {
179 if (filename != NULL) {
180 while (isspace(*filename)) filename += 1;
182 _filename = g_path_get_basename(filename);
184 gchar *filename_ext = g_strdup_printf("%s.tex", filename);
185 Inkscape::IO::dump_fopen_call(filename_ext, "K");
186 FILE *osf = Inkscape::IO::fopen_utf8name(filename_ext, "w+");
187 if (!osf) {
188 fprintf(stderr, "inkscape: fopen(%s): %s\n",
189 filename_ext, strerror(errno));
190 return false;
191 }
192 _stream = osf;
193 g_free(filename_ext);
194 }
196 if (_stream) {
197 /* fixme: this is kinda icky */
198 #if !defined(_WIN32) && !defined(__WIN32__)
199 (void) signal(SIGPIPE, SIG_IGN);
200 #endif
201 }
203 fprintf(_stream, "%%%% Creator: Inkscape %s, www.inkscape.org\n", PACKAGE_STRING);
204 fprintf(_stream, "%%%% PDF/EPS/PS + LaTeX output extension by Johan Engelen, 2010\n");
205 fprintf(_stream, "%%%% Accompanies image file '%s' (pdf, eps, ps)\n", _filename);
206 fprintf(_stream, "%%%%");
207 /* flush this to test output stream as early as possible */
208 if (fflush(_stream)) {
209 if (ferror(_stream)) {
210 g_print("Error %d on LaTeX file output stream: %s\n", errno,
211 g_strerror(errno));
212 }
213 g_print("Output to LaTeX file failed\n");
214 /* fixme: should use pclose() for pipes */
215 fclose(_stream);
216 _stream = NULL;
217 fflush(stdout);
218 return false;
219 }
221 writePreamble();
223 return true;
224 }
226 static char const preamble[] =
227 "%% To include the image in your LaTeX document, write\n"
228 "%% \\setlength{\\unitlength}{<desired width>}\n"
229 "%% \\input{<filename>.tex}\n"
230 "%% instead of\n"
231 "%% \\includegraphics[width=<desired width>]{<filename>.pdf}\n"
232 "\n"
233 "\\begingroup \n"
234 " \\makeatletter \n"
235 " \\providecommand\\color[2][]{% \n"
236 " \\GenericError{(Inkscape) \\space\\space\\@spaces}{% \n"
237 " Color is used for the text in Inkscape, but the color package color is not loaded. \n"
238 " }{Either use black text in Inkscape or load the package \n"
239 " color.sty in LaTeX.}% \n"
240 " \\renewcommand\\color[2][]{}% \n"
241 " }%% \n"
242 " \\providecommand\\rotatebox[2]{#2}% \n"
243 " \\makeatother \n";
245 static char const postamble[] =
246 " \\end{picture}% \n"
247 "\\endgroup \n";
249 void
250 LaTeXTextRenderer::writePreamble()
251 {
252 fprintf(_stream, "%s", preamble);
253 }
254 void
255 LaTeXTextRenderer::writePostamble()
256 {
257 fprintf(_stream, "%s", postamble);
258 }
260 void
261 LaTeXTextRenderer::sp_group_render(SPItem *item)
262 {
263 SPGroup *group = SP_GROUP(item);
265 GSList *l = g_slist_reverse(group->childList(false));
266 while (l) {
267 SPObject *o = SP_OBJECT (l->data);
268 if (SP_IS_ITEM(o)) {
269 renderItem (SP_ITEM (o));
270 }
271 l = g_slist_remove (l, o);
272 }
273 }
275 void
276 LaTeXTextRenderer::sp_use_render(SPItem *item)
277 {
278 /*
279 bool translated = false;
280 SPUse *use = SP_USE(item);
282 if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) {
283 Geom::Matrix tp(Geom::Translate(use->x.computed, use->y.computed));
284 ctx->pushState();
285 ctx->transform(&tp);
286 translated = true;
287 }
289 if (use->child && SP_IS_ITEM(use->child)) {
290 renderItem(SP_ITEM(use->child));
291 }
293 if (translated) {
294 ctx->popState();
295 }
296 */
297 }
299 void
300 LaTeXTextRenderer::sp_text_render(SPItem *item)
301 {
302 SPText *textobj = SP_TEXT (item);
304 Geom::Matrix i2doc = sp_item_i2doc_affine(item);
305 push_transform(i2doc);
307 gchar *str = sp_te_get_string_multiline(item);
309 // get position and alignment
310 Geom::Point pos;
311 gchar *alignment = NULL;
312 Geom::OptRect bbox = item->getBounds(transform());
313 Geom::Interval bbox_x = (*bbox)[Geom::X];
314 Geom::Interval bbox_y = (*bbox)[Geom::Y];
315 SPStyle *style = SP_OBJECT_STYLE (SP_OBJECT(item));
316 switch (style->text_anchor.computed) {
317 case SP_CSS_TEXT_ANCHOR_START:
318 pos = Geom::Point( bbox_x.min() , bbox_y.middle() );
319 alignment = "[l]";
320 break;
321 case SP_CSS_TEXT_ANCHOR_END:
322 pos = Geom::Point( bbox_x.max() , bbox_y.middle() );
323 alignment = "[r]";
324 break;
325 case SP_CSS_TEXT_ANCHOR_MIDDLE:
326 default:
327 pos = bbox->midpoint();
328 alignment = "";
329 break;
330 }
332 // get rotation
333 Geom::Matrix wotransl = i2doc.without_translation();
334 double degrees = -180/M_PI * Geom::atan2(wotransl.xAxis());
335 bool has_rotation = !Geom::are_near(degrees,0.);
337 pop_transform();
339 // write to LaTeX
340 Inkscape::SVGOStringStream os;
342 // os << "\\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){\\makebox(0,0)[" << alignment << "]{\\strut{}" << str << "}}%%\n";
343 os << " \\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){";
344 os << "\\makebox(0,0)" << alignment << "{";
345 if (has_rotation) {
346 os << "\\rotatebox{" << degrees << "}{";
347 }
348 os << str;
349 if (has_rotation) {
350 os << "}"; // rotatebox end
351 }
352 os << "}"; //makebox end
353 os << "}%\n"; // put end
355 fprintf(_stream, "%s", os.str().c_str());
356 }
358 void
359 LaTeXTextRenderer::sp_flowtext_render(SPItem *item)
360 {
361 /* SPFlowtext *group = SP_FLOWTEXT(item);
363 // write to LaTeX
364 Inkscape::SVGOStringStream os;
366 os << " \\begin{picture}(" << _width << "," << _height << ")%%\n";
367 os << " \\gplgaddtomacro\\gplbacktext{%%\n";
368 os << " \\csname LTb\\endcsname%%\n";
369 os << "\\put(0,0){\\makebox(0,0)[lb]{\\strut{}Position}}%%\n";
371 fprintf(_stream, "%s", os.str().c_str());
372 */
373 }
375 void
376 LaTeXTextRenderer::sp_root_render(SPItem *item)
377 {
378 SPRoot *root = SP_ROOT(item);
380 // ctx->pushState();
381 // setStateForItem(ctx, item);
382 Geom::Matrix tempmat (root->c2p);
383 push_transform(tempmat);
384 sp_group_render(item);
385 pop_transform();
386 }
388 void
389 LaTeXTextRenderer::sp_item_invoke_render(SPItem *item)
390 {
391 // Check item's visibility
392 if (item->isHidden()) {
393 return;
394 }
396 if (SP_IS_ROOT(item)) {
397 TRACE(("root\n"));
398 return sp_root_render(item);
399 } else if (SP_IS_GROUP(item)) {
400 TRACE(("group\n"));
401 return sp_group_render(item);
402 } else if (SP_IS_USE(item)) {
403 TRACE(("use begin---\n"));
404 sp_use_render(item);
405 TRACE(("---use end\n"));
406 } else if (SP_IS_TEXT(item)) {
407 TRACE(("text\n"));
408 return sp_text_render(item);
409 } else if (SP_IS_FLOWTEXT(item)) {
410 TRACE(("flowtext\n"));
411 return sp_flowtext_render(item);
412 }
413 // We are not interested in writing the other SPItem types to LaTeX
414 }
416 void
417 LaTeXTextRenderer::setStateForItem(SPItem const *item)
418 {
419 /*
420 SPStyle const *style = SP_OBJECT_STYLE(item);
421 ctx->setStateForStyle(style);
423 CairoRenderState *state = ctx->getCurrentState();
424 state->clip_path = item->clip_ref->getObject();
425 state->mask = item->mask_ref->getObject();
426 state->item_transform = Geom::Matrix (item->transform);
428 // If parent_has_userspace is true the parent state's transform
429 // has to be used for the mask's/clippath's context.
430 // This is so because we use the image's/(flow)text's transform for positioning
431 // instead of explicitly specifying it and letting the renderer do the
432 // transformation before rendering the item.
433 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item) || SP_IS_IMAGE(item))
434 state->parent_has_userspace = TRUE;
435 TRACE(("setStateForItem opacity: %f\n", state->opacity));
436 */
437 }
439 void
440 LaTeXTextRenderer::renderItem(SPItem *item)
441 {
442 // ctx->pushState();
443 // setStateForItem(ctx, item);
445 // CairoRenderState *state = ctx->getCurrentState();
446 // state->need_layer = ( state->mask || state->clip_path || state->opacity != 1.0 );
448 // Draw item on a temporary surface so a mask, clip path, or opacity can be applied to it.
449 // if (state->need_layer) {
450 // state->merge_opacity = FALSE;
451 // ctx->pushLayer();
452 // }
453 Geom::Matrix tempmat (item->transform);
454 // ctx->transform(&tempmat);
455 sp_item_invoke_render(item);
457 // if (state->need_layer)
458 // ctx->popLayer();
460 // ctx->popState();
461 }
463 bool
464 LaTeXTextRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, SPItem *base)
465 {
466 // The boundingbox calculation here should be exactly the same as the one by CairoRenderer::setupDocument !
468 if (!base)
469 base = SP_ITEM(sp_document_root(doc));
471 NRRect d;
472 if (pageBoundingBox) {
473 d.x0 = d.y0 = 0;
474 d.x1 = ceil(sp_document_width(doc));
475 d.y1 = ceil(sp_document_height(doc));
476 } else {
477 sp_item_invoke_bbox(base, &d, sp_item_i2d_affine(base), TRUE, SPItem::RENDERING_BBOX);
478 }
480 // scale all coordinates, such that the width of the image is 1, this is convenient for scaling the image in LaTeX
481 double scale = 1/(d.x1-d.x0);
482 _width = (d.x1-d.x0) * scale;
483 _height = (d.y1-d.y0) * scale;
484 push_transform( Geom::Scale(scale, scale) );
486 if (!pageBoundingBox)
487 {
488 double high = sp_document_height(doc);
490 push_transform( Geom::Translate( -d.x0,
491 -d.y0 ) );
492 }
494 // flip y-axis
495 push_transform( Geom::Scale(1,-1) * Geom::Translate(0, sp_document_height(doc)) );
497 // write the info to LaTeX
498 Inkscape::SVGOStringStream os;
500 os << " \\begin{picture}(" << _width << "," << _height << ")%\n";
501 // strip pathname, as it is probably desired. Having a specific path in the TeX file is not convenient.
502 os << " \\put(0,0){\\includegraphics[width=\\unitlength]{" << _filename << "}}%\n";
504 fprintf(_stream, "%s", os.str().c_str());
506 return true;
507 }
509 Geom::Matrix const &
510 LaTeXTextRenderer::transform()
511 {
512 return _transform_stack.top();
513 }
515 void
516 LaTeXTextRenderer::push_transform(Geom::Matrix const &tr)
517 {
518 if(_transform_stack.size()){
519 Geom::Matrix tr_top = _transform_stack.top();
520 _transform_stack.push(tr * tr_top);
521 } else {
522 _transform_stack.push(tr);
523 }
524 }
526 void
527 LaTeXTextRenderer::pop_transform()
528 {
529 _transform_stack.pop();
530 }
532 /*
533 #include "macros.h" // SP_PRINT_*
535 // Apply an SVG clip path
536 void
537 LaTeXTextRenderer::applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp)
538 {
539 g_assert( ctx != NULL && ctx->_is_valid );
541 if (cp == NULL)
542 return;
544 CairoRenderContext::CairoRenderMode saved_mode = ctx->getRenderMode();
545 ctx->setRenderMode(CairoRenderContext::RENDER_MODE_CLIP);
547 Geom::Matrix saved_ctm;
548 if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) {
549 //SP_PRINT_DRECT("clipd", cp->display->bbox);
550 NRRect clip_bbox(cp->display->bbox);
551 Geom::Matrix t(Geom::Scale(clip_bbox.x1 - clip_bbox.x0, clip_bbox.y1 - clip_bbox.y0));
552 t[4] = clip_bbox.x0;
553 t[5] = clip_bbox.y0;
554 t *= ctx->getCurrentState()->transform;
555 ctx->getTransform(&saved_ctm);
556 ctx->setTransform(&t);
557 }
559 TRACE(("BEGIN clip\n"));
560 SPObject *co = SP_OBJECT(cp);
561 for (SPObject *child = sp_object_first_child(co) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
562 if (SP_IS_ITEM(child)) {
563 SPItem *item = SP_ITEM(child);
565 // combine transform of the item in clippath and the item using clippath:
566 Geom::Matrix tempmat (item->transform);
567 tempmat = tempmat * (ctx->getCurrentState()->item_transform);
569 // render this item in clippath
570 ctx->pushState();
571 ctx->transform(&tempmat);
572 setStateForItem(ctx, item);
573 sp_item_invoke_render(item, ctx);
574 ctx->popState();
575 }
576 }
577 TRACE(("END clip\n"));
579 // do clipping only if this was the first call to applyClipPath
580 if (ctx->getClipMode() == CairoRenderContext::CLIP_MODE_PATH
581 && saved_mode == CairoRenderContext::RENDER_MODE_NORMAL)
582 cairo_clip(ctx->_cr);
584 if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX)
585 ctx->setTransform(&saved_ctm);
587 ctx->setRenderMode(saved_mode);
588 }
590 // Apply an SVG mask
591 void
592 LaTeXTextRenderer::applyMask(CairoRenderContext *ctx, SPMask const *mask)
593 {
594 g_assert( ctx != NULL && ctx->_is_valid );
596 if (mask == NULL)
597 return;
599 //SP_PRINT_DRECT("maskd", &mask->display->bbox);
600 NRRect mask_bbox(mask->display->bbox);
601 // TODO: should the bbox be transformed if maskUnits != userSpaceOnUse ?
602 if (mask->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) {
603 Geom::Matrix t(Geom::Scale(mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0));
604 t[4] = mask_bbox.x0;
605 t[5] = mask_bbox.y0;
606 t *= ctx->getCurrentState()->transform;
607 ctx->setTransform(&t);
608 }
610 // Clip mask contents... but...
611 // The mask's bounding box is the "geometric bounding box" which doesn't allow for
612 // filters which extend outside the bounding box. So don't clip.
613 // ctx->addClippingRect(mask_bbox.x0, mask_bbox.y0, mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0);
615 ctx->pushState();
617 TRACE(("BEGIN mask\n"));
618 SPObject *co = SP_OBJECT(mask);
619 for (SPObject *child = sp_object_first_child(co) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
620 if (SP_IS_ITEM(child)) {
621 SPItem *item = SP_ITEM(child);
622 renderItem(ctx, item);
623 }
624 }
625 TRACE(("END mask\n"));
627 ctx->popState();
628 }
629 */
631 } /* namespace Internal */
632 } /* namespace Extension */
633 } /* namespace Inkscape */
635 #undef TRACE
637 /* End of GNU GPL code */
639 /*
640 Local Variables:
641 mode:c++
642 c-file-style:"stroustrup"
643 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
644 indent-tabs-mode:nil
645 fill-column:99
646 End:
647 */
648 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :