如何改变数学字体?
答案1
与常规 TeX 中的方法相同。有关详细信息,请参阅第 22 章“TeX by Topic”。
更新:抱歉,我的第一条回复太简短了。以下是 Jonathan Kew 编写的在纯 XeTeX 中设置 OpenType 数学的基础知识示例:http://gist.github.com/616937
但请注意,它需要 TECkit 将 ASCII 字母映射到数学字母(称为mapping=math-italic
)。不幸的是,我没有这些文件的副本。但请参阅这个问题由 Adam LS 讲解如何自己创建它们。
答案2
通过本网站的各种问答获得更多知识后,我认为我可以对这个问题提供更为全面的答案。
为了使 XeTeX 能够使用纯文本格式的 OpenType Unicode 数学字体,您需要一些东西:
映射文件。OpenType 数学字体(如 XITS Math、Asana Math 和 Neo Euler)将所有字形都放在一个字体文件中。为了能够从 fraktur、bold、double-struck 等中选择字母“A”的类型,我们需要一种方法来告诉 XeTeX 该范围如何映射到所需的样式。为此,存在一个名为“teckit_编译“它将映射文件编译成二进制形式,以便我们可以在 XeTeX 中加载字体时指定所需的样式。
TeX 命令名称。这可能是一个很长的列表,需要输入。即使在输入了所有普通命令名称之后,您也会开始怀疑是否可以为 TeX 提供更多 Unicode 提供的字形。STIX 字体项目完成了这项艰巨的任务,并发布了一个列表,其中包含构建 TeX 数学命令所需的一切:
- unicode 代码点
- TeX 命令名称(和可能的别名)
- TeX 数学类
因为列出每个命令名称就需要大约 3000 行,所以我决定编写一个又长又乱又丑的红宝石脚本来构建映射文件和命令名。
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
现在您有了映射文件和命令列表。要真正使用其中任何一个,您需要加载一些字体(和其他东西)。第一个\input
ed 文件是使用 -switch 创建的文件--cmds
。其中有一些重复项,因此您可以将其输出通过管道传输到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 }
我不太确定最后的那些\XeTeXmathcode
s 和s。\XeTeXdelcode