1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * A quick hack to use the Cairo renderer to write out a file. This
4 * then makes 'save as...' PDF.
5 *
6 * Authors:
7 * Ted Gould <ted@gould.cx>
8 * Ulf Erikson <ulferikson@users.sf.net>
9 * Johan Engelen <goejendaagh@zonnet.nl>
10 * Jon A. Cruz <jon@joncruz.org>
11 * Abhishek Sharma
12 *
13 * Copyright (C) 2004-2010 Authors
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18 #include <cairo.h>
19 #ifdef CAIRO_HAS_PDF_SURFACE
20
21 #include "cairo-renderer-pdf-out.h"
22 #include "cairo-render-context.h"
23 #include "cairo-renderer.h"
24 #include "latex-text-renderer.h"
25 #include <print.h>
26 #include "extension/system.h"
27 #include "extension/print.h"
28 #include "extension/db.h"
29 #include "extension/output.h"
30
31 #include "display/drawing.h"
32 #include "display/curve.h"
33
34 #include "object/sp-item.h"
35 #include "object/sp-root.h"
36
37 #include <2geom/affine.h>
38 #include "document.h"
39
40 #include "util/units.h"
41
42 namespace Inkscape {
43 namespace Extension {
44 namespace Internal {
45
check(Inkscape::Extension::Extension *)46 bool CairoRendererPdfOutput::check(Inkscape::Extension::Extension * /*module*/)
47 {
48 bool result = true;
49
50 if (nullptr == Inkscape::Extension::db.get("org.inkscape.output.pdf.cairorenderer")) {
51 result = false;
52 }
53
54 return result;
55 }
56
57 static bool
pdf_render_document_to_file(SPDocument * doc,gchar const * filename,unsigned int level,bool texttopath,bool omittext,bool filtertobitmap,int resolution,const gchar * const exportId,bool exportDrawing,bool exportCanvas,float bleedmargin_px)58 pdf_render_document_to_file(SPDocument *doc, gchar const *filename, unsigned int level,
59 bool texttopath, bool omittext, bool filtertobitmap, int resolution,
60 const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px)
61 {
62 doc->ensureUpToDate();
63
64 SPRoot *root = doc->getRoot();
65 SPItem *base = nullptr;
66
67 bool pageBoundingBox = TRUE;
68 if (exportId && strcmp(exportId, "")) {
69 // we want to export the given item only
70 base = SP_ITEM(doc->getObjectById(exportId));
71 if (!base) {
72 throw Inkscape::Extension::Output::export_id_not_found(exportId);
73 }
74 root->cropToObject(base); // TODO: This is inconsistent in CLI (should only happen for --export-id-only)
75 pageBoundingBox = exportCanvas;
76 }
77 else {
78 // we want to export the entire document from root
79 base = root;
80 pageBoundingBox = !exportDrawing;
81 }
82
83 if (!base) {
84 return false;
85 }
86
87 /* Create new arena */
88 Inkscape::Drawing drawing;
89 drawing.setExact(true);
90 unsigned dkey = SPItem::display_key_new(1);
91 root->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY);
92
93 /* Create renderer and context */
94 CairoRenderer *renderer = new CairoRenderer();
95 CairoRenderContext *ctx = renderer->createContext();
96 ctx->setPDFLevel(level);
97 ctx->setTextToPath(texttopath);
98 ctx->setOmitText(omittext);
99 ctx->setFilterToBitmap(filtertobitmap);
100 ctx->setBitmapResolution(resolution);
101
102 bool ret = ctx->setPdfTarget (filename);
103 if(ret) {
104 /* Render document */
105 ret = renderer->setupDocument(ctx, doc, pageBoundingBox, bleedmargin_px, base);
106 if (ret) {
107 renderer->renderItem(ctx, root);
108 ret = ctx->finish();
109 }
110 }
111
112 root->invoke_hide(dkey);
113
114 renderer->destroyContext(ctx);
115 delete renderer;
116
117 return ret;
118 }
119
120 /**
121 \brief This function calls the output module with the filename
122 \param mod unused
123 \param doc Document to be saved
124 \param filename Filename to save to (probably will end in .pdf)
125
126 The most interesting thing that this function does is just attach
127 an '>' on the front of the filename. This is the syntax used to
128 tell the printing system to save to file.
129 */
130 void
save(Inkscape::Extension::Output * mod,SPDocument * doc,gchar const * filename)131 CairoRendererPdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename)
132 {
133 Inkscape::Extension::Extension * ext;
134 unsigned int ret;
135
136 ext = Inkscape::Extension::db.get("org.inkscape.output.pdf.cairorenderer");
137 if (ext == nullptr)
138 return;
139
140 int level = 0;
141 try {
142 const gchar *new_level = mod->get_param_optiongroup("PDFversion");
143 if((new_level != nullptr) && (g_ascii_strcasecmp("PDF-1.5", new_level) == 0)) {
144 level = 1;
145 }
146 }
147 catch(...) {
148 g_warning("Parameter <PDFversion> might not exist");
149 }
150
151 bool new_textToPath = FALSE;
152 try {
153 new_textToPath = (strcmp(mod->get_param_optiongroup("textToPath"), "paths") == 0);
154 }
155 catch(...) {
156 g_warning("Parameter <textToPath> might not exist");
157 }
158
159 bool new_textToLaTeX = FALSE;
160 try {
161 new_textToLaTeX = (strcmp(mod->get_param_optiongroup("textToPath"), "LaTeX") == 0);
162 }
163 catch(...) {
164 g_warning("Parameter <textToLaTeX> might not exist");
165 }
166
167 bool new_blurToBitmap = FALSE;
168 try {
169 new_blurToBitmap = mod->get_param_bool("blurToBitmap");
170 }
171 catch(...) {
172 g_warning("Parameter <blurToBitmap> might not exist");
173 }
174
175 int new_bitmapResolution = 72;
176 try {
177 new_bitmapResolution = mod->get_param_int("resolution");
178 }
179 catch(...) {
180 g_warning("Parameter <resolution> might not exist");
181 }
182
183 const gchar *new_exportId = nullptr;
184 try {
185 new_exportId = mod->get_param_string("exportId");
186 }
187 catch(...) {
188 g_warning("Parameter <exportId> might not exist");
189 }
190
191 bool new_exportCanvas = true;
192 try {
193 new_exportCanvas = (strcmp(ext->get_param_optiongroup("area"), "page") == 0);
194 } catch(...) {
195 g_warning("Parameter <area> might not exist");
196 }
197 bool new_exportDrawing = !new_exportCanvas;
198
199 float new_bleedmargin_px = 0.;
200 try {
201 new_bleedmargin_px = Inkscape::Util::Quantity::convert(mod->get_param_float("bleed"), "mm", "px");
202 }
203 catch(...) {
204 g_warning("Parameter <bleed> might not exist");
205 }
206
207 // Create PDF file
208 {
209 gchar * final_name;
210 final_name = g_strdup_printf("> %s", filename);
211 ret = pdf_render_document_to_file(doc, final_name, level,
212 new_textToPath, new_textToLaTeX, new_blurToBitmap, new_bitmapResolution,
213 new_exportId, new_exportDrawing, new_exportCanvas, new_bleedmargin_px);
214 g_free(final_name);
215
216 if (!ret)
217 throw Inkscape::Extension::Output::save_failed();
218 }
219
220 // Create LaTeX file (if requested)
221 if (new_textToLaTeX) {
222 ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_exportDrawing, new_exportCanvas, new_bleedmargin_px, true);
223
224 if (!ret)
225 throw Inkscape::Extension::Output::save_failed();
226 }
227 }
228
229 #include "clear-n_.h"
230
231 /**
232 \brief A function allocate a copy of this function.
233
234 This is the definition of Cairo PDF out. This function just
235 calls the extension system with the memory allocated XML that
236 describes the data.
237 */
238 void
init()239 CairoRendererPdfOutput::init ()
240 {
241 // clang-format off
242 Inkscape::Extension::build_from_mem(
243 "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
244 "<name>Portable Document Format</name>\n"
245 "<id>org.inkscape.output.pdf.cairorenderer</id>\n"
246 "<param name=\"PDFversion\" gui-text=\"" N_("Restrict to PDF version:") "\" type=\"optiongroup\" appearance=\"combo\" >\n"
247 "<option value='PDF-1.5'>" N_("PDF 1.5") "</option>\n"
248 "<option value='PDF-1.4'>" N_("PDF 1.4") "</option>\n"
249 "</param>\n"
250 "<param name=\"textToPath\" gui-text=\"" N_("Text output options:") "\" type=\"optiongroup\" appearance=\"radio\">\n"
251 "<option value=\"embed\">" N_("Embed fonts") "</option>\n"
252 "<option value=\"paths\">" N_("Convert text to paths") "</option>\n"
253 "<option value=\"LaTeX\">" N_("Omit text in PDF and create LaTeX file") "</option>\n"
254 "</param>\n"
255 "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"bool\">true</param>\n"
256 "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">96</param>\n"
257 "<param name=\"area\" gui-text=\"" N_("Output page size:") "\" type=\"optiongroup\" appearance=\"radio\" >\n"
258 "<option value=\"page\">" N_("Use document's page size") "</option>"
259 "<option value=\"drawing\">" N_("Use exported object's size") "</option>"
260 "</param>"
261 "<param name=\"bleed\" gui-text=\"" N_("Bleed/margin (mm):") "\" type=\"float\" min=\"-10000\" max=\"10000\">0</param>\n"
262 "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID:") "\" type=\"string\"></param>\n"
263 "<output>\n"
264 "<extension>.pdf</extension>\n"
265 "<mimetype>application/pdf</mimetype>\n"
266 "<filetypename>Portable Document Format (*.pdf)</filetypename>\n"
267 "<filetypetooltip>PDF File</filetypetooltip>\n"
268 "</output>\n"
269 "</inkscape-extension>", new CairoRendererPdfOutput());
270 // clang-format on
271
272 return;
273 }
274
275 } } } /* namespace Inkscape, Extension, Internal */
276
277 #endif /* HAVE_CAIRO_PDF */
278