After acquiring more knowledge through various questions and answers in this site, I think I can provide a somewhat more thorough answer to this question.
In order to have OpenType Unicode math fonts with plain-tex format used with
XeTeX, you will need a couple of things:
Mapping files. The OpenType math fonts like XITS Math, Asana Math, and
Neo Euler have all the glyphs inside one single font file. To be able to
choose the type of, say, letter "A" from fraktur, bold, double-struck, etc.,
we need a way to tell XeTeX how that range maps to the wanted style. For
this purpose, there exists a program called "teckit_compile" which compiles a
mapping file into a binary form so we can specify the wanted style when
loading a font in XeTeX.
TeX command names. This can be a long list to start typing.
And even after you've typed all the plain command names, you start to wonder
whether you could have more of the Unicode provided glyphs available to TeX.
The STIX fonts project has gone through this huge task, and published a list
containing everything one needs for constructing a TeX math command:
- the unicode codepoint
- the TeX command name (and possible aliases)
- the TeX math class
Because listing every command name would take ~3k lines alone, I decided to compile a long, messy and ugly ruby script to construct the mapping files and command names, instead.
require 'open-uri'
#
# To use this script, execute it with ruby 1.8.x
#
# Usage: ruby thisfile.rb [ --maps | --cmds ]
#
# Without any arguments this script does nothing (visible, anyway, so you can
# play around with the data in irb)
#
# --maps Creates the files in the directory where you are
# executing this script, so change to appropriate directory first
# (i.e. $TEXMFHOME/fonts/misc/xetex/fontmapping
# on OS X, for example, $TEXMFHOME can be ~/Library/texmf)
# It will overwrite the mapping files without asking!
#
# --cmds Prints the TeX commands to terminal. So to have them go into
# a file, you can "ruby thisfile.rb --cmds > somefile.tex"
@blocks = {
# This is a hash of Unicode alnum ranges spelled out here to ease the
# construction of the mapping files.
#
:roman => {
:digits => (0x0030..0x0039).to_a,
:latin => (0x0041..0x005A).to_a.push((0x0061..0x007A).to_a).flatten,
:greek => (0x0391..0x03A9).to_a.push(0x2207). # push nabla
fill(0x03F4,17,1). # put Theta into the reserved slot
push((0x03B1..0x03C9).to_a. # small greek
push(0x2202, 0x03F5, 0x03F0, 0x03D5, 0x03F1, 0x03D6)). # push partial,
# varepsilon, varkappa, phi, varrho, and varpi
flatten },
"math alnum symbols" => {
"bold" => {
:latin => (0x1D400..0x1D433).to_a,
:greek => (0x1D6A8..0x1D6E1).to_a,
:digits => (0x1D7CE..0x1D7D7).to_a },
"italic" => {
:latin => (0x1D434..0x1D467).to_a.fill(0x210E,33,1), # h
:greek => (0x1D6E2..0x1D71B).to_a },
"bold italic" => {
:latin => (0x1D468..0x1D49B).to_a,
:greek => (0x1D71C..0x1D755).to_a },
"script" => {
:latin => (0x1D49C..0x1D4CF).to_a.fill(
0x212C,1,1).fill( # B
0x2130,4,1).fill( # E
0x2131,5,1).fill( # F
0x210B,7,1).fill( # H
0x2110,8,1).fill( # I
0x2112,11,1).fill( # L
0x2133,12,1).fill( # M
0x211B,17,1).fill( # R
0x212F,30,1).fill( # e
0x210A,32,1).fill( # g
0x2134,40,1) }, # o
"script bold" => { :latin => (0x1D4D0..0x1D503).to_a },
"fraktur" => {
:latin => (0x1D504..0x1D537).to_a.fill(
0x212D,2,1).fill( # C
0x210C,7,1).fill( # H
0x2111,8,1).fill( # I
0x211C,17,1).fill( # R
0x2128,25,1) }, # Z
"double-struck" => {
:latin => (0x1D538..0x1D56B).to_a.fill(
0x2102,2,1).fill( # C
0x210D,7,1).fill( # H
0x2115,13,1).fill( # N
0x2119,15,1).fill( # P
0x211A,16,1).fill( # Q
0x211D,17,1).fill( # R
0x2124,25,1), # Z
:digits => (0x1D7D8..0x1D7E1).to_a },
"fraktur bold" => { :latin => (0x1D56C..0x1D59F).to_a },
"sans-serif" => {
:latin => (0x1D5A0..0x1D5D3).to_a,
:digits => (0x1D7E2..0x1D7EB).to_a },
"sans-serif bold" => {
:latin => (0x1D5D4..0x1D607).to_a,
:greek => (0x1D756..0x1D78F).to_a,
:digits => (0x1D7EC..0x1D7F5).to_a },
"sans-serif italic" => { :latin => (0x1D608..0x1D63B).to_a },
"sans-serif bold italic" => {
:latin => (0x1D63C..0x1D66F).to_a,
:greek => (0x1D790..0x1D7C9).to_a },
"monospace" => {
:latin => (0x1D670..0x1D6A3).to_a,
:digits => (0x1D7F6..0x1D7FF).to_a }
}
}
if ARGV.any? {|arg| arg == '--maps'}
# Build the mapping files from the above Unicode blocks hash by zipping the
# roman block(s) together with the math alnum blocks, and print them in the
# required format. Then run teckit_compile on them if it's found in $PATH.
@blocks["math alnum symbols"].each do |fam,range|
file = File.open("#{fam}.map", 'w')
file.puts "LHSName \"#{fam}\"\nRHSName \"UNICODE\"\npass(unicode)"
range.map {|key,val| @blocks[:roman][key].zip(val) }.each do |f|
f.each {|pair| file.puts pair.map {|i| "U+%04X" % i.to_s }.join(" > ")}
end
file.close
`teckit_compile "#{file.path}"` if `which teckit_compile`
end
end
# And that's it for the mapping files. Next up: TeX command names.
@barbaras_table = "#{ENV['HOME']}/stix-tbl.ascii-2006-10-20.txt"
# ^ I've copied it to my home directory, you can change that to
# http://www.ams.org/STIX/bnb/stix-tbl.ascii-2006-10-20
# but note that due to its size (1.6 MB), it can take a long while to compile
# the command list.
@classes = {
# This is a mapping hash for math classes from Barbara's table to TeX.
#
# Barbara's table includes a column with the following meanings,
# which I copied from "Unicode Technical Report 25 on mathematics".
#
"A" => '"7"0', # Alphabetic
"B" => '"2"0', # Binary
"C" => '"5"0', # Closing; Usually paired with opening delimiter
"D" => '"7"0', # Diacritic
"F" => '"0"0', # Fence
"G" => '"0"0', # Glyph_Part; Pieces for assembling large operators, brackets or arrows
"L" => '"1"0', # Large; N-ary or Large operator, often takes limits
"N" => '"0"0', # Normal; This includes all digits and symbols requiring only one form
"O" => '"4"0', # Opening; Usually paired with closing delimiter
"P" => '"6"0', # Punctuation
"R" => '"3"0', # Relational; Includes arrows
"S" => '"6"0', # Space; Space character
"U" => '"0"0', # Unary; Unary operators
"V" => '"0"0', # Vary; Operators that can be unary or binary
"X" => '"0"0' # Special; Compatibility character
#
# Then there are the TeX math classes used above:
# 0 = Ordinary
# 1 = Large operator
# 2 = Binary operator
# 3 = Relation
# 4 = Opening
# 5 = Closing
# 6 = Punctuation
# 7 = Variable family
#
# I've left all of the TeX family assignments to 0 (Roman) so I can assign
# more families to accommodate the math alnum block.
#
# I'm not sure of all of my choices above, but they're easy enough to change!
}
@commands = Array.new
open(@barbaras_table).each_with_index do |line,index|
break if index > 2837 # We'll handle the math alnum block later
next if index < 3 # Skip the headers
@commands << line.split.select do |words|
# Select words which contain either unicode codepoint, a TeX command name,
# TeX command alias, or a TeX math class (although, we can't be sure
# about anything yet).
words =~ /(\\|(0x)?[0-9A-F]{4,}|\b[#{@classes.keys.join}]\b)/i and
words !~ /(text|Bb|scr|frak)/i # skip the fam's and text stuff
end
end
if ARGV.any? {|arg| arg == '--cmds'}
# Onto constructing TeX commands. We have an array of "stuff" which can
# possibly contain all we need, but the "stuff" needs to be molded a little
@commands.map {|items| # Clean up the input
[ items.first.sub(/[^0-9A-F]/i,''), # remove non-hex stuff from the codepoint
items.select {|c| c =~ /^[#{@classes.keys.join}]$/}.first, # class
items.select {|c| c =~ /\\/}]}. # command name and possible aliases
reject {|r| r.any? {|r| r.nil? or r.empty?}}. # Ditch anything we can't use
map {|cp,cls,cmds|
# Differentiate between the actual command name and its aliases, and
# remove stuff before the command name
[cp, cls, cmds.shift.sub(/^[^\\]+/,'')] + cmds unless cmds.empty?}.
uniq.each do |cp,cls,cmd,shrt|
puts case cls
when "D" : "\\def#{cmd}{\\XeTeXmathaccent#{@classes[cls]}\"#{cp} }"
when /[FOC]/ : "\\def#{cmd}{\\XeTeXdelimiter#{@classes[cls]}\"#{cp} }"
else "\\XeTeXmathchardef#{cmd}#{@classes[cls]}\"#{cp}"
end unless cmd =~ /^\\\d/ # actually, don't do anything if the command name begins with a digit
# the (possible) alias(es) FIXME: ugly duckling
puts "\\let#{shrt}#{cmd}" if shrt and shrt !~ /#{cmd}/ and
shrt != cmd and shrt !~ /\\not/ and shrt =~ /^\\/
end
# Spell out the greek range 'cause I don't think I can grab it from anywhere.
%w(Alpha Beta Gamma Delta Epsilon Zeta Eta varTheta Iota Kappa Lambda
Mu Nu Xi Omicron Pi Rho Theta Sigma Tau Upsilon Phi Chi Psi Omega nabla
alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi
omicron pi rho varsigma sigma tau upsilon varphi chi psi omega
partial varepsilon varkappa phi varrho varpi).
zip(@blocks[:roman][:greek]).each_with_index do |(cmd,cp),i|
puts "\\XeTeXmathchardef\\#{cmd}=\"7\"#{i < 26 ? 0 : 1}\"#{"%04X" % cp}"
# Assign the uppercase greek to roman, and lowercase to italic.
# Both are variable class.
end
end
So now you have the mapping files and a list of commands. To actually be able to use any of these, you'll need to load some fonts (and stuff). The first \inputed file is the one created with the --cmds -switch. There's some duplicates in there so you could pipe its output to uniq.
\input mchardefs
\ifdefined\mathfont\else\def\mathfont{XITS Math}\fi
\ifdefined\mathsize\else\def\mathsize{10pt}\fi
%
\def\hex#1{\ifcase#10\or1\or2\or3\or4\or5\or6\or7\or8\or9\or
A\or B\or C\or D\or E\or F\fi}
%
\def\loadmfont#1#2#3{% font name, options, family number
\def\opts{script=math;kern}
\expandafter\font\csname#1\endcsname="\mathfont:\opts;#2" at \mathsize
\expandafter\font\csname#1s\endcsname="\mathfont/S=7:\opts;+ssty=0;#2"
at \dimexpr\mathsize-3pt
\expandafter\font\csname#1ss\endcsname="\mathfont/S=5:\opts;+ssy=1;#2"
at \dimexpr\mathsize-5pt
\textfont#3=\csname#1\endcsname
\scriptfont#3=\csname#1s\endcsname
\scriptscriptfont#3=\csname#1ss\endcsname}
%
\loadmfont{mathrm}{}{0}
% Italic
\def\testEuler{Neo Euler} % Neo Euler doesn't have italic, so dont map
\ifx\mathfont\testEuler
\textfont1=\mathrm \scriptfont1=\mathrms \scriptscriptfont1=\mathrmss
\else
\loadmfont{mathit}{mapping=italic}{1}
\fi
% Calligraphic
\def\testAsana{Asana Math}
\ifx\mathfont\testAsana
% Asana uses Stylistic Alternates over script
\loadmfont{mathcal}{mapping=script;+salt}{2}
\else
% XITS and Neo Euler use Stylistic Set 1 over script
\loadmfont{mathcal}{mapping=script;+ss01}{2}
\fi
\loadmfont{mathbi}{mapping=bold italic}{3}
\def\mib{\fam3}
% Double-struck (aka Blackboard bold)
\ifx\mathfont\testEuler
\def\bb{\fam6} % Neo Euler doesn't have double-struck, so map it to bold
% and some other stuff...
\def\coloneq{:=}
\else
\loadmfont{mathbb}{mapping=double-struck}{4}
\def\bb{\fam4}
\fi
\loadmfont{mathsl}{slant=0.2}{5}
\loadmfont{mathbf}{mapping=bold}{6}
\loadmfont{mathtt}{mapping=monospace}{7}
% Fraktur
\newfam\frakbffam
\def\frakbf{\tenbf\fam\frakbffam}
\loadmfont{mathfrakbf}{mapping=fraktur bold}{"\hex\frakbffam}
%
\newfam\frakfam
\def\frak{\let\bf\frakbf\fam\frakfam}
\loadmfont{mathfrak}{mapping=fraktur}{"\hex\frakfam}
% Script
\newfam\scriptbffam
\def\scrbf{\tenbf\fam\scriptbffam}
\loadmfont{mathscrbf}{mapping=script bold}{"\hex\scriptbffam}
%
\newfam\scriptfam
\def\scr{\let\bf\scrbf\fam\scriptfam}
\loadmfont{mathscr}{mapping=script}{"\hex\scriptfam}
% Sans-serif
\newfam\sansbfitfam
\def\sfbfit{\fam\sansbfitfam}
\loadmfont{mathsfbfit}{mapping=sans-serif bold italic}{"\hex\sansbfitfam}
%
\newfam\sansbffam
\def\sfbf{\tenbf\fam\sansbffam\let\it\sfbfit}
\loadmfont{mathsfbf}{mapping=sans-serif bold}{"\hex\sansbffam}
%
\newfam\sansitfam
\def\sfit{\tenit\fam\sansitfam\let\bf\sfbfit}
\loadmfont{mathsfit}{mapping=sans-serif italic}{"\hex\sansitfam}
%
\newfam\sansfam
\def\sf{\let\bf\sfbf\let\it\sfit\fam\sansfam}
\loadmfont{mathsf}{mapping=sans-serif}{"\hex\sansfam}
%
% make it work both ways: \scr\bf or \bf\scr
\def\bf{\let\frak\frakbf\let\scr\scrbf\let\sf\sfbf\let\it\mib\tenbf\fam6 }
\def\it{\let\sf\sfit\let\bf\mib\tenit\fam1 }
%
\let\{=\lbrace
\let\}=\rbrace
\let\|=\Vert
\let\to\rightarrow
\let\gets\leftarrow
\let\coleq\coloneq
\let\overline\overbar
\def\setminus{\XeTeXdelimiter"2"0"005C } % Barbara's table uses smallsetminus
\XeTeXmathcode`\,="6"1`,
\XeTeXmathcode`\.="6"1`.
\XeTeXdelcode`\|="0"007C
\XeTeXmathcode`\|="0"0"007C
\XeTeXdelcode`\/="0"2044
\XeTeXmathcode`\-="2"0"2212
\XeTeXmathcode`\/="0"0"2215
\XeTeXmathcode`\*="2"0"2217
\def\hat{\XeTeXmathaccent"7"0"002C6 } % Barbara's table uses the wide variants
\def\check{\XeTeXmathaccent"7"0"002C7 }
\def\bar{\XeTeXmathaccent"7"0"002C9 }
\def\breve{\XeTeXmathaccent"7"0"002D8 }
\def\tilde{\XeTeXmathaccent"7"0"002DC }
I'm not sure about those \XeTeXmathcodes and \XeTeXdelcodes at the end.