/***** * picture.cc * Andy Hammerlindl 2002/06/06 * * Stores a picture as a list of drawElements and handles its output to * PostScript. *****/ #include "errormsg.h" #include "picture.h" #include "util.h" #include "settings.h" #include "interact.h" #include "drawverbatim.h" #include "drawlabel.h" #include "drawlayer.h" #include "drawsurface.h" #include "drawpath3.h" #include "win32helpers.h" #if defined(_WIN32) #include #include #include #define unlink _unlink #endif #include #include using std::ifstream; using std::ofstream; using vm::array; using namespace settings; using namespace gl; texstream::~texstream() { string texengine=getSetting("tex"); bool context=settings::context(texengine); string name; if(!context) name=stripFile(outname()); name += "texput."; unlink((name+"aux").c_str()); unlink((name+"log").c_str()); unlink((name+"out").c_str()); if(settings::pdf(texengine)) { unlink((name+"pdf").c_str()); unlink((name+"m9").c_str()); } else unlink((name+"pbsdat").c_str()); if(context) { unlink("cont-new.log"); unlink((name+"tex").c_str()); unlink((name+"top").c_str()); unlink((name+"tua").c_str()); unlink((name+"tui").c_str()); } } namespace camp { extern void draw(); bool isIdTransform3(const double* t) { return (t == NULL || (t[0]==1 && t[1]==0 && t[2]==0 && t[3]==0 && t[4]==0 && t[5]==1 && t[6]==0 && t[7]==0 && t[8]==0 && t[9]==0 && t[10]==1 && t[11]==0 && t[12]==0 && t[13]==0 && t[14]==0 && t[15]==1)); } // copy array to 4x4 transform matrix with range checks void copyArray4x4C(double*& dest, const vm::array *a) { double tt[16]; const size_t n=checkArray(a); const string fourbyfour="4x4 array of doubles expected"; if(n != 4) reportError(fourbyfour); for(size_t i=0; i < 4; i++) { const vm::array *ai=vm::read(a,i); const size_t aisize=checkArray(ai); double *tti=tt+4*i; if(aisize == 4) { for(size_t j=0; j < 4; j++) tti[j]=vm::read(ai,j); } else reportError(fourbyfour); } copyTransform3(dest,tt); } void copyTransform3(double*& d, const double* s, GCPlacement placement) { if(s != NULL) { if(d == NULL) d=placement == NoGC ? new double[16] : new(placement) double[16]; memcpy(d,s,sizeof(double)*16); } } // t = s*r void multiplyTransform3(double*& t, const double* s, const double* r) { if(isIdTransform3(s)) { copyTransform3(t,r); } else if(isIdTransform3(r)) { copyTransform3(t,s); } else { t=new(UseGC) double[16]; for(size_t i=0; i < 4; i++) { size_t i4=4*i; const double *si=s+i4; const double& s0=si[0]; const double& s1=si[1]; const double& s2=si[2]; const double& s3=si[3]; double *ti=t+i4; ti[0]=s0*r[0]+s1*r[4]+s2*r[8]+s3*r[12]; ti[1]=s0*r[1]+s1*r[5]+s2*r[9]+s3*r[13]; ti[2]=s0*r[2]+s1*r[6]+s2*r[10]+s3*r[14]; ti[3]=s0*r[3]+s1*r[7]+s2*r[11]+s3*r[15]; } } } double xratio(const triple& v) {return v.getx()/v.getz();} double yratio(const triple& v) {return v.gety()/v.getz();} class matrixstack { mem::stack mstack; public: // return current transform const double* T() const { if(mstack.empty()) return NULL; else return mstack.top(); } // we store the accumulated transform of all pushed transforms void push(const double *r) { double* T3 = NULL; multiplyTransform3(T3,T(),r); mstack.push(T3); } void pop() { if(!mstack.empty()) mstack.pop(); } }; const char *texpathmessage() { ostringstream buf; buf << "the directory containing your " << getSetting("tex") << " engine (" << texcommand() << ")"; return Strdup(buf.str()); } picture::~picture() { } void picture::enclose(drawElement *begin, drawElement *end) { assert(begin); assert(end); nodes.push_front(begin); lastnumber=0; lastnumber3=0; for(nodelist::iterator p=nodes.begin(); p != nodes.end(); ++p) { assert(*p); if((*p)->islayer()) { nodes.insert(p,end); ++p; while(p != nodes.end() && (*p)->islayer()) ++p; if(p == nodes.end()) return; nodes.insert(p,begin); } } nodes.push_back(end); } // Insert at beginning of picture. void picture::prepend(drawElement *p) { assert(p); nodes.push_front(p); lastnumber=0; lastnumber3=0; } void picture::append(drawElement *p) { assert(p); nodes.push_back(p); } void picture::add(picture &pic) { if (&pic == this) return; // STL's funny way of copying one list into another. copy(pic.nodes.begin(), pic.nodes.end(), back_inserter(nodes)); } // Insert picture pic at beginning of picture. void picture::prepend(picture &pic) { if (&pic == this) return; copy(pic.nodes.begin(), pic.nodes.end(), inserter(nodes, nodes.begin())); lastnumber=0; lastnumber3=0; } bool picture::havelabels() { size_t n=nodes.size(); if(n > lastnumber && !labels && getSetting("tex") != "none") { // Check to see if there are any labels yet nodelist::iterator p=nodes.begin(); for(size_t i=0; i < lastnumber; ++i) ++p; for(; p != nodes.end(); ++p) { assert(*p); if((*p)->islabel()) { labels=true; break; } } } return labels; } bool picture::have3D() { for(nodelist::iterator p=nodes.begin(); p != nodes.end(); ++p) { assert(*p); if((*p)->is3D()) return true; } return false; } bool picture::havepng() { for(nodelist::iterator p=nodes.begin(); p != nodes.end(); ++p) { assert(*p); if((*p)->svgpng()) return true; } return false; } unsigned int picture::pagecount() { unsigned int c=1; for(nodelist::iterator p=nodes.begin(); p != nodes.end(); ++p) { assert(*p); if((*p)->isnewpage()) ++c; } return c; } bbox picture::bounds() { size_t n=nodes.size(); if(n == lastnumber) return b_cached; if(lastnumber == 0) { // Maybe these should be put into a structure. b_cached=bbox(); labelbounds.clear(); bboxstack.clear(); } if(havelabels()) texinit(); nodelist::iterator p=nodes.begin(); processDataStruct& pd=processData(); for(size_t i=0; i < lastnumber; ++i) ++p; for(; p != nodes.end(); ++p) { assert(*p); (*p)->bounds(b_cached,pd.tex,labelbounds,bboxstack); // Optimization for interpreters with fixed stack limits. if((*p)->endclip()) { nodelist::iterator q=p; if(q != nodes.begin()) { --q; assert(*q); if((*q)->endclip()) (*q)->save(false); } } } lastnumber=n; return b_cached; } bbox3 picture::bounds3() { size_t n=nodes.size(); if(n == lastnumber3) return b3; if(lastnumber3 == 0) b3=bbox3(); matrixstack ms; for(nodelist::const_iterator p=nodes.begin(); p != nodes.end(); ++p) { assert(*p); if((*p)->begingroup3()) ms.push((*p)->transf3()); else if((*p)->endgroup3()) ms.pop(); else (*p)->bounds(ms.T(),b3); } lastnumber3=n; return b3; } pair picture::ratio(double (*m)(double, double)) { bool first=true; pair b; bounds3(); double fuzz=Fuzz*(b3.Max()-b3.Min()).length(); matrixstack ms; for(nodelist::const_iterator p=nodes.begin(); p != nodes.end(); ++p) { assert(*p); if((*p)->begingroup3()) ms.push((*p)->transf3()); else if((*p)->endgroup3()) ms.pop(); else (*p)->ratio(ms.T(),b,m,fuzz,first); } return b; } void texinit() { drawElement::lastpen=pen(initialpen); processDataStruct &pd=processData(); // Output any new texpreamble commands if(pd.tex.isopen()) { if(pd.TeXpipepreamble.empty()) return; texpreamble(pd.tex,pd.TeXpipepreamble,true); pd.TeXpipepreamble.clear(); return; } bool context=settings::context(getSetting("tex")); string dir=stripFile(outname()); string logname; if(!context) logname=dir; logname += "texput.log"; const char *cname=logname.c_str(); ofstream writeable(cname); if(!writeable) reportError("Cannot write to "+logname); else writeable.close(); unlink(cname); dir=dir.substr(0,dir.length()-1); mem::vector cmd; cmd.push_back(texprogram()); string oldPath; if(context) { if(!dir.empty()) { oldPath=getPath(); setPath(dir.c_str()); } cmd.push_back("--pipe"); } else { if(!dir.empty()) cmd.push_back("-output-directory="+dir); string jobname="texput"; if(getSetting("inlineimage") || getSetting("inlinetex")) { string name=stripDir(stripExt((outname()))); size_t pos=name.rfind("-"); if(pos < string::npos) { name=stripExt(name).substr(0,pos); unlink((name+".aux").c_str()); jobname=name.substr(0,pos); cmd.push_back("-jobname="+jobname); #ifdef __MSDOS__ cmd.push_back("NUL"); // For MikTeX #endif } } cmd.push_back("\\scrollmode"); } pd.tex.open(cmd,"texpath"); pd.tex.wait("\n*"); pd.tex << "\n"; texdocumentclass(pd.tex,true); texdefines(pd.tex,pd.TeXpreamble,true); pd.TeXpipepreamble.clear(); } int opentex(const string& texname, const string& prefix, bool dvi) { string aux=auxname(prefix,"aux"); unlink(aux.c_str()); bool context=settings::context(getSetting("tex")); mem::vector cmd; cmd.push_back(texprogram()); if(dvi) cmd.push_back("-output-format=dvi"); string dir=stripFile(texname); dir=dir.substr(0,dir.length()-1); string oldPath; if(context) { if(!dir.empty()) { oldPath=getPath(); setPath(dir.c_str()); } cmd.push_back("--nonstopmode"); cmd.push_back(texname); } else { if(!dir.empty()) cmd.push_back("-output-directory="+dir); cmd.push_back("\\nonstopmode\\input"); cmd.push_back(stripDir(texname)); } bool quiet=verbose <= 1; int status=System(cmd,quiet ? 1 : 0,true,"texpath",texpathmessage()); if(!status && getSetting("twice")) status=System(cmd,quiet ? 1 : 0,true,"texpath",texpathmessage()); if(status) { if(quiet) { cmd[1]=context ? "--scrollmode" : "\\scrollmode\\input"; System(cmd,0); } } if(context && !oldPath.empty()) setPath(oldPath.c_str()); return status; } string dvisvgmCommand(mem::vector& cmd, const string& outname) { string dir=stripFile(outname); string oldPath; if(!dir.empty()) { oldPath=getPath(); setPath(dir.c_str()); } cmd.push_back(getSetting("dvisvgm")); cmd.push_back("-n"); cmd.push_back("-v3"); string libgs=getSetting("libgs"); if(!libgs.empty()) cmd.push_back("--libgs="+libgs); push_split(cmd,getSetting("dvisvgmOptions")); string outfile=stripDir(outname); if(!outfile.empty()) cmd.push_back("-o"+outfile); return oldPath; } bool picture::texprocess(const string& texname, const string& outname, const string& prefix, const pair& bboxshift, bool svg) { int status=1; ifstream outfile; outfile.open(texname.c_str()); bool keep=getSetting("keep"); if(outfile) { outfile.close(); status=opentex(texname,prefix); string texengine=getSetting("tex"); if(status == 0) { string dviname=auxname(prefix,"dvi"); mem::vector cmd; if(svg) { string name=deconstruct ? buildname(prefix+"_%1p","svg") : outname; string oldPath=dvisvgmCommand(cmd,name); cmd.push_back(stripDir(dviname)); if(deconstruct) cmd.push_back("-p1-"); status=System(cmd,0,true,"dvisvgm"); if(!oldPath.empty()) setPath(oldPath.c_str()); if(!keep) unlink(dviname.c_str()); } else { if(!settings::pdf(texengine)) { string psname=auxname(prefix,"ps"); double height=b.top-b.bottom+1.0; // Magic dvips offsets: double hoffset=-128.4; double vertical=height; if(!latex(texengine)) vertical += 2.0; double voffset=(vertical < 13.0) ? -137.8+vertical : -124.8; double paperHeight=getSetting("paperheight"); hoffset += b.left+bboxshift.getx(); voffset += paperHeight-height-b.bottom-bboxshift.gety(); string dvipsrc=getSetting("dir"); if(dvipsrc.empty()) dvipsrc=systemDir; dvipsrc += dirsep+"nopapersize.ps"; #if !defined(_WIN32) setenv("DVIPSRC",dvipsrc.c_str(),1); #else auto setEnvResult = SetEnvironmentVariableA("DVIPSRC",dvipsrc.c_str()); if (!setEnvResult) { camp::reportError("Cannot set DVIPSRC environment variable"); } #endif string papertype=getSetting("papertype") == "letter" ? "letterSize" : "a4size"; cmd.push_back(getSetting("dvips")); cmd.push_back("-R"); cmd.push_back("-Pdownload35"); cmd.push_back("-D600"); cmd.push_back("-O"+String(hoffset)+"bp,"+String(voffset)+"bp"); bool ps=pagecount() > 1; cmd.push_back("-T"+String(getSetting("paperwidth"))+"bp,"+ String(paperHeight)+"bp"); push_split(cmd,getSetting("dvipsOptions")); if(ps && getSetting("papertype") != "") cmd.push_back("-t"+papertype); if(verbose <= 1) cmd.push_back("-q"); cmd.push_back("-o"+psname); cmd.push_back(dviname); status=System(cmd,0,true,"dvips"); if(status == 0) { ifstream fin(psname.c_str()); psfile fout(outname,false); string s; bool first=true; transform t=shift(bboxshift)*T; bool shift=!t.isIdentity(); const string beginspecial="TeXDict begin @defspecial"; const size_t beginlength=beginspecial.size(); const string endspecial="@fedspecial end"; const size_t endlength=endspecial.size(); bool inpapersize=false; while(getline(fin,s)) { if (inpapersize) { if(s.find("%%EndPaperSize") == 0) inpapersize=false; continue; } else { if (s.find("%%BeginPaperSize:") == 0) { inpapersize=true; continue; } } if (s[0] == '%') { if (s.find("%%DocumentPaperSizes:") == 0) continue; if(s.find("%!PS-Adobe-") == 0) { fout.header(!ps); continue; } if (first && s.find("%%BoundingBox:") == 0) { bbox box=b.shift(bboxshift); if(verbose > 2) BoundingBox(cout,box); fout.BoundingBox(box); first=false; continue; } } if (shift) { if (s.compare(0, beginlength, beginspecial) == 0) { fout.verbatimline(s); fout.gsave(); fout.concat(t); continue; } if (s.compare(0, endlength, endspecial) == 0) { fout.grestore(); fout.verbatimline(s); continue; } } // For the default line, output it unchanged. fout.verbatimline(s); } } if(!keep) { unlink(dviname.c_str()); unlink(psname.c_str()); } } } } if(!keep) { unlink(texname.c_str()); if(!getSetting("keepaux")) unlink(auxname(prefix,"aux").c_str()); unlink(auxname(prefix,"log").c_str()); unlink(auxname(prefix,"out").c_str()); string dir=stripFile(prefix); unlink((dir+"texput.log").c_str()); unlink((dir+"texput.aux").c_str()); if(settings::context(texengine)) { unlink(auxname(prefix,"top").c_str()); unlink(auxname(prefix,"tua").c_str()); unlink(auxname(prefix,"tuc").c_str()); unlink(auxname(prefix,"tui").c_str()); unlink(auxname(prefix,"tuo").c_str()); } } if(status == 0) return true; } return false; } int picture::epstopdf(const string& epsname, const string& pdfname) { string outputformat=getSetting("outformat"); bool pdf=settings::pdf(getSetting("tex")); bool pdfformat=(pdf && outputformat == "") || outputformat == "pdf"; string compress=getSetting("compress") && pdfformat ? "true" : "false"; mem::vector cmd; cmd.push_back(getSetting("gs")); cmd.push_back("-q"); cmd.push_back("-dNOPAUSE"); cmd.push_back("-dBATCH"); cmd.push_back("-P"); if(safe) cmd.push_back("-dSAFER"); cmd.push_back("-dALLOWPSTRANSPARENCY"); // Support transparency extensions. cmd.push_back("-sDEVICE=pdfwrite"); cmd.push_back("-dEPSCrop"); cmd.push_back("-dSubsetFonts=true"); cmd.push_back("-dEmbedAllFonts=true"); cmd.push_back("-dMaxSubsetPct=100"); cmd.push_back("-dEncodeColorImages="+compress); cmd.push_back("-dEncodeGrayImages="+compress); cmd.push_back("-dCompatibilityLevel=1.5"); cmd.push_back("-dTransferFunctionInfo=/Apply"); if(!getSetting("autorotate")) cmd.push_back("-dAutoRotatePages=/None"); cmd.push_back("-g"+String(max(ceil(getSetting("paperwidth")),1.0)) +"x"+String(max(ceil(getSetting("paperheight")),1.0))); cmd.push_back("-dDEVICEWIDTHPOINTS="+String(max(b.right-b.left,3.0))); cmd.push_back("-dDEVICEHEIGHTPOINTS="+String(max(b.top-b.bottom,3.0))); push_split(cmd,getSetting("gsOptions")); cmd.push_back("-sOutputFile="+stripDir(pdfname)); if(safe) { cmd.push_back("-c"); cmd.push_back(".setsafe"); cmd.push_back("-f"); } cmd.push_back(stripDir(epsname)); char *oldPath=NULL; string dir=stripFile(pdfname); if(!dir.empty()) { oldPath=getPath(); setPath(dir.c_str()); } int status=System(cmd,0,true,"gs","Ghostscript"); if(oldPath != NULL) setPath(oldPath); return status; } int picture::pdftoeps(const string& pdfname, const string& epsname, bool eps) { mem::vector cmd; cmd.push_back(getSetting("gs")); cmd.push_back("-q"); cmd.push_back("-dNoOutputFonts"); cmd.push_back("-dNOPAUSE"); cmd.push_back("-dBATCH"); cmd.push_back("-P"); if(safe) cmd.push_back("-dSAFER"); string texengine=getSetting("tex"); cmd.push_back("-sDEVICE="+getSetting(eps ? "epsdriver": "psdriver")); cmd.push_back("-sOutputFile="+stripDir(epsname)); cmd.push_back(stripDir(pdfname)); char *oldPath=NULL; string dir=stripFile(epsname); if(!dir.empty()) { oldPath=getPath(); setPath(dir.c_str()); } int status=System(cmd,0,true,"gs","Ghostscript"); if(oldPath != NULL) setPath(oldPath); return status; } bool picture::reloadPDF(const string& Viewer, const string& outname) const { static bool needReload=true; static bool haveReload=false; string reloadprefix="reload"; if(needReload) { needReload=false; string name=getPath()+string("/")+outname; // Write javascript code to redraw picture. runString("settings.tex='pdflatex'; tex('\\ \\pdfannot width 0pt height 0pt { /AA << /PO << /S /JavaScript /JS (try{reload(\""+name+"\");} catch(e) {} closeDoc(this);) >> >> }'); shipout('"+reloadprefix+"',wait=false,view=false);erase();exit();",false); haveReload=true; } if(haveReload) { mem::vector cmd; push_command(cmd,Viewer); string pdfreloadOptions=getSetting("pdfreloadOptions"); if(!pdfreloadOptions.empty()) cmd.push_back(pdfreloadOptions); cmd.push_back(reloadprefix+".pdf"); System(cmd,0,false); } return true; } int picture::epstosvg(const string& epsname, const string& outname, unsigned int pages) { string oldPath; int status=0; if(deconstruct && getSetting("dvisvgmMultipleFiles")) { mem::vector cmd; oldPath=dvisvgmCommand(cmd,stripFile(outname)); for(unsigned i=1; i <= pages; ++i) { ostringstream buf; buf << epsname << i << ".ps"; cmd.push_back(buf.str()); } cmd.push_back("-E"); status=System(cmd,0,true,"dvisvgm"); for(unsigned i=1; i <= pages; ++i) { ostringstream buf; buf << epsname << i << ".ps"; if(!getSetting("keep")) unlink(buf.str().c_str()); } if(!oldPath.empty()) setPath(oldPath.c_str()); } else { string outprefix=stripExt(outname); for(unsigned i=1; i <= pages; ++i) { mem::vector cmd; ostringstream out; out << outprefix; if(deconstruct) out << "_" << i; out << ".svg"; oldPath=dvisvgmCommand(cmd,out.str()); ostringstream buf; buf << epsname << i << ".ps"; cmd.push_back(buf.str()); cmd.push_back("-E"); status=System(cmd,0,true,"dvisvgm"); if(!getSetting("keep")) unlink(buf.str().c_str()); if(!oldPath.empty()) setPath(oldPath.c_str()); if(status != 0) break; } } return status; } void htmlView(string name) { string const browser=getSetting("htmlviewer"); string const htmlFile=locateFile(name, true); if (browser.empty()) { #if defined(_WIN32) // for windows, no browser means to use the windows' default auto const result = reinterpret_cast( ShellExecuteA( nullptr, "open", htmlFile.c_str(), nullptr, nullptr, SW_SHOWNORMAL )); // see https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea if (result <= 32) { // error code should be stored in GetLastError w32::reportAndFailWithLastError( "Cannot open browser for viewing"); } #else reportError("No browser specified; please specify your browser in htmlviewer"); #endif } else { string const browserOptions= getSetting("htmlviewerOptions"); mem::vector cmd; push_command(cmd, browser); cmd.push_back(htmlFile); if (browserOptions.empty()) { push_split(cmd, browserOptions); } System(cmd, 2, false); } } bool picture::postprocess(const string& prename, const string& outname, const string& outputformat, bool wait, bool view, bool pdftex, bool epsformat, bool svg) { int status=0; bool pdf=settings::pdf(getSetting("tex")); bool pdfformat=(pdf && outputformat == "") || outputformat == "pdf"; mem::vector cmd; if(pdftex || !epsformat) { if(pdfformat) { if(pdftex) { status=rename(prename.c_str(),outname.c_str()); if(status != 0) reportError("Cannot rename "+prename+" to "+outname); } else status=epstopdf(prename,outname); } else if(epsformat) { if(svg) { string psname=stripExt(prename); status=pdftoeps(prename,psname+"%d.ps",false); if(status != 0) return false; status=epstosvg(stripDir(psname),outname,pagecount()); if(status != 0) return false; epsformat=false; } else status=pdftoeps(prename,outname); } else { double render=fabs(getSetting("render")); if(render == 0) render=1.0; double res=render*72.0; Int antialias=getSetting("antialias"); if(outputformat == "png" && antialias == 2) { cmd.push_back(getSetting("gs")); cmd.push_back("-q"); cmd.push_back("-dNOPAUSE"); cmd.push_back("-dBATCH"); cmd.push_back("-P"); cmd.push_back("-sDEVICE="+getSetting("pngdriver")); if(safe) cmd.push_back("-dSAFER"); cmd.push_back("-r"+String(res)+"x"+String(res)); push_split(cmd,getSetting("gsOptions")); cmd.push_back("-sOutputFile="+outname); cmd.push_back(prename); status=System(cmd,0,true,"gs","Ghostscript"); } else if(!svg && !getSetting("xasy")) { double expand=antialias; if(expand < 2.0) expand=1.0; res *= expand; string s=getSetting("convert"); cmd.push_back(s); cmd.push_back("-density"); cmd.push_back(String(res)+"x"+String(res)); cmd.push_back(prename); if(expand == 1.0) cmd.push_back("+antialias"); push_split(cmd,getSetting("convertOptions")); cmd.push_back("-resize"); cmd.push_back(String(100.0/expand)+"%x"); if(outputformat == "jpg") cmd.push_back("-flatten"); cmd.push_back(outputformat+":"+outname); status=System(cmd,0,true,"convert"); } } if(!getSetting("keep")) unlink(prename.c_str()); } if(status != 0) return false; if(verbose > 0 && !deconstruct) cout << "Wrote " << outname << endl; return display(outname,outputformat,wait,view,epsformat); } bool picture::display(const string& outname, const string& outputformat, bool wait, bool view, bool epsformat) { static mem::map pids; if (settings::view() && view) { int status; bool const pdf=settings::pdf(getSetting("tex")); bool pdfformat=(pdf && outputformat.empty()) || outputformat == "pdf"; if(epsformat || pdfformat) { // Check to see if there is an existing viewer for this outname. mem::map::iterator const p=pids.find(outname); bool running=(p != pids.end()); string Viewer= pdfformat ? getSetting("pdfviewer") : getSetting("psviewer"); int pid; if(running) { pid=p->second; if(pid) { #if defined(_WIN32) running=w32::isProcessRunning(pid); #else running= (waitpid(pid, &status, WNOHANG) != pid); #endif } } bool pdfreload=pdfformat && getSetting("pdfreload"); if(running) { #if defined(_WIN32) if (pdfreload) { reloadPDF(Viewer,outname); } #else // win32 does not support reload by sighup // Tell gv/acroread to reread file. if(Viewer == "gv") { kill(pid,SIGHUP); } else if(pdfreload) { reloadPDF(Viewer,outname); } #endif } else { // start new process if (Viewer.empty()) { #if defined(_WIN32) // no viewer, use default string const fullOutFilePath= locateFile(outname, true); SHELLEXECUTEINFOA execInfo = {}; execInfo.cbSize= sizeof(execInfo); execInfo.hwnd = nullptr; execInfo.lpVerb= "open"; execInfo.lpFile= fullOutFilePath.c_str(); execInfo.lpDirectory = nullptr; execInfo.nShow= SW_SHOWNORMAL; execInfo.fMask= SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExA(&execInfo)) { return false; } if (!w32::checkShellExecuteResult(reinterpret_cast(execInfo.hInstApp),false)) { // see https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa return false; } if (execInfo.hProcess!=nullptr) { // wait option does not always work, especially if a new process is not created // for example, if an existing PDF viewer with multiple tabs open is the viewer, // asymptote thinks no process is being created; // in this case, treat it as "no wait" pid=static_cast(GetProcessId(execInfo.hProcess)); CloseHandle(execInfo.hProcess); } #else cerr << "No viewer specified" << endl; return false; #endif } else { string viewerOptions= getSetting(pdfformat ? "pdfviewerOptions" : "psviewerOptions"); mem::vector cmd; push_command(cmd, Viewer); if (!viewerOptions.empty()) push_split(cmd, viewerOptions); cmd.push_back(outname); status=System(cmd, 0, wait, pdfformat ? "pdfviewer" : "psviewer", pdfformat ? "your PDF viewer" : "your PostScript viewer", &pid); if (status != 0) return false; } if(!wait) pids[outname]=pid; if(pdfreload) { // Work around race conditions in acroread initialization script std::this_thread::sleep_for(std::chrono::microseconds( getSetting("pdfreloaddelay") )); // Only reload if pdf viewer process is already running #if defined(_WIN32) bool processRunning=w32::isProcessRunning(pid); #else bool processRunning= waitpid(pid, &status, WNOHANG) == pid; #endif if (processRunning) reloadPDF(Viewer,outname); } } } else { if(outputformat == "svg" || outputformat == "html") htmlView(outname); else { string displayProgram=getSetting("display"); if (displayProgram.empty()) { #if defined(_WIN32) auto const result = reinterpret_cast(ShellExecuteA( nullptr, "open", outname.c_str(), nullptr, nullptr, SW_SHOWNORMAL)); if (result <= 32) { cerr << "Cannot start display viewer" << endl; return false; } #else cerr << "No viewer specified; please specify a viewer in 'display' setting" << endl; return false; #endif } else { mem::vector cmd; push_command(cmd, displayProgram); cmd.push_back(outname); string const application= "your " + outputformat + " viewer"; status= System(cmd, 0, wait, "display", application.c_str()); if (status != 0) return false; } } } } return true; } string Outname(const string& prefix, const string& outputformat, bool standardout, string aux="") { return standardout ? "-" : buildname(prefix,outputformat,aux); } bool picture::shipout(picture *preamble, const string& Prefix, const string& format, bool wait, bool view) { bool keep=getSetting("keep"); string aux=""; b=bounds(); bool empty=b.empty; string outputformat=format.empty() ? defaultformat() : format; bool htmlformat=outputformat == "html"; if(htmlformat) { outputformat="svg"; aux="_"; if(view) view=false; else htmlformat=false; } if(outputformat == "v3d") camp::reportError("v3d format only supports 3D files"); bool svgformat=outputformat == "svg"; bool png=outputformat == "png"; string texengine=getSetting("tex"); string texengineSave; if(!empty && !deconstruct && (png || (svgformat && havepng())) && texengine == "latex") { texengineSave=texengine; Setting("tex")=texengine="pdflatex"; } bool usetex=texengine != "none"; bool TeXmode=getSetting("inlinetex") && usetex; bool pdf=settings::pdf(texengine); bool standardout=Prefix == "-"; string prefix=standardout ? standardprefix : Prefix; string preformat=nativeformat(); bool epsformat=outputformat == "eps"; bool pdfformat=pdf || png || outputformat == "pdf"; bool dvi=false; bool svg=svgformat && usetex && (!have3D() || getSetting("render") == 0.0); if(svg) { if(pdf) epsformat=true; else dvi=true; } string outname=Outname(prefix,outputformat,standardout,aux); string epsname=epsformat ? (standardout ? "" : outname) : auxname(prefix,"eps"); bool Labels=labels || TeXmode; bool Empty=b.right-b.left < 1.0 || b.top-b.bottom < 1.0; if(png && Empty) empty=true; if(Empty && (svgformat || htmlformat)) return true; if(empty && !Labels) { // Output a null file bbox b; b.left=b.bottom=0; b.right=b.top=1; psfile out(epsname,false); out.prologue(b); out.epilogue(); out.close(); return postprocess(epsname,outname,outputformat,wait,view,false, epsformat,false); } if(svg || png) Labels=true; if(Labels) prefix=cleanpath(prefix); string prename=((epsformat && !pdf) || !Labels) ? epsname : auxname(prefix,preformat); SetPageDimensions(); pair aligndir=getSetting("aligndir"); string origin=getSetting("align"); pair bboxshift=(origin == "Z" && epsformat) ? pair(0.0,0.0) : pair(-b.left,-b.bottom); if(epsformat) { bboxshift += getSetting("offset"); double yexcess=max(getSetting("paperheight")- (b.top-b.bottom+1.0),0.0); double xexcess=max(getSetting("paperwidth")- (b.right-b.left+1.0),0.0); if(aligndir == pair(0,0)) { if(origin != "Z" && origin != "B") { if(origin == "T") bboxshift += pair(0.0,yexcess); else bboxshift += pair(0.5*xexcess,0.5*yexcess); } } else { double scale=max(fabs(aligndir.getx()),fabs(aligndir.gety())); if(scale != 0) aligndir *= 0.5/scale; bboxshift += pair((aligndir.getx()+0.5)*xexcess,(aligndir.gety()+0.5)*yexcess); } } bool status=true; string texname; texfile *tex=NULL; if(Labels) { texname=TeXmode ? buildname(prefix,"tex") : auxname(prefix,"tex"); tex=dvi ? new svgtexfile(texname,b,false,deconstruct) : new texfile(texname,b); tex->prologue(deconstruct); } nodelist::iterator layerp=nodes.begin(); nodelist::iterator p=layerp; unsigned layer=0; mem::list files; bbox bshift=b; int svgcount=0; typedef mem::list clipstack; clipstack begin; while(p != nodes.end()) { string psname,pdfname; if(Labels) { ostringstream buf; buf << prefix << "_" << layer; psname=buildname(buf.str(),"eps"); if(pdf) pdfname=buildname(buf.str(),"pdf"); } else { psname=epsname; bshift=bshift.shift(bboxshift); } files.push_back(psname); if(pdf) files.push_back(pdfname); psfile out(psname,pdfformat); out.prologue(bshift); if(!Labels) { out.gsave(); out.translate(bboxshift); } if(preamble) { // Postscript preamble. nodelist Nodes=preamble->nodes; nodelist::iterator P=Nodes.begin(); if(P != Nodes.end()) { out.resetpen(); for(; P != Nodes.end(); ++P) { assert(*P); (*P)->draw(&out); } } } out.resetpen(); bool postscript=false; drawLabel *L=NULL; if(dvi) for(nodelist::const_iterator r=begin.begin(); r != begin.end(); ++r) (*r)->draw(&out); processDataStruct &pd=processData(); for(; p != nodes.end(); ++p) { assert(*p); if(Labels && (*p)->islayer()) break; if(dvi && (*p)->svg()) { picture *f=(*p)->svgpng() ? new picture : NULL; nodelist::const_iterator q=layerp; for(;;) { if((*q)->beginclip()) begin.push_back(*q); else if((*q)->endclip()) { if(begin.size() < 1) reportError("endclip without matching beginclip"); begin.pop_back(); } if(q == p) break; ++q; } if(f) { for(nodelist::const_iterator r=begin.begin(); r != begin.end(); ++r) f->append(*r); f->append(*(q++)); } while(q != nodes.end() && !(*q)->islayer()) ++q; clipstack end; for(nodelist::const_iterator r=--q;; --r) { if((*r)->beginclip() && end.size() >= 1) end.pop_back(); else if((*r)->endclip()) end.push_back(*r); if(r == p) break; } for(nodelist::reverse_iterator r=end.rbegin(); r != end.rend(); ++r) { (*r)->draw(&out); if(f) f->append(*r); } if(f) { ostringstream buf; buf << prefix << "_" << svgcount; ++svgcount; string pngname=buildname(buf.str(),"png"); f->shipout(preamble,buf.str(),"png",false,false); pair m=f->bounds().Min(); pair M=f->bounds().Max(); delete f; pair size=M-m; ostringstream cmd; cmd << "\\special{dvisvgm:img " << size.getx()*ps2tex << " " << size.gety()*ps2tex << " " << pngname << "}"; static pen P; static pair zero; L=new drawLabel(cmd.str(),"",identity,pair(m.getx(),M.gety()),zero,P); texinit(); L->bounds(b_cached,pd.tex,labelbounds,bboxstack); postscript=true; } break; } else postscript |= (*p)->draw(&out); } if(Labels) { if(!svg || pdf) tex->beginlayer(pdf ? pdfname : psname,postscript); } else out.grestore(); out.epilogue(); out.close(); if(Labels) { tex->resetpen(); if(pdf && !b.empty) { status=(epstopdf(psname,pdfname) == 0); if(!keep) unlink(psname.c_str()); } if(status) { for (p=layerp; p != nodes.end(); ++p) { assert(*p); bool islayer=(*p)->islayer(); if(dvi && (*p)->svg()) { islayer=true; if((*p)->svgpng()) L->write(tex,b); else (*p)->draw(tex); } else (*p)->write(tex,b); if(islayer) { tex->endlayer(); layerp=++p; layer++; break; } } } } } bool context=settings::context(texengine); if(status) { if(TeXmode) { if(Labels && verbose > 0) cout << "Wrote " << texname << endl; delete tex; } else { if(Labels) { tex->epilogue(); if(context) prefix=stripDir(prefix); status=texprocess(texname,dvi ? outname : prename,prefix, bboxshift,dvi); delete tex; if(!keep) { for(mem::list::iterator p=files.begin(); p != files.end(); ++p) unlink(p->c_str()); } } if(status) { if(context) prename=stripDir(prename); status=postprocess(prename,outname,outputformat,wait, view,pdf && Labels,epsformat,svg); if(pdfformat && !keep) { unlink(auxname(prefix,"m9").c_str()); unlink(auxname(prefix,"pbsdat").c_str()); } } } } if(!status) reportError("shipout failed"); if(!texengineSave.empty()) Setting("tex")=texengineSave; if(htmlformat) { jsfile out; out.svgtohtml(prefix); string name=buildname(prefix,"html"); display(name,"html",wait,true,false); if(!keep) unlink(outname.c_str()); } return true; } // render viewport with width x height pixels. void picture::render(double size2, const triple& Min, const triple& Max, double perspective, bool remesh) const { for(nodelist::const_iterator p=nodes.begin(); p != nodes.end(); ++p) { assert(*p); if(remesh) (*p)->meshinit(); (*p)->render(size2,Min,Max,perspective,remesh); } #ifdef HAVE_GL drawBuffers(); #endif } typedef gl::GLRenderArgs Communicate; Communicate com; extern bool allowRender; void glrenderWrapper() { #ifdef HAVE_GL #ifdef HAVE_PTHREAD wait(initSignal,initLock); endwait(initSignal,initLock); #endif if(allowRender) glrender(com); #endif } bool picture::shipout3(const string& prefix, const string& format, double width, double height, double angle, double zoom, const triple& m, const triple& M, const pair& shift, const pair& margin, double *t, double *tup, double *background, size_t nlights, triple *lights, double *diffuse, double *specular, bool view) { if(getSetting("interrupt")) return true; if(width <= 0 || height <= 0) return false; bool webgl=format == "html"; bool v3d=format == "v3d"; #ifndef HAVE_LIBGLM if(webgl) camp::reportError("to support WebGL rendering, please install glm header files, then ./configure; make"); if(v3d) camp::reportError("to support V3D rendering, please install glm header files, then ./configure; make"); #endif #ifndef HAVE_LIBOSMESA #ifndef HAVE_GL if(!webgl) camp::reportError("to support onscreen OpenGL rendering; please install the glut library, then ./configure; make"); #endif #endif picture *pic = new picture; matrixstack ms; for(nodelist::const_iterator p=nodes.begin(); p != nodes.end(); ++p) { assert(*p); if((*p)->begingroup3()) ms.push((*p)->transf3()); else if((*p)->endgroup3()) ms.pop(); else pic->append((*p)->transformed(ms.T())); } pic->b3=bbox3(); for(nodelist::iterator p=pic->nodes.begin(); p != pic->nodes.end(); ++p) { assert(*p); (*p)->bounds(pic->b3); } pic->lastnumber3=pic->nodes.size(); for(nodelist::iterator p=pic->nodes.begin(); p != pic->nodes.end(); ++p) { assert(*p); (*p)->displacement(); } const string outputformat=format.empty() ? getSetting("outformat") : format; #ifdef HAVE_LIBGLM static int oldpid=0; bool View=settings::view() && view; #endif #ifdef HAVE_GL bool offscreen=false; #ifdef HAVE_LIBOSMESA offscreen=true; #endif #ifdef HAVE_PTHREAD bool animating=getSetting("animating"); bool Wait=!interact::interactive || !View || animating; #endif #endif bool format3d=webgl || v3d; if(!format3d) { #ifdef HAVE_GL if(glthread && !offscreen) { #ifdef HAVE_PTHREAD if(gl::initialize) { gl::initialize=false; com.prefix=prefix; com.pic=pic; com.format=outputformat; com.width=width; com.height=height; com.angle=angle; com.zoom=zoom; com.m=m; com.M=M; com.shift=shift; com.margin=margin; com.t=t; com.tup=tup; com.background=background; com.nlights=nlights; com.lights=lights; com.diffuse=diffuse; com.specular=specular; com.view=View; if(Wait) pthread_mutex_lock(&readyLock); wait(initSignal,initLock); endwait(initSignal,initLock); static bool initialize=true; if(initialize) { wait(initSignal,initLock); endwait(initSignal,initLock); initialize=false; } if(Wait) { pthread_cond_wait(&readySignal,&readyLock); pthread_mutex_unlock(&readyLock); } return true; } if(Wait) pthread_mutex_lock(&readyLock); #endif } else { #if !defined(_WIN32) int pid=fork(); if(pid == -1) camp::reportError("Cannot fork process"); if(pid != 0) { oldpid=pid; waitpid(pid,NULL,interact::interactive && View ? WNOHANG : 0); return true; } #else #pragma message("TODO: Check if (1) we need detach-based gl renderer") #endif } #endif } #if HAVE_LIBGLM gl::GLRenderArgs args; args.prefix=prefix; args.pic=pic; args.format=outputformat; args.width=width; args.height=height; args.angle=angle; args.zoom=zoom; args.m=m; args.M=M; args.shift=shift; args.margin=margin; args.t=t; args.tup=tup; args.background=background; args.nlights=nlights; args.lights=lights; args.diffuse=diffuse; args.specular=specular; args.view=View; glrender(args,oldpid); if(format3d) { string name=buildname(prefix,format); abs3Doutfile *fileObj=nullptr; if(webgl) fileObj=new jsfile(name); else if(v3d) #ifdef HAVE_LIBTIRPC fileObj=new gzv3dfile(name,getSetting("lossy") || getSetting("prerender") > 0.0); #else { ostringstream buf; buf << name << ": XDR write support not enabled"; reportError(buf); } #endif if(fileObj) { for (auto& p : pic->nodes) { assert(p); p->write(fileObj); } fileObj->close(); delete fileObj; } if(webgl && View) htmlView(name); #ifdef HAVE_GL if(format3dWait) { format3dWait=false; #ifdef HAVE_PTHREAD endwait(initSignal,initLock); #endif } #endif return true; } #endif #ifdef HAVE_GL #ifdef HAVE_PTHREAD if(glthread && !offscreen && Wait) { pthread_cond_wait(&readySignal,&readyLock); pthread_mutex_unlock(&readyLock); } return true; #endif #endif return false; } bool picture::shipout3(const string& prefix, const string format) { bounds3(); bool status; string name=buildname(prefix,"prc"); prcfile prc(name); static const double limit=2.5*10.0/INT_MAX; double compressionlimit=max(length(b3.Max()),length(b3.Min()))*limit; groups.push_back(groupmap()); for(nodelist::iterator p=nodes.begin(); p != nodes.end(); ++p) { assert(*p); (*p)->write(&prc,&billboard,compressionlimit,groups); } groups.pop_back(); status=prc.finish(); if(!status) reportError("shipout3 failed"); if(verbose > 0) cout << "Wrote " << name << endl; return true; } picture *picture::transformed(const transform& t) { picture *pic = new picture; nodelist::iterator p; for (p = nodes.begin(); p != nodes.end(); ++p) { assert(*p); pic->append((*p)->transformed(t)); } pic->T=transform(t*T); return pic; } picture *picture::transformed(const array& t) { picture *pic = new picture; double* T=NULL; copyArray4x4C(T,&t); size_t level = 0; for (nodelist::iterator p = nodes.begin(); p != nodes.end(); ++p) { assert(*p); if(level==0) pic->append((*p)->transformed(T)); else pic->append(*p); if((*p)->begingroup3()) level++; if((*p)->endgroup3()) { if(level==0) reportError("endgroup3 without matching begingroup3"); else level--; } } return pic; } } // namespace camp