real angle(transform t)
{
pair z=(2t.xx*t.yy,t.yx*t.yy-t.xx*t.xy);
if(t.xx < 0 || t.yy < 0) z=-z;
return degrees(z,warn=false);
}
transform rotation(transform t)
{
return rotate(angle(t));
}
transform scaleless(transform t)
{
real a=t.xx, b=t.xy, c=t.yx, d=t.yy;
real arg=(a-d)^2+4b*c;
pair delta=arg >= 0 ? sqrt(arg) : I*sqrt(-arg);
real trace=a+d;
pair l1=0.5(trace+delta);
pair l2=0.5(trace-delta);
if(abs(delta) < sqrtEpsilon*max(abs(l1),abs(l2))) {
real s=abs(0.5trace);
return (s != 0) ? scale(1/s)*t : t;
}
if(abs(l1-d) < abs(l2-d)) {pair temp=l1; l1=l2; l2=temp;}
pair dot(pair[] u, pair[] v) {return conj(u[0])*v[0]+conj(u[1])*v[1];}
pair[] unit(pair[] u) {
real norm2=abs(u[0])^2+abs(u[1])^2;
return norm2 != 0 ? u/sqrt(norm2) : u;
}
pair[] u={l1-d,b};
pair[] v={c,l2-a};
u=unit(u);
pair d=dot(u,u);
if(d != 0) v -= dot(u,v)/d*u;
v=unit(v);
pair[][] U={{u[0],v[0]},{u[1],v[1]}};
pair[][] A={{a,b},{c,d}};
pair[][] operator *(pair[][] a, pair[][] b) {
pair[][] c=new pair[2][2];
for(int i=0; i < 2; ++i) {
for(int j=0; j < 2; ++j) {
c[i][j]=a[i][0]*b[0][j]+a[i][1]*b[1][j];
}
}
return c;
}
pair[][] conj(pair[][] a) {
pair[][] c=new pair[2][2];
for(int i=0; i < 2; ++i) {
for(int j=0; j < 2; ++j) {
c[i][j]=conj(a[j][i]);
}
}
return c;
}
A=conj(U)*A*U;
real D=abs(A[0][0]);
if(D != 0) {
A[0][0] /= D;
A[0][1] /= D;
}
D=abs(A[1][1]);
if(D != 0) {
A[1][0] /= D;
A[1][1] /= D;
}
A=U*A*conj(U);
return (0,0,A[0][0].x,A[0][1].x,A[1][0].x,A[1][1].x);
}
struct align {
pair dir;
triple dir3;
bool relative=false;
bool default=true;
bool is3D=false;
void init(pair dir=0, bool relative=false, bool default=false) {
this.dir=dir;
this.relative=relative;
this.default=default;
is3D=false;
}
void init(triple dir=(0,0,0), bool relative=false, bool default=false) {
this.dir3=dir;
this.relative=relative;
this.default=default;
is3D=true;
}
align copy() {
align align=new align;
align.init(dir,relative,default);
align.dir3=dir3;
align.is3D=is3D;
return align;
}
void align(align align) {
if(!align.default) {
bool is3D=align.is3D;
init(align.dir,align.relative);
dir3=align.dir3;
this.is3D=is3D;
}
}
void align(align align, align default) {
align(align);
if(this.default) {
init(default.dir,default.relative,default.default);
dir3=default.dir3;
is3D=default.is3D;
}
}
void write(file file=stdout, suffix suffix=endl) {
if(!default) {
if(relative) {
write(file,"Relative(");
if(is3D)
write(file,dir3);
else
write(file,dir);
write(file,")",suffix);
} else {
if(is3D)
write(file,dir3,suffix);
else
write(file,dir,suffix);
}
}
}
bool Center() {
return relative && (is3D ? dir3 == (0,0,0) : dir == 0);
}
}
struct side {
pair align;
}
side Relative(explicit pair align)
{
side s;
s.align=align;
return s;
}
restricted side NoSide;
restricted side LeftSide=Relative(W);
restricted side Center=Relative((0,0));
restricted side RightSide=Relative(E);
side operator * (real x, side s)
{
side S;
S.align=x*s.align;
return S;
}
align operator cast(pair dir) {align A; A.init(dir,false); return A;}
align operator cast(triple dir) {align A; A.init(dir,false); return A;}
align operator cast(side side) {align A; A.init(side.align,true); return A;}
restricted align NoAlign;
void write(file file=stdout, align align, suffix suffix=endl)
{
align.write(file,suffix);
}
struct position {
pair position;
bool relative;
}
position Relative(real position)
{
position p;
p.position=position;
p.relative=true;
return p;
}
restricted position BeginPoint=Relative(0);
restricted position MidPoint=Relative(0.5);
restricted position EndPoint=Relative(1);
position operator cast(pair x) {position P; P.position=x; return P;}
position operator cast(real x) {return (pair) x;}
position operator cast(int x) {return (pair) x;}
pair operator cast(position P) {return P.position;}
using embed=transform(transform);
transform Shift(transform t) {return identity();}
transform Rotate(transform t) {return rotation(t);}
transform Slant(transform t) {return scaleless(t);}
transform Scale(transform t) {return t;}
embed Rotate(pair z) {
return new transform(transform t) {return rotate(degrees(shiftless(t)*z,
warn=false));};
}
path[] texpath(string s, pen p, bool tex=settings.tex != "none",
bool bbox=false);
struct Label {
string s,size;
position position;
bool defaultposition=true;
align align;
pen p=nullpen;
transform T;
transform3 T3=identity(4);
bool defaulttransform=true;
bool defaulttransform3=true;
embed embed=Rotate; // Shift, Rotate, Slant, or Scale with embedded picture
filltype filltype=NoFill;
void init(string s="", string size="", position position=0,
bool defaultposition=true, align align=NoAlign, pen p=nullpen,
transform T=identity(), transform3 T3=identity4,
bool defaulttransform=true, bool defaulttransform3=true,
embed embed=Rotate, filltype filltype=NoFill) {
this.s=s;
this.size=size;
this.position=position;
this.defaultposition=defaultposition;
this.align=align.copy();
this.p=p;
this.T=T;
this.T3=copy(T3);
this.defaulttransform=defaulttransform;
this.defaulttransform3=defaulttransform3;
this.embed=embed;
this.filltype=filltype;
}
void initalign(string s="", string size="", align align, pen p=nullpen,
embed embed=Rotate, filltype filltype=NoFill) {
init(s,size,align,p,embed,filltype);
}
void transform(transform T) {
this.T=T;
defaulttransform=false;
}
void transform3(transform3 T) {
this.T3=copy(T);
defaulttransform3=false;
}
Label copy(transform3 T3=this.T3) {
Label L=new Label;
L.init(s,size,position,defaultposition,align,p,T,T3,defaulttransform,
defaulttransform3,embed,filltype);
return L;
}
void position(position pos) {
this.position=pos;
defaultposition=false;
}
void align(align a) {
align.align(a);
}
void align(align a, align default) {
align.align(a,default);
}
void p(pen p0) {
if(this.p == nullpen) this.p=p0;
}
void filltype(filltype filltype0) {
if(this.filltype == NoFill) this.filltype=filltype0;
}
void label(frame f, transform t=identity(), pair position, pair align) {
pen p0=p == nullpen ? currentpen : p;
align=length(align)*unit(rotation(t)*align);
pair S=t*position+align*labelmargin(p0)+shift(T)*0;
if(settings.tex != "none")
label(f,s,size,embed(t)*shiftless(T),S,align,p0);
else
fill(f,align(texpath(s,p0),S,align,p0),p0);
}
void out(frame f, transform t=identity(), pair position=position.position,
pair align=align.dir) {
if(filltype == NoFill)
label(f,t,position,align);
else {
frame d;
label(d,t,position,align);
add(f,d,filltype);
}
}
void label(picture pic=currentpicture, pair position, pair align) {
if(s == "") return;
pic.add(new void (frame f, transform t) {
out(f,t,position,align);
},true);
frame f;
// Create a picture with label at the origin to extract its bbox truesize.
label(f,(0,0),align);
pic.addBox(position,position,min(f),max(f));
}
void out(picture pic=currentpicture) {
label(pic,position.position,align.dir);
}
void out(picture pic=currentpicture, path g) {
bool relative=position.relative;
real position=position.position.x;
pair Align=align.dir;
bool alignrelative=align.relative;
if(defaultposition) {relative=true; position=0.5;}
if(relative) position=reltime(g,position);
if(align.default) {
alignrelative=true;
Align=position <= sqrtEpsilon ? S :
position >= length(g)-sqrtEpsilon ? N : E;
}
pic.add(new void (frame f, transform t) {
out(f,t,point(g,position),alignrelative ?
inverse(rotation(t))*-Align*dir(t*g,position)*I : Align);
},!alignrelative);
frame f;
pair align=alignrelative ? -Align*dir(g,position)*I : Align;
label(f,(0,0),align);
pair position=point(g,position);
pic.addBox(position,position,min(f),max(f));
}
void write(file file=stdout, suffix suffix=endl) {
write(file,"\""+s+"\"");
if(!defaultposition) write(file,", position=",position.position);
if(!align.default) write(file,", align=");
write(file,align);
if(p != nullpen) write(file,", pen=",p);
if(!defaulttransform)
write(file,", transform=",T);
if(!defaulttransform3) {
write(file,", transform3=",endl);
write(file,T3);
}
write(file,"",suffix);
}
real relative() {
return defaultposition ? 0.5 : position.position.x;
};
real relative(path g) {
return position.relative ? reltime(g,relative()) : relative();
};
}
Label Label;
void add(frame f, transform t=identity(), Label L)
{
L.out(f,t);
}
void add(picture pic=currentpicture, Label L)
{
L.out(pic);
}
Label operator * (transform t, Label L)
{
Label tL=L.copy();
tL.align.dir=L.align.dir;
tL.transform(t*L.T);
return tL;
}
Label operator * (transform3 t, Label L)
{
Label tL=L.copy(t*L.T3);
tL.align.dir=L.align.dir;
tL.defaulttransform3=false;
return tL;
}
Label Label(string s, string size="", explicit position position,
align align=NoAlign, pen p=nullpen, embed embed=Rotate,
filltype filltype=NoFill)
{
Label L;
L.init(s,size,position,false,align,p,embed,filltype);
return L;
}
Label Label(string s, string size="", pair position, align align=NoAlign,
pen p=nullpen, embed embed=Rotate, filltype filltype=NoFill)
{
return Label(s,size,(position) position,align,p,embed,filltype);
}
Label Label(explicit pair position, align align=NoAlign, pen p=nullpen,
embed embed=Rotate, filltype filltype=NoFill)
{
return Label((string) position,position,align,p,embed,filltype);
}
Label Label(string s="", string size="", align align=NoAlign, pen p=nullpen,
embed embed=Rotate, filltype filltype=NoFill)
{
Label L;
L.initalign(s,size,align,p,embed,filltype);
return L;
}
Label Label(Label L, align align=NoAlign, pen p=nullpen, embed embed=L.embed,
filltype filltype=NoFill)
{
Label L=L.copy();
L.align(align);
L.p(p);
L.embed=embed;
L.filltype(filltype);
return L;
}
Label Label(Label L, explicit position position, align align=NoAlign,
pen p=nullpen, embed embed=L.embed, filltype filltype=NoFill)
{
Label L=Label(L,align,p,embed,filltype);
L.position(position);
return L;
}
Label Label(Label L, pair position, align align=NoAlign,
pen p=nullpen, embed embed=L.embed, filltype filltype=NoFill)
{
return Label(L,(position) position,align,p,embed,filltype);
}
void write(file file=stdout, Label L, suffix suffix=endl)
{
L.write(file,suffix);
}
void label(frame f, Label L, pair position, align align=NoAlign,
pen p=currentpen, filltype filltype=NoFill)
{
add(f,Label(L,position,align,p,filltype));
}
void label(frame f, Label L, align align=NoAlign,
pen p=currentpen, filltype filltype=NoFill)
{
add(f,Label(L,L.position,align,p,filltype));
}
void label(picture pic=currentpicture, Label L, pair position,
align align=NoAlign, pen p=currentpen, filltype filltype=NoFill)
{
Label L=Label(L,position,align,p,filltype);
add(pic,L);
}
void label(picture pic=currentpicture, Label L, align align=NoAlign,
pen p=currentpen, filltype filltype=NoFill)
{
label(pic,L,L.position,align,p,filltype);
}
// Label, but with postscript coords instead of asy
void label(pair origin, picture pic=currentpicture, Label L, align align=NoAlign,
pen p=currentpen, filltype filltype=NoFill)
{
picture opic;
label(opic,L,L.position,align,p,filltype);
add(pic,opic,origin);
}
void label(picture pic=currentpicture, Label L, explicit path g,
align align=NoAlign, pen p=currentpen, filltype filltype=NoFill)
{
Label L=Label(L,align,p,filltype);
L.out(pic,g);
}
void label(picture pic=currentpicture, Label L, explicit guide g,
align align=NoAlign, pen p=currentpen, filltype filltype=NoFill)
{
label(pic,L,(path) g,align,p,filltype);
}
Label operator cast(string s) {return Label(s);}
// A structure that a string, Label, or frame can be cast to.
struct object {
frame f;
Label L=Label;
path g; // Bounding path
void operator init(frame f) {
this.f=f;
g=box(min(f),max(f));
}
void operator init(Label L) {
this.L=L.copy();
if(L != Label) L.out(f);
g=box(min(f),max(f));
}
}
object operator cast(frame f) {
return object(f);
}
object operator cast(Label L)
{
return object(L);
}
object operator cast(string s)
{
return object(s);
}
Label operator cast(object F)
{
return F.L;
}
frame operator cast(object F)
{
return F.f;
}
object operator * (transform t, explicit object F)
{
object f;
f.f=t*F.f;
f.L=t*F.L;
f.g=t*F.g;
return f;
}
// Returns a copy of object F aligned in the direction align
object align(object F, pair align)
{
return shift(F.f,align)*F;
}
void add(picture dest=currentpicture, object F, pair position=0,
bool group=true, filltype filltype=NoFill, bool above=true)
{
add(dest,F.f,position,group,filltype,above);
}
// Pack a list of objects into a frame.
frame pack(pair align=2S ... object inset[])
{
frame F;
int n=inset.length;
pair z;
for (int i=0; i < n; ++i) {
add(F,inset[i].f,z);
z += align+realmult(unit(align),size(inset[i].f));
}
return F;
}
path[] texpath(Label L, bool tex=settings.tex != "none", bool bbox=false)
{
struct stringfont
{
string s;
real fontsize;
string font;
void operator init(Label L)
{
s=replace(L.s,'\n',' ');
fontsize=fontsize(L.p);
font=font(L.p);
}
pen pen() {return fontsize(fontsize)+fontcommand(font);}
}
bool lexorder(stringfont a, stringfont b) {
return a.s < b.s || (a.s == b.s && (a.fontsize < b.fontsize ||
(a.fontsize == b.fontsize &&
a.font < b.font)));
}
static stringfont[] stringcache;
static path[][] pathcache;
static stringfont[] stringlist;
static bool adjust[];
path[] G;
stringfont s=stringfont(L);
pen p=s.pen();
int i=search(stringcache,s,lexorder);
if(i == -1 || lexorder(stringcache[i],s)) {
int k=search(stringlist,s,lexorder);
if(k == -1 || lexorder(stringlist[k],s)) {
++k;
stringlist.insert(k,s);
// PDF tex engines lose track of the baseline.
adjust.insert(k,tex && basealign(L.p) == 1 && pdf());
}
}
path[] transform(path[] g, Label L) {
if(g.length == 0) return g;
pair m=min(g);
pair M=max(g);
pair dir=rectify(inverse(L.T)*-L.align.dir);
if(tex && basealign(L.p) == 1)
dir -= (0,(1-dir.y)*m.y/(M.y-m.y));
pair a=m+realmult(dir,M-m);
return shift(L.position+L.align.dir*labelmargin(L.p))*L.T*shift(-a)*g;
}
if(tex && bbox) {
frame f;
label(f,L);
return transform(box(min(f),max(f)),L);
}
if(stringlist.length > 0) {
path[][] g;
int n=stringlist.length;
string[] s=new string[n];
pen[] p=new pen[n];
for(int i=0; i < n; ++i) {
stringfont S=stringlist[i];
s[i]=adjust[i] ? "."+S.s : S.s;
p[i]=adjust[i] ? S.pen()+basealign : S.pen();
}
g=tex ? _texpath(s,p) : textpath(s,p);
if(tex)
for(int i=0; i < n; ++i)
if(adjust[i]) {
real y=min(g[i][0]).y;
g[i].delete(0);
g[i]=shift(0,-y)*g[i];
}
for(int i=0; i < stringlist.length; ++i) {
stringfont s=stringlist[i];
int j=search(stringcache,s,lexorder)+1;
stringcache.insert(j,s);
pathcache.insert(j,g[i]);
}
stringlist.delete();
adjust.delete();
}
return transform(pathcache[search(stringcache,stringfont(L),lexorder)],L);
}
texpath=new path[](string s, pen p, bool tex=settings.tex != "none", bool bbox=false)
{
return texpath(Label(s,p));
};