1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3  * Rendering LaTeX file (pdf/eps/ps+latex output)
4  *
5  * The idea stems from GNUPlot's epslatex terminal output :-)
6  */
7 /*
8  * Authors:
9  *   Johan Engelen <goejendaagh@zonnet.nl>
10  *   Miklos Erdelyi <erdelyim@gmail.com>
11  *   Jon A. Cruz <jon@joncruz.org>
12  *   Abhishek Sharma
13  *
14  * Copyright (C) 2006-2011 Authors
15  *
16  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17  */
18 
19 #include "latex-text-renderer.h"
20 
21 #include <csignal>
22 #include <cerrno>
23 
24 #include <glibmm/i18n.h>
25 #include <glibmm/regex.h>
26 
27 #include "libnrtype/Layout-TNG.h"
28 #include <2geom/transforms.h>
29 #include <2geom/rect.h>
30 
31 #include "object/sp-item.h"
32 #include "object/sp-item-group.h"
33 #include "object/sp-root.h"
34 #include "object/sp-use.h"
35 #include "object/sp-text.h"
36 #include "object/sp-flowtext.h"
37 #include "object/sp-rect.h"
38 #include "style.h"
39 
40 #include "text-editing.h"
41 
42 #include "util/units.h"
43 
44 #include "extension/output.h"
45 #include "extension/system.h"
46 
47 #include "inkscape-version.h"
48 #include "io/sys.h"
49 #include "document.h"
50 
51 namespace Inkscape {
52 namespace Extension {
53 namespace Internal {
54 
55 /**
56  * This method is called by the PDF, EPS and PS output extensions.
57  * @param filename This should be the filename without '_tex' extension to which the tex code should be written. Output goes to <filename>_tex, note the underscore instead of period.
58  */
59 bool
latex_render_document_text_to_file(SPDocument * doc,gchar const * filename,const gchar * const exportId,bool exportDrawing,bool exportCanvas,float bleedmargin_px,bool pdflatex)60 latex_render_document_text_to_file( SPDocument *doc, gchar const *filename,
61                                     const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px,
62                                     bool pdflatex)
63 {
64     doc->ensureUpToDate();
65 
66     SPRoot *root = doc->getRoot();
67     SPItem *base = nullptr;
68 
69     bool pageBoundingBox = true;
70     if (exportId && strcmp(exportId, "")) {
71         // we want to export the given item only
72         base = dynamic_cast<SPItem *>(doc->getObjectById(exportId));
73         if (!base) {
74             throw Inkscape::Extension::Output::export_id_not_found(exportId);
75         }
76         root->cropToObject(base); // TODO: This is inconsistent in CLI (should only happen for --export-id-only)
77         pageBoundingBox = exportCanvas;
78     }
79     else {
80         // we want to export the entire document from root
81         base = root;
82         pageBoundingBox = !exportDrawing;
83     }
84 
85     if (!base)
86         return false;
87 
88     /* Create renderer */
89     LaTeXTextRenderer *renderer = new LaTeXTextRenderer(pdflatex);
90 
91     bool ret = renderer->setTargetFile(filename);
92     if (ret) {
93         /* Render document */
94         bool ret = renderer->setupDocument(doc, pageBoundingBox, bleedmargin_px, base);
95         if (ret) {
96             renderer->renderItem(root);
97         }
98     }
99 
100     delete renderer;
101 
102     return ret;
103 }
104 
LaTeXTextRenderer(bool pdflatex)105 LaTeXTextRenderer::LaTeXTextRenderer(bool pdflatex)
106   : _stream(nullptr),
107     _filename(nullptr),
108     _pdflatex(pdflatex),
109     _omittext_state(EMPTY),
110     _omittext_page(1)
111 {
112     push_transform(Geom::identity());
113 }
114 
~LaTeXTextRenderer()115 LaTeXTextRenderer::~LaTeXTextRenderer()
116 {
117     if (_stream) {
118         writePostamble();
119 
120         fclose(_stream);
121     }
122 
123     /* restore default signal handling for SIGPIPE */
124 #if !defined(_WIN32) && !defined(__WIN32__)
125     (void) signal(SIGPIPE, SIG_DFL);
126 #endif
127 
128     if (_filename) {
129         g_free(_filename);
130     }
131 
132     return;
133 }
134 
135 /** This should create the output LaTeX file, and assign it to _stream.
136  * @return Returns true when successful
137  */
138 bool
setTargetFile(gchar const * filename)139 LaTeXTextRenderer::setTargetFile(gchar const *filename) {
140     if (filename != nullptr) {
141         while (isspace(*filename)) filename += 1;
142 
143         _filename = g_path_get_basename(filename);
144 
145         gchar *filename_ext = g_strdup_printf("%s_tex", filename);
146         Inkscape::IO::dump_fopen_call(filename_ext, "K");
147         FILE *osf = Inkscape::IO::fopen_utf8name(filename_ext, "w+");
148         if (!osf) {
149             fprintf(stderr, "inkscape: fopen(%s): %s\n", filename_ext, strerror(errno));
150             g_free(filename_ext);
151             return false;
152         }
153         _stream = osf;
154         g_free(filename_ext);
155     }
156 
157     /* fixme: this is kinda icky */
158 #if !defined(_WIN32) && !defined(__WIN32__)
159     (void) signal(SIGPIPE, SIG_IGN);
160 #endif
161 
162     fprintf(_stream, "%%%% Creator: Inkscape %s, www.inkscape.org\n", Inkscape::version_string);
163     fprintf(_stream, "%%%% PDF/EPS/PS + LaTeX output extension by Johan Engelen, 2010\n");
164     fprintf(_stream, "%%%% Accompanies image file '%s' (pdf, eps, ps)\n", _filename);
165     fprintf(_stream, "%%%%\n");
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 = nullptr;
176         fflush(stdout);
177         return false;
178     }
179 
180     writePreamble();
181 
182     return true;
183 }
184 
185 static char const preamble[] =
186 "%% To include the image in your LaTeX document, write\n"
187 "%%   \\input{<filename>.pdf_tex}\n"
188 "%%  instead of\n"
189 "%%   \\includegraphics{<filename>.pdf}\n"
190 "%% To scale the image, write\n"
191 "%%   \\def\\svgwidth{<desired width>}\n"
192 "%%   \\input{<filename>.pdf_tex}\n"
193 "%%  instead of\n"
194 "%%   \\includegraphics[width=<desired width>]{<filename>.pdf}\n"
195 "%%\n"
196 "%% Images with a different path to the parent latex file can\n"
197 "%% be accessed with the `import' package (which may need to be\n"
198 "%% installed) using\n"
199 "%%   \\usepackage{import}\n"
200 "%% in the preamble, and then including the image with\n"
201 "%%   \\import{<path to file>}{<filename>.pdf_tex}\n"
202 "%% Alternatively, one can specify\n"
203 "%%   \\graphicspath{{<path to file>/}}\n"
204 "%% \n"
205 "%% For more information, please see info/svg-inkscape on CTAN:\n"
206 "%%   http://tug.ctan.org/tex-archive/info/svg-inkscape\n"
207 "%%\n"
208 "\\begingroup%\n"
209 "  \\makeatletter%\n"
210 "  \\providecommand\\color[2][]{%\n"
211 "    \\errmessage{(Inkscape) Color is used for the text in Inkscape, but the package \'color.sty\' is not loaded}%\n"
212 "    \\renewcommand\\color[2][]{}%\n"
213 "  }%\n"
214 "  \\providecommand\\transparent[1]{%\n"
215 "    \\errmessage{(Inkscape) Transparency is used (non-zero) for the text in Inkscape, but the package \'transparent.sty\' is not loaded}%\n"
216 "    \\renewcommand\\transparent[1]{}%\n"
217 "  }%\n"
218 "  \\providecommand\\rotatebox[2]{#2}%\n"
219 "  \\newcommand*\\fsize{\\dimexpr\\f@size pt\\relax}%\n"
220 "  \\newcommand*\\lineheight[1]{\\fontsize{\\fsize}{#1\\fsize}\\selectfont}%\n";
221 
222 static char const postamble[] =
223 "  \\end{picture}%\n"
224 "\\endgroup%\n";
225 
226 void
writePreamble()227 LaTeXTextRenderer::writePreamble()
228 {
229     fprintf(_stream, "%s", preamble);
230 }
231 void
writePostamble()232 LaTeXTextRenderer::writePostamble()
233 {
234     fprintf(_stream, "%s", postamble);
235 }
236 
sp_group_render(SPGroup * group)237 void LaTeXTextRenderer::sp_group_render(SPGroup *group)
238 {
239 	std::vector<SPObject*> l = (group->childList(false));
240     for(auto x : l){
241         SPItem *item = dynamic_cast<SPItem*>(x);
242         if (item) {
243             renderItem(item);
244         }
245     }
246 }
247 
sp_use_render(SPUse * use)248 void LaTeXTextRenderer::sp_use_render(SPUse *use)
249 {
250     bool translated = false;
251 
252     if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) {
253         Geom::Affine tp(Geom::Translate(use->x.computed, use->y.computed));
254         push_transform(tp);
255         translated = true;
256     }
257 
258     SPItem *childItem = dynamic_cast<SPItem *>(use->child);
259     if (childItem) {
260         renderItem(childItem);
261     }
262 
263     if (translated) {
264         pop_transform();
265     }
266 }
267 
sp_text_render(SPText * textobj)268 void LaTeXTextRenderer::sp_text_render(SPText *textobj)
269 {
270     // Nothing to do here... (so don't emit an empty box)
271     // Also avoids falling out of sync with the CairoRenderer (which won't render anything in this case either)
272     if (textobj->layout.getActualLength() == 0)
273         return;
274 
275     // Only PDFLaTeX supports importing a single page of a graphics file,
276     // so only PDF backend gets interleaved text/graphics
277     if (_pdflatex && _omittext_state ==  GRAPHIC_ON_TOP)
278         _omittext_state = NEW_PAGE_ON_GRAPHIC;
279 
280     SPStyle *style = textobj->style;
281 
282     // get position and alignment
283     // Align vertically on the baseline of the font (retrieved from the anchor point)
284     // Align horizontally on anchorpoint
285     gchar const *alignment = nullptr;
286     gchar const *aligntabular = nullptr;
287     switch (style->text_anchor.computed) {
288     case SP_CSS_TEXT_ANCHOR_START:
289         alignment = "[lt]";
290         aligntabular = "{l}";
291         break;
292     case SP_CSS_TEXT_ANCHOR_END:
293         alignment = "[rt]";
294         aligntabular = "{r}";
295         break;
296     case SP_CSS_TEXT_ANCHOR_MIDDLE:
297     default:
298         alignment = "[t]";
299         aligntabular = "{c}";
300         break;
301     }
302 
303     Geom::Point anchor;
304     const auto baseline_anchor_point = textobj->layout.baselineAnchorPoint();
305     if (baseline_anchor_point) {
306         anchor = (*baseline_anchor_point) * transform();
307     } else {
308         g_warning("LaTeXTextRenderer::sp_text_render: baselineAnchorPoint unset, text position will be wrong. Please report the issue.");
309     }
310 
311     // determine color and transparency (for now, use rgb color model as it is most native to Inkscape)
312     bool has_color = false; // if the item has no color set, don't force black color
313     bool has_transparency = false;
314     // TODO: how to handle ICC colors?
315     // give priority to fill color
316     guint32 rgba = 0;
317     float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
318     if (style->fill.set && style->fill.isColor()) {
319         has_color = true;
320         rgba = style->fill.value.color.toRGBA32(1.);
321         opacity *= SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
322     } else if (style->stroke.set && style->stroke.isColor()) {
323         has_color = true;
324         rgba = style->stroke.value.color.toRGBA32(1.);
325         opacity *= SP_SCALE24_TO_FLOAT(style->stroke_opacity.value);
326     }
327     if (opacity < 1.0) {
328         has_transparency = true;
329     }
330 
331     // get rotation
332     Geom::Affine i2doc = textobj->i2doc_affine();
333     Geom::Affine wotransl = i2doc.withoutTranslation();
334     double degrees = -180/M_PI * Geom::atan2(wotransl.xAxis());
335     bool has_rotation = !Geom::are_near(degrees,0.);
336 
337     // get line-height
338     float line_height;
339     if (style->line_height.unit == SP_CSS_UNIT_NONE) {
340         // unitless 'line-height' (use as-is, computed value is relative value)
341         line_height = style->line_height.computed;
342     } else {
343         // 'line-height' with unit (make relative, computed value is absolute value)
344         line_height = style->line_height.computed / style->font_size.computed;
345     }
346 
347     // write to LaTeX
348     Inkscape::SVGOStringStream os;
349     os.setf(std::ios::fixed); // don't use scientific notation
350 
351     os << "    \\put(" << anchor[Geom::X] << "," << anchor[Geom::Y] << "){";
352     if (has_color) {
353         os << "\\color[rgb]{" << SP_RGBA32_R_F(rgba) << "," << SP_RGBA32_G_F(rgba) << "," << SP_RGBA32_B_F(rgba) << "}";
354     }
355     if (_pdflatex && has_transparency) {
356         os << "\\transparent{" << opacity << "}";
357     }
358     if (has_rotation) {
359         os << "\\rotatebox{" << degrees << "}{";
360     }
361     os << "\\makebox(0,0)" << alignment << "{";
362     if (line_height != 1) {
363         os << "\\lineheight{" << line_height << "}";
364     }
365     os << "\\smash{";
366     os << "\\begin{tabular}[t]" << aligntabular;
367 
368         // Walk through all spans in the text object.
369         // Write span strings to LaTeX, associated with font weight and style.
370         Inkscape::Text::Layout const &layout = *(te_get_layout (textobj));
371         for (Inkscape::Text::Layout::iterator li = layout.begin(), le = layout.end();
372              li != le; li.nextStartOfSpan())
373         {
374             Inkscape::Text::Layout::iterator ln = li;
375             ln.nextStartOfSpan();
376             Glib::ustring uspanstr = sp_te_get_string_multiline (textobj, li, ln);
377 
378             // escape ampersands
379             uspanstr = Glib::Regex::create("&")->replace_literal(uspanstr, 0, "\\&", (Glib::RegexMatchFlags)0);
380             // escape percent
381             uspanstr = Glib::Regex::create("%")->replace_literal(uspanstr, 0, "\\%", (Glib::RegexMatchFlags)0);
382 
383             const gchar *spanstr = uspanstr.c_str();
384             if (!spanstr) {
385                 continue;
386             }
387 
388             bool is_bold = false, is_italic = false, is_oblique = false;
389 
390             // newline character only -> don't attempt to add style (will break compilation in LaTeX)
391             if (g_strcmp0(spanstr, "\n")) {
392                 SPStyle const &spanstyle = *(sp_te_style_at_position (textobj, li));
393                 if (spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_500 ||
394                     spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_600 ||
395                     spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_700 ||
396                     spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_800 ||
397                     spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_900 ||
398                     spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLD ||
399                     spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLDER)
400                 {
401                     is_bold = true;
402                     os << "\\textbf{";
403                 }
404                 if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_ITALIC)
405                 {
406                     is_italic = true;
407                     os << "\\textit{";
408                 }
409                 if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_OBLIQUE)
410                 {
411                     is_oblique = true;
412                     os << "\\textsl{";  // this is an accurate choice if the LaTeX chosen font matches the font in Inkscape. Gives bad results when it is not so...
413                 }
414             }
415 
416             // replace carriage return with double slash
417             gchar ** splitstr = g_strsplit(spanstr, "\n", 2);
418             os << splitstr[0];
419             if (g_strv_length(splitstr) > 1)
420             {
421                 os << "\\\\";
422             }
423             g_strfreev(splitstr);
424 
425             if (is_oblique) { os << "}"; } // oblique end
426             if (is_italic) { os << "}"; } // italic end
427             if (is_bold) { os << "}"; } // bold end
428         }
429 
430     os << "\\end{tabular}"; // tabular end
431     os << "}"; // smash end
432     if (has_rotation) { os << "}"; } // rotatebox end
433     os << "}"; //makebox end
434     os << "}%\n"; // put end
435 
436     fprintf(_stream, "%s", os.str().c_str());
437 }
438 
sp_flowtext_render(SPFlowtext * flowtext)439 void LaTeXTextRenderer::sp_flowtext_render(SPFlowtext *flowtext)
440 {
441 /*
442 Flowtext is possible by using a minipage! :)
443 Flowing in rectangle is possible, not in arb shape.
444 */
445 
446     // Only PDFLaTeX supports importing a single page of a graphics file,
447     // so only PDF backend gets interleaved text/graphics
448     if (_pdflatex && _omittext_state ==  GRAPHIC_ON_TOP)
449         _omittext_state = NEW_PAGE_ON_GRAPHIC;
450 
451     SPStyle *style = flowtext->style;
452 
453     SPItem *frame_item = flowtext->get_frame(nullptr);
454     SPRect *frame = dynamic_cast<SPRect *>(frame_item);
455     if (!frame_item || !frame) {
456         g_warning("LaTeX export: non-rectangular flowed text shapes are not supported, skipping text.");
457         return; // don't know how to handle non-rect frames yet. is quite uncommon for latex users i think
458     }
459 
460 	// We will transform the coordinates
461     Geom::Rect framebox = frame->getRect();
462 
463     // get position and alignment
464     // Align on topleft corner.
465     gchar const *alignment = "[lt]";
466     gchar const *justification = "";
467     switch (flowtext->layout.paragraphAlignment(flowtext->layout.begin())) {
468     case Inkscape::Text::Layout::LEFT:
469         justification = "\\raggedright ";
470         break;
471     case Inkscape::Text::Layout::RIGHT:
472         justification = "\\raggedleft ";
473         break;
474     case Inkscape::Text::Layout::CENTER:
475         justification = "\\centering ";
476     case Inkscape::Text::Layout::FULL:
477     default:
478         // no need to add LaTeX code for standard justified output :)
479         break;
480     }
481 
482 	// The topleft Corner was calculated after rotating the text which results in a wrong Coordinate.
483 	// Now, the topleft Corner is rotated after calculating it
484     Geom::Point pos(framebox.corner(0) * transform()); //topleft corner
485 
486     // determine color and transparency (for now, use rgb color model as it is most native to Inkscape)
487     bool has_color = false; // if the item has no color set, don't force black color
488     bool has_transparency = false;
489     // TODO: how to handle ICC colors?
490     // give priority to fill color
491     guint32 rgba = 0;
492     float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
493     if (style->fill.set && style->fill.isColor()) {
494         has_color = true;
495         rgba = style->fill.value.color.toRGBA32(1.);
496         opacity *= SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
497     } else if (style->stroke.set && style->stroke.isColor()) {
498         has_color = true;
499         rgba = style->stroke.value.color.toRGBA32(1.);
500         opacity *= SP_SCALE24_TO_FLOAT(style->stroke_opacity.value);
501     }
502     if (opacity < 1.0) {
503         has_transparency = true;
504     }
505 
506     // get rotation
507     Geom::Affine i2doc = flowtext->i2doc_affine();
508     Geom::Affine wotransl = i2doc.withoutTranslation();
509     double degrees = -180/M_PI * Geom::atan2(wotransl.xAxis());
510     bool has_rotation = !Geom::are_near(degrees,0.);
511 
512     // write to LaTeX
513     Inkscape::SVGOStringStream os;
514     os.setf(std::ios::fixed); // don't use scientific notation
515 
516     os << "    \\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){";
517     if (has_color) {
518         os << "\\color[rgb]{" << SP_RGBA32_R_F(rgba) << "," << SP_RGBA32_G_F(rgba) << "," << SP_RGBA32_B_F(rgba) << "}";
519     }
520     if (_pdflatex && has_transparency) {
521         os << "\\transparent{" << opacity << "}";
522     }
523     if (has_rotation) {
524         os << "\\rotatebox{" << degrees << "}{";
525     }
526     os << "\\makebox(0,0)" << alignment << "{";
527 
528 	// Scale the x width correctly
529     os << "\\begin{minipage}{" << framebox.width() * transform().expansionX() << "\\unitlength}";
530     os << justification;
531 
532         // Walk through all spans in the text object.
533         // Write span strings to LaTeX, associated with font weight and style.
534         Inkscape::Text::Layout const &layout = *(te_get_layout(flowtext));
535         for (Inkscape::Text::Layout::iterator li = layout.begin(), le = layout.end();
536              li != le; li.nextStartOfSpan())
537         {
538             SPStyle const &spanstyle = *(sp_te_style_at_position(flowtext, li));
539             bool is_bold = false, is_italic = false, is_oblique = false;
540 
541             if (spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_500 ||
542                 spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_600 ||
543                 spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_700 ||
544                 spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_800 ||
545                 spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_900 ||
546                 spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLD ||
547                 spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLDER)
548             {
549                 is_bold = true;
550                 os << "\\textbf{";
551             }
552             if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_ITALIC)
553             {
554                 is_italic = true;
555                 os << "\\textit{";
556             }
557             if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_OBLIQUE)
558             {
559                 is_oblique = true;
560                 os << "\\textsl{";  // this is an accurate choice if the LaTeX chosen font matches the font in Inkscape. Gives bad results when it is not so...
561             }
562 
563             Inkscape::Text::Layout::iterator ln = li;
564             ln.nextStartOfSpan();
565             Glib::ustring uspanstr = sp_te_get_string_multiline(flowtext, li, ln);
566             const gchar *spanstr = uspanstr.c_str();
567             if (!spanstr) {
568                 continue;
569             }
570             // replace carriage return with double slash
571             gchar ** splitstr = g_strsplit(spanstr, "\n", -1);
572             gchar *spanstr_new = g_strjoinv("\\\\ ", splitstr);
573             os << spanstr_new;
574             g_strfreev(splitstr);
575             g_free(spanstr_new);
576 
577             if (is_oblique) { os << "}"; } // oblique end
578             if (is_italic) { os << "}"; } // italic end
579             if (is_bold) { os << "}"; } // bold end
580         }
581 
582     os << "\\end{minipage}";
583     if (has_rotation) {
584         os << "}"; // rotatebox end
585     }
586     os << "}"; //makebox end
587     os << "}%\n"; // put end
588 
589     fprintf(_stream, "%s", os.str().c_str());
590 }
591 
sp_root_render(SPRoot * root)592 void LaTeXTextRenderer::sp_root_render(SPRoot *root)
593 {
594     push_transform(root->c2p);
595     sp_group_render(root);
596     pop_transform();
597 }
598 
599 void
sp_item_invoke_render(SPItem * item)600 LaTeXTextRenderer::sp_item_invoke_render(SPItem *item)
601 {
602     // Check item's visibility
603     if (item->isHidden()) {
604         return;
605     }
606 
607     SPRoot *root = dynamic_cast<SPRoot *>(item);
608     if (root) {
609         return sp_root_render(root);
610     }
611     SPGroup *group = dynamic_cast<SPGroup *>(item);
612     if (group) {
613         return sp_group_render(group);
614     }
615     SPUse *use = dynamic_cast<SPUse *>(item);
616     if (use) {
617         return sp_use_render(use);
618     }
619     SPText *text = dynamic_cast<SPText *>(item);
620     if (text) {
621         return sp_text_render(text);
622     }
623     SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(item);
624     if (flowtext) {
625         return sp_flowtext_render(flowtext);
626     }
627     // Only PDFLaTeX supports importing a single page of a graphics file,
628     // so only PDF backend gets interleaved text/graphics
629     if (_pdflatex && (_omittext_state == EMPTY || _omittext_state == NEW_PAGE_ON_GRAPHIC)) {
630         writeGraphicPage();
631     }
632     _omittext_state = GRAPHIC_ON_TOP;
633 }
634 
635 void
renderItem(SPItem * item)636 LaTeXTextRenderer::renderItem(SPItem *item)
637 {
638     push_transform(item->transform);
639     sp_item_invoke_render(item);
640     pop_transform();
641 }
642 
643 void
writeGraphicPage()644 LaTeXTextRenderer::writeGraphicPage() {
645     Inkscape::SVGOStringStream os;
646     os.setf(std::ios::fixed); // no scientific notation
647 
648     // strip pathname, as it is probably desired. Having a specific path in the TeX file is not convenient.
649     if (_pdflatex)
650         os << "    \\put(0,0){\\includegraphics[width=\\unitlength,page=" << _omittext_page++ << "]{" << _filename << "}}%\n";
651     else
652         os << "    \\put(0,0){\\includegraphics[width=\\unitlength]{" << _filename << "}}%\n";
653 
654     fprintf(_stream, "%s", os.str().c_str());
655 }
656 
657 bool
setupDocument(SPDocument * doc,bool pageBoundingBox,float bleedmargin_px,SPItem * base)658 LaTeXTextRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, float bleedmargin_px, SPItem *base)
659 {
660 // The boundingbox calculation here should be exactly the same as the one by CairoRenderer::setupDocument !
661 
662     if (!base) {
663         base = doc->getRoot();
664     }
665 
666     Geom::Rect d;
667     if (pageBoundingBox) {
668         d = Geom::Rect::from_xywh(Geom::Point(0,0), doc->getDimensions());
669     } else {
670         Geom::OptRect bbox = base->documentVisualBounds();
671         if (!bbox) {
672             g_message("CairoRenderer: empty bounding box.");
673             return false;
674         }
675         d = *bbox;
676     }
677     d.expandBy(bleedmargin_px);
678 
679     // scale all coordinates, such that the width of the image is 1, this is convenient for scaling the image in LaTeX
680     double scale = 1/(d.width());
681     double _width = d.width() * scale;
682     double _height = d.height() * scale;
683     push_transform(Geom::Translate(-d.corner(3)) * Geom::Scale(scale, -scale));
684 
685     // write the info to LaTeX
686     Inkscape::SVGOStringStream os;
687     os.setf(std::ios::fixed); // no scientific notation
688 
689     // scaling of the image when including it in LaTeX
690     os << "  \\ifx\\svgwidth\\undefined%\n";
691     os << "    \\setlength{\\unitlength}{" << Inkscape::Util::Quantity::convert(d.width(), "px", "pt") << "bp}%\n"; // note: 'bp' is the Postscript pt unit in LaTeX, see LP bug #792384
692     os << "    \\ifx\\svgscale\\undefined%\n";
693     os << "      \\relax%\n";
694     os << "    \\else%\n";
695     os << "      \\setlength{\\unitlength}{\\unitlength * \\real{\\svgscale}}%\n";
696     os << "    \\fi%\n";
697     os << "  \\else%\n";
698     os << "    \\setlength{\\unitlength}{\\svgwidth}%\n";
699     os << "  \\fi%\n";
700     os << "  \\global\\let\\svgwidth\\undefined%\n";
701     os << "  \\global\\let\\svgscale\\undefined%\n";
702     os << "  \\makeatother%\n";
703 
704     os << "  \\begin{picture}(" << _width << "," << _height << ")%\n";
705 
706     // set \baselineskip equal to fontsize (the closest we can seem to get to CSS "line-height: 1;")
707     // and remove column spacing from tabular
708     os << "    \\lineheight{1}%\n";
709     os << "    \\setlength\\tabcolsep{0pt}%\n";
710 
711     fprintf(_stream, "%s", os.str().c_str());
712 
713     if (!_pdflatex)
714         writeGraphicPage();
715 
716     return true;
717 }
718 
719 Geom::Affine const &
transform()720 LaTeXTextRenderer::transform()
721 {
722     return _transform_stack.top();
723 }
724 
725 void
push_transform(Geom::Affine const & tr)726 LaTeXTextRenderer::push_transform(Geom::Affine const &tr)
727 {
728     if(!_transform_stack.empty()){
729         Geom::Affine tr_top = _transform_stack.top();
730         _transform_stack.push(tr * tr_top);
731     } else {
732         _transform_stack.push(tr);
733     }
734 }
735 
736 void
pop_transform()737 LaTeXTextRenderer::pop_transform()
738 {
739     _transform_stack.pop();
740 }
741 
742 }  /* namespace Internal */
743 }  /* namespace Extension */
744 }  /* namespace Inkscape */
745 
746 /*
747   Local Variables:
748   mode:c++
749   c-file-style:"stroustrup"
750   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
751   indent-tabs-mode:nil
752   fill-column:99
753   End:
754 */
755 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
756