/****************************************************************************** * * Copyright (C) 1997-2019 by Dimitri van Heesch. * * Permission to use, copy, modify, and distribute this software and its * documentation under the terms of the GNU General Public License is hereby * granted. No representations are made about the suitability of this software * for any purpose. It is provided "as is" without express or implied warranty. * See the GNU General Public License for more details. * * Documents produced by Doxygen are derivative works derived from the * input used in their production; they are not affected by this license. * */ #include #include "dotfilepatcher.h" #include "dotrunner.h" #include "config.h" #include "message.h" #include "docparser.h" #include "doxygen.h" #include "util.h" #include "dot.h" #include "dir.h" static const char svgZoomHeader[] = "\n" "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" "\n" ; static QCString replaceRef(const QCString &buf,const QCString &relPath, bool urlOnly,const QCString &context,const QCString &target=QCString()) { // search for href="...", store ... part in link QCString href = "href"; //bool isXLink=FALSE; int len = 6; int indexS = buf.find("href=\""), indexE; bool targetAlreadySet = buf.find("target=")!=-1; if (indexS>5 && buf.find("xlink:href=\"")!=-1) // XLink href (for SVG) { indexS-=6; len+=6; href.prepend("xlink:"); //isXLink=TRUE; } if (indexS>=0 && (indexE=buf.find('"',indexS+len))!=-1) { QCString link = buf.mid(indexS+len,indexE-indexS-len); QCString result; if (urlOnly) // for user defined dot graphs { if (link.left(5)=="\\ref " || link.left(5)=="@ref ") // \ref url { result=href+"=\""; // fake ref node to resolve the url DocRef *df = new DocRef( (DocNode*) 0, link.mid(5), context ); result+=externalRef(relPath,df->ref(),TRUE); if (!df->file().isEmpty()) result += df->file() + Doxygen::htmlFileExtension; if (!df->anchor().isEmpty()) result += "#" + df->anchor(); delete df; result += "\""; } else { result = href+"=\"" + link + "\""; } } else // ref$url (external ref via tag file), or $url (local ref) { int marker = link.find('$'); if (marker!=-1) { QCString ref = link.left(marker); QCString url = link.mid(marker+1); if (!ref.isEmpty()) { result = externalLinkTarget(true); if (!result.isEmpty())targetAlreadySet=true; } result+= href+"=\""; result+=externalRef(relPath,ref,TRUE); result+= url + "\""; } else // should not happen, but handle properly anyway { result = href+"=\"" + link + "\""; } } if (!target.isEmpty() && !targetAlreadySet) { result+=" target=\""+target+"\""; } QCString leftPart = buf.left(indexS); QCString rightPart = buf.mid(indexE+1); //printf("replaceRef(\n'%s'\n)->\n'%s+%s+%s'\n", // qPrint(buf),qPrint(leftPart),qPrint(result),qPrint(rightPart)); return leftPart + result + rightPart; } else { return buf; } } /*! converts the rectangles in a client site image map into a stream * \param t the stream to which the result is written. * \param mapName the name of the map file. * \param relPath the relative path to the root of the output directory * (used in case CREATE_SUBDIRS is enabled). * \param urlOnly if FALSE the url field in the map contains an external * references followed by a $ and then the URL. * \param context the context (file, class, or namespace) in which the * map file was found * \returns TRUE if successful. */ bool DotFilePatcher::convertMapFile(TextStream &t,const QCString &mapName, const QCString &relPath, bool urlOnly, const QCString &context) { std::ifstream f(mapName.str(),std::ifstream::in); if (!f.is_open()) { err("problems opening map file %s for inclusion in the docs!\n" "If you installed Graphviz/dot after a previous failing run, \n" "try deleting the output directory and rerun doxygen.\n",qPrint(mapName)); return FALSE; } std::string line; while (getline(f,line)) // foreach line { QCString buf = line+'\n'; if (buf.left(5)=="0 && (indexE=replBuf.find('"',indexS+4))!=-1) { t << replBuf.left(indexS-1) << replBuf.right(replBuf.length() - indexE - 1); } else { t << replBuf; } } } return TRUE; } DotFilePatcher::DotFilePatcher(const QCString &patchFile) : m_patchFile(patchFile) { } bool DotFilePatcher::isSVGFile() const { return m_patchFile.right(4)==".svg"; } int DotFilePatcher::addMap(const QCString &mapFile,const QCString &relPath, bool urlOnly,const QCString &context,const QCString &label) { int id = (int)m_maps.size(); m_maps.emplace_back(mapFile,relPath,urlOnly,context,label); return id; } int DotFilePatcher::addFigure(const QCString &baseName, const QCString &figureName,bool heightCheck) { int id = (int)m_maps.size(); m_maps.emplace_back(figureName,"",heightCheck,"",baseName); return id; } int DotFilePatcher::addSVGConversion(const QCString &relPath,bool urlOnly, const QCString &context,bool zoomable, int graphId) { int id = (int)m_maps.size(); m_maps.emplace_back("",relPath,urlOnly,context,"",zoomable,graphId); return id; } int DotFilePatcher::addSVGObject(const QCString &baseName, const QCString &absImgName, const QCString &relPath) { int id = (int)m_maps.size(); m_maps.emplace_back(absImgName,relPath,false,"",baseName); return id; } bool DotFilePatcher::run() const { //printf("DotFilePatcher::run(): %s\n",qPrint(m_patchFile)); bool interactiveSVG_local = Config_getBool(INTERACTIVE_SVG); bool isSVGFile = m_patchFile.right(4)==".svg"; int graphId = -1; QCString relPath; if (isSVGFile) { const Map &map = m_maps.front(); // there is only one 'map' for a SVG file interactiveSVG_local = interactiveSVG_local && map.zoomable; graphId = map.graphId; relPath = map.relPath; //printf("DotFilePatcher::addSVGConversion: file=%s zoomable=%d\n", // qPrint(m_patchFile),map->zoomable); } std::string tmpName = m_patchFile.str()+".tmp"; std::string patchFile = m_patchFile.str(); Dir thisDir; if (!thisDir.rename(patchFile,tmpName)) { err("Failed to rename file %s to %s!\n",qPrint(m_patchFile),tmpName.c_str()); return FALSE; } std::ifstream fi(tmpName, std::ifstream::in); std::ofstream fo(patchFile, std::ofstream::out | std::ofstream::binary); if (!fi.is_open()) { err("problem opening file %s for patching!\n",tmpName.c_str()); thisDir.rename(tmpName,patchFile); return FALSE; } if (!fo.is_open()) { err("problem opening file %s for patching!\n",qPrint(m_patchFile)); thisDir.rename(tmpName,patchFile); return FALSE; } TextStream t(&fo); int width,height; bool insideHeader=FALSE; bool replacedHeader=FALSE; bool foundSize=FALSE; int lineNr=1; std::string lineStr; while (getline(fi,lineStr)) { QCString line = lineStr+'\n'; //printf("line=[%s]\n",qPrint(line.stripWhiteSpace())); int i; if (isSVGFile) { if (interactiveSVG_local) { if (line.find("500 || height>450); if (foundSize) insideHeader=TRUE; } else if (insideHeader && !replacedHeader && line.find("")!=-1) { if (foundSize) { // insert special replacement header for interactive SVGs t << "<!--zoomable " << height << " -->\n"; t << svgZoomHeader; t << "var viewWidth = " << width << ";\n"; t << "var viewHeight = " << height << ";\n"; if (graphId>=0) { t << "var sectionId = 'dynsection-" << graphId << "';\n"; } t << "</script>\n"; t << "<script xlink:href=\"" << relPath << "svgpan.js\"/>\n"; t << "<svg id=\"graph\" class=\"graph\">\n"; t << "<g id=\"viewport\">\n"; } insideHeader=FALSE; replacedHeader=TRUE; } } if (!insideHeader || !foundSize) // copy SVG and replace refs, // unless we are inside the header of the SVG. // Then we replace it with another header. { const Map &map = m_maps.front(); // there is only one 'map' for a SVG file t << replaceRef(line,map.relPath,map.urlOnly,map.context,"_top"); } } else if ((i=line.find("<!-- SVG"))!=-1 || (i=line.find("[!-- SVG"))!=-1) { //printf("Found marker at %d\n",i); int mapId=-1; t << line.left(i); int n = sscanf(line.data()+i+1,"!-- SVG %d",&mapId); if (n==1 && mapId>=0 && mapId<(int)m_maps.size()) { int e = std::max(line.find("--]"),line.find("-->")); const Map &map = m_maps.at(mapId); //printf("DotFilePatcher::writeSVGFigure: file=%s zoomable=%d\n", // qPrint(m_patchFile),map.zoomable); if (!writeSVGFigureLink(t,map.relPath,map.label,map.mapFile)) { err("Problem extracting size from SVG file %s\n",qPrint(map.mapFile)); } if (e!=-1) t << line.mid(e+3); } else // error invalid map id! { err("Found invalid SVG id in file %s!\n",qPrint(m_patchFile)); t << line.mid(i); } } else if ((i=line.find("<!-- MAP"))!=-1) { int mapId=-1; t << line.left(i); int n = sscanf(line.data()+i,"<!-- MAP %d",&mapId); if (n==1 && mapId>=0 && mapId<(int)m_maps.size()) { TextStream tt; const Map &map = m_maps.at(mapId); //printf("patching MAP %d in file %s with contents of %s\n", // mapId,qPrint(m_patchFile),qPrint(map.mapFile)); convertMapFile(tt,map.mapFile,map.relPath,map.urlOnly,map.context); if (!tt.empty()) { t << "<map name=\"" << correctId(map.label) << "\" id=\"" << correctId(map.label) << "\">\n"; t << tt.str(); t << "</map>\n"; } } else // error invalid map id! { err("Found invalid MAP id in file %s!\n",qPrint(m_patchFile)); t << line.mid(i); } } else if ((i=line.find("% FIG"))!=-1) { int mapId=-1; int n = sscanf(line.data()+i+2,"FIG %d",&mapId); //printf("line='%s' n=%d\n",qPrint(line)+i,n); if (n==1 && mapId>=0 && mapId<(int)m_maps.size()) { const Map &map = m_maps.at(mapId); //printf("patching FIG %d in file %s with contents of %s\n", // mapId,qPrint(m_patchFile),qPrint(map.mapFile)); if (!writeVecGfxFigure(t,map.label,map.mapFile)) { err("problem writing FIG %d figure!\n",mapId); return FALSE; } } else // error invalid map id! { err("Found invalid bounding FIG %d in file %s!\n",mapId,qPrint(m_patchFile)); t << line; } } else { t << line; } lineNr++; } fi.close(); if (isSVGFile && interactiveSVG_local && replacedHeader) { QCString orgName=m_patchFile.left(m_patchFile.length()-4)+"_org.svg"; t << substitute(svgZoomFooter,"$orgname",stripPath(orgName)); t.flush(); fo.close(); // keep original SVG file so we can refer to it, we do need to replace // dummy link by real ones fi.open(tmpName,std::ifstream::in); fo.open(orgName.str(),std::ofstream::out | std::ofstream::binary); if (!fi.is_open()) { err("problem opening file %s for reading!\n",tmpName.c_str()); return FALSE; } if (!fo.is_open()) { err("problem opening file %s for writing!\n",qPrint(orgName)); return FALSE; } t.setStream(&fo); while (getline(fi,lineStr)) // foreach line { std::string line = lineStr+'\n'; const Map &map = m_maps.front(); // there is only one 'map' for a SVG file t << replaceRef(line.c_str(),map.relPath,map.urlOnly,map.context,"_top"); } t.flush(); fi.close(); fo.close(); } // remove temporary file thisDir.remove(tmpName); return TRUE; } //--------------------------------------------------------------------------------------------- // extract size from a dot generated SVG file static bool readSVGSize(const QCString &fileName,int *width,int *height) { bool found=FALSE; std::ifstream f(fileName.str(),std::ifstream::in); if (!f.is_open()) { return false; } std::string line; while (getline(f,line) && !found) { if (qstrncmp(line.c_str(),"<!--zoomable ",13)==0) { *width=-1; *height=-1; sscanf(line.c_str(),"<!--zoomable %d",height); found=true; } else if (sscanf(line.c_str(),"<svg width=\"%dpt\" height=\"%dpt\"",width,height)==2) { found=true; } } return true; } static void writeSVGNotSupported(TextStream &out) { out << "<p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p>"; } /// Check if a reference to a SVG figure can be written and do so if possible. /// Returns FALSE if not possible (for instance because the SVG file is not yet generated). bool DotFilePatcher::writeSVGFigureLink(TextStream &out,const QCString &relPath, const QCString &baseName,const QCString &absImgName) { int width=600,height=600; if (!readSVGSize(absImgName,&width,&height)) { return FALSE; } if (width==-1) { if (height<=60) height=300; else height+=300; // add some extra space for zooming if (height>600) height=600; // clip to maximum height of 600 pixels out << "<div class=\"zoom\">"; //out << "<object type=\"image/svg+xml\" data=\"" //out << "<embed type=\"image/svg+xml\" src=\"" out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\"" << relPath << baseName << ".svg\" width=\"100%\" height=\"" << height << "\">"; } else { //out << "<object type=\"image/svg+xml\" data=\"" //out << "<embed type=\"image/svg+xml\" src=\"" out << "<iframe scrolling=\"no\" frameborder=\"0\" src=\"" << relPath << baseName << ".svg\" width=\"" << ((width*96+48)/72) << "\" height=\"" << ((height*96+48)/72) << "\">"; } writeSVGNotSupported(out); //out << "</object>"; //out << "</embed>"; out << "</iframe>"; if (width==-1) { out << "</div>"; } return TRUE; } bool DotFilePatcher::writeVecGfxFigure(TextStream &out,const QCString &baseName, const QCString &figureName) { int width=400,height=550; if (Config_getBool(USE_PDFLATEX)) { if (!DotRunner::readBoundingBox(figureName+".pdf",&width,&height,FALSE)) { //printf("writeVecGfxFigure()=0\n"); return FALSE; } } else { if (!DotRunner::readBoundingBox(figureName+".eps",&width,&height,TRUE)) { //printf("writeVecGfxFigure()=0\n"); return FALSE; } } //printf("Got PDF/EPS size %d,%d\n",width,height); int maxWidth = 350; /* approx. page width in points, excl. margins */ int maxHeight = 550; /* approx. page height in points, excl. margins */ out << "\\nopagebreak\n" "\\begin{figure}[H]\n" "\\begin{center}\n" "\\leavevmode\n"; if (width>maxWidth || height>maxHeight) // figure too big for page { // c*width/maxWidth > c*height/maxHeight, where c=maxWidth*maxHeight>0 if (width*maxHeight>height*maxWidth) { out << "\\includegraphics[width=" << maxWidth << "pt]"; } else { out << "\\includegraphics[height=" << maxHeight << "pt]"; } } else { out << "\\includegraphics[width=" << width << "pt]"; } out << "{" << baseName << "}\n" "\\end{center}\n" "\\end{figure}\n"; //printf("writeVecGfxFigure()=1\n"); return TRUE; }