How does one change the math font?


Same way it's done in regular TeX. See TeX by Topic, chapter 22, for details.

Update: sorry, my first reply was overly brief. Here's an example written by Jonathan Kew of the basics of setting up OpenType math in plain XeTeX: http://gist.github.com/616937

Note, however, that it requires a TECkit mapping from ascii letters to mathematical ones (referred to by the mapping=math-italic lines). Unfortunately, I don't have a copy of these files. But see this question by Adam L.S. on how to create them yourself.

  • I followed the instructions there, and ended up with the following document: \def\mathfont{Asana Math} \font\mathit="\mathfont/I" \font\mathitseven="\mathfont/I" at 7pt \font\mathitfive="\mathfont/I" at 5pt \newfam\MyFam \textfont\MyFam=\mathit \scriptfont\MyFam=\mathitseven \scriptscriptfont\MyFam=\mathitfive but I cannot see any change; the document stays with Computer Modern. – morbusg Oct 8 '10 at 13:53

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:

  1. 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.

  2. 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(" > ")}
    `teckit_compile "#{file.path}"` if `which teckit_compile`
# 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

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 =~ /^\\/

  # 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.

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
  A\or B\or C\or D\or E\or F\fi}
\def\loadmfont#1#2#3{% font name, options, family number
  \expandafter\font\csname#1\endcsname="\mathfont:\opts;#2" at \mathsize
    at \dimexpr\mathsize-3pt
    at \dimexpr\mathsize-5pt
% Italic
  \def\testEuler{Neo Euler} % Neo Euler doesn't have italic, so dont map
    \textfont1=\mathrm \scriptfont1=\mathrms \scriptscriptfont1=\mathrmss
% Calligraphic
  \def\testAsana{Asana Math}
    % Asana uses Stylistic Alternates over script
    % XITS and Neo Euler use Stylistic Set 1 over script
\loadmfont{mathbi}{mapping=bold italic}{3}
% Double-struck (aka Blackboard bold)
    \def\bb{\fam6} % Neo Euler doesn't have double-struck, so map it to bold
    % and some other stuff...
% Fraktur
  \loadmfont{mathfrakbf}{mapping=fraktur bold}{"\hex\frakbffam}
% Script
  \loadmfont{mathscrbf}{mapping=script bold}{"\hex\scriptbffam}
% Sans-serif
  \loadmfont{mathsfbfit}{mapping=sans-serif bold italic}{"\hex\sansbfitfam}
  \loadmfont{mathsfbf}{mapping=sans-serif bold}{"\hex\sansbffam}
  \loadmfont{mathsfit}{mapping=sans-serif italic}{"\hex\sansitfam}
% 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 }
\def\setminus{\XeTeXdelimiter"2"0"005C } % Barbara's table uses smallsetminus
\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.

  • Nice, thanks. I suggest creating a GitHub (or other) repository for this code so others can reference it more easily (perhaps even publishing it on CTAN). Would you also be able to publish you mapping files? – Will Robertson Nov 29 '10 at 22:57
  • @Will Robertson: I thought it would be easier from here (if it is OK), both google-wise and wiki-wise. The ita-mapping file is essentially the same as the one made by Adam L. S. in the link of your answer, except for epsilon (left-hand side as U+03D0), and varepsilon (right-hand side as U+1D700). As for the other mappings, they are just a place-holder for now because I couldn't understand how to input the GID-area. – morbusg Dec 5 '10 at 12:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.