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));
  };
