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