blob: 979ae9ddf587e3bd7a4eb94d19342b937d33f124 [file] [log] [blame]
/*********************************
* Symbols for Basic Mathematics
********************************/
var Digit = P(VanillaSymbol, function(_, super_) {
_.createLeftOf = function(cursor) {
if (cursor.options.autoSubscriptNumerals
&& cursor.parent !== cursor.parent.parent.sub
&& ((cursor[L] instanceof Variable && cursor[L].isItalic !== false)
|| (cursor[L] instanceof SupSub
&& cursor[L][L] instanceof Variable
&& cursor[L][L].isItalic !== false))) {
LatexCmds._().createLeftOf(cursor);
super_.createLeftOf.call(this, cursor);
cursor.insRightOf(cursor.parent.parent);
}
else super_.createLeftOf.call(this, cursor);
};
});
var Variable = P(Symbol, function(_, super_) {
_.init = function(ch, html) {
super_.init.call(this, ch, '<var>'+(html || ch)+'</var>');
};
_.text = function() {
var text = this.ctrlSeq;
if (this[L] && !(this[L] instanceof Variable)
&& !(this[L] instanceof BinaryOperator)
&& this[L].ctrlSeq !== "\\ ")
text = '*' + text;
if (this[R] && !(this[R] instanceof BinaryOperator)
&& !(this[R] instanceof SupSub))
text += '*';
return text;
};
});
Options.p.autoCommands = { _maxLength: 0 };
optionProcessors.autoCommands = function(cmds) {
if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
throw '"'+cmds+'" not a space-delimited list of only letters';
}
var list = cmds.split(' '), dict = {}, maxLength = 0;
for (var i = 0; i < list.length; i += 1) {
var cmd = list[i];
if (cmd.length < 2) {
throw 'autocommand "'+cmd+'" not minimum length of 2';
}
if (LatexCmds[cmd] === OperatorName) {
throw '"' + cmd + '" is a built-in operator name';
}
dict[cmd] = 1;
maxLength = max(maxLength, cmd.length);
}
dict._maxLength = maxLength;
return dict;
};
var Letter = P(Variable, function(_, super_) {
_.init = function(ch) { return super_.init.call(this, this.letter = ch); };
_.createLeftOf = function(cursor) {
var autoCmds = cursor.options.autoCommands, maxLength = autoCmds._maxLength;
if (maxLength > 0) {
// want longest possible autocommand, so join together longest
// sequence of letters
var str = this.letter, l = cursor[L], i = 1;
while (l instanceof Letter && i < maxLength) {
str = l.letter + str, l = l[L], i += 1;
}
// check for an autocommand, going thru substrings longest to shortest
while (str.length) {
if (autoCmds.hasOwnProperty(str)) {
for (var i = 2, l = cursor[L]; i < str.length; i += 1, l = l[L]);
Fragment(l, cursor[L]).remove();
cursor[L] = l[L];
return LatexCmds[str](str).createLeftOf(cursor);
}
str = str.slice(1);
}
}
super_.createLeftOf.apply(this, arguments);
};
_.italicize = function(bool) {
this.isItalic = bool;
this.jQ.toggleClass('mq-operator-name', !bool);
return this;
};
_.finalizeTree = _.siblingDeleted = _.siblingCreated = function(opts, dir) {
// don't auto-un-italicize if the sibling to my right changed (dir === R or
// undefined) and it's now a Letter, it will un-italicize everyone
if (dir !== L && this[R] instanceof Letter) return;
this.autoUnItalicize(opts);
};
_.autoUnItalicize = function(opts) {
var autoOps = opts.autoOperatorNames;
if (autoOps._maxLength === 0) return;
// want longest possible operator names, so join together entire contiguous
// sequence of letters
var str = this.letter;
for (var l = this[L]; l instanceof Letter; l = l[L]) str = l.letter + str;
for (var r = this[R]; r instanceof Letter; r = r[R]) str += r.letter;
// removeClass and delete flags from all letters before figuring out
// which, if any, are part of an operator name
Fragment(l[R] || this.parent.ends[L], r[L] || this.parent.ends[R]).each(function(el) {
el.italicize(true).jQ.removeClass('mq-first mq-last mq-followed-by-supsub');
el.ctrlSeq = el.letter;
});
// check for operator names: at each position from left to right, check
// substrings from longest to shortest
outer: for (var i = 0, first = l[R] || this.parent.ends[L]; i < str.length; i += 1, first = first[R]) {
for (var len = min(autoOps._maxLength, str.length - i); len > 0; len -= 1) {
var word = str.slice(i, i + len);
if (autoOps.hasOwnProperty(word)) {
for (var j = 0, letter = first; j < len; j += 1, letter = letter[R]) {
letter.italicize(false);
var last = letter;
}
var isBuiltIn = BuiltInOpNames.hasOwnProperty(word);
first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq;
last.ctrlSeq += (isBuiltIn ? ' ' : '}');
if (TwoWordOpNames.hasOwnProperty(word)) last[L][L][L].jQ.addClass('mq-last');
if (!shouldOmitPadding(first[L])) first.jQ.addClass('mq-first');
if (!shouldOmitPadding(last[R]) && !(last[R] instanceof Bracket)) {
// also omit space between operator name and paren like in sin(x)
last.jQ.addClass('mq-last');
if (last[R] instanceof SupSub) {
last.jQ.addClass('mq-followed-by-supsub');
}
}
i += len - 1;
first = last;
continue outer;
}
}
}
};
function shouldOmitPadding(node) {
// omit padding if no node, or if node already has padding (to avoid double-padding)
return !node || (node instanceof BinaryOperator) || (node instanceof SummationNotation);
}
});
var BuiltInOpNames = {}; // the set of operator names like \sin, \cos, etc that
// are built-into LaTeX, see Section 3.17 of the Short Math Guide: http://tinyurl.com/jm9okjc
// MathQuill auto-unitalicizes some operator names not in that set, like 'hcf'
// and 'arsinh', which must be exported as \operatorname{hcf} and
// \operatorname{arsinh}. Note: over/under line/arrow \lim variants like
// \varlimsup are not supported
var AutoOpNames = Options.p.autoOperatorNames = { _maxLength: 9 }; // the set
// of operator names that MathQuill auto-unitalicizes by default; overridable
var TwoWordOpNames = { limsup: 1, liminf: 1, projlim: 1, injlim: 1 };
(function() {
var mostOps = ('arg deg det dim exp gcd hom inf ker lg lim ln log max min sup'
+ ' limsup liminf injlim projlim Pr').split(' ');
for (var i = 0; i < mostOps.length; i += 1) {
BuiltInOpNames[mostOps[i]] = AutoOpNames[mostOps[i]] = 1;
}
var builtInTrigs = // why coth but not sech and csch, LaTeX?
'sin cos tan arcsin arccos arctan sinh cosh tanh sec csc cot coth'.split(' ');
for (var i = 0; i < builtInTrigs.length; i += 1) {
BuiltInOpNames[builtInTrigs[i]] = 1;
}
var autoTrigs = 'sin cos tan sec cosec csc cotan cot ctg'.split(' ');
for (var i = 0; i < autoTrigs.length; i += 1) {
AutoOpNames[autoTrigs[i]] =
AutoOpNames['arc'+autoTrigs[i]] =
AutoOpNames[autoTrigs[i]+'h'] =
AutoOpNames['ar'+autoTrigs[i]+'h'] =
AutoOpNames['arc'+autoTrigs[i]+'h'] = 1;
}
// compat with some of the nonstandard LaTeX exported by MathQuill
// before #247. None of these are real LaTeX commands so, seems safe
var moreNonstandardOps = 'gcf hcf lcm proj span'.split(' ');
for (var i = 0; i < moreNonstandardOps.length; i += 1) {
AutoOpNames[moreNonstandardOps[i]] = 1;
}
}());
optionProcessors.autoOperatorNames = function(cmds) {
if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
throw '"'+cmds+'" not a space-delimited list of only letters';
}
var list = cmds.split(' '), dict = {}, maxLength = 0;
for (var i = 0; i < list.length; i += 1) {
var cmd = list[i];
if (cmd.length < 2) {
throw '"'+cmd+'" not minimum length of 2';
}
dict[cmd] = 1;
maxLength = max(maxLength, cmd.length);
}
dict._maxLength = maxLength;
return dict;
};
var OperatorName = P(Symbol, function(_, super_) {
_.init = function(fn) { this.ctrlSeq = fn; };
_.createLeftOf = function(cursor) {
var fn = this.ctrlSeq;
for (var i = 0; i < fn.length; i += 1) {
Letter(fn.charAt(i)).createLeftOf(cursor);
}
};
_.parser = function() {
var fn = this.ctrlSeq;
var block = MathBlock();
for (var i = 0; i < fn.length; i += 1) {
Letter(fn.charAt(i)).adopt(block, block.ends[R], 0);
}
return Parser.succeed(block.children());
};
});
for (var fn in AutoOpNames) if (AutoOpNames.hasOwnProperty(fn)) {
LatexCmds[fn] = OperatorName;
}
LatexCmds.operatorname = P(MathCommand, function(_) {
_.createLeftOf = noop;
_.numBlocks = function() { return 1; };
_.parser = function() {
return latexMathParser.block.map(function(b) { return b.children(); });
};
});
LatexCmds.f = P(Letter, function(_, super_) {
_.init = function() {
Symbol.p.init.call(this, this.letter = 'f', '<var class="mq-f">f</var>');
};
_.italicize = function(bool) {
this.jQ.html('f').toggleClass('mq-f', bool);
return super_.italicize.apply(this, arguments);
};
});
// VanillaSymbol's
LatexCmds[' '] = LatexCmds.space = bind(VanillaSymbol, '\\ ', '&nbsp;');
LatexCmds["'"] = LatexCmds.prime = bind(VanillaSymbol, "'", '&prime;');
LatexCmds.backslash = bind(VanillaSymbol,'\\backslash ','\\');
if (!CharCmds['\\']) CharCmds['\\'] = LatexCmds.backslash;
LatexCmds.$ = bind(VanillaSymbol, '\\$', '$');
// does not use Symbola font
var NonSymbolaSymbol = P(Symbol, function(_, super_) {
_.init = function(ch, html) {
super_.init.call(this, ch, '<span class="mq-nonSymbola">'+(html || ch)+'</span>');
};
});
LatexCmds['@'] = NonSymbolaSymbol;
LatexCmds['&'] = bind(NonSymbolaSymbol, '\\&', '&amp;');
LatexCmds['%'] = bind(NonSymbolaSymbol, '\\%', '%');
//the following are all Greek to me, but this helped a lot: http://www.ams.org/STIX/ion/stixsig03.html
//lowercase Greek letter variables
LatexCmds.alpha =
LatexCmds.beta =
LatexCmds.gamma =
LatexCmds.delta =
LatexCmds.zeta =
LatexCmds.eta =
LatexCmds.theta =
LatexCmds.iota =
LatexCmds.kappa =
LatexCmds.mu =
LatexCmds.nu =
LatexCmds.xi =
LatexCmds.rho =
LatexCmds.sigma =
LatexCmds.tau =
LatexCmds.chi =
LatexCmds.psi =
LatexCmds.omega = P(Variable, function(_, super_) {
_.init = function(latex) {
super_.init.call(this,'\\'+latex+' ','&'+latex+';');
};
});
//why can't anybody FUCKING agree on these
LatexCmds.phi = //W3C or Unicode?
bind(Variable,'\\phi ','&#981;');
LatexCmds.phiv = //Elsevier and 9573-13
LatexCmds.varphi = //AMS and LaTeX
bind(Variable,'\\varphi ','&phi;');
LatexCmds.epsilon = //W3C or Unicode?
bind(Variable,'\\epsilon ','&#1013;');
LatexCmds.epsiv = //Elsevier and 9573-13
LatexCmds.varepsilon = //AMS and LaTeX
bind(Variable,'\\varepsilon ','&epsilon;');
LatexCmds.piv = //W3C/Unicode and Elsevier and 9573-13
LatexCmds.varpi = //AMS and LaTeX
bind(Variable,'\\varpi ','&piv;');
LatexCmds.sigmaf = //W3C/Unicode
LatexCmds.sigmav = //Elsevier
LatexCmds.varsigma = //LaTeX
bind(Variable,'\\varsigma ','&sigmaf;');
LatexCmds.thetav = //Elsevier and 9573-13
LatexCmds.vartheta = //AMS and LaTeX
LatexCmds.thetasym = //W3C/Unicode
bind(Variable,'\\vartheta ','&thetasym;');
LatexCmds.upsilon = //AMS and LaTeX and W3C/Unicode
LatexCmds.upsi = //Elsevier and 9573-13
bind(Variable,'\\upsilon ','&upsilon;');
//these aren't even mentioned in the HTML character entity references
LatexCmds.gammad = //Elsevier
LatexCmds.Gammad = //9573-13 -- WTF, right? I dunno if this was a typo in the reference (see above)
LatexCmds.digamma = //LaTeX
bind(Variable,'\\digamma ','&#989;');
LatexCmds.kappav = //Elsevier
LatexCmds.varkappa = //AMS and LaTeX
bind(Variable,'\\varkappa ','&#1008;');
LatexCmds.rhov = //Elsevier and 9573-13
LatexCmds.varrho = //AMS and LaTeX
bind(Variable,'\\varrho ','&#1009;');
//Greek constants, look best in non-italicized Times New Roman
LatexCmds.pi = LatexCmds['π'] = bind(NonSymbolaSymbol,'\\pi ','&pi;');
LatexCmds.lambda = bind(NonSymbolaSymbol,'\\lambda ','&lambda;');
//uppercase greek letters
LatexCmds.Upsilon = //LaTeX
LatexCmds.Upsi = //Elsevier and 9573-13
LatexCmds.upsih = //W3C/Unicode "upsilon with hook"
LatexCmds.Upsih = //'cos it makes sense to me
bind(Symbol,'\\Upsilon ','<var style="font-family: serif">&upsih;</var>'); //Symbola's 'upsilon with a hook' is a capital Y without hooks :(
//other symbols with the same LaTeX command and HTML character entity reference
LatexCmds.Gamma =
LatexCmds.Delta =
LatexCmds.Theta =
LatexCmds.Lambda =
LatexCmds.Xi =
LatexCmds.Pi =
LatexCmds.Sigma =
LatexCmds.Phi =
LatexCmds.Psi =
LatexCmds.Omega =
LatexCmds.forall = P(VanillaSymbol, function(_, super_) {
_.init = function(latex) {
super_.init.call(this,'\\'+latex+' ','&'+latex+';');
};
});
// symbols that aren't a single MathCommand, but are instead a whole
// Fragment. Creates the Fragment from a LaTeX string
var LatexFragment = P(MathCommand, function(_) {
_.init = function(latex) { this.latex = latex; };
_.createLeftOf = function(cursor) {
var block = latexMathParser.parse(this.latex);
block.children().adopt(cursor.parent, cursor[L], cursor[R]);
cursor[L] = block.ends[R];
block.jQize().insertBefore(cursor.jQ);
block.finalizeInsert(cursor.options, cursor);
if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L);
if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R);
cursor.parent.bubble('reflow');
};
_.parser = function() {
var frag = latexMathParser.parse(this.latex).children();
return Parser.succeed(frag);
};
});
// for what seems to me like [stupid reasons][1], Unicode provides
// subscripted and superscripted versions of all ten Arabic numerals,
// as well as [so-called "vulgar fractions"][2].
// Nobody really cares about most of them, but some of them actually
// predate Unicode, dating back to [ISO-8859-1][3], apparently also
// known as "Latin-1", which among other things [Windows-1252][4]
// largely coincides with, so Microsoft Word sometimes inserts them
// and they get copy-pasted into MathQuill.
//
// (Irrelevant but funny story: though not a superset of Latin-1 aka
// ISO-8859-1, Windows-1252 **is** a strict superset of the "closely
// related but distinct"[3] "ISO 8859-1" -- see the lack of a dash
// after "ISO"? Completely different character set, like elephants vs
// elephant seals, or "Zombies" vs "Zombie Redneck Torture Family".
// What kind of idiot would get them confused.
// People in fact got them confused so much, it was so common to
// mislabel Windows-1252 text as ISO-8859-1, that most modern web
// browsers and email clients treat the MIME charset of ISO-8859-1
// as actually Windows-1252, behavior now standard in the HTML5 spec.)
//
// [1]: http://en.wikipedia.org/wiki/Unicode_subscripts_andsuper_scripts
// [2]: http://en.wikipedia.org/wiki/Number_Forms
// [3]: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
// [4]: http://en.wikipedia.org/wiki/Windows-1252
LatexCmds['¹'] = bind(LatexFragment, '^1');
LatexCmds['²'] = bind(LatexFragment, '^2');
LatexCmds['³'] = bind(LatexFragment, '^3');
LatexCmds['¼'] = bind(LatexFragment, '\\frac14');
LatexCmds['½'] = bind(LatexFragment, '\\frac12');
LatexCmds['¾'] = bind(LatexFragment, '\\frac34');
var PlusMinus = P(BinaryOperator, function(_) {
_.init = VanillaSymbol.prototype.init;
_.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
if (dir === R) return; // ignore if sibling only changed on the right
// If the left sibling is a binary operator or a separator (comma, semicolon, colon)
// or an open bracket (open parenthesis, open square bracket)
// consider the operator to be unary, otherwise binary
this.jQ[0].className =
(!this[L] || this[L] instanceof BinaryOperator || /^[,;:\(\[]$/.test(this[L].ctrlSeq) ? '' : 'mq-binary-operator');
return this;
};
});
LatexCmds['+'] = bind(PlusMinus, '+', '+');
//yes, these are different dashes, I think one is an en dash and the other is a hyphen
LatexCmds['–'] = LatexCmds['-'] = bind(PlusMinus, '-', '&minus;');
LatexCmds['±'] = LatexCmds.pm = LatexCmds.plusmn = LatexCmds.plusminus =
bind(PlusMinus,'\\pm ','&plusmn;');
LatexCmds.mp = LatexCmds.mnplus = LatexCmds.minusplus =
bind(PlusMinus,'\\mp ','&#8723;');
CharCmds['*'] = LatexCmds.sdot = LatexCmds.cdot =
bind(BinaryOperator, '\\cdot ', '&middot;', '*');
//semantically should be &sdot;, but &middot; looks better
var Inequality = P(BinaryOperator, function(_, super_) {
_.init = function(data, strict) {
this.data = data;
this.strict = strict;
var strictness = (strict ? 'Strict' : '');
super_.init.call(this, data['ctrlSeq'+strictness], data['html'+strictness],
data['text'+strictness]);
};
_.swap = function(strict) {
this.strict = strict;
var strictness = (strict ? 'Strict' : '');
this.ctrlSeq = this.data['ctrlSeq'+strictness];
this.jQ.html(this.data['html'+strictness]);
this.textTemplate = [ this.data['text'+strictness] ];
};
_.deleteTowards = function(dir, cursor) {
if (dir === L && !this.strict) {
this.swap(true);
this.bubble('reflow');
return;
}
super_.deleteTowards.apply(this, arguments);
};
});
var less = { ctrlSeq: '\\le ', html: '&le;', text: '≤',
ctrlSeqStrict: '<', htmlStrict: '&lt;', textStrict: '<' };
var greater = { ctrlSeq: '\\ge ', html: '&ge;', text: '≥',
ctrlSeqStrict: '>', htmlStrict: '&gt;', textStrict: '>' };
LatexCmds['<'] = LatexCmds.lt = bind(Inequality, less, true);
LatexCmds['>'] = LatexCmds.gt = bind(Inequality, greater, true);
LatexCmds['≤'] = LatexCmds.le = LatexCmds.leq = bind(Inequality, less, false);
LatexCmds['≥'] = LatexCmds.ge = LatexCmds.geq = bind(Inequality, greater, false);
var Equality = P(BinaryOperator, function(_, super_) {
_.init = function() {
super_.init.call(this, '=', '=');
};
_.createLeftOf = function(cursor) {
if (cursor[L] instanceof Inequality && cursor[L].strict) {
cursor[L].swap(false);
cursor[L].bubble('reflow');
return;
}
super_.createLeftOf.apply(this, arguments);
};
});
LatexCmds['='] = Equality;
LatexCmds['×'] = LatexCmds.times = bind(BinaryOperator, '\\times ', '&times;', '[x]');
LatexCmds['÷'] = LatexCmds.div = LatexCmds.divide = LatexCmds.divides =
bind(BinaryOperator,'\\div ','&divide;', '[/]');
CharCmds['~'] = LatexCmds.sim = bind(BinaryOperator, '\\sim ', '~', '~');