/*****
* 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 "seconds.h"
#if defined(_WIN32)
#include <Windows.h>
#include <shellapi.h>
#include <cstdio>
#endif
#include <thread>
#include <chrono>
using std::ifstream;
using std::ofstream;
using vm::array;
using namespace settings;
using namespace gl;
texstream::~texstream() {
string texengine=getSetting<string>("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<vm::array*>(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<double>(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<const double*> 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<string>("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<string>("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<string>("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<string> 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<bool>("inlineimage") || getSetting<bool>("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<string>("tex"));
mem::vector<string> 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<bool>("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<string>& cmd, const string& outname)
{
string dir=stripFile(outname);
string oldPath;
if(!dir.empty()) {
oldPath=getPath();
setPath(dir.c_str());
}
cmd.push_back(getSetting<string>("dvisvgm"));
cmd.push_back("-n");
cmd.push_back("-v3");
string libgs=getSetting<string>("libgs");
if(!libgs.empty())
cmd.push_back("--libgs="+libgs);
push_split(cmd,getSetting<string>("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<bool>("keep");
if(outfile) {
outfile.close();
status=opentex(texname,prefix);
string texengine=getSetting<string>("tex");
if(status == 0) {
string dviname=auxname(prefix,"dvi");
mem::vector<string> 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<double>("paperheight");
hoffset += b.left+bboxshift.getx();
voffset += paperHeight-height-b.bottom-bboxshift.gety();
string dvipsrc=getSetting<string>("dir");
if(dvipsrc.empty()) dvipsrc=systemDir;
dvipsrc += dirsep+"nopapersize.ps";
setenv("DVIPSRC",dvipsrc.c_str(),true);
string papertype=getSetting<string>("papertype") == "letter" ?
"letterSize" : "a4size";
cmd.push_back(getSetting<string>("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<double>("paperwidth"))+"bp,"+
String(paperHeight)+"bp");
push_split(cmd,getSetting<string>("dvipsOptions"));
if(ps && getSetting<string>("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<bool>("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<string>("outformat");
bool pdf=settings::pdf(getSetting<string>("tex"));
bool pdfformat=(pdf && outputformat == "") || outputformat == "pdf";
string compress=getSetting<bool>("compress") && pdfformat ?
"true" : "false";
mem::vector<string> cmd;
cmd.push_back(getSetting<string>("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<bool>("autorotate"))
cmd.push_back("-dAutoRotatePages=/None");
cmd.push_back("-g"+String(max(ceil(getSetting<double>("paperwidth")),1.0))
+"x"+String(max(ceil(getSetting<double>("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<string>("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<string> cmd;
cmd.push_back(getSetting<string>("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<string>("tex");
cmd.push_back("-sDEVICE="+getSetting<string>(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<string> cmd;
push_command(cmd,Viewer);
string pdfreloadOptions=getSetting<string>("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<bool>("dvisvgmMultipleFiles")) {
mem::vector<string> 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<bool>("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<string> 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<bool>("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<string>("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<INT_PTR>(
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<string>("htmlviewerOptions");
mem::vector<string> 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<string>("tex"));
bool pdfformat=(pdf && outputformat == "") || outputformat == "pdf";
mem::vector<string> cmd;
if(pdftex || !epsformat) {
if(pdfformat) {
if(pdftex) {
status=renameOverwrite(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<double>("render"));
if(render == 0) render=1.0;
double res=render*72.0;
Int antialias=getSetting<Int>("antialias");
if(outputformat == "png" && antialias == 2) {
cmd.push_back(getSetting<string>("gs"));
cmd.push_back("-q");
cmd.push_back("-dNOPAUSE");
cmd.push_back("-dBATCH");
cmd.push_back("-P");
cmd.push_back("-sDEVICE="+getSetting<string>("pngdriver"));
if(safe)
cmd.push_back("-dSAFER");
cmd.push_back("-r"+String(res)+"x"+String(res));
push_split(cmd,getSetting<string>("gsOptions"));
cmd.push_back("-sOutputFile="+outname);
cmd.push_back("-dTextAlphaBits=4");
cmd.push_back("-dGraphicsAlphaBits=4");
cmd.push_back(prename);
status=System(cmd,0,true,"gs","Ghostscript");
} else if(!svg && !xasy) {
double expand=antialias;
if(expand < 2.0) expand=1.0;
res *= expand;
string s=getSetting<string>("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<string>("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<bool>("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<const string,int> pids;
if (settings::view() && view)
{
int status;
bool const pdf=settings::pdf(getSetting<string>("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<const string,int>::iterator const p=pids.find(outname);
bool running=(p != pids.end());
string Viewer=
pdfformat ?
getSetting<string>("pdfviewer") : getSetting<string>("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<bool>("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<INT_PTR>(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<int>(GetProcessId(execInfo.hProcess));
CloseHandle(execInfo.hProcess);
}
#else
cerr << "No viewer specified" << endl;
return false;
#endif
}
else
{
string viewerOptions= getSetting<string>(pdfformat ? "pdfviewerOptions" : "psviewerOptions");
mem::vector<string> 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<Int>("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<string>("display");
if (displayProgram.empty())
{
#if defined(_WIN32)
auto const result = reinterpret_cast<INT_PTR>(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<string> 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<bool>("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<string>("tex");
string texengineSave;
if(!empty && !deconstruct && (png || (svgformat && havepng())) &&
texengine == "latex") {
texengineSave=texengine;
Setting("tex")=texengine="pdflatex";
}
bool usetex=texengine != "none";
bool TeXmode=getSetting<bool>("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<double>("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<pair>("aligndir");
string origin=getSetting<string>("align");
pair bboxshift=(origin == "Z" && epsformat) ?
pair(0.0,0.0) : pair(-b.left,-b.bottom);
if(epsformat) {
bboxshift += getSetting<pair>("offset");
double yexcess=max(getSetting<double>("paperheight")-
(b.top-b.bottom+1.0),0.0);
double xexcess=max(getSetting<double>("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<string> files;
bbox bshift=b;
int svgcount=0;
typedef mem::list<drawElement *> 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);
delete tex;
status=texprocess(texname,dvi ? outname : prename,prefix,
bboxshift,dvi);
if(!keep) {
for(mem::list<string>::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<bool>("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<string>("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<bool>("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);
allowRender=true;
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<bool>("lossy") ||
getSetting<double>("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