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