/*************************************************************
 *
 *  MathJax/extensions/TeX/xypic.js
 *
 *  Implements Xy-pic environment.
 *  
 *  ---------------------------------------------------------------------
 *  
 *  Copyright (c) 2011-2014 Isao Sonobe <sonoisa@gmail.com>.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

(function () {
  
  var FP = MathJax.Extension.fp = {
    version: "0.1"
  };
  
  /************ Matcher **************/
  FP.Matcher = MathJax.Object.Subclass({
    Init: function () { this.cases = []; },
    Case: function (klass, f) {
      this.cases.push([klass, f]);
      return this;
    },
    match: function (x) {
      if (x instanceof Object && "isa" in x) {
        var i, count, klass, op;
        i = 0;
        count = this.cases.length;
        while (i < count) {
          klass = this.cases[i][0];
          if (x.isa(klass)) {
            op = klass.unapply(x);
            if (op.isDefined) {
              return this.cases[i][1](op.get);
            }
          }
          i = i + 1;
        }
      }
      throw FP.MatchError(x);
    }
  });
  
  /************ Option **************/
  FP.Option = MathJax.Object.Subclass({});

  FP.Option.Some = FP.Option.Subclass({
    Init: function (value) {
      this.get = value;
    },
    isEmpty: false,
    isDefined: true,
    getOrElse: function (ignore) { return this.get; },
    flatMap: function (k) {
      return k(this.get);
    },
    map: function (f) {
      return FP.Option.Some(f(this.get));
    },
    foreach: function (f) {
      f(this.get);
    },
    toString: function () {
      return "Some(" + this.get + ")";
    }
  }, {
    unapply: function (x) { return FP.Option.Some(x.get); }
  });

  FP.Option.None = FP.Option.Subclass({
    Init: function () {},
    isEmpty: true,
    isDefined: false,
    getOrElse: function (value) { return value; },
    flatMap: function (k) { return this; },
    foreach: function (f) {},
    map: function (k) { return this; },
    toString: function () { return "None"; }
  }, {
    unapply: function (x) { return FP.Option.Some(x); }
  });

  FP.Option.Augment({}, {
    empty: FP.Option.None()
  });


  /************ List **************/
  FP.List = MathJax.Object.Subclass({});

  FP.List.Cons = FP.List.Subclass({
    Init: function (head, tail) {
      this.head = head;
      this.tail = tail;
    },
    isEmpty: false,
    at: function (index) {
      if (index < 0 || index >= this.length()) {
        throw Error("no such element at " + index + ". index must be lower than " + this.length() + ".");
      }
      var t = this;
      for (var i = 0; i < index; i++) {
        t = t.tail;
      }
      return t.head;
    },
    length: function () {
      var t = this;
      var l = 0;
      while (!t.isEmpty) {
        l++;
        t = t.tail;
      }
      return l;
    },
    prepend: function (element) {
      return FP.List.Cons(element, this);
    },
    append: function (element) {
      var result = FP.List.Cons(element, FP.List.empty);
      this.reverse().foreach(function (e) {
        result = FP.List.Cons(e, result);
      });
      return result;
    },
    concat: function (that) {
      var result = that;
      this.reverse().foreach(function (e) {
        result = FP.List.Cons(e, result);
      });
      return result;
    },
    foldLeft: function (x0, f) {
      var r, c;
      r = f(x0, this.head);
      c = this.tail;
      while (!c.isEmpty) {
        r = f(r, c.head);
        c = c.tail;
      }
      return r;
    },
    foldRight: function (x0, f) {
      if (this.tail.isEmpty) {
        return f(this.head, x0);
      } else {
        return f(this.head, this.tail.foldRight(x0, f));
      }
    },
    map: function (f) {
      return FP.List.Cons(f(this.head), this.tail.map(f));
    },
    flatMap: function (k) {
      return k(this.head).concat(this.tail.flatMap(k));
    },
    foreach: function (f) {
      var e = this;
      while (!e.isEmpty) {
        f(e.head);
        e = e.tail;
      }
    },
    reverse: function () {
      var r = FP.List.empty;
      this.foreach(function (c) {
        r = FP.List.Cons(c, r);
      });
      return r;
    },
    mkString: function () {
      var open, delim, close;
      switch (arguments.length) {
        case 0:
          open = delim = close = "";
          break;
        case 1:
          delim = arguments[0];
          open = close = "";
          break;
        case 2:
          open = arguments[0];
          delim = arguments[1];
          close = "";
          break;
        default:
          open = arguments[0];
          delim = arguments[1];
          close = arguments[2];
          break;
      }
      var desc, nxt;
      desc = open + this.head.toString();
      nxt = this.tail;
      while (nxt.isa(FP.List.Cons)) {
        desc += delim + nxt.head.toString(); 
        nxt = nxt.tail;
      }
      desc += close;
      return desc;
    },
    toString: function () {
      return this.mkString("[", ", ", "]");
    }
  }, {
    unapply: function (x) { return FP.Option.Some([x.head, x.tail]); }
  });

  FP.List.Nil = FP.List.Subclass({
    isEmpty: true,
    at: function (index) {
      throw Error("cannot get element from an empty list.");
    },
    length: function () { return 0; },
    prepend: function (element) {
      return FP.List.Cons(element, FP.List.empty);
    },
    append: function (element) {
      return FP.List.Cons(element, FP.List.empty);
    },
    concat: function (that) {
      return that;
    },
    foldLeft: function (x0, f) { return x0; },
    foldRight: function (x0, f) { return x0; },
    flatMap: function (f) { return this; },
    map: function (f) { return this; },
    foreach: function (f) {},
    reverse: function () { return this; },
    mkString: function () {
      switch (arguments.length) {
        case 0:
        case 1:
          return "";
        case 2:
          return arguments[0]
        default:
          return arguments[0]+arguments[2];
      }
    },
    toString: function () { return '[]'; }
  }, {
    unapply: function (x) { return FP.Option.Some(x); }
  });

  FP.List.Augment({}, {
    empty: FP.List.Nil(),
    fromArray: function (as) {
      var list, i;
      list = FP.List.empty;
      i = as.length - 1;
      while (i >= 0) {
        list = FP.List.Cons(as[i], list);
        i -= 1;
      }
      return list;
    }
  });


  /************ MatchError **************/
  FP.MatchError = MathJax.Object.Subclass({
    Init: function (obj) { this.obj = obj; },
  //  getMessage: function () {
  //    if (this.obj === null) {
  //      return "null"
  //    } else {
  //      return obj.toString() + " (of class " + obj. + ")"
  //    }
  //  }
    toString: function () { return "MatchError(" + this.obj + ")"; }
  });


  /************ OffsetPosition **************/
  FP.OffsetPosition = MathJax.Object.Subclass({
    Init: function (source, offset) {
      // assert(source.length >= offset)
      this.source = source;
      if (offset === undefined) { this.offset = 0; } else { this.offset = offset; } 
      this._index = null;
      this._line = null;
    },
    index: function () {
      if (this._index !== null) { return this._index; }
      this._index = [];
      this._index.push(0);
      var i = 0;
      while (i < this.source.length) {
        if (this.source.charAt(i) === '\n') { this._index.push(i + 1); }
        i += 1;
      }
      this._index.push(this.source.length);
      return this._index;
    },
    line: function () {
      var lo, hi, mid;
      if (this._line !== null) { return this._line; }
      lo = 0;
      hi = this.index().length - 1;
      while (lo + 1 < hi) {
        mid = (hi + lo) >> 1;
        if (this.offset < this.index()[mid]) {
          hi = mid;
        } else {
          lo = mid;
        }
      }
      this._line = lo + 1;
      return this._line;
    },
    column: function () {
      return this.offset - this.index()[this.line() - 1] + 1;
    },
    lineContents: function () {
      var i, l;
      i = this.index();
      l = this.line();
      return this.source.substring(i[l - 1], i[l]);
    },
    toString: function () { return this.line().toString() + '.' + this.column(); },
    longString: function () {
      var desc, i;
      desc = this.lineContents() + '\n';
      i = 0;
      while (i < this.column()) {
        if (this.lineContents().charAt(i) === '\t') {
          desc += '\t';
        } else {
          desc += ' ';
        }
        i += 1;
      }
      desc += '^';
      return desc;
    },
    isLessThan: function (that) {
      if (that.isa(FP.OffsetPosition)) {
        return this.offset < that.offset;
      } else {
        return (
          this.line() < that.line() || 
          (this.line() === that.line() && this.column() < that.column())
        );
      }
    } 
  });


  /************ StringReader **************/
  FP.StringReader = MathJax.Object.Subclass({
    Init: function (source, offset, context) {
      this.source = source;
      this.offset = offset;
      this.context = context;
    },
    first: function () {
      if (this.offset < this.source.length) {
        return this.source.charAt(this.offset);
      } else {
        return FP.StringReader.EofCh;
      }
    },
    rest: function () {
      if (this.offset < this.source.length) {
        return FP.StringReader(this.source, this.offset + 1, this.context);
      } else {
        return this;
      }
    },
    pos: function () { return FP.OffsetPosition(this.source, this.offset); },
    atEnd: function () { return this.offset >= this.source.length; },
    drop: function (n) {
      var r, count;
      r = this;
      count = n;
      while (count > 0) {
        r = r.rest();
        count -= 1;
      }
      return r;
    }
  }, {
    EofCh: '\x03'
  });


  /************ Parsers **************/
  FP.Parsers = MathJax.Object.Subclass({}, {
    parse: function (p, input) {
      return p.apply(input);
    },
    parseAll: function (p, input) {
      return p.andl(function () { return FP.Parsers.eos(); }).apply(input);
    },
    parseString: function (p, str) {
      var input = FP.StringReader(str, 0, { lastNoSuccess: undefined });
      return FP.Parsers.parse(p, input);
    },
    parseAllString: function (p, str) {
      var input = FP.StringReader(str, 0, { lastNoSuccess: undefined });
      return FP.Parsers.parseAll(p, input);
    },
    _handleWhiteSpace: function (input) {
      var whiteSpaceRegex = input.context.whiteSpaceRegex;
      var source = input.source;
      var offset = input.offset;
      var m = whiteSpaceRegex.exec(source.substring(offset, source.length));
      if (m !== null) {
        return offset + m[0].length;
      } else {
        return offset;
      }
    },
    literal: function (str) {
      return FP.Parsers.Parser(function (input) {
        var source, offset, start, i, j, found;
        source = input.source;
        offset = input.offset;
        start = FP.Parsers._handleWhiteSpace(input);
        i = 0;
        j = start;
        while (i < str.length && j < source.length && 
            str.charAt(i) === source.charAt(j)) {
          i += 1;
          j += 1;
        }
        if (i === str.length) {
          return FP.Parsers.Success(str, input.drop(j - offset));
        } else {
          if (start === source.length) {
            found = "end of source";
          } else {
            found = "`" + source.charAt(start) + "'";
          }
          return FP.Parsers.Failure(
            "`" + str + "' expected but " + found + " found",
            input.drop(start - offset)
          );
        }
      });
    },
    regex: function (rx /* must start with ^ */) {
      if (rx.toString().substring(0, 2) !== "/^") {
        throw ("regex must start with `^' but " + rx);
      }
      return FP.Parsers.Parser(function (input) {
        var source, offset, m, found;
        source = input.source;
        offset = input.offset;
        m = rx.exec(source.substring(offset, source.length));
        if (m !== null) {
          return FP.Parsers.Success(m[0], input.drop(m[0].length));
        } else {
          if (offset === source.length) {
            found = "end of source";
          } else {
            found = "`" + source.charAt(offset) + "'";
          }
          return FP.Parsers.Failure(
            "string matching regex " + rx + " expected but " + found + " found",
            input
          );
        }
      });
    },
    regexLiteral: function (rx /* must start with ^ */) {
      if (rx.toString().substring(0, 2) !== "/^") {
        throw ("regex must start with `^' but " + rx);
      }
      return FP.Parsers.Parser(function (input) {
        var source, offset, start, m, found;
        source = input.source;
        offset = input.offset;
        start = FP.Parsers._handleWhiteSpace(input);
        m = rx.exec(source.substring(start, source.length));
        if (m !== null) {
          return FP.Parsers.Success(m[0], input.drop(start + m[0].length - offset));
        } else {
          if (start === source.length) {
            found = "end of source";
          } else {
            found = "`" + source.charAt(start) + "'";
          }
          return FP.Parsers.Failure(
            "string matching regex " + rx + " expected but " + found + " found",
            input.drop(start - offset)
          );
        }
      });
    },
    eos: function () {
      return FP.Parsers.Parser(function (input) {
        var source, offset, start;
        source = input.source;
        offset = input.offset;
        start = FP.Parsers._handleWhiteSpace(input);
        if (source.length === start) {
          return FP.Parsers.Success("", input);
        } else {
          return FP.Parsers.Failure("end of source expected but `" + 
            source.charAt(start) + "' found", input);
        }
      });
    },
    commit: function (/*lazy*/ p) {
      return FP.Parsers.Parser(function (input) {
        var res = p()(input);
        return (FP.Matcher()
          .Case(FP.Parsers.Success, function (x) { return res; })
          .Case(FP.Parsers.Error, function (x) { return res; })
          .Case(FP.Parsers.Failure, function (x) {
            return FP.Parsers.Error(x[0], x[1]);
          }).match(res)
        );
      });
    },
    //elem: function (kind, p)
    elem: function (e) { return FP.Parsers.accept(e).named('"' + e + '"'); },
    accept: function (e) {
      return FP.Parsers.acceptIf(
        function (x) { return x === e; },
        function (x) { return "`" + e + "' expected but `" + x + "' found"; }
      );
    },
    acceptIf: function (p, err) {
      return FP.Parsers.Parser(function (input) {
        if (p(input.first())) {
          return FP.Parsers.Success(input.first(), input.rest());
        } else {
          return FP.Parsers.Failure(err(input.first()), input);
        }
      });
    },
    //acceptMatch: function (expected, f)
    //acceptSeq: function (es)
    failure: function (msg) {
      return FP.Parsers.Parser(function (input) {
        return FP.Parsers.Failure(msg, input);
      });
    },
    err: function (msg) {
      return FP.Parsers.Parser(function (input) {
        return FP.Parsers.Error(msg, input);
      });
    },
    success: function (v) {
      return FP.Parsers.Parser(function (input) {
        return FP.Parsers.Success(v, input);
      });
    },
    log: function (/*lazy*/ p, name) {
      return FP.Parsers.Parser(function (input) {
        console.log("trying " + name + " at " + input);
        var r = p().apply(input);
        console.log(name + " --> " + r);
        return r;
      });
    },
    rep: function (/*lazy*/ p) {
      var s = FP.Parsers.success(FP.List.empty);
      return FP.Parsers.rep1(p).or(function () { return s; });
    },
    rep1: function (/*lazy*/ p) {
      return FP.Parsers.Parser(function (input) {
        var elems, i, p0, res;
        elems = [];
        i = input;
        p0 = p();
        res = p0.apply(input);
        if (res.isa(FP.Parsers.Success)) {
          while (res.isa(FP.Parsers.Success)) {
            elems.push(res.result);
            i = res.next;
            res = p0.apply(i);
          }
          return FP.Parsers.Success(FP.List.fromArray(elems), i);
        } else {
          return res;
        }
      });
    },
    //rep1: function (/*lazy*/ first, /*lazy*/ p)
    repN: function (num, /*lazy*/ p) {
      if (num === 0) {
        return FP.Parsers.success(FP.List.empty);
      }
      return FP.Parsers.Parser(function (input) {
        var elems, i, p0, res;
        elems = [];
        i = input;
        p0 = p();
        res = p0.apply(i);
        while (res.isa(FP.Parsers.Success)) {
          elems.push(res.result);
          i = res.next;
          if (num === elems.length) {
            return FP.Parsers.Success(FP.List.fromArray(elems), i);
          }
          res = p0.apply(i);
        }
        return res; // NoSuccess
      });
    },
    repsep: function (/*lazy*/ p, /*lazy*/ q) {
      var s = FP.Parsers.success(FP.List.empty);
      return FP.Parsers.rep1sep(p, q).or(function () { return s; });
    },
    rep1sep: function (/*lazy*/ p, /*lazy*/ q) {
      return p().and(FP.Parsers.rep(q().andr(p))).to(function (res) {
        return FP.List.Cons(res.head, res.tail);
      });
    },
  //  chainl1: function (/*lazy*/ p, /*lazy*/ q) {
  //    return this.chainl1(p, p, q)
  //  },
    chainl1: function (/*lazy*/ first, /*lazy*/ p, /*lazy*/ q) {
      return first().and(FP.Parsers.rep(q().and(p))).to(function (res) {
        return res.tail.foldLeft(res.head, function (a, fb) { return fb.head(a, fb.tail); });
      });
    },
    chainr1: function (/*lazy*/ p, /*lazy*/ q, combine, first) {
      return p().and(this.rep(q().and(p))).to(function (res) {
        return FP.List.Cons(FP.Parsers.Pair(combine, res.head),
          res.tail).foldRight(first, function (fa, b) { return fa.head(fa.tail, b); }
          );
      });
    },
    opt: function (/*lazy*/ p) {
      return p().to(function (x) {
        return FP.Option.Some(x);
      }).or(function () {
        return FP.Parsers.success(FP.Option.empty);
      });
    },
    not: function (/*lazy*/ p) {
      return FP.Parsers.Parser(function (input) {
        var r = p().apply(input);
        if (r.successful) {
          return FP.Parsers.Failure("Expected failure", input);
        } else {
          return FP.Parsers.Success(FP.Option.empty, input);
        }
      });
    },
    guard: function (/*lazy*/ p) {
      return FP.Parsers.Parser(function (input) {
        var r = p().apply(input);
        if (r.successful) {
          return FP.Parsers.Success(r.result, input);
        } else {
          return r;
        }
      });
    },
    //positioned: function (/*lazy*/ p)
    //phrase: function (p)
    mkList: function (pair) { return FP.List.Cons(pair.head, pair.tail); },
    fun: function (x) { return function () { return x; }; },
    lazyParser: function (x) {
      var lit, r;
      if (x instanceof String || (typeof x) === "string") {
        lit = FP.Parsers.literal(x);
        return function () { return lit; };
      } else if (x instanceof Function) {
        // x is deemed to be a function which has the return value as Parser. 
        return x;
      } else if (x instanceof Object) {
        if("isa" in x && x.isa(FP.Parsers.Parser)) {
          return function () { return x; };
        } else if (x instanceof RegExp) {
          r = FP.Parsers.regexLiteral(x);
          return function () { return r; };
        } else {
          return FP.Parsers.err("unhandlable type");
        }
      } else {
        return FP.Parsers.err("unhandlable type");
      }
    },
    seq: function () {
      var count, parser, i;
      count = arguments.length;
      if (count === 0) { return FP.Parsers.err("at least one element must be specified"); }
      parser = FP.Parsers.lazyParser(arguments[0])();
      i = 1;
      while (i < count) {
        parser = parser.and(FP.Parsers.lazyParser(arguments[i]));
        i += 1;
      }
      return parser;
    },
    or: function () {
      var count, parser, i;
      count = arguments.length;
      if (count === 0) { return FP.Parsers.err("at least one element must be specified"); }
      parser = FP.Parsers.lazyParser(arguments[0])();
      i = 1;
      while (i < count) {
        parser = parser.or(FP.Parsers.lazyParser(arguments[i]));
        i += 1;
      }
      return parser;
    }
  });


  /************ Pair **************/
  FP.Parsers.Pair = MathJax.Object.Subclass({
    Init: function (head, tail) {
      this.head = head;
      this.tail = tail;
    },
    toString: function () { return '(' + this.head + '~' + this.tail + ')'; }
  }, {
    unapply: function (x) { return FP.Option.Some([x.head, x.tail]); }
  });


  /************ ParseResult **************/
  FP.Parsers.ParseResult = MathJax.Object.Subclass({
    Init: function () {},
    isEmpty: function () { return !this.successful; },
    getOrElse: function (/*lazy*/ defaultValue) {
      if (this.isEmpty) { return defaultValue(); } else { return this.get(); }
    } 
  });


  /************ Success **************/
  FP.Parsers.Success = FP.Parsers.ParseResult.Subclass({
    Init: function (result, next) {
      this.result = result;
      this.next = next;
    },
    map: function (f) { return FP.Parsers.Success(f(this.result), this.next); },
    mapPartial: function (f, err) {
      try {
        return FP.Parsers.Success(f(this.result), this.next);
      } catch (e) {
        if ("isa" in e && e.isa(FP.MatchError)) {
          return FP.Parsers.Failure(err(this.result), this.next);
        } else {
          throw e;
        }
      }
    },
    flatMapWithNext: function (f) { return f(this.result).apply(this.next); },
    append: function (/*lazy*/ a) { return this; },
    get: function () { return this.result; },
    successful: true,
    toString: function () { return '[' + this.next.pos() + '] parsed: ' + this.result; }
  }, {
    unapply: function (x) { return FP.Option.Some([x.result, x.next]); }
  });


  /************ NoSuccess **************/
  FP.Parsers.NoSuccess = FP.Parsers.ParseResult.Subclass({
    Init: function () {},
    _setLastNoSuccess: function () {
      var context = this.next.context;
      if (context.lastNoSuccess === undefined || !this.next.pos().isLessThan(context.lastNoSuccess.next.pos())) {
        context.lastNoSuccess = this;
      }
    },
    map: function (f) { return this; },
    mapPartial: function (f, error) { return this; },
    flatMapWithNext: function (f) { return this; },
    get: function () { return FP.Parsers.error("No result when parsing failed"); },
    successful: false
  });


  /************ Failure **************/
  FP.Parsers.Failure = FP.Parsers.NoSuccess.Subclass({
    Init: function (msg, next) {
      this.msg = msg;
      this.next = next;
      this._setLastNoSuccess();
    },
    append: function (/*lazy*/ a) {
      var alt = a();
      if (alt.isa(FP.Parsers.Success)) {
        return alt;
      } else if (alt.isa(FP.Parsers.NoSuccess)) {
        if (alt.next.pos().isLessThan(this.next.pos())) {
          return this;
        } else {
          return alt;
        }
      } else {
        throw FP.MatchError(alt);
      }
    },
    toString: function () { return ('[' + this.next.pos() + '] failure: ' + 
      this.msg + '\n\n' + this.next.pos().longString()); }
  }, {
    unapply: function (x) { return FP.Option.Some([x.msg, x.next]); }
  });


  /************ Error **************/
  FP.Parsers.Error = FP.Parsers.NoSuccess.Subclass({
    Init: function (msg, next) {
      this.msg = msg;
      this.next = next;
      this._setLastNoSuccess();
    },
    append: function (/*lazy*/ a) { return this; },
    toString: function () { return ('[' + this.next.pos() + '] error: ' + 
      this.msg + '\n\n' + this.next.pos().longString()); }
  }, {
    unapply: function (x) { return FP.Option.Some([x.msg, x.next]); }
  });


  /************ Parser **************/
  FP.Parsers.Parser = MathJax.Object.Subclass({
    Init: function (f) { this.apply = f; },
    name: '',
    named: function (name) { this.name = name; return this; },
    toString: function () { return 'Parser (' + this.name + ')'; },
    flatMap: function (f) {
      var app = this.apply;
      return FP.Parsers.Parser(function (input) {
        return app(input).flatMapWithNext(f);
      });
    },
    map: function (f) {
      var app = this.apply;
      return FP.Parsers.Parser(function (input) {
        return app(input).map(f);
      });
    },
    append: function (/*lazy*/ p) {
      var app = this.apply;
      return FP.Parsers.Parser(function (input) {
        return app(input).append(function () {
          return p().apply(input);
        });
      });
    },
    and: function (/*lazy*/ p) {
      return this.flatMap(function (a) {
        return p().map(function (b) {
          return FP.Parsers.Pair(a, b);
        });
      }).named('~');
    },
    andr: function (/*lazy*/ p) {
      return this.flatMap(function (a) {
        return p().map(function (b) {
          return b;
        });
      }).named('~>');
    },
    andl: function (/*lazy*/ p) {
      return this.flatMap(function (a) {
        return p().map(function (b) {
          return a;
        });
      }).named('<~');
    },
    or: function (/*lazy*/ q) { return this.append(q).named("|"); },
    andOnce: function (/*lazy*/ p) {
      var flatMap = this.flatMap;
      return FP.Parsers.OnceParser(function () {
        return flatMap(function (a) {
          return FP.Parsers.commit(p).map(function (b) {
            return FP.Parsers.Pair(a, b);
          });
        }).named('~!');
      });
    },
    longestOr: function (/*lazy*/ q0) {
      var app = this.apply;
      return FP.Parsers.Parser(function (input) {
        var res1, res2;
        res1 = app(input);
        res2 = q0()(input);
        if (res1.successful) {
          if (res2.successful) {
            if (res2.next.pos().isLessThan(res1.next.pos())) {
              return res1;
            } else {
              return res2;
            }
          } else {
            return res1;
          }
        } else if (res2.successful) {
          return res2;
        } else if (res1.isa(FP.Parsers.Error)) {
          return res1;
        } else {
          if (res2.next.pos().isLessThan(res1.next.pos())) {
            return res1;
          } else {
            return res2;
          }
        }
      }).named("|||");
    },
    to: function (f) { return this.map(f).named(this.toString() + '^^'); },
    ret: function (/*lazy*/ v) {
      var app = this.apply;
      return FP.Parsers.Parser(function (input) {
        return app(input).map(function (x) { return v(); });
      }).named(this.toString() + "^^^");
    },
    toIfPossible: function (f, error) {
      if (error === undefined) {
        error = function (r) { return "Constructor function not defined at " + r; };
      }
      var app = this.apply;
      return FP.Parsers.Parser(function (input) {
        return app(input).mapPartial(f, error);
      }).named(this.toString() + "^?");
    },
    into: function (fq) { return this.flatMap(fq); },
    rep: function () {
      var p = this;
      return FP.Parsers.rep(function () { return p; });
    },
    chain: function (/*lazy*/ sep) {
      var p, lp;
      p = this;
      lp = function () { return p; };
      return FP.Parsers.chainl1(lp, lp, sep);
    },
    rep1: function () {
      var p = this;
      return FP.Parsers.rep1(function () { return p; });
    },
    opt: function () {
      var p = this;
      return FP.Parsers.opt(function () { return p; });
    }
  });


  /************ OnceParser **************/
  FP.Parsers.OnceParser = FP.Parsers.Parser.Subclass({
    Init: function (f) { this.apply = f; },
    and: function (p) {
      var flatMap = this.flatMap;
      return FP.Parsers.OnceParser(function () {
        return flatMap(function (a) {
          return FP.Parsers.commit(p).map(function (b) {
            return FP.Parsers.Pair(a, b);
          });
        });
      }).named('~');
    }
  });
  
  MathJax.Hub.Startup.signal.Post("Functional Programming library Ready");
})();

MathJax.Extension.xypic = {
  version: "0.1",
  constants: {
    whiteSpaceRegex: /^(\s+|%[^\r\n]*(\r\n|\r|\n)?)+/,
    unsupportedBrowserErrorMessage: "Unsupported Browser. Please open with Firefox/Safari/Chrome/Opera"
  },
  signalHandler: {
    signals: [],
    hookedSignals: [],
    chains: [],
    chainSignal: function (successor, predecessors) {
      for (var i = 0; i < predecessors.length; i++) {
        MathJax.Extension.xypic.signalHandler.addSignal(predecessors[i]);
      }
      MathJax.Extension.xypic.signalHandler.chains.push({succ:successor, pred:predecessors});
    },
    addSignal: function (signal) {
      var signals = MathJax.Extension.xypic.signalHandler.signals;
      for (var i = 0; i < signals.length; i++) {
        if (signals[i] === signal) {
          return;
        }
      }
      MathJax.Extension.xypic.signalHandler.signals.push(signal);
      var handler = MathJax.Extension.xypic.signalHandler.handleSignal(signal);
      MathJax.Hub.Register.StartupHook(signal, handler);
    },
    handleSignal: function (signal) {
      return function () {
        MathJax.Extension.xypic.signalHandler.hookedSignals.push(signal);
        MathJax.Extension.xypic.signalHandler.handleChains();
      }
    },
    handleChains: function () {
      var i = 0;
      var chains = MathJax.Extension.xypic.signalHandler.chains;
      var remainingChains = [];
      var invokableSignals = [];
      while (i < chains.length) {
        var c = chains[i];
        var pred = c.pred;
        var invokable = true;
        for (var j = 0; j < pred.length; j++) {
          var p = pred[j];
          if (!MathJax.Extension.xypic.signalHandler.listenedSignal(p)) {
            invokable = false;
            break;
          }
        }
        if (invokable) {
          invokableSignals.push(c.succ);
        } else {
          remainingChains.push(c);
        }
        i++;
      }
      MathJax.Extension.xypic.signalHandler.chains = remainingChains;
      for (i = 0; i < invokableSignals.length; i++) {
        MathJax.Hub.Startup.signal.Post(invokableSignals[i]);
      }
    },
    listenedSignal: function (signal) {
      var signals = MathJax.Extension.xypic.signalHandler.hookedSignals;
      for (var i = 0; i < signals.length; i++) {
        if (signals[i] === signal) {
          return true;
        }
      }
      return false;
    }
  }
}

// "TeX Xy-pic" depends on "Functional Programming library" and "TeX Jax".
MathJax.Extension.xypic.signalHandler.chainSignal("TeX Xy-pic Require", ["Functional Programming library Ready", "TeX Jax Ready"]);

// "HTML-CSS Xy-pic Config" depends on "TeX Xy-pic" and "HTML-CSS Jax".
MathJax.Extension.xypic.signalHandler.chainSignal("HTML-CSS Xy-pic Config Require", ["TeX Xy-pic Ready", "HTML-CSS Jax Ready"]);

// "SVG Xy-pic Config" depends on "TeX Xy-pic" and "SVG Jax".
MathJax.Extension.xypic.signalHandler.chainSignal("SVG Xy-pic Config Require", ["TeX Xy-pic Ready", "SVG Jax Ready"]);

// "Device-Independent Xy-pic" depends on "TeX Xy-pic" OR "SVG Jax".
MathJax.Extension.xypic.signalHandler.chainSignal("Device-Independent Xy-pic Require", ["HTML-CSS Xy-pic Config Ready"]);
MathJax.Extension.xypic.signalHandler.chainSignal("Device-Independent Xy-pic Require", ["SVG Xy-pic Config Ready"]);

// "HTML-CSS Xy-pic" depends on "HTML-CSS Xy-pic Config" and "Device-Independent Xy-pic".
MathJax.Extension.xypic.signalHandler.chainSignal("HTML-CSS Xy-pic Require", ["HTML-CSS Xy-pic Config Ready", "Device-Independent Xy-pic Ready"]);

// "SVG Xy-pic" depends on "SVG Xy-pic Config" and "Device-Independent Xy-pic".
MathJax.Extension.xypic.signalHandler.chainSignal("SVG Xy-pic Require", ["SVG Xy-pic Config Ready", "Device-Independent Xy-pic Ready"]);

MathJax.Hub.Register.StartupHook("TeX Xy-pic Require",function () {
  var FP = MathJax.Extension.fp;
  var MML = MathJax.ElementJax.mml;
  var TEX = MathJax.InputJax.TeX;
  var TEXDEF = TEX.Definitions;
  var xypic = MathJax.Extension.xypic;
  var AST = xypic.AST = MathJax.Object.Subclass({});
  
  MathJax.Hub.Insert(TEXDEF, {
    macros: {
      //hole: ['Macro', '{\\bbox[3pt]{}}']
      hole: ['Macro', '{\\style{visibility:hidden}{x}}'],
      objectstyle: ['Macro', '\\textstyle'],
      labelstyle: ['Macro', '\\scriptstyle'],
      twocellstyle: ['Macro', '\\scriptstyle'],
      xybox: 'Xybox',
      xymatrix: 'Xymatrix',
      newdir: 'XypicNewdir',
      includegraphics: 'Xyincludegraphics'
    },
    environment: {
      xy: ['ExtensionEnv', null, 'XYpic']
    }
  });
  
  // override MathJax.InputJax.TeX.formatError function to display parse error.
  var tex_formatError = TEX.formatError;
  TEX.formatError = function (err, math, displaystyle, script) {
    if (err.xyjaxError !== undefined) {
      return err.toMML();
    } else {
      return tex_formatError(err, math, displaystyle, script);
    }
  }
  
  xypic.memoize = function (object, funcName) {
    var func = object[funcName];
    var memo = function () {
        var value = func.call(this);
        var constFunc = function () {
            return value;
        }
        constFunc.reset = reset;
        object[funcName] = constFunc;
        return value;
    }
    var reset = function () {
        object[funcName] = memo;
    }
    memo.reset = reset;
    reset();
  };
  
  AST.xypic = MML.mbase.Subclass({
    Init: function (cmd) {
      this.data = [];
      this.cmd = cmd;
    },
    type: "xypic",
    inferRow: false,
    defaults: {
      mathbackground: MML.INHERIT,
      mathcolor: MML.INHERIT,
      notation: MML.NOTATION.LONGDIV,
      texClass: MML.TEXCLASS.ORD
    },
    setTeXclass: MML.mbase.setSeparateTeXclasses,
    toString: function () { return this.type + "(" + this.cmd + ")"; }
  });
  
  AST.xypic.newdir = MML.mbase.Subclass({
    Init: function (cmd) {
      this.data = [];
      this.cmd = cmd;
    },
    type: "newdir",
    inferRow: false,
    defaults: {
      mathbackground: MML.INHERIT,
      mathcolor: MML.INHERIT,
      notation: MML.NOTATION.LONGDIV,
      texClass: MML.TEXCLASS.ORD
    },
    setTeXclass: MML.mbase.setSeparateTeXclasses,
    toString: function () { return this.type + "(" + this.cmd + ")"; }
  });
  
  AST.xypic.includegraphics = MML.mbase.Subclass({
    Init: function (cmd) {
      this.data = [];
      this.cmd = cmd;
    },
    type: "includegraphics",
    inferRow: false,
    defaults: {
      mathbackground: MML.INHERIT,
      mathcolor: MML.INHERIT,
      notation: MML.NOTATION.LONGDIV,
      texClass: MML.TEXCLASS.ORD
    },
    setTeXclass: MML.mbase.setSeparateTeXclasses,
    toString: function () { return this.type + "(" + this.cmd + ")"; }
  });
  
  // <pos-decor> ::= <pos> <decor>
  AST.PosDecor = MathJax.Object.Subclass({
    Init: function (pos, decor) {
      this.pos = pos;
      this.decor = decor;
    },
    toString: function () {
      return this.pos.toString() + " " + this.decor;
    }
  });
  
  // <pos>
  AST.Pos = MathJax.Object.Subclass({});
  // <pos> ::= <coord> <pos2>*
  AST.Pos.Coord = MathJax.Object.Subclass({
    Init: function (coord, pos2s) {
      this.coord = coord;
      this.pos2s = pos2s;
    },
    toString: function () {
      return this.coord.toString() + " " + this.pos2s.mkString(" ");
    }
  });
  // <pos2> ::= '+' <coord>
  AST.Pos.Plus = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return "+(" + this.coord + ")";
    }
  });
  // <pos2> ::= '-' <coord>
  AST.Pos.Minus = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return "-(" + this.coord + ")";
    }
  });
  // <pos2> ::= '!' <coord>
  AST.Pos.Skew = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return "!(" + this.coord + ")";
    }
  });
  // <pos2> ::= '.' <coord>
  AST.Pos.Cover = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return ".(" + this.coord + ")";
    }
  });
  // <pos2> ::= ',' <coord>
  AST.Pos.Then = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return ",(" + this.coord + ")";
    }
  });
  // <pos2> ::= ';' <coord>
  AST.Pos.SwapPAndC = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return ";(" + this.coord + ")";
    }
  });
  // <pos2> ::= ':' <coord>
  AST.Pos.SetBase = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return ":(" + this.coord + ")";
    }
  });
  // <pos2> ::= '::' <coord>
  AST.Pos.SetYBase = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return "::(" + this.coord + ")";
    }
  });
  // <pos2> ::= '**' <object>
  AST.Pos.ConnectObject = MathJax.Object.Subclass({
    Init: function (object) {
      this.object = object;
    },
    toString: function () {
      return "**(" + this.object + ")";
    }
  });
  // <pos2> ::= '*' <object>
  AST.Pos.DropObject = MathJax.Object.Subclass({
    Init: function (object) {
      this.object = object;
    },
    toString: function () {
      return "*(" + this.object + ")";
    }
  });
  // <pos2> ::= '?' <place>
  AST.Pos.Place = MathJax.Object.Subclass({
    Init: function (place) {
      this.place = place;
    },
    toString: function () {
      return "?(" + this.place + ")";
    }
  });
  // <pos2> ::= '@+' <coord>
  AST.Pos.PushCoord = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return "@+(" + this.coord + ")";
    }
  });
  // <pos2> ::= '@-' <coord>
  AST.Pos.EvalCoordThenPop = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return "@-(" + this.coord + ")";
    }
  });
  // <pos2> ::= '@=' <coord>
  AST.Pos.LoadStack = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return "@=(" + this.coord + ")";
    }
  });
  // <pos2> ::= '@@' <coord>
  AST.Pos.DoCoord = MathJax.Object.Subclass({
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return "@@(" + this.coord + ")";
    }
  });
  // <pos2> ::= '@i'
  AST.Pos.InitStack = MathJax.Object.Subclass({
    Init: function () {
    },
    toString: function () {
      return "@i";
    }
  });
  // <pos2> ::= '@('
  AST.Pos.EnterFrame = MathJax.Object.Subclass({
    Init: function () {
    },
    toString: function () {
      return "@(";
    }
  });
  // <pos2> ::= '@)'
  AST.Pos.LeaveFrame = MathJax.Object.Subclass({
    Init: function () {
    },
    toString: function () {
      return "@)";
    }
  });
  // <pos2> ::= '=' '"' <id> '"'
  AST.Pos.SavePos = MathJax.Object.Subclass({
    Init: function (id) {
      this.id = id;
    },
    toString: function () {
      return '="' + this.id + '"';
    }
  });
  // <pos2> ::= '=' <coord> '"' <id> '"'
  AST.Pos.SaveMacro = MathJax.Object.Subclass({
    Init: function (macro, id) {
      this.macro = macro;
      this.id = id;
    },
    toString: function () {
      return "=(" + this.macro + ' "' + this.id + '")';
    }
  });
  // <pos2> ::= '=:' '"' <id> '"'
  AST.Pos.SaveBase = MathJax.Object.Subclass({
    Init: function (id) {
      this.id = id;
    },
    toString: function () {
      return '=:"' + this.id + '"';
    }
  });
  // <pos2> ::= '=@' '"' <id> '"'
  AST.Pos.SaveStack = MathJax.Object.Subclass({
    Init: function (id) {
      this.id = id;
    },
    toString: function () {
      return '=@"' + this.id + '"';
    }
  });
  
  // <coord> 
  AST.Coord = MathJax.Object.Subclass({});
  // <coord> ::= <vector>
  AST.Coord.Vector = MathJax.Object.Subclass({
    Init: function (vector) {
      this.vector = vector;
    },
    toString: function () {
      return this.vector.toString();
    }
  });
  // <coord> ::= <empty> | 'c'
  AST.Coord.C = MathJax.Object.Subclass({
    toString: function () {
      return "c";
    }
  });
  // <coord> ::= 'p'
  AST.Coord.P = MathJax.Object.Subclass({
    toString: function () {
      return "p";
    }
  });
  // <coord> ::= 'x'
  AST.Coord.X = MathJax.Object.Subclass({
    toString: function () {
      return "x";
    }
  });
  // <coord> ::= 'y'
  AST.Coord.Y = MathJax.Object.Subclass({
    toString: function () {
      return "y";
    }
  });
  // <coord> ::= '"' <id> '"'
  AST.Coord.Id = MathJax.Object.Subclass({
    Init: function (id) {
      this.id = id;
    },
    toString: function () {
      return '"' + this.id + '"';
    }
  });
  // <coord> ::= '{' <pos> <decor> '}'
  AST.Coord.Group = MathJax.Object.Subclass({
    Init: function (posDecor) {
      this.posDecor = posDecor;
    },
    toString: function () {
      return '{' + this.posDecor + '}';
    }
  });
  // <coord> ::= 's' <digit>
  // <coord> ::= 's' '{' <nonnegative-number> '}'
  AST.Coord.StackPosition = MathJax.Object.Subclass({
    Init: function (number) {
      this.number = number;
    },
    toString: function () {
      return 's{' + this.number + '}';
    }
  });
  
  // coordinate for xymatrix
  // <coord> ::= '[' ('"'<prefix>'"')? <number> ',' <number> ']'
  AST.Coord.DeltaRowColumn = MathJax.Object.Subclass({
    /**
     * @param {String} prefix name of the xymatrix
     * @param {Number} dr rows below
     * @param {Number} dc columns right
     */
    Init: function (prefix, dr, dc) {
      this.prefix = prefix;
      this.dr = dr;
      this.dc = dc;
    },
    toString: function () {
      return '[' + (this.prefix === ''? '' : '"' + this.prefix + '"') + this.dr + "," + this.dc + "]";
    }
  });
  // coordinate for xymatrix
  // <coord> ::= '[' ('"'<prefix>'"')? ( 'l' | 'r' | 'u' | 'd' )* ']'
  AST.Coord.Hops = MathJax.Object.Subclass({
    /**
     * @param {String} prefix name of the xymatrix
     * @param {List[String]} hops hops
     */
    Init: function (prefix, hops) {
      this.prefix = prefix;
      this.hops = hops;
    },
    toString: function () {
      return '[' + (this.prefix === ''? '' : '"' + this.prefix + '"') + this.hops.mkString("") + "]";
    }
  });
  // coordinate for xymatrix
  // <coord> ::= '[' ('"'<prefix>'"')? ( 'l' | 'r' | 'u' | 'd' )+ <place> ']'
  AST.Coord.HopsWithPlace = MathJax.Object.Subclass({
    /**
     * @param {String} prefix name of the xymatrix
     * @param {List[String]} hops hops
     * @param {AST.Pos.Place} place place
     */
    Init: function (prefix, hops, place) {
      this.prefix = prefix;
      this.hops = hops;
      this.place = place;
    },
    toString: function () {
      return '[' + (this.prefix === ''? '' : '"' + this.prefix + '"') + this.hops.mkString("") + this.place + "]";
    }
  });
  
  // <vector>
  AST.Vector = MathJax.Object.Subclass({});
  // <vector> ::= '(' <factor> ',' <factor> ')'
  AST.Vector.InCurBase = MathJax.Object.Subclass({
    Init: function (x, y) {
      this.x = x;
      this.y = y;
    },
    toString: function () {
      return "(" + this.x + ", " + this.y + ")";
    }
  });
  // <vector> ::= '<' <dimen> ',' <dimen> '>'
  // <vector> ::= '<' <dimen> '>'
  AST.Vector.Abs = MathJax.Object.Subclass({
    Init: function (x, y) {
      this.x = x;
      this.y = y;
    },
    toString: function () {
      return "<" + this.x + ", " + this.y + ">";
    }
  });
  // <vector> ::= 'a' '(' <number> ')'
  AST.Vector.Angle = MathJax.Object.Subclass({
    Init: function (degree) {
      this.degree = degree;
    },
    toString: function () {
      return "a(" + this.degree + ")";
    }
  });
  // <vector> ::= '/' <direction> <dimen> '/'
  AST.Vector.Dir = MathJax.Object.Subclass({
    Init: function (dir, dimen) {
      this.dir = dir;
      this.dimen = dimen;
    },
    toString: function () {
      return "/" + this.dir + " " + this.dimen + "/";
    }
  });
  // <vector> ::= <corner>
  //          |   <corner> '(' <factor> ')'
  AST.Vector.Corner = MathJax.Object.Subclass({
    Init: function (corner, factor) {
      this.corner = corner;
      this.factor = factor;
    },
    toString: function () {
      return this.corner.toString() + "(" + this.factor + ")";
    }
  });
  
  // <corner> ::= 'L' | 'R' | 'D' | 'U'
  //          | 'CL' | 'CR' | 'CD' | 'CU'
  //          | 'LD' | 'RD' | 'LU' | 'RU'
  //          | 'E' | 'P'
  //          | 'A'
  AST.Corner = MathJax.Object.Subclass({});
  AST.Corner.L = MathJax.Object.Subclass({
    toString: function () { return "L"; }
  });
  AST.Corner.R = MathJax.Object.Subclass({
    toString: function () { return "R"; }
  });
  AST.Corner.D = MathJax.Object.Subclass({
    toString: function () { return "D"; }
  });
  AST.Corner.U = MathJax.Object.Subclass({
    toString: function () { return "U"; }
  });
  AST.Corner.CL = MathJax.Object.Subclass({
    toString: function () { return "CL"; }
  });
  AST.Corner.CR = MathJax.Object.Subclass({
    toString: function () { return "CR"; }
  });
  AST.Corner.CD = MathJax.Object.Subclass({
    toString: function () { return "CD"; }
  });
  AST.Corner.CU = MathJax.Object.Subclass({
    toString: function () { return "CU"; }
  });
  AST.Corner.LD = MathJax.Object.Subclass({
    toString: function () { return "LD"; }
  });
  AST.Corner.RD = MathJax.Object.Subclass({
    toString: function () { return "RD"; }
  });
  AST.Corner.LU = MathJax.Object.Subclass({
    toString: function () { return "LU"; }
  });
  AST.Corner.RU = MathJax.Object.Subclass({
    toString: function () { return "RU"; }
  });
  AST.Corner.NearestEdgePoint = MathJax.Object.Subclass({
    toString: function () { return "E"; }
  });
  AST.Corner.PropEdgePoint = MathJax.Object.Subclass({
    toString: function () { return "P"; }
  });
  AST.Corner.Axis = MathJax.Object.Subclass({
    toString: function () { return "A"; }
  });
  
  // <place> ::= '<' <place>
  // <place> ::= '>' <place>
  // <place> ::= '(' <factor> ')' <place>
  // <place> ::= '!' '{' <pos> '}' <slide>
  // <place> ::= <slide>
  AST.Place = MathJax.Object.Subclass({
    Init: function (shaveP, shaveC, factor, slide) {
      this.shaveP = shaveP;
      this.shaveC = shaveC;
      this.factor = factor;
      this.slide = slide;
    },
    compound: function (that) {
      return AST.Place(
        this.shaveP + that.shaveP,
        this.shaveC + that.shaveC,
        that.factor === undefined? this.factor : that.factor,
        that.slide);
    },
    toString: function () {
      var desc = "";
      for (var l = 0; l < this.shaveP; l++) {
        desc += "<";
      }
      for (var r = 0; r < this.shaveC; r++) {
        desc += ">";
      }
      if (this.factor !== undefined) {
        desc += "(" + this.factor + ")";
      }
      this.slide.dimen.foreach(function (d) {
        desc += "/" + d + "/";
      });
      return desc;
    }
  });
  AST.Place.Factor = MathJax.Object.Subclass({
    Init: function (factor) {
      this.factor = factor;
    },
    isIntercept: false,
    toString: function () {
      return this.factor.toString();
    }
  });
  AST.Place.Intercept = MathJax.Object.Subclass({
    Init: function (pos) {
      this.pos = pos;
    },
    isIntercept: true,
    toString: function () {
      return "!{" + this.pos + "}";
    }
  });
  
  // <slide> ::= <empty>
  // <slide> ::= '/' <dimen> '/'
  AST.Slide = MathJax.Object.Subclass({
    Init: function (dimen) {
      this.dimen = dimen;
    },
    toString: function () {
      return this.dimen.getOrElse("");
    }
  });
  
  
  // <object> ::= <modifier>* <objectbox>
  AST.Object = MathJax.Object.Subclass({
    Init: function (modifiers, object) {
      this.modifiers = modifiers;
      this.object = object;
    },
    dirVariant: function () { return this.object.dirVariant(); },
    dirMain: function () { return this.object.dirMain(); },
    isDir: function () { return this.object.isDir(); },
    toString: function () {
      return this.modifiers.mkString() + this.object.toString();
    }
  });
  
  // <objectbox>
  AST.ObjectBox = MathJax.Object.Subclass({
    dirVariant: function () { return undefined; },
    dirMain: function () { return undefined; },
    isDir: function () { return false; },
    isEmpty: false
  });
  // <objectbox> ::= '{' <text> '}'
  // <objectbox> ::= <TeX box> '{' <text> '}'
  AST.ObjectBox.Text = AST.ObjectBox.Subclass({
    Init: function (math) {
      this.math = math;
    },
    toString: function () { return "{" + this.math.toString() + "}"; }
  });
  AST.ObjectBox.Empty = AST.ObjectBox.Subclass({
    isEmpty: true,
    toString: function () { return "{}"; }
  });
  
  // <objectbox> ::= 'xymatrix' <xymatrix>
  AST.ObjectBox.Xymatrix = AST.ObjectBox.Subclass({
    /**
     * @param {AST.Command.Xymatrix} xymatrix xymatrix
     */
    Init: function (xymatrix) {
      this.xymatrix = xymatrix;
    },
    toString: function () { return this.xymatrix.toString(); }
  });
  
  // <objectbox> ::= '\txt' <width> <style> '{' <text> '}'
  AST.ObjectBox.Txt = AST.ObjectBox.Subclass({
    Init: function (width, textObject) {
      this.width = width;
      this.textObject = textObject;
    },
    toString: function () { return "\\txt" + this.width + "{" + this.textObject.toString() + "}"; }
  });
  AST.ObjectBox.Txt.Width = AST.ObjectBox.Subclass({
  });
  AST.ObjectBox.Txt.Width.Vector = AST.ObjectBox.Subclass({
    Init: function (vector) {
      this.vector = vector;
    },
    toString: function () { return this.vector.toString(); }
  });
  AST.ObjectBox.Txt.Width.Default = AST.ObjectBox.Subclass({
    toString: function () { return ""; }
  });
  
  // <objectbox> ::= '\object' <object>
  AST.ObjectBox.WrapUpObject = AST.ObjectBox.Subclass({
    Init: function (object) {
      this.object = object;
    },
    toString: function () { return "\\object" + this.object.toString(); }
  });
  
  // <objectbox> ::= '\composite' '{' <composite_object> '}'
  // <composite_object> ::= <object> ( '*' <object> )*
  AST.ObjectBox.CompositeObject = AST.ObjectBox.Subclass({
    Init: function (objects) {
      this.objects = objects;
    },
    toString: function () { return "\\composite{" + this.objects.mkString(" * ") + "}"; }
  });
  
  // <objectbox> ::= '\xybox' '{' <pos> <decor> '}'
  AST.ObjectBox.Xybox = AST.ObjectBox.Subclass({
    Init: function (posDecor) {
      this.posDecor = posDecor;
    },
    toString: function () { return "\\xybox{" + this.posDecor.toString() + "}"; }
  });
  
  // <objectbox> ::= '\cir' <radius> '{' <cir> '}'
  // <cir_radius> ::= <vector>
  //          | <empty>
  // <cir> ::= <diag> <orient> <diag>
  //       | <empty>
  AST.ObjectBox.Cir = AST.ObjectBox.Subclass({
    Init: function (radius, cir) {
      this.radius = radius;
      this.cir = cir;
    },
    toString: function () {
      return "\\cir"+this.radius+"{"+this.cir+"}";
    }
  });
  AST.ObjectBox.Cir.Radius = MathJax.Object.Subclass({});
  AST.ObjectBox.Cir.Radius.Vector = MathJax.Object.Subclass({
    Init: function (vector) {
      this.vector = vector;
    },
    toString: function () { return this.vector.toString(); }
  });
  AST.ObjectBox.Cir.Radius.Default = MathJax.Object.Subclass({
    toString: function () { return ""; }
  });
  AST.ObjectBox.Cir.Cir = MathJax.Object.Subclass({});
  AST.ObjectBox.Cir.Cir.Segment = MathJax.Object.Subclass({
    Init: function (startDiag, orient, endDiag) {
      this.startDiag = startDiag;
      this.orient = orient;
      this.endDiag = endDiag;
    },
    toString: function () { return this.startDiag.toString()+this.orient+this.endDiag; }
  });
  AST.ObjectBox.Cir.Cir.Full = MathJax.Object.Subclass({
    toString: function () { return ""; }
  });
  
  // <objectbox> ::= '\dir' <variant> '{' <main> '}'
  // <variant> ::= '^' | '_' | '0' | '1' | '2' | '3' | <empty>
  // <main> ::= ('-' | '.' | '~' | '>' | '<' | '(' | ')' | '`' | "'" | '|' | '*' | '+' | 'x' | '/' | 'o' | '=' | ':' | /[a-zA-Z@ ]/)*
  AST.ObjectBox.Dir = AST.ObjectBox.Subclass({
    Init: function (variant, main) {
      this.variant = variant;
      this.main = main;
    },
    dirVariant: function () { return this.variant; },
    dirMain: function () { return this.main; },
    isDir: function () { return true; },
    toString: function () { return "\\dir" + this.variant + "{" + this.main + "}"; }
  });
  
  // <objectbox> ::= '\crv' <curve-modifier> '{' <curve-object> <curve-poslist> '}'
  AST.ObjectBox.Curve = AST.ObjectBox.Subclass({
    Init: function (modifiers, objects, poslist) {
      this.modifiers = modifiers;
      this.objects = objects;
      this.poslist = poslist;
    },
    dirVariant: function () { return ""; },
    dirMain: function () { return "-"; },
    isDir: function () { return false; },
    toString: function () { return "\\curve"+this.modifiers.mkString("")+"{"+this.objects.mkString(" ")+" "+this.poslist.mkString("&")+"}"; }
  });
  // <curve-modifier> ::= ( '~' <curve-option> )*
  // <curve-option> ::= 'p' | 'P' | 'l' | 'L' | 'c' | 'C'
  //                |   'pc' | 'pC' | 'Pc' | 'PC'
  //                |   'lc' | 'lC' | 'Lc' | 'LC'
  //                |   'cC'
  AST.ObjectBox.Curve.Modifier = MathJax.Object.Subclass({});
  AST.ObjectBox.Curve.Modifier.p = MathJax.Object.Subclass({
    toString: function () { return "~p"; }
  });
  AST.ObjectBox.Curve.Modifier.P = MathJax.Object.Subclass({
    toString: function () { return "~P"; }
  });
  AST.ObjectBox.Curve.Modifier.l = MathJax.Object.Subclass({
    toString: function () { return "~l"; }
  });
  AST.ObjectBox.Curve.Modifier.L = MathJax.Object.Subclass({
    toString: function () { return "~L"; }
  });
  AST.ObjectBox.Curve.Modifier.c = MathJax.Object.Subclass({
    toString: function () { return "~c"; }
  });
  AST.ObjectBox.Curve.Modifier.C = MathJax.Object.Subclass({
    toString: function () { return "~C"; }
  });
  AST.ObjectBox.Curve.Modifier.pc = MathJax.Object.Subclass({
    toString: function () { return "~pc"; }
  });
  AST.ObjectBox.Curve.Modifier.pC = MathJax.Object.Subclass({
    toString: function () { return "~pC"; }
  });
  AST.ObjectBox.Curve.Modifier.Pc = MathJax.Object.Subclass({
    toString: function () { return "~Pc"; }
  });
  AST.ObjectBox.Curve.Modifier.PC = MathJax.Object.Subclass({
    toString: function () { return "~PC"; }
  });
  AST.ObjectBox.Curve.Modifier.lc = MathJax.Object.Subclass({
    toString: function () { return "~lc"; }
  });
  AST.ObjectBox.Curve.Modifier.lC = MathJax.Object.Subclass({
    toString: function () { return "~lC"; }
  });
  AST.ObjectBox.Curve.Modifier.Lc = MathJax.Object.Subclass({
    toString: function () { return "~Lc"; }
  });
  AST.ObjectBox.Curve.Modifier.LC = MathJax.Object.Subclass({
    toString: function () { return "~LC"; }
  });
  AST.ObjectBox.Curve.Modifier.cC = MathJax.Object.Subclass({
    toString: function () { return "~cC"; }
  });
  // <curve-object> ::= <empty>
  //                |   '~*' <object> <curve-object>
  //                |   '~**' <object> <curve-object>
  AST.ObjectBox.Curve.Object = MathJax.Object.Subclass({});
  AST.ObjectBox.Curve.Object.Drop = MathJax.Object.Subclass({
    Init: function (object) {
      this.object = object;
    },
    toString: function () { return "~*" + this.object; }
  });
  AST.ObjectBox.Curve.Object.Connect = MathJax.Object.Subclass({
    Init: function (object) {
      this.object = object;
    },
    toString: function () { return "~**" + this.object; }
  });
  // <curve-poslist> ::= <empty> ^^ Empty List
  //           |   '&' <curve-poslist2> ^^ (c, <poslist>)
  //           |   <nonemptyPos> ^^ (<nonemptyPos>, Nil)
  //           |   <nonemptyPos> '&' <curve-poslist2> ^^ (<nonemptyPos>, <poslist>)
  //           |   '~@' ^^ (~@, Nil)
  //           |   '~@' '&' <curve-poslist2> ^^ (~@, <poslist>)
  // <curve-poslist2> ::= <empty> ^^ (c, Nil)
  //           |   '&' <curve-poslist2> ^^ (c, <poslist>)
  //           |   <nonemptyPos> ^^ (<nonemptyPos>, Nil)
  //           |   <nonemptyPos> '&' <curve-poslist2> ^^ (<nonemptyPos>, <poslist>)
  //           |   '~@' ^^ (~@, Nil)
  //           |   '~@' '&' <curve-poslist2> ^^ (~@, <poslist>)
  AST.ObjectBox.Curve.PosList = MathJax.Object.Subclass({});
  AST.ObjectBox.Curve.PosList.CurPos = MathJax.Object.Subclass({
    toString: function () { return ""; }
  });
  AST.ObjectBox.Curve.PosList.Pos = MathJax.Object.Subclass({
    Init: function (pos) {
      this.pos = pos;
    },
    toString: function () { return this.pos.toString(); }
  });
  AST.ObjectBox.Curve.PosList.AddStack = MathJax.Object.Subclass({
    toString: function () { return "~@"; }
  });
  
  // <modifier>
  AST.Modifier = MathJax.Object.Subclass({
  });
  // <modifier> ::= '!' <vector>
  AST.Modifier.Vector = AST.Modifier.Subclass({
    Init: function (vector) {
      this.vector = vector;
    },
    toString: function () { return "!" + this.vector; }
  });
  // <modifier> ::= '!'
  AST.Modifier.RestoreOriginalRefPoint = AST.Modifier.Subclass({
    toString: function () { return "!"; }
  });
  // <modifier> ::= <add-op> <size>
  // <add-op> ::= '+' | '-' | '=' | '+=' | '-='
  // <size> ::= <vector> | <empty>
  AST.Modifier.AddOp = AST.Modifier.Subclass({
    Init: function (op, size) {
      this.op = op;
      this.size = size;
    },
    toString: function () { return this.op.toString() + " " + this.size; }
  });
  AST.Modifier.AddOp.Grow = MathJax.Object.Subclass({
    toString: function () { return '+'; }
  });
  AST.Modifier.AddOp.Shrink = MathJax.Object.Subclass({
    toString: function () { return '-'; }
  });
  AST.Modifier.AddOp.Set = MathJax.Object.Subclass({
    toString: function () { return '='; }
  });
  AST.Modifier.AddOp.GrowTo = MathJax.Object.Subclass({
    toString: function () { return '+='; }
  });
  AST.Modifier.AddOp.ShrinkTo = MathJax.Object.Subclass({
    toString: function () { return '-='; }
  });
  AST.Modifier.AddOp.VactorSize = MathJax.Object.Subclass({
    Init: function (vector) {
      this.vector = vector;
    },
    isDefault: false,
    toString: function () { return this.vector.toString(); }
  });
  AST.Modifier.AddOp.DefaultSize = MathJax.Object.Subclass({
    isDefault: true,
    toString: function () { return ""; }
  });
  
  // <modifier> ::= '[' <shape> ']'
  // <shape> ::= '.' 
  //          | <frame_shape>
  //          | <alphabets>
  //          | '=' <alphabets>
  //          | <empty>
  // <alphabets> ::= /[a-zA-Z]+/
  AST.Modifier.Shape = MathJax.Object.Subclass({});
  AST.Modifier.Shape.Point = AST.Modifier.Subclass({
    toString: function () { return "[.]"; }
  });
  AST.Modifier.Shape.Rect = AST.Modifier.Subclass({
    toString: function () { return "[]"; }
  });
  AST.Modifier.Shape.Alphabets = AST.Modifier.Subclass({
    Init: function (alphabets) {
      this.alphabets = alphabets;
    },
    toString: function () { return "[" + this.alphabets + "]"; }
  });
  AST.Modifier.Shape.DefineShape = AST.Modifier.Subclass({
    Init: function (shape) {
      this.shape = shape;
    },
    toString: function () { return "[" + this.shape + "]"; }
  });
  // 以下はxypic.ModifierRepositoryに格納されるもの
  AST.Modifier.Shape.Circle = AST.Modifier.Subclass({
    toString: function () { return "[o]"; }
  });
  AST.Modifier.Shape.L = AST.Modifier.Subclass({
    toString: function () { return "[l]"; }
  });
  AST.Modifier.Shape.R = AST.Modifier.Subclass({
    toString: function () { return "[r]"; }
  });
  AST.Modifier.Shape.U = AST.Modifier.Subclass({
    toString: function () { return "[u]"; }
  });
  AST.Modifier.Shape.D = AST.Modifier.Subclass({
    toString: function () { return "[d]"; }
  });
  AST.Modifier.Shape.C = AST.Modifier.Subclass({
    toString: function () { return "[c]"; }
  });
  AST.Modifier.Shape.ChangeColor = AST.Modifier.Subclass({
    Init: function (colorName) {
      this.colorName = colorName;
    },
    toString: function () { return "[" + this.colorName + "]"; }
  });
  // ユーザ定義されたshape
  AST.Modifier.Shape.CompositeModifiers = AST.Modifier.Subclass({
    /**
     * @param {List[AST.Modifier.Shape.*]} modifiers ユーザ定義されたmodifierのリスト
     */
    Init: function (modifiers) {
      this.modifiers = modifiers;
    },
    toString: function () { return this.modifiers.mkString(""); }
  });
  
  // <frame_shape> ::= 'F' <frame_main> ( ':' ( <frame_radius_vector> | <color_name> ))*
  AST.Modifier.Shape.Frame = AST.Modifier.Subclass({
    Init: function (main, options) {
      this.main = main;
      this.options = options;
    },
    toString: function () {
      return "[F" + this.main + this.options.mkString("") + "]";
    }
  });
  AST.Modifier.Shape.Frame.Radius = MathJax.Object.Subclass({
    Init: function (vector) {
      this.vector = vector;
    },
    toString: function () {
      return ":" + this.vector;
    }
  });
  // <color_name> ::= /[a-zA-Z][a-zA-Z0-9]*/
  AST.Modifier.Shape.Frame.Color = MathJax.Object.Subclass({
    Init: function (colorName) {
      this.colorName = colorName;
    },
    toString: function () {
      return ":" + this.colorName;
    }
  });
  
  // <modifier> ::= 'i'
  AST.Modifier.Invisible = AST.Modifier.Subclass({
    toString: function () { return "i"; }
  });
  
  // <modifier> ::= 'h'
  AST.Modifier.Hidden = AST.Modifier.Subclass({
    toString: function () { return "h"; }
  });
  
  // <modifier> ::= <nonempty-direction>
  AST.Modifier.Direction = AST.Modifier.Subclass({
    Init: function (direction) {
      this.direction = direction;
    },
    toString: function () { return this.direction.toString(); }
  });
  
  // <direction>
  AST.Direction = MathJax.Object.Subclass({});
  // <direction> ::= <direction0> <direction1>*
  AST.Direction.Compound = MathJax.Object.Subclass({
    Init: function (dir, rots) {
      this.dir = dir;
      this.rots = rots;
    },
    toString: function () {
      return this.dir.toString() + this.rots.mkString();
    }
  });
  // <direction0> ::= <diag>
  AST.Direction.Diag = MathJax.Object.Subclass({
    Init: function (diag) {
      this.diag = diag;
    },
    toString: function () { return this.diag.toString(); }
  });
  // <direction0> ::= 'v' <vector>
  AST.Direction.Vector = MathJax.Object.Subclass({
    Init: function (vector) {
      this.vector = vector;
    },
    toString: function () { return "v" + this.vector.toString(); }
  });
  // <direction0> ::= 'q' '{' <pos> <decor> '}'
  AST.Direction.ConstructVector = MathJax.Object.Subclass({
    Init: function (posDecor) {
      this.posDecor = posDecor;
    },
    toString: function () { return "q{" + this.posDecor.toString() + "}"; }
  });
  // <direction1> ::= ':' <vector>
  AST.Direction.RotVector = MathJax.Object.Subclass({
    Init: function (vector) {
      this.vector = vector;
    },
    toString: function () { return ":" + this.vector.toString(); }
  });
  // <direction1> ::= '_'
  AST.Direction.RotAntiCW = MathJax.Object.Subclass({
    toString: function () { return "_"; }
  });
  // <direction1> ::= '^'
  AST.Direction.RotCW = MathJax.Object.Subclass({
    toString: function () { return "^"; }
  });
  
  // <diag>
  AST.Diag = MathJax.Object.Subclass({});
  // <diag> ::= <empty>
  AST.Diag.Default = MathJax.Object.Subclass({
    toString: function () { return ""; }
  });
  // <diag> ::= 'l' | 'r' | 'd' | 'u' | 'ld' | 'rd' | 'lu' | 'ru'
  AST.Diag.Angle = MathJax.Object.Subclass({
    toString: function () { return this.symbol; }
  });
  AST.Diag.LD = AST.Diag.Angle.Subclass({
    symbol: 'ld',
    ang: -3*Math.PI/4,
    turn: function (orient) {
      return (orient === "^"? AST.Diag.RD() : AST.Diag.LU());
    }
  });
  AST.Diag.RD = AST.Diag.Angle.Subclass({
    symbol: 'rd',
    ang: -Math.PI/4,
    turn: function (orient) {
      return (orient === "^"? AST.Diag.RU() : AST.Diag.LD());
    }
  });
  AST.Diag.LU = AST.Diag.Angle.Subclass({
    symbol: 'lu',
    ang: 3*Math.PI/4,
    turn: function (orient) {
      return (orient === "^"? AST.Diag.LD() : AST.Diag.RU());
    }
  });
  AST.Diag.RU = AST.Diag.Angle.Subclass({
    symbol: 'ru',
    ang: Math.PI/4,
    turn: function (orient) {
      return (orient === "^"? AST.Diag.LU() : AST.Diag.RD());
    }
  });
  AST.Diag.L = AST.Diag.Angle.Subclass({
    symbol: 'l',
    ang: Math.PI,
    turn: function (orient) {
      return (orient === "^"? AST.Diag.D() : AST.Diag.U());
    }
  });
  AST.Diag.R = AST.Diag.Angle.Subclass({
    symbol: 'r',
    ang: 0,
    turn: function (orient) {
      return (orient === "^"? AST.Diag.U() : AST.Diag.D());
    }
  });
  AST.Diag.D = AST.Diag.Angle.Subclass({
    symbol: 'd',
    ang: -Math.PI/2,
    turn: function (orient) {
      return (orient === "^"? AST.Diag.R() : AST.Diag.L());
    }
  });
  AST.Diag.U = AST.Diag.Angle.Subclass({
    symbol: 'u',
    ang: Math.PI/2,
    turn: function (orient) {
      return (orient === "^"? AST.Diag.L() : AST.Diag.R());
    }
  });
  
  // <objectbox> ::= '\frm' <frame_radius> '{' <frame_main> '}'
  // <frame_radius> ::= <frame_radius_vector>
  //          | <empty>
  // <frame_main> ::= ( '-' | '=' | '.' | ',' | 'o' | 'e' | '*' )*
  //          | ( '_' | '^' )? ( '\{' | '\}' | '(' | ')' )
  // <frame_radius_vector> ::= '<' <dimen> ',' <dimen> '>'
  //          |   '<' <dimen> '>'
  AST.ObjectBox.Frame = AST.ObjectBox.Subclass({
    Init: function (radius, main) {
      this.radius = radius;
      this.main = main;
    },
    toString: function () {
      return "\\frm"+this.radius+"{"+this.main+"}";
    }
  });
  AST.ObjectBox.Frame.Radius = MathJax.Object.Subclass({});
  AST.ObjectBox.Frame.Radius.Vector = MathJax.Object.Subclass({
    Init: function (vector) {
      this.vector = vector;
    },
    toString: function () { return this.vector.toString(); }
  });
  AST.ObjectBox.Frame.Radius.Default = MathJax.Object.Subclass({
    toString: function () { return ""; }
  });
  
  // <decor> ::= <command>*
  AST.Decor = MathJax.Object.Subclass({
    Init: function (commands) {
      this.commands = commands;
    },
    toString: function () {
      return this.commands.mkString(" ");
    }
  });
  
  AST.Command = MathJax.Object.Subclass({});
  // <command> ::= '\save' <pos>
  AST.Command.Save = MathJax.Object.Subclass({
    Init: function (pos) {
      this.pos = pos;
    },
    toString: function () {
      return "\\save " + this.pos;
    }
  });
  // <command> ::= '\restore'
  AST.Command.Restore = MathJax.Object.Subclass({
    toString: function () {
      return "\\restore";
    }
  });
  // <command> ::= '\POS' <pos>
  AST.Command.Pos = MathJax.Object.Subclass({
    Init: function (pos) {
      this.pos = pos;
    },
    toString: function () {
      return "\\POS " + this.pos;
    }
  });
  // <command> ::= '\afterPOS' '{' <decor> '}' <pos>
  AST.Command.AfterPos = MathJax.Object.Subclass({
    Init: function (decor, pos) {
      this.decor = decor;
      this.pos = pos;
    },
    toString: function () {
      return "\\afterPOS{" + this.decor + "} " + this.pos;
    }
  });
  // <command> ::= '\drop' <object>
  AST.Command.Drop = MathJax.Object.Subclass({
    Init: function (object) {
      this.object = object;
    },
    toString: function () {
      return "\\drop " + this.object;
    }
  });
  // <command> ::= '\connect' <object>
  AST.Command.Connect = MathJax.Object.Subclass({
    Init: function (object) {
      this.object = object;
    },
    toString: function () {
      return "\\connect " + this.object;
    }
  });
  // <command> ::= '\relax'
  AST.Command.Relax = MathJax.Object.Subclass({
    toString: function () {
      return "\\relax";
    }
  });
  // <command> ::= '\xyignore' '{' <pos> <decor> '}'
  AST.Command.Ignore = MathJax.Object.Subclass({
    Init: function (pos, decor) {
      this.pos = pos;
      this.decor = decor;
    },
    toString: function () {
      return "\\ignore{" + this.pos + " " + this.decor + "}";
    }
  });
  // <command> ::= '\xyshowAST' '{' <pos> <decor> '}'
  AST.Command.ShowAST = MathJax.Object.Subclass({
    Init: function (pos, decor) {
      this.pos = pos;
      this.decor = decor;
    },
    toString: function () {
      return "\\xyshowAST{" + this.pos + " " + this.decor + "}";
    }
  });
  
  // <command> ::= '\PATH' <path>
  AST.Command.Path = MathJax.Object.Subclass({
    Init: function (path) {
      this.path = path;
    },
    toString: function () {
      return "\\PATH " + this.path;
    }
  });
  // <command> ::= '\afterPATH' '{' <decor> '}' <path>
  AST.Command.AfterPath = MathJax.Object.Subclass({
    Init: function (decor, path) {
      this.decor = decor;
      this.path = path;
    },
    toString: function () {
      return "\\afterPATH{" + this.decor + "} " + this.path;
    }
  });
  
  // <path> ::= <path2>(Nil)
  AST.Command.Path.Path = MathJax.Object.Subclass({
    Init: function (pathElements) {
      this.pathElements = pathElements;
    },
    toString: function () {
      return this.pathElements.mkString("[", ", ", "]");
    }
  });
  // <path2> ::= '~' <action> '{' <pos> <decor> '}' <path2>(fc)
  // <action> ::= '=' | '/'
  AST.Command.Path.SetBeforeAction = MathJax.Object.Subclass({
    Init: function (posDecor) {
      this.posDecor = posDecor;
    },
    toString: function () {
      return "~={" + this.posDecor + "}";
    }
  });
  AST.Command.Path.SetAfterAction = MathJax.Object.Subclass({
    Init: function (posDecor) {
      this.posDecor = posDecor;
    },
    toString: function () {
      return "~/{" + this.posDecor + "}";
    }
  });
  // <path2> ::= '~' <which> '{' <labels> '}' <path2>(fc)
  // <which> ::= '<' | '>' | '+'
  AST.Command.Path.AddLabelNextSegmentOnly = MathJax.Object.Subclass({
    Init: function (labels) {
      this.labels = labels;
    },
    toString: function () {
      return "~<{" + this.labels + "}";
    }
  });
  AST.Command.Path.AddLabelLastSegmentOnly = MathJax.Object.Subclass({
    Init: function (labels) {
      this.labels = labels;
    },
    toString: function () {
      return "~>{" + this.labels + "}";
    }
  });
  AST.Command.Path.AddLabelEverySegment = MathJax.Object.Subclass({
    Init: function (labels) {
      this.labels = labels;
    },
    toString: function () {
      return "~+{" + this.labels + "}";
    }
  });
  // <path2> ::= "'" <segment> <path2>(fc)
  AST.Command.Path.StraightSegment = MathJax.Object.Subclass({
    Init: function (segment) {
      this.segment = segment;
    },
    toString: function () {
      return "'" + this.segment;
    }
  });
  // <path2> ::= '`' <turn> <segment> <path2>(fc)
  AST.Command.Path.TurningSegment = MathJax.Object.Subclass({
    Init: function (turn, segment) {
      this.turn = turn;
      this.segment = segment;
    },
    toString: function () {
      return "`" + this.turn + " " + this.segment;
    }
  });
  // <path2> ::= <segment>
  AST.Command.Path.LastSegment = MathJax.Object.Subclass({
    Init: function (segment) {
      this.segment = segment;
    },
    toString: function () {
      return this.segment.toString();
    }
  });
  
  // <turn> ::= <diag> <turn-radius>
  AST.Command.Path.Turn = MathJax.Object.Subclass({});
  AST.Command.Path.Turn.Diag = MathJax.Object.Subclass({
    Init: function (diag, radius) {
      this.diag = diag;
      this.radius = radius;
    },
    toString: function () {
      return this.diag.toString() + " " + this.radius;
    }
  });
  // <turn> ::= <cir> <turnradius>
  AST.Command.Path.Turn.Cir = MathJax.Object.Subclass({
    Init: function (cir, radius) {
      this.cir = cir;
      this.radius = radius;
    },
    toString: function () {
      return this.cir.toString() + " " + this.radius;
    }
  });
  // <turn-radius> ::= <empty> | '/' <dimen>
  AST.Command.Path.TurnRadius = MathJax.Object.Subclass({});
  AST.Command.Path.TurnRadius.Default = MathJax.Object.Subclass({
    toString: function () {
      return "";
    }
  });
  AST.Command.Path.TurnRadius.Dimen = MathJax.Object.Subclass({
    Init: function (dimen) {
      this.dimen = dimen;
    },
    toString: function () {
      return "/" + this.dimen;
    }
  });
  
  // <segment> ::= <nonempty-pos> <slide> <labels>
  AST.Command.Path.Segment = MathJax.Object.Subclass({
    Init: function (pos, slide, labels) {
      this.pos = pos;
      this.slide = slide;
      this.labels = labels;
    },
    toString: function () {
      return this.pos.toString() + " " + this.slide + " " + this.labels;
    }
  });
  
  // <labels> ::= <label>*
  AST.Command.Path.Labels = MathJax.Object.Subclass({
    Init: function (labels) {
      this.labels = labels;
    },
    toString: function () {
      return this.labels.mkString(" ");
    }
  });
  // <label> ::= '^' <anchor> <it> <alias>?
  // <anchor> ::= '-' | <place>
  // <it> ::= ( '[' <shape> ']' )* <it2>
  // <it2> ::= <digit> | <letter>
  //       |   '{' <text> '}'
  //       |   '*' <object>
  //       |   '@' <dir>
  AST.Command.Path.Label = MathJax.Object.Subclass({
    Init: function (anchor, it, aliasOption) {
      this.anchor = anchor;
      this.it = it;
      this.aliasOption = aliasOption;
    }
  });
  AST.Command.Path.Label.Above = AST.Command.Path.Label.Subclass({
    toString: function () {
      return "^(" + this.anchor + " " + this.it + " " + this.aliasOption + ")";
    }
  });
  // <label> ::= '_' <anchor> <it> <alias>?
  AST.Command.Path.Label.Below = AST.Command.Path.Label.Subclass({
    toString: function () {
      return "_(" + this.anchor + " " + this.it + " " + this.aliasOption + ")";
    }
  });
  // <label> ::= '|' <anchor> <it> <alias>?
  AST.Command.Path.Label.At = AST.Command.Path.Label.Subclass({
    toString: function () {
      return "|(" + this.anchor + " " + this.it + " " + this.aliasOption + ")";
    }
  });
  
  // <command> ::= '\ar' ( <arrow_form> )* <path>
  AST.Command.Ar = MathJax.Object.Subclass({
    Init: function (forms, path) {
      this.forms = forms;
      this.path = path;
    },
    toString: function () {
      return "\\ar " + this.forms.mkString(" ") + " " + this.path;
    }
  });
  
  // <arrow_form>
  AST.Command.Ar.Form = MathJax.Object.Subclass({});
  // <arrow_form> ::= '@' <variant> ( '{' <tip> ( <conn> <tip> )? '}' )?
  // <variant> ::= /[^_0123]/ | <empty>
  AST.Command.Ar.Form.BuildArrow = AST.Command.Ar.Form.Subclass({
    /**
     * @param {String} variant variant
     * @param {AST.Command.Ar.Form.Tip.*} tailTip arrow tail
     * @param {AST.Command.Ar.Form.Conn.*} stemConn arrow stem
     * @param {AST.Command.Ar.Form.Tip.*} headTip arrow head
     */
    Init: function (variant, tailTip, stemConn, headTip) {
      this.variant = variant;
      this.tailTip = tailTip;
      this.stemConn = stemConn;
      this.headTip = headTip;
    },
    toString: function () {
      return "@" + this.variant + "{" + this.tailTip.toString() + ", " + this.stemConn.toString() + ", " + this.headTip.toString() + "}";
    }
  });
  // <arrow_form> ::= '@' <variant>
  AST.Command.Ar.Form.ChangeVariant = AST.Command.Ar.Form.Subclass({
    /**
     * @param {String} variant variant
     */
    Init: function (variant) {
      this.variant = variant;
    },
    toString: function () {
      return "@" + this.variant;
    }
  });
  // <tip> ::= /[<>()|'`+/a-zA-Z ]+/
  //         | <arrow_dir>
  //         | <empty>
  // <arrow_dir> ::= '*' <object>
  //               | <dir>
  AST.Command.Ar.Form.Tip = MathJax.Object.Subclass({});
  AST.Command.Ar.Form.Tip.Tipchars = MathJax.Object.Subclass({
    /**
     * @param {String} tipchars tip characters
     */
    Init: function (tipchars) {
      this.tipchars = tipchars;
    },
    toString: function () {
      return this.tipchars;
    }
  });
  AST.Command.Ar.Form.Tip.Object = MathJax.Object.Subclass({
    /**
     * @param {AST.Object} object object as a dir
     */
    Init: function (object) {
      this.object = object;
    },
    toString: function () {
      return "*" + this.object;
    }
  });
  AST.Command.Ar.Form.Tip.Dir = MathJax.Object.Subclass({
    /**
     * @param {AST.ObjectBox.Dir} dir dir
     */
    Init: function (dir) {
      this.dir = dir;
    },
    toString: function () {
      return this.dir;
    }
  });
  
  // <conn> ::= /[\-\.~=:]+/
  //          | <arrow_dir>
  //          | <empty>
  AST.Command.Ar.Form.Conn = MathJax.Object.Subclass({});
  AST.Command.Ar.Form.Conn.Connchars = MathJax.Object.Subclass({
    /**
     * @param {String} connchars direction name
     */
    Init: function (connchars) {
      this.connchars = connchars;
    },
    toString: function () {
      return this.connchars;
    }
  });
  AST.Command.Ar.Form.Conn.Object = MathJax.Object.Subclass({
    /**
     * @param {AST.Object} object object as a dir
     */
    Init: function (object) {
      this.object = object;
    },
    toString: function () {
      return "*" + this.object;
    }
  });
  AST.Command.Ar.Form.Conn.Dir = MathJax.Object.Subclass({
    /**
     * @param {AST.ObjectBox.Dir} dir dir
     */
    Init: function (dir) {
      this.dir = dir;
    },
    toString: function () {
      return this.dir;
    }
  });
  
  // <arrow_form> ::= '@' <conchar>
  // <conchar> ::= /[\-\.~=:]/
  AST.Command.Ar.Form.ChangeStem = MathJax.Object.Subclass({
    /**
     * @param {String} connchar arrow stem name
     */
    Init: function (connchar) {
      this.connchar = connchar;
    },
    toString: function () {
      return "@" + this.connchar;
    }
  });
  
  // <arrow_form> ::= '@' '!'
  AST.Command.Ar.Form.DashArrowStem = MathJax.Object.Subclass({
    toString: function () {
      return "@!";
    }
  });
  
  // <arrow_form> ::= '@' '/' <direction> ( <loose-dimen> )? '/'
  AST.Command.Ar.Form.CurveArrow = MathJax.Object.Subclass({
    /**
     * @param {AST.Direction.*} curve direction 
     * @param {String} dist curve distance (dimension)
     */
    Init: function (direction, dist) {
      this.direction = direction;
      this.dist = dist;
    },
    toString: function () {
      return "@/" + this.direction + " " + this.dist + "/";
    }
  });
  
  // <arrow_form> ::= '@' '(' <direction> ',' <direction> ')'
  AST.Command.Ar.Form.CurveFitToDirection = MathJax.Object.Subclass({
    /**
     * @param {AST.Direction.*} out direction 
     * @param {AST.Direction.*} in direction 
     */
    Init: function (outDirection, inDirection) {
      this.outDirection = outDirection;
      this.inDirection = inDirection;
    },
    toString: function () {
      return "@(" + this.outDirection + "," + this.inDirection + ")";
    }
  });
  
  // <arrow_form> ::= '@' '`' <coord>
  AST.Command.Ar.Form.CurveWithControlPoints = MathJax.Object.Subclass({
    /**
     * @param {AST.Coord} controlPoints
     */
    Init: function (coord) {
      this.coord = coord;
    },
    toString: function () {
      return "@`{" + this.coord + "}"
    }
  });
  
  // <arrow_form> ::= '@' '[' <shape> ']'
  AST.Command.Ar.Form.AddShape = MathJax.Object.Subclass({
    /**
     * @param {AST.Modifier.Shape.*} shape shape
     */
    Init: function (shape) {
      this.shape = shape;
    },
    toString: function () {
      return "@[" + this.shape + "]";
    }
  });
  
  // <arrow_form> ::= '@' '*' '{' ( <modifier> )* '}'
  AST.Command.Ar.Form.AddModifiers = MathJax.Object.Subclass({
    /**
     * @param {List[AST.Modifier.*]} modifiers modifiers
     */
    Init: function (modifiers) {
      this.modifiers = modifiers;
    },
    toString: function () {
      return "@*{" + this.modifiers.mkString(" ") + "}";
    }
  });
  
  // <arrow_form> ::= '@' '<' <dimen> '>'
  AST.Command.Ar.Form.Slide = MathJax.Object.Subclass({
    /**
     * @param {String} slide dimension
     */
    Init: function (slideDimen) {
      this.slideDimen = slideDimen;
    },
    toString: function () {
      return "@<" + this.slideDimen + ">";
    }
  });
  
  // <arrow_form> ::= '|' <anchor> <it>
  AST.Command.Ar.Form.LabelAt = MathJax.Object.Subclass({
    /**
     * @param {AST.Place} anchor label anchor
     * @param {AST.Object} it label
     */
    Init: function (anchor, it) {
      this.anchor = anchor;
      this.it = it;
    },
    toString: function () {
      return "|" + this.anchor + " " + this.it;
    }
  });
  
  // <arrow_form> ::= '^' <anchor> <it>
  AST.Command.Ar.Form.LabelAbove = MathJax.Object.Subclass({
    /**
     * @param {AST.Place} anchor label anchor
     * @param {AST.Object} it label
     */
    Init: function (anchor, it) {
      this.anchor = anchor;
      this.it = it;
    },
    toString: function () {
      return "^" + this.anchor + " " + this.it;
    }
  });
  
  // <arrow_form> ::= '_' <anchor> <it>
  AST.Command.Ar.Form.LabelBelow = MathJax.Object.Subclass({
    /**
     * @param {AST.Place} anchor label anchor
     * @param {AST.Object} it label
     */
    Init: function (anchor, it) {
      this.anchor = anchor;
      this.it = it;
    },
    toString: function () {
      return "_" + this.anchor + " " + this.it;
    }
  });
  
  // <arrow_form> ::= '@' '?'
  AST.Command.Ar.Form.ReverseAboveAndBelow = MathJax.Object.Subclass({
    toString: function () {
      return "@?";
    }
  });
  
  
  // <decor> ::= '\xymatrix' <xymatrix>
  // <xymatrix> ::= <setup> '{' <rows> '}'
  AST.Command.Xymatrix = MathJax.Object.Subclass({
    /**
     * @param {List[AST.Command.Xymatrix.Setup.*]} setup setup configurations
     * @param {List[AST.Command.Xymatrix.Row]} rows rows
     */
    Init: function (setup, rows) {
      this.setup = setup;
      this.rows = rows;
    },
    toString: function () {
      return "\\xymatrix" + this.setup + "{\n" + this.rows.mkString("", "\\\\\n", "") + "\n}";
    }
  });
  // <setup> ::= <switch>*
  AST.Command.Xymatrix.Setup = MathJax.Object.Subclass({});
  
  // <switch> ::= '"' <prefix> '"'
  AST.Command.Xymatrix.Setup.Prefix = MathJax.Object.Subclass({
    /**
     * @param {String} prefix name of the xymatrix
     */
    Init: function (prefix) {
      this.prefix = prefix;
    },
    toString: function () {
      return '"' + this.prefix + '"';
    }
  });
  
  // <switch> ::= '@' <rcchar> <add op> <dimen>
  AST.Command.Xymatrix.Setup.ChangeSpacing = MathJax.Object.Subclass({
    /**
     * @param {AST.Modifier.AddOp.*} addop sizing operator
     * @param {String} dimen size
     */
    Init: function (addop, dimen) {
      this.addop = addop;
      this.dimen = dimen;
    }
  });
  AST.Command.Xymatrix.Setup.ChangeSpacing.Row = AST.Command.Xymatrix.Setup.ChangeSpacing.Subclass({
    toString: function () {
      return "@R" + this.addop + this.dimen;
    }
  });
  AST.Command.Xymatrix.Setup.ChangeSpacing.Column = AST.Command.Xymatrix.Setup.ChangeSpacing.Subclass({
    toString: function () {
      return "@C" + this.addop + this.dimen;
    }
  });
  AST.Command.Xymatrix.Setup.ChangeSpacing.RowAndColumn = AST.Command.Xymatrix.Setup.ChangeSpacing.Subclass({
    toString: function () {
      return "@" + this.addop + this.dimen;
    }
  });
  
  // <switch> ::= '@' '!' <rcchar> '0'
  // <switch> ::= '@' '!' <rcchar> '=' <dimen>
  // <rcchar> ::= 'R' | 'C' | <empty>
  AST.Command.Xymatrix.Setup.PretendEntrySize = MathJax.Object.Subclass({
    /**
     * @param {String} dimen size
     */
    Init: function (dimen) {
      this.dimen = dimen;
    }
  });
  AST.Command.Xymatrix.Setup.PretendEntrySize.Height = AST.Command.Xymatrix.Setup.PretendEntrySize.Subclass({
    toString: function () {
      return "@!R=" + this.dimen;
    }
  });
  AST.Command.Xymatrix.Setup.PretendEntrySize.Width = AST.Command.Xymatrix.Setup.PretendEntrySize.Subclass({
    toString: function () {
      return "@!C=" + this.dimen;
    }
  });
  AST.Command.Xymatrix.Setup.PretendEntrySize.HeightAndWidth = AST.Command.Xymatrix.Setup.PretendEntrySize.Subclass({
    toString: function () {
      return "@!=" + this.dimen;
    }
  });
  
  // <switch> ::= '@' '!' <rcchar>
  AST.Command.Xymatrix.Setup.FixGrid = MathJax.Object.Subclass({});
  AST.Command.Xymatrix.Setup.FixGrid.Row = AST.Command.Xymatrix.Setup.FixGrid.Subclass({
    toString: function () {
      return "@!R";
    }
  });
  AST.Command.Xymatrix.Setup.FixGrid.Column = AST.Command.Xymatrix.Setup.FixGrid.Subclass({
    toString: function () {
      return "@!C";
    }
  });
  AST.Command.Xymatrix.Setup.FixGrid.RowAndColumn = AST.Command.Xymatrix.Setup.FixGrid.Subclass({
    toString: function () {
      return "@!";
    }
  });
  
  // <switch> ::= '@' ( 'M' | 'W' | 'H' ) <add op> <dimen>
  // <switch> ::= '@' '1'
  AST.Command.Xymatrix.Setup.AdjustEntrySize = MathJax.Object.Subclass({
    /**
     * @param {AST.Modifier.AddOp.*} addop sizing operator
     * @param {String} dimen size
     */
    Init: function (addop, dimen) {
      this.addop = addop;
      this.dimen = dimen;
    }
  });
  AST.Command.Xymatrix.Setup.AdjustEntrySize.Margin = AST.Command.Xymatrix.Setup.AdjustEntrySize.Subclass({
    toString: function () {
      return "@M" + this.addop + this.dimen;
    }
  });
  AST.Command.Xymatrix.Setup.AdjustEntrySize.Width = AST.Command.Xymatrix.Setup.AdjustEntrySize.Subclass({
    toString: function () {
      return "@W" + this.addop + this.dimen;
    }
  });
  AST.Command.Xymatrix.Setup.AdjustEntrySize.Height = AST.Command.Xymatrix.Setup.AdjustEntrySize.Subclass({
    toString: function () {
      return "@H" + this.addop + this.dimen;
    }
  });
  
  // <switch> ::= '@' 'L' <add op> <dimen>
  AST.Command.Xymatrix.Setup.AdjustLabelSep = MathJax.Object.Subclass({
    /**
     * @param {AST.Modifier.AddOp.*} addop sizing operator
     * @param {String} dimen size
     */
    Init: function (addop, dimen) {
      this.addop = addop;
      this.dimen = dimen;
    },
    toString: function () {
      return "@L" + this.addop + this.dimen;
    }
  });
  
  // <switch> ::= '@' <nonemptyDirection>
  AST.Command.Xymatrix.Setup.SetOrientation = MathJax.Object.Subclass({
    /**
     * @param {AST.Direction} direction the orientation of the row
     */
    Init: function (direction) {
      this.direction = direction;
    },
    toString: function () {
      return "@" + this.direction;
    }
  });
  
  // <switch> ::= '@' '*' '[' <shape> ']'
  //          |   '@' '*' <add op> <size>
  AST.Command.Xymatrix.Setup.AddModifier = MathJax.Object.Subclass({
    /**
     * @param {AST.Modifier.*} shape  object shape modifier for all entries
     */
    Init: function (modifier) {
      this.modifier = modifier;
    },
    toString: function () {
      return "@*" + this.modifier;
    }
  });
  
  // <rows> ::= <row> ( '\\' <row> )*
  // <row> ::= <entry> ( '&' <entry> )*
  AST.Command.Xymatrix.Row = MathJax.Object.Subclass({
    /**
     * @param {List[AST.Command.Xymatrix.Entry.*]} entries entries in the row
     */
    Init: function (entries) {
      this.entries = entries;
    },
    toString: function () {
      return this.entries.mkString(" & ");
    }
  });
  // <entry> ::= ( '**' '[' <shape> ']' | '**' '{' <modifier>* '}' )* <loose objectbox> <decor>
  //         |   '*' <object> <pos> <decor>
  // <loose objectbox> ::= <objectbox>
  //                   |   /[^\\{}&]+/* ( ( '\' not( '\' | <decor command names> ) ( '{' | '}' | '&' ) | '{' <text> '}' ) /[^\\{}&]+/* )*
  // <decor command names> ::= 'ar' | 'xymatrix' | 'PATH' | 'afterPATH'
  //                       |   'save' | 'restore' | 'POS' | 'afterPOS' | 'drop' | 'connect' | 'xyignore'
  AST.Command.Xymatrix.Entry = MathJax.Object.Subclass({});
  AST.Command.Xymatrix.Entry.SimpleEntry = AST.Command.Xymatrix.Entry.Subclass({
    /**
     * @param {List[AST.Modifier.*]} modifiers object modifiers
     * @param {AST.ObjectBox.*} objectbox entry objectbox
     * @param {AST.Decor} decor decoration
     */
    Init: function (modifiers, objectbox, decor) {
      this.modifiers = modifiers;
      this.objectbox = objectbox;
      this.decor = decor;
    },
    isEmpty: false,
    toString: function () {
      return this.modifiers.mkString("**{", "", "}") + " " + this.objectbox + " " + this.decor;
    }
  });
  AST.Command.Xymatrix.Entry.ObjectEntry = AST.Command.Xymatrix.Entry.Subclass({
    /**
     * @param {AST.Object} object entry object
     * @param {AST.Pos.Coord} pos position (ignorable)
     * @param {AST.Decor} decor decoration
     */
    Init: function (object, pos, decor) {
      this.object = object;
      this.pos = pos;
      this.decor = decor;
    },
    isEmpty: false,
    toString: function () {
      return "*" + this.object + " " + this.pos + " " + this.decor;
    }
  });
  AST.Command.Xymatrix.Entry.EmptyEntry = AST.Command.Xymatrix.Entry.Subclass({
    /**
     * @param {AST.Decor} decor decoration
     */
    Init: function (decor) {
      this.decor = decor;
    },
    isEmpty: true,
    toString: function () {
      return "" + this.decor;
    }
  });
  
  // <command> ::= <twocell> <twocell switch>* <twocell arrow>
  AST.Command.Twocell = MathJax.Object.Subclass({
    /**
     * @param {AST.Command.Twocell.Hops2cell} twocell 2-cell
     * @param {List[AST.Command.Twocell.Switch.*]} switches switches
     * @param {AST.Command.Twocell.Arrow.*} arrow
     */
    Init: function (twocell, switches, arrow) {
      this.twocell = twocell;
      this.switches = switches;
      this.arrow = arrow;
    },
    toString: function () {
      return this.twocell.toString() + this.switches.mkString("") + this.arrow;
    }
  });
  // <twocell> ::= '\' /[lrud]+/ 'twocell'
  //           |   '\xtwocell' '[' /[lrud]+/ ']' '{' <text> '}'
  AST.Command.Twocell.Hops2cell = MathJax.Object.Subclass({
    /**
     * @param {String} hops hops
     * @param {Option[AST.Object]} maybeDisplace displacement
     */
    Init: function (hops, maybeDisplace) {
      this.hops = hops;
      this.maybeDisplace = maybeDisplace;
    }
  });
  AST.Command.Twocell.Twocell = AST.Command.Twocell.Hops2cell.Subclass({
    toString: function () {
      return "\\xtwocell[" + this.hops + "]" + this.maybeDisplace.getOrElse("{}");
    }
  });
  //           |   '\' /[lrud]+/ 'uppertwocell'
  //           |   '\xuppertwocell' '[' /[lrud]+/ ']' '{' <text> '}'
  AST.Command.Twocell.UpperTwocell = AST.Command.Twocell.Hops2cell.Subclass({
    toString: function () {
      return "\\xuppertwocell[" + this.hops + "]" + this.maybeDisplace.getOrElse("{}");
    }
  });
  //           |   '\' /[lrud]+/ 'lowertwocell'
  //           |   '\xlowertwocell' '[' /[lrud]+/ ']' '{' <text> '}'
  AST.Command.Twocell.LowerTwocell = AST.Command.Twocell.Hops2cell.Subclass({
    toString: function () {
      return "\\xlowertwocell[" + this.hops + "]" + this.maybeDisplace.getOrElse("{}");
    }
  });
  //           |   '\' /[lrud]+/ 'compositemap'
  //           |   '\xcompositemap' '[' /[lrud]+/ ']' '{' <text> '}'
  AST.Command.Twocell.CompositeMap = AST.Command.Twocell.Hops2cell.Subclass({
    toString: function () {
      return "\\xcompositemap[" + this.hops + "]" + this.maybeDisplace.getOrElse("{}");
    }
  });
  
  // <twocell switch> ::= '^' <twocell label>
  AST.Command.Twocell.Switch = MathJax.Object.Subclass({});
  AST.Command.Twocell.Switch.UpperLabel = MathJax.Object.Subclass({
    /**
     * @param {AST.Command.Twocell.Label} label label
     */
    Init: function (label) {
      this.label = label;
    },
    toString: function () {
      return "^" + this.label;
    }
  });
  //          |   '_' <twocell label>
  AST.Command.Twocell.Switch.LowerLabel = MathJax.Object.Subclass({
    /**
     * @param {AST.Command.Twocell.Label} label label
     */
    Init: function (label) {
      this.label = label;
    },
    toString: function () {
      return "_" + this.label;
    }
  });
  //          |   <nudge>
  AST.Command.Twocell.Switch.SetCurvature = MathJax.Object.Subclass({
    /**
     * @param {AST.Command.Twocell.Nudge.*} nudge
     */
    Init: function (nudge) {
      this.nudge = nudge;
    },
    toString: function () {
      return this.nudge.toString();
    }
  });
  //          |   '\omit'
  AST.Command.Twocell.Switch.DoNotSetCurvedArrows = MathJax.Object.Subclass({
    toString: function () {
      return "\\omit";
    }
  });
  //          |   '~!'
  AST.Command.Twocell.Switch.PlaceModMapObject = MathJax.Object.Subclass({
    toString: function () {
      return "~!";
    }
  });
  
  //          |   '~' ( '`' | "'" ) '{' <object> '}'
  AST.Command.Twocell.Switch.ChangeHeadTailObject = MathJax.Object.Subclass({
    /**
     * @param {String} what
     * @param {AST.Object} object
     */
    Init: function (what, object) {
      this.what = what;
      this.object = object;
    },
    toString: function () {
      return "~" + this.what + "{" + this.object + "}";
    }
  });
  
  //          |   '~' ( '' | '^' | '_' ) '{' <object> ( '~**' <object> )? '}'
  AST.Command.Twocell.Switch.ChangeCurveObject = MathJax.Object.Subclass({
    /**
     * @param {String} what
     * @param {AST.Object} spacer
     * @param {AST.Object} maybeObject
     */
    Init: function (what, spacer, maybeObject) {
      this.what = what;
      this.spacer = spacer;
      this.maybeObject = maybeObject;
    },
    toString: function () {
      return "~" + this.what + "{" + this.spacer + (this.maybeObject.isDefined? "~**" + this.maybeObject.get : "") + "}";
    }
  });
  
  // <twocell label> ::= <digit> | <letter> | <cs>
  //                 |   '{' <nudge>? '*' <object> '}'
  //                 |   '{' <nudge>? <text> '}'
  AST.Command.Twocell.Label = MathJax.Object.Subclass({
    /**
     * @param {Option[AST.Command.Twocell.Nudge.*]} maybeNudge
     * @param {AST.Object} labelObject
     */
    Init: function (maybeNudge, labelObject) {
      this.maybeNudge = maybeNudge;
      this.labelObject = labelObject;
    },
    toString: function () {
      return this.maybeNudge.toString() + this.labelObject;
    }
  });
  
  // <nudge> ::= '<' <factor> '>'
  //         |   '<\omit>'
  AST.Command.Twocell.Nudge = MathJax.Object.Subclass({});
  AST.Command.Twocell.Nudge.Number = AST.Command.Twocell.Nudge.Subclass({
    /**
     * @param {Number} number number
     */
    Init: function (number) {
      this.number = number;
    },
    toString: function () {
      return "<" + this.number + ">";
    }
  });
  AST.Command.Twocell.Nudge.Omit = AST.Command.Twocell.Nudge.Subclass({
    toString: function () {
      return "<\\omit>";
    }
  });
  
  // <twocell arrow> ::= '{' <twocell tok> (<twocell label entry> '}'
  //                 |   '{' <twocell label entry> '}'
  //                 |   <empty>
  // <twocell tok> ::= '^' | '_' | '='
  //               |   '\omit'
  //               |   '`' | "'" | '"' | '!'
  // <twocell label entry> ::= '*' <object>
  //                       |   <text>
  AST.Command.Twocell.Arrow = MathJax.Object.Subclass({});
  AST.Command.Twocell.Arrow.WithOrientation = AST.Command.Twocell.Arrow.Subclass({
    /**
     * @param {String} tok
     * @param {AST.Object} labelObject
     */
    Init: function (tok, labelObject) {
      this.tok = tok;
      this.labelObject = labelObject;
    },
    toString: function () {
      return "{[" + this.tok + "] " + this.labelObject + "}";
    }
  });
  //                 |   '{' <nudge> <twocell label entry> '}'
  AST.Command.Twocell.Arrow.WithPosition = AST.Command.Twocell.Arrow.Subclass({
    /**
     * @param {AST.Command.Twocell.Nudge.*} nudge
     * @param {AST.Object} labelObject
     */
    Init: function (nudge, labelObject) {
      this.nudge = nudge;
      this.labelObject = labelObject;
    },
    toString: function () {
      return "{[" + this.nudge + "] " + this.labelObject + "}";
    }
  });
  
  // '\newdir' '{' <main> '}' '{' <composite_object> '}'
  AST.Command.Newdir = MathJax.Object.Subclass({
    /**
     * @param {String} dirMain
     * @param {AST.ObjectBox.CompositeObject} compositeObject
     */
    Init: function (dirMain, compositeObject) {
      this.dirMain = dirMain;
      this.compositeObject = compositeObject;
    },
    toString: function () {
      return "\\newdir{" + this.dirMain + "}{" + this.compositeObject + "}";
    }
  });
  
  // '\xyimport' '(' <factor> ',' <factor> ')' ( '(' <factor> ',' <factor> ')' )? '{' <TeX command> '}'
  AST.Pos.Xyimport = MathJax.Object.Subclass({});
  AST.Pos.Xyimport.TeXCommand = AST.Pos.Xyimport.Subclass({
    /**
     * @param {Number} width the width of the graphics in the coordinate system
     * @param {Number} height the height of the graphics in the coordinate system
     * @param {Number} xOffset the distance of the origin of coordinates from left corner
     * @param {Number} yOffset the distance of the origin of coordinates from bottom corner
     * @param {AST.Command.*} graphics object
     */
    Init: function (width, height, xOffset, yOffset, graphics) {
      this.width = width;
      this.height = height;
      this.xOffset = xOffset;
      this.yOffset = yOffset;
      this.graphics = graphics;
    },
    toString: function () {
      return "\\xyimport(" + this.width + ", " + this.height + ")(" + this.xOffset + ", " + this.yOffset + "){" + this.graphics + "}";
    }
  });
  
  // '\xyimport' '(' <factor> ',' <factor> ')' ( '(' <factor> ',' <factor> ')' )? '{' <include graphics> '}'
  AST.Pos.Xyimport.Graphics = AST.Pos.Xyimport.Subclass({
    /**
     * @param {Number} width the width of the graphics in the coordinate system
     * @param {Number} height the height of the graphics in the coordinate system
     * @param {Number} xOffset the distance of the origin of coordinates from left corner
     * @param {Number} yOffset the distance of the origin of coordinates from bottom corner
     * @param {AST.Command.Includegraphics} graphics object
     */
    Init: function (width, height, xOffset, yOffset, graphics) {
      this.width = width;
      this.height = height;
      this.xOffset = xOffset;
      this.yOffset = yOffset;
      this.graphics = graphics;
    },
    toString: function () {
      return "\\xyimport(" + this.width + ", " + this.height + ")(" + this.xOffset + ", " + this.yOffset + "){" + this.graphics + "}";
    }
  });
  
  /* \includegraphics command from the graphicx package */
  // '\includegraphics' '*'? '[' ( <includegraphics attr key val> ( ',' <includegraphics attr key val> )* )? ']' '{' <file path> '}'
  AST.Command.Includegraphics = MathJax.Object.Subclass({
    /**
     * @param {boolean} isClipped whether the graphics is clipped to the size specified or not
     * @param {List[AST.Command.Includegraphics.Attr]} attributeList attribute key-value list
     * @param {String} filepath image file path
     */
    Init: function (isClipped, attributeList, filepath) {
      this.isClipped = isClipped;
      this.attributeList = attributeList;
      this.filepath = filepath;
    },
    isIncludegraphics: true,
    toString: function () {
      return "\\includegraphics" + (this.isClipped? "*" : "") + this.attributeList.mkString("[", ",", "]") + "{" + this.filepath + "}";
    }
  });
  
  // TODO: define <includegraphics attr key val>
  // <includegraphics attr key val> := 'width' '=' <dimen>
  //                                |  'height' '=' <dimen>
  AST.Command.Includegraphics.Attr = MathJax.Object.Subclass({});
  AST.Command.Includegraphics.Attr.Width = AST.Command.Includegraphics.Attr.Subclass({
    /**
     * @param {String} dimen 
     */
    Init: function (dimen) {
      this.dimen = dimen;
    },
    toString: function () {
      return "width=" + this.dimen;
    }
  });
  AST.Command.Includegraphics.Attr.Height = AST.Command.Includegraphics.Attr.Subclass({
    /**
     * @param {String} dimen 
     */
    Init: function (dimen) {
      this.dimen = dimen;
    },
    toString: function () {
      return "height=" + this.dimen;
    }
  });
  
  var fun = FP.Parsers.fun;
  var elem = FP.Parsers.elem;
  var felem = function (x) { return fun(FP.Parsers.elem(x)); }
  var lit = FP.Parsers.literal;
  var regex = FP.Parsers.regex;
  var regexLit = FP.Parsers.regexLiteral;
  var flit = function (x) { return fun(FP.Parsers.literal(x)); }
  var seq = FP.Parsers.seq;
  var or = FP.Parsers.or;
  var rep = function (x) { return FP.Parsers.lazyParser(x)().rep(); }
  var rep1 = function (x) { return FP.Parsers.lazyParser(x)().rep1(); }
  var opt = function (x) { return FP.Parsers.lazyParser(x)().opt(); }
  var not = function (x) { return FP.Parsers.not(FP.Parsers.lazyParser(x)); }
  var success = FP.Parsers.success;
  var memo = function (parser) {
    return function () {
      var m = parser.memo;
      if (m === undefined) {
        m = parser.memo = parser();
      }
      return m;
    }
  }
  
  var p = FP.Parsers.Subclass({
    // <pos-decor> '\end' '{' 'xy' '}'
    xy: memo(function () {
      return p.posDecor().into(function (pd) {
        return FP.Parsers.guard(function() { return lit('\\end').andl(flit('{')).andl(flit('xy')).andl(flit('}')).to(function () {
          return pd;
        })});
      });
    }),
    
    // \xyboxの後の
    // '{' <pos-decor> '}'
    xybox: memo(function () {
      return lit("{").andr(p.posDecor).andl(flit("}")).to(function (pd) {
        return pd;
      });
    }),
    
    // \xymatrixの後の
    // <xymatrix>
    xymatrixbox: memo(function () {
      return p.xymatrix().to(function (m) {
        return AST.PosDecor(AST.Pos.Coord(AST.Coord.C(), FP.List.empty), AST.Decor(FP.List.empty.append(m)));
      });
    }),
    
    // <pos-decor> ::= <pos> <decor>
    posDecor: memo(function () {
      return seq(p.pos, p.decor).to(function (pd) {
        return AST.PosDecor(pd.head, pd.tail);
      });
    }),
    
    // <pos> ::= <coord> <pos2>*
    pos: memo(function () {
      return seq(p.coord, rep(p.pos2)).to(function (cps) {
        return AST.Pos.Coord(cps.head, cps.tail);
      });
    }),
    
    // <nonemptyPos> ::= <coord> <pos2>*
    nonemptyPos: memo(function () {
      return or(
        seq(p.nonemptyCoord, rep(p.pos2)),
        seq(p.coord, rep1(p.pos2))
      ).to(function (cps) {
        return AST.Pos.Coord(cps.head, cps.tail);
      });
    }),
    
    // <pos2> ::= '+' <coord>
    //        |   '-' <coord>
    //        |   '!' <coord>
    //        |   '.' <coord>
    //        |   ',' <coord>
    //        |   ';' <coord>
    //        |   '::' <coord>
    //        |   ':' <coord>
    //        |   '**' <object>
    //        |   '*' <object>
    //        |   '?' <place>
    //        |   '@+' <corrd>
    //        |   '@-' <corrd>
    //        |   '@=' <corrd>
    //        |   '@@' <corrd>
    //        |   '@i'
    //        |   '@('
    //        |   '@)'
    //        |   '=:' '"' <id> '"'
    //        |   '=@' '"' <id> '"'
    //        |   '=' '"' <id> '"'
    //        |   '=' <nonemptyCoord> '"' <id> '"'
    //        |   <xyimport>
    pos2: memo(function () {
      return or(
        lit('+').andr(p.coord).to(function (c) { return AST.Pos.Plus(c); }),
        lit('-').andr(p.coord).to(function (c) { return AST.Pos.Minus(c); }),
        lit('!').andr(p.coord).to(function (c) { return AST.Pos.Skew(c); }),
        lit('.').andr(p.coord).to(function (c) { return AST.Pos.Cover(c); }),
        lit(',').andr(p.coord).to(function (c) { return AST.Pos.Then(c); }),
        lit(';').andr(p.coord).to(function (c) { return AST.Pos.SwapPAndC(c); }),
        lit('::').andr(p.coord).to(function (c) { return AST.Pos.SetYBase(c); }),
        lit(':').andr(p.coord).to(function (c) { return AST.Pos.SetBase(c); }),
        lit('**').andr(p.object).to(function (o) { return AST.Pos.ConnectObject(o); }),
        lit('*').andr(p.object).to(function (o) { return AST.Pos.DropObject(o); }),
        lit('?').andr(p.place).to(function (o) { return AST.Pos.Place(o); }),
        lit('@+').andr(p.coord).to(function (c) { return AST.Pos.PushCoord(c); }),
        lit('@-').andr(p.coord).to(function (c) { return AST.Pos.EvalCoordThenPop(c); }),
        lit('@=').andr(p.coord).to(function (c) { return AST.Pos.LoadStack(c); }),
        lit('@@').andr(p.coord).to(function (c) { return AST.Pos.DoCoord(c); }),
        lit('@i').to(function () { return AST.Pos.InitStack(); }),
        lit('@(').to(function () { return AST.Pos.EnterFrame(); }),
        lit('@)').to(function () { return AST.Pos.LeaveFrame(); }),
        lit('=:').andr(flit('"')).andr(p.id).andl(felem('"')).to(function (id) { return AST.Pos.SaveBase(id); }),
        lit('=@').andr(flit('"')).andr(p.id).andl(felem('"')).to(function (id) { return AST.Pos.SaveStack(id); }),
        lit('=').andr(flit('"')).andr(p.id).andl(felem('"')).to(function (id) { return AST.Pos.SavePos(id); }),
        lit('=').andr(p.nonemptyCoord).andl(flit('"')).and(p.id).andl(felem('"')).to(function (mid) { return AST.Pos.SaveMacro(mid.head, mid.tail); }),
        p.xyimport
      );
    }),
    
    // <coord> ::= <nonemptyCoord> | <empty>
    coord: memo(function () {
      return or(
        p.nonemptyCoord,
        success('empty').to(function () { return AST.Coord.C(); })
      );
    }),
    
    // <nonemptyCoord> ::= 'c' | 'p' | 'x' | 'y'
    //                 |   <vector>
    //                 |   '"' <id> '"'
    //                 |   '{' <pos> <decor> '}'
    //                 |   's' <digit>
    //                 |   's' '{' <nonnegative-number> '}'
    //                 |   '[' ('"'<prefix>'"')? <number> ',' <number> ']'
    //                 |   '[' ('"'<prefix>'"')? ( 'l' | 'r' | 'u' | 'd' )* ']'
    //                 |   '[' ('"'<prefix>'"')? ( 'l' | 'r' | 'u' | 'd' )+ <place> ']'
    nonemptyCoord: memo(function () {
      return or(
        lit('c').to(function () { return AST.Coord.C(); }), 
        lit('p').to(function () { return AST.Coord.P(); }), 
        lit('x').to(function () { return AST.Coord.X(); }), 
        lit('y').to(function () { return AST.Coord.Y(); }),
        p.vector().to(function (v) { return AST.Coord.Vector(v); }), 
        lit('"').andr(p.id).andl(felem('"')).to(function (id) { return AST.Coord.Id(id) }),
        lit('{').andr(p.posDecor).andl(flit('}')).to(function (pd) { return AST.Coord.Group(pd) }),
        lit('s').andr(fun(regexLit(/^\d/))).to(function (n) {
          return AST.Coord.StackPosition(parseInt(n));
        }),
        lit('s').andr(flit('{')).and(p.nonnegativeNumber).andl(flit('}')).to(function (n) {
          return AST.Coord.StackPosition(n);
        }),
        lit('[').andr(fun(
          opt(lit('"').andr(p.id).andl(felem('"'))).to(function (id) { return id.getOrElse(""); }) )
        ).and(p.number).andl(flit(",")).and(p.number).andl(flit("]")).to(function (prc) {
          return AST.Coord.DeltaRowColumn(prc.head.head, prc.head.tail, prc.tail);
        }),
        lit('[').andr(fun(
          opt(lit('"').andr(p.id).andl(felem('"'))).to(function (id) { return id.getOrElse(""); }) )
        ).and(fun(rep(regex(/^[lrud]/)))).andl(flit("]")).to(function (ph) {
          return AST.Coord.Hops(ph.head, ph.tail);
        }),
        lit('[').andr(fun(
          opt(lit('"').andr(p.id).andl(felem('"'))).to(function (id) { return id.getOrElse(""); }) )
        ).and(fun(rep1(regex(/^[lrud]/)))).and(p.place).andl(flit("]")).to(function (php) {
          return AST.Coord.DeltaRowColumn(php.head.head, php.head.tail, AST.Pos.Place(php.tail));
        })
      );
    }),
    
    // <vector> ::= '(' <factor> ',' <factor> ')'
    //          |   '<' <dimen> ',' <dimen> '>'
    //          |   '<' <dimen> '>'
    //          |   'a' '(' <number> ')'
    //          |   '/' <direction> <loose-dimen> '/'
    //          |   0
    //          |   <corner>
    //          |   <corner> '(' <factor> ')'
    vector: memo(function () {
      return or(
        lit('(').andr(p.factor).andl(flit(',')).and(p.factor).andl(flit(')')).to(
          function (xy) {
            return AST.Vector.InCurBase(xy.head, xy.tail);
          }
        ),
        lit('<').andr(p.dimen).andl(flit(',')).and(p.dimen).andl(flit('>')).to(
          function (xy) {
            return AST.Vector.Abs(xy.head, xy.tail);
          }
        ),
        lit('<').andr(p.dimen).andl(flit('>')).to(
          function (x) {
            return AST.Vector.Abs(x, x);
          }
        ),
        lit('a').andr(flit('(')).andr(p.number).andl(flit(')')).to(
          function (d) {
            return AST.Vector.Angle(d);
          }
        ),
        lit('/').andr(p.direction).and(p.looseDimen).andl(flit('/')).to(
          function (dd) {
            return AST.Vector.Dir(dd.head, dd.tail);
          }
        ),
        lit('0').to(function (x) { return AST.Vector.Abs("0mm", "0mm"); }),
        function () { return p.corner().and(fun(FP.Parsers.opt(
          fun(lit('(').andr(p.factor).andl(flit(')')))).to(function (f) {
            return f.getOrElse(1);
          }))).to(function (cf) {
            return AST.Vector.Corner(cf.head, cf.tail);
          })
        }
      );
    }),
    
    // <corner> ::= 'L' | 'R' | 'D' | 'U'
    //          | 'CL' | 'CR' | 'CD' | 'CU' | 'LC' | 'RC' | 'DC' | 'UC'
    //          | 'LD' | 'RD' | 'LU' | 'RU' | 'DL' | 'DR' | 'UL' | 'UR'
    //          | 'E' | 'P'
    //          | 'A'
    corner: memo(function () {
      return or(
        regexLit(/^(CL|LC)/).to(function () { return AST.Corner.CL(); }),
        regexLit(/^(CR|RC)/).to(function () { return AST.Corner.CR(); }),
        regexLit(/^(CD|DC)/).to(function () { return AST.Corner.CD(); }),
        regexLit(/^(CU|UC)/).to(function () { return AST.Corner.CU(); }),
        regexLit(/^(LD|DL)/).to(function () { return AST.Corner.LD(); }),
        regexLit(/^(RD|DR)/).to(function () { return AST.Corner.RD(); }),
        regexLit(/^(LU|UL)/).to(function () { return AST.Corner.LU(); }),
        regexLit(/^(RU|UR)/).to(function () { return AST.Corner.RU(); }),
        lit('L').to(function () { return AST.Corner.L(); }),
        lit('R').to(function () { return AST.Corner.R(); }),
        lit('D').to(function () { return AST.Corner.D(); }),
        lit('U').to(function () { return AST.Corner.U(); }),
        lit('E').to(function () { return AST.Corner.NearestEdgePoint(); }),
        lit('P').to(function () { return AST.Corner.PropEdgePoint(); }),
        lit('A').to(function () { return AST.Corner.Axis(); })
      );
    }),
    
    // <place> ::= '<' <place>
    //         | '>' <place>
    //         | '(' <factor> ')' <place>
    //         | '!' '{' <pos> '}' <slide>
    //         | <slide>
    place: memo(function () {
      return or(
        lit('<').andr(p.place).to(function (pl) {
          return AST.Place(1, 0, undefined, undefined).compound(pl);
        }), 
        lit('>').andr(p.place).to(function (pl) {
          return AST.Place(0, 1, undefined, undefined).compound(pl);
        }), 
        lit('(').andr(p.factor).andl(flit(')')).and(p.place).to(function (pl) {
          return AST.Place(0, 0, AST.Place.Factor(pl.head), undefined).compound(pl.tail);
        }), 
        lit('!').andr(flit('{')).andr(p.pos).andl(flit('}')).and(p.slide).to(function (ps) {
          return AST.Place(0, 0, AST.Place.Intercept(ps.head), ps.tail);
        }),
        function () { return p.slide().to(function (s) {
          return AST.Place(0, 0, undefined, s);
        }) }
      );
    }),
    
    // <slide> ::= '/' <dimen> '/'
    //         | <empty>
    slide: memo(function () {
      return or(
        lit('/').andr(p.dimen).andl(flit('/')).to(function (d) {
          return AST.Slide(FP.Option.Some(d));
        }),
        success("no slide").to(function () {
          return AST.Slide(FP.Option.empty);
        })
      );
    }),
    
    // <factor>
    factor: memo(fun(regexLit(/^[+\-]?(\d+(\.\d*)?|\d*\.\d+)/).to(
      function (v) { return parseFloat(v); })
    )),
    
    // <number>
    number: memo(fun(regexLit(/^[+\-]?\d+/).to(
      function (n) { return parseInt(n); })
    )),
    
    // <nonnegative-number>
    nonnegativeNumber: memo(fun(regexLit(/^\d+/).to(
      function (n) { return parseInt(n); })
    )),
    
    unit: memo(fun(regexLit(/^(em|ex|px|pt|pc|in|cm|mm|mu)/).to(function (d) {
        return d
    }))),
    
    // <dimen> ::= <factor> ( 'em' | 'ex' | 'px' | 'pt' | 'pc' | 'in' | 'cm' | 'mm' | 'mu' )
    dimen: memo(function () {
      return p.factor().and(p.unit).to(function (x) {
        return x.head.toString() + x.tail;
      })
    }),
    
    // <loose-dimen> ::= <loose-factor> ( 'em' | 'ex' | 'px' | 'pt' | 'pc' | 'in' | 'cm' | 'mm' | 'mu' )
    looseDimen: memo(function () {
      return p.looseFactor().and(p.unit).to(function (x) {
        return x.head.toString() + x.tail;
      })
    }),
    
    // <loose-factor>
    // makeshift against /^ 3.5mm/ converted to /^ 3 .5mm/ by MathJax.InputJax.TeX.prefilterMath()
    looseFactor: memo(fun(or(
      regexLit(/^(\d \d*(\.\d*))/).to(function (v) {
        return parseFloat(v.replace(/ /, ""));
      }),
      regexLit(/^[+\-]?(\d+(\.\d*)?|\d*\.\d+)/).to(function (v) {
        return parseFloat(v);
      })
    ))),
    
    // <id>
    id: memo(fun(regex(/^[^"]+/))), // "
    
    // <object> ::= <modifier>* <objectbox>
    object: memo(function () {
      return or(
        rep(p.modifier).and(p.objectbox).to(function (mso) {
          return AST.Object(mso.head, mso.tail);
        })
      );
    }),
    
    // <objectbox> ::= '{' <text> '}'
    //          | '@' <dir>
    //          | '\dir' <dir>
    //          | '\cir' <cir_radius> '{' <cir> '}'
    //          | '\frm' <frame_radius> '{' <frame_main> '}'
    //          | '\object' <object>
    //          | '\composite' '{' <composite_object> '}'
    //          | '\xybox' '{' <pos> <decor> '}'
    //          | '\xymatrix' <xymatrix>
    //          | <curve>
    //          | <TeX box> '{' <text> '}'
    objectbox: memo(function () {
      return or(
        p.mathText,
        lit("@").andr(p.dir),
        lit("\\dir").andr(p.dir),
        lit("\\cir").andr(p.cirRadius).andl(flit("{")).and(p.cir).andl(flit("}")).to(function (rc) {
          return AST.ObjectBox.Cir(rc.head, rc.tail);
        }),
        lit("\\frm").andr(p.frameRadius).andl(flit("{")).and(p.frameMain).andl(flit("}")).to(function (rm) {
          return AST.ObjectBox.Frame(rm.head, rm.tail);
        }),
        lit("\\object").andr(p.object).to(function (o) {
          return AST.ObjectBox.WrapUpObject(o);
        }),
        lit("\\composite").and(flit("{")).andr(p.compositeObject).andl(flit("}")).to(function (os) {
          return AST.ObjectBox.CompositeObject(os);
        }),
        lit("\\xybox").and(flit("{")).andr(p.posDecor).andl(flit("}")).to(function (pd) {
          return AST.ObjectBox.Xybox(pd);
        }),
        lit("\\xymatrix").andr(p.xymatrix).to(function (m) {
          return AST.ObjectBox.Xymatrix(m);
        }),
        p.txt,
        p.curve,
        regex(/^(\\[a-zA-Z@][a-zA-Z0-9@]+)/).andl(flit("{")).and(p.text).andl(flit("}")).to(function (bt) {
          return p.toMath(bt.head + "{" + bt.tail + "}");
        })
      );
    }),
    
    // <composite_object> ::= <object> ( '*' <object> )*
    compositeObject: memo(function () {
      return p.object().and(fun(rep(lit("*").andr(p.object)))).to(function (oos) {
        return oos.tail.prepend(oos.head);
      });
    }),
    
    // <math-text> ::= '{' <text> '}'
    mathText: memo(function () {
      return lit("{").andr(p.text).andl(felem("}")).to(function (text) {
        return p.toMath("\\hbox{$\\objectstyle{" + text + "}$}");
      });
    }),
    toMath: function (math) {
      var mml = TEX.Parse(math).mml();
      if (mml.inferred) {
        mml = MML.apply(MathJax.ElementJax, mml.data);
      } else {
        mml = MML(mml);
      }
      TEX.combineRelations(mml.root);
      return AST.ObjectBox.Text(mml.root);
    },
    
    // <text> ::= /[^{}\\%]*/ (( '\{' | '\}' | '\%' | '\\' | '{' <text> '}' | /%[^\r\n]*(\r\n|\r|\n)?/ ) /[^{}\\%]*/ )*
    text: memo(function () {
      return regex(/^[^{}\\%]*/).and(function () {
        return (
          or(
            regex(/^(\\\{|\\\}|\\%|\\)/).to(function (x) {
              return x;
            }),
            elem("{").andr(p.text).andl(felem("}")).to(function (x) {
              return "{" + x + "}";
            }),
            regex(/^%[^\r\n]*(\r\n|\r|\n)?/).to(function (x) {
              return ' '; // ignore comments
            })
          ).and(fun(regex(/^[^{}\\%]*/)))).rep().to(function (xs) {
            var res = "";
            xs.foreach(function (x) {
              res += x.head + x.tail;
            });
            return res;
        })
      }).to(function (x) {
        return x.head + x.tail
      });
    }),
    
    txt: memo(function () {
      return lit("\\txt").andr(p.txtWidth).and(fun(regex(/^(\\[a-zA-Z@][a-zA-Z0-9@]+)?/))).andl(flit("{")).and(p.text).andl(flit("}")).to(function (wst) {
          var width = wst.head.head;
          var style = wst.head.tail;
          var text = wst.tail;
          var math;
          var lines = text.split("\\\\");
          if (lines.length <= 1) {
            math = style + "{\\hbox{" + text + "}}";
          } else {
            math = "\\hbox{$\\begin{array}{c}\n";
            for (var i = 0; i < lines.length; i++) {
              math += style + "{\\hbox{" + lines[i].replace(/(^[\r\n\s]+)|([\r\n\s]+$)/g, "") + "}}";
              if (i != lines.length - 1) {
                math += "\\\\\n";
              }
            }
            math += "\\end{array}$}";
          }
          return AST.ObjectBox.Txt(width, p.toMath(math));
        });
    }),
    
    // <txt_width> ::= '<' <dimen> '>'
    //          | <empty>
    txtWidth: memo(function () {
      return or(
        lit('<').andr(p.dimen).andl(flit('>')).to(
          function (x) {
            return AST.Vector.Abs(x, x);
          }
        ).to(function (v) {
          return AST.ObjectBox.Txt.Width.Vector(v);
        }),
        success("default").to(function () {
          return AST.ObjectBox.Txt.Width.Default();
        })
      );
    }),
    
    // <dir> ::= <variant> '{' <main> '}'
    // <variant> ::= '^' | '_' | '0' | '1' | '2' | '3' | <empty>
    dir: memo(function () {
      return regexLit(/^[\^_0123]/).opt().andl(flit('{')).and(p.dirMain).andl(flit('}')).to(function (vm) {
        return AST.ObjectBox.Dir(vm.head.getOrElse(""), vm.tail);
      })
    }),
    
    // <main> ::= ('-' | '.' | '~' | '>' | '<' | '(' | ')' | '`' | "'" | '|' | '*' | '+' | 'x' | '/' | 'o' | '=' | ':' | /[a-zA-Z@ ]/)*
    dirMain: memo(function () {
      return regex(/^(-|\.|~|>|<|\(|\)|`|'|\||\*|\+|x|\/|o|=|:|[a-zA-Z@ ])+/ /*'*/).opt().to(function (m) {
        return m.getOrElse("");
      })
    }),
    
    // <cir_radius> ::= <vector>
    //          | <empty>
    cirRadius: memo(function () {
      return or(
        p.vector().to(function (v) {
          return AST.ObjectBox.Cir.Radius.Vector(v);
        }),
        success("default").to(function () {
          return AST.ObjectBox.Cir.Radius.Default();
        })
      );
    }),
    
    // <frame_radius> ::= <frame_radius_vector>
    //          | <empty>
    frameRadius: memo(function () {
      return or(
        p.frameRadiusVector().to(function (v) {
          return AST.ObjectBox.Frame.Radius.Vector(v);
        }),
        success("default").to(function () {
          return AST.ObjectBox.Frame.Radius.Default();
        })
      );
    }),

    // <frame_radius_vector> ::= '<' <dimen> ',' <dimen> '>'
    //          |   '<' <dimen> '>'
    frameRadiusVector: memo(function () {
      return or(
        lit('<').andr(p.dimen).andl(flit(',')).and(p.dimen).andl(flit('>')).to(
          function (xy) {
            return AST.Vector.Abs(xy.head, xy.tail);
          }
        ),
        lit('<').andr(p.dimen).andl(flit('>')).to(
          function (x) {
            return AST.Vector.Abs(x, x);
          }
        )
      );
    }),
    
    // <frame_main> ::= ( '-' | '=' | '.' | ',' | 'o' | 'e' | '*' )*
    //          | ( '_' | '^' )? ( '\{' | '\}' | '(' | ')' )
    frameMain: memo(function () {
      return regex(/^(((_|\^)?(\\\{|\\\}|\(|\)))|[\-=oe,\.\*]*)/);
    }),
    
    // <cir> ::= <diag> <orient> <diag>
    //       | <empty>
    cir: memo(function () {
      return or(
        p.nonemptyCir,
        success("full").to(function () {
          return AST.ObjectBox.Cir.Cir.Full();
        })
      );
    }),
    nonemptyCir: memo(function () {
      return p.diag().and(fun(regexLit(/^[_\^]/))).and(p.diag).to(function (dod) {
        return AST.ObjectBox.Cir.Cir.Segment(dod.head.head, dod.head.tail, dod.tail);
      });
    }),
    
    // <curve> ::= '\crv' <curve-modifier> '{' <curve-object> <curve-poslist> '}'
    curve: memo(function () {
      return lit("\\crv").andr(p.curveModifier).andl(flit("{")).and(p.curveObject).and(p.curvePoslist).andl(flit("}")).to(function (mop) {
        return AST.ObjectBox.Curve(mop.head.head, mop.head.tail, mop.tail);
      });
    }),
    
    // <curve-modifier> ::= ( '~' <curve-option> )*
    curveModifier: memo(function () {
      return rep(fun(lit("~").andr(p.curveOption)));
    }),
    
    // <curve-option> ::= 'p' | 'P' | 'l' | 'L' | 'c' | 'C'
    //                |   'pc' | 'pC' | 'Pc' | 'PC'
    //                |   'lc' | 'lC' | 'Lc' | 'LC'
    //                |   'cC'
    curveOption: memo(function () {
      return or(
        lit("p").to(function () { return AST.ObjectBox.Curve.Modifier.p(); }),
        lit("P").to(function () { return AST.ObjectBox.Curve.Modifier.P(); }),
        lit("l").to(function () { return AST.ObjectBox.Curve.Modifier.l(); }),
        lit("L").to(function () { return AST.ObjectBox.Curve.Modifier.L(); }),
        lit("c").to(function () { return AST.ObjectBox.Curve.Modifier.c(); }),
        lit("C").to(function () { return AST.ObjectBox.Curve.Modifier.C(); }),
        lit("pc").to(function () { return AST.ObjectBox.Curve.Modifier.pc(); }),
        lit("pC").to(function () { return AST.ObjectBox.Curve.Modifier.pC(); }),
        lit("Pc").to(function () { return AST.ObjectBox.Curve.Modifier.Pc(); }),
        lit("PC").to(function () { return AST.ObjectBox.Curve.Modifier.PC(); }),
        lit("lc").to(function () { return AST.ObjectBox.Curve.Modifier.lc(); }),
        lit("lC").to(function () { return AST.ObjectBox.Curve.Modifier.lC(); }),
        lit("Lc").to(function () { return AST.ObjectBox.Curve.Modifier.Lc(); }),
        lit("LC").to(function () { return AST.ObjectBox.Curve.Modifier.LC(); }),
        lit("cC").to(function () { return AST.ObjectBox.Curve.Modifier.cC(); })
      );
    }),
    
    // <curve-object> ::= <empty>
    //                |   '~*' <object> <curve-object>
    //                |   '~**' <object> <curve-object>
    curveObject: memo(function () {
      return rep(or(
        lit("~*").andr(p.object).to(function (obj) {
          return AST.ObjectBox.Curve.Object.Drop(obj);
        }),
        lit("~**").andr(p.object).to(function (obj) {
          return AST.ObjectBox.Curve.Object.Connect(obj);
        })
      ));
    }),
    
    // <curve-poslist> ::= <empty> ^^ Empty List
    //           |   '&' <curve-poslist2> ^^ (c, <poslist>)
    //           |   <nonemptyPos> ^^ (<nonemptyPos>, Nil)
    //           |   <nonemptyPos> '&' <curve-poslist2> ^^ (<nonemptyPos>, <poslist>)
    //           |   '~@' ^^ (~@, Nil)
    //           |   '~@' '&' <curve-poslist2> ^^ (~@, <poslist>)
    // <curve-poslist2> ::= <empty> ^^ (c, Nil)
    //           |   '&' <curve-poslist2> ^^ (c, <poslist>)
    //           |   <nonemptyPos> ^^ (<nonemptyPos>, Nil)
    //           |   <nonemptyPos> '&' <curve-poslist2> ^^ (<nonemptyPos>, <poslist>)
    //           |   '~@' ^^ (~@, Nil)
    //           |   '~@' '&' <curve-poslist2> ^^ (~@, <poslist>)
    curvePoslist: memo(function () {
      return or(
        lit("&").andr(p.curvePoslist2).to(function (ps) {
          return ps.prepend(AST.ObjectBox.Curve.PosList.CurPos());
        }),
        lit("~@").andr(flit("&")).andr(p.curvePoslist2).to(function (ps) {
          return ps.prepend(AST.ObjectBox.Curve.PosList.AddStack());
        }),
        lit("~@").to(function () {
          return FP.List.empty.prepend(AST.ObjectBox.Curve.PosList.AddStack());
        }),
        p.pos().andl(flit("&")).and(p.curvePoslist2).to(function (pps) {
          return pps.tail.prepend(AST.ObjectBox.Curve.PosList.Pos(pps.head));
        }),
        p.nonemptyPos().to(function (p) {
          return FP.List.empty.prepend(AST.ObjectBox.Curve.PosList.Pos(p));
        }),
        success("empty").to(function () {
          return FP.List.empty;
        })
      );
    }),
    curvePoslist2: memo(function () {
      return or(
        lit("&").andr(p.curvePoslist2).to(function (ps) {
          return ps.prepend(AST.ObjectBox.Curve.PosList.CurPos());
        }),
        lit("~@").andr(flit("&")).andr(p.curvePoslist2).to(function (ps) {
          return ps.prepend(AST.ObjectBox.Curve.PosList.AddStack());
        }),
        lit("~@").to(function () {
          return FP.List.empty.prepend(AST.ObjectBox.Curve.PosList.AddStack());
        }),
        p.nonemptyPos().andl(flit("&")).and(p.curvePoslist2).to(function (pps) {
          return pps.tail.prepend(AST.ObjectBox.Curve.PosList.Pos(pps.head));
        }),
        p.nonemptyPos().to(function (p) {
          return FP.List.empty.prepend(AST.ObjectBox.Curve.PosList.Pos(p));
        }),
        success("empty").to(function () {
          return FP.List.empty.prepend(AST.ObjectBox.Curve.PosList.CurPos());
        })
      );
    }),
    
    // <modifier> ::= '!' <vector>
    //            |   '[' <shape> ']'
    //            |   'i'
    //            |   'h'
    //            |   <add-op> <size>
    //            |   <nonemptyDirection>
    modifier: memo(function () {
      return or(
        lit("!").andr(p.vector).to(function (v) {
          return AST.Modifier.Vector(v);
        }),
        lit("!").to(function (v) {
          return AST.Modifier.RestoreOriginalRefPoint();
        }),
        lit("[").andr(p.shape).andl(flit("]")).to(function (s) {
          return s;
        }),
        lit("i").to(function (v) {
          return AST.Modifier.Invisible();
        }),
        lit("h").to(function (v) {
          return AST.Modifier.Hidden();
        }),
        p.addOp().and(p.size).to(function (os) {
          return AST.Modifier.AddOp(os.head, os.tail);
        }),
        p.nonemptyDirection().to(function (d) {
          return AST.Modifier.Direction(d);
        })
      );
    }),
    
    // <add-op> ::= '+' | '-' | '=' | '+=' | '-='
    addOp: memo(function () {
      return or(
        lit("+=").to(function () { return AST.Modifier.AddOp.GrowTo(); }),
        lit("-=").to(function () { return AST.Modifier.AddOp.ShrinkTo(); }),
        lit("+").to(function () { return AST.Modifier.AddOp.Grow(); }),
        lit("-").to(function () { return AST.Modifier.AddOp.Shrink(); }),
        lit("=").to(function () { return AST.Modifier.AddOp.Set(); })
      );
    }),
    
    // <size> ::= <vector> | <empty>
    size: memo(function () {
      return or(
        function () { return p.vector().to(function (v) {
          return AST.Modifier.AddOp.VactorSize(v);
        }) },
        success("default size").to(function () {
          return AST.Modifier.AddOp.DefaultSize();
        })
      );
    }),
    
    // <shape> ::= '.' 
    //          | <frame_shape>
    //          | <alphabets>
    //          | '=' <alphabets>
    //          | <empty>
    shape: memo(function () {
      return or(
        lit(".").to(function () { return AST.Modifier.Shape.Point(); }),
        p.frameShape,
        p.alphabets().to(function (name) {
          return AST.Modifier.Shape.Alphabets(name);
        }),
        lit("=").andr(p.alphabets).to(function (name) {
          return AST.Modifier.Shape.DefineShape(name);
        }),
        success("rect").to(function () { return AST.Modifier.Shape.Rect(); })
      );
    }),
    
    // <frame_shape> ::= 'F' <frame_main> ( ':' ( <frame_radius_vector> | <color_name> ))*
    frameShape: memo(function () {
      return lit("F").andr(p.frameMain).and(fun(
        rep(lit(":").andr(fun(
          or(
            p.frameRadiusVector().to(function (v) {
              return AST.Modifier.Shape.Frame.Radius(v);
            }),
            p.colorName().to(function (c) {
                return AST.Modifier.Shape.Frame.Color(c);
            })
          )
        )))
      )).to(function (mo) {
        var main = mo.head;
        if (main === "") {
          main = "-";
        }
        return AST.Modifier.Shape.Frame(main, mo.tail);
      });
    }),
    
    // <alphabets> ::= /[a-zA-Z]+/
    alphabets: memo(function () {
      return regex(/^([a-zA-Z]+)/);
    }),
    
    // <color_name> ::= /[a-zA-Z][a-zA-Z0-9]*/
    colorName: memo(function () {
      return regex(/^([a-zA-Z][a-zA-Z0-9]*)/);
    }),
    
    // <direction> ::= <direction0> <direction1>*
    // <direction0> ::= <direction2>
    //              |   <diag>
    // <direction1> | ':' <vector>
    //              | '_'
    //              | '^'
    // <direction2> ::= 'v' <vector>
    //              |   'q' '{' <pos> <decor> '}'
    direction: memo(function () {
      return seq(p.direction0, rep(p.direction1)).to(function (drs){
        return AST.Direction.Compound(drs.head, drs.tail);
      });
    }),
    direction0: memo(function () {
      return or(
        p.direction2,
        p.diag().to(function (d) {
          return AST.Direction.Diag(d);
        })
      );
    }),
    direction1: memo(function () {
      return or(
        lit(':').andr(p.vector).to(function (v) {
          return AST.Direction.RotVector(v);
        }),
        lit('_').to(function (x) {
          return AST.Direction.RotAntiCW();
        }),
        lit('^').to(function (x) {
          return AST.Direction.RotCW();
        })
      );
    }),
    direction2: memo(function () {
      return or(
        lit('v').andr(p.vector).to(function (v) {
          return AST.Direction.Vector(v);
        }),
        lit('q').andr(flit('{')).andr(p.posDecor).andl(flit('}')).to(function (pd) {
          return AST.Direction.ConstructVector(pd);
        })
      );
    }),
    
    // <nonempty-direction> ::= <nonempty-direction0> <direction1>*
    //                      |   <direction0> <direction1>+
    // <nonempty-direction0> ::= <nonempty-diag>
    //                       |   <direction2>
    nonemptyDirection: memo(function () {
      return or(
        seq(p.nonemptyDirection0, rep(p.direction1)),
        seq(p.direction0, rep1(p.direction1))
      ).to(function (drs){
        return AST.Direction.Compound(drs.head, drs.tail);
      });
    }),
    nonemptyDirection0: memo(function () {
      return or(
        p.direction2,
        p.nonemptyDiag().to(function (d) {
          return AST.Direction.Diag(d);
        })
      );
    }),
    
    // <diag> ::= <nonempty-diag> | <empty>
    // <nonempty-diag> ::= 'l' | 'r' | 'd' | 'u' | 'ld' | 'rd' | 'lu' | 'ru'
    diag: memo(function () {
      return or(
        p.nonemptyDiag,
        success("empty").to(function (x) {
          return AST.Diag.Default();
        })
      );
    }),
    nonemptyDiag: memo(function () {
      return or(
        regexLit(/^(ld|dl)/).to(function (x) { return AST.Diag.LD(); }),
        regexLit(/^(rd|dr)/).to(function (x) { return AST.Diag.RD(); }),
        regexLit(/^(lu|ul)/).to(function (x) { return AST.Diag.LU(); }),
        regexLit(/^(ru|ur)/).to(function (x) { return AST.Diag.RU(); }),
        lit('l').to(function (x) { return AST.Diag.L(); }),
        lit('r').to(function (x) { return AST.Diag.R(); }),
        lit('d').to(function (x) { return AST.Diag.D(); }),
        lit('u').to(function (x) { return AST.Diag.U(); })
      );
    }),
    
    // <decor> ::= <command>*
    decor: memo(function () {
      return p.command().rep().to(function (cs) {
        return AST.Decor(cs);
      })
    }),
    
    // <command> ::= '\ar' ( <arrow_form> )* <path>
    //           |   '\xymatrix' <xymatrix>
    //           |   '\PATH' <path>
    //           |   '\afterPATH' '{' <decor> '}' <path>
    //           |   '\save' <pos>
    //           |   '\restore'
    //           |   '\POS' <pos>
    //           |   '\afterPOS' '{' <decor> '}' <pos>
    //           |   '\drop' <object>
    //           |   '\connect' <object>
    //           |   '\relax'
    //           |   '\xyignore' '{' <pos> <decor> '}'
    //           |   <twocell command>
    command: memo(function () {
      return or(
        lit("\\ar").andr(fun(rep(p.arrowForm))).and(p.path).to(function (fsp) {
          return AST.Command.Ar(fsp.head, fsp.tail);
        }),
        lit("\\xymatrix").andr(p.xymatrix),
        lit("\\PATH").andr(p.path).to(function (path) {
          return AST.Command.Path(path);
        }),
        lit("\\afterPATH").andr(flit('{')).andr(p.decor).andl(flit('}')).and(p.path).to(function (dp) {
          return AST.Command.AfterPath(dp.head, dp.tail);
        }),
        lit("\\save").andr(p.pos).to(function (pos) {
          return AST.Command.Save(pos);
        }),
        lit("\\restore").to(function () {
          return AST.Command.Restore();
        }),
        lit("\\POS").andr(p.pos).to(function (pos) {
          return AST.Command.Pos(pos);
        }),
        lit("\\afterPOS").andr(flit('{')).andr(p.decor).andl(flit('}')).and(p.pos).to(function (dp) {
          return AST.Command.AfterPos(dp.head, dp.tail);
        }),
        lit("\\drop").andr(p.object).to(function (obj) {
          return AST.Command.Drop(obj);
        }),
        lit("\\connect").andr(p.object).to(function (obj) {
          return AST.Command.Connect(obj);
        }),
        lit("\\relax").to(function () {
          return AST.Command.Relax();
        }),
        lit("\\xyignore").andr(flit('{')).andr(p.pos).and(p.decor).andl(flit('}')).to(function (pd) {
          return AST.Command.Ignore(pd.head, pd.tail);
        }),
        lit("\\xyshowAST").andr(flit('{')).andr(p.pos).and(p.decor).andl(flit('}')).to(function (pd) {
          return AST.Command.ShowAST(pd.head, pd.tail);
        }),
        p.twocellCommand
      );
    }),
    
    // <arrow_form> ::= '@' <conchar>
    //              |   '@' '!'
    //              |   '@' '/' <direction> ( <loose-dimen> )? '/'
    //              |   '@' '(' <direction> ',' <direction> ')'
    //              |   '@' '`' '{' <curve-poslist> '}'
    //              |   '@' '[' <shape> ']'
    //              |   '@' '*' '{' ( <modifier> )* '}'
    //              |   '@' '<' <dimen> '>'
    //              |   '|' <anchor> <it>
    //              |   '^' <anchor> <it>
    //              |   '_' <anchor> <it>
    //              |   '@' '?'
    //              |   '@' <variant> ( <tip_conn_tip> )?
    // <conchar> ::= /[\-\.~=:]/
    // <variant> ::= /[\^_0123]/ | <empty>
    arrowForm: memo(function () {
      return or(
        lit("@").andr(fun(regex(/^([\-\.~=:])/))).to(function (c) {
          return AST.Command.Ar.Form.ChangeStem(c);
        }),
        lit("@").andr(flit("!")).to(function (c) {
          return AST.Command.Ar.Form.DashArrowStem();
        }),
        lit("@").andr(flit("/")).andr(p.direction).and(fun(opt(p.looseDimen))).andl(flit("/")).to(function (dd) {
          return AST.Command.Ar.Form.CurveArrow(dd.head, dd.tail.getOrElse(".5pc"));
        }),
        lit("@").andr(flit("(")).andr(p.direction).andl(flit(",")).and(p.direction).andl(flit(")")).to(function (dd) {
          return AST.Command.Ar.Form.CurveFitToDirection(dd.head, dd.tail);
        }),
        lit("@").andr(flit("`")).andr(p.coord).to(function (c) {
          return AST.Command.Ar.Form.CurveWithControlPoints(c);
        }),
        lit("@").andr(flit("[")).andr(p.shape).andl(flit("]")).to(function (s) {
          return AST.Command.Ar.Form.AddShape(s);
        }),
        lit("@").andr(flit("*")).andr(flit("{")).andr(fun(rep(p.modifier))).andl(flit("}")).to(function (ms) {
          return AST.Command.Ar.Form.AddModifiers(ms);
        }),
        lit("@").andr(flit("<")).andr(p.dimen).andl(flit(">")).to(function (d) {
          return AST.Command.Ar.Form.Slide(d);
        }),
        lit("|").andr(p.anchor).and(p.it).to(function (ai) {
          return AST.Command.Ar.Form.LabelAt(ai.head, ai.tail);
        }),
        lit("^").andr(p.anchor).and(p.it).to(function (ai) {
          return AST.Command.Ar.Form.LabelAbove(ai.head, ai.tail);
        }),
        lit("_").andr(p.anchor).and(p.it).to(function (ai) {
          return AST.Command.Ar.Form.LabelBelow(ai.head, ai.tail);
        }),
        lit("@").andr(flit("?")).to(function () {
          return AST.Command.Ar.Form.ReverseAboveAndBelow();
        }),
        lit("@").andr(fun(regex(/^([\^_0123])/).opt())).and(fun(opt(p.tipConnTip))).to(function (vtct) {
          var variant = vtct.head.getOrElse("");
          if (vtct.tail.isDefined) {
            var tct = vtct.tail.get;
            return AST.Command.Ar.Form.BuildArrow(variant, tct.tail, tct.stem, tct.head);
          } else {
            return AST.Command.Ar.Form.ChangeVariant(variant);
          }
        })
      );
    }),
    
    // <tip_conn_tip> ::= '{' <nonempty_tip>? <nonempty_conn>? <nonempty_tip>? '}'
    tipConnTip: memo(function () {
      return lit("{").andr(fun(opt(p.nonemptyTip))).and(fun(opt(p.nonemptyConn))).and(fun(opt(p.nonemptyTip))).andl(flit("}")).to(function (pcp) {
        var maybeTail = pcp.head.head;
        var maybeStem = pcp.head.tail;
        var maybeHead = pcp.tail;
        
        var emptyTip = AST.Command.Ar.Form.Tip.Tipchars("");
        var tail, stem, head;
        if (!maybeStem.isDefined && !maybeHead.isDefined) {
          if (!maybeTail.isDefined) {
            tail = emptyTip;
            stem = AST.Command.Ar.Form.Conn.Connchars("");
            head = emptyTip;
          } else {
            tail = emptyTip;
            stem = AST.Command.Ar.Form.Conn.Connchars("-");
            head = maybeTail.getOrElse(emptyTip);
          }
        } else {
          tail = maybeTail.getOrElse(emptyTip);
          stem = maybeStem.getOrElse(AST.Command.Ar.Form.Conn.Connchars(""));
          head = maybeHead.getOrElse(emptyTip);
        }
        return {
          tail:tail,
          stem:stem,
          head:head
        };
      });
    }),
    
    // <nonempty_tip> ::= /[<>()|'`+/a-zA-Z ]+/
    //         | <arrow_dir>
    // <arrow_dir> ::= '*' <object>
    //               | <dir>
    nonemptyTip: memo(function () {
      return or(
        regex(/^([<>()|'`+\/a-zA-Z ]+)/).to(function (cs) {
          return AST.Command.Ar.Form.Tip.Tipchars(cs);
        }),
        lit("*").andr(p.object).to(function (o) {
          return AST.Command.Ar.Form.Tip.Object(o);
        }),
        p.dir().to(function (d) {
          return AST.Command.Ar.Form.Tip.Dir(d);
        })
      );
    }),
    
    // <nonempty_conn> ::= /[\-\.~=:]+/
    //          | <arrow_dir>
    nonemptyConn: memo(function () {
      return or(
        regex(/^([\-\.~=:]+)/).to(function (cs) {
          return AST.Command.Ar.Form.Conn.Connchars(cs);
        }),
        lit("*").andr(p.object).to(function (o) {
          return AST.Command.Ar.Form.Conn.Object(o);
        }),
        p.dir().to(function (d) {
          return AST.Command.Ar.Form.Conn.Dir(d);
        })
      );
    }),
    
    // <path> ::= <path2>(Nil)
    path: memo(function () {
      return p.path2(FP.List.empty /* initial failure continuation */).to(function (ps) {
        return AST.Command.Path.Path(ps);
      })
    }),
    
    // <path2>(fc) ::= '~' <action> '{' <pos> <decor> '}' <path2>(fc)
    //             |   '~' <which> '{' <labels> '}' <path2>(fc)
    //             |   "'" <segment> <path2>(fc)
    //             |   '`' <turn> <segment> <path2>(fc)
    //             |   '~' '{' <path2 as fc'> '}' <path2>(fc')
    //             |   <segment>
    //             |   <empty>
    // <action> ::= '=' | '/'
    // <which> ::= '<' | '>' | '+'
    path2: function (fc) {
      var q = memo(function () { return p.path2(fc) });
      return or(
        p.path3().and(q).to(function (ep) {
          return ep.tail.prepend(ep.head);
        }),
        seq('~', '{', q, '}').to(function (newFc) {
          return newFc.head.tail;
        }).into(function (newFc) {
          return p.path2(newFc);
        }),
        p.segment().to(function (s) {
          return FP.List.empty.prepend(AST.Command.Path.LastSegment(s));
        }),
        success(fc).to(function (fc) {
          return fc;
        })
      );
    },
    path3: memo(function () {
      return or(
        seq('~', '=', '{', p.posDecor, '}').to(function (pd) {
          return AST.Command.Path.SetBeforeAction(pd.head.tail);
        }),
        seq('~', '/', '{', p.posDecor, '}').to(function (pd) {
          return AST.Command.Path.SetAfterAction(pd.head.tail);
        }),
        seq('~', '<', '{', p.labels, '}').to(function (ls) {
          return AST.Command.Path.AddLabelNextSegmentOnly(ls.head.tail);
        }),
        seq('~', '>', '{', p.labels, '}').to(function (ls) {
          return AST.Command.Path.AddLabelLastSegmentOnly(ls.head.tail);
        }),
        seq('~', '+', '{', p.labels, '}').to(function (ls) {
          return AST.Command.Path.AddLabelEverySegment(ls.head.tail);
        }),
        seq("'", p.segment).to(function (s) {
          return AST.Command.Path.StraightSegment(s.tail);
        }),
        seq('`', p.turn, p.segment).to(function (ts) {
          return AST.Command.Path.TurningSegment(ts.head.tail, ts.tail);
        })
      );
    }),
    
    // <turn> ::= <diag> <turn-radius>
    //        |   <cir> <turnradius>
    turn: memo(function () {
      return or(
        p.nonemptyCir().and(p.turnRadius).to(function (cr) {
          return AST.Command.Path.Turn.Cir(cr.head, cr.tail);
        }),
        p.diag().and(p.turnRadius).to(function (dr) {
          return AST.Command.Path.Turn.Diag(dr.head, dr.tail);
        })
      );
    }),
    
    // <turn-radius> ::= <empty> | '/' <dimen>
    turnRadius: memo(function () {
      return or(
        lit('/').andr(p.dimen).to(function (d) {
          return AST.Command.Path.TurnRadius.Dimen(d);
        }),
        success("default").to(function () {
          return AST.Command.Path.TurnRadius.Default();
        })
      );
    }),
    
    // <segment> ::= <nonempty-pos> <slide> <labels>
    segment: memo(function () {
      return p.nonemptyPos().and(p.pathSlide).and(p.labels).to(function (psl) {
        return AST.Command.Path.Segment(psl.head.head, psl.head.tail, psl.tail);
      });
    }),
    
    // <slide> ::= '<' <dimen> '>'
    //         | <empty>
    pathSlide: memo(function () {
      return or(
        lit('<').andr(p.dimen).andl(flit('>')).to(function (d) {
          return AST.Slide(FP.Option.Some(d));
        }),
        success("no slide").to(function () {
          return AST.Slide(FP.Option.empty);
        })
      );
    }),
    
    // <labels> ::= <label>*
    labels: memo(function () {
      return p.label().rep().to(function (ls) {
        return AST.Command.Path.Labels(ls);
      });
    }),
    
    // <label> ::= '^' <anchor> <it> <alias>?
    // <label> ::= '_' <anchor> <it> <alias>?
    // <label> ::= '|' <anchor> <it> <alias>?
    label: memo(function () {
      return or(
        seq('^', p.anchor, p.it, p.alias).to(function (aia) {
          return AST.Command.Path.Label.Above(AST.Pos.Place(aia.head.head.tail), aia.head.tail, aia.tail);
        }),
        seq('_', p.anchor, p.it, p.alias).to(function (aia) {
          return AST.Command.Path.Label.Below(AST.Pos.Place(aia.head.head.tail), aia.head.tail, aia.tail);
        }),
        seq('|', p.anchor, p.it, p.alias).to(function (aia) {
          return AST.Command.Path.Label.At(AST.Pos.Place(aia.head.head.tail), aia.head.tail, aia.tail);
        })
      );
    }),
    
    // <anchor> ::= '-' <anchor> | <place>
    anchor: memo(function () {
      return or(
        lit('-').andr(p.anchor).to(function (a) {
          return AST.Place(1, 1, AST.Place.Factor(0.5), undefined).compound(a);
        }),
        p.place
      );
    }),
    
    // <it> ::= ( '[' <shape> ']' )* <it2>
    it: memo(function () {
      return rep(lit('[').andr(p.shape).andl(flit(']')).to(function (s) {
        return s;
      })).and(p.it2).to(function (si) {
        return AST.Object(si.head.concat(si.tail.modifiers), si.tail.object);
      });
    }),
    
    // <it2> ::= <digit> | <letter>
    //       |   '{' <text> '}'
    //       |   '\' <letters>
    //       |   '*' <object>
    //       |   '@' <dir>
    it2: memo(function () {
      return or(
        regexLit(/^[0-9a-zA-Z]/).to(function (c) {
          return AST.Object(FP.List.empty, p.toMath("\\labelstyle " + c));
        }),
        regexLit(/^(\\[a-zA-Z][a-zA-Z0-9]*)/).to(function (c) {
          return AST.Object(FP.List.empty, p.toMath("\\labelstyle " + c));
        }),
        lit("{").andr(p.text).andl(felem("}")).to(function (t) {
          return AST.Object(FP.List.empty, p.toMath("\\labelstyle " + t));
        }),
        lit('*').andr(p.object),
        lit('@').andr(p.dir).to(function (dir) {
          return AST.Object(FP.List.empty, dir);
        })
      );
    }),
    
    // <alias> ::= '=' '"' <id> '"'
    alias: memo(function () {
      return seq('=', '"', p.id, '"').opt().to(function (optId) {
        return optId.map(function (id) { return id.head.tail; });
      });
    }),
    
    // <xymatrix> ::= <setup> '{' <rows> '}'
    xymatrix: memo(function () {
      return p.setup().andl(flit("{")).and(p.rows).andl(flit("}")).to(function (sr) {
        return AST.Command.Xymatrix(sr.head, sr.tail);
      })
    }),
    
    // <setup> ::= <switch>*
    // <switch> ::= '"' <prefix> '"'
    //          |   '@' <rcchar> <add op> <dimen>
    //          |   '@' '!' <rcchar> '0'
    //          |   '@' '!' <rcchar> '=' <dimen>
    //          |   '@' '!' <rcchar>
    //          |   '@' ( 'M' | 'W' | 'H' ) <add op> <dimen>
    //          |   '@' '1'
    //          |   '@' 'L' <add op> <dimen>
    //          |   '@' <nonemptyDirection>
    //          |   '@' '*' '[' <shape> ']'
    //          |   '@' '*' <add op> <size>
    // <rcchar> ::= 'R' | 'C' | <empty>
    // <mwhlchar> ::= 'M' | 'W' | 'H' | 'L'
    setup: memo(function () {
      return rep(fun(or(
        lit('"').andr(fun(regex(/^[^"]+/))).andl(felem('"')).to(function (p) {
          return AST.Command.Xymatrix.Setup.Prefix(p);
        }),
        lit("@!").andr(fun(regex(/^[RC]/).opt().to(function (c) {
          return c.getOrElse("");
        }))).and(fun(
          or(
            elem("0").to(function() { return "0em"; }),
            elem("=").andr(p.dimen)
          )
        )).to(function (cd) {
          var dimen = cd.tail;
          switch (cd.head) {
            case "R": return AST.Command.Xymatrix.Setup.PretendEntrySize.Height(dimen);
            case "C": return AST.Command.Xymatrix.Setup.PretendEntrySize.Width(dimen);
            default: return AST.Command.Xymatrix.Setup.PretendEntrySize.HeightAndWidth(dimen);
          }
        }),
        lit("@!").andr(fun(
          or(
            elem("R").to(function () { return AST.Command.Xymatrix.Setup.FixGrid.Row(); }),
            elem("C").to(function () { return AST.Command.Xymatrix.Setup.FixGrid.Column(); })
          ).opt().to(function (rc) {
            return rc.getOrElse(AST.Command.Xymatrix.Setup.FixGrid.RowAndColumn());
          })
        )),
        lit("@").andr(fun(regex(/^[MWHL]/))).and(p.addOp).and(p.dimen).to(function (cod) {
          var addop = cod.head.tail;
          var dimen = cod.tail;
          switch (cod.head.head) {
            case "M": return AST.Command.Xymatrix.Setup.AdjustEntrySize.Margin(addop, dimen);
            case "W": return AST.Command.Xymatrix.Setup.AdjustEntrySize.Width(addop, dimen);
            case "H": return AST.Command.Xymatrix.Setup.AdjustEntrySize.Height(addop, dimen);
            case "L": return AST.Command.Xymatrix.Setup.AdjustLabelSep(addop, dimen);
          }
        }),
        lit("@").andr(p.nonemptyDirection).to(function (d) {
          return AST.Command.Xymatrix.Setup.SetOrientation(d);
        }),
        lit("@*[").andr(p.shape).andl(flit("]")).to(function (s) {
          return AST.Command.Xymatrix.Setup.AddModifier(s);
        }),
        lit("@*").andr(p.addOp).and(p.size).to(function (os) {
          return AST.Command.Xymatrix.Setup.AddModifier(AST.Modifier.AddOp(os.head, os.tail));
        }),
        lit("@").andr(fun(regex(/^[RC]/).opt().to(function (c) {
          return c.getOrElse("");
        }))).and(p.addOp).and(p.dimen).to(function (cod) {
          var addop = cod.head.tail;
          var dimen = cod.tail;
          switch (cod.head.head) {
            case "R": return AST.Command.Xymatrix.Setup.ChangeSpacing.Row(addop, dimen);
            case "C": return AST.Command.Xymatrix.Setup.ChangeSpacing.Column(addop, dimen);
            default: return AST.Command.Xymatrix.Setup.ChangeSpacing.RowAndColumn(addop, dimen);
          }
        }),
        lit("@1").to(function () {
          return AST.Command.Xymatrix.Setup.AdjustEntrySize.Margin(AST.Modifier.AddOp.Set(), "1pc");
        })
      )));
    }),
    
    // <rows> ::= <row> ( '\\' <row> )*
    rows: memo(function () {
      return p.row().and(fun(rep(lit("\\\\").andr(p.row)))).to(function (rrs) {
        var rows = rrs.tail.prepend(rrs.head);
        if (!rows.isEmpty) {
          var lastRow = rows.at(rows.length() - 1);
          if (lastRow.entries.length() === 1 && lastRow.entries.at(0).isEmpty) {
            rows = rows.reverse().tail.reverse();
          }
        }
        return rows;
      })
    }),
    
    // <row> ::= <entry> ( '&' <entry> )*
    row: memo(function () {
      return p.entry().and(fun(rep(lit("&").andr(p.entry)))).to(function (ees) {
        return AST.Command.Xymatrix.Row(ees.tail.prepend(ees.head));
      })
    }),
    
    // <entry> ::= '*' <object> <pos> <decor>
    //         |   <entry modifier>* <loose objectbox> <decor>
    entry: memo(function () {
      return or(
        lit("*").andr(p.object).and(p.pos).and(p.decor).to(function (opd) {
          var obj = opd.head.head;
          var pos = opd.head.tail;
          var decor = opd.tail;
          return AST.Command.Xymatrix.Entry.ObjectEntry(obj, pos, decor);
        }),
        p.entryModifier().rep().and(p.looseObjectbox).and(p.decor).to(function (mopd) {
          var modifiers = mopd.head.head.foldLeft(FP.List.empty, function (tmpMs, ms) {
            return ms.concat(tmpMs);
          });
          var isEmpty = mopd.head.tail.isEmpty;
          var objbox = mopd.head.tail.object;
          var decor = mopd.tail;
          if (isEmpty && modifiers.isEmpty) {
            return AST.Command.Xymatrix.Entry.EmptyEntry(decor);
          }
          return AST.Command.Xymatrix.Entry.SimpleEntry(modifiers, objbox, decor);
        })
      );
    }),
    
    // <entry modifier> ::= '**' '[' <shape> ']' | '**' '{' <modifier>* '}'
    entryModifier: memo(function () {
      return or(
        lit("**").andr(flit("[")).andr(p.shape).andl(flit("]")).to(function (s) {
          return FP.List.empty.append(s);
        }),
        lit("**").andr(flit("{")).andr(fun(rep(p.modifier))).andl(flit("}"))
      );
    }),
    
    // <loose objectbox> ::= <objectbox>
    //                   |   /[^\\{}%&]+/* ( ( '\' not( '\' | <decor command names> ) ( '{' | '}' | '%' | '&' ) | '{' <text> '}' | /%[^\r\n]*(\r\n|\r|\n)?/ ) /[^\\{}%&]+/* )*
    // <decor command names> ::= 'ar' | 'xymatrix' | 'PATH' | 'afterPATH'
    //                       |   'save' | 'restore' | 'POS' | 'afterPOS' | 'drop' | 'connect' | 'xyignore'
    looseObjectbox: memo(function () {
      return or(
        p.objectbox().to(function (o) { return {
          isEmpty:false, object:o
        } }),
        regex(/^[^\\{}%&]+/).opt().to(function (rs) { return rs.getOrElse(""); }).and(fun(
          rep(
            or(
              elem("{").andr(p.text).andl(felem("}")).to(function (t) { return "{" + t + "}"; }),
              elem("\\").andr(fun(
                not(regex(/^(\\|ar|xymatrix|PATH|afterPATH|save|restore|POS|afterPOS|drop|connect|xyignore|([lrud]+(twocell|uppertwocell|lowertwocell|compositemap))|xtwocell|xuppertwocell|xlowertwocell|xcompositemap)/))
              )).andr(fun(
                regex(/^[{}%&]/).opt().to(function (c) { return c.getOrElse(""); })
              )).to(function (t) {
                return "\\" + t;
              }),
              regex(/^%[^\r\n]*(\r\n|\r|\n)?/).to(function (x) {
                return ' '; // ignore comments
              })
            ).and(fun(
              regex(/^[^\\{}%&]+/).opt().to(function (cs) { return cs.getOrElse(""); })
            )).to(function (tt) {
              return tt.head + tt.tail;
            })
          ).to(function (cs) { return cs.mkString("") })
        )).to(function (tt) {
          var text = tt.head + tt.tail;
          var isEmpty = (text.trim().length === 0);
          var object = p.toMath("\\hbox{$\\objectstyle{" + text + "}$}");
          return {
            isEmpty:isEmpty, object:object
          };
        })
      )
    }),
    
    // <command> ::= <twocell> <twocell switch>* <twocell arrow>
    twocellCommand: memo(function () {
      return p.twocell().and(fun(rep(p.twocellSwitch))).and(p.twocellArrow).to(function (tsa) {
        return AST.Command.Twocell(tsa.head.head, tsa.head.tail, tsa.tail);
      });
    }),
    
    // <twocell> ::= '\' /[lrud]+/ 'twocell'
    //           |   '\' /[lrud]+/ 'uppertwocell'
    //           |   '\' /[lrud]+/ 'lowertwocell'
    //           |   '\' /[lrud]+/ 'compositemap'
    //           |   '\xtwocell' '[' /[lrud]+/ ']' '{' <text> '}'
    //           |   '\xuppertwocell' '[' /[lrud]+/ ']' '{' <text> '}'
    //           |   '\xlowertwocell' '[' /[lrud]+/ ']' '{' <text> '}'
    //           |   '\xcompositemap' '[' /[lrud]+/ ']' '{' <text> '}'
    twocell: memo(function () {
      return or(
        regexLit(/^\\[lrud]+twocell/).to(function (h) {
          var hops = h.substring(1, h.length - "twocell".length);
          return AST.Command.Twocell.Twocell(hops, FP.Option.empty);
        }),
        regexLit(/^\\[lrud]+uppertwocell/).to(function (h) {
          var hops = h.substring(1, h.length - "uppertwocell".length);
          return AST.Command.Twocell.UpperTwocell(hops, FP.Option.empty);
        }),
        regexLit(/^\\[lrud]+lowertwocell/).to(function (h) {
          var hops = h.substring(1, h.length - "lowertwocell".length);
          return AST.Command.Twocell.LowerTwocell(hops, FP.Option.empty);
        }),
        regexLit(/^\\[lrud]+compositemap/).to(function (h) {
          var hops = h.substring(1, h.length - "compositemap".length);
          return AST.Command.Twocell.CompositeMap(hops, FP.Option.empty);
        }),
        or(
          lit("\\xtwocell").to(function () { return AST.Command.Twocell.Twocell; }),
          lit("\\xuppertwocell").to(function () { return AST.Command.Twocell.UpperTwocell; }),
          lit("\\xlowertwocell").to(function () { return AST.Command.Twocell.LowerTwocell; }),
          lit("\\xcompositemap").to(function () { return AST.Command.Twocell.CompositeMap; })
        ).andl(flit("[")).and(fun(regex(/^[lrud]+/))).andl(flit("]")).andl(flit("{")).and(p.text).andl(flit("}")).to(function (cht) {
          var textObject = AST.Object(FP.List.empty, p.toMath("\\labelstyle " + cht.tail));
          return cht.head.head(cht.head.tail, FP.Option.Some(textObject));
        })
      );
    }),
    
    // <twocell switch> ::= '^' <twocell label>
    //          |   '_' <twocell label>
    //          |   '\omit'
    //          |   '~!'
    //          |   '~' ( '`' | "'" ) '{' <object> '}'
    //          |   '~' ( '' | '^' | '_' ) '{' <object> ( '~**' <object> )? '}'
    //          |   <nudge>
    twocellSwitch: memo(function () {
      return or(
        lit("^").andr(p.twocellLabel).to(function (l) {
          return AST.Command.Twocell.Switch.UpperLabel(l);
        }),
        lit("_").andr(p.twocellLabel).to(function (l) {
          return AST.Command.Twocell.Switch.LowerLabel(l);
        }),
        lit("\\omit").to(function () {
          return AST.Command.Twocell.Switch.DoNotSetCurvedArrows();
        }),
        lit("~!").to(function () {
          return AST.Command.Twocell.Switch.PlaceModMapObject();
        }),
        regexLit(/^(~[`'])/).andl(flit("{")).and(p.object).andl(flit("}")).to(function (wo) {
          var what = wo.head.substring(1);
          return AST.Command.Twocell.Switch.ChangeHeadTailObject(what, wo.tail);
        }),
        regexLit(/^(~[\^_]?)/).andl(flit("{")).and(p.object).and(fun(opt(lit("~**").andr(p.object)))).andl(flit("}")).to(function (wso) {
          var what = wso.head.head.substring(1);
          var spacer = wso.head.tail;
          var maybeObject = wso.tail;
          return AST.Command.Twocell.Switch.ChangeCurveObject(what, spacer, maybeObject);
        }),
        p.nudge().to(function (n) {
          return AST.Command.Twocell.Switch.SetCurvature(n);
        })
      );
    }),
    
    // <twocell label> ::= <digit> | <letter> | <cs>
    //                 |   '{' <nudge>? '*' <object> '}'
    //                 |   '{' <nudge>? <text> '}'
    twocellLabel: memo(function () {
      return or(
        regexLit(/^[0-9a-zA-Z]/).to(function (c) {
          var obj = AST.Object(FP.List.empty, p.toMath("\\twocellstyle " + c));
          return AST.Command.Twocell.Label(FP.Option.empty, obj);
        }),
        regexLit(/^(\\[a-zA-Z][a-zA-Z0-9]*)/).to(function (c) {
          var obj = AST.Object(FP.List.empty, p.toMath("\\twocellstyle " + c));
          return AST.Command.Twocell.Label(FP.Option.empty, obj);
        }),
        lit("{").andr(fun(opt(p.nudge))).andl(flit("*")).and(p.object).andl(flit("}")).to(function (no) {
          return AST.Command.Twocell.Label(no.head, no.tail);
        }),
        lit("{").andr(fun(opt(p.nudge))).and(p.text).andl(felem("}")).to(function (nt) {
          var obj = AST.Object(FP.List.empty, p.toMath("\\twocellstyle " + nt.tail));
          return AST.Command.Twocell.Label(nt.head, obj);
        })
      );
    }),
    
    // <nudge> ::= '<' <factor> '>'
    //         |   '<\omit>'
    nudge: memo(function () {
      return or(
        lit("<\\omit>").to(function () {
          return AST.Command.Twocell.Nudge.Omit();
        }),
        lit("<").andr(p.factor).andl(flit(">")).to(function (n) {
          return AST.Command.Twocell.Nudge.Number(n);
        })
      );
    }),
    
    // <twocell arrow> ::= '{' <twocell tok> (<twocell label entry> '}'
    //                 |   '{' <nudge> <twocell label entry> '}'
    //                 |   '{' <twocell label entry> '}'
    //                 |   <empty>
    // <twocell tok> ::= '^' | '_' | '='
    //               |   '\omit'
    //               |   '`' | "'" | '"' | '!'
    twocellArrow: memo(function () {
      return or(
        lit("{").andr(fun(regexLit(/^([\^_=`'"!]|\\omit)/))).and(p.twocellLabelEntry).andl(flit("}")).to(function (te) {
          return AST.Command.Twocell.Arrow.WithOrientation(te.head, te.tail);
        }),
        lit("{").andr(p.nudge).and(p.twocellLabelEntry).andl(flit("}")).to(function (te) {
          return AST.Command.Twocell.Arrow.WithPosition(te.head, te.tail);
        }),
        lit("{").andr(p.twocellLabelEntry).andl(flit("}")).to(function (e) {
          return AST.Command.Twocell.Arrow.WithOrientation('', e);
        }),
        success("no arrow label").to(function () {
          // TODO 無駄な空描画処理をなくす。
          return AST.Command.Twocell.Arrow.WithOrientation('', AST.Object(FP.List.empty, p.toMath("\\twocellstyle{}")));
        })
      );
    }),
    
    // <twocell label entry> ::= '*' <object>
    //                       |   <text>
    twocellLabelEntry: memo(function () {
      return or(
        lit("*").andr(p.object),
        p.text().to(function (t) {
          return AST.Object(FP.List.empty, p.toMath("\\twocellstyle " + t));
        })
      );
    }),
    
    // \newdirの後の
    // '{' <main> '}' '{' <composite_object> '}'
    newdir: memo(function () {
      return lit("{").andr(p.dirMain).andl(felem("}")).andl(flit("{")).and(p.compositeObject).andl(flit("}")).to(function (mc) {
        return AST.Command.Newdir(mc.head, AST.ObjectBox.CompositeObject(mc.tail));
      });
    }),
    
    // '\xyimport' '(' <factor> ',' <factor> ')' ( '(' <factor> ',' <factor> ')' )? '{' ( <include graphics> | <TeX command> ) '}'
    xyimport: memo(function () {
      return lit("\\xyimport").andr(flit("(")).andr(p.factor).andl(flit(",")).and(p.factor).andl(flit(")")).and(fun(
        opt(lit("(").andr(p.factor).andl(flit(",")).and(p.factor).andl(flit(")")))
      )).andl(flit("{")).and(fun(
        or(
          lit("\\includegraphics").andr(p.includegraphics),
          p.text().to(function (t) { return p.toMath("\\hbox{$\\objectstyle{" + t + "}$}"); })
      ))).andl(flit("}")).to(function (whog) {
        var w = whog.head.head.head;
        var h = whog.head.head.tail;
        var xOffset, yOffset;
        if (whog.head.tail.isDefined) {
          xOffset = whog.head.tail.get.head;
          yOffset = whog.head.tail.get.tail;
        } else {
          xOffset = 0;
          yOffset = 0;
        }
        var graphics = whog.tail;
        if (graphics.isIncludegraphics !== undefined) {
          return AST.Pos.Xyimport.Graphics(w, h, xOffset, yOffset, graphics);
        } else {
          return AST.Pos.Xyimport.TeXCommand(w, h, xOffset, yOffset, graphics);
        }
      });
    }),
    
    // \includegraphicsの後の
    // '*'? '[' ( <includegraphics attr list> )? ']' '{' <file path> '}'
    includegraphics: memo(function () {
      return lit("[").andr(fun(opt(p.includegraphicsAttrList))).andl(flit("]")).andl(flit("{")).and(fun(regexLit(/^[^\s{}]+/))).andl(flit("}")).to(function (af) {
        var attrList = af.head.getOrElse(FP.List.empty);
        var file = af.tail;
        return AST.Command.Includegraphics(false, attrList, file);
      });
    }),
    
    // <includegraphics attr list> := <includegraphics attr key val> ( ',' <includegraphics attr key val> )*
    includegraphicsAttrList: memo(function () {
      return p.includegraphicsAttr().and(fun(rep(lit(",").andr(p.includegraphicsAttr)))).to(function (aas) {
        return aas.tail.prepend(aas.head);
      });
    }),
    
    // <includegraphics attr key val> := 'width' '=' <dimen>
    //                                |  'height' '=' <dimen>
    includegraphicsAttr: memo(function () {
      return or(
        lit("width").andr(flit("=")).andr(p.dimen).to(function (d) {
          return AST.Command.Includegraphics.Attr.Width(d);
        }),
        lit("height").andr(flit("=")).andr(p.dimen).to(function (d) {
          return AST.Command.Includegraphics.Attr.Height(d);
        })
      );
    })
    
  })();
  
  MathJax.Hub.Insert(TEXDEF,{
    environment: {
      xy:            ['XY', null]
    }
  });
  
  xypic.ExecutionError = MathJax.Object.Subclass({
    Init: function (message) {
      this.message = message;
    },
    toMML: function () {
      return MML.merror(MML.mtext(this.message));
    },
    texError: true,
    xyjaxError: true
  });
  
  xypic.ParseError = MathJax.Object.Subclass({
    Init: function (parseResult) {
      this.parseResult = parseResult;
    },
    toMML: function () {
      var pos = this.parseResult.next.pos();
      var lineContents = pos.lineContents();
      return MML.merror(MML.mtext('parse error at or near "' + lineContents + '"'));
      /*
      var col = pos.column();
      var left = lineContents.substring(0, col-1);
      var mid = lineContents.substring(col-1, col);
      var right = lineContents.substring(col);
      return MML.merror(MML.mtext('parse error at or near "'), MML.mtext(left).With({color:"black"}), MML.mtext(mid).With({color:"red"}), MML.mtext(right).With({color:"black"}), MML.mtext('"'));
      */
    },
    texError: true,
    xyjaxError: true
  });
  
  TEX.Parse.Augment({
    /*
     * Handle XY environment
     */
    XY: function(begin) {
      try {
        var parseContext = {
          lastNoSuccess: undefined,
          whiteSpaceRegex: xypic.constants.whiteSpaceRegex
        };
        var input = FP.StringReader(this.string, this.i, parseContext);
        var result = FP.Parsers.parse(p.xy(), input);
        this.i = result.next.offset;
      } catch (e) {
        throw e;
      }
      
      if (result.successful) {
        if (supportGraphics) {
          this.Push(AST.xypic(result.result));
        } else {
          this.Push(MML.merror(xypic.unsupportedBrowserErrorMessage));
        }
      } else {
        throw xypic.ParseError(parseContext.lastNoSuccess);
      }
      
      return begin;
    },
    
    /**
     * Handle xybox
     */
    Xybox: function () {
      try {
        var parseContext = {
          lastNoSuccess: undefined,
          whiteSpaceRegex: xypic.constants.whiteSpaceRegex
        };
        var input = FP.StringReader(this.string, this.i, parseContext);
        var result = FP.Parsers.parse(p.xybox(), input);
        this.i = result.next.offset;
      } catch (e) {
        throw e;
      }
      
      if (result.successful) {
        if (supportGraphics) {
          this.Push(AST.xypic(result.result));
        } else {
          this.Push(MML.merror(xypic.unsupportedBrowserErrorMessage));
        }
      } else {
        throw xypic.ParseError(parseContext.lastNoSuccess);
      }
    },
    
    /**
     * Handle xymatrix
     */
    Xymatrix: function () {
      try {
        var parseContext = {
          lastNoSuccess: undefined,
          whiteSpaceRegex: xypic.constants.whiteSpaceRegex
        };
        var input = FP.StringReader(this.string, this.i, parseContext);
        var result = FP.Parsers.parse(p.xymatrixbox(), input);
        this.i = result.next.offset;
      } catch (e) {
        throw e;
      }
      
      if (result.successful) {
        if (supportGraphics) {
          this.Push(AST.xypic(result.result));
        } else {
          this.Push(MML.merror(xypic.unsupportedBrowserErrorMessage));
        }
      } else {
        throw xypic.ParseError(parseContext.lastNoSuccess);
      }
    },
    
    /**
     * Handle newdir
     */
    XypicNewdir: function () {
      try {
        var parseContext = {
          lastNoSuccess: undefined,
          whiteSpaceRegex: xypic.constants.whiteSpaceRegex
        };
        var input = FP.StringReader(this.string, this.i, parseContext);
        var result = FP.Parsers.parse(p.newdir(), input);
        this.i = result.next.offset;
      } catch (e) {
        throw e;
      }
      
      if (result.successful) {
        if (supportGraphics) {
          this.Push(AST.xypic.newdir(result.result));
        } else {
          this.Push(MML.merror(xypic.unsupportedBrowserErrorMessage));
        }
      } else {
        throw xypic.ParseError(parseContext.lastNoSuccess);
      }
    },
    
    
    /**
     * Handle includegraphics
     */
    Xyincludegraphics: function () {
      try {
        var parseContext = {
          lastNoSuccess: undefined,
          whiteSpaceRegex: xypic.constants.whiteSpaceRegex
        };
        var input = FP.StringReader(this.string, this.i, parseContext);
        var result = FP.Parsers.parse(p.includegraphics(), input);
        this.i = result.next.offset;
      } catch (e) {
        throw e;
      }
      
      if (result.successful) {
        if (supportGraphics) {
          this.Push(AST.xypic.includegraphics(result.result));
        } else {
          this.Push(MML.merror(xypic.unsupportedBrowserErrorMessage));
        }
      } else {
        throw xypic.ParseError(parseContext.lastNoSuccess);
      }
    }
  });
  
  var supportGraphics = false;
  MathJax.Hub.Browser.Select({
    Firefox: function (browser) {
      supportGraphics = true;
    },
    Safari: function (browser) {
      supportGraphics = true;
    },
    Chrome: function (browser) {
      supportGraphics = true;
    },
    Opera: function (browser) {
      supportGraphics = true;
    },
    MSIE: function (browser) {
      if (MathJax.Hub.Browser.versionAtLeast("9.0") && document.documentMode >= 9) {
        supportGraphics = true;
      }
    }
  });
  
  MathJax.Hub.Startup.signal.Post("TeX Xy-pic Ready");
});












MathJax.Hub.Register.StartupHook("HTML-CSS Xy-pic Config Require",function () {
  MathJax.Hub.Startup.signal.Post("HTML-CSS Xy-pic Config Ready");
});

MathJax.Hub.Register.StartupHook("SVG Xy-pic Config Require",function () {
  MathJax.Hub.Startup.signal.Post("SVG Xy-pic Config Ready");
});

MathJax.Hub.Register.StartupHook("Device-Independent Xy-pic Require",function () {
  var FP = MathJax.Extension.fp;
  var MML = MathJax.ElementJax.mml;
  var HUB = MathJax.Hub;
  var xypic = MathJax.Extension.xypic;
  var AST = xypic.AST;
  
  var SVGNS = "http://www.w3.org/2000/svg";
  var XHTMLNS = "http://www.w3.org/1999/xhtml";
  var XLINKNS = "http://www.w3.org/1999/xlink";
  
  
  // override MathJax.Hub.formatError function to display runtime error.
  var hub_formatError = HUB.formatError;
  HUB.formatError = function (script, err) {
    if (err.xyjaxError !== undefined) {
      var origMessage = HUB.config.errorSettings.message;
      HUB.config.errorSettings.message = "[" + err.message + "]";
      hub_formatError.apply(HUB, [script, err]);
      HUB.config.errorSettings.message = origMessage;
    } else {
      throw err;
      hub_formatError.apply(HUB, [script, err]);
    }
  }
  
  var memoize = xypic.memoize;
  
  AST.xypic.Augment({}, {
    lengthResolution: 128,
    interpolationResolution: 5,
    machinePrecision: 1e-12
  });
  
  xypic.DirRepository = MathJax.Object.Subclass({
    Init: function () {
      this.userDirMap = {};
    },
    get: function (dirMain) {
      return this.userDirMap[dirMain];
    },
    put: function (dirMain, compositeObject) {
      this.userDirMap[dirMain] = compositeObject;
    }
  });
  
  xypic.ModifierRepository = MathJax.Object.Subclass({
    Init: function () {
      this.userModifierMap = {};
    },
    get: function (shapeName) {
      var modifier = xypic.ModifierRepository.embeddedModifierMap[shapeName];
      if (modifier !== undefined) {
        return modifier;
      }
      return this.userModifierMap[shapeName];
    },
    put: function (shapeName, modifier) {
      if (xypic.ModifierRepository.embeddedModifierMap[shapeName] === undefined) {
        this.userModifierMap[shapeName] = modifier;
      }
    }
  }, {
    embeddedModifierMap: {
      "o":AST.Modifier.Shape.Circle(),
      "l":AST.Modifier.Shape.L(),
      "r":AST.Modifier.Shape.R(),
      "u":AST.Modifier.Shape.U(),
      "d":AST.Modifier.Shape.D(),
      "c":AST.Modifier.Shape.C(),
      "aliceblue":AST.Modifier.Shape.ChangeColor("aliceblue"),
      "antiquewhite":AST.Modifier.Shape.ChangeColor("antiquewhite"),
      "aqua":AST.Modifier.Shape.ChangeColor("aqua"),
      "aquamarine":AST.Modifier.Shape.ChangeColor("aquamarine"),
      "azure":AST.Modifier.Shape.ChangeColor("azure"),
      "beige":AST.Modifier.Shape.ChangeColor("beige"),
      "bisque":AST.Modifier.Shape.ChangeColor("bisque"),
      "black":AST.Modifier.Shape.ChangeColor("black"),
      "blanchedalmond":AST.Modifier.Shape.ChangeColor("blanchedalmond"),
      "blue":AST.Modifier.Shape.ChangeColor("blue"),
      "blueviolet":AST.Modifier.Shape.ChangeColor("blueviolet"),
      "brown":AST.Modifier.Shape.ChangeColor("brown"),
      "burlywood":AST.Modifier.Shape.ChangeColor("burlywood"),
      "cadetblue":AST.Modifier.Shape.ChangeColor("cadetblue"),
      "chartreuse":AST.Modifier.Shape.ChangeColor("chartreuse"),
      "chocolate":AST.Modifier.Shape.ChangeColor("chocolate"),
      "coral":AST.Modifier.Shape.ChangeColor("coral"),
      "cornflowerblue":AST.Modifier.Shape.ChangeColor("cornflowerblue"),
      "cornsilk":AST.Modifier.Shape.ChangeColor("cornsilk"),
      "crimson":AST.Modifier.Shape.ChangeColor("crimson"),
      "cyan":AST.Modifier.Shape.ChangeColor("cyan"),
      "darkblue":AST.Modifier.Shape.ChangeColor("darkblue"),
      "darkcyan":AST.Modifier.Shape.ChangeColor("darkcyan"),
      "darkgoldenrod":AST.Modifier.Shape.ChangeColor("darkgoldenrod"),
      "darkgray":AST.Modifier.Shape.ChangeColor("darkgray"),
      "darkgreen":AST.Modifier.Shape.ChangeColor("darkgreen"),
      "darkgrey":AST.Modifier.Shape.ChangeColor("darkgrey"),
      "darkkhaki":AST.Modifier.Shape.ChangeColor("darkkhaki"),
      "darkmagenta":AST.Modifier.Shape.ChangeColor("darkmagenta"),
      "darkolivegreen":AST.Modifier.Shape.ChangeColor("darkolivegreen"),
      "darkorange":AST.Modifier.Shape.ChangeColor("darkorange"),
      "darkorchid":AST.Modifier.Shape.ChangeColor("darkorchid"),
      "darkred":AST.Modifier.Shape.ChangeColor("darkred"),
      "darksalmon":AST.Modifier.Shape.ChangeColor("darksalmon"),
      "darkseagreen":AST.Modifier.Shape.ChangeColor("darkseagreen"),
      "darkslateblue":AST.Modifier.Shape.ChangeColor("darkslateblue"),
      "darkslategray":AST.Modifier.Shape.ChangeColor("darkslategray"),
      "darkslategrey":AST.Modifier.Shape.ChangeColor("darkslategrey"),
      "darkturquoise":AST.Modifier.Shape.ChangeColor("darkturquoise"),
      "darkviolet":AST.Modifier.Shape.ChangeColor("darkviolet"),
      "deeppink":AST.Modifier.Shape.ChangeColor("deeppink"),
      "deepskyblue":AST.Modifier.Shape.ChangeColor("deepskyblue"),
      "dimgray":AST.Modifier.Shape.ChangeColor("dimgray"),
      "dimgrey":AST.Modifier.Shape.ChangeColor("dimgrey"),
      "dodgerblue":AST.Modifier.Shape.ChangeColor("dodgerblue"),
      "firebrick":AST.Modifier.Shape.ChangeColor("firebrick"),
      "floralwhite":AST.Modifier.Shape.ChangeColor("floralwhite"),
      "forestgreen":AST.Modifier.Shape.ChangeColor("forestgreen"),
      "fuchsia":AST.Modifier.Shape.ChangeColor("fuchsia"),
      "gainsboro":AST.Modifier.Shape.ChangeColor("gainsboro"),
      "ghostwhite":AST.Modifier.Shape.ChangeColor("ghostwhite"),
      "gold":AST.Modifier.Shape.ChangeColor("gold"),
      "goldenrod":AST.Modifier.Shape.ChangeColor("goldenrod"),
      "gray":AST.Modifier.Shape.ChangeColor("gray"),
      "grey":AST.Modifier.Shape.ChangeColor("grey"),
      "green":AST.Modifier.Shape.ChangeColor("green"),
      "greenyellow":AST.Modifier.Shape.ChangeColor("greenyellow"),
      "honeydew":AST.Modifier.Shape.ChangeColor("honeydew"),
      "hotpink":AST.Modifier.Shape.ChangeColor("hotpink"),
      "indianred":AST.Modifier.Shape.ChangeColor("indianred"),
      "indigo":AST.Modifier.Shape.ChangeColor("indigo"),
      "ivory":AST.Modifier.Shape.ChangeColor("ivory"),
      "khaki":AST.Modifier.Shape.ChangeColor("khaki"),
      "lavender":AST.Modifier.Shape.ChangeColor("lavender"),
      "lavenderblush":AST.Modifier.Shape.ChangeColor("lavenderblush"),
      "lawngreen":AST.Modifier.Shape.ChangeColor("lawngreen"),
      "lemonchiffon":AST.Modifier.Shape.ChangeColor("lemonchiffon"),
      "lightblue":AST.Modifier.Shape.ChangeColor("lightblue"),
      "lightcoral":AST.Modifier.Shape.ChangeColor("lightcoral"),
      "lightcyan":AST.Modifier.Shape.ChangeColor("lightcyan"),
      "lightgoldenrodyellow":AST.Modifier.Shape.ChangeColor("lightgoldenrodyellow"),
      "lightgray":AST.Modifier.Shape.ChangeColor("lightgray"),
      "lightgreen":AST.Modifier.Shape.ChangeColor("lightgreen"),
      "lightgrey":AST.Modifier.Shape.ChangeColor("lightgrey"),
      "lightpink":AST.Modifier.Shape.ChangeColor("lightpink"),
      "lightsalmon":AST.Modifier.Shape.ChangeColor("lightsalmon"),
      "lightseagreen":AST.Modifier.Shape.ChangeColor("lightseagreen"),
      "lightskyblue":AST.Modifier.Shape.ChangeColor("lightskyblue"),
      "lightslategray":AST.Modifier.Shape.ChangeColor("lightslategray"),
      "lightslategrey":AST.Modifier.Shape.ChangeColor("lightslategrey"),
      "lightsteelblue":AST.Modifier.Shape.ChangeColor("lightsteelblue"),
      "lightyellow":AST.Modifier.Shape.ChangeColor("lightyellow"),
      "lime":AST.Modifier.Shape.ChangeColor("lime"),
      "limegreen":AST.Modifier.Shape.ChangeColor("limegreen"),
      "linen":AST.Modifier.Shape.ChangeColor("linen"),
      "magenta":AST.Modifier.Shape.ChangeColor("magenta"),
      "maroon":AST.Modifier.Shape.ChangeColor("maroon"),
      "mediumaquamarine":AST.Modifier.Shape.ChangeColor("mediumaquamarine"),
      "mediumblue":AST.Modifier.Shape.ChangeColor("mediumblue"),
      "mediumorchid":AST.Modifier.Shape.ChangeColor("mediumorchid"),
      "mediumpurple":AST.Modifier.Shape.ChangeColor("mediumpurple"),
      "mediumseagreen":AST.Modifier.Shape.ChangeColor("mediumseagreen"),
      "mediumslateblue":AST.Modifier.Shape.ChangeColor("mediumslateblue"),
      "mediumspringgreen":AST.Modifier.Shape.ChangeColor("mediumspringgreen"),
      "mediumturquoise":AST.Modifier.Shape.ChangeColor("mediumturquoise"),
      "mediumvioletred":AST.Modifier.Shape.ChangeColor("mediumvioletred"),
      "midnightblue":AST.Modifier.Shape.ChangeColor("midnightblue"),
      "mintcream":AST.Modifier.Shape.ChangeColor("mintcream"),
      "mistyrose":AST.Modifier.Shape.ChangeColor("mistyrose"),
      "moccasin":AST.Modifier.Shape.ChangeColor("moccasin"),
      "navajowhite":AST.Modifier.Shape.ChangeColor("navajowhite"),
      "navy":AST.Modifier.Shape.ChangeColor("navy"),
      "oldlace":AST.Modifier.Shape.ChangeColor("oldlace"),
      "olive":AST.Modifier.Shape.ChangeColor("olive"),
      "olivedrab":AST.Modifier.Shape.ChangeColor("olivedrab"),
      "orange":AST.Modifier.Shape.ChangeColor("orange"),
      "orangered":AST.Modifier.Shape.ChangeColor("orangered"),
      "orchid":AST.Modifier.Shape.ChangeColor("orchid"),
      "palegoldenrod":AST.Modifier.Shape.ChangeColor("palegoldenrod"),
      "palegreen":AST.Modifier.Shape.ChangeColor("palegreen"),
      "paleturquoise":AST.Modifier.Shape.ChangeColor("paleturquoise"),
      "palevioletred":AST.Modifier.Shape.ChangeColor("palevioletred"),
      "papayawhip":AST.Modifier.Shape.ChangeColor("papayawhip"),
      "peachpuff":AST.Modifier.Shape.ChangeColor("peachpuff"),
      "peru":AST.Modifier.Shape.ChangeColor("peru"),
      "pink":AST.Modifier.Shape.ChangeColor("pink"),
      "plum":AST.Modifier.Shape.ChangeColor("plum"),
      "powderblue":AST.Modifier.Shape.ChangeColor("powderblue"),
      "purple":AST.Modifier.Shape.ChangeColor("purple"),
      "red":AST.Modifier.Shape.ChangeColor("red"),
      "rosybrown":AST.Modifier.Shape.ChangeColor("rosybrown"),
      "royalblue":AST.Modifier.Shape.ChangeColor("royalblue"),
      "saddlebrown":AST.Modifier.Shape.ChangeColor("saddlebrown"),
      "salmon":AST.Modifier.Shape.ChangeColor("salmon"),
      "sandybrown":AST.Modifier.Shape.ChangeColor("sandybrown"),
      "seagreen":AST.Modifier.Shape.ChangeColor("seagreen"),
      "seashell":AST.Modifier.Shape.ChangeColor("seashell"),
      "sienna":AST.Modifier.Shape.ChangeColor("sienna"),
      "silver":AST.Modifier.Shape.ChangeColor("silver"),
      "skyblue":AST.Modifier.Shape.ChangeColor("skyblue"),
      "slateblue":AST.Modifier.Shape.ChangeColor("slateblue"),
      "slategray":AST.Modifier.Shape.ChangeColor("slategray"),
      "slategrey":AST.Modifier.Shape.ChangeColor("slategrey"),
      "snow":AST.Modifier.Shape.ChangeColor("snow"),
      "springgreen":AST.Modifier.Shape.ChangeColor("springgreen"),
      "steelblue":AST.Modifier.Shape.ChangeColor("steelblue"),
      "tan":AST.Modifier.Shape.ChangeColor("tan"),
      "teal":AST.Modifier.Shape.ChangeColor("teal"),
      "thistle":AST.Modifier.Shape.ChangeColor("thistle"),
      "tomato":AST.Modifier.Shape.ChangeColor("tomato"),
      "turquoise":AST.Modifier.Shape.ChangeColor("turquoise"),
      "violet":AST.Modifier.Shape.ChangeColor("violet"),
      "wheat":AST.Modifier.Shape.ChangeColor("wheat"),
      "white":AST.Modifier.Shape.ChangeColor("white"),
      "whitesmoke":AST.Modifier.Shape.ChangeColor("whitesmoke"),
      "yellow":AST.Modifier.Shape.ChangeColor("yellow"),
      "yellowgreen":AST.Modifier.Shape.ChangeColor("yellowgreen")
    }
  });
  
  // user defined shapes are global in scope.
  xypic.repositories = MathJax.Object.Subclass({});
  
  xypic.repositories.modifierRepository = xypic.ModifierRepository();
  xypic.repositories.dirRepository = xypic.DirRepository();
  
  xypic.Graphics = MathJax.Object.Subclass({}, {
    createElement: function (type) {
//      if (document.createElementNS !== undefined) {
        return document.createElementNS(SVGNS, type);
//      } else {
//        return document.createElement(type);
//      }
    }
  });
  xypic.Graphics.SVG = xypic.Graphics.Subclass({
    createGroup: function (transform) {
      return xypic.Graphics.SVG.Group(this, transform);
    },
    createChangeColorGroup: function (color) {
      return xypic.Graphics.SVG.ChangeColorGroup(this, color);
    },
    createSVGElement: function (type, def) {
      var obj = xypic.Graphics.createElement(type);
      if (def) {
        for (var id in def) {
          if (def.hasOwnProperty(id)) {
            if (id === "xlink:href") {
              obj.setAttributeNS(XLINKNS, id, def[id].toString());
            } else {
              obj.setAttribute(id, def[id].toString());
            }
          }
        }
      }
      this.drawArea.appendChild(obj);
      return obj;
    },
    appendChild: function (svgElement) {
      this.drawArea.appendChild(svgElement);
      return svgElement;
    },
    transformBuilder: function () {
      return xypic.Graphics.SVG.Transform();
    }
  });
  xypic.Graphics.SVG.World = xypic.Graphics.SVG.Subclass({
    Init: function (height, depth, width, strokeWidth, color, def) {
      var svg = xypic.Graphics.createElement("svg");
      svg.setAttribute("xmlns", SVGNS);
      svg.setAttribute("version", "1.1");
      if (def) {
        for (var id in def) {
          if (def.hasOwnProperty(id)) {
            svg.setAttribute(id, def[id].toString());
          }
        }
      }
      if (svg.style) {
        svg.style.width = xypic.Em(width);
        svg.style.height = xypic.Em(height + depth);
      }
      var def = {
        fill:"none", stroke:color, "stroke-linecap":"round",
        "stroke-width":xypic.em2px(strokeWidth)
      };
      this.drawArea = xypic.Graphics.createElement("g");
      for (var id in def) {
        if (def.hasOwnProperty(id)) {
          this.drawArea.setAttribute(id, def[id].toString());
        }
      }
      svg.appendChild(this.drawArea);
      this.svg = svg;
      this.boundingBox = undefined;
      this.color = color;
    },
    setHeight: function (height) {
      this.svg.style.height = xypic.Em(height);
    },
    setWidth: function (height) {
      this.svg.style.width = xypic.Em(height);
    },
    setAttribute: function (name, value) {
      this.svg.setAttribute(name, value.toString());
    },
    extendBoundingBox: function (boundingBox) {
      this.boundingBox = xypic.Frame.combineRect(this.boundingBox, boundingBox);
    },
    getOrigin: function () {
      return { x:0, y:0 };
    },
    getCurrentColor: function () {
      return this.color;
    }
  });
  
  xypic.Graphics.SVG.Transform = MathJax.Object.Subclass({
    Init: function (transform) {
      this.transform = transform || FP.List.empty;
    },
    translate: function (x, y) {
      return xypic.Graphics.SVG.Transform(
        this.transform.append(xypic.Graphics.SVG.Transform.Translate(x, y))
      );
    },
    rotateDegree: function (degree) {
      return xypic.Graphics.SVG.Transform(
        this.transform.append(xypic.Graphics.SVG.Transform.Rotate(degree / 180 * Math.PI))
      );
    },
    rotateRadian: function (radian) {
      return xypic.Graphics.SVG.Transform(
        this.transform.append(xypic.Graphics.SVG.Transform.Rotate(radian))
      );
    },
    toString: function () {
      var form = "";
      this.transform.foreach(function (tr) { form += tr.toTranslateForm() });
      return form;
    },
    apply: function (x, y) {
      var o = { x:x, y:y };
      this.transform.foreach(function (tr) { o = tr.apply(o.x, o.y) });
      return o;
    }
  });
  
  xypic.Graphics.SVG.Transform.Translate = MathJax.Object.Subclass({
    Init: function (dx, dy) {
      this.dx = dx;
      this.dy = dy;
    },
    apply: function (x, y) {
      return { x:x - this.dx, y:y + this.dy };
    },
    toTranslateForm: function () {
      return "translate(" + xypic.em2px(this.dx) + "," + xypic.em2px(-this.dy) + ") ";
    }
  });
  
  xypic.Graphics.SVG.Transform.Rotate = MathJax.Object.Subclass({
    Init: function (radian) {
      this.radian = radian;
    },
    apply: function (x, y) {
      var c = Math.cos(this.radian);
      var s = Math.sin(this.radian);
      return { x:c * x + s * y, y:-s * x + c * y };
    },
    toTranslateForm: function () {
      return "rotate(" + (-180 * this.radian / Math.PI) + ") ";
    }
  });
  
  xypic.Graphics.SVG.Group = xypic.Graphics.SVG.Subclass({
    Init: function (parent, transform) {
      this.parent = parent;
      this.drawArea = parent.createSVGElement("g", 
        transform === undefined? {} : { transform: transform.toString() });
      var parentOrigin = parent.getOrigin();
      if (transform === undefined) {
        this.origin = parentOrigin;
      } else {
        this.origin = transform.apply(parentOrigin.x, parentOrigin.y);
      }
      memoize(this, "getCurrentColor");
    },
    remove: function () {
      this.drawArea.parentNode.removeChild(this.drawArea);
    },
    extendBoundingBox: function (boundingBox) {
      this.parent.extendBoundingBox(boundingBox);
    },
    getOrigin: function () {
      return this.origin;
    },
    getCurrentColor: function () {
      return this.parent.getCurrentColor();
    }
  });
  
  xypic.Graphics.SVG.ChangeColorGroup = xypic.Graphics.SVG.Subclass({
    Init: function (parent, color) {
      this.parent = parent;
      this.drawArea = parent.createSVGElement("g", {
        stroke: color
      });
      this.color = color;
      memoize(this, "getOrigin");
    },
    remove: function () {
      this.drawArea.parentNode.removeChild(this.drawArea);
    },
    extendBoundingBox: function (boundingBox) {
      this.parent.extendBoundingBox(boundingBox);
    },
    getOrigin: function () {
      return this.parent.getOrigin();
    },
    getCurrentColor: function () {
      return this.color;
    }
  });
  
  xypic.Graphics.Augment({}, {
    createSVG: function (height, depth, width, strokeWidth, color, def) {
      return xypic.Graphics.SVG.World(height, depth, width, strokeWidth, color, def);
    }
  });
  
  xypic.DrawingContext = MathJax.Object.Subclass({
    Init: function (shape, env) {
      this.shape = shape;
      this.env = env;
    },
    
    duplicateEnv: function () {
      var newEnv = this.env.duplicate();
      return xypic.DrawingContext(this.shape, newEnv);
    },
    
    /**
     * shapeを最前面に追加する。
     * @param {xypic.Shape} shape 追加する図形
     */
    appendShapeToFront: function (shape) {
      if (shape.isNone) {
      } else if (this.shape.isNone) {
        this.shape = shape;
      } else {
        this.shape = xypic.Shape.CompositeShape(shape, this.shape);
      }
    },
    
    /**
     * shapeを最背面に追加する。
     * @param {xypic.Shape} shape 追加する図形
     */
    appendShapeToBack: function (shape) {
      if (shape.isNone) {
      } else if (this.shape.isNone) {
        this.shape = shape;
      } else {
        this.shape = xypic.Shape.CompositeShape(this.shape, shape);
      }
    }
  });
  
  xypic.Util = MathJax.Object.Subclass({}, {
    extProd: function (v1, v2) {
      return [v1[1]*v2[2]-v1[2]*v2[1], v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1]-v1[1]*v2[0]];
    },
    sign: function (x) {
      return (x < 0? -1 : (x > 0? 1 : 0));
    },
    sign2: function (x) {
      return (x < 0? -1 : 1);
    },
    roundEpsilon: function (x) {
      if (Math.abs(x) < AST.xypic.machinePrecision) {
        return 0;
      } else {
        return x;
      }
    }
  });
  
  HUB.Browser.Select({
    MSIE: function (browser) {
      if (HUB.Browser.versionAtLeast("9.0") && document.documentMode >= 9) {
        xypic.useSVG = true;
      }
    },
    Firefox: function (browser) {
      xypic.useSVG = true;
    },
    Safari: function (browser) {
      xypic.useSVG = true;
    },
    Chrome: function (browser) {
      xypic.useSVG = true;
    },
    Opera: function (browser) {
      xypic.useSVG = true;
    }
  });
  
  xypic.Frame = MathJax.Object.Subclass({
    toRect: function (def) {
      return xypic.Frame.Rect(this.x, this.y, def);
    },
    toPoint: function () {
      return xypic.Frame.Point(this.x, this.y);
    },
    combineRect: function (that) {
      return xypic.Frame.combineRect(this, that);
    }
  },{
    combineRect: function (frame1, frame2) {
      if (frame1 === undefined) {
        return frame2;
      } else if (frame2 === undefined) {
        return frame1;
      } else {
        var l = -(Math.min(frame1.x-frame1.l, frame2.x-frame2.l) - frame1.x);
        var r = Math.max(frame1.x+frame1.r, frame2.x+frame2.r) - frame1.x;
        var d = -(Math.min(frame1.y-frame1.d, frame2.y-frame2.d) - frame1.y);
        var u = Math.max(frame1.y+frame1.u, frame2.y+frame2.u) - frame1.y;
        return frame1.toRect({l:l, r:r, d:d, u:u});
      }
    }
  });
  
  xypic.Frame.Point = xypic.Frame.Subclass({
    Init: function (x, y) {
      this.x = x;
      this.y = y;
    },
    l: 0,
    r: 0,
    u: 0,
    d: 0,
    isPoint: function () { return true; },
    isRect: function () { return false; },
    isCircle: function () { return false; },
    edgePoint: function (x, y) { return this; },
    proportionalEdgePoint: function (x, y) { return this; },
    grow: function (xMargin, yMargin) {
      var xm = Math.max(0, xMargin);
      var ym = Math.max(0, yMargin);
      return this.toRect({l:xm, r:xm, u:ym, d:ym});
    },
    toSize: function (width, height) {
      return this.toRect({ l:width / 2, r:width / 2, u:height / 2, d:height / 2 });
    },
    growTo: function (width, height) {
      var w = Math.max(0, width);
      var h = Math.max(0, height);
      return this.toRect({ l:w / 2, r:w / 2, u:h / 2, d:h / 2 });
    },
    shrinkTo: function (width, height) {
      return this;
    },
    move: function (x, y) {
      return xypic.Frame.Point(x, y);
    },
    shiftFrame: function (dx, dy) {
      return this;
    },
    rotate: function (angle) {
      return this;
    },
    contains: function (point) {
      return false;
    },
    toString: function () {
      return "{x:"+this.x+", y:"+this.y+"}";
    }
  });
  
  xypic.Frame.Rect = xypic.Frame.Subclass({
    Init: function (x, y, def) {
      this.x = x;
      this.y = y;
      this.l = (def.l || 0);
      this.r = (def.r || 0);
      this.u = (def.u || 0);
      this.d = (def.d || 0);
    },
    isPoint: function () {
      return this.l === 0 && this.r === 0 && this.u === 0 && this.d === 0;
    },
    isRect: function () { return !this.isPoint(); },
    isCircle: function () { return false; },
    edgePoint: function (x, y) {
      if (this.isPoint()) {
        return this;
      }
      var dx = x - this.x;
      var dy = y - this.y;
      if (dx > 0) {
        var ey = dy * this.r / dx;
        if (ey > this.u) {
          return xypic.Frame.Point(this.x + this.u * dx / dy, this.y + this.u);
        } else if (ey < -this.d) {
          return xypic.Frame.Point(this.x - this.d * dx / dy, this.y - this.d);
        }
        return xypic.Frame.Point(this.x + this.r, this.y + ey);
      } else if (dx < 0) {
        var ey = -dy * this.l / dx;
        if (ey > this.u) {
          return xypic.Frame.Point(this.x + this.u * dx / dy, this.y + this.u);
        } else if (ey < -this.d) {
          return xypic.Frame.Point(this.x - this.d * dx / dy, this.y - this.d);
        }
        return xypic.Frame.Point(this.x - this.l, this.y + ey);
      } else {
        if (dy > 0) {
          return xypic.Frame.Point(this.x, this.y + this.u);
        }
        return xypic.Frame.Point(this.x, this.y - this.d);
      }
    },
    proportionalEdgePoint: function (x, y) {
      if (this.isPoint()) {
        return this;
      }
      var dx = x - this.x;
      var dy = y - this.y;
      if (Math.abs(dx) < AST.xypic.machinePrecision && Math.abs(dy) < AST.xypic.machinePrecision) {
        return xypic.Frame.Point(this.x - this.l, this.y + this.u);
      }
      var w = this.l + this.r, h = this.u + this.d;
      var pi = Math.PI;
      var angle = Math.atan2(dy, dx);
      var f;
      if (-3*pi/4 < angle && angle <= -pi/4) {
        // d
        f = (angle + 3*pi/4)/(pi/2);
        return xypic.Frame.Point(this.x + this.r - f * w, this.y + this.u);
      } else if (-pi/4 < angle && angle <= pi/4) {
        // r
        f = (angle + pi/4)/(pi/2);
        return xypic.Frame.Point(this.x - this.l, this.y + this.u - f * h);
      } else if (pi/4 < angle && angle <= 3*pi/4) {
        // u
        f = (angle - pi/4)/(pi/2);
        return xypic.Frame.Point(this.x - this.l + f * w, this.y - this.d);
      } else {
        // l
        f = (angle - (angle > 0? 3*pi/4 : -5*pi/4))/(pi/2);
        return xypic.Frame.Point(this.x + this.r, this.y - this.d + f * h);
      }
    },
    grow: function (xMargin, yMargin) {
      return this.toRect({
        l:Math.max(0, this.l + xMargin),
        r:Math.max(0, this.r + xMargin),
        u:Math.max(0, this.u + yMargin),
        d:Math.max(0, this.d + yMargin)
      });
    },
    toSize: function (width, height) {
      var u, d, r, l;
      var ow = this.l + this.r;
      var oh = this.u + this.d;
      if (ow === 0) {
        l = width / 2;
        r = width / 2;
      } else {
        l = width * this.l / ow;
        r = width * this.r / ow;
      }
      if (oh === 0) {
        u = height / 2;
        d = height / 2;
      } else {
        u = height * this.u / oh;
        d = height * this.d / oh;
      }
      return this.toRect({ l:l, r:r, u:u, d:d });
    },
    growTo: function (width, height) {
      var u = this.u;
      var d = this.d;
      var r = this.r;
      var l = this.l;
      var ow = l + r;
      var oh = u + d;
      if (width > ow) {
        if (ow === 0) {
          l = width / 2;
          r = width / 2;
        } else {
          l = width * this.l / ow;
          r = width * this.r / ow;
        }
      }
      if (height > oh) {
        if (oh === 0) {
          u = height / 2;
          d = height / 2;
        } else {
          u = height * this.u / oh;
          d = height * this.d / oh;
        }
      }
      return this.toRect({ l:l, r:r, u:u, d:d });
    },
    shrinkTo: function (width, height) {
      var u = this.u;
      var d = this.d;
      var r = this.r;
      var l = this.l;
      var ow = l + r;
      var oh = u + d;
      if (width < ow) {
        if (ow === 0) {
          l = width / 2;
          r = width / 2;
        } else {
          l = width * this.l / ow;
          r = width * this.r / ow;
        }
      }
      if (height < oh) {
        if (oh === 0) {
          u = height / 2;
          d = height / 2;
        } else {
          u = height * this.u / oh;
          d = height * this.d / oh;
        }
      }
      
      return this.toRect({ l:l, r:r, u:u, d:d });
    },
    move: function (x, y) {
      return xypic.Frame.Rect(x, y, { l:this.l, r:this.r, u:this.u, d:this.d });
    },
    shiftFrame: function (dx, dy) {
      return xypic.Frame.Rect(this.x, this.y, {
        l:Math.max(0, this.l - dx),
        r:Math.max(0, this.r + dx),
        u:Math.max(0, this.u + dy),
        d:Math.max(0, this.d - dy)
      });
    },
    rotate: function (angle) {
      var c = Math.cos(angle), s = Math.sin(angle);
      var lx = -this.l, rx = this.r, uy = this.u, dy = -this.d;
      var lu = {x:lx*c-uy*s, y:lx*s+uy*c};
      var ld = {x:lx*c-dy*s, y:lx*s+dy*c};
      var ru = {x:rx*c-uy*s, y:rx*s+uy*c};
      var rd = {x:rx*c-dy*s, y:rx*s+dy*c};
      return this.toRect({
        l:-Math.min(lu.x, ld.x, ru.x, rd.x),
        r:Math.max(lu.x, ld.x, ru.x, rd.x),
        u:Math.max(lu.y, ld.y, ru.y, rd.y),
        d:-Math.min(lu.y, ld.y, ru.y, rd.y)
      });
    },
    contains: function (point) {
      var x = point.x;
      var y = point.y;
      return (x >= this.x - this.l) && (x <= this.x + this.r) && (y >= this.y - this.d) && (y <= this.y + this.u);
    },
    toString: function () {
      return "{x:"+this.x+", y:"+this.y+", l:"+this.l+", r:"+this.r+", u:"+this.u+", d:"+this.d+"}";
    }
  });
  
  xypic.Frame.Ellipse = xypic.Frame.Subclass({
    Init: function (x, y, l, r, u, d) {
      this.x = x;
      this.y = y;
      this.l = l;
      this.r = r;
      this.u = u;
      this.d = d;
    },
    isPoint: function () { return this.r === 0 && this.l ===0 || this.u === 0 && this.d ===0; },
    isRect: function () { return false; },
    isCircle: function () { return !this.isPoint(); },
    isPerfectCircle: function () {
      return this.l === this.r && this.l === this.u && this.l === this.d;
    },
    edgePoint: function (x, y) {
      if (this.isPoint()) {
        return this;
      }
      if (this.isPerfectCircle()) {
        var dx = x - this.x;
        var dy = y - this.y;
        var angle;
        if (Math.abs(dx) < AST.xypic.machinePrecision && Math.abs(dy) < AST.xypic.machinePrecision) {
          angle = -Math.PI/2;
        } else {
          angle = Math.atan2(dy, dx);
        }
        return xypic.Frame.Point(this.x + this.r * Math.cos(angle), this.y + this.r * Math.sin(angle));
      } else {
        // ellipse
        var pi = Math.PI;
        var l = this.l;
        var r = this.r;
        var u = this.u;
        var d = this.d;
        var x0 = this.x;
        var y0 = this.y;
        var cx = x0 + (r - l) / 2;
        var cy = y0 + (u - d) / 2;
        var rx = (l + r) / 2;
        var ry = (u + d) / 2;
        
        var dx = x - x0;
        var dy = y - y0;
        var a0 = dy;
        var b0 = -dx;
        var c0 = dx * y0 - dy * x0;
        var a = a0 * rx;
        var b = b0 * ry;
        var c = c0 * rx + (rx - ry) * b0 * cy;
        var aabb = a * a + b * b;
        var d = a * cx + b * cy + c;
        var e = -d / aabb;
        var ff = aabb * rx * rx - d * d;
        if (ff < 0) {
          return xypic.Frame.Point(this.x, this.y - this.d);
        }
        var f = Math.sqrt(ff) / aabb;
        
        var xp = a * e + b * f + cx;
        var yp = b * e - a * f + cy;
        var xm = a * e - b * f + cx;
        var ym = b * e + a * f + cy;
        
        var eps = ry / rx;
        var xp0 = xp;
        var yp0 = eps * (yp - cy) + cy;
        var xm0 = xm;
        var ym0 = eps * (ym - cy) + cy;
        
        var sign = xypic.Util.sign;
        
        if (sign(xp0 - cx) === sign(x - cx) && sign(yp0 - cy) === sign(y - cy)) {
          return xypic.Frame.Point(xp0, yp0);
        } else {
          return xypic.Frame.Point(xm0, ym0);
        }
      }
    },
    proportionalEdgePoint: function (x, y) {
      if (this.isPoint()) {
        return this;
      }
      if (this.isPerfectCircle()) {
        var dx = x - this.x;
        var dy = y - this.y;
        var angle;
        if (Math.abs(dx) < AST.xypic.machinePrecision && Math.abs(dy) < AST.xypic.machinePrecision) {
          angle = -Math.PI/2;
        } else {
          angle = Math.atan2(dy, dx);
        }
        return xypic.Frame.Point(this.x - this.r * Math.cos(angle), this.y - this.r * Math.sin(angle));
      } else {
        // ellipse
        var pi = Math.PI;
        var l = this.l;
        var r = this.r;
        var u = this.u;
        var d = this.d;
        var x0 = this.x;
        var y0 = this.y;
        var cx = x0 + (r - l) / 2;
        var cy = y0 + (u - d) / 2;
        var rx = (l + r) / 2;
        var ry = (u + d) / 2;
        
        var dx = x - x0;
        var dy = y - y0;
        var a0 = dy;
        var b0 = -dx;
        var c0 = dx * y0 - dy * x0;
        var a = a0 * rx;
        var b = b0 * ry;
        var c = c0 * rx + (rx - ry) * b0 * cy;
        var aabb = a * a + b * b;
        var d = a * cx + b * cy + c;
        var e = -d / aabb;
        var ff = aabb * rx * rx - d * d;
        if (ff < 0) {
          return xypic.Frame.Point(this.x, this.y - this.d);
        }
        var f = Math.sqrt(ff) / aabb;
        
        var xp = a * e + b * f + cx;
        var yp = b * e - a * f + cy;
        var xm = a * e - b * f + cx;
        var ym = b * e + a * f + cy;
        
        var eps = ry / rx;
        var xp0 = xp;
        var yp0 = eps * (yp - cy) + cy;
        var xm0 = xm;
        var ym0 = eps * (ym - cy) + cy;
        
        var dxp = xp0 - x;
        var dyp = yp0 - y;
        var dxm = xm0 - x;
        var dym = ym0 - y;
        
        if (sign(xp0 - cx) === sign(x - cx) && sign(yp0 - cy) === sign(y - cy)) {
          return xypic.Frame.Point(xm0, ym0);
        } else {
          return xypic.Frame.Point(xp0, yp0);
        }
      }
    },
    grow: function (xMargin, yMargin) {
      return xypic.Frame.Ellipse(
        this.x, this.y, 
        Math.max(0, this.l + xMargin), 
        Math.max(0, this.r + xMargin), 
        Math.max(0, this.u + yMargin), 
        Math.max(0, this.d + yMargin));
    },
    toSize: function (width, height) {
      var u, d, r, l;
      var ow = this.l + this.r;
      var oh = this.u + this.d;
      if (ow === 0) {
        l = width / 2;
        r = width / 2;
      } else {
        l = width * this.l / ow;
        r = width * this.r / ow;
      }
      if (oh === 0) {
        u = height / 2;
        d = height / 2;
      } else {
        u = height * this.u / oh;
        d = height * this.d / oh;
      }
      
      return xypic.Frame.Ellipse(this.x, this.y, l, r, u, d);
    },
    growTo: function (width, height) {
      var u = this.u;
      var d = this.d;
      var r = this.r;
      var l = this.l;
      var ow = l + r;
      var oh = u + d;
      if (width > ow) {
        if (ow === 0) {
          l = width / 2;
          r = width / 2;
        } else {
          l = width * this.l / ow;
          r = width * this.r / ow;
        }
      }
      if (height > oh) {
        if (oh === 0) {
          u = height / 2;
          d = height / 2;
        } else {
          u = height * this.u / oh;
          d = height * this.d / oh;
        }
      }
      
      return xypic.Frame.Ellipse(this.x, this.y, l, r, u, d);
    },
    shrinkTo: function (width, height) {
      var u = this.u;
      var d = this.d;
      var r = this.r;
      var l = this.l;
      var ow = l + r;
      var oh = u + d;
      if (width < ow) {
        if (ow === 0) {
          l = width / 2;
          r = width / 2;
        } else {
          l = width * this.l / ow;
          r = width * this.r / ow;
        }
      }
      if (height < oh) {
        if (oh === 0) {
          u = height / 2;
          d = height / 2;
        } else {
          u = height * this.u / oh;
          d = height * this.d / oh;
        }
      }
      
      return xypic.Frame.Ellipse(this.x, this.y, l, r, u, d);
    },
    move: function (x, y) {
      return xypic.Frame.Ellipse(x, y, this.l, this.r, this.u, this.d);
    },
    shiftFrame: function (dx, dy) {
      return xypic.Frame.Ellipse(this.x, this.y, 
        Math.max(0, this.l - dx),
        Math.max(0, this.r + dx),
        Math.max(0, this.u + dy),
        Math.max(0, this.d - dy)
      );
    },
    rotate: function (angle) {
      return this;
    },
    contains: function (point) {
      var x = point.x;
      var y = point.y;
      if (this.isPoint()) {
        return false;
      }
      var l = this.l;
      var r = this.r;
      var u = this.u;
      var d = this.d;
      var x0 = this.x;
      var y0 = this.y;
      var cx = x0 + (r - l) / 2;
      var cy = y0 + (u - d) / 2;
      var rx = (l + r) / 2;
      var ry = (u + d) / 2;
      
      var eps = ry / rx;
      var dx = x - cx;
      var dy = (y - cy) / eps;
      
      return dx * dx + dy * dy <= rx * rx;
    },
    toString: function () {
      return "{x:" + this.x + ", y:" + this.y + ", l:" + this.l + ", r:" + this.r + ", u:" + this.u + ", d:" + this.d + "}";
    }
  });
  
  xypic.Range = MathJax.Object.Subclass({
    Init: function (start, end) {
      if (start > end) {
        this.start = end;
        this.end = start;
      } else {
        this.start = start;
        this.end = end;
      }
    },
    /**
     * returns difference ranges between this range and a given range: this range \ a given range.
     */
    difference: function (range) {
      var diff = FP.List.empty;
      var a0 = this.start;
      var a1 = this.end;
      var b0 = range.start;
      var b1 = range.end;
      if (a1 <= b0) {
        // a0 < a1 <= b0 < b1
        diff = diff.prepend(this);
      } else if (b1 <= a0) {
        // b0 < b1 <= a0 < a1
        diff = diff.prepend(this);
      } else if (a0 < b0) {
        if (a1 <= b1) {
          // a0 < b0 <= a1 <= b1
          diff = diff.prepend(xypic.Range(a0, b0));
        } else {
          // a0 < b0 < b1 < a1
          diff = diff.prepend(xypic.Range(a0, b0));
          diff = diff.prepend(xypic.Range(b1, a1));
        }
      } else /* if (b0 <= a0) */ {
        if (b1 < a1) {
          // b0 <= a0 <= b1 < a1
          diff = diff.prepend(xypic.Range(b1, a1));
        } /* else {
          // b0 <= a0 < a1 <= b1
        } */
      }
      return diff;
    },
    differenceRanges: function (ranges) {
      var result = FP.List.empty.prepend(this);
      ranges.foreach(function (range) {
        result = result.flatMap(function (remaining) {
          return remaining.difference(range);
        });
      });
      return result;
    },
    toString: function () {
      return "[" + this.start + ", " + this.end + "]";
    }
  });
  
  xypic.Shape = MathJax.Object.Subclass({
    // <<interface>>
    /**
     * 図形を描画する。
     * @param {xypic.Graphics.SVG} svg SVG
     */
    // draw: function (svg) {}
    
    /**
     * Bounding Boxを返す。
     * @returns {xypic.Frame.Rect} Bounding Box (ない場合はundefined)
     */
    // getBoundingBox: function () {}
    
    /**
     * 中身が空であるかどうかを返す。
     * @returns {boolean} true:空である、false:空でない
     */
    isNone: false
  });
  
  xypic.Shape.NoneShape = xypic.Shape.Subclass({
    draw: function (svg) {
    },
    getBoundingBox: function () {
      return undefined;
    },
    toString: function () {
      return "NoneShape";
    },
    isNone: true
  });
  
  xypic.Shape.Augment({}, {
    none: xypic.Shape.NoneShape()
  });
  
  xypic.Shape.InvisibleBoxShape = xypic.Shape.Subclass({
    Init: function (bbox) {
      this.bbox = bbox;
    },
    draw: function (svg) {
    },
    getBoundingBox: function () {
      return this.bbox;
    },
    toString: function () {
      return "InvisibleBoxShape[bbox:" + this.bbox.toString() + "]";
    }
  });
  
  xypic.Shape.TranslateShape = xypic.Shape.Subclass({
    Init: function (dx, dy, shape) {
      this.dx = dx;
      this.dy = dy;
      this.shape = shape;
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      var g = svg.createGroup(svg.transformBuilder().translate(this.dx, this.dy));
      this.shape.draw(g);
    },
    getBoundingBox: function () {
      var bbox = this.shape.getBoundingBox();
      if (bbox === undefined) {
        return undefined;
      }
      return xypic.Frame.Rect(bbox.x + this.dx, bbox.y + this.dy, bbox);
    },
    toString: function () {
      return "TranslateShape[dx:" + this.dx + ", dy:" + this.dy + ", shape:" + this.shape.toString() + "]";
    }
  });
  
  xypic.Shape.CompositeShape = xypic.Shape.Subclass({
    Init: function (foregroundShape, backgroundShape) {
      this.foregroundShape = foregroundShape;
      this.backgroundShape = backgroundShape;
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      this.backgroundShape.draw(svg);
      this.foregroundShape.draw(svg);
    },
    getBoundingBox: function () {
      return xypic.Frame.combineRect(this.foregroundShape.getBoundingBox(), this.backgroundShape.getBoundingBox());
    },
    toString: function () {
      return "(" + this.foregroundShape.toString() + ", " + this.backgroundShape.toString() + ")";
    }
  });
  
  xypic.Shape.ChangeColorShape = xypic.Shape.Subclass({
    Init: function (color, shape) {
      this.color = color;
      this.shape = shape;
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      var g = svg.createChangeColorGroup(this.color);
      this.shape.draw(g);
    },
    getBoundingBox: function () {
      return this.shape.getBoundingBox();
    },
    toString: function () {
      return "" + this.shape + ", color:" + this.color;
    }
  });
  
  xypic.Shape.CircleSegmentShape = xypic.Shape.Subclass({
    Init: function (x, y, sx, sy, r, large, flip, ex, ey) {
      this.x = x;
      this.y = y;
      this.sx = sx;
      this.sy = sy;
      this.r = r;
      this.large = large;
      this.flip = flip;
      this.ex = ex;
      this.ey = ey;
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      svg.createSVGElement("path", {
        d:"M" + xypic.em2px(this.sx) + "," + xypic.em2px(-this.sy) + " A" + xypic.em2px(this.r) + "," + xypic.em2px(this.r) + " 0 " + this.large + "," + this.flip + " " + xypic.em2px(this.ex) + "," + xypic.em2px(-this.ey)
      });
    },
    getBoundingBox: function () {
      return xypic.Frame.Ellipse(this.x, this.y, this.r, this.r, this.r, this.r);
    },
    toString: function () {
      return "CircleSegmentShape[x:" + this.x + ", y:" + this.y + ", sx:" + this.sx + ", sy:" + this.sy + ", r:" + this.r + ", large:" + this.large + ", flip:" + this.flip + ", ex:" + this.ex + ", ey:" + this.ey + "]";
    }
  });
  
  xypic.Shape.FullCircleShape = xypic.Shape.Subclass({
    Init: function (x, y, r) {
      this.x = x;
      this.y = y;
      this.r = r;
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      svg.createSVGElement("circle", {
        cx:xypic.em2px(this.x), cy:xypic.em2px(-this.y), r:xypic.em2px(this.r)
      });
    },
    getBoundingBox: function () {
      return xypic.Frame.Ellipse(this.x, this.y, this.r, this.r, this.r, this.r);
    },
    toString: function () {
      return "FullCircleShape[x:" + this.x + ", y:" + this.y + ", r:" + this.r + "]";
    }
  });
  
  xypic.Shape.RectangleShape = xypic.Shape.Subclass({
    Init: function (x, y, left, right, up, down, r, isDoubled, color, dasharray, fillColor, hideLine) {
      this.x = x;
      this.y = y;
      this.left = left;
      this.right = right;
      this.up = up;
      this.down = down;
      this.r = r;
      this.isDoubled = isDoubled;
      this.color = color;
      this.dasharray = dasharray;
      this.fillColor = fillColor;
      this.hideLine = hideLine || false;
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      var def;
      def = {
        x:xypic.em2px(this.x - this.left), 
        y:-xypic.em2px(this.y + this.up), 
        width:xypic.em2px(this.left + this.right), 
        height:xypic.em2px(this.up + this.down), 
        rx:xypic.em2px(this.r)
      };
      if (this.dasharray !== undefined) {
        def["stroke-dasharray"] = this.dasharray;
      }
      if (this.hideLine) {
        def["stroke"] = "none";
      } else if (this.color !== undefined) {
        def["stroke"] = this.color;
      }
      if (this.fillColor !== undefined) {
        def["fill"] = this.fillColor;
      }
      svg.createSVGElement("rect", def);
      if (this.isDoubled) {
        def = {
          x:xypic.em2px(this.x - this.left + AST.xypic.thickness), 
          y:-xypic.em2px(this.y + this.up - AST.xypic.thickness), 
          width:xypic.em2px(this.left + this.right - 2 * AST.xypic.thickness), 
          height:xypic.em2px(this.up + this.down - 2 * AST.xypic.thickness), 
          rx:xypic.em2px(Math.max(this.r - AST.xypic.thickness, 0))
        };
        if (this.dasharray !== undefined) {
          def["stroke-dasharray"] = this.dasharray;
        }
        if (this.hideLine) {
          def["stroke"] = "none";
        } else if (this.color !== undefined) {
          def["stroke"] = this.color;
        }
        if (this.fillColor !== undefined) {
          def["fill"] = this.fillColor;
        }
        svg.createSVGElement("rect", def);
      }
    },
    getBoundingBox: function () {
      return xypic.Frame.Rect(this.x, this.y, { l:this.left, r:this.right, u:this.up, d:this.down });
    },
    toString: function () {
      return "RectangleShape[x:" + this.x + ", y:" + this.y + ", left:" + this.left + ", right:" + this.right + ", up:" + this.up + ", down:" + this.down + ", r:" + this.r + ", isDouble:" + this.isDouble + ", dasharray:" + this.dasharray + "]";
    }
  });
  
  xypic.Shape.EllipseShape = xypic.Shape.Subclass({
    Init: function (x, y, rx, ry, isDoubled, color, dasharray, fillColor, hideLine) {
      this.x = x;
      this.y = y;
      this.rx = rx;
      this.ry = ry;
      this.isDoubled = isDoubled;
      this.color = color;
      this.dasharray = dasharray;
      this.fillColor = fillColor;
      this.hideLine = hideLine || false;
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      var def;
      def = {
        cx:xypic.em2px(this.x), 
        cy:-xypic.em2px(this.y), 
        rx:xypic.em2px(this.rx), 
        ry:xypic.em2px(this.ry)
      };
      if (this.dasharray !== undefined) {
        def["stroke-dasharray"] = this.dasharray;
      }
      if (this.hideLine) {
        def["stroke"] = "none";
      } else if (this.color !== undefined) {
        def["stroke"] = this.color;
      }
      if (this.fillColor !== undefined) {
        def["fill"] = this.fillColor;
      }
      svg.createSVGElement("ellipse", def);
      if (this.isDoubled) {
        def = {
          cx:xypic.em2px(this.x), 
          cy:-xypic.em2px(this.y), 
          rx:xypic.em2px(Math.max(this.rx - AST.xypic.thickness)), 
          ry:xypic.em2px(Math.max(this.ry - AST.xypic.thickness))
        };
        if (this.dasharray !== undefined) {
          def["stroke-dasharray"] = this.dasharray;
        }
        if (this.hideLine) {
          def["stroke"] = "none";
        } else if (this.color !== undefined) {
          def["stroke"] = this.color;
        }
        if (this.fillColor !== undefined) {
          def["fill"] = this.fillColor;
        }
        svg.createSVGElement("ellipse", def);
      }
    },
    getBoundingBox: function () {
      return xypic.Frame.Rect(this.x, this.y, { l:this.rx, r:this.rx, u:this.ry, d:this.ry });
    },
    toString: function () {
      return "EllipseShape[x:" + this.x + ", y:" + this.y + ", rx:" + this.rx + ", ry:" + this.ry + ", isDoubled:" + this.isDoubled + ", dasharray:" + this.dasharray + "]";
    }
  });
  
  xypic.Shape.BoxShadeShape = xypic.Shape.Subclass({
    Init: function (x, y, left, right, up, down, depth, color) {
      this.x = x;
      this.y = y;
      this.left = left;
      this.right = right;
      this.up = up;
      this.down = down;
      this.depth = depth;
      this.color = color || "currentColor";
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      var x = xypic.em2px(this.x);
      var y = xypic.em2px(this.y);
      var l = xypic.em2px(this.left);
      var r = xypic.em2px(this.right);
      var u = xypic.em2px(this.up);
      var d = xypic.em2px(this.down);
      var depth = xypic.em2px(this.depth);
      svg.createSVGElement("path", {
        d: "M" + (x - l + depth) + "," + (-y + d) + 
          "L" + (x + r) + "," + (-y + d) + 
          "L" + (x + r) + "," + (-y - u + depth) + 
          "L" + (x + r + depth) + "," + (-y - u + depth) + 
          "L" + (x + r + depth) + "," + (-y + d + depth) + 
          "L" + (x - l + depth) + "," + (-y + d + depth) + 
          "Z",
        stroke: this.color,
        fill: this.color
      });
    },
    getBoundingBox: function () {
      return xypic.Frame.Rect(this.x, this.y, { l:this.left, r:this.right + this.depth, u:this.up, d:this.down + this.depth });
    },
    toString: function () {
      return "RectangleShape[x:" + this.x + ", y:" + this.y + ", left:" + this.left + ", right:" + this.right + ", up:" + this.up + ", down:" + this.down + ", depth:" + this.depth + "]";
    }
  });
  
  xypic.Shape.LeftBrace = xypic.Shape.Subclass({
    Init: function (x, y, up, down, degree, color) {
      this.x = x;
      this.y = y;
      this.up = up;
      this.down = down;
      this.degree = degree;
      this.color = color || "currentColor";
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      var scale = xypic.oneem;
      var down = Math.max(0.759375 + 0.660375, this.down / scale * 1.125) - 0.660375;
      var up = - Math.max(0.759375 + 0.660375, this.up / scale * 1.125) + 0.660375;
      
      var d;
      d = "M" + xypic.em2px(-0.0675) + " " + xypic.em2px(down) + 
        "T" + xypic.em2px(-0.068625) + " " + xypic.em2px(0.07875 + down) + 
        "Q" + xypic.em2px(-0.068625) + " " + xypic.em2px(0.190125 + down) + 
        " " + xypic.em2px(-0.0585) + " " + xypic.em2px(0.250875 + down) + 
        "T" + xypic.em2px(-0.01125) + " " + xypic.em2px(0.387 + down) + 
        "Q" + xypic.em2px(0.07425) + " " + xypic.em2px(0.55575 + down) + 
        " " + xypic.em2px(0.2475) + " " + xypic.em2px(0.6525 + down) + 
        "L" + xypic.em2px(0.262125) + " " + xypic.em2px(0.660375 + down) + 
        "L" + xypic.em2px(0.3015) + " " + xypic.em2px(0.660375 + down) + 
        "L" + xypic.em2px(0.30825) + " " + xypic.em2px(0.653625 + down) + 
        "V" + xypic.em2px(0.622125 + down) + 
        "Q" + xypic.em2px(0.30825) + " " + xypic.em2px(0.60975 + down) + 
        " " + xypic.em2px(0.2925) + " " + xypic.em2px(0.60075 + down) + 
        "Q" + xypic.em2px(0.205875) + " " + xypic.em2px(0.541125 + down) + 
        " " + xypic.em2px(0.149625) + " " + xypic.em2px(0.44775 + down) + 
        "T" + xypic.em2px(0.07425) + " " + xypic.em2px(0.239625 + down) + 
        "Q" + xypic.em2px(0.07425) + " " + xypic.em2px(0.2385 + down) + 
        " " + xypic.em2px(0.073125) + " " + xypic.em2px(0.235125 + down) + 
        "Q" + xypic.em2px(0.068625) + " " + xypic.em2px(0.203625 + down) + 
        " " + xypic.em2px(0.0675) + " " + xypic.em2px(0.041625 + down) + 
        "L" + xypic.em2px(0.0675) + " " + xypic.em2px(0.75825) + 
        "Q" + xypic.em2px(0.0675) + " " + xypic.em2px(0.496125) + 
        " " + xypic.em2px(0.066375) + " " + xypic.em2px(0.486) + 
        "Q" + xypic.em2px(0.05625) + " " + xypic.em2px(0.336375) + 
        " " + xypic.em2px(-0.021375) + " " + xypic.em2px(0.212625) + 
        "T" + xypic.em2px(-0.226125) + " " + xypic.em2px(0.010125) + 
        "L" + xypic.em2px(-0.241875) + " 0" + 
        "L" + xypic.em2px(-0.226125) + " " + xypic.em2px(-0.010125) + 
        "Q" + xypic.em2px(-0.106875) + " " + xypic.em2px(-0.084375) + 
        " " + xypic.em2px(-0.025875) + " " + xypic.em2px(-0.207) + 
        "T" + xypic.em2px(0.066375) + " " + xypic.em2px(-0.486) + 
        "Q" + xypic.em2px(0.0675) + " " + xypic.em2px(-0.496125) + 
        " " + xypic.em2px(0.0675) + " " + xypic.em2px(-0.75825) + 
        "L" + xypic.em2px(0.0675) + " " + xypic.em2px(-0.041625 + up) + 
        "Q" + xypic.em2px(0.068625) + " " + xypic.em2px(-0.203625 + up) + 
        " " + xypic.em2px(0.073125) + " " + xypic.em2px(-0.235125 + up) + 
        "Q" + xypic.em2px(0.07425) + " " + xypic.em2px(-0.2385 + up) + 
        " " + xypic.em2px(0.07425) + " " + xypic.em2px(-0.239625 + up) + 
        "Q" + xypic.em2px(0.093375) + " " + xypic.em2px(-0.354375 + up) + 
        " " + xypic.em2px(0.149625) + " " + xypic.em2px(-0.44775 + up) + 
        "T" + xypic.em2px(0.2925) + " " + xypic.em2px(-0.60075 + up) + 
        "Q" + xypic.em2px(0.30825) + " " + xypic.em2px(-0.60975 + up) + 
        " " + xypic.em2px(0.30825) + " " + xypic.em2px(-0.622125 + up) + 
        "L" + xypic.em2px(0.30825) + " " + xypic.em2px(-0.653625 + up) + 
        "L" + xypic.em2px(0.3015) + " " + xypic.em2px(-0.660375 + up) + 
        "L" + xypic.em2px(0.262125) + " " + xypic.em2px(-0.660375 + up) + 
        "L" + xypic.em2px(0.2475) + " " + xypic.em2px(-0.6525 + up) + 
        "Q" + xypic.em2px(0.07425) + " " + xypic.em2px(-0.55575 + up) + 
        " " + xypic.em2px(-0.01125) + " " + xypic.em2px(-0.387 + up) + 
        "Q" + xypic.em2px(-0.048375) + " " + xypic.em2px(-0.311625 + up) + 
        " " + xypic.em2px(-0.0585) + " " + xypic.em2px(-0.250875 + up) + 
        "T" + xypic.em2px(-0.068625) + " " + xypic.em2px(-0.07875 + up) + 
        "Q" + xypic.em2px(-0.0675) + " " + xypic.em2px(up) + 
        " " + xypic.em2px(-0.0675) + " " + xypic.em2px(up) + 
        "L" + xypic.em2px(-0.0675) + " " + xypic.em2px(-0.759375) + 
        "V" + xypic.em2px(-0.5985) + 
        "Q" + xypic.em2px(-0.0675) + " " + xypic.em2px(-0.47925) + 
        " " + xypic.em2px(-0.075375) + " " + xypic.em2px(-0.41175) + 
        "T" + xypic.em2px(-0.11475) + " " + xypic.em2px(-0.27) + 
        "Q" + xypic.em2px(-0.133875) + " " + xypic.em2px(-0.2205) + 
        " " + xypic.em2px(-0.160875) + " " + xypic.em2px(-0.17775) + 
        "T" + xypic.em2px(-0.212625) + " " + xypic.em2px(-0.106875) + 
        "T" + xypic.em2px(-0.25875) + " " + xypic.em2px(-0.06075) + 
        "T" + xypic.em2px(-0.293625) + " " + xypic.em2px(-0.0315) + 
        "T" + xypic.em2px(-0.307125) + " " + xypic.em2px(-0.02025) + 
        "Q" + xypic.em2px(-0.30825) + " " + xypic.em2px(-0.019125) + 
        " " + xypic.em2px(-0.30825) + " 0" + 
        "T" + xypic.em2px(-0.307125) + " " + xypic.em2px(0.02025) + 
        "Q" + xypic.em2px(-0.307125) + " " + xypic.em2px(0.021375) + 
        " " + xypic.em2px(-0.284625) + " " + xypic.em2px(0.03825) + 
        "T" + xypic.em2px(-0.2295) + " " + xypic.em2px(0.091125) + 
        "T" + xypic.em2px(-0.162) + " " + xypic.em2px(0.176625) + 
        "T" + xypic.em2px(-0.10125) + " " + xypic.em2px(0.30825) + 
        "T" + xypic.em2px(-0.068625) + " " + xypic.em2px(0.482625) + 
        "Q" + xypic.em2px(-0.0675) + " " + xypic.em2px(0.496125) + 
        " " + xypic.em2px(-0.0675) + " " + xypic.em2px(0.759375) + 
        "Z";
      svg.createSVGElement("path", {
        d:d, 
        fill:this.color, 
        stroke:this.color, 
        "stroke-width":"0pt", 
        transform:"translate(" + xypic.em2px(this.x) + "," + xypic.em2px(-this.y) +") rotate(" + (-this.degree) + ") scale(" + (scale / 1.125) + ")"
      });
    },
    getBoundingBox: function () {
      var scale = xypic.oneem;
      return xypic.Frame.Rect(this.x, this.y, { l:0.274 * scale, r:0.274 * scale, u:Math.max((0.759375 + 0.660375) * scale / 1.125, this.up), d:Math.max((0.759375 + 0.660375) * scale / 1.125, this.down) }).rotate(this.degree * Math.PI / 180);
    },
    toString: function () {
      return "LeftBrace[x:" + this.x + ", y:" + this.y + ", up:" + this.up + ", down:" + this.down + "]";
    }
  });
  
  xypic.Shape.LeftParenthesis = xypic.Shape.Subclass({
    Init: function (x, y, height, degree, color) {
      this.x = x;
      this.y = y;
      this.height = height;
      this.degree = degree;
      this.color = color || "currentColor";
      memoize(this, "getBoundingBox");
    },
    draw: function (svg) {
      var scale = xypic.oneem;
      var down = Math.max(0.660375, this.height / 2 / scale * 1.125) - 0.660375;
      var up = -down;
      
      var d;
      d = "M" + xypic.em2px(-0.0675) + " " + xypic.em2px(down) + 
        "T" + xypic.em2px(-0.068625) + " " + xypic.em2px(0.07875 + down) + 
        "Q" + xypic.em2px(-0.068625) + " " + xypic.em2px(0.190125 + down) + 
        " " + xypic.em2px(-0.0585) + " " + xypic.em2px(0.250875 + down) + 
        "T" + xypic.em2px(-0.01125) + " " + xypic.em2px(0.387 + down) + 
        "Q" + xypic.em2px(0.07425) + " " + xypic.em2px(0.55575 + down) + 
        " " + xypic.em2px(0.2475) + " " + xypic.em2px(0.6525 + down) + 
        "L" + xypic.em2px(0.262125) + " " + xypic.em2px(0.660375 + down) + 
        "L" + xypic.em2px(0.3015) + " " + xypic.em2px(0.660375 + down) + 
        "L" + xypic.em2px(0.30825) + " " + xypic.em2px(0.653625 + down) + 
        "V" + xypic.em2px(0.622125 + down) + 
        "Q" + xypic.em2px(0.30825) + " " + xypic.em2px(0.60975 + down) + 
        " " + xypic.em2px(0.2925) + " " + xypic.em2px(0.60075 + down) + 
        "Q" + xypic.em2px(0.205875) + " " + xypic.em2px(0.541125 + down) + 
        " " + xypic.em2px(0.149625) + " " + xypic.em2px(0.44775 + down) + 
        "T" + xypic.em2px(0.07425) + " " + xypic.em2px(0.239625 + down) + 
        "Q" + xypic.em2px(0.07425) + " " + xypic.em2px(0.2385 + down) + 
        " " + xypic.em2px(0.073125) + " " + xypic.em2px(0.235125 + down) + 
        "Q" + xypic.em2px(0.068625) + " " + xypic.em2px(0.203625 + down) + 
        " " + xypic.em2px(0.0675) + " " + xypic.em2px(0.041625 + down) + 
        "L" + xypic.em2px(0.0675) + " " + xypic.em2px(-0.041625 + up) + 
        "Q" + xypic.em2px(0.068625) + " " + xypic.em2px(-0.203625 + up) + 
        " " + xypic.em2px(0.073125) + " " + xypic.em2px(-0.235125 + up) + 
        "Q" + xypic.em2px(0.07425) + " " + xypic.em2px(-0.2385 + up) + 
        " " + xypic.em2px(0.07425) + " " + xypic.em2px(-0.239625 + up) + 
        "Q" + xypic.em2px(0.093375) + " " + xypic.em2px(-0.354375 + up) + 
        " " + xypic.em2px(0.149625) + " " + xypic.em2px(-0.44775 + up) + 
        "T" + xypic.em2px(0.2925) + " " + xypic.em2px(-0.60075 + up) + 
        "Q" + xypic.em2px(0.30825) + " " + xypic.em2px(-0.60975 + up) + 
        " " + xypic.em2px(0.30825) + " " + xypic.em2px(-0.622125 + up) + 
        "L" + xypic.em2px(0.30825) + " " + xypic.em2px(-0.653625 + up) + 
        "L" + xypic.em2px(0.3015) + " " + xypic.em2px(-0.660375 + up) + 
        "L" + xypic.em2px(0.262125) + " " + xypic.em2px(-0.660375 + up) + 
        "L" + xypic.em2px(0.2475) + " " + xypic.em2px(-0.6525 + up) + 
        "Q" + xypic.em2px(0.07425) + " " + xypic.em2px(-0.55575 + up) + 
        " " + xypic.em2px(-0.01125) + " " + xypic.em2px(-0.387 + up) + 
        "Q" + xypic.em2px(-0.048375) + " " + xypic.em2px(-0.311625 + up) + 
        " " + xypic.em2px(-0.0585) + " " + xypic.em2px(-0.250875 + up) + 
        "T" + xypic.em2px(-0.068625) + " " + xypic.em2px(-0.07875 + up) + 
        "Q" + xypic.em2px(-0.0675) + " " + xypic.em2px(up) + 
        " " + xypic.em2px(-0.0675) + " " + xypic.em2px(up) + 
        "Z";
      svg.createSVGElement("path", {
        d:d, 
        fill:this.color, 
        stroke:this.color, 
        "stroke-width":"0pt", 
        transform:"translate(" + xypic.em2px(this.x) + "," + xypic.em2px(-this.y) +") rotate(" + (-this.degree) + ") scale(" + (scale / 1.125) + ")"
      });
    },
    getBoundingBox: function () {
      var scale = xypic.oneem;
      return xypic.Frame.Rect(this.x, this.y, { l:0.06 * scale, r:0.274 * scale, u:Math.max(0.660375 * scale / 1.125, this.height / 2), d:Math.max(0.660375 * scale / 1.125, this.height / 2) }).rotate(this.degree * Math.PI / 180);
    },
    toString: function () {
      return "LeftBrace[x:" + this.x + ", y:" + this.y + ", up:" + this.up + ", down:" + this.down + "]";
    }
  });
  
  xypic.Shape.TextShape = xypic.Shape.Subclass({
    Init: function (c, math, svgForTestLayout) {
      this.c = c;
      this.math = math;
      this.svgForTestLayout = svgForTestLayout;
      this.originalBBox = undefined;
      memoize(this, "getBoundingBox");
      memoize(this, "getOriginalReferencePoint");
    },
    draw: function (svg) {
      this._draw(svg, false);
    },
    getBoundingBox: function () {
      return this._draw(this.svgForTestLayout, true);
    },
    getOriginalReferencePoint: function () {
      this.getBoundingBox();
      var originalBBox = this.originalBBox;
      
      var c = this.c;
      var H = originalBBox.H;
      var D = originalBBox.D;
      return xypic.Frame.Point(c.x, c.y - (H - D) / 2);
    },
    toString: function () {
      return "TextShape[c:" + this.c.toString() + ", math:" + this.math.toString() + "]";
    }
  });
  
  xypic.Shape.ImageShape = xypic.Shape.Subclass({
    Init: function (c, url) {
      this.c = c;
      this.url = url;
      memoize(this, "getBoundingBox");
      memoize(this, "getOriginalReferencePoint");
    },
    draw: function (svg) {
      var c = this.c;
      svg.createSVGElement("image", {
        x: xypic.em2px(c.x - c.l),
        y: xypic.em2px(-c.y - c.u),
        width: xypic.em2px(c.l + c.r),
        height: xypic.em2px(c.u + c.d),
        preserveAspectRatio: "none",
        "xlink:href": this.url
      });
    },
    getBoundingBox: function () {
      return this.c;
    },
    getOriginalReferencePoint: function () {
      return this.c;
    },
    toString: function () {
      return "ImageShape[c:" + this.c.toString() + ", height:" + this.height + ", width:" + this.width + ", url:" + this.url + "]";
    }
  });
  
  xypic.Shape.ArrowheadShape = xypic.Shape.Subclass({
    draw: function (svg) {
      var g = svg.createGroup(svg.transformBuilder().translate(this.c.x, this.c.y).rotateRadian(this.angle));
      this.drawDelegate(g);
    },
    getBoundingBox: function () {
      return this.c.toRect(this.getBox()).rotate(this.angle);
    },
    toString: function () {
      return "ArrowheadShape[c:" + this.c.toString() + ", angle:" + this.angle + "]";
    }
  });
  
  // @2{>}
  xypic.Shape.GT2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.456 * scale, r:0, d:0.229 * scale, u:0.229 * scale }; },
    getRadius: function () {
      var scale = xypic.oneem;
      return 0.213 * scale;
    },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var gu = svg.createGroup(svg.transformBuilder().rotateDegree(-10));
      var gd = svg.createGroup(svg.transformBuilder().rotateDegree(10));
      gu.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + ","+xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @3{>}
  xypic.Shape.GT3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.507 * scale, r:0, d:0.268 * scale, u:0.268 * scale }; }, 
    getRadius: function () {
      var scale = xypic.oneem;
      return 0.325 * scale;
    },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var gu = svg.createGroup(svg.transformBuilder().rotateDegree(-15));
      var gd = svg.createGroup(svg.transformBuilder().rotateDegree(15));
      gu.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:xypic.em2px(-0.507 * scale), y2:0
      });
      gd.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @^{>}
  xypic.Shape.UpperGTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.489 * scale, r:0, d:0, u:0.147 * scale }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
    }
  });
  
  // @_{>}
  xypic.Shape.LowerGTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.489 * scale, r:0, d:0.147 * scale, u:0 }; },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @{>}
  xypic.Shape.GTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.489 * scale, r:0, d:0.147 * scale, u:0.147 * scale }; },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
    }
  });
  
  // @2{<}
  xypic.Shape.LT2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.456 * scale, d:0.229 * scale, u:0.229  * scale }; }, 
    getRadius: function () {
      var scale = xypic.oneem;
      return 0.213 * scale;
    },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var gu = svg.createGroup(svg.transformBuilder().rotateDegree(10)); 
      var gd = svg.createGroup(svg.transformBuilder().rotateDegree(-10));
      gu.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @3{<}
  xypic.Shape.LT3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.507 * scale, d:0.268 * scale, u:0.268 * scale }; }, 
    getRadius: function () {
      var scale = xypic.oneem;
      return 0.325 * scale;
    },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var gu = svg.createGroup(svg.transformBuilder().rotateDegree(15));
      var gd = svg.createGroup(svg.transformBuilder().rotateDegree(-15));
      gu.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:xypic.em2px(0.507 * scale), y2:0
      });
      gd.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @^{<}
  xypic.Shape.UpperLTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.489 * scale, d:0, u:0.147 * scale }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
    }
  });
  
  // @_{<}
  xypic.Shape.LowerLTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.489 * scale, d:0.147 * scale, u:0 }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @{<}
  xypic.Shape.LTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.489 * scale, d:0.147 * scale, u:0.147 * scale }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @^{|}
  xypic.Shape.UpperColumnArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0, u:AST.xypic.lineElementLength, d:0 }; }, 
    drawDelegate: function (svg) {
      var l = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:0, y2:-l
      });
    }
  });
  
  // @_{|}
  xypic.Shape.LowerColumnArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0, u:0, d:AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var l = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:0, y2:l
      });
    }
  });
  
  // @2{|}
  xypic.Shape.Column2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0, u:0.5 * (AST.xypic.lineElementLength + AST.xypic.thickness), d:0.5 * (AST.xypic.lineElementLength + AST.xypic.thickness) }; }, 
    drawDelegate: function (svg) {
      var l = xypic.em2px(0.5 * (AST.xypic.lineElementLength + AST.xypic.thickness));
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
    }
  });
  
  // @3{|}
  xypic.Shape.Column3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0, u:0.5 * AST.xypic.lineElementLength + AST.xypic.thickness, d:0.5 * AST.xypic.lineElementLength + AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength + AST.xypic.thickness);
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
    }
  });
  
  // @{|}
  xypic.Shape.ColumnArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
    }
  });
  
  // @^{(}
  xypic.Shape.UpperLParenArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0.5 * AST.xypic.lineElementLength, r:0, u:AST.xypic.lineElementLength, d:0 }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M0,0 A " + r + "," + r + " 0 0,1 0," + (-2 * r)
      });
    }
  });
  
  // @_{(}
  xypic.Shape.LowerLParenArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0.5 * AST.xypic.lineElementLength, r:0, u:0, d:AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M0,0 A " + r + "," + r + " 0 0,0 0," + (2 * r)
      });
    }
  });
  
  // @{(}
  xypic.Shape.LParenArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0.5 * AST.xypic.lineElementLength, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M" + r + "," + (-r) + " A " + r + "," + r + " 0 0,0 " + r + "," + r
      });
    }
  });
  
  // @^{)}
  xypic.Shape.UpperRParenArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0.5 * AST.xypic.lineElementLength, u:AST.xypic.lineElementLength, d:0 }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M0,0 A " + r + "," + r + " 0 0,0 0," + (-2 * r)
      });
    }
  });
  
  // @_{)}
  xypic.Shape.LowerRParenArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0.5 * AST.xypic.lineElementLength, u:0, d:AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M0,0 A " + r + "," + r + " 0 0,1 0," + (2 * r)
      });
    }
  });
  
  // @{)}
  xypic.Shape.RParenArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0.5 * AST.xypic.lineElementLength, r:0, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; },
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M" + (-r) + "," + (-r) + " A " + r + "," + r + " 0 0,1 " + (-r) + "," + r
      });
    }
  });
  
  // @_{`}
  xypic.Shape.LowerBackquoteArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0.5 * AST.xypic.lineElementLength, r:0, u:0, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M0,0 A " + r + "," + r + " 0 0,0 " + (-r) + "," + (r)
      });
    }
  });
  
  // @{`}, @^{`}
  xypic.Shape.UpperBackquoteArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0.5 * AST.xypic.lineElementLength, r:0, u:0.5 * AST.xypic.lineElementLength, d:0 }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M0,0 A " + r + "," + r + " 0 0,1 " + (-r) + "," + (-r)
      });
    }
  });
  
  // @_{'}
  xypic.Shape.LowerQuoteArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0.5 * AST.xypic.lineElementLength, u:0, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M0,0 A " + r + "," + r + " 0 0,1 " + r + "," + (r)
      });
    }
  });
  
  // @{'}, @^{'}
  xypic.Shape.UpperQuoteArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0.5 * AST.xypic.lineElementLength, u:0.5 * AST.xypic.lineElementLength, d:0 }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var r = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("path", {
        d:"M0,0 A " + r + "," + r + " 0 0,0 " + r + "," + (-r)
      });
    }
  });
  
  // @{*}
  xypic.Shape.AsteriskArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = 0;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:AST.xypic.thickness, r:AST.xypic.thickness, u:AST.xypic.thickness, d:AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      svg.createSVGElement("circle", {
        cx:0, cy:0, r:xypic.em2px(AST.xypic.thickness),
        fill: "currentColor"
      });
    }
  });
  
  // @{o}
  xypic.Shape.OArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = 0;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:AST.xypic.thickness, r:AST.xypic.thickness, u:AST.xypic.thickness, d:AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      svg.createSVGElement("circle", {
        cx:0, cy:0, r:xypic.em2px(AST.xypic.thickness)
      });
    }
  });
  
  // @{+}
  xypic.Shape.PlusArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0.5 * AST.xypic.lineElementLength, r:0.5 * AST.xypic.lineElementLength, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var halfLen = AST.xypic.lineElementLength / 2;
      var halfLenPx = xypic.em2px(halfLen);
      svg.createSVGElement("line", {
        x1:-halfLenPx, y1:0, x2:halfLenPx, y2:0
      });
      svg.createSVGElement("line", {
        x1:0, y1:halfLenPx, x2:0, y2:-halfLenPx
      });
    }
  });
  
  // @{x}
  xypic.Shape.XArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle + Math.PI / 4;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0.5 * AST.xypic.lineElementLength, r:0.5 * AST.xypic.lineElementLength, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var halfLen = AST.xypic.lineElementLength / 2;
      var halfLenPx = xypic.em2px(halfLen);
      svg.createSVGElement("line", {
        x1:-halfLenPx, y1:0, x2:halfLenPx, y2:0
      });
      svg.createSVGElement("line", {
        x1:0, y1:halfLenPx, x2:0, y2:-halfLenPx
      });
    }
  });
  
  // @{/}
  xypic.Shape.SlashArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle - Math.PI / 10;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0, u:AST.xypic.lineElementLength / 2, d:AST.xypic.lineElementLength / 2 }; }, 
    drawDelegate: function (svg) {
      var halfLen = AST.xypic.lineElementLength / 2;
      var halfLenPx = xypic.em2px(halfLen);
      svg.createSVGElement("line", {
        x1:0, y1:halfLenPx, x2:0, y2:-halfLenPx
      });
    }
  });
  
  // @3{-}
  xypic.Shape.Line3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:AST.xypic.lineElementLength, u:AST.xypic.thickness, d:AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var lineLen = xypic.em2px(AST.xypic.lineElementLength);
      var vshift = xypic.em2px(AST.xypic.thickness);
      svg.createSVGElement("line", {
        x1:0, y1:vshift, x2:lineLen, y2:vshift
      });
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:lineLen, y2:0
      });
      svg.createSVGElement("line", {
        x1:0, y1:-vshift, x2:lineLen, y2:-vshift
      });
    }
  });
  
  // @2{-}
  xypic.Shape.Line2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:AST.xypic.lineElementLength, u:0.5 * AST.xypic.thickness, d:0.5 * AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var vshift = xypic.em2px(0.5 * AST.xypic.thickness);
      var lineLen = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:vshift, x2:lineLen, y2:vshift
      });
      svg.createSVGElement("line", {
        x1:0, y1:-vshift, x2:lineLen, y2:-vshift
      });
    }
  });
  
  // @{-}
  xypic.Shape.LineArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:AST.xypic.lineElementLength, u:0, d:0 }; }, 
    drawDelegate: function (svg) {
      var lineLen = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:lineLen, y2:0
      });
    }
  });
  
  // @3{.}
  xypic.Shape.Dot3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0, u:AST.xypic.thickness, d:AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var vshift = xypic.em2px(AST.xypic.thickness);
      var lineLen = xypic.em2px(AST.xypic.thickness);
      var dasharray = AST.xypic.dottedDasharray;
      svg.createSVGElement("line", {
        x1:0, y1:vshift, x2:lineLen, y2:vshift,
        "stroke-dasharray": dasharray
      });
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:lineLen, y2:0,
        "stroke-dasharray": dasharray
      });
      svg.createSVGElement("line", {
        x1:0, y1:-vshift, x2:lineLen, y2:-vshift,
        "stroke-dasharray": dasharray
      });
      
    }
  });
  
  // @2{.}
  xypic.Shape.Dot2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0, u:0.5 * AST.xypic.thickness, d:0.5 * AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var vshift = xypic.em2px(0.5 * AST.xypic.thickness);
      var lineLen = xypic.em2px(AST.xypic.thickness);
      var dasharray = AST.xypic.dottedDasharray;
      svg.createSVGElement("line", {
        x1:0, y1:vshift, x2:lineLen, y2:vshift,
      "stroke-dasharray": dasharray
      });
      svg.createSVGElement("line", {
        x1:0, y1:-vshift, x2:lineLen, y2:-vshift,
      "stroke-dasharray": dasharray
      });
    }
  });
  
  // @{.}
  xypic.Shape.DotArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:0, u:0, d:0 }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var lineLen = xypic.em2px(AST.xypic.thickness);
      var dasharray = AST.xypic.dottedDasharray;
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:lineLen, y2:0,
        "stroke-dasharray": dasharray
      });
    }
  });
  
  // @3{~}
  xypic.Shape.Tilde3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:-2 * AST.xypic.thickness, r:2 * AST.xypic.thickness, u:2 * AST.xypic.thickness, d:2* AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var s = xypic.em2px(AST.xypic.thickness);
      svg.createSVGElement("path", {
        d:"M" + (-2 * s) + "," + s + 
          " Q" + (-s) + ",0" + 
          " 0," + s +
          " T" + (2 * s) + "," + s + 
          "M" + (-2 * s) + ",0" + 
          " Q" + (-s) + "," + (-s) +
          " 0,0" +
          " T" + (2 * s) + ",0" + 
          "M" + (-2 * s) + "," + (-s) + 
          " Q" + (-s) + "," + (-2 * s) +
          " 0," + (-s) +
          " T" + (2 * s) + "," + (-s)
      });
    }
  });
  
  // @2{~}
  xypic.Shape.Tilde2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:-2 * AST.xypic.thickness, r:2 * AST.xypic.thickness, u:1.5 * AST.xypic.thickness, d:1.5 * AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var s = xypic.em2px(AST.xypic.thickness);
      svg.createSVGElement("path", {
        d:"M" + (-2 * s) + "," + (0.5 * s) + 
          " Q" + (-s) + "," + (-0.5 * s) +
          " 0," + (0.5 * s) +
          " T" + (2 * s) + "," + (0.5 * s) + 
          "M" + (-2 * s) + "," + (-0.5 * s) + 
          " Q" + (-s) + "," + (-1.5 * s) +
          " 0," + (-0.5 * s) +
          " T" + (2 * s) + "," + (-0.5 * s)
      });
    }
  });
  
  // @{~}
  xypic.Shape.TildeArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:-2 * AST.xypic.thickness, r:2 * AST.xypic.thickness, u:AST.xypic.thickness, d:AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var s = xypic.em2px(AST.xypic.thickness);
      svg.createSVGElement("path", {
        d:"M" + (-2 * s) + ",0" + 
          " Q" + (-s) + "," + (-s) +
          " 0,0" +
          " T" + (2 * s) + ",0"
      });
    }
  });
  
  // @{~}
  xypic.Shape.TildeArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:-2 * AST.xypic.thickness, r:2 * AST.xypic.thickness, u:AST.xypic.thickness, d:AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var s = xypic.em2px(AST.xypic.thickness);
      svg.createSVGElement("path", {
        d:"M" + (-2 * s) + ",0" + 
          " Q" + (-s) + "," + (-s) +
          " 0,0" +
          " T" + (2 * s) + ",0"
      });
    }
  });
  
  // @{>>}
  xypic.Shape.GTGTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.489 * scale + 2 * AST.xypic.thickness, r:0, d:0.147 * scale, u:0.147 * scale }; },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var hshift = xypic.em2px(2 * t);
      svg.createSVGElement("path", {
        d:"M" + (-hshift) + ",0 Q" + (xypic.em2px(-0.222 * scale) - hshift) + "," + xypic.em2px(0.020 * scale) + " " + (xypic.em2px(-0.489 * scale) - hshift) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M" + (-hshift) + ",0 Q" + (xypic.em2px(-0.222 * scale) - hshift) + "," + xypic.em2px(-0.020 * scale) + " " + (xypic.em2px(-0.489 * scale) - hshift) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
    }
  });
  
  // @^{>>}
  xypic.Shape.UpperGTGTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.489 * scale + 2 * AST.xypic.thickness, r:0, d:0, u:0.147 * scale }; },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var hshift = xypic.em2px(2 * t);
      svg.createSVGElement("path", {
        d:"M" + (-hshift) + ",0 Q" + (xypic.em2px(-0.222 * scale) - hshift) + "," + xypic.em2px(-0.020 * scale) + " " + (xypic.em2px(-0.489 * scale) - hshift) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
    }
  });
  
  // @_{>>}
  xypic.Shape.LowerGTGTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.489 * scale + 2 * AST.xypic.thickness, r:0, d:0.147 * scale, u:0 }; },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var hshift = xypic.em2px(2 * t);
      svg.createSVGElement("path", {
        d:"M" + (-hshift) + ",0 Q" + (xypic.em2px(-0.222 * scale) - hshift) + "," + xypic.em2px(0.020 * scale) + " " + (xypic.em2px(-0.489 * scale) - hshift) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @2{>>}
  xypic.Shape.GTGT2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.456 * scale + 2 * AST.xypic.thickness, r:0, d:0.229 * scale, u:0.229 * scale }; },
    getRadius: function () {
      var scale = xypic.oneem;
      return 0.213 * scale;
    },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var gu1 = svg.createGroup(svg.transformBuilder().rotateDegree(-10));
      var gd1 = svg.createGroup(svg.transformBuilder().rotateDegree(10));
      gu1.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd1.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + ","+xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      var gu2 = svg.createGroup(svg.transformBuilder().translate(-2 * t, 0).rotateDegree(-10));
      var gd2 = svg.createGroup(svg.transformBuilder().translate(-2 * t, 0).rotateDegree(10));
      gu2.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd2.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + ","+xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @3{>>}
  xypic.Shape.GTGT3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.507 * scale + 2 * AST.xypic.thickness, r:0, d:0.268 * scale, u:0.268 * scale }; }, 
    getRadius: function () {
      var scale = xypic.oneem;
      return 0.325 * scale;
    },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var gu1 = svg.createGroup(svg.transformBuilder().rotateDegree(-15));
      var gd1 = svg.createGroup(svg.transformBuilder().rotateDegree(15));
      gu1.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd1.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + ","+xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      var gu2 = svg.createGroup(svg.transformBuilder().translate(-2 * t, 0).rotateDegree(-15));
      var gd2 = svg.createGroup(svg.transformBuilder().translate(-2 * t, 0).rotateDegree(15));
      gu2.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd2.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + ","+xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:xypic.em2px(-0.507 * scale - 2 * t), y2:0
      });
    }
  });
  
  // @{<<}
  xypic.Shape.LTLTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.489 * scale + 2 * AST.xypic.thickness, d:0.147 * scale, u:0.147 * scale }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var hshift = xypic.em2px(2 * t);
      svg.createSVGElement("path", {
        d:"M" + hshift + ",0 Q" + (xypic.em2px(0.222 * scale) + hshift) + "," + xypic.em2px(-0.020 * scale) + " " + (xypic.em2px(0.489 * scale) + hshift) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M" + hshift + ",0 Q" + (xypic.em2px(0.222 * scale) + hshift) + "," + xypic.em2px(0.020 * scale) + " " + (xypic.em2px(0.489 * scale) + hshift) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @^{<<}
  xypic.Shape.UpperLTLTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.489 * scale + 2 * AST.xypic.thickness, d:0, u:0.147 * scale }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var hshift = xypic.em2px(2 * t);
      svg.createSVGElement("path", {
        d:"M" + hshift + ",0 Q" + (xypic.em2px(0.222 * scale) + hshift) + "," + xypic.em2px(-0.020 * scale) + " " + (xypic.em2px(0.489 * scale) + hshift) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
    }
  });
  
  // @_{<<}
  xypic.Shape.LowerLTLTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.489 * scale + 2 * AST.xypic.thickness, d:0.147 * scale, u:0 }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var hshift = xypic.em2px(2 * t);
      svg.createSVGElement("path", {
        d:"M" + hshift + ",0 Q" + (xypic.em2px(0.222 * scale) + hshift) + "," + xypic.em2px(0.020 * scale) + " " + (xypic.em2px(0.489 * scale) + hshift) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @2{<<}
  xypic.Shape.LTLT2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.456 + scale + 2 * AST.xypic.thickness, d:0.229 * scale, u:0.229 * scale }; }, 
    getRadius: function () {
      var scale = xypic.oneem;
      return 0.213 * scale;
    },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var gu1 = svg.createGroup(svg.transformBuilder().translate(2 * t, 0).rotateDegree(10)); 
      var gd1 = svg.createGroup(svg.transformBuilder().translate(2 * t, 0).rotateDegree(-10));
      gu1.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd1.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      var gu2 = svg.createGroup(svg.transformBuilder().rotateDegree(10)); 
      var gd2 = svg.createGroup(svg.transformBuilder().rotateDegree(-10));
      gu2.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd2.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @3{<<}
  xypic.Shape.LTLT3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.507 * scale + 2 * AST.xypic.thickness, d:0.268 * scale, u:0.268 * scale }; }, 
    getRadius: function () {
      var scale = xypic.oneem;
      return 0.325 * scale;
    },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var gu1 = svg.createGroup(svg.transformBuilder().translate(2 * t, 0).rotateDegree(15)); 
      var gd1 = svg.createGroup(svg.transformBuilder().translate(2 * t, 0).rotateDegree(-15));
      gu1.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd1.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      var gu2 = svg.createGroup(svg.transformBuilder().rotateDegree(15)); 
      var gd2 = svg.createGroup(svg.transformBuilder().rotateDegree(-15));
      gu2.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd2.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:xypic.em2px(0.507 * scale + 2 * t), y2:0
      });
    }
  });
  
  // @{||}
  xypic.Shape.ColumnColumnArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:AST.xypic.thickness, r:0, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
      svg.createSVGElement("line", {
        x1:-xypic.em2px(t), y1:l, x2:-xypic.em2px(t), y2:-l
      });
    }
  });
  
  // @^{||}
  xypic.Shape.UpperColumnColumnArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:AST.xypic.thickness, r:0, u:AST.xypic.lineElementLength, d:0 }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var l = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:0, y2:-l
      });
      svg.createSVGElement("line", {
        x1:-xypic.em2px(t), y1:0, x2:-xypic.em2px(t), y2:-l
      });
    }
  });
  
  // @_{||}
  xypic.Shape.LowerColumnColumnArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:AST.xypic.thickness, r:0, u:0, d:AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var l = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:0, y2:l
      });
      svg.createSVGElement("line", {
        x1:-xypic.em2px(t), y1:0, x2:-xypic.em2px(t), y2:l
      });
    }
  });
  
  // @2{||}
  xypic.Shape.ColumnColumn2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:AST.xypic.thickness, r:0, u:0.5 * (AST.xypic.lineElementLength + AST.xypic.thickness), d:0.5 * (AST.xypic.lineElementLength + AST.xypic.thickness) }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var l = xypic.em2px(0.5 * (AST.xypic.lineElementLength + AST.xypic.thickness));
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
      svg.createSVGElement("line", {
        x1:-xypic.em2px(t), y1:l, x2:-xypic.em2px(t), y2:-l
      });
    }
  });
  
  // @3{||}
  xypic.Shape.ColumnColumn3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:AST.xypic.thickness, r:0, u:0.5 * AST.xypic.lineElementLength + AST.xypic.thickness, d:0.5 * AST.xypic.lineElementLength + AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var t = AST.xypic.thickness;
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength + AST.xypic.thickness);
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
      svg.createSVGElement("line", {
        x1:-xypic.em2px(t), y1:l, x2:-xypic.em2px(t), y2:-l
      });
    }
  });
  
  // @{|-}
  xypic.Shape.ColumnLineArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:AST.xypic.lineElementLength, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
      var lineLen = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:lineLen, y2:0
      });
    }
  });
  
  // @^{|-}
  xypic.Shape.UpperColumnLineArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:AST.xypic.lineElementLength, u:AST.xypic.lineElementLength, d:0 }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var l = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:0, y2:-l
      });
      var lineLen = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:lineLen, y2:0
      });
    }
  });
  
  // @_{|-}
  xypic.Shape.LowerColumnLineArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:AST.xypic.lineElementLength, u:0, d:AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var l = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:0, y2:l
      });
      var lineLen = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:lineLen, y2:0
      });
    }
  });
  
  // @2{|-}
  xypic.Shape.ColumnLine2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:AST.xypic.lineElementLength, u:0.5 * (AST.xypic.lineElementLength + AST.xypic.thickness), d:0.5 * (AST.xypic.lineElementLength + AST.xypic.thickness) }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var l = xypic.em2px(0.5 * (AST.xypic.lineElementLength + AST.xypic.thickness));
      svg.createSVGElement("line", {
        x1:0, y1:-l, x2:0, y2:l
      });
      var vshift = xypic.em2px(0.5 * t);
      var lineLen = xypic.em2px(AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:vshift, x2:lineLen, y2:vshift
      });
      svg.createSVGElement("line", {
        x1:0, y1:-vshift, x2:lineLen, y2:-vshift
      });
    }
  });
  
  // @3{|-}
  xypic.Shape.ColumnLine3ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:0, r:AST.xypic.lineElementLength, u:0.5 * AST.xypic.lineElementLength + AST.xypic.thickness, d:0.5 * AST.xypic.lineElementLength + AST.xypic.thickness }; }, 
    drawDelegate: function (svg) {
      var t = AST.xypic.thickness;
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength + AST.xypic.thickness);
      svg.createSVGElement("line", {
        x1:0, y1:-l, x2:0, y2:l
      });
      var lineLen = xypic.em2px(AST.xypic.lineElementLength);
      var vshift = xypic.em2px(t);
      svg.createSVGElement("line", {
        x1:0, y1:vshift, x2:lineLen, y2:vshift
      });
      svg.createSVGElement("line", {
        x1:0, y1:0, x2:lineLen, y2:0
      });
      svg.createSVGElement("line", {
        x1:0, y1:-vshift, x2:lineLen, y2:-vshift
      });
    }
  });
  
  // @{>|}
  xypic.Shape.GTColumnArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.489 * scale, r:0, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
    }
  });
  
  // @{>>|}
  xypic.Shape.GTGTColumnArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0.489 * scale + 2 * AST.xypic.thickness, r:0, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
      var hshift = xypic.em2px(2 * t);
      svg.createSVGElement("path", {
        d:"M" + (-hshift) + ",0 Q" + (xypic.em2px(-0.222 * scale) - hshift) + "," + xypic.em2px(0.020 * scale) + " " + (xypic.em2px(-0.489 * scale) - hshift) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M" + (-hshift) + ",0 Q" + (xypic.em2px(-0.222 * scale) - hshift) + "," + xypic.em2px(-0.020 * scale) + " " + (xypic.em2px(-0.489 * scale) - hshift) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
    }
  });
  
  // @{|<}
  xypic.Shape.ColumnLTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.489 * scale, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @{|<<}
  xypic.Shape.ColumnLTLTArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:0, r:0.489 * scale + 2 * AST.xypic.thickness, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var t = AST.xypic.thickness;
      var l = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:l, x2:0, y2:-l
      });
      var hshift = xypic.em2px(2 * t);
      svg.createSVGElement("path", {
        d:"M" + hshift + ",0 Q" + (xypic.em2px(0.222 * scale) + hshift) + "," + xypic.em2px(-0.020 * scale) + " " + (xypic.em2px(0.489 * scale) + hshift) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M" + hshift + ",0 Q" + (xypic.em2px(0.222 * scale) + hshift) + "," + xypic.em2px(0.020 * scale) + " " + (xypic.em2px(0.489 * scale) + hshift) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(0.222 * scale) + "," + xypic.em2px(0.020 * scale) + " " + xypic.em2px(0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
    }
  });
  
  // @{//}
  xypic.Shape.SlashSlashArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle - Math.PI / 10;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { return { l:AST.xypic.thickness, r:0, u:0.5 * AST.xypic.lineElementLength, d:0.5 * AST.xypic.lineElementLength }; }, 
    drawDelegate: function (svg) {
      var hshift = xypic.em2px(AST.xypic.thickness);
      var halfLenPx = xypic.em2px(0.5 * AST.xypic.lineElementLength);
      svg.createSVGElement("line", {
        x1:0, y1:halfLenPx, x2:0, y2:-halfLenPx
      });
      svg.createSVGElement("line", {
        x1:-hshift, y1:halfLenPx, x2:-hshift, y2:-halfLenPx
      });
    }
  });
  
  // @{=>}
  xypic.Shape.LineGT2ArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:AST.xypic.lineElementLength, r:AST.xypic.lineElementLength, d:0.229 * scale, u:0.229 * scale }; },
    getRadius: function () {
      var scale = xypic.oneem;
      return 0.213 * scale;
    },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var halfLen = AST.xypic.lineElementLength;
      var hshift = xypic.em2px(halfLen);
      var v = 0.5 * AST.xypic.thickness;
      var vshift = xypic.em2px(v);
      var r = this.getRadius();
      var delta = xypic.em2px(Math.sqrt(r * r - v * v));
      
      var gu = svg.createGroup(svg.transformBuilder().translate(halfLen, 0).rotateDegree(-10));
      var gd = svg.createGroup(svg.transformBuilder().translate(halfLen, 0).rotateDegree(10));
      gu.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + "," + xypic.em2px(-0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(-0.147 * scale)
      });
      gd.createSVGElement("path", {
        d:"M0,0 Q" + xypic.em2px(-0.222 * scale) + ","+xypic.em2px(0.020 * scale) + " " + xypic.em2px(-0.489 * scale) + "," + xypic.em2px(0.147 * scale)
      });
      svg.createSVGElement("path", {
        d:"M" + (-hshift) + "," + vshift + " L" + (hshift - delta) + "," + vshift + 
          " M" + (-hshift) + "," + (-vshift) + " L" + (hshift - delta) + "," + (-vshift)
      });
    }
  });
  
  // twocell equality arrow
  xypic.Shape.TwocellEqualityArrowheadShape = xypic.Shape.ArrowheadShape.Subclass({
    Init: function (c, angle) {
      this.c = c;
      this.angle = angle;
      memoize(this, "getBoundingBox");
    },
    getBox: function () { var scale = xypic.oneem; return { l:AST.xypic.lineElementLength, r:AST.xypic.lineElementLength, d:0.5 * AST.xypic.thickness, u:0.5 * AST.xypic.thickness }; },
    drawDelegate: function (svg) {
      var scale = xypic.oneem;
      var hshift = xypic.em2px(AST.xypic.lineElementLength);
      var vshift = xypic.em2px(0.5 * AST.xypic.thickness);
      svg.createSVGElement("path", {
        d:"M" + (-hshift) + "," + vshift + " L" + hshift + "," + vshift + 
          " M" + (-hshift) + "," + (-vshift) + " L" + hshift + "," + (-vshift)
      });
    }
  });
  
  
  xypic.Shape.LineShape = xypic.Shape.Subclass({
    Init: function (line, object, main, variant, bbox) {
      this.line = line;
      this.object = object;
      this.main = main;
      this.variant = variant;
      this.bbox = bbox;
      this.holeRanges = FP.List.empty;
    },
    sliceHole: function (range) {
      this.holeRanges = this.holeRanges.prepend(range);
    },
    draw: function (svg) {
      this.line.drawLine(svg, this.object, this.main, this.variant, this.holeRanges);
    },
    getBoundingBox: function () {
      return this.bbox;
    },
    toString: function () {
      return "LineShape[line:" + this.line + ", object:" + this.object + ", main:" + this.main + ", variant:" + this.variant + "]";
    }
  });
  
  xypic.Shape.CurveShape = xypic.Shape.Subclass({
    Init: function (curve, objectForDrop, objectForConnect, bbox) {
      this.curve = curve;
      this.objectForDrop = objectForDrop;
      this.objectForConnect = objectForConnect;
      this.bbox = bbox;
      this.holeRanges = FP.List.empty;
    },
    sliceHole: function (range) {
      this.holeRanges = this.holeRanges.prepend(range);
    },
    draw: function (svg) {
      this.curve.drawCurve(svg, this.objectForDrop, this.objectForConnect, this.holeRanges);
    },
    getBoundingBox: function () {
      return this.bbox;
    },
    toString: function () {
      return "CurveShape[curve" + this.curve + ", objectForDrop:" + (this.objectForDrop !== undefined? this.objectForDrop.toString() : "null") + ", objectForConnect:" + (this.objectForConnect !== undefined? this.objectForConnect.toString() : "null") + "]";
    }
  });
  
  
  xypic.Curve = MathJax.Object.Subclass({
    velocity: function (t) {
      var dx = this.dpx(t);
      var dy = this.dpy(t);
      return Math.sqrt(dx*dx+dy*dy);
    },
    length: function (t) {
      if (t < 0 || t > 1) {
        throw xypic.ExecutionError("illegal cubic Bezier parameter t:"+t);
      }
      this.buildLengthArray();
      
      var n = AST.xypic.lengthResolution;
      var tn = t*n;
      var f = Math.floor(tn);
      var c = Math.ceil(tn);
      if (f === c) {
        return this.lengthArray[f];
      }
      var sf = this.lengthArray[f];
      var sc = this.lengthArray[c];
      return sf + (sc-sf)/(c-f)*(tn-f);  // linear interpolation 
    },
    tOfLength: function (s) {
      this.buildLengthArray();
      
      var a = this.lengthArray;
      if (s < a[0]) {
        return 0;
      } else if (s > a[a.length - 1]) {
        return 1;
      }
      
      var m, al, ah;
      var l = 0;
      var r = a.length-2;
      while (l <= r) {
        m = (l + r) >> 1;
        al = a[m];
        ah = a[m+1];
        if (s >= al && s <= ah) {
          break;
        }
        if (s < al) {
          r = m-1;
        } else {
          l = m+1;
        }
      }
      
      var n = AST.xypic.lengthResolution;
      if (al === ah) {
        return m/n;
      }
      var t = (m + (s-al)/(ah-al))/n;
      return t;
    },
    tOfShavedStart: function (frame) {
      if (frame.isPoint()) {
        return 0; // trivial
      }
      
      var ts = this.tOfIntersections(frame);
      if (ts.length == 0) {
        return undefined; // No solution.
      }
      return Math.min.apply(Math, ts);
    },
    tOfShavedEnd: function (frame) {
      if (frame.isPoint()) {
        return 1; // trivial
      }
      
      var ts = this.tOfIntersections(frame);
      if (ts.length == 0) {
        return undefined; // No solution.
      }
      return Math.max.apply(Math, ts);
    },
    shaveStart: function (frame) {
      if (frame.isPoint()) {
        return this; // trivial
      }
      
      var ts = this.tOfIntersections(frame);
      if (ts.length == 0) {
        return undefined; // No solution.
      }
      var t = Math.min.apply(Math, ts);
      return this.divide(t)[1];
    },
    shaveEnd: function (frame) {
      if (frame.isPoint()) {
        return this; // trivial
      }
      
      var ts = this.tOfIntersections(frame);
      if (ts.length == 0) {
        return undefined; // No solution.
      }
      var t = Math.max.apply(Math, ts);
      return this.divide(t)[0];
    },
    buildLengthArray: function () {
      if (this.lengthArray !== undefined) {
        return;
      }
      
      var n = AST.xypic.lengthResolution;
      // lengthArray[i]: \int_0^{t_{2i}} v(t) dt with Simpson's rule, (i=0, 1, \cdots, n)
      // where, t_k=k h, h=1/(2n): step length.
      var lengthArray = new Array(n+1);
      
      var sum = 0;
      var h = 1/2/n;
      var i = 0;
      var delta = h/3;
      lengthArray[0] = 0;
      sum = this.velocity(0) + 4*this.velocity(h);
      lastv = this.velocity(2*h);
      lengthArray[1] = delta*(sum + lastv);
      for (i = 2; i <= n; i++) {
        sum += 2*lastv + 4*this.velocity((2*i-1)*h);
        lastv = this.velocity(2*i*h);
        lengthArray[i] = delta*(sum + lastv);
      }
      this.lengthArray = lengthArray;
    },
    drawParallelCurve: function (svg, vshift) {
      var i, n = this.countOfSegments() * AST.xypic.interpolationResolution;
      var ts = new Array(n+1);
      var x1s = new Array(n+1);
      var y1s = new Array(n+1);
      var x2s = new Array(n+1);
      var y2s = new Array(n+1);
      var hpi = Math.PI/2;
      var d = vshift;
      var t, angle, p, x, y, dc, ds;
      for (i = 0; i <= n; i++) {
        t = i/n;
        ts[i] = t;  // TODO: 高速化。ts[i+1]-ts[i]が定数の場合に特化した作りにする。
        angle = this.angle(t);
        p = this.position(t);
        x = p.x;
        y = p.y;
        dc = d*Math.cos(angle+hpi);
        ds = d*Math.sin(angle+hpi);
        x1s[i] = x+dc;
        y1s[i] = y+ds;
        x2s[i] = x-dc;
        y2s[i] = y-ds;
      }
      xypic.Curve.CubicBeziers.interpolation(ts, x1s, y1s).drawPrimitive(svg, "none");
      xypic.Curve.CubicBeziers.interpolation(ts, x2s, y2s).drawPrimitive(svg, "none");
    },
    drawParallelDottedCurve: function (svg, spacing, vshift) {
      var px = 1/xypic.em, hpx = px/2;
      var sp = px + spacing;
      var len = this.length(1);
      var n = Math.floor((len-px)/sp);
      var d = vshift;
      if (n >= 0) {
        var i, hpi = Math.PI/2;
        var s = this.startPosition(), e = this.endPosition();
        for (i = 0; i <= n; i++) {
          var s = hpx + i*sp;
          // TODO: 高速化。毎回二分探索せず、端から線形探索・適用するようにする。
          var t = this.tOfLength(s);
          var angle = this.angle(t);
          var p = this.position(t);
          var x = p.x, y = p.y
          var dc = d*Math.cos(angle+hpi), ds = d*Math.sin(angle+hpi);
          svg.createSVGElement("circle", {
            cx:xypic.em2px(x+dc), cy:-xypic.em2px(y+ds), r:0.12,
            fill: "currentColor"
          });
          svg.createSVGElement("circle", {
            cx:xypic.em2px(x-dc), cy:-xypic.em2px(y-ds), r:0.12,
            fill: "currentColor"
          });
        }
      }
    },
    drawParallelDashedCurve: function (svg, dash, vshift) {
      var len = this.length(1);
      var n = Math.floor((len-dash)/(2*dash)), m = 2*n+1;
      var hshift = (len-dash)/2-n*dash;
      var i;
      var ts = new Array(n+1);
      var x1s = new Array(n+1);
      var y1s = new Array(n+1);
      var x2s = new Array(n+1);
      var y2s = new Array(n+1);
      var hpi = Math.PI/2;
      var d = vshift;
      var t, angle, p, x, y, dc, ds;
      for (i = 0; i <= m; i++) {
        // TODO: 高速化。毎回二分探索せず、端から線形探索・適用するようにする。
        t = this.tOfLength(hshift + i*dash);
        ts[i] = t;
        angle = this.angle(t);
        p = this.position(t);
        x = p.x;
        y = p.y;
        dc = d*Math.cos(angle+hpi);
        ds = d*Math.sin(angle+hpi);
        x1s[i] = x+dc;
        y1s[i] = y+ds;
        x2s[i] = x-dc;
        y2s[i] = y-ds;
      }
      xypic.Curve.CubicBeziers.interpolation(ts, x1s, y1s).drawSkipped(svg);
      xypic.Curve.CubicBeziers.interpolation(ts, x2s, y2s).drawSkipped(svg);
    },
    drawSquigCurve: function (svg, variant) {
      var thickness = xypic.length2em("0.15em");
      var len = this.length(1);
      var wave = 4*thickness;
      var amp = thickness;
      if (len >= wave) {
        var n = Math.floor(len/wave);
        var shiftLen = (len-n*wave)/2;
        
        var s, t, p, angle, nx, ny, hpi = Math.PI/2, d1, d2, d3;
        switch (variant) {
          case "3":
            s = shiftLen;
            t = this.tOfLength(s);
            p = this.position(t);
            angle = this.angle(t);
            nx = amp*Math.cos(angle+hpi);
            ny = amp*Math.sin(angle+hpi);
            d1 = "M"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
            d2 = "M"+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
            d3 = "M"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
            
            for (var i = 0; i < n; i++) {
              s = shiftLen + wave*i + thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " Q"+xypic.em2px(p.x+2*nx)+","+xypic.em2px(-p.y-2*ny);
              d2 += " Q"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d3 += " Q"+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              
              s = shiftLen + wave*i + 2*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " "+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " "+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              d3 += " "+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              
              s = shiftLen + wave*i + 3*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " Q"+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              d2 += " Q"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              d3 += " "+xypic.em2px(p.x-2*nx)+","+xypic.em2px(-p.y+2*ny);
              
              s = shiftLen + wave*(i+1);
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " "+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " "+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              d3 += " "+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
            }
            svg.createSVGElement("path", {"d":d1});
            svg.createSVGElement("path", {"d":d2});
            svg.createSVGElement("path", {"d":d3});
            break;
            
          case "2":
            s = shiftLen;
            t = this.tOfLength(s);
            p = this.position(t);
            angle = this.angle(t);
            nx = amp*Math.cos(angle+hpi)/2;
            ny = amp*Math.sin(angle+hpi)/2;
            d1 = "M"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
            d2 = "M"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
            
            for (var i = 0; i < n; i++) {
              s = shiftLen + wave*i + thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi)/2;
              ny = amp*Math.sin(angle+hpi)/2;
              d1 += " Q"+xypic.em2px(p.x+3*nx)+","+xypic.em2px(-p.y-3*ny);
              d2 += " Q"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              
              s = shiftLen + wave*i + 2*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi)/2;
              ny = amp*Math.sin(angle+hpi)/2;
              d1 += " "+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " "+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              
              s = shiftLen + wave*i + 3*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi)/2;
              ny = amp*Math.sin(angle+hpi)/2;
              d1 += " Q"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              d2 += " Q"+xypic.em2px(p.x-3*nx)+","+xypic.em2px(-p.y+3*ny);
              
              s = shiftLen + wave*(i+1);
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi)/2;
              ny = amp*Math.sin(angle+hpi)/2;
              d1 += " "+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " "+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
            }
            svg.createSVGElement("path", {"d":d1});
            svg.createSVGElement("path", {"d":d2});
            break;
            
          default:
            s = shiftLen;
            t = this.tOfLength(s);
            p = this.position(t);
            d1 = "M"+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
            
            for (var i = 0; i < n; i++) {
              s = shiftLen + wave*i + thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " Q"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              
              s = shiftLen + wave*i + 2*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              d1 += " "+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              
              s = shiftLen + wave*i + 3*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " Q"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              
              s = shiftLen + wave*(i+1);
              t = this.tOfLength(s);
              p = this.position(t);
              d1 += " "+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
            }
            svg.createSVGElement("path", {"d":d1});
        }
      }
    },
    drawDashSquigCurve: function (svg, variant) {
      var thickness = AST.xypic.thickness;
      var len = this.length(1);
      var wave = 4*thickness;
      var amp = thickness;
      if (len >= wave) {
        var n = Math.floor((len-wave)/2/wave);
        var shiftLen = (len-wave)/2-n*wave;
        
        var s, t, p, angle, nx, ny, hpi = Math.PI/2, d1, d2, d3;
        switch (variant) {
          case "3":
            d1 = d2 = d3 = "";
            for (var i = 0; i <= n; i++) {
              s = shiftLen + wave*i*2;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " M"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " M"+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              d3 += " M"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              
              s = shiftLen + wave*i*2 + thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " Q"+xypic.em2px(p.x+2*nx)+","+xypic.em2px(-p.y-2*ny);
              d2 += " Q"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d3 += " Q"+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              
              s = shiftLen + wave*i*2 + 2*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " "+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " "+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              d3 += " "+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              
              s = shiftLen + wave*i*2 + 3*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " Q"+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              d2 += " Q"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              d3 += " "+xypic.em2px(p.x-2*nx)+","+xypic.em2px(-p.y+2*ny);
              
              s = shiftLen + wave*(i*2+1);
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " "+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " "+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              d3 += " "+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
            }
            svg.createSVGElement("path", {"d":d1});
            svg.createSVGElement("path", {"d":d2});
            svg.createSVGElement("path", {"d":d3});
            break;
            
          case "2":
            d1 = d2 = "";
            for (var i = 0; i <= n; i++) {
              s = shiftLen + wave*i*2;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi)/2;
              ny = amp*Math.sin(angle+hpi)/2;
              d1 += " M"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " M"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              
              s = shiftLen + wave*i*2 + thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi)/2;
              ny = amp*Math.sin(angle+hpi)/2;
              d1 += " Q"+xypic.em2px(p.x+3*nx)+","+xypic.em2px(-p.y-3*ny);
              d2 += " Q"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              
              s = shiftLen + wave*i*2 + 2*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi)/2;
              ny = amp*Math.sin(angle+hpi)/2;
              d1 += " "+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " "+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              
              s = shiftLen + wave*i*2 + 3*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi)/2;
              ny = amp*Math.sin(angle+hpi)/2;
              d1 += " Q"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              d2 += " Q"+xypic.em2px(p.x-3*nx)+","+xypic.em2px(-p.y+3*ny);
              
              s = shiftLen + wave*(i*2+1);
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi)/2;
              ny = amp*Math.sin(angle+hpi)/2;
              d1 += " "+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              d2 += " "+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
            }
            svg.createSVGElement("path", {"d":d1});
            svg.createSVGElement("path", {"d":d2});
            break;
            
          default:
            d1 = "";
            for (var i = 0; i <= n; i++) {
              s = shiftLen + wave*i*2;
              t = this.tOfLength(s);
              p = this.position(t);
              d1 += " M"+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              
              s = shiftLen + wave*i*2 + thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " Q"+xypic.em2px(p.x+nx)+","+xypic.em2px(-p.y-ny);
              
              s = shiftLen + wave*i*2 + 2*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              d1 += " "+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
              
              s = shiftLen + wave*i*2 + 3*thickness;
              t = this.tOfLength(s);
              p = this.position(t);
              angle = this.angle(t);
              nx = amp*Math.cos(angle+hpi);
              ny = amp*Math.sin(angle+hpi);
              d1 += " Q"+xypic.em2px(p.x-nx)+","+xypic.em2px(-p.y+ny);
              
              s = shiftLen + wave*(i*2+1);
              t = this.tOfLength(s);
              p = this.position(t);
              d1 += " "+xypic.em2px(p.x)+","+xypic.em2px(-p.y);
            }
            svg.createSVGElement("path", {"d":d1});
        }
      }
    },
    drawCurve: function (svg, objectForDrop, objectForConnect, holeRanges) {
      if (holeRanges.isEmpty) {
        this._drawCurve(svg, objectForDrop, objectForConnect);
      } else {
        var clippingRanges = xypic.Range(0, 1).differenceRanges(holeRanges);
        var self = this;
        clippingRanges.foreach(function (range) {
          self.slice(range.start, range.end)._drawCurve(svg, objectForDrop, objectForConnect);
        });
      }
    },
    _drawCurve: function (svg, objectForDrop, objectForConnect) {
      var thickness = xypic.length2em("0.15em");
      var vshift;
      if (objectForConnect !== undefined) {
        var main = objectForConnect.dirMain();
        var variant = objectForConnect.dirVariant();
        switch (main) {
          case "=":
            main = "-";
            variant = "2";
            break;
          case "==":
            main = "--";
            variant = "2";
            break;
          case ':':
          case '::':
            main = ".";
            variant = "2";
            break;
        }
        
        switch (main) {
          case '':
            // draw nothing.
            break;
            
          case '-':
            switch (variant) {
              case "2":
                vshift = thickness/2;
                this.drawParallelCurve(svg, vshift);
                break;
              case "3":
                vshift = thickness;
                this.drawParallelCurve(svg, vshift);
                this.drawPrimitive(svg, "none");
                break;
              default:
                vshift = 0;
                this.drawPrimitive(svg, "none");
            }
            break;
            
          case '.':
          case '..':
            switch (variant) {
              case "2":
                vshift = thickness/2;
                this.drawParallelDottedCurve(svg, thickness, vshift)
                break;
                
              case "3":
                vshift = thickness;
                this.drawParallelDottedCurve(svg, thickness, vshift)
                this.drawPrimitive(svg, AST.xypic.dottedDasharray);
                break;
                
              default:
                vshift = 0;
                this.drawPrimitive(svg, AST.xypic.dottedDasharray);
                break;
            }
            break;
            
          case '--':
            var dash = 3 * thickness;
            var len = this.length(1);
            if (len >= dash) {
              switch (variant) {
                case "2":
                  vshift = thickness / 2;
                  this.drawParallelDashedCurve(svg, dash, vshift);
                  break;
                  
                case "3":
                  vshift = thickness;
                  this.drawParallelDashedCurve(svg, dash, vshift);
                  var shiftLen = (len - dash) / 2 - Math.floor((len - dash) / 2 / dash) * dash;
                  var shiftT = this.tOfLength(shiftLen);
                  var shifted = this.divide(shiftT)[1];
                  shifted.drawPrimitive(svg, xypic.em2px(dash) + " " + xypic.em2px(dash))
                  break;
                  
                default:
                  vshift = 0;
                  var shiftLen = (len - dash) / 2 - Math.floor((len - dash) / 2 / dash) * dash;
                  var shiftT = this.tOfLength(shiftLen);
                  var shifted = this.divide(shiftT)[1];
                  shifted.drawPrimitive(svg, xypic.em2px(dash) + " " + xypic.em2px(dash));
              }
            }
            break;
            
          case '~':
            this.drawSquigCurve(svg, variant);
            switch (variant) {
              case "2":
                vshift = 1.5 * thickness;
                break;
              case "3":
                vshift = 2 * thickness;
                break;
              default:
                vshift = 0
            }
            break;
            
          case '~~':
            this.drawDashSquigCurve(svg, variant);
            switch (variant) {
              case "2":
                vshift = 1.5 * thickness;
                break;
              case "3":
                vshift = 2 * thickness;
                break;
              default:
                vshift = 0
            }
            break;
            
          default:
            // TODO: ~* と ~** の順序を考慮する。
            var dummyEnv = xypic.Env();
            dummyEnv.c = xypic.Env.originPosition;
            var dummyContext = xypic.DrawingContext(xypic.Shape.none, dummyEnv);
            var conBBox = objectForConnect.boundingBox(dummyContext);
            if (conBBox == undefined) {
              return;
            }
            
            var cl = conBBox.l;
            var conLen = cl + conBBox.r;
            
            var dropLen, dl;
            if (objectForDrop !== undefined) {
              var dropBBox = objectForDrop.boundingBox(dummyContext);
              if (dropBBox !== undefined) {
                dl = dropBBox.l;
                dropLen = dl + dropBBox.r;
              }
            } else {
              dropLen = 0;
            }
            
            var compositeLen = conLen + dropLen;
            if (compositeLen == 0) {
              compositeLen = AST.xypic.strokeWidth;
            }
            
            var len = this.length(1);
            var n = Math.floor(len / compositeLen);
            if (n == 0) {
              return;
            }
            
            var shiftLen = (len - n * compositeLen) / 2;
            
            var dummyContext = xypic.DrawingContext(xypic.Shape.none, dummyEnv);
            var s, t;
            for (var i = 0; i < n; i++) {
              s = shiftLen + i * compositeLen;
              if (objectForDrop !== undefined) {
                t = this.tOfLength(s + dl);
                dummyEnv.c = this.position(t);
                dummyEnv.angle = this.angle(t);
                objectForDrop.toDropShape(dummyContext).draw(svg);
              }
              t = this.tOfLength(s + dropLen + cl);
              dummyEnv.c = this.position(t);
              dummyEnv.angle = this.angle(t);
              bbox = objectForConnect.toDropShape(dummyContext).draw(svg);
            }
        }
      } else {
        var dummyEnv = xypic.Env();
        dummyEnv.c = xypic.Env.originPosition;
        var dummyContext = xypic.DrawingContext(xypic.Shape.none, dummyEnv);
        var object = objectForDrop;
        var objectBBox = object.boundingBox(dummyContext);
        if (objectBBox === undefined) {
          return;
        }
        var objectWidth = objectBBox.l + objectBBox.r;
        var objectLen = objectWidth;
        if (objectLen == 0) {
          objectLen = AST.xypic.strokeWidth;
        }
        
        var len = this.length(1);
        var n = Math.floor(len / objectLen);
        if (n == 0) {
          return;
        }
        
        var shiftLen = (len - n * objectLen + objectLen - objectWidth) / 2 + objectBBox.l;
        var dummyContext = xypic.DrawingContext(xypic.Shape.none, dummyEnv);
        for (var i = 0; i < n; i++) {
          var s = shiftLen + i * objectLen;
          var t = this.tOfLength(s);
          dummyEnv.c = this.position(t);
          dummyEnv.angle = 0;
          object.toDropShape(dummyContext).draw(svg);
        }
      }
    },
    toShape: function (context, objectForDrop, objectForConnect) {
      var env = context.env;
      var thickness = xypic.length2em("0.15em");
      var shape = xypic.Shape.none;
      var vshift;
      if (objectForConnect !== undefined) {
        var main = objectForConnect.dirMain();
        var variant = objectForConnect.dirVariant();
        switch (main) {
          case "=":
            main = "-";
            variant = "2";
            break;
          case "==":
            main = "--";
            variant = "2";
            break;
          case ':':
          case '::':
            main = ".";
            variant = "2";
            break;
        }
        
        switch (main) {
          case '':
            vshift = 0;
            break;
            
          case '-':
          case '.':
          case '..':
            switch (variant) {
              case "2":
                vshift = thickness / 2;
                break;
                
              case "3":
                vshift = thickness;
                break;
                
              default:
                vshift = 0;
                break;
            }
            break;
            
          case '--':
            var dash = 3 * thickness;
            var len = this.length(1);
            if (len >= dash) {
              switch (variant) {
                case "2":
                  vshift = thickness / 2;
                  break;
                  
                case "3":
                  vshift = thickness;
                  break;
                  
                default:
                  vshift = 0;
              }
            }
            break;
            
          case '~':
          case '~~':
            switch (variant) {
              case "2":
                vshift = 1.5 * thickness;
                break;
              case "3":
                vshift = 2 * thickness;
                break;
              default:
                vshift = 0
            }
            break;
            
          default:
            // TODO: ~* と ~** の順序を考慮する。
            var conBBox = objectForConnect.boundingBox(context);
            if (conBBox == undefined) {
              env.angle = 0;
              env.lastCurve = xypic.LastCurve.none;
              return xypic.Shape.none;
            }
            vshift = Math.max(conBBox.u, conBBox.d);
            
            var cl = conBBox.l;
            var conLen = cl + conBBox.r;
            
            var dropLen, dl;
            if (objectForDrop !== undefined) {
              var dropBBox = objectForDrop.boundingBox(context);
              if (dropBBox !== undefined) {
                dl = dropBBox.l;
                dropLen = dl + dropBBox.r;
                vshift = Math.max(vshift, dropBBox.u, dropBBox.d);
              }
            } else {
              dropLen = 0;
            }
            
            var compositeLen = conLen + dropLen;
            if (compositeLen == 0) {
              compositeLen = AST.xypic.strokeWidth;
            }
            
            var len = this.length(1);
            var n = Math.floor(len / compositeLen);
            if (n == 0) {
              env.angle = 0;
              env.lastCurve = xypic.LastCurve.none;
              return xypic.Shape.none;
            }
            
            shape = xypic.Shape.CurveShape(this, objectForDrop, objectForConnect, this.boundingBox(vshift));
            context.appendShapeToFront(shape);
            return shape;
        }
        if (vshift === undefined) {
          return xypic.Shape.none;
        } else {
          shape = xypic.Shape.CurveShape(this, objectForDrop, objectForConnect, this.boundingBox(vshift));
          context.appendShapeToFront(shape);
          return shape;
        }
      } else if (objectForDrop !== undefined) {
        var object = objectForDrop;
        var objectBBox = object.boundingBox(context);
        if (objectBBox == undefined) {
          env.angle = 0;
          env.lastCurve = xypic.LastCurve.none;
          return xypic.Shape.none;
        }
        
        var objectWidth = objectBBox.l + objectBBox.r;
        var objectLen = objectWidth;
        if (objectLen == 0) {
          objectLen = AST.xypic.strokeWidth;
        }
        
        var len = this.length(1);
        var n = Math.floor(len / objectLen);
        if (n == 0) {
          env.angle = 0;
          env.lastCurve = xypic.LastCurve.none;
          return xypic.Shape.none;
        }
        
        vshift = Math.max(objectBBox.u, objectBBox.d);
        shape = xypic.Shape.CurveShape(this, objectForDrop, objectForConnect, this.boundingBox(vshift));
        context.appendShapeToFront(shape);
        return shape;
      }
      return shape;
    }
  }, {
    sign: function (x) { return x>0? 1 : (x===0? 0 : -1); },
    solutionsOfCubicEq: function (a3, a2, a1, a0) {
      // find solutions t in [0, 1]
      if (a3 === 0) {
        return xypic.Curve.solutionsOfQuadEq(a2, a1, a0);
      }
      var b2 = a2/3/a3, b1 = a1/a3, b0 = a0/a3;
      var p = b2*b2-b1/3, q = -b0/2+b1*b2/2-b2*b2*b2;
      var d = q*q-p*p*p;
      if (d === 0) {
        var s = Math.pow(q, 1/3);
        var t0 = 2*s-b2, t1 = -s-b2;
        return xypic.Curve.filterByIn0to1([t0, t1]);
      } else if (d > 0) {
        var u = q+xypic.Curve.sign(q)*Math.sqrt(d);
        var r = xypic.Curve.sign(u)*Math.pow(Math.abs(u), 1/3);
        var s = p/r;
        var t = r+s-b2;
        return xypic.Curve.filterByIn0to1([t]);
      } else {
        var r = 2*Math.sqrt(p);
        var s = Math.acos(2*q/p/r);
        var t0 = r*Math.cos(s/3)-b2;
        var t1 = r*Math.cos((s+2*Math.PI)/3)-b2;
        var t2 = r*Math.cos((s+4*Math.PI)/3)-b2;
        return xypic.Curve.filterByIn0to1([t0, t1, t2]);
      }
    },
    solutionsOfQuadEq: function (a2, a1, a0) {
      // find solutions t in [0, 1]
      if (a2 === 0) {
        return xypic.Curve.solutionsOfLinearEq(a1, a0);
      } else {
        var d = a1 * a1 - 4 * a0 * a2;
        if (d >= 0) {
          var s = Math.sqrt(d);
          var tp = (-a1 + s) / 2 / a2;
          var tm = (-a1 - s) / 2 / a2;
          return xypic.Curve.filterByIn0to1([tp, tm]);
        } else {
          return [];
        }
      }
    },
    solutionsOfLinearEq: function (a1, a0) {
      // find solution t in [0, 1]
      if (a1 === 0) {
        return (a0 === 0? 0 : []);
      }
      return xypic.Curve.filterByIn0to1([-a0 / a1]);
    },
    filterByIn0to1: function (ts) {
      var filterdTs = [];
      for (var i = 0; i < ts.length; i++) {
        var t = ts[i];
        if (t >= 0 && t <= 1) {
          filterdTs.push(t);
        }
      }
      return filterdTs;
    }
  });
  
  xypic.Curve.QuadBezier = xypic.Curve.Subclass({
    Init: function (cp0, cp1, cp2) {
      this.cp0 = cp0;
      this.cp1 = cp1;
      this.cp2 = cp2;
      
      var a0x = cp0.x;
      var a1x = 2*(cp1.x - cp0.x);
      var a2x = cp2.x - 2*cp1.x + cp0.x;
      this.px = function(t) { return a0x + t*a1x + t*t*a2x; }
      this.dpx = function(t) { return a1x + 2*t*a2x; }
      
      var a0y = cp0.y;
      var a1y = 2*(cp1.y - cp0.y);
      var a2y = cp2.y - 2*cp1.y + cp0.y;
      this.py = function(t) { return a0y + t*a1y + t*t*a2y; }
      this.dpy = function(t) { return a1y + 2*t*a2y; }
    },
    startPosition: function () {
      return this.cp0;
    },
    endPosition: function () {
      return this.cp2;
    },
    position: function (t) {
      return xypic.Frame.Point(this.px(t), this.py(t));
    },
    derivative: function (t) {
      return xypic.Frame.Point(this.dpx(t), this.dpy(t));
    },
    angle: function (t) {
      return Math.atan2(this.dpy(t), this.dpx(t));
    },
    boundingBox: function (vshift) {
      var maxMinX = this.maxMin(this.cp0.x, this.cp1.x, this.cp2.x, vshift);
      var maxMinY = this.maxMin(this.cp0.y, this.cp1.y, this.cp2.y, vshift);
      if (vshift === 0) {
        return xypic.Frame.Rect(this.cp0.x, this.cp0.y, {
          l:this.cp0.x-maxMinX.min, r:maxMinX.max-this.cp0.x,
          u:maxMinY.max-this.cp0.y, d:this.cp0.y-maxMinY.min
        });
      } else {
        var hpi = Math.PI/2;
        var sx = this.cp0.x;
        var sy = this.cp0.y;
        var ex = this.cp2.x;
        var ey = this.cp2.y;
        var a0 = this.angle(0)+hpi;
        var a1 = this.angle(1)+hpi;
        var vc0 = vshift*Math.cos(a0), vs0 = vshift*Math.sin(a0);
        var vc1 = vshift*Math.cos(a1), vs1 = vshift*Math.sin(a1);
        var minX = Math.min(maxMinX.min, sx+vc0, sx-vc0, ex+vc1, ex-vc1);
        var maxX = Math.max(maxMinX.max, sx+vc0, sx-vc0, ex+vc1, ex-vc1);
        var minY = Math.min(maxMinY.min, sy+vs0, sy-vs0, ey+vs1, ey-vs1);
        var maxY = Math.max(maxMinY.max, sy+vs0, sy-vs0, ey+vs1, ey-vs1);
        return xypic.Frame.Rect(sx, sy, {
          l:sx-minX, r:maxX-sx, u:maxY-sy, d:sy-minY
        });
      }
    },
    maxMin: function (x0, x1, x2, vshift) {
      var max, min;
      if (x0 > x2) {
        max = x0;
        min = x2;
      } else {
        max = x2;
        min = x0;
      }
      
      var roundEp = xypic.Util.roundEpsilon;
      
      var a0 = roundEp(x0);
      var a1 = roundEp(x1 - x0);
      var a2 = roundEp(x2 - 2*x1 + x0);
      var p = function(t) { return a0 + 2*t*a1 + t*t*a2 }
      
      var x, t;
      if (a2 != 0) {
        t = -a1/a2;
        if (t > 0 && t < 1) {
          x = p(t);
          max = Math.max(max, x + vshift, x - vshift);
          min = Math.min(min, x + vshift, x - vshift);
        }
      }
      return {min:min, max:max};
    },
    divide: function (t) {
      if (t < 0 || t > 1) {
        throw xypic.ExecutionError("illegal quadratic Bezier parameter t:"+t);
      }
      
      var x0 = this.cp0.x;
      var x1 = this.cp1.x;
      var x2 = this.cp2.x;
      
      var y0 = this.cp0.y;
      var y1 = this.cp1.y;
      var y2 = this.cp2.y;
      
      var tx = this.px(t);
      var ty = this.py(t);
      
      var p0 = this.cp0;
      var p1 = xypic.Frame.Point(x0+t*(x1-x0), y0+t*(y1-y0));
      var p2 = xypic.Frame.Point(tx, ty);
      
      var q0 = p2;
      var q1 = xypic.Frame.Point(x1+t*(x2-x1), y1+t*(y2-y1));
      var q2 = this.cp2;
      
      return [
        xypic.Curve.QuadBezier(p0, p1, p2),
        xypic.Curve.QuadBezier(q0, q1, q2)
      ]
    },
    slice: function (t0, t1) {
      if (t0 >= t1) {
        return undefined;
      }
      
      if (t0 < 0) {
        t0 = 0;
      } 
      if (t1 > 1) {
        t1 = 1;
      }
      
      if (t0 === 0 && t1 === 1) {
        return this;
      }
      
      var x0 = this.cp0.x;
      var x1 = this.cp1.x;
      var x2 = this.cp2.x;
      
      var y0 = this.cp0.y;
      var y1 = this.cp1.y;
      var y2 = this.cp2.y;
      
      var q0x = this.px(t0);
      var q0y = this.py(t0);
      var q1x = x1 + t0 * (x2 - x1);
      var q1y = y1 + t0 * (y2 - y1);
      
      var p0 = xypic.Frame.Point(q0x, q0y);
      var p1 = xypic.Frame.Point(q0x + t1 * (q1x - q0x), q0y + t1 * (q1y - q0y));
      var p2 = xypic.Frame.Point(this.px(t1), this.py(t1));
      
      return xypic.Curve.QuadBezier(p0, p1, p2);
    },
    tOfIntersections: function (frame) {
      if (frame.isPoint()) {
        return []; // CAUTION: Point does not intersect with any curves.
      }
      
      if (frame.isRect()) {
        // find starting edge point
        var rx = frame.x + frame.r;
        var lx = frame.x - frame.l;
        var uy = frame.y + frame.u;
        var dy = frame.y - frame.d;
        
        var roundEp = xypic.Util.roundEpsilon;
        
        var x0 = this.cp0.x;
        var x1 = this.cp1.x;
        var x2 = this.cp2.x;
        
        var a0x = roundEp(x0);
        var a1x = roundEp(2*(x1 - x0));
        var a2x = roundEp(x2 - 2*x1 + x0);
        var px = function(t) { return a0x + t*a1x + t*t*a2x; }
        
        var y0 = this.cp0.y;
        var y1 = this.cp1.y;
        var y2 = this.cp2.y;
        
        var a0y = roundEp(y0);
        var a1y = roundEp(2*(y1 - y0));
        var a2y = roundEp(y2 - 2*y1 + y0);
        var py = function(t) { return a0y + t*a1y + t*t*a2y; }
        
        var ts = [];
        
        var tsCandidate;
        tsCandidate = xypic.Curve.solutionsOfQuadEq(a2x, a1x, a0x - rx);
        tsCandidate = tsCandidate.concat(xypic.Curve.solutionsOfQuadEq(a2x, a1x, a0x - lx));
        for (var i = 0; i < tsCandidate.length; i++) {
          var t = tsCandidate[i];
          var y = py(t);
          if (y >= dy && y <= uy) {
            ts.push(t);
          }
        }
        tsCandidate = xypic.Curve.solutionsOfQuadEq(a2y, a1y, a0y - uy);
        tsCandidate = tsCandidate.concat(xypic.Curve.solutionsOfQuadEq(a2y, a1y, a0y - dy));
        for (var i = 0; i < tsCandidate.length; i++) {
          var t = tsCandidate[i];
          var x = px(t);
          if (x >= lx && x <= rx) {
            ts.push(t);
          }
        }
        
        return ts;
      } else if (frame.isCircle()) {
        var pi = Math.PI;
        var x = frame.x;
        var y = frame.y;
        var l = frame.l;
        var r = frame.r;
        var u = frame.u;
        var d = frame.d;
        var cx = x + (r - l) / 2;
        var cy = y + (u - d) / 2;
        var rx = (l + r) / 2;
        var ry = (u + d) / 2;
        
        var delta = pi / 180; // overlapping
        var arc0 = xypic.CurveSegment.Arc(cx, cy, rx, ry, -pi - delta, -pi / 2 + delta);
        var arc1 = xypic.CurveSegment.Arc(cx, cy, rx, ry, -pi / 2 - delta, 0 + delta);
        var arc2 = xypic.CurveSegment.Arc(cx, cy, rx, ry, 0 - delta, pi / 2 + delta);
        var arc3 = xypic.CurveSegment.Arc(cx, cy, rx, ry, pi / 2 - delta, pi + delta);
        
        var bezier = xypic.CurveSegment.QuadBezier(this, 0, 1);
        
        var intersec = [];
        intersec = intersec.concat(xypic.CurveSegment.findIntersections(arc0, bezier));
        intersec = intersec.concat(xypic.CurveSegment.findIntersections(arc1, bezier));
        intersec = intersec.concat(xypic.CurveSegment.findIntersections(arc2, bezier));
        intersec = intersec.concat(xypic.CurveSegment.findIntersections(arc3, bezier));
        
        var ts = [];
        for (var i = 0; i < intersec.length; i++) { 
          var t = (intersec[i][1].min + intersec[i][1].max) / 2;
          ts.push(t);
        }
        return ts;
      }
    },
    countOfSegments: function () { return 1; },
    drawPrimitive: function (svg, dasharray) {
      var cp0 = this.cp0, cp1 = this.cp1, cp2 = this.cp2;
      svg.createSVGElement("path", {
        "d":"M"+xypic.em2px(cp0.x)+","+xypic.em2px(-cp0.y)+
          " Q"+xypic.em2px(cp1.x)+","+xypic.em2px(-cp1.y)+
          " "+xypic.em2px(cp2.x)+","+xypic.em2px(-cp2.y),
        "stroke-dasharray":dasharray
      });
    },
    toString: function () {
      return "QuadBezier("+this.cp0.x+", "+this.cp0.y+")-("+this.cp1.x+", "+this.cp1.y+")-("+this.cp2.x+", "+this.cp2.y+")"
    }
  });
  
  xypic.Curve.CubicBezier = xypic.Curve.Subclass({
    Init: function (cp0, cp1, cp2, cp3) {
      this.cp0 = cp0;
      this.cp1 = cp1;
      this.cp2 = cp2;
      this.cp3 = cp3;
      
      var a0x = cp0.x;
      var a1x = 3*(cp1.x - cp0.x);
      var a2x = 3*(cp2.x - 2*cp1.x + cp0.x);
      var a3x = cp3.x - 3*cp2.x + 3*cp1.x - cp0.x;
      this.px = function(t) { return a0x + t*a1x + t*t*a2x + t*t*t*a3x; }
      this.dpx = function(t) { return a1x + 2*t*a2x + 3*t*t*a3x; }
      
      var a0y = cp0.y;
      var a1y = 3*(cp1.y - cp0.y);
      var a2y = 3*(cp2.y - 2*cp1.y + cp0.y);
      var a3y = cp3.y - 3*cp2.y + 3*cp1.y - cp0.y;
      this.py = function(t) { return a0y + t*a1y + t*t*a2y + t*t*t*a3y; }
      this.dpy = function(t) { return a1y + 2*t*a2y + 3*t*t*a3y; }
    },
    startPosition: function () {
      return this.cp0;
    },
    endPosition: function () {
      return this.cp3;
    },
    position: function (t) {
      return xypic.Frame.Point(this.px(t), this.py(t));
    },
    derivative: function (t) {
      return xypic.Frame.Point(this.dpx(t), this.dpy(t));
    },
    angle: function (t) {
      return Math.atan2(this.dpy(t), this.dpx(t));
    },
    boundingBox: function (vshift) {
      var maxMinX = this.maxMin(this.cp0.x, this.cp1.x, this.cp2.x, this.cp3.x, vshift);
      var maxMinY = this.maxMin(this.cp0.y, this.cp1.y, this.cp2.y, this.cp3.y, vshift);
      if (vshift === 0) {
        return xypic.Frame.Rect(this.cp0.x, this.cp0.y, {
          l:this.cp0.x-maxMinX.min, r:maxMinX.max-this.cp0.x,
          u:maxMinY.max-this.cp0.y, d:this.cp0.y-maxMinY.min
        });
      } else {
        var hpi = Math.PI/2;
        var sx = this.cp0.x;
        var sy = this.cp0.y;
        var ex = this.cp3.x;
        var ey = this.cp3.y;
        var a0 = this.angle(0)+hpi;
        var a1 = this.angle(1)+hpi;
        var vc0 = vshift*Math.cos(a0), vs0 = vshift*Math.sin(a0);
        var vc1 = vshift*Math.cos(a1), vs1 = vshift*Math.sin(a1);
        var minX = Math.min(maxMinX.min, sx+vc0, sx-vc0, ex+vc1, ex-vc1);
        var maxX = Math.max(maxMinX.max, sx+vc0, sx-vc0, ex+vc1, ex-vc1);
        var minY = Math.min(maxMinY.min, sy+vs0, sy-vs0, ey+vs1, ey-vs1);
        var maxY = Math.max(maxMinY.max, sy+vs0, sy-vs0, ey+vs1, ey-vs1);
        return xypic.Frame.Rect(sx, sy, {
          l:sx-minX, r:maxX-sx, u:maxY-sy, d:sy-minY
        });
      }
    },
    maxMin: function (x0, x1, x2, x3, vshift) {
      var max, min;
      if (x0 > x3) {
        max = x0;
        min = x3;
      } else {
        max = x3;
        min = x0;
      }
      
      var roundEp = xypic.Util.roundEpsilon;
      var a0 = roundEp(x0);
      var a1 = roundEp(x1 - x0);
      var a2 = roundEp(x2 - 2*x1 + x0);
      var a3 = roundEp(x3 - 3*x2 + 3*x1 - x0);
      var p = function(t) { return a0 + 3*t*a1 + 3*t*t*a2 + t*t*t*a3 }
      
      var updateMinMax = function (t) {
        if (t > 0 && t < 1) {
          x = p(t);
          max = Math.max(max, x + vshift, x - vshift);
          min = Math.min(min, x + vshift, x - vshift);
        }
      }
      
      var t, x;
      if (a3 == 0) {
        if (a2 != 0) {
          t = -a1/a2/2;
          updateMinMax(t);
        }
      } else {
        var d = a2*a2 - a1*a3;
        if (d > 0) {
          t = (-a2 + Math.sqrt(d))/a3;
          updateMinMax(t);
          t = (-a2 - Math.sqrt(d))/a3;
          updateMinMax(t);
        } else if (d == 0) {
          t = -a2/a3;
          updateMinMax(t);
        }
      }
      return {min:min, max:max};
    },
    divide: function (t) {
      if (t < 0 || t > 1) {
        throw xypic.ExecutionError("illegal cubic Bezier parameter t:"+t);
      }
      
      var x0 = this.cp0.x;
      var x1 = this.cp1.x;
      var x2 = this.cp2.x;
      var x3 = this.cp3.x;
      
      var y0 = this.cp0.y;
      var y1 = this.cp1.y;
      var y2 = this.cp2.y;
      var y3 = this.cp3.y;
      
      var tx = this.px(t);
      var ty = this.py(t);
      
      var p0 = this.cp0;
      var p1 = xypic.Frame.Point(x0+t*(x1-x0), y0+t*(y1-y0));
      var p2 = xypic.Frame.Point(
        x0+2*t*(x1-x0)+t*t*(x2-2*x1+x0),
        y0+2*t*(y1-y0)+t*t*(y2-2*y1+y0)
      );
      var p3 = xypic.Frame.Point(tx, ty);
      
      var q0 = p3;
      var q1 = xypic.Frame.Point(
        x1+2*t*(x2-x1)+t*t*(x3-2*x2+x1),
        y1+2*t*(y2-y1)+t*t*(y3-2*y2+y1)
      );
      var q2 = xypic.Frame.Point(x2+t*(x3-x2), y2+t*(y3-y2));
      var q3 = this.cp3;
      
      return [
        xypic.Curve.CubicBezier(p0, p1, p2, p3),
        xypic.Curve.CubicBezier(q0, q1, q2, q3)
      ]
    },
    slice: function (t0, t1) {
      if (t0 >= t1) {
        return undefined;
      }
      
      if (t0 < 0) {
        t0 = 0;
      } 
      if (t1 > 1) {
        t1 = 1;
      }
      
      if (t0 === 0 && t1 === 1) {
        return this;
      }
      
      var x0 = this.cp0.x;
      var x1 = this.cp1.x;
      var x2 = this.cp2.x;
      var x3 = this.cp3.x;
      
      var y0 = this.cp0.y;
      var y1 = this.cp1.y;
      var y2 = this.cp2.y;
      var y3 = this.cp3.y;
      
      var q0x = this.px(t0);
      var q0y = this.py(t0);
      var q1x = x1 + 2 * t0 * (x2 - x1) + t0 * t0 * (x3 - 2 * x2 + x1);
      var q1y = y1 + 2 * t0 * (y2 - y1) + t0 * t0 * (y3 - 2 * y2 + y1);
      var q2x = x2 + t0 * (x3 - x2);
      var q2y = y2 + t0 * (y3 - y2);
      
      var p0 = xypic.Frame.Point(q0x, q0y);
      var p1 = xypic.Frame.Point(q0x + t1 * (q1x - q0x), q0y + t1 * (q1y - q0y));
      var p2 = xypic.Frame.Point(
        q0x + 2 * t1 * (q1x - q0x) + t1 * t1 * (q2x - 2 * q1x + q0x),
        q0y + 2 * t1 * (q1y - q0y) + t1 * t1 * (q2y - 2 * q1y + q0y)
      );
      var p3 = xypic.Frame.Point(this.px(t1), this.py(t1));
      
      return xypic.Curve.CubicBezier(p0, p1, p2, p3);
    },
    tOfIntersections: function (frame) {
      if (frame.isPoint()) {
        return []; // CAUTION: Point does not intersect with any curves.
      }
      
      if (frame.isRect()) {
        // find starting edge point
        var rx = frame.x + frame.r;
        var lx = frame.x - frame.l;
        var uy = frame.y + frame.u;
        var dy = frame.y - frame.d;
        
        var roundEp = xypic.Util.roundEpsilon;
        
        var x0 = this.cp0.x;
        var x1 = this.cp1.x;
        var x2 = this.cp2.x;
        var x3 = this.cp3.x;
        
        var y0 = this.cp0.y;
        var y1 = this.cp1.y;
        var y2 = this.cp2.y;
        var y3 = this.cp3.y;
        
        var a0x = roundEp(x0);
        var a1x = roundEp(3*(x1 - x0));
        var a2x = roundEp(3*(x2 - 2*x1 + x0));
        var a3x = roundEp(x3 - 3*x2 + 3*x1 - x0);
        var px = function(t) { return a0x + t*a1x + t*t*a2x + t*t*t*a3x }
        
        var a0y = roundEp(y0);
        var a1y = roundEp(3*(y1 - y0));
        var a2y = roundEp(3*(y2 - 2*y1 + y0));
        var a3y = roundEp(y3 - 3*y2 + 3*y1 - y0);
        var py = function(t) { return a0y + t*a1y + t*t*a2y + t*t*t*a3y }
        
        var ts = [];
        var tsCandidate;
        tsCandidate = xypic.Curve.solutionsOfCubicEq(a3x, a2x, a1x, a0x-rx);
        tsCandidate = tsCandidate.concat(xypic.Curve.solutionsOfCubicEq(a3x, a2x, a1x, a0x-lx));
        for (var i = 0; i < tsCandidate.length; i++) {
          var t = tsCandidate[i];
          var y = py(t);
          if (y >= dy && y <= uy) {
            ts.push(t);
          }
        }
        tsCandidate = xypic.Curve.solutionsOfCubicEq(a3y, a2y, a1y, a0y-uy);
        tsCandidate = tsCandidate.concat(xypic.Curve.solutionsOfCubicEq(a3y, a2y, a1y, a0y-dy));
        for (var i = 0; i < tsCandidate.length; i++) {
          var t = tsCandidate[i];
          var x = px(t);
          if (x >= lx && x <= rx) {
            ts.push(t);
          }
        }
        
        return ts;
      } else if (frame.isCircle()) {
        var pi = Math.PI;
        var x = frame.x;
        var y = frame.y;
        var l = frame.l;
        var r = frame.r;
        var u = frame.u;
        var d = frame.d;
        var cx = x + (r - l) / 2;
        var cy = y + (u - d) / 2;
        var rx = (l + r) / 2;
        var ry = (u + d) / 2;
        
        var delta = pi / 180; // overlapping
        var arc0 = xypic.CurveSegment.Arc(cx, cy, rx, ry, -pi - delta, -pi / 2 + delta);
        var arc1 = xypic.CurveSegment.Arc(cx, cy, rx, ry, -pi / 2 - delta, 0 + delta);
        var arc2 = xypic.CurveSegment.Arc(cx, cy, rx, ry, 0 - delta, pi / 2 + delta);
        var arc3 = xypic.CurveSegment.Arc(cx, cy, rx, ry, pi / 2 - delta, pi + delta);
        
        var bezier = xypic.CurveSegment.CubicBezier(this, 0, 1);
        
        var intersec = [];
        intersec = intersec.concat(xypic.CurveSegment.findIntersections(arc0, bezier));
        intersec = intersec.concat(xypic.CurveSegment.findIntersections(arc1, bezier));
        intersec = intersec.concat(xypic.CurveSegment.findIntersections(arc2, bezier));
        intersec = intersec.concat(xypic.CurveSegment.findIntersections(arc3, bezier));
        
        var ts = [];
        for (var i = 0; i < intersec.length; i++) { 
          var t = (intersec[i][1].min + intersec[i][1].max) / 2;
          ts.push(t);
        }
        return ts;
      }
    },
    countOfSegments: function () { return 1; },
    drawPrimitive: function (svg, dasharray) {
      var cp0 = this.cp0, cp1 = this.cp1, cp2 = this.cp2, cp3 = this.cp3;
      svg.createSVGElement("path", {
        "d":"M"+xypic.em2px(cp0.x)+","+xypic.em2px(-cp0.y)+
          " C"+xypic.em2px(cp1.x)+","+xypic.em2px(-cp1.y)+
          " "+xypic.em2px(cp2.x)+","+xypic.em2px(-cp2.y)+
          " "+xypic.em2px(cp3.x)+","+xypic.em2px(-cp3.y),
        "stroke-dasharray":dasharray
      });
    },
    toString: function () {
      return "CubicBezier("+this.cp0.x+", "+this.cp0.y+")-("+this.cp1.x+", "+this.cp1.y+")-("+this.cp2.x+", "+this.cp2.y+")-("+this.cp3.x+", "+this.cp3.y+")"
    }
  });
  
  xypic.Curve.CubicBeziers = xypic.Curve.Subclass({
    Init: function (cbs) {
      this.cbs = cbs;
      var n = this.cbs.length;
      this.delegate = (n == 0?
        function (t, succ, fail) {
          return fail;
        } : function (t, succ, fail) {
          var tn = t * n;
          var i = Math.floor(tn);
          if (i < 0) { i = 0; }
          if (i >= n) { i = n - 1; }
          var s = tn - i;
          var cb = cbs[i];
          return succ(cb, s);
        }
      );
    },
    startPosition: function () {
      return this.cbs[0].cp0;
    },
    endPosition: function () {
      return this.cbs[this.cbs.length - 1].cp3;
    },
    position: function (t) {
      return this.delegate(t, function (cb, s) { return cb.position(s) }, undefined);
    },
    derivative: function (t) {
      return this.delegate(t, function (cb, s) { return cb.derivative(s) }, undefined);
    },
    angle: function (t) {
      return this.delegate(t, function (cb, s) { return cb.angle(s) }, 0);
    },
    velocity: function (t) {
      var n = this.cbs.length;
      return this.delegate(t, function (cb, s) { return n * cb.velocity(s) }, 0);
    },
    boundingBox: function (vshift) {
      if (this.cbs.length == 0) {
        return undefined;
      }
      var bbox = this.cbs[0].boundingBox(vshift);
      var i, n = this.cbs.length;
      for (i = 1; i < n; i++) {
        bbox = bbox.combineRect(this.cbs[i].boundingBox(vshift))
      }
      return bbox;
    },
    tOfIntersections: function (frame) {
      var ts = [];
      var i = 0, n = this.cbs.length;
      for (i = 0; i < n; i++) {
        var cb = this.cbs[i];
        unnormalizedTs = cb.tOfIntersections(frame);
        for (j = 0; j < unnormalizedTs.length; j++) {
          ts.push((unnormalizedTs[j] + i) / n);
        }
      }
      return ts;
    },
    divide: function (t) {
      if (t < 0 || t > 1) {
        throw xypic.ExecutionError("illegal cubic Bezier parameter t:"+t);
      } else if (t === 0) {
        return [xypic.Curve.CubicBeziers([]), this];
      } else if (t === 1) {
        return [this, xypic.Curve.CubicBeziers([])];
      }
      
      var n = this.cbs.length;
      var tn = t * n;
      var i = Math.floor(tn);
      if (i === n) {
        i = n - 1;
      }
      var s = tn - i;
      var divS = this.cbs.slice(0, i);
      var divE = this.cbs.slice(i + 1);
      var cb = this.cbs[i];
      var divB = cb.divide(s);
      divS.push(divB[0]);
      divE.unshift(divB[1]);
      return [xypic.Curve.CubicBeziers(divS), xypic.Curve.CubicBeziers(divE)];
    },
    slice: function (t0, t1) {
      if (t0 >= t1) {
        return undefined;
      }
      
      if (t0 < 0) {
        t0 = 0;
      } 
      if (t1 > 1) {
        t1 = 1;
      }
      
      if (t0 === 0 && t1 === 1) {
        return this;
      }
      
      var n = this.cbs.length;
      var tn0 = t0 * n;
      var tn1 = t1 * n;
      var i0 = Math.floor(tn0);
      var i1 = Math.floor(tn1);
      if (i0 === n) {
        i0 = n - 1;
      }
      if (i1 === n) {
        i1 = n - 1;
      }
      var s0 = tn0 - i0;
      var s1 = tn1 - i1;
      var subBeziers;
      if (i0 === i1) {
        subBeziers = [this.cbs[i0].slice(s0, s1)];
      } else {
        subBeziers = this.cbs.slice(i0 + 1, i1);
        subBeziers.push(this.cbs[i1].slice(0, s1));
        subBeziers.unshift(this.cbs[i0].slice(s0, 1));
      }
      return xypic.Curve.CubicBeziers(subBeziers);
    },
    countOfSegments: function () { return this.cbs.length; },
    drawPrimitive: function (svg, dasharray) {
      var n = this.cbs.length;
      var cbs = this.cbs;
      var cb = cbs[0];
      var cp0 = cb.cp0, cp1 = cb.cp1, cp2 = cb.cp2, cp3 = cb.cp3;
      var d = ("M"+xypic.em2px(cp0.x)+","+xypic.em2px(-cp0.y)+
          " C"+xypic.em2px(cp1.x)+","+xypic.em2px(-cp1.y)+
          " "+xypic.em2px(cp2.x)+","+xypic.em2px(-cp2.y)+
          " "+xypic.em2px(cp3.x)+","+xypic.em2px(-cp3.y));
      for (var i = 1; i < n; i++) {
        cb = cbs[i];
        cp2 = cb.cp2, cp3 = cb.cp3;
        d += " S"+xypic.em2px(cp2.x)+","+xypic.em2px(-cp2.y)+" "+xypic.em2px(cp3.x)+","+xypic.em2px(-cp3.y);
      }
      svg.createSVGElement("path", {"d":d, "stroke-dasharray":dasharray});
    },
    drawSkipped: function (svg) {
      var n = this.cbs.length;
      var cbs = this.cbs;
      var d = "";
      for (var i = 0; i < n; i+=2) {
        var cb = cbs[i];
        var cp0 = cb.cp0, cp1 = cb.cp1, cp2 = cb.cp2, cp3 = cb.cp3;
        d += ("M"+xypic.em2px(cp0.x)+","+xypic.em2px(-cp0.y)+
            " C"+xypic.em2px(cp1.x)+","+xypic.em2px(-cp1.y)+
            " "+xypic.em2px(cp2.x)+","+xypic.em2px(-cp2.y)+
            " "+xypic.em2px(cp3.x)+","+xypic.em2px(-cp3.y));
      }
      svg.createSVGElement("path", {"d":d});
    }
  },{
    interpolation: function (ts, xs, ys) {
      var x12 = xypic.Curve.CubicBeziers.cubicSplineInterpolation(ts, xs);
      var x1 = x12[0];
      var x2 = x12[1];
      
      var y12 = xypic.Curve.CubicBeziers.cubicSplineInterpolation(ts, ys);
      var y1 = y12[0];
      var y2 = y12[1];
      
      var i, n = ts.length;
      var beziers = new Array(n-1);
      for (i = 0; i < n-1; i++) {
        beziers[i] = xypic.Curve.CubicBezier(
          xypic.Frame.Point(xs[i], ys[i]),
          xypic.Frame.Point(x1[i], y1[i]),
          xypic.Frame.Point(x2[i], y2[i]),
          xypic.Frame.Point(xs[i+1], ys[i+1])
        )
      }
      return xypic.Curve.CubicBeziers(beziers);
    },
    cubicSplineInterpolation: function (ts, xs) {
      var n = ts.length-1;
      var hs = new Array(n);
      var i;
      for (i = 0; i < n; i++) {
        hs[i] = ts[i+1] - ts[i];
      }
      var as = new Array(n);
      for (i = 1; i < n; i++) {
        as[i] = 3*(xs[i+1] - xs[i])/hs[i] - 3*(xs[i] - xs[i-1])/hs[i-1];
      }
      var ls = new Array(n+1);
      var ms = new Array(n+1);
      var zs = new Array(n+1);
      ls[0] = 1;
      ms[0] = 0;
      zs[0] = 0;
      for (i = 1; i < n; i++) {
        ls[i] = 2*(ts[i+1] - ts[i-1]) - hs[i-1]*ms[i-1];
        ms[i] = hs[i]/ls[i];
        zs[i] = (as[i] - hs[i-1]*zs[i-1])/ls[i];
      }
      ls[n] = 1;
      zs[n] = 0;
      var bs = new Array(n);
      var cs = new Array(n+1);
      cs[n] = 0;
      for (i = n-1; i >= 0; i--) {
        var h = hs[i], c1 = cs[i+1], c0 = h*h*zs[i] - ms[i]*c1;
        cs[i] = c0;
        bs[i] = (xs[i+1] - xs[i]) - (c1 + 2*c0)/3;
      }
      var p1s = new Array(n);
      var p2s = new Array(n);
      for (i = 0; i < n; i++) {
        var a = xs[i], b = bs[i], c = cs[i];
        p1s[i] = a + b/3;
        p2s[i] = a + (2*b + c)/3;
      }
      return [p1s, p2s];
    }
  });
  
  // xypic.Curve.CubicBeziers factory class
  xypic.Curve.CubicBSpline = MathJax.Object.Subclass({
    Init: function (s, intCps, e) {
      if (intCps.length < 1) {
        throw xypic.ExecutionError("the number of internal control points of cubic B-spline must be greater than or equal to 1");
      }
      
      var controlPoints = [];
      controlPoints.push(s);
      for (var i = 0, l = intCps.length; i < l; i++) {
        controlPoints.push(intCps[i]);
      }
      controlPoints.push(e);
      this.cps = controlPoints;
      
      var n = this.cps.length - 1;
      var cps = function (i) {
        if (i < 0) {
          return controlPoints[0];
        } else if (i > n) {
          return controlPoints[n];
        } else {
          return controlPoints[i];
        }
      }
      var N = function (t) {
        var s = Math.abs(t);
        if (s <= 1) {
          return (3*s*s*s - 6*s*s + 4)/6;
        } else if (s <= 2) {
          return -(s-2)*(s-2)*(s-2)/6;
        } else {
          return 0;
        }
      }
      this.px = function (t) {
        var s = (n+2)*t-1;
        var minj = Math.ceil(s-2);
        var maxj = Math.floor(s+2);
        var p = 0;
        for (var j = minj; j <= maxj; j++) {
          p += N(s-j)*cps(j).x;
        }
        return p;
      }
      this.py = function (t) {
        var s = (n+2)*t-1;
        var minj = Math.ceil(s-2);
        var maxj = Math.floor(s+2);
        var p = 0;
        for (var j = minj; j <= maxj; j++) {
          p += N(s-j)*cps(j).y;
        }
        return p;
      }
      var dN = function (t) {
        var u = (t>0? 1 : (t<0? -1 : 0));
        var s = Math.abs(t);
        if (s <= 1) {
          return u*(3*s*s - 4*s)/2;
        } else if (s <= 2) {
          return -u*(s-2)*(s-2)/2;
        } else {
          return 0;
        }
      }
      this.dpx = function (t) {
        var s = (n+2)*t-1;
        var minj = Math.ceil(s-2);
        var maxj = Math.floor(s+2);
        var p = 0;
        for (var j = minj; j <= maxj; j++) {
          p += dN(s-j)*cps(j).x;
        }
        return p;
      }
      this.dpy = function (t) {
        var s = (n+2)*t-1;
        var minj = Math.ceil(s-2);
        var maxj = Math.floor(s+2);
        var p = 0;
        for (var j = minj; j <= maxj; j++) {
          p += dN(s-j)*cps(j).y;
        }
        return p;
      }
    },
    position: function (t) {
      return xypic.Frame.Point(this.px(t), this.py(t));
    },
    angle: function (t) {
      return Math.atan2(this.dpy(t), this.dpx(t));
    },
    toCubicBeziers: function () {
      var cbs = [];
      var cps = this.cps;
      
      var cp0 = cps[0];
      var cp1 = cps[1];
      var cp2 = cps[2];
      var p0x = cp0.x;
      var p0y = cp0.y;
      var p1x = p0x+(cp1.x-p0x)/3;
      var p1y = p0y+(cp1.y-p0y)/3;
      var p2x = p0x+(cp1.x-p0x)*2/3;
      var p2y = p0y+(cp1.y-p0y)*2/3;
      var n1x = cp1.x+(cp2.x-cp1.x)/3;
      var n1y = cp1.y+(cp2.y-cp1.y)/3;
      var p3x = (p2x+n1x)/2;
      var p3y = (p2y+n1y)/2;
      var p0 = cp0;
      var p1 = xypic.Frame.Point(p1x, p1y);
      var p2 = xypic.Frame.Point(p2x, p2y);
      var p3 = xypic.Frame.Point(p3x, p3y);
      var cb = xypic.Curve.CubicBezier(p0, p1, p2, p3);
      cbs.push(cb);
      
      var len = this.cps.length - 1;
      for (var i=2; i < len; i++) {
        cp0 = cp1;
        cp1 = cp2;
        cp2 = cps[i+1];
        p0x = p3x;
        p0y = p3y;
        p1x = 2*p3x - p2x;
        p1y = 2*p3y - p2y;
        p2x = cp0.x+(cp1.x-cp0.x)*2/3;
        p2y = cp0.y+(cp1.y-cp0.y)*2/3;
        n1x = cp1.x+(cp2.x-cp1.x)/3;
        n1y = cp1.y+(cp2.y-cp1.y)/3;
        p3x = (p2x+n1x)/2;
        p3y = (p2y+n1y)/2;
        p0 = p3;
        p1 = xypic.Frame.Point(p1x, p1y);
        p2 = xypic.Frame.Point(p2x, p2y);
        p3 = xypic.Frame.Point(p3x, p3y);
        cb = xypic.Curve.CubicBezier(p0, p1, p2, p3);
        cbs.push(cb);
      }
      
      cp0 = cp1;
      cp1 = cp2;
      p0x = p3x;
      p0y = p3y;
      p1x = 2*p3x - p2x;
      p1y = 2*p3y - p2y;
      p2x = cp0.x+(cp1.x-cp0.x)*2/3;
      p2y = cp0.y+(cp1.y-cp0.y)*2/3;
      p3x = cp1.x;
      p3y = cp1.y;
      p0 = p3;
      p1 = xypic.Frame.Point(p1x, p1y);
      p2 = xypic.Frame.Point(p2x, p2y);
      p3 = xypic.Frame.Point(p3x, p3y);
      cb = xypic.Curve.CubicBezier(p0, p1, p2, p3);
      cbs.push(cb);
      
      return cbs;
    },
    countOfSegments: function () { return this.cps.length - 1; }
  });
  
  xypic.Curve.Line = MathJax.Object.Subclass({
    Init: function (s, e) {
      this.s = s;
      this.e = e;
    },
    position: function (t) {
      return xypic.Frame.Point(
        this.s.x + t * (this.e.x - this.s.x),
        this.s.y + t * (this.e.y - this.s.y)
      );
    },
    slice: function (t0, t1) {
      if (t0 >= t1) {
        return undefined;
      }
      
      if (t0 < 0) {
        t0 = 0;
      }
      
      if (t1 > 1) {
        t1 = 1;
      }
      
      if (t0 === 0 && t1 === 1) {
        return this;
      }
      
      var s = this.s;
      var e = this.e;
      var dx = e.x - s.x;
      var dy = e.y - s.y;
      var newS = xypic.Frame.Point(s.x + t0 * dx, s.y + t0 * dy);
      var newE = xypic.Frame.Point(s.x + t1 * dx, s.y + t1 * dy);
      return xypic.Curve.Line(newS, newE);
    },
    tOfIntersections: function (frame) {
      if (frame.isPoint()) {
        return []; // CAUTION: Point does not intersect with any curves.
      }
      
      var s = this.s;
      var e = this.e;
      if (frame.isRect()) {
        // find starting edge point
        var rx = frame.x + frame.r;
        var lx = frame.x - frame.l;
        var uy = frame.y + frame.u;
        var dy = frame.y - frame.d;
        
        var a0x = s.x;
        var a0y = s.y;
        var a1x = e.x - a0x;
        var a1y = e.y - a0y;
        var px = function (t) { return a0x + t * a1x; }
        var py = function (t) { return a0y + t * a1y; }
        
        var ts = [];
        var tsCandidate;
        tsCandidate = xypic.Curve.solutionsOfLinearEq(a1x, a0x - rx);
        tsCandidate = tsCandidate.concat(xypic.Curve.solutionsOfLinearEq(a1x, a0x - lx));
        for (var i = 0; i < tsCandidate.length; i++) {
          var t = tsCandidate[i];
          var y = py(t);
          if (y >= dy && y <= uy) {
            ts.push(t);
          }
        }
        tsCandidate = xypic.Curve.solutionsOfLinearEq(a1y, a0y - uy);
        tsCandidate = tsCandidate.concat(xypic.Curve.solutionsOfLinearEq(a1y, a0y - dy));
        for (var i = 0; i < tsCandidate.length; i++) {
          var t = tsCandidate[i];
          var x = px(t);
          if (x >= lx && x <= rx) {
            ts.push(t);
          }
        }
        
        return ts;
      } else if (frame.isCircle()) {
        var pi = Math.PI;
        var l = frame.l;
        var r = frame.r;
        var u = frame.u;
        var d = frame.d;
        var x0 = frame.x;
        var y0 = frame.y;
        var cx = x0 + (r - l) / 2;
        var cy = y0 + (u - d) / 2;
        var rx = (l + r) / 2;
        var ry = (u + d) / 2;
        
        var sx = s.x;
        var sy = s.y;
        var ex = e.x;
        var ey = e.y;
        
        var dx = ex - sx;
        var dy = ey - sy;
        var a0 = dy;
        var b0 = -dx;
        var c0 = dx * sy - dy * sx;
        var a = a0 * rx;
        var b = b0 * ry;
        var c = c0 * rx + (rx - ry) * b0 * cy;
        var aabb = a * a + b * b;
        var d = a * cx + b * cy + c;
        var e = -d / aabb;
        var ff = aabb * rx * rx - d * d;
        if (ff < 0) {
          return [];
        }
        var f = Math.sqrt(ff) / aabb;
        
        var xp = a * e + b * f + cx;
        var yp = b * e - a * f + cy;
        var xm = a * e - b * f + cx;
        var ym = b * e + a * f + cy;
        
        var eps = ry / rx;
        var xp0 = xp;
        var yp0 = eps * (yp - cy) + cy;
        var xm0 = xm;
        var ym0 = eps * (ym - cy) + cy;
        
        var tp, tm;
        if (Math.abs(dx) > Math.abs(dy)) {
          tp = (xp0 - sx) / dx;
          tm = (xm0 - sx) / dx;
        } else {
          tp = (yp0 - sy) / dy;
          tm = (ym0 - sy) / dy;
        }
        
        var ts = [];
        if (tp >= 0 && tp <= 1) {
          ts.push(tp);
        }
        if (tm >= 0 && tm <= 1) {
          ts.push(tm);
        }
        return ts;
      }
    },
    toShape: function (context, object, main, variant) {
      // 多重線の幅、点線・破線の幅の基準
      var env = context.env;
      var thickness = AST.xypic.thickness;
      var s = this.s;
      var e = this.e;
      if (s.x !== e.x || s.y !== e.y) {
        var dx = e.x - s.x;
        var dy = e.y - s.y;
        var angle = Math.atan2(dy, dx);
        var vshift;
        var shape = xypic.Shape.none;
        switch (main) {
          case "=":
            main = "-";
            variant = "2";
            break;
          case "==":
            main = "--";
            variant = "2";
            break;
          case ':':
          case '::':
            main = ".";
            variant = "2";
            break;
        }
        
        switch (main) {
          case '':
            // draw invisible line
            env.angle = angle;
            env.lastCurve = xypic.LastCurve.Line(s, e, env.p, env.c, undefined);
            return shape;
            
          case '-':
          case '.':
          case '..':
            switch (variant) {
              case "2":
                vshift = thickness / 2;
                break;
                
              case "3":
                vshift = thickness;
                break;
                
              default:
                vshift = 0;
                break;
            }
            break;
            
          case '--':
            var dash = 3 * thickness;
            var len = Math.sqrt(dx * dx + dy * dy);
            if (len >= dash) {
              switch (variant) {
                case "2":
                  vshift = thickness / 2;
                  break;
                  
                case "3":
                  vshift = thickness;
                  break;
                  
                default:
                  vshift = 0;
              }
            }
            break;
            
          case '~':
          case '~~':
            switch (variant) {
              case "2":
                vshift = 1.5 * thickness;
                break;
              case "3":
                vshift = 2 * thickness;
                break;
              default:
                vshift = 0
            }
            break;
            
          default:
            // connect by arrowheads
            var arrowBBox = object.boundingBox(context);
            if (arrowBBox == undefined) {
              env.angle = 0;
              env.lastCurve = xypic.LastCurve.none;
              return xypic.Shape.none;
            }
            
            var arrowLen = arrowBBox.l + arrowBBox.r;
            if (arrowLen == 0) {
              arrowLen = AST.xypic.strokeWidth;
            }
            
            var len = Math.sqrt(dx * dx + dy * dy);
            var n = Math.floor(len / arrowLen);
            if (n == 0) {
              env.angle = 0;
              env.lastCurve = xypic.LastCurve.none;
              return xypic.Shape.none;
            }
            
            vshift = Math.max(arrowBBox.u, arrowBBox.d);
        }
        
        if (vshift !== undefined) {
          var bbox = this.boundingBox(vshift);
          shape = xypic.Shape.LineShape(this, object, main, variant, bbox);
          context.appendShapeToFront(shape);
          
          env.angle = angle;
          env.lastCurve = xypic.LastCurve.Line(s, e, env.p, env.c, shape);
          return shape;
        }
      }
      
      env.angle = 0;
      env.lastCurve = xypic.LastCurve.none;
      return xypic.Shape.none;
    },
    boundingBox: function (vshift) {
      var s = this.s;
      var e = this.e;
      var dx = e.x - s.x;
      var dy = e.y - s.y;
      var angle = Math.atan2(dy, dx);
      var cx = vshift * Math.cos(angle + Math.PI/2);
      var cy = vshift * Math.sin(angle + Math.PI/2);
      return xypic.Frame.Rect(s.x, s.y, {
        l:s.x-Math.min(s.x+cx, s.x-cx, e.x+cx, e.x-cx),
        r:Math.max(s.x+cx, s.x-cx, e.x+cx, e.x-cx)-s.x,
        u:Math.max(s.y+cy, s.y-cy, e.y+cy, e.y-cy)-s.y,
        d:s.y-Math.min(s.y+cy, s.y-cy, e.y+cy, e.y-cy)
      });
    },
    drawLine: function (svg, object, main, variant, holeRanges) {
      if (holeRanges.isEmpty) {
        this._drawLine(svg, object, main, variant);
      } else {
        var clippingRanges = xypic.Range(0, 1).differenceRanges(holeRanges);
        var self = this;
        clippingRanges.foreach(function (range) {
          self.slice(range.start, range.end)._drawLine(svg, object, main, variant);
        });
      }
    },
    _drawLine: function (svg, object, main, variant) {
      // 多重線の幅、点線・破線の幅の基準
      var t = AST.xypic.thickness;
      var s = this.s;
      var e = this.e;
      if (s.x !== e.x || s.y !== e.y) {
        var dx = e.x - s.x;
        var dy = e.y - s.y;
        var angle = Math.atan2(dy, dx);
        var shift = { x:0, y:0 };
        
        switch (main) {
          case '':
            // draw nothing
            break;
          case '-':
            this.drawStraightLine(svg, s, e, shift, angle, t, variant, "");
            break;
          case '=':
            this.drawStraightLine(svg, s, e, shift, angle, t, "2", "");
            break;
          case '.':
          case '..':
            this.drawStraightLine(svg, s, e, shift, angle, t, variant, AST.xypic.dottedDasharray);
            break;
          case ':':
          case '::':
            this.drawStraightLine(svg, s, e, shift, angle, t, "2", AST.xypic.dottedDasharray);
            break;
          case '--':
          case '==':
            var len = Math.sqrt(dx * dx + dy * dy);
            var dash = 3 * t;
            if (len >= dash) {
              var shiftLen = (len - dash) / 2 - Math.floor((len - dash) / 2 / dash) * dash;
              shift = { x:shiftLen * Math.cos(angle), y:shiftLen * Math.sin(angle) };
              this.drawStraightLine(svg, s, e, shift, angle, t, (main === "=="? "2" : variant), xypic.em2px(dash) + " " + xypic.em2px(dash));
            }
            break;
          case '~':
            var len = Math.sqrt(dx * dx + dy * dy);
            var wave = 4 * t;
            if (len >= wave) {
              var n = Math.floor(len / wave);
              var shiftLen = (len - n * wave) / 2;
              shift = { x:shiftLen * Math.cos(angle), y:shiftLen * Math.sin(angle) };
              var cx = t * Math.cos(angle + Math.PI / 2);
              var cy = t * Math.sin(angle + Math.PI / 2);
              var tx = t * Math.cos(angle);
              var ty = t * Math.sin(angle);
              var sx = s.x + shift.x;
              var sy = -s.y - shift.y;
              var d = "M" + xypic.em2px(sx) + "," + xypic.em2px(sy) +
                " Q" + xypic.em2px(sx + tx + cx) + "," + xypic.em2px(sy - ty - cy) +
                " " + xypic.em2px(sx + 2 * tx) + "," + xypic.em2px(sy - 2 * ty) +
                " T" + xypic.em2px(sx + 4 * tx) + "," + xypic.em2px(sy - 4 * ty);
              for (var i = 1; i < n; i++) {
                d += " T" + xypic.em2px(sx + (4 * i + 2) * tx) + "," + xypic.em2px(sy - (4 * i + 2) * ty) +
                  " T" + xypic.em2px(sx + (4 * i + 4) * tx) + "," + xypic.em2px(sy - (4 * i + 4) * ty);
              }
              this.drawSquigglyLineShape(svg, d, s, e, cx, cy, variant);
            }
            break;
          case '~~':
            var len = Math.sqrt(dx * dx + dy * dy);
            var wave = 4 * t;
            if (len >= wave) {
              var n = Math.floor((len - wave) / 2 / wave);
              var shiftLen = (len - wave) / 2 - n * wave;
              shift = { x:shiftLen * Math.cos(angle), y:shiftLen * Math.sin(angle) };
              var cx = t * Math.cos(angle + Math.PI / 2);
              var cy = t * Math.sin(angle + Math.PI / 2);
              var tx = t * Math.cos(angle);
              var ty = t * Math.sin(angle);
              var sx = s.x + shift.x;
              var sy = -s.y - shift.y;
              var d = "";
              for (var i = 0; i <= n; i++) {
                d += " M" + xypic.em2px(sx + 8 * i * tx) + "," + xypic.em2px(sy - 8 * i * ty) + 
                  " Q" + xypic.em2px(sx + (8 * i + 1) * tx + cx) + "," + xypic.em2px(sy - (8 * i + 1) * ty - cy) + 
                  " " + xypic.em2px(sx + (8 * i + 2) * tx) + "," + xypic.em2px(sy - (8 * i + 2) * ty) + 
                  " T" + xypic.em2px(sx + (8 * i + 4) * tx) + "," + xypic.em2px(sy - (8 * i + 4) * ty);
              }
              this.drawSquigglyLineShape(svg, d, s, e, cx, cy, variant);
            }
            break;
            
          default:
            // connect by arrowheads
            var dummyEnv = xypic.Env();
            dummyEnv.c = xypic.Env.originPosition;
            var dummyContext = xypic.DrawingContext(xypic.Shape.none, dummyEnv);
            var arrowBBox = object.boundingBox(dummyContext);
            if (arrowBBox == undefined) {
              return;
            }
            
            var arrowLen = arrowBBox.l + arrowBBox.r;
            if (arrowLen == 0) {
              arrowLen = AST.xypic.strokeWidth;
            }
            
            var len = Math.sqrt(dx * dx + dy * dy);
            var n = Math.floor(len / arrowLen);
            if (n == 0) {
              return;
            }
            
            var shiftLen = (len - n * arrowLen) / 2;
            var cos = Math.cos(angle), sin = Math.sin(angle);
            var ac = arrowLen * cos, as = arrowLen * sin;
            var startX = s.x + (shiftLen + arrowBBox.l) * cos;
            var startY = s.y + (shiftLen + arrowBBox.l) * sin;
            
            var dummyContext = xypic.DrawingContext(xypic.Shape.none, dummyEnv);
            for (var i = 0; i < n; i++) {
              dummyEnv.c = xypic.Frame.Point(startX + i * ac, startY + i * as);
              dummyEnv.angle = angle;
              object.toDropShape(dummyContext).draw(svg);
            }
        }
      }
    },
    drawStraightLine: function (svg, s, e, shift, angle, t, variant, dasharray) {
      if (variant === "3") {
        var cx = t*Math.cos(angle+Math.PI/2);
        var cy = t*Math.sin(angle+Math.PI/2);
        svg.createSVGElement("line", {
          x1:xypic.em2px(s.x+shift.x), y1:-xypic.em2px(s.y+shift.y),
          x2:xypic.em2px(e.x), y2:-xypic.em2px(e.y), 
          "stroke-dasharray":dasharray
        });
        svg.createSVGElement("line", {
          x1:xypic.em2px(s.x+cx+shift.x), y1:-xypic.em2px(s.y+cy+shift.y),
          x2:xypic.em2px(e.x+cx), y2:-xypic.em2px(e.y+cy), 
          "stroke-dasharray":dasharray
        });
        svg.createSVGElement("line", {
          x1:xypic.em2px(s.x-cx+shift.x), y1:-xypic.em2px(s.y-cy+shift.y),
          x2:xypic.em2px(e.x-cx), y2:-xypic.em2px(e.y-cy), 
          "stroke-dasharray":dasharray
        });
      } else if (variant === "2") {
        var cx = t*Math.cos(angle+Math.PI/2)/2;
        var cy = t*Math.sin(angle+Math.PI/2)/2;
        svg.createSVGElement("line", {
          x1:xypic.em2px(s.x+cx+shift.x), y1:-xypic.em2px(s.y+cy+shift.y),
          x2:xypic.em2px(e.x+cx), y2:-xypic.em2px(e.y+cy), 
          "stroke-dasharray":dasharray
        });
        svg.createSVGElement("line", {
          x1:xypic.em2px(s.x-cx+shift.x), y1:-xypic.em2px(s.y-cy+shift.y),
          x2:xypic.em2px(e.x-cx), y2:-xypic.em2px(e.y-cy), 
          "stroke-dasharray":dasharray
        });
      } else {
        svg.createSVGElement("line", {
          x1:xypic.em2px(s.x+shift.x), y1:-xypic.em2px(s.y+shift.y),
          x2:xypic.em2px(e.x), y2:-xypic.em2px(e.y), 
          "stroke-dasharray":dasharray
        });
      }
    },
    drawSquigglyLineShape: function (svg, d, s, e, cx, cy, variant) {
      var g1, g2;
      if (variant === "3") {
        svg.createSVGElement("path", { d:d });
        g1 = svg.createGroup(svg.transformBuilder().translate(cx, cy));
        g1.createSVGElement("path", { d:d });
        g2 = svg.createGroup(svg.transformBuilder().translate(-cx, -cy));
        g2.createSVGElement("path", { d:d });
      } else if (variant === "2") {
        g1 = svg.createGroup(svg.transformBuilder().translate(cx / 2, cy / 2));
        g1.createSVGElement("path", { d:d });
        g2 = svg.createGroup(svg.transformBuilder().translate(-cx / 2, -cy / 2));
        g2.createSVGElement("path", { d:d });
      } else {
        svg.createSVGElement("path", { d:d });
      }
    }
  });
  
  xypic.CurveSegment = MathJax.Object.Subclass({
    bezierFatLine: function (n) {
      var p0 = this.cps[0], pn = this.cps[n];
      var a, b, c;
      if (p0.x !== pn.x || p0.y !== pn.y) {
        a = p0.y-pn.y;
        b = pn.x-p0.x;
        l = Math.sqrt(a*a+b*b);
        a /= l;
        b /= l;
        c = (p0.x*pn.y-p0.y*pn.x)/l;
      } else {
        var angle = this.bezier.angle(this.tmin);
        a = -Math.sin(angle);
        b = Math.cos(angle);
        c = -a*this.cp0.x-b*this.cp0.y;
      }
      
      var cmin = c, cmax = c;
      for (var i = 1; i < n; i++) {
        var ci = -a*this.cps[i].x-b*this.cps[i].y;
        if (ci > cmax) {
          cmax = ci;
        } else if (ci < cmin) {
          cmin = ci;
        }
      }
      return {min:[a, b, cmin], max:[a, b, cmax]};
    }, 
    clippedLineRange: function (ps, lineMin, lineMax) {
      var n = ps.length - 1;
      var es = new Array(n+1);
      var extProd = xypic.Util.extProd;
      for (var i = 0; i <= n; i++) {
        es[i] = [i/n, -lineMin[0]*ps[i].x-lineMin[1]*ps[i].y-lineMin[2], 1];
      }
      
      var vminAgainstLineMin, vmaxAgainstLineMin, t;
      if (es[0][1] < 0) {
        var allLiesBelow = true;
        for (i = 1; i <= n; i++) {
          var l0i = extProd(es[0], es[i]);
          v = -l0i[2]/l0i[0];
          if (v > 0 && v < 1 && (vminAgainstLineMin === undefined || v < vminAgainstLineMin)) {
            vminAgainstLineMin = v;
          }
          if (es[i][1] >= 0) {
            allLiesBelow = false;
          }
        }
        if (allLiesBelow) {
          // clip away everything.
          return undefined;
        }
      } else {
        vminAgainstLineMin = 0;
      }
      if (es[n][1] < 0) {
        for (i = 0; i < n; i++) {
          var lni = extProd(es[n], es[i]);
          v = -lni[2]/lni[0];
          if (v > 0 && v < 1 && (vmaxAgainstLineMin === undefined || v > vmaxAgainstLineMin)) {
            vmaxAgainstLineMin = v;
          }
        }
      } else {
        vmaxAgainstLineMin = 1;
      }
      
      for (i = 0; i <= n; i++) {
        es[i] = [i/n, lineMax[0]*ps[i].x+lineMax[1]*ps[i].y+lineMax[2], 1];
      }
      
      var vminAgainstLineMax, vmaxAgainstLineMax;
      if (es[0][1] < 0) {
        var allLiesAbove = true;
        for (i = 1; i <= n; i++) {
          var l0i = extProd(es[0], es[i]);
          v = -l0i[2]/l0i[0];
          if (v > 0 && v < 1 && (vminAgainstLineMax === undefined || v < vminAgainstLineMax)) {
            vminAgainstLineMax = v;
          }
          if (es[i][1] >= 0) {
            allLiesAbove = false;
          }
        }
        if (allLiesAbove) {
          // clip away everything.
          return undefined;
        }
      } else {
        vminAgainstLineMax = 0;
      }
      if (es[n][1] < 0) {
        for (i = 0; i < n; i++) {
          var lni = extProd(es[n], es[i]);
          v = -lni[2]/lni[0];
          if (v > 0 && v < 1 && (vmaxAgainstLineMax === undefined || v > vmaxAgainstLineMax)) {
            vmaxAgainstLineMax = v;
          }
        }
      } else {
        vmaxAgainstLineMax = 1;
      }
      
      var vmin = Math.max(vminAgainstLineMin, vminAgainstLineMax);
      var vmax = Math.min(vmaxAgainstLineMin, vmaxAgainstLineMax);
      return {min:this.tmin + vmin*(this.tmax - this.tmin), max:this.tmin + vmax*(this.tmax - this.tmin)};
    }
  }, {
    findIntersections: function (segment0, segment1) {
      var n = xypic.CurveSegment.maxIterations;
      var acc = xypic.CurveSegment.goalAccuracy;
      var queue = [[segment0, segment1, false]];
      var i = 0;
      var intersections = [];
      while (i < n && queue.length > 0) {
        i++;
        var head = queue.shift();
        var segment0 = head[0];
        var segment1 = head[1];
        var swapped = head[2];
        
//        segment0.drawFatLine();
        
        var fatLine = segment0.fatLine();
        var tminMax = segment1.clippedRange(fatLine.min, fatLine.max);
        if (tminMax == undefined) {
          // clip away everything
          continue;
        }
        
        var tmin = tminMax.min;
        var tmax = tminMax.max;
        var tlen = tmax - tmin;
        if (tlen < acc && segment0.paramLength() < acc) {
          // intersection found
          if (swapped) {
            intersections.push([segment1.clip(tmin, tmax).paramRange(), segment0.paramRange()]);
          } else {
            intersections.push([segment0.paramRange(), segment1.clip(tmin, tmax).paramRange()]);
          }
          continue;
        }
        if (tlen <= segment1.paramLength() * 0.8) {
          queue.push([segment1.clip(tmin, tmax), segment0, !swapped]);
        } else {
          // subdivision
          if (tlen > segment0.paramLength()) {
            var tmid = (tmax + tmin)/2;
            queue.push([segment1.clip(tmin, tmid), segment0, !swapped]);
            queue.push([segment1.clip(tmid, tmax), segment0, !swapped]);
          } else {
            var newSegment = segment1.clip(tmin, tmax);
            var range0 = segment0.paramRange();
            var mid0 = (range0.min + range0.max)/2;
            queue.push([newSegment, segment0.clip(range0.min, mid0), !swapped]);
            queue.push([newSegment, segment0.clip(mid0, range0.max), !swapped]);
          }
        }
      }
      return intersections;
    },
    maxIterations: 30,
    goalAccuracy: 1e-4
  });
  
  xypic.CurveSegment.Line = xypic.CurveSegment.Subclass({
    Init: function (p0, p1, tmin, tmax) {
      this.p0 = p0;
      this.p1 = p1;
      this.tmin = tmin;
      this.tmax = tmax;
    },
    paramRange: function () { return {min:this.tmin, max:this.tmax}; },
    paramLength: function () { return this.tmax - this.tmin; },
    containsParam: function (t) { return t >= this.tmin && t <= this.tmax; }, 
    position: function (t) {
      return {
        x:this.p0.x + t*(this.p1.x - this.p0.x),
        y:this.p0.y + t*(this.p1.y - this.p0.y)
      };
    },
    fatLine: function () {
      var a = (this.p1.y - this.p0.y), b = (this.p0.x - this.p1.x), c = this.p1.x*this.p0.y - this.p0.x*this.p1.y;
      var l = Math.sqrt(a * a + b * b);
      if (l === 0) {
        a = 1;
        b = 0;
      } else {
        a /= l;
        b /= l;
        c /= l;
      }
      return {min:[a, b, c], max:[a, b, c]};
    },
    clip: function (tmin, tmax) {
      return xypic.CurveSegment.Line(this.p0, this.p1, tmin, tmax);
    },
    clippedRange: function (lineMin, lineMax) {
      var ps = new Array(2);
      ps[0] = this.position(this.tmin);
      ps[1] = this.position(this.tmax);
      return this.clippedLineRange(ps, lineMin, lineMax);
    },
    drawFatLine: function () {
      var fatLine = this.fatLine();
      var lmin = fatLine.min;
      var y = function (x, l) {
        return -(x*l[0] + l[2])/l[1];
      }
      var xmin = this.p0.x;
      var xmax = this.p1.x;
      xypic.svgForDebug.createSVGElement("line", {
        x1:xypic.em2px(xmin), y1:-xypic.em2px(y(xmin, lmax)),
        x2:xypic.em2px(xmax), y2:-xypic.em2px(y(xmax, lmax)),
        "stroke-width":xypic.em2px(0.02 * xypic.oneem), stroke:"red"
      });
    }
  });

  xypic.CurveSegment.QuadBezier = xypic.CurveSegment.Subclass({
    Init: function (bezier, tmin, tmax) {
      this.bezier = bezier;
      this.tmin = tmin;
      this.tmax = tmax;
      this.cp0 = bezier.position(tmin);
      this.cp1 = xypic.Frame.Point(
        (1-tmax)*(1-tmin)*bezier.cp0.x + (tmin+tmax-2*tmin*tmax)*bezier.cp1.x + tmin*tmax*bezier.cp2.x,
        (1-tmax)*(1-tmin)*bezier.cp0.y + (tmin+tmax-2*tmin*tmax)*bezier.cp1.y + tmin*tmax*bezier.cp2.y
      );
      this.cp2 = bezier.position(tmax);
      this.cps = [this.cp0, this.cp1, this.cp2];
    },
    paramRange: function () { return {min:this.tmin, max:this.tmax}; },
    paramLength: function () { return this.tmax - this.tmin; },
    fatLine: function () { return this.bezierFatLine(2); },
    clip: function (tmin, tmax) {
      return xypic.CurveSegment.QuadBezier(this.bezier, tmin, tmax);
    },
    clippedRange: function (lineMin, lineMax) {
      return this.clippedLineRange(this.cps, lineMin, lineMax);
    },
    drawFatLine: function () {
      var fatLine = this.fatLine();
      var lmin = fatLine.min;
      var lmax = fatLine.max;
      var y = function (x, l) {
        return -(x*l[0] + l[2])/l[1];
      }
      var xmin = this.cp0.x
      var xmax = this.cp2.x
      xypic.svgForDebug.createSVGElement("line", {
        x1:xypic.em2px(xmin), y1:-xypic.em2px(y(xmin, lmin)),
        x2:xypic.em2px(xmax), y2:-xypic.em2px(y(xmax, lmin)),
        "stroke-width":xypic.em2px(0.02 * xypic.oneem), stroke:"blue"
      });
      xypic.svgForDebug.createSVGElement("line", {
        x1:xypic.em2px(xmin), y1:-xypic.em2px(y(xmin, lmax)),
        x2:xypic.em2px(xmax), y2:-xypic.em2px(y(xmax, lmax)),
        "stroke-width":xypic.em2px(0.02 * xypic.oneem), stroke:"red"
      });
    }
  });
  
  xypic.CurveSegment.CubicBezier = xypic.CurveSegment.Subclass({
    Init: function (bezier, tmin, tmax) {
      this.bezier = bezier;
      this.tmin = tmin;
      this.tmax = tmax;
      this.cp0 = bezier.position(tmin);
      this.cp1 = xypic.Frame.Point(
        (1-tmax)*(1-tmin)*(1-tmin)*bezier.cp0.x + (1-tmin)*(2*tmin+tmax-3*tmin*tmax)*bezier.cp1.x + tmin*(2*tmax+tmin-3*tmin*tmax)*bezier.cp2.x + tmin*tmin*tmax*bezier.cp3.x,
        (1-tmax)*(1-tmin)*(1-tmin)*bezier.cp0.y + (1-tmin)*(2*tmin+tmax-3*tmin*tmax)*bezier.cp1.y + tmin*(2*tmax+tmin-3*tmin*tmax)*bezier.cp2.y + tmin*tmin*tmax*bezier.cp3.y
      );
      this.cp2 = xypic.Frame.Point(
        (1-tmin)*(1-tmax)*(1-tmax)*bezier.cp0.x + (1-tmax)*(2*tmax+tmin-3*tmin*tmax)*bezier.cp1.x + tmax*(2*tmin+tmax-3*tmin*tmax)*bezier.cp2.x + tmin*tmax*tmax*bezier.cp3.x,
        (1-tmin)*(1-tmax)*(1-tmax)*bezier.cp0.y + (1-tmax)*(2*tmax+tmin-3*tmin*tmax)*bezier.cp1.y + tmax*(2*tmin+tmax-3*tmin*tmax)*bezier.cp2.y + tmin*tmax*tmax*bezier.cp3.y
      );
      this.cp3 = bezier.position(tmax);
      this.cps = [this.cp0, this.cp1, this.cp2, this.cp3];
    },
    paramRange: function () { return {min:this.tmin, max:this.tmax}; },
    paramLength: function () { return this.tmax - this.tmin; },
    fatLine: function () { return this.bezierFatLine(3); },
    clip: function (tmin, tmax) {
      return xypic.CurveSegment.CubicBezier(this.bezier, tmin, tmax);
    },
    clippedRange: function (lineMin, lineMax) {
      return this.clippedLineRange(this.cps, lineMin, lineMax);
    },
    drawFatLine: function () {
      var fatLine = this.fatLine();
      var lmin = fatLine.min;
      var lmax = fatLine.max;
      var y = function (x, l) {
        return -(x*l[0] + l[2])/l[1];
      }
      var xmin = this.cp0.x
      var xmax = this.cp3.x
      xypic.svgForDebug.createSVGElement("line", {
        x1:xypic.em2px(xmin), y1:-xypic.em2px(y(xmin, lmin)),
        x2:xypic.em2px(xmax), y2:-xypic.em2px(y(xmax, lmin)),
        "stroke-width":xypic.em2px(0.02 * xypic.oneem), stroke:"blue"
      });
      xypic.svgForDebug.createSVGElement("line", {
        x1:xypic.em2px(xmin), y1:-xypic.em2px(y(xmin, lmax)),
        x2:xypic.em2px(xmax), y2:-xypic.em2px(y(xmax, lmax)),
        "stroke-width":xypic.em2px(0.02 * xypic.oneem), stroke:"red"
      });
    }
  });
  
  xypic.CurveSegment.Arc = xypic.CurveSegment.Subclass({
    Init: function (x, y, rx, ry, angleMin, angleMax) {
      this.x = x;
      this.y = y;
      this.rx = rx;
      this.ry = ry;
      this.angleMin = angleMin;
      this.angleMax = angleMax;
    },
    paramRange: function () { return { min:this.angleMin, max:this.angleMax }; },
    paramLength: function () { return this.angleMax - this.angleMin; },
    normalizeAngle: function (angle) {
      angle = angle % 2 * Math.PI;
      if (angle > Math.PI) {
        return angle - 2 * Math.PI;
      }
      if (angle < -Math.PI) {
        return angle + 2 * Math.PI;
      }
      return angle;
    },
    containsParam: function (angle) { return angle >= this.angleMin && angle <= this.angleMax; }, 
    fatLine: function () {
      var rx = this.rx;
      var ry = this.ry;
      var tp = (this.angleMax + this.angleMin) / 2;
      var tm = (this.angleMax - this.angleMin) / 2;
      var cosp = Math.cos(tp), sinp = Math.sin(tp);
      var r = Math.sqrt(rx * rx * sinp * sinp + ry * ry * cosp * cosp);
      if (r < AST.xypic.machinePrecision) {
        var Lmin = [1, 0, this.x * ry * cosp + this.y * rx * sinp + rx * ry * Math.cos(tm)];
        var Lmax = [1, 0, this.x * ry * cosp + this.y * rx * sinp + rx * ry];
      } else {
        var rrx = rx / r;
        var rry = ry / r;
        var Lmin = [-rry * cosp, -rrx * sinp, this.x * rry * cosp + this.y * rrx * sinp + rx * ry / r * Math.cos(tm)];
        var Lmax = [-rry * cosp, -rrx * sinp, this.x * rry * cosp + this.y * rrx * sinp + rx * ry / r];
      }
      return { min:Lmin, max:Lmax };
    },
    clip: function (angleMin, angleMax) {
      return xypic.CurveSegment.Arc(this.x, this.y, this.rx, this.ry, angleMin, angleMax);
    },
    toCircleLine: function (line, x0, y0, rx, ry) {
      var a = line[0];
      var b = line[1];
      var c = line[2];
      var a2 = a * rx;
      var b2 = b * ry;
      var c2 = c * rx + (rx - ry) * b * y0;
      var l = Math.sqrt(a2 * a2 + b2 * b2);
      if (l < AST.xypic.machinePrecision) {
        a2 = 1;
        b2 = 0;
      } else {
        a2 /= l;
        b2 /= l;
        c2 /= l;
      }
      return [a2, b2, c2];
    },
    clippedRange: function (origLineMin, origLineMax) {
      var x = this.x;
      var y = this.y;
      var rx = this.rx;
      var ry = this.ry;
      
      var lineMin = this.toCircleLine(origLineMin, x, y, rx, ry);
      var lineMax = this.toCircleLine(origLineMax, x, y, rx, ry);
      var r = rx;
      
      var angleMin = this.angleMin;
      var angleMax = this.angleMax;
      var d = -(lineMin[0] * x + lineMin[1] * y + lineMin[2]);
      
      var sign = xypic.Util.sign2;
      var angles = [];
      var det = r * r - d * d;
      if (det >= 0) {
        var xp = lineMin[0] * d - lineMin[1] * Math.sqrt(r * r - d * d);
        var yp = lineMin[1] * d + lineMin[0] * Math.sqrt(r * r - d * d);
        var xm = lineMin[0] * d + lineMin[1] * Math.sqrt(r * r - d * d);
        var ym = lineMin[1] * d - lineMin[0] * Math.sqrt(r * r - d * d);
        var anglep = Math.atan2(yp, xp);
        var anglem = Math.atan2(ym, xm);
        if (this.containsParam(anglep)) {
          angles.push(anglep);
        }
        if (this.containsParam(anglem)) {
          angles.push(anglem);
        }
      }
      
      var d0 = -(lineMin[0] * (x + r * Math.cos(angleMin)) + lineMin[1] * (y + r * Math.sin(angleMin)) + lineMin[2]);
      var d1 = -(lineMin[0] * (x + r * Math.cos(angleMax)) + lineMin[1] * (y + r * Math.sin(angleMax)) + lineMin[2]);
      var angleMinAgainstLineMin, angleMaxAgainstLineMin;
      if (d0 < 0) {
        if (angles.length == 0) {
          // no intersection
          return undefined;
        }
        angleMinAgainstLineMin = Math.min.apply(Math, angles);
      } else {
        angleMinAgainstLineMin = this.angleMin;
      }
      if (d1 < 0) {
        if (angles.length == 0) {
          // no intersection
          return undefined;
        }
        angleMaxAgainstLineMin = Math.max.apply(Math, angles);
      } else {
        angleMaxAgainstLineMin = this.angleMax;
      }
      
      var d = lineMax[0] * x + lineMax[1] * y + lineMax[2];
      var angles = [];
      var det = r * r - d * d;
      if (det >= 0) {
        var xp = -lineMin[0] * d + lineMin[1] * Math.sqrt(r * r - d * d);
        var yp = -lineMin[1] * d - lineMin[0] * Math.sqrt(r * r - d * d);
        var xm = -lineMin[0] * d - lineMin[1] * Math.sqrt(r * r - d * d);
        var ym = -lineMin[1] * d + lineMin[0] * Math.sqrt(r * r - d * d);
        var anglep = Math.atan2(yp, xp);
        var anglem = Math.atan2(ym, xm);
        if (this.containsParam(anglep)) {
          angles.push(anglep);
        }
        if (this.containsParam(anglem)) {
          angles.push(anglem);
        }
      }
      
      var d0 = lineMax[0] * (x + r * Math.cos(angleMin)) + lineMax[1] * (y + r * Math.sin(angleMin)) + lineMax[2];
      var d1 = lineMax[0] * (x + r * Math.cos(angleMax)) + lineMax[1] * (y + r * Math.sin(angleMax)) + lineMax[2];
      var angleMinAgainstLineMax, angleMaxAgainstLineMax;
      if (d0 < 0) {
        if (angles.length == 0) {
          // no intersection
          return undefined;
        }
        angleMinAgainstLineMax = Math.min.apply(Math, angles);
      } else {
        angleMinAgainstLineMax = this.angleMin;
      }
      if (d1 < 0) {
        if (angles.length == 0) {
          // no intersection
          return undefined;
        }
        angleMaxAgainstLineMax = Math.max.apply(Math, angles);
      } else {
        angleMaxAgainstLineMax = this.angleMax;
      }
      
      return {
        min:Math.max(angleMinAgainstLineMin, angleMinAgainstLineMax), 
        max:Math.min(angleMaxAgainstLineMin, angleMaxAgainstLineMax)
      };
    },
    drawFatLine: function () {
      var fatLine = this.fatLine();
      var lmin = fatLine.min;
      var lmax = fatLine.max;
      var y = function (x, l) {
        return -(x * l[0] + l[2]) / l[1];
      }
      var x0 = this.x + this.r * Math.cos(this.angleMin);
      var x1 = this.x + this.r * Math.cos(this.angleMax);
      var xmin = x0;
      var xmax = x1;
      xypic.svgForDebug.createSVGElement("line", {
        x1:xypic.em2px(xmin), y1:-xypic.em2px(y(xmin, lmin)),
        x2:xypic.em2px(xmax), y2:-xypic.em2px(y(xmax, lmin)),
        "stroke-width":xypic.em2px(0.02 * xypic.oneem), stroke:"blue"
      });
      xypic.svgForDebug.createSVGElement("line", {
        x1:xypic.em2px(xmin), y1:-xypic.em2px(y(xmin, lmax)),
        x2:xypic.em2px(xmax), y2:-xypic.em2px(y(xmax, lmax)),
        "stroke-width":xypic.em2px(0.02 * xypic.oneem), stroke:"red"
      });
    }
  });
  
  
  xypic.LastCurve = MathJax.Object.Subclass({});
  
  xypic.LastCurve.None = xypic.LastCurve.Subclass({
    Init: function () {}, 
    isDefined: false,
    segments: function () { return []; },
    angle: function () { return 0; }
  });
  
  xypic.LastCurve.Augment({}, {
    none: xypic.LastCurve.None()
  });
  
  xypic.LastCurve.Line = xypic.LastCurve.Subclass({
    Init: function (start, end, p, c, lineShape) {
      this.start = start;
      this.end = end;
      this.p = p;
      this.c = c;
      this.lineShape = lineShape; // line from start to end.
    },
    isDefined: true,
    position: function (t) {
      return xypic.Frame.Point(
        this.p.x + t*(this.c.x - this.p.x),
        this.p.y + t*(this.c.y - this.p.y)
      );
    },
    derivative: function (t) {
      return xypic.Frame.Point(
        this.c.x - this.p.x,
        this.c.y - this.p.y
      );
    },
    angle: function (t) {
      var dx = this.c.x - this.p.x;
      var dy = this.c.y - this.p.y;
      if (dx === 0 && dy === 0) {
        return 0;
      }
      return Math.atan2(dy, dx);
    },
    tOfPlace: function (shaveP, shaveC, factor, slideEm) {
      var start = (shaveP? this.start : this.p);
      var end = (shaveC? this.end : this.c);
      if (start.x === end.x && start.y === end.y) {
        return 0;
      } else {
        var dx = end.x - start.x;
        var dy = end.y - start.y;
        var l = Math.sqrt(dx * dx + dy * dy);
        var x, y;
        if (factor > 0.5) {
          x = end.x - (1 - factor) * dx + slideEm * dx / l;
          y = end.y - (1 - factor) * dy + slideEm * dy / l;
        } else {
          x = start.x + factor * dx + slideEm * dx / l;
          y = start.y + factor * dy + slideEm * dy / l;
        }
        var tx = this.c.x - this.p.x;
        var ty = this.c.y - this.p.y;
        if (tx === 0 && ty === 0) {
          return 0;
        }
        if (Math.abs(tx) > Math.abs(ty)) {
          return (x - this.p.x) / tx;
        } else {
          return (y - this.p.y) / ty;
        }
      }
    },
    sliceHole: function (holeFrame, t) {
      if (this.lineShape === undefined || holeFrame.isPoint()) {
        return;
      }
      var shape = this.lineShape;
      var line = shape.line;
      var intersections = line.tOfIntersections(holeFrame); // ts of the line from start to end.
      intersections.push(0);
      intersections.push(1);
      intersections.sort();
      
      var t0 = intersections[0], t1;
      for (var i = 1; i < intersections.length; i++) {
        var t1 = intersections[i];
        var p = line.position((t1 + t0) / 2);
        if (holeFrame.contains(p)) {
          var range = xypic.Range(t0, t1);
          shape.sliceHole(range);
        }
        t0 = t1;
      }
    },
    segments: function () {
      return [xypic.CurveSegment.Line(this.p, this.c, 0, 1)];
    }
  });
  
  xypic.LastCurve.QuadBezier = xypic.LastCurve.Subclass({
    Init: function (origBezier, tOfShavedStart, tOfShavedEnd, curveShape) {
      this.origBezier = origBezier; // unshaved
      this.tOfShavedStart = tOfShavedStart;
      this.tOfShavedEnd = tOfShavedEnd;
      if (!curveShape.isNone) {
        this.curveShape = curveShape;
        if (tOfShavedStart > 0) { curveShape.sliceHole(xypic.Range(0, tOfShavedStart)); }
        if (tOfShavedEnd < 1) { curveShape.sliceHole(xypic.Range(tOfShavedEnd, 1)); }
      }
    },
    isDefined: true,
    position: function (t) {
      return this.origBezier.position(t);
    },
    derivative: function (t) {
      return this.origBezier.derivative(t);
    },
    angle: function (t) {
      return this.origBezier.angle(t);
    },
    tOfPlace: function (shaveP, shaveC, factor, slide) {
      var offset;
      var normalizer;
      if (shaveP) {
        offset = this.tOfShavedStart;
        if (shaveC) {
          normalizer = this.tOfShavedEnd - this.tOfShavedStart;
        } else {
          normalizer = 1  - this.tOfShavedStart;
        }
      } else {
        offset = 0;
        if (shaveC) {
          normalizer = this.tOfShavedEnd;
        } else {
          normalizer = 1;
        }
      }
      var bezier = this.origBezier;
      var pos, angle;
      var normalizedFactor = offset + normalizer * factor;
      if (slide !== 0) {
        var fd = bezier.length(normalizedFactor);
        normalizedFactor = bezier.tOfLength(fd + slide);
      }
      return normalizedFactor;
    },
    sliceHole: function (holeFrame, t) {
      var shape = this.curveShape;
      if (shape === undefined || holeFrame.isPoint()) {
        return;
      }
      var curve = shape.curve;
      var intersections = curve.tOfIntersections(holeFrame); // ts of the curve from p to c.
      intersections.push(0);
      intersections.push(1);
      intersections.sort();
      
      var t0 = intersections[0], t1;
      for (var i = 1; i < intersections.length; i++) {
        var t1 = intersections[i];
        if (t0 <= t && t <= t1) {
          var p = curve.position((t1 + t0) / 2);
          if (holeFrame.contains(p)) {
            var range = xypic.Range(t0, t1);
            shape.sliceHole(range);
          }
        }
        t0 = t1;
      }
    },
    segments: function () {
      return [xypic.CurveSegment.QuadBezier(this.origBezier, 0, 1)];
    }
  });
  
  xypic.LastCurve.CubicBezier = xypic.LastCurve.Subclass({
    Init: function (origBezier, tOfShavedStart, tOfShavedEnd, curveShape) {
      this.origBezier = origBezier; // unshaved
      this.tOfShavedStart = tOfShavedStart;
      this.tOfShavedEnd = tOfShavedEnd;
      if (!curveShape.isNone) {
        this.curveShape = curveShape;
        if (tOfShavedStart > 0) { curveShape.sliceHole(xypic.Range(0, tOfShavedStart)); }
        if (tOfShavedEnd < 1) { curveShape.sliceHole(xypic.Range(tOfShavedEnd, 1)); }
      }
    },
    originalLine: function () {
      return this.originalLine;
    },
    isDefined: true,
    position: function (t) {
      return this.origBezier.position(t);
    },
    derivative: function (t) {
      return this.origBezier.derivative(t);
    },
    angle: function (t) {
      return this.origBezier.angle(t);
    },
    tOfPlace: function (shaveP, shaveC, factor, slide) {
      var offset;
      var normalizer;
      if (shaveP) {
        offset = this.tOfShavedStart;
        if (shaveC) {
          normalizer = this.tOfShavedEnd - this.tOfShavedStart;
        } else {
          normalizer = 1  - this.tOfShavedStart;
        }
      } else {
        offset = 0;
        if (shaveC) {
          normalizer = this.tOfShavedEnd;
        } else {
          normalizer = 1;
        }
      }
      var bezier = this.origBezier;
      var pos, angle;
      var normalizedFactor = offset + normalizer * factor;
      if (slide !== 0) {
        var fd = bezier.length(normalizedFactor);
        normalizedFactor = bezier.tOfLength(fd + slide);
      }
      return normalizedFactor;
    },
    sliceHole: function (holeFrame, t) {
      var shape = this.curveShape;
      if (shape === undefined || holeFrame.isPoint()) {
        return;
      }
      var curve = shape.curve;
      var intersections = curve.tOfIntersections(holeFrame); // ts of the curve from p to c.
      intersections.push(0);
      intersections.push(1);
      intersections.sort();
      
      var t0 = intersections[0], t1;
      for (var i = 1; i < intersections.length; i++) {
        var t1 = intersections[i];
        if (t0 <= t && t <= t1) {
          var p = curve.position((t1 + t0) / 2);
          if (holeFrame.contains(p)) {
            var range = xypic.Range(t0, t1);
            shape.sliceHole(range);
          }
        }
        t0 = t1;
      }
    },
    segments: function () {
      return [xypic.CurveSegment.CubicBezier(this.origBezier, 0, 1)];
    }
  });
  
  xypic.LastCurve.CubicBSpline = xypic.LastCurve.Subclass({
    Init: function (s, e, origBeziers, tOfShavedStart, tOfShavedEnd, curveShape) {
      this.s = s;
      this.e = e;
      this.origBeziers = origBeziers; // unshaved
      this.tOfShavedStart = tOfShavedStart;
      this.tOfShavedEnd = tOfShavedEnd;
      if (!curveShape.isNone) {
        this.curveShape = curveShape;
        if (tOfShavedStart > 0) { curveShape.sliceHole(xypic.Range(0, tOfShavedStart)); }
        if (tOfShavedEnd < 1) { curveShape.sliceHole(xypic.Range(tOfShavedEnd, 1)); }
      }
    },
    isDefined: true,
    position: function (t) {
      return this.origBeziers.position(t);
    },
    derivative: function (t) {
      return this.origBeziers.derivative(t);
    },
    angle: function (t) {
      return this.origBeziers.angle(t);
    },
    tOfPlace: function (shaveP, shaveC, factor, slide) {
      var offset;
      var normalizer;
      if (shaveP) {
        offset = this.tOfShavedStart;
        if (shaveC) {
          normalizer = this.tOfShavedEnd - this.tOfShavedStart;
        } else {
          normalizer = 1  - this.tOfShavedStart;
        }
      } else {
        offset = 0;
        if (shaveC) {
          normalizer = this.tOfShavedEnd;
        } else {
          normalizer = 1;
        }
      }
      var beziers = this.origBeziers;
      var pos, angle;
      var normalizedFactor = offset + normalizer * factor;
      if (slide !== 0) {
        var fd = beziers.length(normalizedFactor);
        normalizedFactor = beziers.tOfLength(fd + slide);
      }
      return normalizedFactor;
    },
    sliceHole: function (holeFrame, t) {
      var shape = this.curveShape;
      if (shape === undefined || holeFrame.isPoint()) {
        return;
      }
      var curve = shape.curve;
      var intersections = curve.tOfIntersections(holeFrame); // ts of the curve from p to c.
      intersections.push(0);
      intersections.push(1);
      intersections.sort();
      
      var t0 = intersections[0], t1;
      for (var i = 1; i < intersections.length; i++) {
        var t1 = intersections[i];
        if (t0 <= t && t <= t1) {
          var p = curve.position((t1 + t0) / 2);
          if (holeFrame.contains(p)) {
            var range = xypic.Range(t0, t1);
            shape.sliceHole(range);
          }
        }
        t0 = t1;
      }
    },
    segments: function () {
      var segments = new Array(this.origBeziers.length);
      var n = segments.length;
      for (var i = 0; i < n; i++) {
        segments[i] = xypic.CurveSegment.CubicBezier(this.origBezier, i/n, (i+1)/n);
      }
      return segments;
    }
  });
  
  
  xypic.Saving = MathJax.Object.Subclass({});
  xypic.Saving.Position = MathJax.Object.Subclass({
    Init: function (pos) {
      this.pos = pos;
    },
    position: function (context) {
      return this.pos;
    },
    toString: function () {
      return this.pos.toString();
    }
  });
  
  xypic.Saving.Macro = MathJax.Object.Subclass({
    Init: function (macro) {
      this.macro = macro;
    },
    position: function (context) {
      env.c = this.macro.position(context);
      return env.c;
    },
    toString: function () {
      return this.macro.toString();
    }
  });
  
  xypic.Saving.Base = MathJax.Object.Subclass({
    Init: function (origin, xBase, yBase) {
      this.origin = origin;
      this.xBase = xBase;
      this.yBase = yBase;
    },
    position: function (context) {
      var env = context.env;
      env.origin = this.origin;
      env.xBase = this.xBase;
      env.yBase = this.yBase;
      return env.c;
    },
    toString: function () {
      return "origin:" + this.origin + ", xBase:" + this.xBase + ", yBase:" + this.yBase;
    }
  });
  
  xypic.Saving.Stack = MathJax.Object.Subclass({
    Init: function (stack) {
      this.stack = stack;
    },
    position: function (context) {
      var env = context.env;
      if (!this.stack.isEmpty) {
        this.stack.tail.reverse().foreach(function (p) {
          env.capturePosition(p);
        });
        env.c = this.stack.head;
      }
      return env.c;
    },
    toString: function () {
      return this.stack.toString();
    }
  });
  
  
  xypic.Env = MathJax.Object.Subclass({
    Init: function () {
      var onemm = xypic.length2em("1mm");
      this.origin = {x:0, y:0};
      this.xBase = {x:onemm, y:0};
      this.yBase = {x:0, y:onemm};
      this.savedPosition = {};
      this.stateStack = FP.List.empty;
      this.stackFrames = FP.List.empty;
      this.stack = FP.List.empty;
      this.angle = 0; // radian
      this.lastCurve = xypic.LastCurve.none;
      this.p = this.c = xypic.Env.originPosition;
      this.shouldCapturePos = false;
      this.capturedPositions = FP.List.empty;
      this.objectmargin = AST.xypic.objectmargin;
      this.objectheight = AST.xypic.objectheight;
      this.objectwidth = AST.xypic.objectwidth;
      this.labelmargin = AST.xypic.labelmargin;
    },
    duplicate: function () {
      var newEnv = xypic.Env();
      xypic.Env.copyFields(this, newEnv);
      return newEnv;
    },
    saveState: function () {
      var currentState = this.duplicate();
      this.stateStack = this.stateStack.prepend(currentState);
    },
    restoreState: function () {
      if (!this.stateStack.isEmpty) {
        var savedState = this.stateStack.head;
        this.stateStack = this.stateStack.tail;
        xypic.Env.copyFields(savedState, this);
      }
    },
    absVector: function (x, y) {
      var ax = this.origin.x + x * this.xBase.x + y * this.yBase.x;
      var ay = this.origin.y + x * this.xBase.y + y * this.yBase.y;
      return {x:ax, y:ay};
    },
    inverseAbsVector: function (ax, ay) {
      var bxx = this.xBase.x;
      var bxy = this.xBase.y;
      var byx = this.yBase.x;
      var byy = this.yBase.y;
      var det = bxx * byy - bxy * byx;
      var dx = ax - this.origin.x;
      var dy = ay - this.origin.y
      var x = (byy * dx - byx * dy) / det;
      var y = (-bxy * dx + bxx * dy) / det;
      return {x:x, y:y};
    },
    setOrigin: function (x, y) {
      this.origin = {x:x, y:y};
    },
    setXBase: function (x, y) {
      this.xBase = {x:x, y:y};
    },
    setYBase: function (x, y) {
      this.yBase = {x:x, y:y};
    },
    swapPAndC: function () {
      var t = this.p;
      this.p = this.c;
      this.c = t;
    },
    enterStackFrame: function () {
      this.stackFrames = this.stackFrames.prepend(this.stack);
      this.initStack();
    },
    leaveStackFrame: function () {
      if (!this.stackFrames.isEmpty) {
        this.stack = this.stackFrames.head;
        this.stackFrames = this.stackFrames.tail;
      } else {
        this.initStack();
      } 
    },
    savePos: function (id, pos) {
      this.savedPosition[id] = pos;
    },
    startCapturePositions: function () {
      this.shouldCapturePos = true;
      this.capturedPositions = FP.List.empty;
    },
    endCapturePositions: function () {
      this.shouldCapturePos = false;
      var positions = this.capturedPositions;
      this.capturedPositions = FP.List.empty;
      return positions;
    },
    capturePosition: function (pos) {
      if (this.shouldCapturePos && pos !== undefined) {
        this.capturedPositions = this.capturedPositions.prepend(pos);
      }
    },
    pushPos: function (pos) {
      if (pos !== undefined) {
        this.stack = this.stack.prepend(pos);
      }
    },
    popPos: function () {
      if (this.stack.isEmpty) {
        throw xypic.ExecutionError("cannot pop from the empty stack");
      } else {
        var pos = this.stack.head;
        this.stack = this.stack.tail;
        return pos;
      }
    },
    initStack: function () {
      this.stack = FP.List.empty;
    },
    setStack: function (positions) {
      this.stack = positions;
    },
    stackAt: function (number) {
      return this.stack.at(number);
    },
    lookupPos: function (id, errorMessage) {
      var pos = this.savedPosition[id];
      if (pos === undefined) {
        if (errorMessage !== undefined) {
          throw xypic.ExecutionError(errorMessage);
        } else {
          throw xypic.ExecutionError('<pos> "' + id + '" not defined.');
        }
      } else {
        return pos;
      }
    },
    toString: function () {
      var savedPositionDesc = "";
      for (var id in this.savedPosition) {
        if (this.savedPosition.hasOwnProperty(id)) {
          if (savedPositionDesc.length > 0) {
            savedPositionDesc += ", "
          }
          savedPositionDesc += id.toString()+":"+this.savedPosition[id];
        }
      }
      return "Env\n  p:"+this.p+"\n  c:"+this.c+"\n  angle:"+this.angle+"\n  lastCurve:"+this.lastCurve+"\n  savedPosition:{"+savedPositionDesc+"}\n  origin:{x:"+this.origin.x+", y:"+this.origin.y+"}\n  xBase:{x:"+this.xBase.x+", y:"+this.xBase.y+"}\n  yBase:{x:"+this.yBase.x+", y:"+this.yBase.y+"}\n  stackFrames:"+this.stackFrames+"\n  stack:"+this.stack+"\n  shouldCapturePos:"+this.shouldCapturePos+"\n  capturedPositions:"+this.capturedPositions;
    }
  }, {
    originPosition: xypic.Frame.Point(0, 0),
    copyFields: function (from, to) {
      for (var attr in from) {
        if (from.hasOwnProperty(attr)) {
          to[attr] = from[attr];
        }
      }
      to.savedPosition = {};
      for (var id in from.savedPosition) {
        if (from.savedPosition.hasOwnProperty(id)) {
          to.savedPosition[id] = from.savedPosition[id];
        }
      }
    }
  });
  
  AST.PosDecor.Augment({
    toShape: function (context) {
      this.pos.toShape(context);
      this.decor.toShape(context);
    }
  });
  
  AST.Pos.Coord.Augment({
    toShape: function (context) {
      context.env.c = this.coord.position(context);
      this.pos2s.foreach(function (p) { p.toShape(context); });
    }
  });
  
  AST.Pos.Plus.Augment({
    toShape: function (context) {
      var env = context.env;
      var pos = this.coord.position(context);
      env.c = pos.move(env.c.x + pos.x, env.c.y + pos.y);
    }
  });
  
  AST.Pos.Minus.Augment({
    toShape: function (context) {
      var env = context.env;
      var pos = this.coord.position(context);
      env.c = pos.move(env.c.x - pos.x, env.c.y - pos.y);
    }
  });
  
  AST.Pos.Skew.Augment({
    toShape: function (context) {
      var env = context.env;
      var pos = this.coord.position(context);
      var rp = xypic.Frame.Point(pos.x + env.c.x, pos.y + env.c.y);
      env.c = rp.combineRect(env.c);
    }
  });
  
  AST.Pos.Cover.Augment({
    toShape: function (context) {
      var env = context.env;
      var pos = this.coord.position(context);
      env.c = env.c.combineRect(pos);
    }
  });
  
  AST.Pos.Then.Augment({
    toShape: function (context) {
      var env = context.env;
      env.capturePosition(env.c);
      env.c = this.coord.position(context);
    }
  });
  
  AST.Pos.SwapPAndC.Augment({
    toShape: function (context) {
      var env = context.env;
      env.swapPAndC();
      env.c = this.coord.position(context);
    }
  });
  
  AST.Pos.SetBase.Augment({
    toShape: function (context) {
      var env = context.env;
      var p = env.p;
      var x = env.c.x - p.x;
      var y = env.c.y - p.y;
      env.setOrigin(p.x, p.y);
      env.setXBase(x, y);
      env.setYBase(-y, x);
      env.c = this.coord.position(context);
    }
  });
  
  AST.Pos.SetYBase.Augment({
    toShape: function (context) {
      var env = context.env;
      env.setYBase(env.c.x - env.origin.x, env.c.y - env.origin.y);
      env.c = this.coord.position(context);
    }
  });
  
  AST.Pos.ConnectObject.Augment({
    toShape: function (context) {
      this.object.toConnectShape(context);
    }
  });
  
  AST.Pos.DropObject.Augment({
    toShape: function (context) {
      this.object.toDropShape(context);
    }
  });
  
  AST.Pos.Place.Augment({
    toShape: function (context) {
      var env = context.env;
      if (env.lastCurve.isDefined) {
        var place = this.place;
        var start, end, f, dimen;
        var shouldShaveP = (place.shaveP > 0);
        var shouldShaveC = (place.shaveC > 0);
        var jotP = (shouldShaveP? place.shaveP - 1 : 0);
        var jotC = (shouldShaveC? place.shaveC - 1 : 0);
        
        if (shouldShaveP) { f = 0; }
        if (shouldShaveC) { f = 1; }
        if (shouldShaveP == shouldShaveC) {
          f = 0.5;
        }
        if (place.factor !== undefined) {
          if (place.factor.isIntercept) {
            shouldShaveC = shouldShaveP = false;
            f = place.factor.value(context);
            if (f === undefined) {
              return;
            }
          } else {
            f = place.factor.value(context);
          }
        }
        
        dimen = xypic.length2em(place.slide.dimen.getOrElse("0"));
        var jot = AST.xypic.jot;
        var slideEm = dimen + (jotP - jotC) * jot;
        var t = env.lastCurve.tOfPlace(shouldShaveP, shouldShaveC, f, slideEm);
        var pos = env.lastCurve.position(t);
        var angle = env.lastCurve.angle(t);
        env.c = pos;
        env.angle = angle;
        return t;
      }
      return undefined;
    }
  });
  
  AST.Pos.PushCoord.Augment({
    toShape: function (context) {
      var env = context.env;
      var pos = this.coord.position(context);
      env.pushPos(pos);
    }
  });
  
  AST.Pos.EvalCoordThenPop.Augment({
    toShape: function (context) {
      var env = context.env;
      env.c = this.coord.position(context);
      env.popPos();
    }
  });
  
  AST.Pos.LoadStack.Augment({
    toShape: function (context) {
      var env = context.env;
      env.startCapturePositions();
      this.coord.position(context);
      var positions = env.endCapturePositions();
      env.setStack(positions);
      env.pushPos(env.c);
    }
  });
  
  AST.Pos.DoCoord.Augment({
    toShape: function (context) {
      var env = context.env;
      var coord = this.coord;
      var pos = env.stack.reverse();
      pos.foreach(function (c) {
        env.c = c;
        coord.position(context);
      });
    }
  });
  
  AST.Pos.InitStack.Augment({
    toShape: function (context) {
      context.env.initStack();
    }
  });
  
  AST.Pos.EnterFrame.Augment({
    toShape: function (context) {
      context.env.enterStackFrame();
    }
  });
  
  AST.Pos.LeaveFrame.Augment({
    toShape: function (context) {
      context.env.leaveStackFrame();
    }
  });
  
  AST.Place.Factor.Augment({
    value: function (context) {
      return this.factor;
    }
  });
  
  AST.Place.Intercept.Augment({
    value: function (context) {
      var env = context.env;
      if (!env.lastCurve.isDefined) {
        return undefined;
      }
      
      var tmpEnv = env.duplicate();
      tmpEnv.angle = 0;
      tmpEnv.lastCurve = xypic.LastCurve.none;
      tmpEnv.p = tmpEnv.c = xypic.Env.originPosition;
      var tmpContext = xypic.DrawingContext(xypic.Shape.none, tmpEnv);
      
      var box = this.pos.toShape(tmpContext);
      context.appendShapeToFront(tmpContext.shape);
      
      if (!tmpEnv.lastCurve.isDefined) {
        tmpEnv.lastCurve = xypic.LastCurve.Line(tmpEnv.p, tmpEnv.c, tmpEnv.p, tmpEnv.c, undefined);
      }
      
      var intersec = [];
      var thisSegs = env.lastCurve.segments();
      var thatSegs = tmpEnv.lastCurve.segments();
      
      for (var i = 0; i < thisSegs.length; i++) {
        for (var j = 0; j < thatSegs.length; j++) {
          intersec = intersec.concat(xypic.CurveSegment.findIntersections(thisSegs[i], thatSegs[j]));
        }
      }
      
      if (intersec.length === 0) {
        // find the nearest point, if no intersection was found.
        console.log("perhaps no curve intersection.");
        
        // Levenberg-Marqardt Method
        var line0 = env.lastCurve;
        var line1 = tmpEnv.lastCurve;
        
        var n = 100; // maxIterations
        var goalAccuracy = 1e-5;
        var tau = 1e-3;
        
        var k = 0;
        var nu = 2;
        
        // TODO: 複数個の開始地点から探索し、尤もらしい解を選択する。
        var x0 = 0;
        var x1 = 0;
        
        var tx = function (x) {
          return 1 / (1 + Math.exp(-x));
        }
        var dtx = function (x) {
          var ex = Math.exp(-x);
          return ex / (1 + ex) / (1 + ex);
        }
        
        var t0 = tx(x0);
        var t1 = tx(x1);
        var dt0 = dtx(x0);
        var dt1 = dtx(x1);
        
        var dp0 = line0.derivative(t0);
        var dp1 = line1.derivative(t1);
        
        var j00 = dp0.x * dt0, j01 = -dp1.x * dt1;
        var j10 = dp0.y * dt0, j11 = -dp1.y * dt1;
        
        var a00 = j00 * j00 + j10 * j10, a01 = j00 * j01 + j10 * j11;
        var a10 = j01 * j00 + j11 * j10, a11 = j01 * j01 + j11 * j11;
        
        var p0 = line0.position(t0);
        var p1 = line1.position(t1);
        
        var f0 = p0.x - p1.x;
        var f1 = p0.y - p1.y;
        
        var g0 = j00 * f0 + j10 * f1;
        var g1 = j01 * f0 + j11 * f1;
        
        var stop = Math.sqrt(g0 * g0 + g1 * g1) < goalAccuracy;
        var mu = tau * Math.max(a00, a11);
        
        while (!stop && k < n) {
          k++;
          do {
            var am00 = a00 + mu, am01 = a01;
            var am10 = a10, am11 = a11 + mu;
            
            var det = am00 * am11 - am01 * am10;
            var d0 = (am11 * g0 - a01 * g1) / det;
            var d1 = (-am10 * g0 + a00 * g1) / det;
            
            if ((d0 * d0 + d1 * d1) < goalAccuracy * goalAccuracy * (x0 * x0 + x1 * x1)) {
              stop = true;
            } else {
              var newX0 = x0 - d0;
              var newX1 = x1 - d1;
              
              var newT0 = tx(newX0);
              var newT1 = tx(newX1);
              
              var newP0 = line0.position(newT0);
              var newP1 = line1.position(newT1);
              
              var newF0 = newP0.x - newP1.x;
              var newF1 = newP0.y - newP1.y;
              
              var rho = ((f0 * f0 + f1 * f1) - (newF0 * newF0 + newF1 * newF1)) / (d0 * (mu * d0 + g0) + d1 * (mu * d1 + g1));
              
              if (rho > 0) {
                x0 = newX0;
                x1 = newX1;
                t0 = newT0;
                t1 = newT1;
                dt0 = dtx(x0);
                dt1 = dtx(x1);
                dp0 = line0.derivative(t0);
                dp1 = line1.derivative(t1);
                j00 = dp0.x * dt0;  j01 = -dp1.x * dt1;
                j10 = dp0.y * dt0;  j11 = -dp1.y * dt1;
                a00 = j00 * j00 + j10 * j10;  a01 = j00 * j01 + j10 * j11;
                a10 = j01 * j00 + j11 * j10;  a11 = j01 * j01 + j11 * j11;
                f0 = newF0;
                f1 = newF1;
                g0 = j00 * f0 + j10 * f1;
                g1 = j01 * f0 + j11 * f1;
                stop = Math.sqrt(g0 * g0 + g1 * g1) < goalAccuracy;
                var sigma = 2 * rho - 1;
                mu = mu + Math.max(1 / 3, 1 - sigma * sigma * sigma);
                nu = 2;
              } else {
                mu = mu * nu;
                nu = 2 * nu;
              }
            }
          } while (!stop && !(rho !== undefined && rho > 0))
        }
        
        return tx(x0);
      } else {
        var t = (intersec[0][0].min + intersec[0][0].max)/2;
        for (var i = 1; i < intersec.length; i++) { 
          var ttmp = (intersec[i][0].min + intersec[i][0].max)/2;
          if (t > ttmp) { t = ttmp; }
        }
        return t;
      }
    }
  });
  
  AST.Pos.SavePos.Augment({
    toShape: function (context) {
      var env = context.env;
      env.savePos(this.id, xypic.Saving.Position(env.c));
    }
  });
  
  AST.Pos.SaveMacro.Augment({
    toShape: function (context) {
      var env = context.env;
      env.savePos(this.id, xypic.Saving.Macro(this.macro));
    }
  });
  
  AST.Pos.SaveBase.Augment({
    toShape: function (context) {
      var env = context.env;
      env.savePos(this.id, xypic.Saving.Base(env.origin, env.xBase, env.yBase));
    }
  });
  
  AST.Pos.SaveStack.Augment({
    toShape: function (context) {
      var env = context.env;
      env.savePos(this.id, xypic.Saving.Stack(env.stack));
    }
  });
  
  AST.Object.Augment({
    toDropShape: function (context) {
      var env = context.env;
      if (env.c === undefined) {
        return xypic.Shape.none;
      }
      
      var modifiers = this.modifiers;
      if (modifiers.isEmpty) {
        return this.object.toDropShape(context);
      } else {
        var tmpEnv = env.duplicate();
        var subcontext = xypic.DrawingContext(xypic.Shape.none, tmpEnv);
        var reversedProcessedModifiers = FP.List.empty;
        modifiers.foreach(function (m) {
          m.preprocess(subcontext, reversedProcessedModifiers);
          reversedProcessedModifiers = reversedProcessedModifiers.prepend(m);
        });
        var objectShape = this.object.toDropShape(subcontext);
        var objectBoundingBox = tmpEnv.c;
        if (objectBoundingBox === undefined) {
          return xypic.Shape.none;
        }
        var originalReferencePoint = tmpEnv.originalReferencePoint;
        tmpEnv = env.duplicate(); // restore angle
        tmpEnv.c = objectBoundingBox;
        tmpEnv.originalReferencePoint = originalReferencePoint;
        subcontext = xypic.DrawingContext(xypic.Shape.none, tmpEnv);
        objectShape = modifiers.head.modifyShape(subcontext, objectShape, modifiers.tail);
        context.appendShapeToFront(objectShape);
        env.c = tmpEnv.c.move(env.c.x, env.c.y);
        return objectShape;
      }
    },
    toConnectShape: function (context) {
      var env = context.env;
      if (env.c === undefined) {
        return xypic.Shape.none;
      }
      
      var modifiers = this.modifiers;
      if (modifiers.isEmpty) {
        return this.object.toConnectShape(context);
      } else {
        var tmpEnv = env.duplicate();
        var subcontext = xypic.DrawingContext(xypic.Shape.none, tmpEnv);
        var reversedProcessedModifiers = FP.List.empty;
        modifiers.foreach(function (m) {
          m.preprocess(subcontext, reversedProcessedModifiers);
          reversedProcessedModifiers = reversedProcessedModifiers.prepend(m);
        });
        var objectShape = this.object.toConnectShape(subcontext);
        env.angle = tmpEnv.angle;
        env.lastCurve = tmpEnv.lastCurve;
        var objectBoundingBox = tmpEnv.c;
        if (objectBoundingBox === undefined) {
          return xypic.Shape.none;
        }
        var originalReferencePoint = tmpEnv.originalReferencePoint;
        tmpEnv = env.duplicate(); // restore angle
        tmpEnv.c = objectBoundingBox;
        tmpEnv.originalReferencePoint = originalReferencePoint;
        subcontext = xypic.DrawingContext(xypic.Shape.none, tmpEnv);
        objectShape = modifiers.head.modifyShape(subcontext, objectShape, modifiers.tail);
        context.appendShapeToFront(objectShape);
        env.c = tmpEnv.c.move(env.c.x, env.c.y);
        return objectShape;
      }
    },
    boundingBox: function (context) {
      var tmpEnvContext = context.duplicateEnv();
      var tmpEnv = tmpEnvContext.env;
      tmpEnv.angle = 0;
      tmpEnv.p = tmpEnv.c = xypic.Env.originPosition;
      tmpEnvContext.shape = xypic.Shape.none;
      var dropShape = this.toDropShape(tmpEnvContext);
      return dropShape.getBoundingBox();
    }
  });
  
  AST.ObjectBox.Augment({
    toConnectShape: function (context) {
      // 多重線の幅、点線・破線の幅の基準
      var env = context.env;
      var origC = env.c;
      var env = context.env;
      var t = AST.xypic.thickness;
      var s = env.p.edgePoint(env.c.x, env.c.y);
      var e = env.c.edgePoint(env.p.x, env.p.y);
      if (s.x !== e.x || s.y !== e.y) {
        var shape = xypic.Curve.Line(s, e).toShape(context, this, "196883" /* dummy dir name */, "");
        env.originalReferencePoint = origC;
        return shape;
      } else {
        env.angle = 0;
        env.lastCurve = xypic.LastCurve.none;
        env.originalReferencePoint = origC;
        return xypic.Shape.none;
      }
    },
    boundingBox: function (context) {
      var tmpEnvContext = context.duplicateEnv();
      var tmpEnv = tmpEnvContext.env;
      tmpEnv.angle = 0;
      tmpEnv.p = tmpEnv.c = xypic.Env.originPosition;
      tmpEnvContext.shape = xypic.Shape.none;
      var dropShape = this.toDropShape(tmpEnvContext);
      return dropShape.getBoundingBox();
    }
  });
  
  AST.ObjectBox.WrapUpObject.Augment({
    toDropShape: function (context) {
      var env = context.env;
      var shape = this.object.toDropShape(context);
      env.originalReferencePoint = env.c;
      return shape;
    },
    toConnectShape: function (context) {
      var env = context.env;
      var shape = this.object.toConnectShape(context);
      env.originalReferencePoint = env.c;
      return shape;
    }
  });
  
  AST.ObjectBox.CompositeObject.Augment({
    toDropShape: function (context) {
      var env = context.env;
      var origC = env.c;
      if (origC === undefined) {
        return xypic.Shape.none;
      }
      var c = origC;
      var tmpEnv = env.duplicate();
      var subcontext = xypic.DrawingContext(xypic.Shape.none, tmpEnv);
      this.objects.foreach(function (obj) {
        tmpEnv.c = origC;
        var tmpShape = obj.toDropShape(subcontext);
        c = xypic.Frame.combineRect(c, tmpEnv.c);
        c = xypic.Frame.combineRect(c, tmpShape.getBoundingBox().toPoint());
      });
      env.c = c;
      var compositeShape = subcontext.shape;
      context.appendShapeToFront(compositeShape);
      env.originalReferencePoint = origC;
      return compositeShape;
    }
  });
  
  AST.ObjectBox.Xybox.Augment({
    toDropShape: function (context) {
      var env = context.env;
      var c = env.c;
      if (c === undefined) {
        return xypic.Shape.none;
      }
      var subenv = xypic.Env();
      var subcontext = xypic.DrawingContext(xypic.Shape.none, subenv);
      this.posDecor.toShape(subcontext);
      var subshape = subcontext.shape;
      var bbox = subshape.getBoundingBox();
      if (bbox === undefined) {
        return xypic.Shape.none;
      }
      var l = Math.max(0, bbox.l - bbox.x);
      var r = Math.max(0, bbox.r + bbox.x);
      var u = Math.max(0, bbox.u + bbox.y);
      var d = Math.max(0, bbox.d - bbox.y);
      env.c = xypic.Frame.Rect(c.x, c.y, { l:l, r:r, u:u, d:d });
      env.originalReferencePoint = c;
      var objectShape = xypic.Shape.TranslateShape(c.x, c.y, subshape);
      context.appendShapeToFront(objectShape);
      return objectShape;
    }
  });
  
  AST.ObjectBox.Xymatrix.Augment({
    toDropShape: function (context) {
      var env = context.env;
      var c = env.c;
      var shape = this.xymatrix.toShape(context);
      env.originalReferencePoint = c;
      return shape;
    }
  });
  
  AST.ObjectBox.Text.Augment({
    toDropShape: function (context) {
      var env = context.env;
      var textShape = xypic.Shape.TextShape(env.c, this.math, xypic.svgForTestLayout);
      context.appendShapeToFront(textShape);
      env.c = textShape.getBoundingBox();
      env.originalReferencePoint = textShape.getOriginalReferencePoint();
      return textShape;
    }
  });
  
  AST.ObjectBox.Empty.Augment({
    toDropShape: function (context) {
      var env = context.env;
      env.originalReferencePoint = env.c;
      env.c = xypic.Frame.Point(env.c.x, env.c.y);
      return xypic.Shape.none;
    }
  });

  
  AST.ObjectBox.Txt.Augment({
    toDropShape: function (context) {
      var env = context.env;
      if (env.c === undefined) {
        return xypic.Shape.none;
      }
      // TODO change width
      var textShape = this.textObject.toDropShape(context);
      env.originalReferencePoint = env.c;
      return textShape;
    }
  });
  AST.ObjectBox.Txt.Width.Vector.Augment({
    width: function (context) {
      return this.vector.xy().x;
    }
  });
  AST.ObjectBox.Txt.Width.Vector.Augment({
    width: function (context) {
      var c = context.env.c;
      return c.r + c.l;
    }
  });
  
  AST.ObjectBox.Cir.Augment({
    toDropShape: function (context) {
      var env = context.env;
      if (env.c === undefined) {
        return xypic.Shape.none;
      }
      env.originalReferencePoint = env.c;
      var r = this.radius.radius(context);
      var x = env.c.x;
      var y = env.c.y;
      var circleShape = this.cir.toDropShape(context, x, y, r);
      env.c = xypic.Frame.Ellipse(x, y, r, r, r, r);
      
      return circleShape;
    },
    toConnectShape: function (context) {
      // TODO: 何もしなくてよいかTeXの出力結果を確認する。
      var env = context.env;
      env.originalReferencePoint = env.c;
      return xypic.Shape.none;
    }
  });
  
  AST.ObjectBox.Cir.Radius.Vector.Augment({
    radius: function (context) {
      return this.vector.xy(context).x;
    }
  });
  AST.ObjectBox.Cir.Radius.Default.Augment({
    radius: function (context) {
      return context.env.c.r;
    }
  });
  AST.ObjectBox.Cir.Cir.Segment.Augment({
    toDropShape: function (context, x, y, r) {
      var env = context.env;
      var sa = this.startPointDegree(context);
      var ea = this.endPointDegree(context, sa);
      var da = ea - sa;
      da = (da < 0? da + 360 : da);
      if (da === 0) {
        return xypic.Shape.none;
      }
      
      var large, flip;
      if (this.orient === "^") {
        large = (da > 180? "1" : "0");
        flip = "0";
      } else {
        large = (da > 180? "0" : "1");
        flip = "1";
      }
      
      var degToRadCoef = Math.PI / 180;
      var sx = x + r * Math.cos(sa * degToRadCoef);
      var sy = y + r * Math.sin(sa * degToRadCoef);
      var ex = x + r * Math.cos(ea * degToRadCoef);
      var ey = y + r * Math.sin(ea * degToRadCoef);
      
      var circleSegmentShape = xypic.Shape.CircleSegmentShape(x, y, sx, sy, r, large, flip, ex, ey);
      context.appendShapeToFront(circleSegmentShape);
      return circleSegmentShape;
    },
    startPointDegree: function (contect) {
      var sd = this.startDiag.toString();
      var sa;
      if (this.orient === "^") {
        sa = this.diagToAngleACW(sd);
      } else {
        sa = this.diagToAngleCW(sd);
      }
      return sa;
    },
    endPointDegree: function (contect, startAngle) {
      var ed = this.endDiag.toString();
      var ea;
      if (this.orient === "^") {
        ea = this.diagToAngleACW(ed, startAngle);
      } else {
        ea = this.diagToAngleCW(ed, startAngle);
      }
      return ea;
    },
    diagToAngleACW: function (diag, angle) {
      switch (diag) {
        case "l": return 90;
        case "r": return -90;
        case "d": return 180;
        case "u": return 0;
        case "dl":
        case "ld":
          return 135;
        case "dr":
        case "rd":
          return -135;
        case "ul":
        case "lu":
          return 45;
        case "ur":
        case "ru":
          return -45;
        default:
          if (angle !== undefined) {
            return angle + 180;
          } else {
            return 0;
          }
      }
    },
    diagToAngleCW: function (diag, angle) {
      switch (diag) {
        case "l": return -90;
        case "r": return 90;
        case "d": return 0;
        case "u": return 180;
        case "dl":
        case "ld":
          return -45;
        case "dr":
        case "rd":
          return 45;
        case "ul":
        case "lu":
          return -135;
        case "ur":
        case "ru":
          return 135;
        default:
          if (angle !== undefined) {
            return angle + 180;
          } else {
            return 0;
          }
      }
    }
  });
  AST.ObjectBox.Cir.Cir.Full.Augment({
    toDropShape: function (context, x, y, r) {
      var fullCircleShape = xypic.Shape.FullCircleShape(x, y, r);
      context.appendShapeToFront(fullCircleShape);
      return fullCircleShape;
    }
  });
  
  AST.ObjectBox.Frame.Augment({
    toDropShape: function (context) {
      var env = context.env;
      env.originalReferencePoint = env.c;
      return this.toDropFilledShape(context, "currentColor", false)
    },
    toDropFilledShape: function (context, color, convertToEllipse) {
      var env = context.env;
      var c = env.c;
      if (c === undefined) {
        return xypic.Shape.none;
      }
      
      var t = AST.xypic.thickness;
      var x = c.x;
      var y = c.y;
      var left = c.l;
      var right = c.r;
      var up = c.u;
      var down = c.d;
      var shape = xypic.Shape.none;
      switch (this.main) {
        case '--':
          var dash = 3 * t;
          if (convertToEllipse) {
            var xy = this.radius.xy(context);
            shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, false, color, xypic.em2px(dash) + " " + xypic.em2px(dash));
          } else {
            var radius = this.radius.radius(context);
            shape = xypic.Shape.RectangleShape(x, y, left, right, up, down, radius, false, color, xypic.em2px(dash) + " " + xypic.em2px(dash));
          }
          break;
          
        case '==':
          var dash = 3 * t;
          if (convertToEllipse) {
            var xy = this.radius.xy(context);
            shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, true, color, xypic.em2px(dash) + " " + xypic.em2px(dash));
          } else {
            var radius = this.radius.radius(context);
            shape = xypic.Shape.RectangleShape(x, y, left, right, up, down, radius, true, color, xypic.em2px(dash) + " " + xypic.em2px(dash));
          }
          break;
          
        case 'o-':
          var dash = 3 * t;
          var radius = AST.xypic.lineElementLength;
          shape = xypic.Shape.RectangleShape(x, y, left, right, up, down, radius, false, color, xypic.em2px(dash) + " " + xypic.em2px(dash));
          break;
          
        case 'oo':
          var xy = this.radius.xy(context);
          var r = xy.x;
          shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, r, r, true, color, undefined);
          break;
          
        case 'ee':
          var xy = this.radius.xy(context);
          shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, true, color, undefined);
          break;
          
        case '-,':
          var depth = this.radius.depth(context);
          var radius = this.radius.radius(context);
          shape = xypic.Shape.CompositeShape(
            xypic.Shape.RectangleShape(x, y, left, right, up, down, radius, false, color, undefined),
            xypic.Shape.BoxShadeShape(x, y, left, right, up, down, depth)
          );
          break;
          
        case '.o':
          var xy = this.radius.xy(context);
          var r = xy.x;
          shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, r, r, false, color, AST.xypic.dottedDasharray);
          break;
          
        case '-o':
          var dash = 3 * t;
          var xy = this.radius.xy(context);
          var r = xy.x;
          shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, r, r, false, color, xypic.em2px(dash) + " " + xypic.em2px(dash));
          break;
          
        case '.e':
          var xy = this.radius.xy(context);
          shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, false, color, AST.xypic.dottedDasharray);
          break;
          
        case '-e':
          var dash = 3 * t;
          var xy = this.radius.xy(context);
          shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, false, color, xypic.em2px(dash) + " " + xypic.em2px(dash));
          break;
          
        case '-':
          if (convertToEllipse) {
            var xy = this.radius.xy(context);
            shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, false, color, undefined);
          } else {
            var radius = this.radius.radius(context);
            shape = xypic.Shape.RectangleShape(x, y, left, right, up, down, radius, false, color, undefined);
          }
          break;
          
        case '=':
          if (convertToEllipse) {
            var xy = this.radius.xy(context);
            shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, true, color, undefined);
          } else {
            var radius = this.radius.radius(context);
            shape = xypic.Shape.RectangleShape(x, y, left, right, up, down, radius, true, color, undefined);
          }
          break;
          
        case '.':
          if (convertToEllipse) {
            var xy = this.radius.xy(context);
            shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, false, color, AST.xypic.dottedDasharray);
          } else {
            var radius = this.radius.radius(context);
            shape = xypic.Shape.RectangleShape(x, y, left, right, up, down, radius, false, color, AST.xypic.dottedDasharray);
          }
          break;
          
        case ',':
          var depth = this.radius.depth(context);
          shape = xypic.Shape.BoxShadeShape(x, y, left, right, up, down, depth, color);
          break;
          
        case 'o':
          var xy = this.radius.xy(context);
          var r = xy.x;
          shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, r, r, false, color, undefined);
          break;
          
        case 'e':
          var xy = this.radius.xy(context);
          shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, false, color, undefined);
          break;
          
        case '\\{':
          shape = xypic.Shape.LeftBrace(x - left, y, up, down, 0, color);
          break;
          
        case '\\}':
          shape = xypic.Shape.LeftBrace(x + right, y, down, up, 180, color);
          break;
          
        case '^\\}':
        case '^\\{':
          shape = xypic.Shape.LeftBrace(x, y + up, right, left, 270, color);
          break;
          
        case '_\\{':
        case '_\\}':
          shape = xypic.Shape.LeftBrace(x, y - down, left, right, 90, color);
          break;
          
        case '(':
          shape = xypic.Shape.LeftParenthesis(x - left, y + (up - down) / 2, up + down, 0, color);
          break;
          
        case ')':
          shape = xypic.Shape.LeftParenthesis(x + right, y + (up - down) / 2, up + down, 180, color);
          break;
          
        case '^(':
        case '^)':
          shape = xypic.Shape.LeftParenthesis(x + (right - left) / 2, y + up, left + right, 270, color);
          break;
          
        case '_(':
        case '_)':
          shape = xypic.Shape.LeftParenthesis(x + (right - left) / 2, y - down, left + right, 90, color);
          break;
          
        case '*':
          if (c.isCircle()) {
            var xy = this.radius.xy(context);
            shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, false, "currentColor", undefined, color, true);
          } else {
            var radius = this.radius.radius(context);
            shape = xypic.Shape.RectangleShape(x, y, left, right, up, down, radius, false, "currentColor", undefined, color, true);
          }
          break;
        
        case '**':
          if (c.isCircle()) {
            var xy = this.radius.xy(context);
            shape = xypic.Shape.EllipseShape(x + (right - left) / 2, y + (up - down) / 2, xy.x, xy.y, false, "currentColor", undefined, color, false);
          } else {
            var radius = this.radius.radius(context);
            shape = xypic.Shape.RectangleShape(x, y, left, right, up, down, radius, false, "currentColor", undefined, color, false);
          }
          break;
          
        default:
          return xypic.Shape.none;
      }
      
      context.appendShapeToFront(shape);
      
      return shape;
    },
    toConnectShape: function (context) {
      var env = context.env;
      var c = env.c;
      var p = env.p;
      if (c === undefined || p === undefined) {
        xypic.Shape.none;
      }
      env.originalReferencePoint = c;
      
      var tmpEnv = env.duplicate();
      tmpEnv.c = p.combineRect(c);
      
      var tmpContext = xypic.DrawingContext(xypic.Shape.none, tmpEnv);
      var shape = this.toDropShape(tmpContext);
      context.appendShapeToFront(shape);
      
      return shape;
    }
  });
  AST.ObjectBox.Frame.Radius.Vector.Augment({
    radius: function (context) {
      return this.vector.xy(context).x;
    },
    depth: function (context) {
      return this.vector.xy(context).x;
    },
    xy: function (context) {
      return this.vector.xy(context);
    }
  });
  AST.ObjectBox.Frame.Radius.Default.Augment({
    radius: function (context) {
      return 0;
    },
    depth: function (context) {
      return AST.xypic.thickness / 2;
    },
    xy: function (context) {
      var c = context.env.c;
      return { x:(c.l + c.r) / 2, y:(c.u + c.d) / 2 };
    }
  });
  
  AST.ObjectBox.Dir.Augment({
    toDropShape: function (context) {
      var env = context.env;
      var c = env.c;
      env.originalReferencePoint = c;
      var angle = env.angle;
      if (c === undefined) {
        return xypic.Shape.none;
      }
      env.c = xypic.Frame.Point(c.x, c.y);
      
      var t = AST.xypic.thickness;
      var shape = xypic.Shape.none;
      switch (this.main) {
        case "":
          return xypic.Shape.none;
        case ">":
          switch (this.variant) {
            case "2":
              shape = xypic.Shape.GT2ArrowheadShape(c, angle);
              var r = shape.getRadius();
              env.c = xypic.Frame.Ellipse(c.x, c.y, r, r, r, r);
              break;
            case "3":
              shape = xypic.Shape.GT3ArrowheadShape(c, angle);
              var r = shape.getRadius();
              env.c = xypic.Frame.Ellipse(c.x, c.y, r, r, r, r);
              break;
            default:
              if (this.variant === "^") {
                shape = xypic.Shape.UpperGTArrowheadShape(c, angle);
              } else if (this.variant === "_") {
                shape = xypic.Shape.LowerGTArrowheadShape(c, angle);
              } else {
                shape = xypic.Shape.GTArrowheadShape(c, angle);
              }
          }
          break;
        case "<":
          switch (this.variant) {
            case "2":
              shape = xypic.Shape.LT2ArrowheadShape(c, angle);
              var r = shape.getRadius();
              env.c = xypic.Frame.Ellipse(c.x, c.y, r, r, r, r);
              break;
            case "3":
              shape = xypic.Shape.LT3ArrowheadShape(c, angle);
              var r = shape.getRadius();
              env.c = xypic.Frame.Ellipse(c.x, c.y, r, r, r, r);
              break;
            default:
              if (this.variant === "^") {
                shape = xypic.Shape.UpperLTArrowheadShape(c, angle);
              } else if (this.variant === "_") {
                shape = xypic.Shape.LowerLTArrowheadShape(c, angle);
              } else {
                shape = xypic.Shape.LTArrowheadShape(c, angle);
              }
          }
          break;
        case "|":
          switch (this.variant) {
            case "^":
              shape = xypic.Shape.UpperColumnArrowheadShape(c, angle);
              break;
            case "_":
              shape = xypic.Shape.LowerColumnArrowheadShape(c, angle);
              break;
            case "2":
              shape = xypic.Shape.Column2ArrowheadShape(c, angle);
              break;
            case "3":
              shape = xypic.Shape.Column3ArrowheadShape(c, angle);
              break;
            default:
              shape = xypic.Shape.ColumnArrowheadShape(c, angle);
          }
          break;
        case "(":
          switch (this.variant) {
            case "^":
              shape = xypic.Shape.UpperLParenArrowheadShape(c, angle);
              break;
            case "_":
              shape = xypic.Shape.LowerLParenArrowheadShape(c, angle);
              break;
            default:
              shape = xypic.Shape.LParenArrowheadShape(c, angle);
          }
          break;
        case ")":
          switch (this.variant) {
            case "^":
              shape = xypic.Shape.UpperRParenArrowheadShape(c, angle);
              break;
            case "_":
              shape = xypic.Shape.LowerRParenArrowheadShape(c, angle);
              break;
            default:
              shape = xypic.Shape.RParenArrowheadShape(c, angle);
          }
          break;
        case "`":
          switch (this.variant) {
            case "_":
              shape = xypic.Shape.LowerBackquoteArrowheadShape(c, angle);
              break;
            case "^":
            default:
              shape = xypic.Shape.UpperBackquoteArrowheadShape(c, angle);
              break;
          }
          break;
        case "'":
          switch (this.variant) {
            case "_":
              shape = xypic.Shape.LowerQuoteArrowheadShape(c, angle);
              break;
            case "^":
            default:
              shape = xypic.Shape.UpperQuoteArrowheadShape(c, angle);
              break;
          }
          break;
        case '*':
          shape = xypic.Shape.AsteriskArrowheadShape(c, 0);
          break;
        case 'o':
          shape = xypic.Shape.OArrowheadShape(c, 0);
          break;
        case '+':
          shape = xypic.Shape.PlusArrowheadShape(c, angle);
          break;
        case 'x':
          shape = xypic.Shape.XArrowheadShape(c, angle);
          break;
        case '/':
          shape = xypic.Shape.SlashArrowheadShape(c, angle);
          break;
        case '-':
        case '--':
          var lineLen = AST.xypic.lineElementLength;
          if (this.variant === "3") {
            shape = xypic.Shape.Line3ArrowheadShape(c, angle);
          } else if (this.variant === "2") {
            shape = xypic.Shape.Line2ArrowheadShape(c, angle);
          } else {
            shape = xypic.Shape.LineArrowheadShape(c, angle);
          }
          break;
        case '=':
        case '==':
          shape = xypic.Shape.Line2ArrowheadShape(c, angle);
          break;
        case '.':
        case '..':
          if (this.variant === "3") {
            shape = xypic.Shape.Dot3ArrowheadShape(c, angle);
          } else if (this.variant === "2") {
            shape = xypic.Shape.Dot2ArrowheadShape(c, angle);
          } else {
            shape = xypic.Shape.DotArrowheadShape(c, angle);
          }
          break;
        case ':':
        case '::':
          shape = xypic.Shape.Dot2ArrowheadShape(c, angle);
          break;
        case '~':
        case '~~':
          if (this.variant === "3") {
            shape = xypic.Shape.Tilde3ArrowheadShape(c, angle);
          } else if (this.variant === "2") {
            shape = xypic.Shape.Tilde2ArrowheadShape(c, angle);
          } else {
            shape = xypic.Shape.TildeArrowheadShape(c, angle);
          }
          break;
        case '>>':
          switch (this.variant) {
            case "^":
              shape = xypic.Shape.UpperGTGTArrowheadShape(c, angle);
              break;
            case "_":
              shape = xypic.Shape.LowerGTGTArrowheadShape(c, angle);
              break;
            case "2":
              shape = xypic.Shape.GTGT2ArrowheadShape(c, angle);
              var r = shape.getRadius();
              env.c = xypic.Frame.Ellipse(c.x, c.y, r, r, r, r);
              break;
            case "3":
              shape = xypic.Shape.GTGT3ArrowheadShape(c, angle);
              var r = shape.getRadius();
              env.c = xypic.Frame.Ellipse(c.x, c.y, r, r, r, r);
              break;
            default:
              shape = xypic.Shape.GTGTArrowheadShape(c, angle);
              break;
          }
          break;
        case '<<':
          switch (this.variant) {
            case "^":
              shape = xypic.Shape.UpperLTLTArrowheadShape(c, angle);
              break;
            case "_":
              shape = xypic.Shape.LowerLTLTArrowheadShape(c, angle);
              break;
            case "2":
              shape = xypic.Shape.LTLT2ArrowheadShape(c, angle);
              var r = shape.getRadius();
              env.c = xypic.Frame.Ellipse(c.x, c.y, r, r, r, r);
              break;
            case "3":
              shape = xypic.Shape.LTLT3ArrowheadShape(c, angle);
              var r = shape.getRadius();
              env.c = xypic.Frame.Ellipse(c.x, c.y, r, r, r, r);
              break;
            default:
              shape = xypic.Shape.LTLTArrowheadShape(c, angle);
              break;
          }
          break;
        case '||':
          switch (this.variant) {
            case "^":
              shape = xypic.Shape.UpperColumnColumnArrowheadShape(c, angle);
              break;
            case "_":
              shape = xypic.Shape.LowerColumnColumnArrowheadShape(c, angle);
              break;
            case "2":
              shape = xypic.Shape.ColumnColumn2ArrowheadShape(c, angle);
              break;
            case "3":
              shape = xypic.Shape.ColumnColumn3ArrowheadShape(c, angle);
              break;
            default:
              shape = xypic.Shape.ColumnColumnArrowheadShape(c, angle);
              break;
          }
          break;
        case '|-':
          switch (this.variant) {
            case "^":
              shape = xypic.Shape.UpperColumnLineArrowheadShape(c, angle);
              break;
            case "_":
              shape = xypic.Shape.LowerColumnLineArrowheadShape(c, angle);
              break;
            case "2":
              shape = xypic.Shape.ColumnLine2ArrowheadShape(c, angle);
              break;
            case "3":
              shape = xypic.Shape.ColumnLine3ArrowheadShape(c, angle);
              break;
            default:
              shape = xypic.Shape.ColumnLineArrowheadShape(c, angle);
              break;
          }
          break;
        case '>|':
          shape = xypic.Shape.GTColumnArrowheadShape(c, angle);
          break;
        case ">>|":
          shape = xypic.Shape.GTGTColumnArrowheadShape(c, angle);
          break;
        case "|<":
          shape = xypic.Shape.ColumnLTArrowheadShape(c, angle);
          break;
        case "|<<":
          shape = xypic.Shape.ColumnLTLTArrowheadShape(c, angle);
          break;
        case "//":
          shape = xypic.Shape.SlashSlashArrowheadShape(c, angle);
          break;
        case "=>":
          shape = xypic.Shape.LineGT2ArrowheadShape(c, angle);
          break;
          
        default:
          var newdirObj = xypic.repositories.dirRepository.get(this.main);
          if (newdirObj !== undefined) {
            shape = newdirObj.toDropShape(context);
          } else {
            throw xypic.ExecutionError("\\dir " + this.variant + "{" + this.main + "} not defined.");
          }
      }
      
      context.appendShapeToFront(shape);
      return shape;
    },
    toConnectShape: function (context) {
      // 多重線の幅、点線・破線の幅の基準
      var env = context.env;
      env.originalReferencePoint = env.c;
      var t = AST.xypic.thickness;
      var s = env.p.edgePoint(env.c.x, env.c.y);
      var e = env.c.edgePoint(env.p.x, env.p.y);
      if (s.x !== e.x || s.y !== e.y) {
        var shape = xypic.Curve.Line(s, e).toShape(context, this, this.main, this.variant);
        return shape;
      } else {
        env.angle = 0;
        env.lastCurve = xypic.LastCurve.none;
        return xypic.Shape.none;
      }
    }
  });
  
  AST.ObjectBox.Curve.Augment({
    toDropShape: function (context) {
      var env = context.env;
      env.originalReferencePoint = env.c;
      return xypic.Shape.none;
    },
    toConnectShape: function (context) {
      var env = context.env;
      env.originalReferencePoint = env.c;
      // find object for drop and connect
      var objectForDrop = undefined;
      var objectForConnect = undefined;
      this.objects.foreach(function (o) {
        objectForDrop = o.objectForDrop(objectForDrop);
        objectForConnect = o.objectForConnect(objectForConnect);
      });
      if (objectForDrop === undefined && objectForConnect === undefined) {
        objectForConnect = AST.Object(FP.List.empty, AST.ObjectBox.Dir("", "-"));
      }
      
      var thickness = AST.xypic.thickness;
      
      var c = env.c;
      var p = env.p;
      var controlPoints = [];
      this.poslist.foreach(function (p) {
        p.addPositions(controlPoints, context);
//        svg.createSVGElement("circle", {
//          cx:xypic.em2px(env.c.x), cy:-xypic.em2px(env.c.y), r:xypic.em2px(thickness/2)
//        });
      });
      
      env.c = c;
      env.p = p;
      var shape = xypic.Shape.none;
      var s = p;
      var e = c;
      switch (controlPoints.length) {
        case 0:
          if (s.x === e.x && s.y === e.y) {
            env.lastCurve = xypic.LastCurve.none;
            env.angle = 0;
            return xypic.Shape.none;
          }
          if (objectForConnect !== undefined) {
            return objectForConnect.toConnectShape(context);
          } else {
            return objectForDrop.toConnectShape(context);
          }
          
        case 1:
          var origBezier = xypic.Curve.QuadBezier(s, controlPoints[0], e);
          var tOfShavedStart = origBezier.tOfShavedStart(s);
          var tOfShavedEnd = origBezier.tOfShavedEnd(e);
          if (tOfShavedStart === undefined || tOfShavedEnd === undefined || tOfShavedStart >= tOfShavedEnd) {
              env.angle = 0;
              env.lastCurve = xypic.LastCurve.none;
              return xypic.Shape.none;
          }
          shape = origBezier.toShape(context, objectForDrop, objectForConnect);
          env.lastCurve = xypic.LastCurve.QuadBezier(origBezier, tOfShavedStart, tOfShavedEnd, shape);
          env.angle = Math.atan2(e.y - s.y, e.x - s.x);
          break;
          
        case 2:
          var origBezier = xypic.Curve.CubicBezier(s, controlPoints[0], controlPoints[1], e);
          var tOfShavedStart = origBezier.tOfShavedStart(s);
          var tOfShavedEnd = origBezier.tOfShavedEnd(e);
          if (tOfShavedStart === undefined || tOfShavedEnd === undefined || tOfShavedStart >= tOfShavedEnd) {
              env.angle = 0;
              env.lastCurve = xypic.LastCurve.none;
              return xypic.Shape.none;
          }
          shape = origBezier.toShape(context, objectForDrop, objectForConnect);
          env.lastCurve = xypic.LastCurve.CubicBezier(origBezier, tOfShavedStart, tOfShavedEnd, shape);
          env.angle = Math.atan2(e.y - s.y, e.x - s.x);
          break;
          
        default:
          var spline = xypic.Curve.CubicBSpline(s, controlPoints, e);
          var origBeziers = xypic.Curve.CubicBeziers(spline.toCubicBeziers());
          var tOfShavedStart = origBeziers.tOfShavedStart(s);
          var tOfShavedEnd = origBeziers.tOfShavedEnd(e);
          if (tOfShavedStart === undefined || tOfShavedEnd === undefined || tOfShavedStart >= tOfShavedEnd) {
              env.angle = 0;
              env.lastCurve = xypic.LastCurve.none;
              return xypic.Shape.none;
          }
          shape = origBeziers.toShape(context, objectForDrop, objectForConnect);
          env.lastCurve = xypic.LastCurve.CubicBSpline(s, e, origBeziers, tOfShavedStart, tOfShavedEnd, shape);
          env.angle = Math.atan2(e.y - s.y, e.x - s.x);
          break;
      }
      
//        svg.createSVGElement("rect", {
//          x:xypic.em2px(box.x-box.l), y:xypic.em2px(-box.y-box.u), width:xypic.em2px(box.l+box.r), height:xypic.em2px(box.u+box.d),
//          "stroke-width":"0.02em", stroke:"green"
//        })
      return shape;
    }
  });
  
  AST.ObjectBox.Curve.Object.Drop.Augment({
    objectForDrop: function (object) {
      return this.object;
    },
    objectForConnect: function (object) {
      return object;
    }
  });
  
  AST.ObjectBox.Curve.Object.Connect.Augment({
    objectForDrop: function (object) {
      return object;
    },
    objectForConnect: function (object) {
      return this.object;
    }
  });
  
  AST.ObjectBox.Curve.PosList.CurPos.Augment({
    addPositions: function (controlPoints, context) {
      var env = context.env;
      controlPoints.push(env.c);
    }
  });
  
  AST.ObjectBox.Curve.PosList.Pos.Augment({
    addPositions: function (controlPoints, context) {
      var env = context.env;
      this.pos.toShape(context);
      controlPoints.push(env.c);
    }
  });
  
  AST.ObjectBox.Curve.PosList.AddStack.Augment({
    addPositions: function (controlPoints, context) {
      context.env.stack.reverse().foreach(function (p) {
        controlPoints.push(p);
      });
    }
  });
  
  AST.Coord.C.Augment({
    position: function (context) {
      return context.env.c;
    }
  });
  
  AST.Coord.P.Augment({
    position: function (context) {
      return context.env.p;
    }
  });
  
  AST.Coord.X.Augment({
    position: function (context) {
      var env = context.env;
      var p = env.p;
      var c = env.c;
      var o = env.origin;
      var b = env.xBase;
      var a0 = c.y - p.y, b0 = p.x - c.x, c0 = c.x * p.y - c.y * p.x;
      var a1 = b.y, b1 = -b.x, c1 = b.x * o.y - b.y * o.x;
      var d = a0 * b1 - a1 * b0;
      
      if (Math.abs(d) < AST.xypic.machinePrecision) {
        console.log("there is no intersection point.");
        return xypic.Env.originPosition;
      }
      var x = -(b1 * c0 - b0 * c1)/d;
      var y = (a1 * c0 - a0 * c1)/d;
      return xypic.Frame.Point(x, y);
    }
  });
  
  AST.Coord.Y.Augment({
    position: function (context) {
      var env = context.env;
      var p = env.p;
      var c = env.c;
      var o = env.origin;
      var b = env.yBase;
      var a0 = c.y - p.y, b0 = p.x - c.x, c0 = c.x * p.y - c.y * p.x;
      var a1 = b.y, b1 = -b.x, c1 = b.x * o.y - b.y * o.x;
      var d = a0 * b1 - a1 * b0;
      
      if (Math.abs(d) < AST.xypic.machinePrecision) {
        console.log("there is no intersection point.");
        return xypic.Env.originPosition;
      }
      var x = -(b1 * c0 - b0 * c1)/d;
      var y = (a1 * c0 - a0 * c1)/d;
      return xypic.Frame.Point(x, y);
    }
  });
  
  AST.Coord.Vector.Augment({
    position: function (context) {
      var xy = this.vector.xy(context);
      return xypic.Frame.Point(xy.x, xy.y);
    }
  });
  
  AST.Coord.Id.Augment({
    position: function (context) {
      return context.env.lookupPos(this.id).position(context);
    }
  });
  
  AST.Coord.Group.Augment({
    position: function (context) {
      var env = context.env;
      var origin = env.origin;
      var xBase = env.xBase;
      var yBase = env.yBase;
      var p = env.p;
      // side effect
      this.posDecor.toShape(context);
      env.p = p;
      env.origin = origin;
      env.xBase = xBase;
      env.yBase = yBase;
      return env.c;
    }
  });
  
  AST.Coord.StackPosition.Augment({
    position: function (context) {
      return context.env.stackAt(this.number);
    }
  });
  
  AST.Coord.DeltaRowColumn.Augment({
    position: function (context) {
      var env = context.env;
      var row = env.xymatrixRow;
      var col = env.xymatrixCol;
      if (row === undefined || col === undefined) {
        throw xypic.ExecutionError("xymatrix rows and columns not found for " + this.toSring());
      }
      var id = this.prefix + (row + this.dr) + "," + (col + this.dc);
      return context.env.lookupPos(id, 'in entry "' + env.xymatrixRow + "," + env.xymatrixCol + '": No ' + this + " (is " + id + ") from here.").position(context);
    }
  });
  
  AST.Coord.Hops.Augment({
    position: function (context) {
      var env = context.env;
      var row = env.xymatrixRow;
      var col = env.xymatrixCol;
      if (row === undefined || col === undefined) {
        throw xypic.ExecutionError("xymatrix rows and columns not found for " + this.toSring());
      }
      this.hops.foreach(function (hop) {
        switch (hop) {
          case 'u':
            row -= 1;
            break;
          case 'd':
            row += 1;
            break;
          case 'l':
            col -= 1;
            break;
          case 'r':
            col += 1;
            break;
        }
      });
      var id = this.prefix + row + "," + col;
      return context.env.lookupPos(id, 'in entry "' + env.xymatrixRow + "," + env.xymatrixCol + '": No ' + this + " (is " + id + ") from here.").position(context);
    }
  });
  
  AST.Coord.HopsWithPlace.Augment({
    position: function (context) {
      var env = context.env;
      var row = env.xymatrixRow;
      var col = env.xymatrixCol;
      if (row === undefined || col === undefined) {
        throw xypic.ExecutionError("xymatrix rows and columns not found for " + this.toSring());
      }
      this.hops.foreach(function (hop) {
        switch (hop) {
          case 'u':
            row -= 1;
            break;
          case 'd':
            row += 1;
            break;
          case 'l':
            col -= 1;
            break;
          case 'r':
            col += 1;
            break;
        }
      });
      var id = this.prefix + row + "," + col;
      var pos = context.env.lookupPos(id, 'in entry "' + env.xymatrixRow + "," + env.xymatrixCol + '": No ' + this + " (is " + id + ") from here.").position(context);
      var c = env.c;
      
      var tmpEnv = env.duplicate();
      tmpEnv.p = env.c;
      tmpEnv.c = pos;
      var dx = tmpEnv.c.x - tmpEnv.p.x;
      var dy = tmpEnv.c.y - tmpEnv.p.y;
      var angle;
      if (dx === 0 && dy === 0) {
        angle = 0;
      } else {
        angle = Math.atan2(dy, dx);
      }
      tmpEnv.angle = angle;
      var s = tmpEnv.p.edgePoint(tmpEnv.c.x, tmpEnv.c.y);
      var e = tmpEnv.c.edgePoint(tmpEnv.p.x, tmpEnv.p.y);
      tmpEnv.lastCurve = xypic.LastCurve.Line(s, e, tmpEnv.p, tmpEnv.c, undefined);
      var tmpContext = xypic.DrawingContext(xypic.Shape.none, tmpEnv);
      var t = this.place.toShape(tmpContext);
      
      return tmpEnv.lastCurve.position(t);
    }
  });
  
  AST.Vector.InCurBase.Augment({
    xy: function (context) {
      return context.env.absVector(this.x, this.y);
    },
    angle: function (context) {
      var xy = context.env.absVector(this.x, this.y);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Vector.Abs.Augment({
    xy: function (context) {
      return { x:xypic.length2em(this.x), y:xypic.length2em(this.y) };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Vector.Angle.Augment({
    xy: function (context) {
      var angle = Math.PI / 180 * this.degree;
      var xy = context.env.absVector(Math.cos(angle), Math.sin(angle));
      return xy;
    },
    angle: function (context) {
      return Math.PI / 180 * this.degree;
    }
  });
  
  AST.Vector.Dir.Augment({
    xy: function (context) {
      var l = xypic.length2em(this.dimen);
      var angle = this.dir.angle(context);
      return { x:l * Math.cos(angle), y:l * Math.sin(angle) };
    },
    angle: function (context) {
      return this.dir.angle(context);
    }
  });
  
  AST.Vector.Corner.Augment({
    xy: function (context) {
      var xy = this.corner.xy(context);
      return { x:xy.x*this.factor, y:xy.y*this.factor };
    },
    angle: function (context) {
      return this.corner.angle(context);
    }
  });
  
  AST.Corner.L.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:-c.l, y:0 };
    },
    angle: function (context) {
      return Math.PI;
    }
  });
  
  AST.Corner.R.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:c.r, y:0 };
    },
    angle: function (context) {
      return 0;
    }
  });
  
  AST.Corner.D.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:0, y:-c.d };
    },
    angle: function (context) {
      return -Math.PI / 2;
    }
  });
  
  AST.Corner.U.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:0, y:c.u };
    },
    angle: function (context) {
      return Math.PI / 2;
    }
  });
  
  AST.Corner.CL.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:-c.l, y:(c.u - c.d) / 2 };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.CR.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:c.r, y:(c.u - c.d) / 2 };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.CD.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:(c.r-c.l)/2, y:-c.d };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.CU.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:(c.r-c.l)/2, y:c.u };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.LU.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:-c.l, y:c.u };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.LD.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:-c.l, y:-c.d };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.RU.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:c.r, y:c.u };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.RD.Augment({
    xy: function (context) {
      var c = context.env.c;
      return { x:c.r, y:-c.d };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.NearestEdgePoint.Augment({
    xy: function (context) {
      var env = context.env;
      var c = env.c;
      var e = c.edgePoint(env.p.x, env.p.y);  
      return { x:e.x - c.x, y:e.y - c.y };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.PropEdgePoint.Augment({
    xy: function (context) {
      var env = context.env;
      var c = env.c;
      var e = c.proportionalEdgePoint(env.p.x, env.p.y);
      return { x:e.x - c.x, y:e.y - c.y };
    },
    angle: function (context) {
      var xy = this.xy(context);
      return Math.atan2(xy.y, xy.x);
    }
  });
  
  AST.Corner.Axis.Augment({
    xy: function (context) {
      return { x:0, y:AST.xypic.axisHeightLength };
    },
    angle: function (context) {
      return Math.PI / 2;
    }
  });
  
  AST.Modifier.Augment({
    proceedModifyShape: function (context, objectShape, restModifiers) {
      if (restModifiers.isEmpty) {
        return objectShape;
      } else {
        return restModifiers.head.modifyShape(context, objectShape, restModifiers.tail);
      }
    }
  });
  
  AST.Modifier.Vector.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var d = this.vector.xy(context);
      var env = context.env;
      env.c = env.c.shiftFrame(-d.x, -d.y);
      objectShape = xypic.Shape.TranslateShape(-d.x, -d.y, objectShape);
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.RestoreOriginalRefPoint.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var env = context.env;
      var origRefPoint = env.originalReferencePoint;
      if (origRefPoint !== undefined) {
        var dx = env.c.x - origRefPoint.x;
        var dy = env.c.y - origRefPoint.y;
        env.c = env.c.shiftFrame(dx, dy);
        objectShape = xypic.Shape.TranslateShape(dx, dy, objectShape);
      }
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Shape.Point.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var c = context.env.c;
      context.env.c = xypic.Frame.Point(c.x, c.y);
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Shape.Rect.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var c = context.env.c;
      context.env.c = xypic.Frame.Rect(c.x, c.y, { l:c.l, r:c.r, u:c.u, d:c.d });
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Shape.Circle.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var c = context.env.c;
      context.env.c = xypic.Frame.Ellipse(c.x, c.y, c.l, c.r, c.u, c.d);
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Shape.L.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var env = context.env;
      var c = env.c;
      if (c !== undefined) {
        var width = c.r + c.l;
        var height = c.u + c.d;
        var dx, dy;
        if (width < height) {
          dx = (c.l - c.r) / 2;
          dy = (c.d - c.u) / 2;
        } else {
          dx = -c.r + height / 2;
          dy = (c.d - c.u) / 2;
        }
        env.c = env.c.shiftFrame(dx, dy);
        objectShape = xypic.Shape.TranslateShape(dx, dy, objectShape);
      }
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Shape.R.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var env = context.env;
      var c = env.c;
      if (c !== undefined) {
        var width = c.r + c.l;
        var height = c.u + c.d;
        var dx, dy;
        if (width < height) {
          dx = (c.l - c.r) / 2;
          dy = (c.d - c.u) / 2;
        } else {
          dx = c.l - height / 2;
          dy = (c.d - c.u) / 2;
        }
        env.c = env.c.shiftFrame(dx, dy);
        objectShape = xypic.Shape.TranslateShape(dx, dy, objectShape);
      }
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Shape.U.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var env = context.env;
      var c = env.c;
      if (c !== undefined) {
        var width = c.r + c.l;
        var height = c.u + c.d;
        var dx, dy;
        if (width > height) {
          dx = (c.l - c.r) / 2;
          dy = (c.d - c.u) / 2;
        } else {
          dx = (c.l - c.r) / 2;
          dy = c.d - width / 2;
        }
        env.c = env.c.shiftFrame(dx, dy);
        objectShape = xypic.Shape.TranslateShape(dx, dy, objectShape);
      }
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Shape.D.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var env = context.env;
      var c = env.c;
      if (c !== undefined) {
        var width = c.r + c.l;
        var height = c.u + c.d;
        var dx, dy;
        if (width > height) {
          dx = (c.l - c.r) / 2;
          dy = (c.d - c.u) / 2;
        } else {
          dx = (c.l - c.r) / 2;
          dy = -c.u + width / 2;
        }
        env.c = env.c.shiftFrame(dx, dy);
        objectShape = xypic.Shape.TranslateShape(dx, dy, objectShape);
      }
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Shape.C.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var env = context.env;
      var c = env.c;
      if (c !== undefined) {
        var dx, dy;
        dx = (c.l - c.r) / 2;
        dy = (c.d - c.u) / 2;
        env.c = env.c.shiftFrame(dx, dy);
        objectShape = xypic.Shape.TranslateShape(dx, dy, objectShape);
      }
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Shape.ChangeColor.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      objectShape = this.proceedModifyShape(context, objectShape, restModifiers);
      return xypic.Shape.ChangeColorShape(this.colorName, objectShape);
    }
  });
  
  AST.Modifier.Shape.Alphabets.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
      var modifier = xypic.repositories.modifierRepository.get(this.alphabets);
      if (modifier !== undefined) {
        return modifier.preprocess(context, reversedProcessedModifiers);
      } else {
        // TODO 存在しないshape名が指定されたらエラーを発生させる。
      }
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var modifier = xypic.repositories.modifierRepository.get(this.alphabets);
      if (modifier !== undefined) {
        return modifier.modifyShape(context, objectShape, restModifiers);
      }
    }
  });
  
  AST.Modifier.Shape.DefineShape.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
      var processedModifiers = reversedProcessedModifiers.reverse();
      xypic.repositories.modifierRepository.put(this.shape, AST.Modifier.Shape.CompositeModifiers(processedModifiers));
    },
    modifyShape: function (context, objectShape, restModifiers) {
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  // ユーザ定義されたshape
  AST.Modifier.Shape.CompositeModifiers.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
      this.modifiers.foreach(function (m) {
        m.preprocess(context, reversedProcessedModifiers);
        reversedProcessedModifiers = reversedProcessedModifiers.prepend(m);
      });
    },
    modifyShape: function (context, objectShape, restModifiers) {
      objectShape = this.proceedModifyShape(context, objectShape, this.modifiers);
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Invisible.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      objectShape = this.proceedModifyShape(context, objectShape, restModifiers);
      return xypic.Shape.none;
    }
  });
  
  AST.Modifier.Hidden.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      // TODO implement hidden modifier
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.Direction.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
      context.env.angle = this.direction.angle(context);
    },
    modifyShape: function (context, objectShape, restModifiers) {
      context.env.angle = this.direction.angle(context);
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  
  AST.Modifier.AddOp.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var c = context.env.c;
      context.env.c = this.op.apply(this.size, c, context);
      context.appendShapeToFront(xypic.Shape.InvisibleBoxShape(context.env.c));
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  AST.Modifier.AddOp.Grow.Augment({
    apply: function (size, c, context) {
      var env = context.env;
      var margin = (size.isDefault?
        { x:2 * env.objectmargin, y:2 * env.objectmargin }:
        size.vector.xy(context));
      var xMargin = Math.abs(margin.x / 2);
      var yMargin = Math.abs(margin.y / 2);
      return c.grow(xMargin, yMargin);
    },
    applyToDimen: function (lhsEm, rhsEm) {
      return lhsEm + rhsEm;
    }
  });
  AST.Modifier.AddOp.Shrink.Augment({
    apply: function (size, c, context) {
      var env = context.env;
      var margin = (size.isDefault?
        { x:2 * env.objectmargin, y:2 * env.objectmargin }:
        size.vector.xy(context));
      var xMargin = -Math.abs(margin.x / 2);
      var yMargin = -Math.abs(margin.y / 2);
      return c.grow(xMargin, yMargin);
    },
    applyToDimen: function (lhsEm, rhsEm) {
      return lhsEm - rhsEm;
    }
  });
  AST.Modifier.AddOp.Set.Augment({
    apply: function (size, c, context) {
      var env = context.env;
      var margin = (size.isDefault?
        { x:env.objectwidth, y:env.objectheight }:
        size.vector.xy(context));
      var width = Math.abs(margin.x);
      var height = Math.abs(margin.y);
      return c.toSize(width, height);
    },
    applyToDimen: function (lhsEm, rhsEm) {
      return rhsEm;
    }
  });
  AST.Modifier.AddOp.GrowTo.Augment({
    apply: function (size, c, context) {
      var l = Math.max(c.l + c.r, c.u + c.d);
      var margin = (size.isDefault? { x:l, y:l } : size.vector.xy(context));
      var width = Math.abs(margin.x);
      var height = Math.abs(margin.y);
      return c.growTo(width, height);
    },
    applyToDimen: function (lhsEm, rhsEm) {
      return Math.max(Math.max(lhsEm, rhsEm), 0);
    }
  });
  AST.Modifier.AddOp.ShrinkTo.Augment({
    apply: function (size, c, context) {
      var l = Math.min(c.l + c.r, c.u + c.d);
      var margin = (size.isDefault? { x:l, y:l } : size.vector.xy(context));
      var width = Math.abs(margin.x);
      var height = Math.abs(margin.y);
      return c.shrinkTo(width, height);
    },
    applyToDimen: function (lhsEm, rhsEm) {
      return Math.max(Math.min(lhsEm, rhsEm), 0);
    }
  });
  
  AST.Modifier.Shape.Frame.Augment({
    preprocess: function (context, reversedProcessedModifiers) {
    },
    modifyShape: function (context, objectShape, restModifiers) {
      var env = context.env;
      if (env.c !== undefined) {
        var main = this.main;
        var radius = AST.ObjectBox.Frame.Radius.Default();
        var colorName = "currentColor";
        this.options.foreach(function (op) { radius = op.getRadius(radius); });
        this.options.foreach(function (op) { colorName = op.getColorName(colorName); });
        
        var dummyEnv = env.duplicate();
        var dummyContext = xypic.DrawingContext(xypic.Shape.none, dummyEnv);
        var frameObject = AST.ObjectBox.Frame(radius, this.main);
        var frameShape = frameObject.toDropFilledShape(dummyContext, colorName, env.c.isCircle());
        objectShape = xypic.Shape.CompositeShape(objectShape, frameShape);
      }
      return this.proceedModifyShape(context, objectShape, restModifiers);
    }
  });
  AST.Modifier.Shape.Frame.Radius.Augment({
    getRadius: function (radius) {
      return AST.ObjectBox.Frame.Radius.Vector(this.vector);
    },
    getColorName: function (colorName) {
      return colorName;
    }
  });
  AST.Modifier.Shape.Frame.Color.Augment({
    getRadius: function (radius) {
      return radius;
    },
    getColorName: function (colorName) {
      return this.colorName;
    }
  });
  
  AST.Direction.Compound.Augment({
    angle: function (context) {
      var angle = this.dir.angle(context);
      this.rots.foreach(function (rot) { angle = rot.rotate(angle, context); });
      return angle;
    }
  });
  
  AST.Direction.Diag.Augment({
    angle: function (context) {
      return this.diag.angle(context);
    }
  });
  
  AST.Direction.Vector.Augment({
    angle: function (context) {
      return this.vector.angle(context);
    }
  });
  
  AST.Direction.ConstructVector.Augment({
    angle: function (context) {
      var env = context.env;
      var origin = env.origin;
      var xBase = env.xBase;
      var yBase = env.yBase;
      var p = env.p;
      var c = env.c;
      // side effect
      this.posDecor.toShape(context);
      var angle = Math.atan2(env.c.y - env.p.y, env.c.x - env.p.x);
      env.c = c;
      env.p = p;
      env.origin = origin;
      env.xBase = xBase;
      env.yBase = yBase;
      return angle;
    }
  });
  
  AST.Direction.RotVector.Augment({
    rotate: function (angle, context) {
      return angle + this.vector.angle(context);
    }
  });
  
  AST.Direction.RotCW.Augment({
    rotate: function (angle, context) {
      return angle + Math.PI /2;
    }
  });
  
  AST.Direction.RotAntiCW.Augment({
    rotate: function (angle, context) {
      return angle - Math.PI / 2;
    }
  });
  
  AST.Diag.Default.Augment({
    isEmpty: true,
    angle: function (context) {
      return context.env.angle;
    }
  });
    
  AST.Diag.Angle.Augment({
    isEmpty: false,
    angle: function (context) {
      return this.ang;
    }
  });
  
  AST.Decor.Augment({
    toShape: function (context) {
      this.commands.foreach(function (c) {
        c.toShape(context);
      });
    }
  });
  
  AST.Command.Save.Augment({
    toShape: function (context) {
      context.env.saveState();
      this.pos.toShape(context);
    }
  });
  
  AST.Command.Restore.Augment({
    toShape: function (context) {
      context.env.restoreState();
    }
  });
  
  AST.Command.Pos.Augment({
    toShape: function (context) {
      this.pos.toShape(context);
    }
  });
  
  AST.Command.AfterPos.Augment({
    toShape: function (context) {
      this.pos.toShape(context);
      this.decor.toShape(context);
    }
  });
  
  AST.Command.Drop.Augment({
    toShape: function (context) {
      this.object.toDropShape(context);
    }
  });
  
  AST.Command.Connect.Augment({
    toShape: function (context) {
      this.object.toConnectShape(context);
    }
  });
  
  AST.Command.Relax.Augment({
    toShape: function (context) {
      // do nothing
    }
  });
  
  AST.Command.Ignore.Augment({
    toShape: function (context) {
      // do nothing
    }
  });
  
  AST.Command.ShowAST.Augment({
    toShape: function (context) {
      console.log(this.pos.toString() + " " + this.decor);
    }
  });
  
  AST.Command.Ar.Augment({
    toShape: function (context) {
      var env = context.env;
      var origin = env.origin;
      var xBase = env.xBase;
      var yBase = env.yBase;
      var p = env.p;
      var c = env.c;
      
      env.pathActionForBeforeSegment = FP.Option.empty;
      env.pathActionForAfterSegment = FP.Option.empty;
      env.labelsForNextSegmentOnly = FP.Option.empty;
      env.labelsForLastSegmentOnly = FP.Option.empty;
      env.labelsForEverySegment = FP.Option.empty;
      env.segmentSlideEm = FP.Option.empty;
      env.lastTurnDiag = FP.Option.empty;
      
      env.arrowVariant = "";
      env.tailTip = AST.Command.Ar.Form.Tip.Tipchars("");
      env.headTip = AST.Command.Ar.Form.Tip.Tipchars(">");
      env.stemConn = AST.Command.Ar.Form.Conn.Connchars("-");
      env.reverseAboveAndBelow = false;
      env.arrowObjectModifiers = FP.List.empty;
      
      this.forms.foreach(function (f) { f.toShape(context); });
      
      if (!env.pathActionForBeforeSegment.isDefined) {
      // the following AST means **\dir{stem}.
        env.pathActionForBeforeSegment = FP.Option.Some(
          AST.PosDecor(
            AST.Pos.Coord(
              AST.Coord.C(),
              FP.List.empty.append(
                AST.Pos.ConnectObject(
                  AST.Object(
                    env.arrowObjectModifiers, 
                    env.stemConn.getObject(context)
                  )
                )
              )
            ),
            AST.Decor(FP.List.empty)
          )
        );
      }
      
      env.labelsForNextSegmentOnly = FP.Option.Some(
        AST.Command.Path.Labels(
          FP.List.empty.append(
            AST.Command.Path.Label.At(
              AST.Pos.Place(AST.Place(1, 1, AST.Place.Factor(0), AST.Slide(FP.Option.empty))),
              env.tailTip.getObject(context),
              FP.Option.empty
            )
          )
        )
      );
      
      // arrow head
      env.labelsForLastSegmentOnly = FP.Option.Some(
        AST.Command.Path.Labels(
          FP.List.empty.append(
            AST.Command.Path.Label.At(
              AST.Pos.Place(AST.Place(1, 1, AST.Place.Factor(1), AST.Slide(FP.Option.empty))),
              env.headTip.getObject(context),
              FP.Option.empty
            )
          )
        )
      );
      
      this.path.toShape(context);
      
      env.c = c;
      env.p = p;
      env.origin = origin;
      env.xBase = xBase;
      env.yBase = yBase;
    }
  });
  
  AST.Command.Ar.Form.BuildArrow.Augment({
    toShape: function (context) {
      var env = context.env;
      env.arrowVariant = this.variant;
      env.tailTip = this.tailTip;
      env.stemConn = this.stemConn;
      env.headTip = this.headTip;
    }
  });
  
  AST.Command.Ar.Form.ChangeVariant.Augment({
    toShape: function (context) {
      var env = context.env;
      env.arrowVariant = this.variant;
    }
  });
  
  AST.Command.Ar.Form.ChangeStem.Augment({
    toShape: function (context) {
      var env = context.env;
      env.stemConn = AST.Command.Ar.Form.Conn.Connchars(this.connchar);
    }
  });
  
  AST.Command.Ar.Form.DashArrowStem.Augment({
    toShape: function (context) {
      // TODO impl
    }
  });
  
  AST.Command.Ar.Form.CurveArrow.Augment({
    toShape: function (context) {
      var env = context.env;
      var cpDist = xypic.em2length(xypic.length2em(this.dist) * 2);
      // the following AST means **\crv{{**@{} ?+/d 2l/}}. too long...
      env.pathActionForBeforeSegment = FP.Option.Some(
        AST.PosDecor(
          AST.Pos.Coord(
            AST.Coord.C(),
            FP.List.empty.append(
              AST.Pos.ConnectObject(AST.Object(env.arrowObjectModifiers, AST.ObjectBox.Curve(
                FP.List.empty,
                FP.List.empty.append(
                  AST.ObjectBox.Curve.Object.Connect(
                    env.stemConn.getObject(context)
                  )
                ),
                FP.List.empty.append(
                  AST.ObjectBox.Curve.PosList.Pos(
                    AST.Pos.Coord(
                      AST.Coord.Group(
                        AST.PosDecor(
                          AST.Pos.Coord(
                            AST.Coord.C(),
                            FP.List.empty.append(
                              AST.Pos.ConnectObject(
                                AST.Object(
                                  FP.List.empty,
                                  AST.ObjectBox.Dir("", "")
                                )
                              )
                            ).append(
                              AST.Pos.Place(
                                AST.Place(0, 0, undefined, AST.Slide(FP.Option.empty))
                              )
                            ).append(
                              AST.Pos.Plus(
                                AST.Coord.Vector(
                                  AST.Vector.Dir(this.direction, cpDist)
                                )
                              )
                            )
                          ),
                          AST.Decor(FP.List.empty)
                        )
                      ),
                      FP.List.empty
                    )
                  )
                )
              )))
            )
          ),
          AST.Decor(FP.List.empty)
        )
      );
    }
  });
  
  AST.Command.Ar.Form.CurveFitToDirection.Augment({
    toShape: function (context) {
      // the following AST means **\crv{;+/outdir 3pc/ & ;+/indir 3pc/}.
      var env = context.env;
      env.pathActionForBeforeSegment = FP.Option.Some(
        AST.PosDecor(
          AST.Pos.Coord(
            AST.Coord.C(),
            FP.List.empty.append(
              AST.Pos.ConnectObject(AST.Object(env.arrowObjectModifiers, AST.ObjectBox.Curve(
                FP.List.empty,
                FP.List.empty.append(
                  AST.ObjectBox.Curve.Object.Connect(
                    env.stemConn.getObject(context)
                  )
                ),
                FP.List.empty.append(
                  AST.ObjectBox.Curve.PosList.Pos(
                    AST.Pos.Coord(
                      AST.Coord.C(),
                      FP.List.empty.append(
                        AST.Pos.SwapPAndC(
                          AST.Coord.C()
                        )
                      ).append(
                        AST.Pos.Plus(
                          AST.Coord.Vector(AST.Vector.Dir(this.outDirection, "3pc"))
                        )
                      )
                    )
                  )
                ).append(
                  AST.ObjectBox.Curve.PosList.Pos(
                    AST.Pos.Coord(
                      AST.Coord.C(),
                      FP.List.empty.append(
                        AST.Pos.SwapPAndC(
                          AST.Coord.C()
                        )
                      ).append(
                        AST.Pos.Plus(
                          AST.Coord.Vector(AST.Vector.Dir(this.inDirection, "3pc"))
                        )
                      )
                    )
                  )
                )
              )))
            )
          ),
          AST.Decor(FP.List.empty)
        )
      );
    }
  });
  
  AST.Command.Ar.Form.CurveWithControlPoints.Augment({
    toShape: function (context) {
      var env = context.env;
      tmpEnv = env.duplicate();
      tmpEnv.startCapturePositions();
      var tmpContext = xypic.DrawingContext(xypic.Shape.none, tmpEnv);
      this.coord.position(tmpContext);
      var positions = tmpEnv.endCapturePositions();
      positions = positions.append(tmpEnv.c);
      
      var points = FP.List.empty;
      positions.reverse().foreach(function (pos) {
        var xy = env.inverseAbsVector(pos.x, pos.y);
        points = points.prepend(AST.ObjectBox.Curve.PosList.Pos(
                    AST.Pos.Coord(
                      AST.Coord.Vector(AST.Vector.InCurBase(xy.x, xy.y)),
                      FP.List.empty
                    )
                  ));
      });
      
      // the following AST means **\crv{ control points }.
      env.pathActionForBeforeSegment = FP.Option.Some(
        AST.PosDecor(
          AST.Pos.Coord(
            AST.Coord.C(),
            FP.List.empty.append(
              AST.Pos.ConnectObject(AST.Object(env.arrowObjectModifiers, AST.ObjectBox.Curve(
                FP.List.empty,
                FP.List.empty.append(
                  AST.ObjectBox.Curve.Object.Connect(
                    env.stemConn.getObject(context)
                  )
                ),
                points
              )))
            )
          ),
          AST.Decor(FP.List.empty)
        )
      );
    }
  });
  
  AST.Command.Ar.Form.AddShape.Augment({
    toShape: function (context) {
      context.env.arrowObjectModifiers = FP.List.empty.append(this.shape);
    }
  });
  
  AST.Command.Ar.Form.AddModifiers.Augment({
    toShape: function (context) {
      context.env.arrowObjectModifiers = this.modifiers;
    }
  });
  
  AST.Command.Ar.Form.Slide.Augment({
    toShape: function (context) {
      context.env.segmentSlideEm = FP.Option.Some(xypic.length2em(this.slideDimen));
    }
  });
  
  AST.Command.Ar.Form.LabelAt.Augment({
    toShape: function (context) {
      var env = context.env;
      env.labelsForEverySegment = FP.Option.Some(
        AST.Command.Path.Labels(
          FP.List.empty.append(
            AST.Command.Path.Label.At(
              AST.Pos.Place(this.anchor), this.it, FP.Option.empty
            )
          )
        )
      );
    }
  });
  
  AST.Command.Ar.Form.LabelAbove.Augment({
    toShape: function (context) {
      var env = context.env;
      var label;
      if (env.reverseAboveAndBelow) {
        label = AST.Command.Path.Label.Below(
              AST.Pos.Place(this.anchor), this.it, FP.Option.empty
            );
      } else {
        label = AST.Command.Path.Label.Above(
              AST.Pos.Place(this.anchor), this.it, FP.Option.empty
            );
      }
      env.labelsForEverySegment = FP.Option.Some(
        AST.Command.Path.Labels(FP.List.empty.append(label))
      );
    }
  });
  
  AST.Command.Ar.Form.LabelBelow.Augment({
    toShape: function (context) {
      var env = context.env;
      var label;
      if (env.reverseAboveAndBelow) {
        label = AST.Command.Path.Label.Above(
              AST.Pos.Place(this.anchor), this.it, FP.Option.empty
            );
      } else {
        label = AST.Command.Path.Label.Below(
              AST.Pos.Place(this.anchor), this.it, FP.Option.empty
            );
      }
      env.labelsForEverySegment = FP.Option.Some(
        AST.Command.Path.Labels(FP.List.empty.append(label))
      );
    }
  });
  
  AST.Command.Ar.Form.ReverseAboveAndBelow.Augment({
    toShape: function (context) {
      context.env.reverseAboveAndBelow = true;
    }
  });
  
  AST.Command.Ar.Form.Conn.Connchars.Augment({
    getObject: function (context) {
      var env = context.env;
      var dir = AST.ObjectBox.Dir(env.arrowVariant, this.connchars);
      return AST.Object(env.arrowObjectModifiers, dir);
    }
  });
  
  AST.Command.Ar.Form.Conn.Object.Augment({
    getObject: function (context) {
      var modifiers = context.env.arrowObjectModifiers.concat(this.object.modifiers);
      return AST.Object(modifiers, this.object.object);
    }
  });
  
  AST.Command.Ar.Form.Conn.Dir.Augment({
    getObject: function (context) {
      var env = context.env;
      var thisDir = this.dir;
      var dir = thisDir;
      if (thisDir.variant === "" && env.arrowVariant !== "") {
        dir = AST.ObjectBox.Dir(env.arrowVariant, thisDir.main);
      }
      return AST.Object(env.arrowObjectModifiers, dir);
    }
  });
  
  AST.Command.Ar.Form.Tip.Tipchars.Augment({
    getObject: function (context) {
      var env = context.env;
      var dir = AST.ObjectBox.Dir(env.arrowVariant, this.tipchars);
      return AST.Object(env.arrowObjectModifiers, dir);
    }
  });
  
  AST.Command.Ar.Form.Tip.Object.Augment({
    getObject: function (context) {
      var modifiers = context.env.arrowObjectModifiers.concat(this.object.modifiers);
      return AST.Object(modifiers, this.object.object);
    }
  });
  
  AST.Command.Ar.Form.Tip.Dir.Augment({
    getObject: function (context) {
      var env = context.env;
      var thisDir = this.dir;
      var dir = thisDir;
      if (thisDir.variant === "" && env.arrowVariant !== "") {
        dir = AST.ObjectBox.Dir(env.arrowVariant, thisDir.main);
      }
      return AST.Object(env.arrowObjectModifiers, dir);
    }
  });
  
  
  
  AST.Command.Path.Augment({
    toShape: function (context) {
      var env = context.env;
      var origin = env.origin;
      var xBase = env.xBase;
      var yBase = env.yBase;
      var p = env.p;
      var c = env.c;
      
      env.pathActionForBeforeSegment = FP.Option.empty;
      env.pathActionForAfterSegment = FP.Option.empty;
      env.labelsForNextSegmentOnly = FP.Option.empty;
      env.labelsForLastSegmentOnly = FP.Option.empty;
      env.labelsForEverySegment = FP.Option.empty;
      env.segmentSlideEm = FP.Option.empty;
      env.lastTurnDiag = FP.Option.empty;
      
      this.path.toShape(context);
      
      env.c = c;
      env.p = p;
      env.origin = origin;
      env.xBase = xBase;
      env.yBase = yBase;
    }
  });
  
  AST.Command.AfterPath.Augment({
    toShape: function (context) {
      this.path.toShape(context);
      this.decor.toShape(context);
    }
  });
  
  AST.Command.Path.Path.Augment({
    toShape: function (context) {
      this.pathElements.foreach(function (e) {
        e.toShape(context);
      });
    }
  });
  
  AST.Command.Path.SetBeforeAction.Augment({
    toShape: function (context) {
      context.env.pathActionForBeforeSegment = FP.Option.Some(this.posDecor);
    }
  });
  
  AST.Command.Path.SetAfterAction.Augment({
    toShape: function (context) {
      context.env.pathActionForAfterSegment = FP.Option.Some(this.posDecor);
    }
  });
  
  AST.Command.Path.AddLabelNextSegmentOnly.Augment({
    toShape: function (context) {
      context.env.labelsForNextSegmentOnly = FP.Option.Some(this.labels);
    }
  });
  
  AST.Command.Path.AddLabelLastSegmentOnly.Augment({
    toShape: function (context) {
      context.env.labelsForLastSegmentOnly = FP.Option.Some(this.labels);
    }
  });
  
  AST.Command.Path.AddLabelEverySegment.Augment({
    toShape: function (context) {
      context.env.labelsForEverySegment = FP.Option.Some(this.labels);
    }
  });
  
  AST.Command.Path.StraightSegment.Augment({
    toShape: function (context) {
      var env = context.env;
      this.segment.setupPositions(context);
      var c = env.c;
      env.pathActionForBeforeSegment.foreach(function (action) {
        action.toShape(context);
      });
      env.labelsForNextSegmentOnly.foreach(function (labels) {
        labels.toShape(context);
        env.labelsForNextSegmentOnly = FP.Option.empty;
      });
      env.labelsForEverySegment.foreach(function (labels) {
        labels.toShape(context);
      });
      env.c = c;
      env.pathActionForAfterSegment.foreach(function (action) {
        action.toShape(context);
      });
      this.segment.toLabelsShape(context);
    }
  });
  
  AST.Command.Path.LastSegment.Augment({
    toShape: function (context) {
      var env = context.env;
      this.segment.setupPositions(context);
      var c = env.c;
      env.pathActionForBeforeSegment.foreach(function (action) {
        action.toShape(context);
      });
      env.labelsForNextSegmentOnly.foreach(function (labels) {
        labels.toShape(context);
        env.labelsForNextSegmentOnly = FP.Option.empty;
      });
      env.labelsForLastSegmentOnly.foreach(function (labels) {
        labels.toShape(context);
        env.labelsForNextSegmentOnly = FP.Option.empty;
      });
      env.labelsForEverySegment.foreach(function (labels) {
        labels.toShape(context);
      });
      env.c = c;
      env.pathActionForAfterSegment.foreach(function (action) {
        action.toShape(context);
      });
      this.segment.toLabelsShape(context);
    }
  });
  
  AST.Command.Path.TurningSegment.Augment({
    toShape: function (context) {
      var env = context.env;
      var p = env.c;
      this.segment.pos.toShape(context);
      env.p = p;
      var circle = this.turn.explicitizedCircle(context);
      var r = this.turn.radius.radius(context);
      env.lastTurnDiag = FP.Option.Some(circle.endDiag);
      
      var sv = circle.startVector(context);
      var ev = circle.endVector(context);
      
      var slideEm = env.segmentSlideEm.getOrElse(0);
      this.segment.slide.dimen.foreach(function (d) {
        slideEm = xypic.length2em(d);
        env.segmentSlideEm = FP.Option.Some(slideEm);
      });
      if (slideEm !== 0) {
        env.p = env.p.move(
          env.p.x - slideEm * sv.y,
          env.p.y + slideEm * sv.x);
        env.c = env.c.move(
          env.c.x - slideEm * ev.y,
          env.c.y + slideEm * ev.x);
        if (circle.orient === "^") {
          r = Math.max(0, r - slideEm);
        } else {
          r = Math.max(0, r + slideEm);
        }
      }
      
      var s = env.p.edgePoint(env.p.x + sv.x, env.p.y + sv.y);
      var e = env.c;
      
      var ds = circle.relativeStartPoint(context, r);
      var de = circle.relativeEndPoint(context, r);
      var deo = circle.relativeEndPoint(context, r + (circle.orient === "^"? slideEm : -slideEm));
      
      var t;
      var det = sv.x * ev.y - sv.y * ev.x;
      if (Math.abs(det) < AST.xypic.machinePrecision) {
        t = 0;
      } else {
        var dx = e.x - s.x + ds.x - de.x;
        var dy = e.y - s.y + ds.y - de.y;
        t = (ev.y * dx - ev.x * dy)/det;
        if (t < 0) { t = 0; }
      }
      var x = s.x - ds.x + t * sv.x;
      var y = s.y - ds.y + t * sv.y;
      
      var circleShape = circle.toDropShape(context, x, y, r);
      
      var c = xypic.Frame.Point(x + deo.x, y + deo.y);
      
      env.c = xypic.Frame.Point(x + ds.x, y + ds.y);
      env.pathActionForBeforeSegment.foreach(function (action) {
        action.toShape(context);
      });
      env.labelsForNextSegmentOnly.foreach(function (labels) {
        labels.toShape(context);
        env.labelsForNextSegmentOnly = FP.Option.empty;
      });
      env.labelsForEverySegment.foreach(function (labels) {
        labels.toShape(context);
      });
      env.c = c;
      env.pathActionForAfterSegment.foreach(function (action) {
        action.toShape(context);
      });
      
      this.segment.toLabelsShape(context);
    }
  });
  
  AST.Command.Path.Turn.Cir.Augment({
    explicitizedCircle: function (context) {
      var env = context.env;
      var startDiag, orient, endDiag;
      if (this.cir.startDiag.isEmpty) {
        startDiag = env.lastTurnDiag.getOrElse(AST.Diag.R());
      } else {
        startDiag = this.cir.startDiag;
      }
      orient = this.cir.orient;
      if (this.cir.endDiag.isEmpty) {
        endDiag = startDiag.turn(orient);
      } else {
        endDiag = this.cir.endDiag;
      }
      return AST.ObjectBox.Cir.Cir.Segment(startDiag, orient, endDiag);
    }
  });
  
  AST.ObjectBox.Cir.Cir.Segment.Augment({
    startVector: function (context) {
      var angle = this.startDiag.angle(context);
      return { x:Math.cos(angle), y:Math.sin(angle) };
    },
    endVector: function (context) {
      var angle = this.endDiag.angle(context);
      return { x:Math.cos(angle), y:Math.sin(angle) };
    },
    relativeStartPointAngle: function (context) {
      return this.startPointDegree(context) / 180 * Math.PI;
    },
    relativeStartPoint: function (context, r) {
      var angle = this.startPointDegree(context) / 180 * Math.PI;
      return { x:r * Math.cos(angle), y:r * Math.sin(angle) };
    },
    relativeEndPoint: function (context, r) {
      var angle;
      angle = this.endPointDegree(context, this.relativeStartPointAngle(context)) / 180 * Math.PI;
      return { x:r * Math.cos(angle), y:r * Math.sin(angle) };
    }
  });
  
  AST.Command.Path.Turn.Diag.Augment({
    explicitizedCircle: function (context) {
      var env = context.env;
      var startDiag, orient, endDiag;
      if (this.diag.isEmpty) {
        startDiag = env.lastTurnDiag.getOrElse(AST.Diag.R());
      } else {
        startDiag = this.diag;
      }
      var angle = startDiag.angle(context);
      var det = (env.c.x - env.p.x) * Math.sin(angle) - (env.c.y - env.p.y) * Math.cos(angle);
      orient = (det < 0? "^" : "_");
      endDiag = startDiag.turn(orient);
      return AST.ObjectBox.Cir.Cir.Segment(startDiag, orient, endDiag);
    }
  });
  
  AST.Command.Path.TurnRadius.Default.Augment({
    radius: function (context) {
      return AST.xypic.turnradius;
    }
  });
  
  AST.Command.Path.TurnRadius.Dimen.Augment({
    radius: function (context) {
      return xypic.length2em(this.dimen);
    }
  });
  
  AST.Command.Path.Segment.Augment({
    setupPositions: function (context) {
      var env = context.env;
      env.p = env.c;
      this.pos.toShape(context);
      var p = env.p;
      var c = env.c;
      
      var tx = c.x - p.x;
      var ty = c.y - p.y;
      var angle = Math.atan2(ty, tx) + Math.PI / 2;
      var slideEm = env.segmentSlideEm.getOrElse(0);
      this.slide.dimen.foreach(function (d) {
        slideEm = xypic.length2em(d);
        env.segmentSlideEm = FP.Option.Some(slideEm);
      });
      if (slideEm !== 0) {
        p = p.move(p.x + slideEm * Math.cos(angle), p.y + slideEm * Math.sin(angle));
        c = c.move(c.x + slideEm * Math.cos(angle), c.y + slideEm * Math.sin(angle));
      }
      
      env.p = p;
      env.c = c;
    },
    toLabelsShape: function (context) {
      var env = context.env;
      var c = env.c, p = env.p;
      this.labels.toShape(context);
      env.c = c;
      env.p = p;
    }
  });
  
  AST.Command.Path.Labels.Augment({
    toShape: function (context) {
      this.labels.foreach(function (label) {
        label.toShape(context);
      });
    }
  });
  
  AST.Command.Path.Label.Augment({
    toShape: function (context) {
      var env = context.env;
      var p = env.p;
      var c = env.c;
      var t = this.anchor.toShape(context);
      var labelmargin = this.getLabelMargin(context);
      if (labelmargin !== 0) {
        var lastCurve = env.lastCurve;
        var angle;
        if (!lastCurve.isNone) {
          angle = lastCurve.angle(t) + Math.PI/2 + (labelmargin > 0? 0 : Math.PI);
        } else {
          angle = Math.atan2(c.y - p.y, c.x - p.x) + Math.PI/2;
        }
        var c = env.c;
        var subcontext = xypic.DrawingContext(xypic.Shape.none, env);
        this.it.toDropShape(subcontext);
        var labelShape = subcontext.shape;
        var bbox = labelShape.getBoundingBox();
        if (bbox !== undefined) {
          var x = bbox.x - c.x;
          var y = bbox.y - c.y;
          var l = bbox.l;
          var r = bbox.r;
          var u = bbox.u;
          var d = bbox.d;
          
          var cos = Math.cos(angle);
          var sin = Math.sin(angle);
          var delta = Math.min(
            (x - l) * cos + (y - d) * sin,
            (x - l) * cos + (y + u) * sin,
            (x + r) * cos + (y - d) * sin,
            (x + r) * cos + (y + u) * sin
          );
          var margin = Math.abs(labelmargin) - delta;
          env.c = env.c.move(c.x + margin * cos, c.y + margin * sin);
          context.appendShapeToFront(xypic.Shape.TranslateShape(margin * cos, margin * sin, labelShape));
        }
      } else {
        this.it.toDropShape(context);
      }
      var lastCurve = env.lastCurve;
      
      if (this.shouldSliceHole && lastCurve.isDefined && t !== undefined) {
        lastCurve.sliceHole(env.c, t);
      }
      this.aliasOption.foreach(function (alias) {
        env.savePos(alias, xypic.Saving.Position(env.c));
      });
    }
  });
  
  AST.Command.Path.Label.Above.Augment({
    getLabelMargin: function (context) {
      return context.env.labelmargin;
    },
    shouldSliceHole: false
  });
  
  AST.Command.Path.Label.Below.Augment({
    getLabelMargin: function (context) {
      return -context.env.labelmargin;
    },
    shouldSliceHole: false
  });
  
  AST.Command.Path.Label.At.Augment({
    getLabelMargin: function (context) {
      return 0;
    },
    shouldSliceHole: true
  });
  
  AST.Command.Xymatrix.Augment({
    toShape: function (context) {
      var origEnv = context.env;
      if (origEnv.c === undefined) {
        return xypic.Shape.none;
      }
      
      var subEnv = origEnv.duplicate();
      var subcontext = xypic.DrawingContext(xypic.Shape.none, subEnv);
      subEnv.xymatrixPrefix = "";
      subEnv.xymatrixRowSepEm = xypic.length2em("2pc");
      subEnv.xymatrixColSepEm = xypic.length2em("2pc");
      subEnv.xymatrixPretendEntryHeight = FP.Option.empty;
      subEnv.xymatrixPretendEntryWidth = FP.Option.empty;
      subEnv.xymatrixFixedRow = false;
      subEnv.xymatrixFixedCol = false;
      subEnv.xymatrixOrientationAngle = 0;
      subEnv.xymatrixEntryModifiers = FP.List.empty;
      
      this.setup.foreach(function (sw) { sw.toShape(subcontext); });
      
      var orientation = subEnv.xymatrixOrientationAngle;
      
      var rowCount;
      var columnCount = 0;
      var rownum = 0, colnum;
      var matrix = xypic.Xymatrix(
        this.rows.map(function (row) {
          rownum += 1;
          colnum = 0;
          var rowModel = xypic.Xymatrix.Row(
            row.entries.map(function (entry) {
              colnum += 1;
              var localEnv = subEnv.duplicate();
              localEnv.origin = {x:0, y:0};
              localEnv.p = localEnv.c = xypic.Env.originPosition;
              localEnv.angle = 0;
              localEnv.lastCurve = xypic.LastCurve.none;
              localEnv.xymatrixRow = rownum; // = \the\Row
              localEnv.xymatrixCol = colnum; // = \the\Col
              var localContext = xypic.DrawingContext(xypic.Shape.none, localEnv);
              var shape = entry.toShape(localContext);
              var c = localEnv.c;
              var l, r, u, d;
              if (subEnv.xymatrixPretendEntryHeight.isDefined) {
                var h = subEnv.xymatrixPretendEntryHeight.get;
                u = h / 2;
                d = h / 2;
              } else {
                u = c.u;
                d = c.d;
              }
              if (subEnv.xymatrixPretendEntryWidth.isDefined) {
                var w = subEnv.xymatrixPretendEntryWidth.get;
                l = w / 2;
                r = w / 2;
              } else {
                l = c.l;
                r = c.r;
              }
              var frame = xypic.Frame.Rect(0, 0, { l:l, r:r, u:u, d:d });
              return xypic.Xymatrix.Entry(localEnv.c, shape, entry.decor, frame);
            }),
            orientation
          );
          columnCount = Math.max(columnCount, colnum);
          return rowModel;
        }),
        orientation
      );
      rowCount = rownum;
      
      if (rowCount === 0) {
        return xypic.Shape.none;
      }
      
      var colnum;
      matrix.rows.foreach(function (row) {
        colnum = 0;
        row.entries.foreach (function (entry) {
          colnum += 1;
          var column = matrix.getColumn(colnum);
          column.addEntry(entry);
        });
      });
      
      /*
      console.log(matrix.toString());
      
      var rownum = 0;
      matrix.rows.foreach(function (row) {
        rownum += 1;
        console.log("row[" + rownum + "] #" + row.entries.length() + " u:" + row.getU() + ", d:" + row.getD());
      })
      var colnum = 0;
      matrix.columns.foreach(function (col) {
        colnum += 1;
        console.log("column[" + colnum + "] #" + col.entries.length() + " l:" + col.getL() + ", r:" + col.getR());
      })
      */
      
      var colsep = subEnv.xymatrixColSepEm;
      var xs = [];
      var x = origEnv.c.x;
      xs.push(x);
      if (subEnv.xymatrixFixedCol) {
        var maxL = 0;
        var maxR = 0;
        matrix.columns.foreach(function (col) {
          maxL = Math.max(maxL, col.getL());
          maxR = Math.max(maxR, col.getR());
        });
        matrix.columns.tail.foreach(function (col) {
          x = x + maxR + colsep + maxL;
          xs.push(x);
        });
        l = maxL;
        r = xs[xs.length - 1] + maxR;
      } else {
        var prevCol = matrix.columns.head;
        matrix.columns.tail.foreach(function (col) {
          x = x + prevCol.getR() + colsep + col.getL();
          xs.push(x);
          prevCol = col;
        });
        l = matrix.columns.head.getL();
        r = x + matrix.columns.at(columnCount - 1).getR() - xs[0];
      }
      
      var rowsep = subEnv.xymatrixRowSepEm;
      var ys = [];
      var y = origEnv.c.y;
      ys.push(y);
      if (subEnv.xymatrixFixedRow) {
        var maxU = 0;
        var maxD = 0;
        matrix.rows.foreach(function (row) {
          maxU = Math.max(maxU, row.getU());
          maxD = Math.max(maxD, row.getD());
        });
        matrix.rows.tail.foreach(function (row) {
          y = y - (maxD + rowsep + maxU);
          ys.push(y);
        });
        u = maxU;
        d = ys[0] - ys[ys.length - 1] + maxD;
      } else {
        var prevRow = matrix.rows.head;
        matrix.rows.tail.foreach(function (row) {
          y = y - (prevRow.getD() + rowsep + row.getU());
          ys.push(y);
          prevRow = row;
        });
        u = matrix.rows.head.getU();
        d = ys[0] - y + matrix.rows.at(rowCount - 1).getD();
      }
      origEnv.c = xypic.Frame.Rect(origEnv.c.x, origEnv.c.y, { l:l, r:r, u:u, d:d });
      
      var prefix = subEnv.xymatrixPrefix;
      var cos = Math.cos(orientation);
      var sin = Math.sin(orientation);
      var rowIndex = 0;
      matrix.rows.foreach(function (row) {
        colIndex = 0;
        row.entries.foreach (function (entry) {
          var x0 = xs[colIndex];
          var y0 = ys[rowIndex];
          var x = x0 * cos - y0 * sin;
          var y = x0 * sin + y0 * cos;
          var colnum = colIndex + 1;
          var rownum = rowIndex + 1;
          var pos = xypic.Saving.Position(entry.c.move(x, y));
          subEnv.savePos("" + rownum + "," + colnum, pos);
          subEnv.savePos(prefix + rownum + "," + colnum, pos);
          colIndex += 1;
        });
        rowIndex += 1;
      });
      
      subcontext = xypic.DrawingContext(xypic.Shape.none, subEnv);
      var rowIndex = 0;
      matrix.rows.foreach(function (row) {
        colIndex = 0;
        row.entries.foreach (function (entry) {
          var x0 = xs[colIndex];
          var y0 = ys[rowIndex];
          var x = x0 * cos - y0 * sin;
          var y = x0 * sin + y0 * cos;
          var colnum = colIndex + 1;
          var rownum = rowIndex + 1;
          var objectShape = xypic.Shape.TranslateShape(x, y, entry.objectShape);
          subcontext.appendShapeToFront(objectShape);
          // draw decor
          subEnv.c = entry.c.move(x, y);
          subEnv.xymatrixRow = rownum; // = \the\Row
          subEnv.xymatrixCol = colnum; // = \the\Col
          entry.decor.toShape(subcontext);
          colIndex += 1;
        });
        rowIndex += 1;
      });
      var matrixShape = subcontext.shape;
      context.appendShapeToFront(matrixShape);
      origEnv.savedPosition = subEnv.savedPosition;
      
      return matrixShape;
    }
  });
  
  // xymatrix data models
  xypic.Xymatrix = MathJax.Object.Subclass({
    Init: function (rows, orientation) {
      this.rows = rows;
      this.columns = FP.List.empty;
      this.orientation = orientation;
    },
    getColumn: function (colnum /* >= 1 */) {
      if (this.columns.length() >= colnum) {
        return this.columns.at(colnum - 1);
      } else {
        var column = xypic.Xymatrix.Column(this.orientation);
        this.columns = this.columns.append(column);
        return column;
      }
    },
    toString: function () {
      return "Xymatrix{\n" + this.rows.mkString("\\\\\n") + "\n}";
    }
  });
  xypic.Xymatrix.Row = MathJax.Object.Subclass({
    Init: function (entries, orientation) {
      this.entries = entries;
      this.orientation = orientation;
      memoize(this, "getU");
      memoize(this, "getD");
    },
    getU: function () {
      var orientation = this.orientation;
      var maxU = 0;
      this.entries.foreach(function (e) { maxU = Math.max(maxU, e.getU(orientation)); })
      return maxU;
    },
    getD: function () {
      var orientation = this.orientation;
      var maxD = 0;
      this.entries.foreach(function (e) { maxD = Math.max(maxD, e.getD(orientation)); })
      return maxD;
    },
    toString: function () {
      return this.entries.mkString(" & ");
    }
  });
  xypic.Xymatrix.Column = MathJax.Object.Subclass({
    Init: function (orientation) {
      this.entries = FP.List.empty;
      this.orientation = orientation;
      memoize(this, "getL");
      memoize(this, "getR");
    },
    addEntry: function (entry) {
      this.entries = this.entries.append(entry);
      this.getL.reset;
      this.getR.reset;
    },
    getL: function () {
      var orientation = this.orientation;
      var maxL = 0;
      this.entries.foreach(function (e) { maxL = Math.max(maxL, e.getL(orientation)); })
      return maxL;
    },
    getR: function () {
      var orientation = this.orientation;
      var maxR = 0;
      this.entries.foreach(function (e) { maxR = Math.max(maxR, e.getR(orientation)); })
      return maxR;
    },
    toString: function () {
      return this.entries.mkString(" \\\\ ");
    }
  });
  xypic.Xymatrix.Entry = MathJax.Object.Subclass({
    Init: function (c, objectShape, decor, frame) {
      this.c = c;
      this.objectShape = objectShape;
      this.decor = decor;
      this.frame = frame;
    },
    getDistanceToEdgePoint: function (frame, angle) {
      var edgePoint = frame.edgePoint(frame.x + Math.cos(angle), frame.y + Math.sin(angle));
      var dx = edgePoint.x - frame.x;
      var dy = edgePoint.y - frame.y;
      return Math.sqrt(dx * dx + dy * dy);
    },
    getU: function (orientation) {
      if (orientation === 0) {
        return this.frame.u;
      }
      return this.getDistanceToEdgePoint(this.frame, orientation + Math.PI / 2);
    },
    getD: function (orientation) {
      if (orientation === 0) {
        return this.frame.d;
      }
      return this.getDistanceToEdgePoint(this.frame, orientation - Math.PI / 2);
    },
    getL: function (orientation) {
      if (orientation === 0) {
        return this.frame.l;
      }
      return this.getDistanceToEdgePoint(this.frame, orientation + Math.PI);
    },
    getR: function (orientation) {
      if (orientation === 0) {
        return this.frame.r;
      }
      return this.getDistanceToEdgePoint(this.frame, orientation);
    },
    toString: function () {
      return this.objectShape.toString() + " " + this.decor;
    }
  });
  
  
  AST.Command.Xymatrix.Setup.Prefix.Augment({
    toShape: function (context) {
      context.env.xymatrixPrefix = this.prefix;
    }
  });
  
  AST.Command.Xymatrix.Setup.ChangeSpacing.Row.Augment({
    toShape: function (context) {
      var env = context.env;
      env.xymatrixRowSepEm = this.addop.applyToDimen(env.xymatrixRowSepEm, xypic.length2em(this.dimen));
    }
  });
  
  AST.Command.Xymatrix.Setup.ChangeSpacing.Column.Augment({
    toShape: function (context) {
      var env = context.env;
      env.xymatrixColSepEm = this.addop.applyToDimen(env.xymatrixColSepEm, xypic.length2em(this.dimen));
    }
  });
  
  AST.Command.Xymatrix.Setup.ChangeSpacing.RowAndColumn.Augment({
    toShape: function (context) {
      var env = context.env;
      var sepEm = this.addop.applyToDimen(env.xymatrixRowSepEm, xypic.length2em(this.dimen));
      env.xymatrixRowSepEm = sepEm;
      env.xymatrixColSepEm = sepEm;
    }
  });
  
  AST.Command.Xymatrix.Setup.PretendEntrySize.Height.Augment({
    toShape: function (context) {
      context.env.xymatrixPretendEntryHeight = FP.Option.Some(xypic.length2em(this.dimen));
    }
  });
  
  AST.Command.Xymatrix.Setup.PretendEntrySize.Width.Augment({
    toShape: function (context) {
      context.env.xymatrixPretendEntryWidth = FP.Option.Some(xypic.length2em(this.dimen));
    }
  });
  
  AST.Command.Xymatrix.Setup.PretendEntrySize.HeightAndWidth.Augment({
    toShape: function (context) {
      var len = FP.Option.Some(xypic.length2em(this.dimen));
      context.env.xymatrixPretendEntryHeight = len;
      context.env.xymatrixPretendEntryWidth = len;
    }
  });
  
  AST.Command.Xymatrix.Setup.FixGrid.Row.Augment({
    toShape: function (context) {
      context.env.xymatrixFixedRow = true;
    }
  });
  
  AST.Command.Xymatrix.Setup.FixGrid.Column.Augment({
    toShape: function (context) {
      context.env.xymatrixFixedCol = true;
    }
  });
  
  AST.Command.Xymatrix.Setup.FixGrid.RowAndColumn.Augment({
    toShape: function (context) {
      context.env.xymatrixFixedRow = true;
      context.env.xymatrixFixedCol = true;
    }
  });
  
  AST.Command.Xymatrix.Setup.AdjustEntrySize.Margin.Augment({
    toShape: function (context) {
      var env = context.env;
      env.objectmargin = this.addop.applyToDimen(env.objectmargin, xypic.length2em(this.dimen));
    }
  });
  
  AST.Command.Xymatrix.Setup.AdjustEntrySize.Width.Augment({
    toShape: function (context) {
      var env = context.env;
      env.objectwidth = this.addop.applyToDimen(env.objectwidth, xypic.length2em(this.dimen));
    }
  });
  
  AST.Command.Xymatrix.Setup.AdjustEntrySize.Height.Augment({
    toShape: function (context) {
      var env = context.env;
      env.objectheight = this.addop.applyToDimen(env.objectheight, xypic.length2em(this.dimen));
    }
  });
  
  AST.Command.Xymatrix.Setup.AdjustLabelSep.Augment({
    toShape: function (context) {
      var env = context.env;
      env.labelmargin = this.addop.applyToDimen(env.labelmargin, xypic.length2em(this.dimen));
    }
  });
  
  AST.Command.Xymatrix.Setup.SetOrientation.Augment({
    toShape: function (context) {
      var env = context.env;
      env.xymatrixOrientationAngle = this.direction.angle(context);
    }
  });
  
  AST.Command.Xymatrix.Setup.AddModifier.Augment({
    toShape: function (context) {
      var env = context.env;
      env.xymatrixEntryModifiers = env.xymatrixEntryModifiers.prepend(this.modifier);
    }
  });
  
  AST.Command.Xymatrix.Entry.SimpleEntry.Augment({
    toShape: function (context) {
      var env = context.env;
      var defaultWidth = xypic.em2length(env.objectmargin + env.objectwidth);
      var defaultHeight = xypic.em2length(env.objectmargin + env.objectheight);
      var defaultSizeModifier = AST.Modifier.AddOp(AST.Modifier.AddOp.GrowTo(), AST.Modifier.AddOp.VactorSize(AST.Vector.Abs(
        defaultWidth, defaultHeight
      )));
      var margin = xypic.em2length(env.objectmargin);
      var marginModifier = AST.Modifier.AddOp(AST.Modifier.AddOp.Grow(), AST.Modifier.AddOp.VactorSize(AST.Vector.Abs(
        margin, margin
      )));
      var modifiers = this.modifiers.concat(env.xymatrixEntryModifiers).prepend(defaultSizeModifier).prepend(marginModifier);
      return AST.Object(modifiers, this.objectbox).toDropShape(context);
    }
  });
  
  AST.Command.Xymatrix.Entry.EmptyEntry.Augment({
    toShape: function (context) {
      var env = context.env;
      var defaultWidth = xypic.em2length(env.objectmargin + env.objectwidth);
      var defaultHeight = xypic.em2length(env.objectmargin + env.objectheight);
      var defaultSizeModifier = AST.Modifier.AddOp(AST.Modifier.AddOp.GrowTo(), AST.Modifier.AddOp.VactorSize(AST.Vector.Abs(
        defaultWidth, defaultHeight
      )));
      var margin = xypic.em2length(env.objectmargin);
      var marginModifier = AST.Modifier.AddOp(AST.Modifier.AddOp.Grow(), AST.Modifier.AddOp.VactorSize(AST.Vector.Abs(
        margin, margin
      )));
      var modifiers = env.xymatrixEntryModifiers.prepend(defaultSizeModifier).prepend(marginModifier);
      return AST.Object(modifiers, AST.ObjectBox.Empty()).toDropShape(context);
    }
  });
  
  AST.Command.Xymatrix.Entry.ObjectEntry.Augment({
    toShape: function (context) {
      return this.object.toDropShape(context);
    }
  });
  
  
  AST.Command.Twocell.Augment({
    toShape: function (context) {
      var origEnv = context.env;
      if (origEnv.c === undefined) {
        return xypic.Shape.none;
      }
      
      var subEnv = origEnv.duplicate();
      var subcontext = xypic.DrawingContext(xypic.Shape.none, subEnv);
      subEnv.twocellmodmapobject = origEnv.twocellmodmapobject || AST.Object(FP.List.empty, AST.ObjectBox.Dir("", "|"));
      subEnv.twocellhead = origEnv.twocellhead || AST.Object(FP.List.empty, AST.ObjectBox.Dir("", ">"));
      subEnv.twocelltail = origEnv.twocelltail || AST.Object(FP.List.empty, AST.ObjectBox.Dir("", ""));
      subEnv.twocellarrowobject = origEnv.twocellarrowobject || AST.Object(FP.List.empty, AST.ObjectBox.Dir("", "=>"));
      
      subEnv.twocellUpperCurveObjectSpacer = origEnv.twocellUpperCurveObjectSpacer;
      subEnv.twocellUpperCurveObject = origEnv.twocellUpperCurveObject;
      subEnv.twocellLowerCurveObjectSpacer = origEnv.twocellLowerCurveObjectSpacer;
      subEnv.twocellLowerCurveObject = origEnv.twocellLowerCurveObject;
      
      // temporary attributes
      subEnv.twocellUpperLabel = FP.Option.empty;
      subEnv.twocellLowerLabel = FP.Option.empty;
      subEnv.twocellCurvatureEm = FP.Option.empty;
      subEnv.twocellShouldDrawCurve = true;
      subEnv.twocellShouldDrawModMap = false;
      
      this.switches.foreach(function (sw) { sw.setup(subcontext); });
      this.twocell.toShape(subcontext, this.arrow);
      context.appendShapeToFront(subcontext.shape);
    }
  });
  
  AST.Command.Twocell.Hops2cell.Augment({
    toShape: function (context, arrow) {
      var env = context.env;
      var c = env.c;
      var angle = env.angle;
      
      var s = env.c;
      var e = this.targetPosition(context);
      if (s === undefined || e === undefined) {
        return;
      }
      
      var dx = e.x - s.x;
      var dy = e.y - s.y;
      if (dx === 0 && dy === 0) {
        return;
      }
      
      var m = xypic.Frame.Point(
          s.x + dx * 0.5,
          s.y + dy * 0.5
        );
      var tangle = Math.atan2(dy, dx);
      var antiClockwiseAngle = tangle + Math.PI / 2;
      
      var curvatureEm = env.twocellCurvatureEm.getOrElse(this.getDefaultCurvature());
      var ncos = Math.cos(antiClockwiseAngle);
      var nsin = Math.sin(antiClockwiseAngle);
      var ucp = this.getUpperControlPoint(s, e, m, curvatureEm, ncos, nsin);
      var lcp = this.getLowerControlPoint(s, e, m, curvatureEm, ncos, nsin);
      
      if (env.twocellShouldDrawCurve) {
        // upper curve
        var objectForDrop = env.twocellUpperCurveObjectSpacer;
        var objectForConnect;
        if (objectForDrop === undefined) {
          objectForConnect = AST.Object(FP.List.empty, AST.ObjectBox.Dir("", "-"));
        } else {
          if (env.twocellUpperCurveObject !== undefined) {
            objectForConnect = env.twocellUpperCurveObject.getOrElse(undefined);
          } else {
            objectForConnect = undefined;
          }
        }
        this.toUpperCurveShape(context, s, ucp, e, objectForDrop, objectForConnect);
        if (env.lastCurve.isDefined) {
          env.angle = tangle;
          var ucmp = this.getUpperLabelPosition(s, e, m, curvatureEm, ncos, nsin);
          var uangle = this.getUpperLabelAngle(antiClockwiseAngle, s, e, m, curvatureEm, ncos, nsin);
          env.twocellUpperLabel.foreach(function (l) {
            l.toShape(context, ucmp, Math.cos(uangle), Math.sin(uangle), tangle);
          });
          if (this.hasUpperTips) {
            arrow.toUpperTipsShape(context);
          }
        }
        
        // lower curve
        var objectForDrop = env.twocellLowerCurveObjectSpacer;
        var objectForConnect;
        if (objectForDrop === undefined) {
          objectForConnect = AST.Object(FP.List.empty, AST.ObjectBox.Dir("", "-"));
        } else {
          if (env.twocellLowerCurveObject !== undefined) {
            objectForConnect = env.twocellLowerCurveObject.getOrElse(undefined);
          } else {
            objectForConnect = undefined;
          }
        }
        this.toLowerCurveShape(context, s, lcp, e, objectForDrop, objectForConnect);
        if (env.lastCurve.isDefined) {
          env.angle = tangle;
          var lcmp = this.getLowerLabelPosition(s, e, m, curvatureEm, ncos, nsin);
          var langle = this.getLowerLabelAngle(antiClockwiseAngle, s, e, m, curvatureEm, ncos, nsin);
          env.twocellLowerLabel.foreach(function (l) {
            l.toShape(context, lcmp, Math.cos(langle), Math.sin(langle), tangle);
          });
          if (this.hasLowerTips) {
            arrow.toLowerTipsShape(context);
          }
        }
      }
      
      env.c = this.getDefaultArrowPoint(s, e, m, curvatureEm, ncos, nsin);
      env.angle = antiClockwiseAngle + Math.PI;
      var labelOrigin = m;
      arrow.toArrowShape(context, labelOrigin);
      
      env.c = c;
      env.angle = angle;
    },
    _toCurveShape: function (context, s, cp, e, objectForDrop, objectForConnect) {
      var env = context.env;
      var origBezier = xypic.Curve.QuadBezier(s, cp, e);
      var tOfShavedStart = origBezier.tOfShavedStart(s);
      var tOfShavedEnd = origBezier.tOfShavedEnd(e);
      if (tOfShavedStart === undefined || tOfShavedEnd === undefined || tOfShavedStart >= tOfShavedEnd) {
        env.lastCurve = xypic.LastCurve.none;
        return;
      }
      var curveShape = origBezier.toShape(context, objectForDrop, objectForConnect);
      env.lastCurve = xypic.LastCurve.QuadBezier(origBezier, tOfShavedStart, tOfShavedEnd, curveShape);
    },
    targetPosition: function (context) {
      var env = context.env;
      var row = env.xymatrixRow;
      var col = env.xymatrixCol;
      if (row === undefined || col === undefined) {
        throw xypic.ExecutionError("rows and columns not found for hops [" + this.hops + "]");
      }
      for (var i = 0; i < this.hops.length; i++) {
        switch (this.hops[i]) {
          case 'u':
            row -= 1;
            break;
          case 'd':
            row += 1;
            break;
          case 'l':
            col -= 1;
            break;
          case 'r':
            col += 1;
            break;
        }
      }
      var id = "" + row + "," + col;
      return context.env.lookupPos(id, 'in entry "' + env.xymatrixRow + "," + env.xymatrixCol + '": No ' + this + " (is " + id + ") from here.").position(context);
    }
  });
  
  AST.Command.Twocell.Twocell.Augment({
    getUpperControlPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x + curvatureEm * ncos,
        midPoint.y + curvatureEm * nsin
      );
    },
    getLowerControlPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x - curvatureEm * ncos,
        midPoint.y - curvatureEm * nsin
      );
    },
    getUpperLabelPosition: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x + 0.5 * curvatureEm * ncos,
        midPoint.y + 0.5 * curvatureEm * nsin
      );
    },
    getLowerLabelPosition: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x - 0.5 * curvatureEm * ncos,
        midPoint.y - 0.5 * curvatureEm * nsin
      );
    },
    getUpperLabelAngle: function (antiClockwiseAngle, s, e, midPoint, curvatureEm, ncos, nsin) {
      var rot = (curvatureEm < 0? Math.PI : 0);
      return antiClockwiseAngle + rot;
    },
    getLowerLabelAngle: function (antiClockwiseAngle, s, e, midPoint, curvatureEm, ncos, nsin) {
      var rot = (curvatureEm < 0? 0 : Math.PI);
      return antiClockwiseAngle + rot;
    },
    getDefaultArrowPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return midPoint;
    },
    toUpperCurveShape: function (context, s, cp, e, objectForDrop, objectForConnect) {
      this._toCurveShape(context, s, cp, e, objectForDrop, objectForConnect);
    },
    toLowerCurveShape: function (context, s, cp, e, objectForDrop, objectForConnect) {
      this._toCurveShape(context, s, cp, e, objectForDrop, objectForConnect);
    },
    getDefaultCurvature: function () { return 3.5 * AST.xypic.lineElementLength; },
    hasUpperTips: true,
    hasLowerTips: true
  });
  
  AST.Command.Twocell.UpperTwocell.Augment({
    getUpperControlPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x + curvatureEm * ncos,
        midPoint.y + curvatureEm * nsin
      );
    },
    getLowerControlPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return midPoint;
    },
    getUpperLabelPosition: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x + 0.5 * curvatureEm * ncos,
        midPoint.y + 0.5 * curvatureEm * nsin
      );
    },
    getLowerLabelPosition: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return midPoint;
    },
    getUpperLabelAngle: function (antiClockwiseAngle, s, e, midPoint, curvatureEm, ncos, nsin) {
      var rot = (curvatureEm < 0? Math.PI : 0);
      return antiClockwiseAngle + rot;
    },
    getLowerLabelAngle: function (antiClockwiseAngle, s, e, midPoint, curvatureEm, ncos, nsin) {
      var rot = (curvatureEm < 0? 0 : Math.PI);
      return antiClockwiseAngle + rot;
    },
    getDefaultArrowPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x + 0.25 * curvatureEm * ncos,
        midPoint.y + 0.25 * curvatureEm * nsin
      );
    },
    toUpperCurveShape: function (context, s, cp, e, objectForDrop, objectForConnect) {
      this._toCurveShape(context, s, cp, e, objectForDrop, objectForConnect);
    },
    toLowerCurveShape: function (context, s, cp, e, objectForDrop, objectForConnect) {
      var shavedS = s.edgePoint(e.x, e.y);
      var shavedE = e.edgePoint(s.x, s.y);
      if (shavedS.x !== shavedE.x || shavedS.y !== shavedE.y) {
        context.env.lastCurve = xypic.LastCurve.Line(shavedS, shavedE, s, e, undefined);
      } else {
        context.env.lastCurve = xypic.LastCurve.none;
      }
    },
    getDefaultCurvature: function () { return 7 * AST.xypic.lineElementLength; },
    hasUpperTips: true,
    hasLowerTips: false
  });
  
  AST.Command.Twocell.LowerTwocell.Augment({
    getUpperControlPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return midPoint;
    },
    getLowerControlPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x + curvatureEm * ncos,
        midPoint.y + curvatureEm * nsin
      );
    },
    getUpperLabelPosition: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return midPoint;
    },
    getLowerLabelPosition: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x + 0.5 * curvatureEm * ncos,
        midPoint.y + 0.5 * curvatureEm * nsin
      );
    },
    getUpperLabelAngle: function (antiClockwiseAngle, s, e, midPoint, curvatureEm, ncos, nsin) {
      var rot = (curvatureEm < 0? 0 : Math.PI);
      return antiClockwiseAngle + rot;
    },
    getLowerLabelAngle: function (antiClockwiseAngle, s, e, midPoint, curvatureEm, ncos, nsin) {
      var rot = (curvatureEm < 0? Math.PI : 0);
      return antiClockwiseAngle + rot;
    },
    getDefaultArrowPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return xypic.Frame.Point(
        midPoint.x + 0.25 * curvatureEm * ncos,
        midPoint.y + 0.25 * curvatureEm * nsin
      );
    },
    toUpperCurveShape: function (context, s, cp, e, objectForDrop, objectForConnect) {
      var shavedS = s.edgePoint(e.x, e.y);
      var shavedE = e.edgePoint(s.x, s.y);
      if (shavedS.x !== shavedE.x || shavedS.y !== shavedE.y) {
        context.env.lastCurve = xypic.LastCurve.Line(shavedS, shavedE, s, e, undefined);
      } else {
        context.env.lastCurve = xypic.LastCurve.none;
      }
    },
    toLowerCurveShape: function (context, s, cp, e, objectForDrop, objectForConnect) {
      this._toCurveShape(context, s, cp, e, objectForDrop, objectForConnect);
    },
    getDefaultCurvature: function () { return -7 * AST.xypic.lineElementLength; },
    hasUpperTips: false,
    hasLowerTips: true
  });
  
  AST.Command.Twocell.CompositeMap.Augment({
    getUpperControlPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      var midBoxSize = this.getMidBoxSize();
      return xypic.Frame.Ellipse(
        midPoint.x + curvatureEm * ncos,
        midPoint.y + curvatureEm * nsin,
        midBoxSize, midBoxSize, midBoxSize, midBoxSize
      );
    },
    getLowerControlPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      var midBoxSize = this.getMidBoxSize();
      return xypic.Frame.Ellipse(
        midPoint.x + curvatureEm * ncos,
        midPoint.y + curvatureEm * nsin,
        midBoxSize, midBoxSize, midBoxSize, midBoxSize
      );
    },
    getUpperLabelPosition: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      var dx = midPoint.x + curvatureEm * ncos - e.x;
      var dy = midPoint.y + curvatureEm * nsin - e.y;
      var l = Math.sqrt(dx * dx + dy * dy);
      return xypic.Frame.Point(
        e.x + 0.5 * dx,
        e.y + 0.5 * dy
      );
    },
    getLowerLabelPosition: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      var dx = midPoint.x + curvatureEm * ncos - s.x;
      var dy = midPoint.y + curvatureEm * nsin - s.y;
      var l = Math.sqrt(dx * dx + dy * dy);
      return xypic.Frame.Point(
        s.x + 0.5 * dx,
        s.y + 0.5 * dy
      );
    },
    getUpperLabelAngle: function (antiClockwiseAngle, s, e, midPoint, curvatureEm, ncos, nsin) {
      var dx = e.x - midPoint.x + curvatureEm * ncos;
      var dy = e.y - midPoint.y + curvatureEm * nsin;
      var angle = Math.atan2(dy, dx);
      var rot = (curvatureEm < 0? Math.PI : 0);
      return angle + Math.PI / 2 + rot;
    },
    getLowerLabelAngle: function (antiClockwiseAngle, s, e, midPoint, curvatureEm, ncos, nsin) {
      var dx = midPoint.x + curvatureEm * ncos - s.x;
      var dy = midPoint.y + curvatureEm * nsin - s.y;
      var angle = Math.atan2(dy, dx);
      var rot = (curvatureEm < 0? Math.PI : 0);
      return angle + Math.PI / 2 + rot;
    },
    getDefaultArrowPoint: function (s, e, midPoint, curvatureEm, ncos, nsin) {
      return midPoint;
    },
    toUpperCurveShape: function (context, s, cp, e, objectForDrop, objectForConnect) {
      var env = context.env;
      var start = s;
      var end = cp;
      var shavedS = start.edgePoint(end.x, end.y);
      var shavedE = end.edgePoint(start.x, start.y);
      var p = env.p;
      var c = env.c;
      env.p = start;
      env.c = end;
      xypic.Curve.Line(shavedS, shavedE).toShape(context, undefined, "-", "");
      env.p = p;
      env.c = c;
    },
    toLowerCurveShape: function (context, s, cp, e, objectForDrop, objectForConnect) {
      var env = context.env;
      var start = cp;
      var end = e;
      var shavedS = start.edgePoint(end.x, end.y);
      var shavedE = end.edgePoint(start.x, start.y);
      var p = env.p;
      var c = env.c;
      env.p = start;
      env.c = end;
      xypic.Curve.Line(shavedS, shavedE).toShape(context, undefined, "-", "");
      env.p = p;
      env.c = c;
    },
    getMidBoxSize: function () { return 0.5 * AST.xypic.lineElementLength; },
    getDefaultCurvature: function () { return 3.5 * AST.xypic.lineElementLength; },
    hasUpperTips: true,
    hasLowerTips: true
  });
  
  AST.Command.Twocell.Switch.UpperLabel.Augment({
    setup: function (context) {
      var env = context.env;
      env.twocellUpperLabel = FP.Option.Some(this);
    },
    toShape: function (context, curveMidPos, ncos, nsin, tangle) {
      this.label.toShape(context, curveMidPos, ncos, nsin, tangle);
    }
  });
  
  AST.Command.Twocell.Switch.LowerLabel.Augment({
    setup: function (context) {
      var env = context.env;
      env.twocellLowerLabel = FP.Option.Some(this);
    },
    toShape: function (context, curveMidPos, ncos, nsin, tangle) {
      this.label.toShape(context, curveMidPos, ncos, nsin, tangle);
    }
  });
  
  AST.Command.Twocell.Switch.SetCurvature.Augment({
    setup: function (context) {
      var env = context.env;
      if (this.nudge.isOmit) {
        env.twocellShouldDrawCurve = false;
      } else {
        env.twocellCurvatureEm = FP.Option.Some(this.nudge.number * AST.xypic.lineElementLength);
      }
    }
  });
  
  AST.Command.Twocell.Switch.DoNotSetCurvedArrows.Augment({
    setup: function (context) {
      var env = context.env;
      env.twocellShouldDrawCurve = false;
    }
  });
  
  AST.Command.Twocell.Switch.PlaceModMapObject.Augment({
    setup: function (context) {
      var env = context.env;
      env.twocellShouldDrawModMap = true;
    }
  });
  
  AST.Command.Twocell.Switch.ChangeHeadTailObject.Augment({
    setup: function (context) {
      var env = context.env;
      switch (this.what) {
        case '`':
          env.twocelltail = this.object;
          break;
        case "'":
          env.twocellhead = this.object;
          break;
      }
    }
  });
  
  AST.Command.Twocell.Switch.ChangeCurveObject.Augment({
    setup: function (context) {
      var env = context.env;
      switch (this.what) {
        case '':
          env.twocellUpperCurveObjectSpacer = this.spacer;
          env.twocellUpperCurveObject = this.maybeObject;
          env.twocellLowerCurveObjectSpacer = this.spacer;
          env.twocellLowerCurveObject = this.maybeObject;
          break;
        case '^':
          env.twocellUpperCurveObjectSpacer = this.spacer;
          env.twocellUpperCurveObject = this.maybeObject;
          break;
        case '_':
          env.twocellLowerCurveObjectSpacer = this.spacer;
          env.twocellLowerCurveObject = this.maybeObject;
          break;
      }
    }
  });
  
  AST.Command.Twocell.Label.Augment({
    toShape: function (context, curveMidPos, ncos, nsin, tangle) {
      var maybeNudge = this.maybeNudge;
      var offset;
      if (maybeNudge.isDefined) {
        var nudge = maybeNudge.get;
        if (nudge.isOmit) {
          return;
        } else {
          offset = nudge.number * AST.xypic.lineElementLength;
        }
      } else {
        offset = this.getDefaultLabelOffset();
      }
      
      var env = context.env;
      var c = env.c;
      env.c = xypic.Frame.Point(
        curveMidPos.x + offset * ncos,
        curveMidPos.y + offset * nsin
      );
      var labelObject = this.labelObject;
      labelObject.toDropShape(context);
      env.c = c;
      
    },
    getDefaultLabelOffset: function () { return AST.xypic.lineElementLength; }
  });
  
  AST.Command.Twocell.Nudge.Number.Augment({
    isOmit: false
  });
  
  AST.Command.Twocell.Nudge.Omit.Augment({
    isOmit: true
  });
  
  AST.Command.Twocell.Arrow.Augment({
    toTipsShape: function (context, reversed, doubleHeaded) {
      var env = context.env;
      var lastCurve = env.lastCurve;
      var c = env.c;
      var angle = env.angle;
      
      var rot = (reversed? Math.PI : 0);
      var t = lastCurve.tOfPlace(true, true, (reversed? 0 : 1), 0);
      env.c = lastCurve.position(t);
      env.angle = lastCurve.angle(t) + rot;
      env.twocellhead.toDropShape(context);
      
      var t = lastCurve.tOfPlace(true, true, (reversed? 1 : 0), 0);
      env.c = lastCurve.position(t);
      env.angle = lastCurve.angle(t) + rot;
      if (doubleHeaded) {
        env.twocellhead.toDropShape(context);
      } else {
        env.twocelltail.toDropShape(context);
      }
      
      if (env.twocellShouldDrawModMap) {
        var t = lastCurve.tOfPlace(false, false, 0.5, 0);
        env.c = lastCurve.position(t);
        env.angle = lastCurve.angle(t) + rot;
        env.twocellmodmapobject.toDropShape(context);
      }
      
      env.c = c;
      env.angle = angle;
    }
  });
  
  AST.Command.Twocell.Arrow.WithOrientation.Augment({
    toUpperTipsShape: function (context) {
      switch (this.tok) {
        case '':
        case '^':
        case '_':
        case '=':
        case '\\omit':
        case "'":
          this.toTipsShape(context, false, false);
          break;
        case '`':
          this.toTipsShape(context, true, false);
          break;
        case '"':
          this.toTipsShape(context, false, true);
          break;
        case '!':
          break;
      }
    },
    toLowerTipsShape: function (context) {
      switch (this.tok) {
        case '':
        case '^':
        case '_':
        case '=':
        case '\\omit':
        case '`':
          this.toTipsShape(context, false, false);
          break;
        case "'":
          this.toTipsShape(context, true, false);
          break;
        case '"':
          this.toTipsShape(context, false, true);
          break;
        case '!':
          break;
      }
    },
    toArrowShape: function(context, labelOrigin) {
      var env = context.env;
      var c = env.c;
      switch (this.tok) {
        case '^':
          var angle = env.angle;
          env.angle = angle + Math.PI;
          env.twocellarrowobject.toDropShape(context);
          env.c = xypic.Frame.Point(
            c.x + AST.xypic.lineElementLength * Math.cos(angle - Math.PI / 2),
            c.y + AST.xypic.lineElementLength * Math.sin(angle - Math.PI / 2)
          );
          this.labelObject.toDropShape(context);
          env.angle = angle;
          break;
        case '':
        case '_':
          var angle = env.angle;
          env.twocellarrowobject.toDropShape(context);
          env.c = xypic.Frame.Point(
            c.x + AST.xypic.lineElementLength * Math.cos(angle + Math.PI / 2),
            c.y + AST.xypic.lineElementLength * Math.sin(angle + Math.PI / 2)
          );
          this.labelObject.toDropShape(context);
          break;
        case '=':
          var angle = env.angle;
          var shape = xypic.Shape.TwocellEqualityArrowheadShape(env.c, env.angle);
          context.appendShapeToFront(shape);
          env.c = xypic.Frame.Point(
            c.x + AST.xypic.lineElementLength * Math.cos(angle + Math.PI / 2),
            c.y + AST.xypic.lineElementLength * Math.sin(angle + Math.PI / 2)
          );
          this.labelObject.toDropShape(context);
          break;
        default:
          this.labelObject.toDropShape(context);
          break;
      }
      env.c = c;
    }
  });
  
  AST.Command.Twocell.Arrow.WithPosition.Augment({
    toUpperTipsShape: function (context) {
      this.toTipsShape(context, false, false);
    },
    toLowerTipsShape: function (context) {
      this.toTipsShape(context, false, false);
    },
    toArrowShape: function(context, labelOrigin) {
      var env = context.env;
      var c = env.c;
      var angle = env.angle;
      var arrowPos;
      var nudge = this.nudge;
      if (nudge.isOmit) {
        arrowPos = c;
      } else {
        var offset = nudge.number * AST.xypic.lineElementLength;
        arrowPos = xypic.Frame.Point(
          labelOrigin.x + offset * Math.cos(angle),
          labelOrigin.y + offset * Math.sin(angle)
        );
      }
      
      env.c = arrowPos;
      env.twocellarrowobject.toDropShape(context);
      if (!nudge.isOmit) {
        env.c = xypic.Frame.Point(
          arrowPos.x + AST.xypic.lineElementLength * Math.cos(angle + Math.PI / 2),
          arrowPos.y + AST.xypic.lineElementLength * Math.sin(angle + Math.PI / 2)
        );
        this.labelObject.toDropShape(context);
      }
      env.c = c;
    }
  });
  
  AST.Pos.Xyimport.TeXCommand.Augment({
    toShape: function (context) {
      var origEnv = context.env;
      if (origEnv.c === undefined) {
        return xypic.Shape.none;
      }
      
      var subEnv = origEnv.duplicate();
      var subcontext = xypic.DrawingContext(xypic.Shape.none, subEnv);
      var shape = this.graphics.toDropShape(subcontext);
      
      var xyWidth = this.width;
      var xyHeight = this.height;
      if (xyWidth === 0 || xyHeight === 0) {
        throw xypic.ExecutionError("the 'width' and 'height' attributes of the \\xyimport should be non-zero.");
      }
      
      var c = subEnv.c;
      var imageWidth = c.l + c.r;
      var imageHeight = c.u + c.d;
      
      if (imageWidth === 0 || imageHeight === 0) {
        throw xypic.ExecutionError("the width and height of the graphics to import should be non-zero.");
      }
      
      var xOffset = this.xOffset;
      var yOffset = this.yOffset;
      
      origEnv.c = c.toRect({
        u:imageHeight / xyHeight * (xyHeight - yOffset),
        d:imageHeight / xyHeight * yOffset,
        l:imageWidth / xyWidth * xOffset,
        r:imageWidth / xyWidth * (xyWidth - xOffset)
      });
      
      origEnv.setXBase(imageWidth / xyWidth, 0);
      origEnv.setYBase(0, imageHeight / xyHeight);
      
      var dx = c.l - origEnv.c.l;
      var dy = c.d - origEnv.c.d;
      var shape = xypic.Shape.TranslateShape(dx, dy, subcontext.shape);
      context.appendShapeToFront(shape);
    }
  });
  
  AST.Pos.Xyimport.Graphics.Augment({
    toShape: function (context) {
      var origEnv = context.env;
      if (origEnv.c === undefined) {
        return xypic.Shape.none;
      }
      
      var subEnv = origEnv.duplicate();
      var subcontext = xypic.DrawingContext(xypic.Shape.none, subEnv);
      
      var xyWidth = this.width;
      var xyHeight = this.height;
      if (xyWidth === 0 || xyHeight === 0) {
        throw xypic.ExecutionError("the 'width' and 'height' attributes of the \\xyimport should be non-zero.");
      }
      
      var graphics = this.graphics;
      graphics.setup(subcontext);
      if (!subEnv.includegraphicsWidth.isDefined || !subEnv.includegraphicsHeight.isDefined) {
        throw xypic.ExecutionError("the 'width' and 'height' attributes of the \\includegraphics are required.");
      }
      var imageWidth = subEnv.includegraphicsWidth.get;
      var imageHeight = subEnv.includegraphicsHeight.get;
      
      if (imageWidth === 0 || imageHeight === 0) {
        throw xypic.ExecutionError("the 'width' and 'height' attributes of the \\includegraphics should be non-zero.");
      }
      
      var xOffset = this.xOffset;
      var yOffset = this.yOffset;
      
      origEnv.c = subEnv.c.toRect({
        u:imageHeight / xyHeight * (xyHeight - yOffset),
        d:imageHeight / xyHeight * yOffset,
        l:imageWidth / xyWidth * xOffset,
        r:imageWidth / xyWidth * (xyWidth - xOffset)
      });
      
      origEnv.setXBase(imageWidth / xyWidth, 0);
      origEnv.setYBase(0, imageHeight / xyHeight);
      
      var imageShape = xypic.Shape.ImageShape(origEnv.c, graphics.filepath);
      context.appendShapeToFront(imageShape);
    }
  });
  
  AST.Command.Includegraphics.Augment({
    setup: function (context) {
      var env = context.env;
      env.includegraphicsWidth = FP.Option.empty;
      env.includegraphicsHeight = FP.Option.empty;
      
      this.attributeList.foreach(function (attr) {
        attr.setup(context);
      });
    }
  });
  
  AST.Command.Includegraphics.Attr.Width.Augment({
    setup: function (context) {
      var env = context.env;
      env.includegraphicsWidth = FP.Option.Some(xypic.length2em(this.dimen));
    }
  });
  
  AST.Command.Includegraphics.Attr.Height.Augment({
    setup: function (context) {
      var env = context.env;
      env.includegraphicsHeight = FP.Option.Some(xypic.length2em(this.dimen));
    }
  });
  
  MathJax.Hub.Startup.signal.Post("Device-Independent Xy-pic Ready");
});


MathJax.Hub.Register.StartupHook("HTML-CSS Xy-pic Require",function () {
  var FP = MathJax.Extension.fp;
  var MML = MathJax.ElementJax.mml;
  var HTMLCSS = MathJax.OutputJax["HTML-CSS"];
  var HUB = MathJax.Hub;
  var xypic = MathJax.Extension.xypic;
  var AST = xypic.AST;
  
  var SVGNS = "http://www.w3.org/2000/svg";
  var XHTMLNS = "http://www.w3.org/1999/xhtml";
  var XLINKNS = "http://www.w3.org/1999/xlink";
  
  var setupHTMLCSSMeasure = function () {
    xypic.length2em = function (len) { return HTMLCSS.length2em(len); }
    xypic.oneem = xypic.length2em("1em");
    xypic.em2length = function (len) { return (len / xypic.oneem) + "em"; }
    xypic.Em = function (x) { return HTMLCSS.Em(x); }
    xypic.em = HTMLCSS.em;
    xypic.em2px = function (n) { return Math.round(n * HTMLCSS.em * 100) / 100; }
    xypic.axis_height = HTMLCSS.TeX.axis_height;
    
    AST.xypic.strokeWidth = xypic.length2em("0.04em");
    AST.xypic.thickness = xypic.length2em("0.15em");
    AST.xypic.jot = xypic.length2em("3pt");
    AST.xypic.objectmargin = xypic.length2em("3pt");
    AST.xypic.objectwidth = xypic.length2em("0pt");
    AST.xypic.objectheight = xypic.length2em("0pt");
    AST.xypic.labelmargin = xypic.length2em("2.5pt");
    AST.xypic.turnradius = xypic.length2em("10pt");
    AST.xypic.lineElementLength = xypic.length2em("5pt");
    AST.xypic.axisHeightLength = xypic.axis_height * xypic.length2em("10pt");
    
    AST.xypic.dottedDasharray = "" + xypic.oneem + " " + xypic.em2px(AST.xypic.thickness);
  };
  
  AST.xypic.Augment({
    toHTML: function (span) {
      if (!xypic.useSVG) {
        return span;
      }
      
      setupHTMLCSSMeasure();
      
      // HTML-CSS Text Objects
      var textObjects = [];
      
      var p = xypic.length2em("0.2em");
      var t = AST.xypic.strokeWidth;
      
      span = this.HTMLcreateSpan(span);
      var svgStack = HTMLCSS.createStack(span);
      
      xypic.Shape.TextShape.Augment({
        _draw: function (svg, test) {
          var math = this.math;
          var span, stack, base;
          math.setTeXclass();
          
          // padding
          var p = xypic.length2em("0.1em");
          var mathSpan = HTMLCSS.Element("span", {
            className:"MathJax", 
            style:{ "text-align":"center", "role":"textbox", "aria-readonly":"true", "position":"absolute", color:svg.getCurrentColor() /*, "border":"0.1px dashed" */ }
          });
          
          svgStack.appendChild(mathSpan);
          
          // clear spanID for connecting objects.
          var clearSpanId = function (mml) {
            if (mml) {
              if (mml.hasOwnProperty("spanID")) { delete mml.spanID; }
              if (mml.data) {
                for (var i = 0, n = mml.data.length; i < n; i++) {
                  clearSpanId(mml.data[i]);
                }
              }
            }
          }
          
          clearSpanId(math);
          var span = math.HTMLcreateSpan(mathSpan);
          stack = HTMLCSS.createStack(span);
          base = HTMLCSS.createBox(stack);
          math.HTMLmeasureChild(0, base);
          var H = base.bbox.h + p, D = base.bbox.d + p, W = base.bbox.w + 2 * p;
          var frame = HTMLCSS.createFrame(stack, H + D, 0, W, 0, "none");
          frame.id = "MathJax-frame-" + math.spanID + HTMLCSS.idPostfix;
    //      stack.style.border = "solid 0.1px pink";
    //      base.style.border = "solid 0.1px pink";
    //      frame.style.border = "solid 0.1px pink";
          HTMLCSS.addBox(stack, frame);
          stack.insertBefore(frame, base);
          frame.style.width = xypic.em2px(W);
          frame.style.height = xypic.em2px(H + D);
          HTMLCSS.placeBox(frame, 0, -D, true);
          HTMLCSS.placeBox(base, p, 0);
          math.HTMLhandleSpace(span);
          math.HTMLhandleColor(span);
          
          var spanHeight = span.offsetHeight;
          var halfHD = (H + D) / 2;
          var halfW = W / 2;
          
          var c = this.c;
          this.originalBBox = { H:H, D:D, W:W };
          
          if (!test) {
            var origin = svg.getOrigin();
            mathSpan.setAttribute("x", c.x - halfW - origin.x);
            mathSpan.setAttribute("y", -c.y - halfHD - origin.y - stack.offsetTop / HTMLCSS.em + H);
            textObjects.push(mathSpan);
            
    /*        
            svg.createSVGElement("rect", {
              x:xypic.em2px(c.x - halfW),
              y:-xypic.em2px(c.y - (H - D) / 2),
              width:xypic.em2px(W),
              height:0.1,
              stroke:"green", "stroke-width":0.3
            });
            
            console.log("span.top:" + span.offsetTop + ", " + (span.offsetTop / HTMLCSS.em) + "em");
            console.log("span.height:" + span.offsetHeight + ", " + (span.offsetHeight / HTMLCSS.em) + "em");
            console.log("stack.top:" + stack.offsetTop + ", " + (stack.offsetTop / HTMLCSS.em) + "em");
            console.log("stack.height:" + stack.offsetHeight + ", " + (stack.offsetHeight / HTMLCSS.em) + "em");
            console.log("frame.top:" + frame.offsetTop + ", " + (frame.offsetTop / HTMLCSS.em) + "em");
            console.log("frame.height:" + frame.offsetHeight + ", " + (frame.offsetHeight / HTMLCSS.em) + "em");
            console.log("base.top:" + base.offsetTop + ", " + (base.offsetTop / HTMLCSS.em) + "em");
            console.log("base.height:" + base.offsetHeight + ", " + (base.offsetHeight / HTMLCSS.em) + "em");
            console.log("p:" + xypic.em2px(p) + ", " + p + "em");
            console.log("D:" + xypic.em2px(D) + ", " + D + "em");
            console.log("H:" + xypic.em2px(H) + ", " + H + "em");
            console.log("d:" + xypic.em2px(base.bbox.d) + ", " + base.bbox.d + "em");
            console.log("h:" + xypic.em2px(base.bbox.h) + ", " + base.bbox.h + "em");
            
            svg.createSVGElement("rect", {
              x:xypic.em2px(c.x - halfW),
              y:-xypic.em2px(c.y + halfHD),
              width:xypic.em2px(W),
              height:xypic.em2px(H + D),
              stroke:"green", "stroke-width":0.5
            });
         */
          } else {
            svgStack.removeChild(mathSpan);
          }
          
          return c.toRect({ u:halfHD, d:halfHD, l:halfW, r:halfW });
        }
      });
      
      var bbox = { h:1, d:0, w:1, lw:0, rw:1 };
      var H = bbox.h, D = bbox.d, W = bbox.w;
      var frame = HTMLCSS.createFrame(svgStack, H + D, 0, W, t, "none");
      frame.id = "MathJax-frame-" + this.spanID + HTMLCSS.idPostfix;
      
      var svg;
      var color = "black";
      svg = xypic.Graphics.createSVG(H, D, W, t, color, {
        viewBox:[0, -xypic.em2px(H + D), xypic.em2px(W), xypic.em2px(H + D)].join(" "),
        overflow:"visible"
      });
      xypic.svgForDebug = svg;
      xypic.svgForTestLayout = svg;
      var scale = HTMLCSS.createBox(svgStack);
      scale.appendChild(svg.svg);
      
      var xypicData = this.cmd;
      if (xypicData) {
        var env = xypic.Env();
        
        var context = xypic.DrawingContext(xypic.Shape.none, env);
        xypicData.toShape(context);
        var shape = context.shape;
        shape.draw(svg);
        
        var box = shape.getBoundingBox();
        if (box !== undefined) {
          box = xypic.Frame.Rect(
            0, 0,
            {
              l:Math.max(0, -(box.x - box.l)),
              r:Math.max(0, box.x + box.r),
              u:Math.max(0, box.y + box.u),
              d:Math.max(0, -(box.y - box.d))
            }
          );
          
          svg.setWidth(box.l + box.r + 2 * p);
          svg.setHeight(box.u + box.d + 2 * p);
          svg.setAttribute("viewBox", [ xypic.em2px(box.x - box.l - p), -xypic.em2px(box.y + box.u + p), xypic.em2px(box.l + box.r + 2 * p), xypic.em2px(box.u + box.d + 2 * p) ].join(" "));
          var c = textObjects.length;
          for (var i = 0; i < c; i++) {
            var to = textObjects[i];
            var x = parseFloat(to.getAttribute("x"));
            var y = parseFloat(to.getAttribute("y"));
            
            to.style.left = "" + xypic.em2px(x + box.l + p * xypic.oneem) + "px";
            to.style.top = "" + xypic.em2px(y - xypic.axis_height) + "px";
          }
          
          bbox = { h:(box.u + p), d:(box.d + p), w:(box.l + box.r + 2 * p), lw:0, rw:(box.l + box.r + 2 * p)}
          span.bbox = bbox;
          D = box.d + p;
          W = box.l + box.r + 2 * p;
          H = box.h + p;
          
          HTMLCSS.placeBox(scale, 0, xypic.axis_height - D, true);
          frame.style.width = xypic.Em(W);
          frame.style.height = xypic.Em(H + D);
          HTMLCSS.addBox(svgStack, frame); 
          HTMLCSS.placeBox(frame, W - 1, -D, true);
          this.HTMLhandleSpace(span);
          this.HTMLhandleColor(span);
        } else {
          // there is no contents
          span = span.parentNode;
          span.removeChild(span.firstChild);
        }
      } else {
        // there is no contents
        span = span.parentNode;
        span.removeChild(span.firstChild);
      }
      
      return span;
    }
  });
  
  AST.xypic.newdir.Augment({
    toHTML: function (span) {
      var newdir = this.cmd;
      xypic.repositories.dirRepository.put(newdir.dirMain, newdir.compositeObject);
      return span;
    }
  });
  
  AST.xypic.includegraphics.Augment({
    toHTML: function (span) {
      setupHTMLCSSMeasure();
      
      var graphics = this.cmd;
      
      var env = xypic.Env();
      var context = xypic.DrawingContext(xypic.Shape.none, env);
      
      graphics.setup(context);
      if (!env.includegraphicsWidth.isDefined || !env.includegraphicsHeight.isDefined) {
        throw xypic.ExecutionError("the 'width' and 'height' attributes of the \\includegraphics are required.");
      }
      
      var imageWidth = env.includegraphicsWidth.get;
      var imageHeight = env.includegraphicsHeight.get;
      
      span = this.HTMLcreateSpan(span);
      var stack = HTMLCSS.createStack(span);
      var base = HTMLCSS.createBox(stack);
      
      var img = new Image();
      img.src = graphics.filepath;
      img.style.width = HTMLCSS.Em(imageWidth);
      img.style.height = HTMLCSS.Em(imageHeight);
      base.appendChild(img);
      
      var bbox = {h:imageHeight, d:0, w:imageWidth, rw:imageWidth, lw:0, exactW:true};
      img.bbox = bbox;
      var H = imageHeight, D = 0, W = imageWidth;
      
      HTMLCSS.Measured(img);
      
      var frame = HTMLCSS.createFrame(stack, H+D, 0, W, 0, "none");
      frame.id = "MathJax-frame-" + this.spanID;
      HTMLCSS.addBox(stack, frame);
      stack.insertBefore(frame, base);
      var T = 0, B = 0, R = 0, L = 0, dx = 0, dy = 0;
      frame.style.width = HTMLCSS.Em(W-L-R);
      frame.style.height = HTMLCSS.Em(H+D-T-B);
      HTMLCSS.placeBox(frame, 0, dy-D, true);
      HTMLCSS.placeBox(base, dx, 0);
      this.HTMLhandleSpace(span);
      this.HTMLhandleColor(span);
      
      return span;
    }
  });
  
  MathJax.Hub.Startup.signal.Post("HTML-CSS Xy-pic Ready");
});


MathJax.Hub.Register.StartupHook("SVG Xy-pic Require",function () {
  var FP = MathJax.Extension.fp;
  var MML = MathJax.ElementJax.mml;
  var SVG = MathJax.OutputJax.SVG;
  var BBOX = SVG.BBOX;
  var HUB = MathJax.Hub;
  var xypic = MathJax.Extension.xypic;
  var AST = xypic.AST;
  
  var SVGNS = "http://www.w3.org/2000/svg";
  var XHTMLNS = "http://www.w3.org/1999/xhtml";
  var XLINKNS = "http://www.w3.org/1999/xlink";
  
  var memoize = xypic.memoize;
  
  BBOX.PPATH = BBOX.Subclass({
    type: "path", removeable: false,
    Init: function (h,d,w,p,t,color,def) {
      if (def == null) {def = {}}; def.fill = "none";
      if (color) {def.stroke = color}
      def["stroke-width"] = t.toFixed(2).replace(/\.?0+$/,"");
      def.d = p;
      this.SUPER(arguments).Init.call(this,def);
      this.w = this.r = w; this.h = this.H = h+d;
      this.d = this.D = this.l = 0; this.y = -d;
    }
  });
  
  BBOX.XYPIC = BBOX.Subclass({
    type: "g", removeable: false,
    Init: function (bbox, x, y, svg) {
      this.element = svg;
      
      this.x = x;
      this.y = y;
      this.r = bbox.r;
      this.l = bbox.l;
      this.h = bbox.h;
      this.d = bbox.d;
      this.w = bbox.w;
      this.H = bbox.h;
      this.D = bbox.d;
      
      this.scale = 1;
      this.n = 1;
    }
  });
  
  var setupSVGMeasure = function (mu, scale) {
    xypic.length2em = function (len) { return SVG.length2em(len, mu, 1/SVG.em) * scale; }
    xypic.oneem = xypic.length2em("1em");
    xypic.em2length = function (len) { return (len / xypic.oneem) + "em"; }
    
    xypic.Em = function (x) { return SVG.Em(x); }
    xypic.em = SVG.em;
    xypic.em2px = function (n) { return Math.round(n * SVG.em * 100) / 100; }
    xypic.axis_height = SVG.TeX.axis_height;
    
    AST.xypic.strokeWidth = xypic.length2em("0.04em");
    AST.xypic.thickness = xypic.length2em("0.15em");
    AST.xypic.jot = xypic.length2em("3pt");
    AST.xypic.objectmargin = xypic.length2em("3pt");
    AST.xypic.objectwidth = xypic.length2em("0pt");
    AST.xypic.objectheight = xypic.length2em("0pt");
    AST.xypic.labelmargin = xypic.length2em("2.5pt");
    AST.xypic.turnradius = xypic.length2em("10pt");
    AST.xypic.lineElementLength = xypic.length2em("5pt");
    AST.xypic.axisHeightLength = xypic.axis_height * xypic.length2em("1em") / 1000;
    
    AST.xypic.dottedDasharray = "" + xypic.oneem + " " + xypic.em2px(AST.xypic.thickness);
  };
  
  AST.xypic.Augment({
    toSVG: function (HW, DD) {
      this.SVGgetStyles();
      
      var svg = this.SVG();
      this.SVGhandleSpace(svg);
      
      var mu = this.SVGgetMu(svg);
      var scale = this.SVGgetScale();
      setupSVGMeasure(mu, scale);
      
      var p = xypic.length2em("0.2em");
      var t = AST.xypic.strokeWidth;
      
      var jaxSVG = svg;
      
      xypic.Shape.TextShape.Augment({
        _draw: function (svg, test) {
          var math = this.math;
          
          // padding
          var p = xypic.length2em("0.1em");
          var c = this.c;
          
          math.setTeXclass();
          math.SVGgetStyles();
          
          math.SVGhandleSpace(jaxSVG);
          var box = math.data[0].toSVG();
          var x = c.x - box.w / 2;
          var y = c.y - (box.h + box.d) / 2 + box.d + xypic.axis_height;
          var H = box.h + p;
          var D = box.d + p;
          var W = box.w + 2 * p;
          this.originalBBox = { H:H, D:D, W:W };
          var halfHD = (H + D) / 2;
          var halfW = W / 2;
          
          if (!test) {
            var origin = svg.getOrigin();
            var color = svg.getCurrentColor();
            var localSVG = BBOX.G({ stroke:color, fill:color, "stroke-thickness":0, transform:"scale(" + SVG.em + ") matrix(1 0 0 -1 0 0) translate(" + xypic.em2px(x / SVG.em) + ", " + xypic.em2px((c.y - (H - D) / 2) / SVG.em) + ")" }).With({removeable: false});
            localSVG.Add(box, 0, 0, true, true);
            localSVG.ic = box.ic;
            localSVG.Clean();
            math.SVGhandleColor(localSVG);
            math.SVGsaveData(localSVG);
            
            svg.appendChild(localSVG.element);
          }
          
          return c.toRect({ u:halfHD, d:halfHD, l:halfW, r:halfW });
        }
      });
      
      var bbox = { h:xypic.oneem, d:0, w:xypic.oneem, lw:0, rw:xypic.oneem };
      var H = bbox.h, D = bbox.d, W = bbox.w;
      var color = "black";
      var xypicSVG = xypic.Graphics.createSVG(H, D, W, t, color, {
        viewBox:[0, -xypic.em2px(H + D), xypic.em2px(W), xypic.em2px(H + D)].join(" "),
        overflow:"visible"
      });
      xypic.svgForDebug = xypicSVG;
      xypic.svgForTestLayout = xypicSVG;
      
      var xypicData = this.cmd;
      if (xypicData) {
        var env = xypic.Env();
        
        var context = xypic.DrawingContext(xypic.Shape.none, env);
        xypicData.toShape(context);
        var shape = context.shape;
        shape.draw(xypicSVG);
        
        var shapeBBox = shape.getBoundingBox();
        if (shapeBBox !== undefined) {
          var box = xypic.Frame.Rect(
            0, 0,
            {
              l:Math.max(0, -(shapeBBox.x - shapeBBox.l)),
              r:Math.max(0, shapeBBox.x + shapeBBox.r),
              u:Math.max(0, shapeBBox.y + shapeBBox.u),
              d:Math.max(0, -(shapeBBox.y - shapeBBox.d))
            }
          );
          bbox = { h:(box.u + p + xypic.axis_height), d:(box.d + p - xypic.axis_height), w:(box.r + box.l + 2*p), l:(- box.l - p), r:(box.r + p)}
          
          this.SVGhandleSpace(svg);
          var xypicBBOX = BBOX.XYPIC(bbox, 0, 0, xypicSVG.drawArea);
          xypicBBOX.element.setAttribute("transform", "scale(" + (1 / SVG.em) + ") matrix(1 0 0 -1 0 0) translate(0," + xypic.em2px(- xypic.axis_height) + ")");
          svg.Add(xypicBBOX);
          
          
          // FIXME
          svg.x += box.l + p;
          svg.w -= box.l + p;
          
          
          this.SVGhandleColor(svg);
          this.SVGsaveData(svg);
        }
      }
      
      return svg;
    }
  });
  
  AST.xypic.newdir.Augment({
    toSVG: function () {
      var newdir = this.cmd;
      xypic.repositories.dirRepository.put(newdir.dirMain, newdir.compositeObject);
      return this.SVG();
    }
  });
  
  AST.xypic.includegraphics.Augment({
    toSVG: function (HW, DD) {
      this.SVGgetStyles();
      
      var svg = this.SVG();
      this.SVGhandleSpace(svg);
      
      var mu = this.SVGgetMu(svg);
      var scale = this.SVGgetScale();
      setupSVGMeasure(mu, scale);
      var t = AST.xypic.strokeWidth;
      
      var bbox = { h:xypic.oneem, d:0, w:xypic.oneem, lw:0, rw:xypic.oneem };
      var H = bbox.h, D = bbox.d, W = bbox.w;
      var color = "black";
      var xypicSVG = xypic.Graphics.createSVG(H, D, W, t, color, {
        viewBox:[0, -xypic.em2px(H + D), xypic.em2px(W), xypic.em2px(H + D)].join(" "),
        overflow:"visible"
      });
      xypic.svgForDebug = xypicSVG;
      xypic.svgForTestLayout = xypicSVG;
      
      var env = xypic.Env();
      var context = xypic.DrawingContext(xypic.Shape.none, env);
      
      var graphics = this.cmd;
      graphics.setup(context);
      if (!env.includegraphicsWidth.isDefined || !env.includegraphicsHeight.isDefined) {
        throw xypic.ExecutionError("the 'width' and 'height' attributes of the \\includegraphics are required.");
      }
      var imageWidth = env.includegraphicsWidth.get;
      var imageHeight = env.includegraphicsHeight.get;
      
      var c = env.c;
      c = c.toRect({ u:imageHeight - xypic.axis_height, d:xypic.axis_height, l:0, r:imageWidth });
      var imageShape = xypic.Shape.ImageShape(c, graphics.filepath);
      imageShape.draw(xypicSVG);
      
      var shapeBBox = imageShape.getBoundingBox();
      var box = xypic.Frame.Rect(
        0, 0,
        {
          l:Math.max(0, -(shapeBBox.x - shapeBBox.l)),
          r:Math.max(0, shapeBBox.x + shapeBBox.r),
          u:Math.max(0, shapeBBox.y + shapeBBox.u),
          d:Math.max(0, -(shapeBBox.y - shapeBBox.d))
        }
      );
      
      var bbox = { h:(box.u + xypic.axis_height), d:(box.d - xypic.axis_height), w:(box.r + box.l), l:(- box.l), r:(box.r)}
      
      this.SVGhandleSpace(svg);
      var xypicBBOX = BBOX.XYPIC(bbox, 0, 0, xypicSVG.drawArea);
      xypicBBOX.element.setAttribute("transform", "scale(" + (1 / SVG.em) + ") matrix(1 0 0 -1 0 0) translate(0," + xypic.em2px(- xypic.axis_height) + ")");
      svg.Add(xypicBBOX);
      svg.x += box.l;
      svg.w -= box.l;
      this.SVGhandleColor(svg);
      this.SVGsaveData(svg);
      
      return svg;
    }
  });
  
  MathJax.Hub.Startup.signal.Post("SVG Xy-pic Ready");
});

MathJax.Ajax.loadComplete("/static/XyJax/extensions/TeX/xypic.js");