// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * Implementation of the file dialog interfaces defined in filedialogimpl.h. */ /* Authors: * Bob Jamison * Joel Holdsworth * Bruno Dilly * Other dudes from The Inkscape Organization * Abhishek Sharma * * Copyright (C) 2004-2007 Bob Jamison * Copyright (C) 2006 Johan Engelen * Copyright (C) 2007-2008 Joel Holdsworth * Copyright (C) 2004-2007 The Inkscape Organization * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include #include #include // GStatBuf #include "svg-preview.h" #include "document.h" #include "ui/view/svg-view-widget.h" namespace Inkscape { namespace UI { namespace Dialog { /*######################################################################### ### SVG Preview Widget #########################################################################*/ bool SVGPreview::setDocument(SPDocument *doc) { if (viewer) { viewer->setDocument(doc); } else { viewer = Gtk::manage(new Inkscape::UI::View::SVGViewWidget(doc)); pack_start(*viewer, true, true); } if (document) { delete document; } document = doc; show_all(); return true; } bool SVGPreview::setFileName(Glib::ustring &theFileName) { Glib::ustring fileName = theFileName; fileName = Glib::filename_to_utf8(fileName); /** * I don't know why passing false to keepalive is bad. But it * prevents the display of an svg with a non-ascii filename */ SPDocument *doc = SPDocument::createNewDoc(fileName.c_str(), true); if (!doc) { g_warning("SVGView: error loading document '%s'\n", fileName.c_str()); return false; } setDocument(doc); return true; } bool SVGPreview::setFromMem(char const *xmlBuffer) { if (!xmlBuffer) return false; gint len = (gint)strlen(xmlBuffer); SPDocument *doc = SPDocument::createNewDocFromMem(xmlBuffer, len, false); if (!doc) { g_warning("SVGView: error loading buffer '%s'\n", xmlBuffer); return false; } setDocument(doc); return true; } void SVGPreview::showImage(Glib::ustring &theFileName) { Glib::ustring fileName = theFileName; // Let's get real width and height from SVG file. These are template // files so we assume they are well formed. // std::cout << "SVGPreview::showImage: " << theFileName << std::endl; std::string width; std::string height; /*##################################### # LET'S HAVE SOME FUN WITH SVG! # Instead of just loading an image, why # don't we make a lovely little svg and # display it nicely? #####################################*/ // Arbitrary size of svg doc -- rather 'portrait' shaped gint previewWidth = 400; gint previewHeight = 600; // Get some image info. Smart pointer does not need to be deleted Glib::RefPtr img(nullptr); try { img = Gdk::Pixbuf::create_from_file(fileName); } catch (const Glib::FileError &e) { g_message("caught Glib::FileError in SVGPreview::showImage"); return; } catch (const Gdk::PixbufError &e) { g_message("Gdk::PixbufError in SVGPreview::showImage"); return; } catch (...) { g_message("Caught ... in SVGPreview::showImage"); return; } gint imgWidth = img->get_width(); gint imgHeight = img->get_height(); Glib::ustring svg = ".svg"; if (hasSuffix(fileName, svg)) { std::ifstream input(theFileName.c_str()); if( !input ) { std::cerr << "SVGPreview::showImage: Failed to open file: " << theFileName << std::endl; } else { Glib::ustring token; Glib::MatchInfo match_info; Glib::RefPtr regex1 = Glib::Regex::create("width=\"(.*)\""); Glib::RefPtr regex2 = Glib::Regex::create("height=\"(.*)\""); while( !input.eof() && (height.empty() || width.empty()) ) { input >> token; // std::cout << "|" << token << "|" << std::endl; if (regex1->match(token, match_info)) { width = match_info.fetch(1).raw(); } if (regex2->match(token, match_info)) { height = match_info.fetch(1).raw(); } } } } // TODO: replace int to string conversion with std::to_string when fully C++11 compliant if (height.empty() || width.empty()) { std::ostringstream s_width; std::ostringstream s_height; s_width << imgWidth; s_height << imgHeight; width = s_width.str(); height = s_height.str(); } // Find the minimum scale to fit the image inside the preview area double scaleFactorX = (0.9 * (double)previewWidth) / ((double)imgWidth); double scaleFactorY = (0.9 * (double)previewHeight) / ((double)imgHeight); double scaleFactor = scaleFactorX; if (scaleFactorX > scaleFactorY) scaleFactor = scaleFactorY; // Now get the resized values gint scaledImgWidth = (int)(scaleFactor * (double)imgWidth); gint scaledImgHeight = (int)(scaleFactor * (double)imgHeight); // center the image on the area gint imgX = (previewWidth - scaledImgWidth) / 2; gint imgY = (previewHeight - scaledImgHeight) / 2; // wrap a rectangle around the image gint rectX = imgX - 1; gint rectY = imgY - 1; gint rectWidth = scaledImgWidth + 2; gint rectHeight = scaledImgHeight + 2; // Our template. Modify to taste gchar const *xformat = R"A( %s x %s )A"; // if (!Glib::get_charset()) //If we are not utf8 fileName = Glib::filename_to_utf8(fileName); // Filenames in xlinks are decoded, so any % will break without this. auto encodedName = Glib::uri_escape_string(fileName); // Fill in the template /* FIXME: Do proper XML quoting for fileName. */ gchar *xmlBuffer = g_strdup_printf(xformat, previewWidth, previewHeight, imgX, imgY, scaledImgWidth, scaledImgHeight, encodedName.c_str(), rectX, rectY, rectWidth, rectHeight, width.c_str(), height.c_str() ); // g_message("%s\n", xmlBuffer); // now show it! setFromMem(xmlBuffer); g_free(xmlBuffer); } void SVGPreview::showNoPreview() { // Are we already showing it? if (showingNoPreview) return; // Our template. Modify to taste gchar const *xformat = R"B( %s )B"; // Fill in the template gchar *xmlBuffer = g_strdup_printf(xformat, _("No preview")); // g_message("%s\n", xmlBuffer); // Now show it! setFromMem(xmlBuffer); g_free(xmlBuffer); showingNoPreview = true; } /** * Inform the user that the svg file is too large to be displayed. * This does not check for sizes of embedded images (yet) */ void SVGPreview::showTooLarge(long fileLength) { // Our template. Modify to taste gchar const *xformat = R"C( %.1f MB %s )C"; // Fill in the template double floatFileLength = ((double)fileLength) / 1048576.0; // printf("%ld %f\n", fileLength, floatFileLength); gchar *xmlBuffer = g_strdup_printf(xformat, floatFileLength, _("Too large for preview")); // g_message("%s\n", xmlBuffer); // now show it! setFromMem(xmlBuffer); g_free(xmlBuffer); } bool SVGPreview::set(Glib::ustring &fileName, int dialogType) { if (!Glib::file_test(fileName, Glib::FILE_TEST_EXISTS)) { showNoPreview(); return false; } if (Glib::file_test(fileName, Glib::FILE_TEST_IS_DIR)) { showNoPreview(); return false; } if (Glib::file_test(fileName, Glib::FILE_TEST_IS_REGULAR)) { Glib::ustring fileNameUtf8 = Glib::filename_to_utf8(fileName); gchar *fName = const_cast( fileNameUtf8.c_str()); // const-cast probably not necessary? (not necessary on Windows version of stat()) GStatBuf info; if (g_stat(fName, &info)) // stat returns 0 upon success { g_warning("SVGPreview::set() : %s : %s", fName, strerror(errno)); return false; } if (info.st_size > 0xA00000L) { showingNoPreview = false; showTooLarge(info.st_size); return false; } } Glib::ustring svg = ".svg"; Glib::ustring svgz = ".svgz"; if ((dialogType == SVG_TYPES || dialogType == IMPORT_TYPES) && (hasSuffix(fileName, svg) || hasSuffix(fileName, svgz))) { bool retval = setFileName(fileName); showingNoPreview = false; return retval; } else if (isValidImageFile(fileName)) { showImage(fileName); showingNoPreview = false; return true; } else { showNoPreview(); return false; } } SVGPreview::SVGPreview() : Gtk::Box(Gtk::ORIENTATION_VERTICAL) , document(nullptr) , viewer(nullptr) , showingNoPreview(false) { set_size_request(200, 300); } SVGPreview::~SVGPreview() { if (viewer) { viewer->setDocument(nullptr); } delete document; } } // namespace Dialog } // namespace UI } // namespace Inkscape /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :