fixed dependencies
This commit is contained in:
573
vendor/github.com/go-latex/latex/mtex/parser.go
generated
vendored
Normal file
573
vendor/github.com/go-latex/latex/mtex/parser.go
generated
vendored
Normal file
@@ -0,0 +1,573 @@
|
||||
// Copyright ©2020 The go-latex Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mtex
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-latex/latex"
|
||||
"github.com/go-latex/latex/ast"
|
||||
"github.com/go-latex/latex/font"
|
||||
"github.com/go-latex/latex/internal/tex2unicode"
|
||||
"github.com/go-latex/latex/mtex/symbols"
|
||||
"github.com/go-latex/latex/tex"
|
||||
)
|
||||
|
||||
// Parse parses a LaTeX math expression and returns the TeX-like box model
|
||||
// and an error if any.
|
||||
func Parse(expr string, fontSize, DPI float64, backend font.Backend) (tex.Node, error) {
|
||||
p := newParser(backend)
|
||||
return p.parse(expr, fontSize, DPI)
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
be font.Backend
|
||||
|
||||
expr string
|
||||
macros map[string]handler
|
||||
}
|
||||
|
||||
func newParser(be font.Backend) *parser {
|
||||
p := &parser{
|
||||
be: be,
|
||||
macros: make(map[string]handler),
|
||||
}
|
||||
p.init()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *parser) parse(x string, size, dpi float64) (tex.Node, error) {
|
||||
p.expr = x
|
||||
node, err := latex.ParseExpr(x)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse latex expression %q: %w", x, err)
|
||||
}
|
||||
|
||||
state := tex.NewState(p.be, font.Font{
|
||||
Name: "default",
|
||||
Size: size,
|
||||
Type: "rm",
|
||||
}, dpi)
|
||||
|
||||
v := visitor{p: p, state: state}
|
||||
ast.Walk(&v, node)
|
||||
nodes := tex.HListOf(v.nodes, true)
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
type visitor struct {
|
||||
p *parser
|
||||
nodes []tex.Node
|
||||
state tex.State
|
||||
math bool
|
||||
}
|
||||
|
||||
func (v *visitor) Visit(n ast.Node) ast.Visitor {
|
||||
switch n := n.(type) {
|
||||
case ast.List:
|
||||
case *ast.Symbol:
|
||||
switch {
|
||||
case v.math:
|
||||
h := v.p.handler(n.Text)
|
||||
if h == nil {
|
||||
panic("no handler for symbol [" + n.Text + "]")
|
||||
}
|
||||
v.nodes = append(v.nodes, h.Handle(v.p, n, v.state, v.math))
|
||||
default:
|
||||
v.nodes = append(v.nodes, tex.NewChar(string(n.Text), v.state, v.math))
|
||||
}
|
||||
case *ast.Word:
|
||||
var nodes []tex.Node
|
||||
for _, x := range n.Text {
|
||||
nodes = append(nodes, tex.NewChar(string(x), v.state, v.math))
|
||||
}
|
||||
v.nodes = append(v.nodes, tex.HListOf(nodes, true))
|
||||
case *ast.Literal:
|
||||
h := handlerFunc(handleSymbol)
|
||||
for _, c := range n.Text {
|
||||
n := &ast.Literal{Text: string(c)}
|
||||
v.nodes = append(v.nodes, h.Handle(v.p, n, v.state, v.math))
|
||||
}
|
||||
|
||||
case *ast.MathExpr:
|
||||
oldm := v.math
|
||||
oldt := v.state.Font.Type
|
||||
v.math = true
|
||||
v.state.Font.Type = rcparams("mathtext.default").(string)
|
||||
|
||||
for _, x := range n.List {
|
||||
v.Visit(x)
|
||||
}
|
||||
v.math = oldm
|
||||
v.state.Font.Type = oldt
|
||||
return nil
|
||||
|
||||
case *ast.Macro:
|
||||
if n.Name == nil {
|
||||
panic("macro with nil identifier")
|
||||
}
|
||||
macro := n.Name.Name
|
||||
h := v.p.handler(macro)
|
||||
if h == nil {
|
||||
panic(fmt.Errorf("unknown macro %q", macro))
|
||||
}
|
||||
v.nodes = append(v.nodes, h.Handle(v.p, n, v.state, v.math))
|
||||
return nil
|
||||
|
||||
case nil:
|
||||
return v
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("unknown ast node %T", n))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *parser) handleNode(node ast.Node, state tex.State, math bool) tex.Node {
|
||||
v := visitor{p: p, state: state, math: math}
|
||||
ast.Walk(&v, node)
|
||||
return tex.HListOf(v.nodes, true)
|
||||
}
|
||||
|
||||
func (p *parser) handler(name string) handler {
|
||||
if _, ok := spaceWidth[name]; ok {
|
||||
return handlerFunc(handleSpace)
|
||||
}
|
||||
if symbols.IsSpaced(name) || symbols.PunctuationSymbols.Has(name) {
|
||||
return handlerFunc(handleSymbol)
|
||||
}
|
||||
if name == `\hspace` {
|
||||
return handlerFunc(handleCustomSpace)
|
||||
}
|
||||
if symbols.FunctionNames.Has(name[1:]) { // drop leading `\`
|
||||
return handlerFunc(handleFunction)
|
||||
}
|
||||
switch name {
|
||||
case `\frac`:
|
||||
return handlerFunc(handleFrac)
|
||||
case `\dfrac`:
|
||||
return handlerFunc(handleDFrac)
|
||||
case `\tfrac`:
|
||||
return handlerFunc(handleTFrac)
|
||||
case `\binom`:
|
||||
return handlerFunc(handleBinom)
|
||||
// case `\genfrac`:
|
||||
// return handlerFunc(handleGenFrac)
|
||||
case `\sqrt`:
|
||||
return handlerFunc(handleSqrt)
|
||||
case `\overline`:
|
||||
return handlerFunc(handleOverline)
|
||||
}
|
||||
_, ok := p.macros[name]
|
||||
if ok {
|
||||
return handlerFunc(handleSymbol)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) init() {
|
||||
for _, k := range tex2unicode.Symbols() {
|
||||
p.macros[`\`+k] = builtinMacro("")
|
||||
}
|
||||
for k, v := range builtinMacros {
|
||||
p.macros[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func handleSymbol(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
pos := int(node.Pos())
|
||||
sym := ""
|
||||
switch node := node.(type) {
|
||||
case *ast.Macro:
|
||||
sym = node.Name.Name
|
||||
case *ast.Symbol:
|
||||
sym = node.Text
|
||||
case *ast.Word:
|
||||
sym = node.Text
|
||||
case *ast.Literal:
|
||||
sym = node.Text
|
||||
default:
|
||||
panic("invalid ast Node")
|
||||
}
|
||||
ch := tex.NewChar(sym, state, math)
|
||||
switch {
|
||||
case symbols.IsSpaced(sym):
|
||||
i := strings.LastIndexFunc(p.expr[:pos], func(r rune) bool {
|
||||
return r != ' '
|
||||
})
|
||||
prev := ""
|
||||
if i >= 0 {
|
||||
prev = string(p.expr[i])
|
||||
}
|
||||
switch {
|
||||
case symbols.BinaryOperators.Has(sym) && (len(strings.Split(p.expr[:pos], " ")) == 0 ||
|
||||
prev == "{" ||
|
||||
symbols.LeftDelim.Has(prev)):
|
||||
// binary operators at start of string should not be spaced
|
||||
return ch
|
||||
default:
|
||||
return tex.HListOf([]tex.Node{
|
||||
p.makeSpace(state, 0.2),
|
||||
ch,
|
||||
p.makeSpace(state, 0.2),
|
||||
}, true)
|
||||
}
|
||||
|
||||
case symbols.PunctuationSymbols.Has(sym):
|
||||
switch sym {
|
||||
case ".":
|
||||
pos := strings.Index(p.expr[pos:], sym)
|
||||
if (pos > 0 && isdigit(p.expr[pos-1])) &&
|
||||
(pos < len(p.expr)-1 && isdigit(p.expr[pos+1])) {
|
||||
// do not space dots as decimal separators.
|
||||
return ch
|
||||
}
|
||||
return tex.HListOf([]tex.Node{
|
||||
ch,
|
||||
p.makeSpace(state, 0.2),
|
||||
}, true)
|
||||
}
|
||||
panic("not implemented")
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
var spaceWidth = map[string]float64{
|
||||
`\,`: 0.16667, // 3/18 em = 3 mu
|
||||
`\thinspace`: 0.16667, // 3/18 em = 3 mu
|
||||
`\/`: 0.16667, // 3/18 em = 3 mu
|
||||
`\>`: 0.22222, // 4/18 em = 4 mu
|
||||
`\:`: 0.22222, // 4/18 em = 4 mu
|
||||
`\;`: 0.27778, // 5/18 em = 5 mu
|
||||
`\ `: 0.33333, // 6/18 em = 6 mu
|
||||
`~`: 0.33333, // 6/18 em = 6 mu, nonbreakable
|
||||
`\enspace`: 0.5, // 9/18 em = 9 mu
|
||||
`\quad`: 1, // 1 em = 18 mu
|
||||
`\qquad`: 2, // 2 em = 36 mu
|
||||
`\!`: -0.16667, // -3/18 em = -3 mu
|
||||
|
||||
}
|
||||
|
||||
func handleSpace(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
var (
|
||||
width float64
|
||||
ok bool
|
||||
)
|
||||
switch node := node.(type) {
|
||||
case *ast.Symbol:
|
||||
width, ok = spaceWidth[node.Text]
|
||||
case *ast.Macro:
|
||||
width, ok = spaceWidth[node.Name.Name]
|
||||
default:
|
||||
panic(fmt.Errorf("invalid ast node %#v (%T)", node, node))
|
||||
}
|
||||
if !ok {
|
||||
panic(fmt.Errorf("could not find a width for %#v (%T)", node, node))
|
||||
}
|
||||
|
||||
return p.makeSpace(state, width)
|
||||
}
|
||||
|
||||
func handleCustomSpace(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
macro := node.(*ast.Macro)
|
||||
arg := macro.Args[0].(*ast.Arg).List[0].(*ast.Literal).Text
|
||||
val, err := strconv.ParseFloat(arg, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not parse customspace: %+v", err))
|
||||
}
|
||||
return p.makeSpace(state, val)
|
||||
}
|
||||
|
||||
func handleFunction(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
macro := node.(*ast.Macro)
|
||||
state.Font.Type = "rm"
|
||||
fun := macro.Name.Name[1:] // drop leading `\`
|
||||
nodes := make([]tex.Node, 0, len(fun))
|
||||
for _, c := range fun {
|
||||
nodes = append(nodes, tex.NewChar(string(c), state, math))
|
||||
}
|
||||
return tex.HListOf(nodes, true)
|
||||
}
|
||||
|
||||
func handleFrac(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
var (
|
||||
macro = node.(*ast.Macro)
|
||||
thickness = state.Backend().UnderlineThickness(state.Font, state.DPI)
|
||||
numNode = ast.List(macro.Args[0].(*ast.Arg).List)
|
||||
denNode = ast.List(macro.Args[1].(*ast.Arg).List)
|
||||
)
|
||||
|
||||
num := p.handleNode(numNode, state, math)
|
||||
den := p.handleNode(denNode, state, math)
|
||||
|
||||
// FIXME(sbinet): this should be infered from the context.
|
||||
// ie: textStyle when in $ $ environment.
|
||||
// displayStyle when in \[\] environment.
|
||||
sty := textStyle
|
||||
|
||||
return p.genfrac("", "", thickness, sty, num, den, state)
|
||||
}
|
||||
|
||||
func handleDFrac(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
var (
|
||||
macro = node.(*ast.Macro)
|
||||
thickness = state.Backend().UnderlineThickness(state.Font, state.DPI)
|
||||
numNode = ast.List(macro.Args[0].(*ast.Arg).List)
|
||||
denNode = ast.List(macro.Args[1].(*ast.Arg).List)
|
||||
)
|
||||
|
||||
num := p.handleNode(numNode, state, math)
|
||||
den := p.handleNode(denNode, state, math)
|
||||
|
||||
return p.genfrac("", "", thickness, displayStyle, num, den, state)
|
||||
}
|
||||
|
||||
func handleTFrac(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
var (
|
||||
macro = node.(*ast.Macro)
|
||||
thickness = state.Backend().UnderlineThickness(state.Font, state.DPI)
|
||||
numNode = ast.List(macro.Args[0].(*ast.Arg).List)
|
||||
denNode = ast.List(macro.Args[1].(*ast.Arg).List)
|
||||
)
|
||||
|
||||
num := p.handleNode(numNode, state, math)
|
||||
den := p.handleNode(denNode, state, math)
|
||||
|
||||
return p.genfrac("", "", thickness, textStyle, num, den, state)
|
||||
}
|
||||
|
||||
func handleBinom(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
var (
|
||||
macro = node.(*ast.Macro)
|
||||
numNode = ast.List(macro.Args[0].(*ast.Arg).List)
|
||||
denNode = ast.List(macro.Args[1].(*ast.Arg).List)
|
||||
)
|
||||
|
||||
num := p.handleNode(numNode, state, math)
|
||||
den := p.handleNode(denNode, state, math)
|
||||
|
||||
return p.genfrac("(", ")", 0, textStyle, num, den, state)
|
||||
}
|
||||
|
||||
func (p *parser) genfrac(ldelim, rdelim string, rule float64, style mathStyleKind, num, den tex.Node, state tex.State) tex.Node {
|
||||
thickness := state.Backend().UnderlineThickness(state.Font, state.DPI)
|
||||
|
||||
if style != displayStyle {
|
||||
num.Shrink()
|
||||
den.Shrink()
|
||||
}
|
||||
|
||||
cnum := tex.HCentered([]tex.Node{num})
|
||||
cden := tex.HCentered([]tex.Node{den})
|
||||
width := math.Max(num.Width(), den.Width())
|
||||
|
||||
const additional = false // i.e.: exactly
|
||||
cnum.HPack(width, additional)
|
||||
cden.HPack(width, additional)
|
||||
|
||||
vlist := tex.VListOf([]tex.Node{
|
||||
cnum, // numerator
|
||||
tex.VBox(0, thickness*2), // space
|
||||
tex.HRule(state, rule), // rule
|
||||
tex.VBox(0, thickness*2), // space
|
||||
cden, // denominator
|
||||
})
|
||||
|
||||
// shift so the fraction line sits in the middle of the '=' sign
|
||||
fnt := state.Font
|
||||
fnt.Type = rcparams("mathtext.default").(string)
|
||||
metrics := state.Backend().Metrics("=", fnt, state.DPI, true)
|
||||
shift := cden.Height() - ((metrics.YMax+metrics.YMin)/2 - 3*thickness)
|
||||
vlist.SetShift(shift)
|
||||
|
||||
box := tex.HListOf([]tex.Node{vlist, tex.HBox(2 * thickness)}, true)
|
||||
if ldelim != "" || rdelim != "" {
|
||||
if ldelim == "" {
|
||||
ldelim = "."
|
||||
}
|
||||
if rdelim == "" {
|
||||
rdelim = "."
|
||||
}
|
||||
return p.autoSizedDelimiter(ldelim, []tex.Node{box}, rdelim, state)
|
||||
}
|
||||
|
||||
return box
|
||||
}
|
||||
|
||||
func handleSqrt(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
var (
|
||||
macro = node.(*ast.Macro)
|
||||
root tex.Node
|
||||
body *tex.HList
|
||||
)
|
||||
switch len(macro.Args) {
|
||||
case 2:
|
||||
root = p.handleNode(
|
||||
ast.List(macro.Args[0].(*ast.OptArg).List),
|
||||
state, math,
|
||||
)
|
||||
body = p.handleNode(
|
||||
ast.List(macro.Args[1].(*ast.Arg).List),
|
||||
state, math,
|
||||
).(*tex.HList)
|
||||
case 1:
|
||||
// ok
|
||||
body = p.handleNode(
|
||||
ast.List(macro.Args[0].(*ast.Arg).List),
|
||||
state, math,
|
||||
).(*tex.HList)
|
||||
default:
|
||||
panic("invalid sqrt")
|
||||
}
|
||||
|
||||
thickness := state.Backend().UnderlineThickness(state.Font, state.DPI)
|
||||
|
||||
// determine the height of the body, add a little extra to it so
|
||||
// it doesn't seem too cramped.
|
||||
height := body.Height() - body.Shift() + 5*thickness
|
||||
depth := body.Depth() + body.Shift()
|
||||
check := tex.AutoHeightChar(`\__sqrt__`, height, depth, state, 0)
|
||||
height = check.Height() - check.Shift()
|
||||
depth = check.Depth() + check.Shift()
|
||||
|
||||
// put a little extra space to the left and right of the body
|
||||
padded := tex.HListOf([]tex.Node{
|
||||
tex.HBox(2 * thickness),
|
||||
body,
|
||||
tex.HBox(2 * thickness),
|
||||
}, true)
|
||||
rhs := tex.VListOf([]tex.Node{
|
||||
tex.HRule(state, -1),
|
||||
tex.NewGlue("fill"),
|
||||
padded,
|
||||
})
|
||||
|
||||
// stretch the glue between the HRule and the body
|
||||
const additional = false
|
||||
rhs.VPack(height+(state.Font.Size*state.DPI)/(100*12), additional, depth)
|
||||
|
||||
// add the root and shift it upward so it is above the tick.
|
||||
switch root {
|
||||
case nil:
|
||||
root = tex.HBox(check.Width() * 0.5)
|
||||
default:
|
||||
root.Shrink()
|
||||
root.Shrink()
|
||||
}
|
||||
|
||||
vl := tex.VListOf([]tex.Node{
|
||||
tex.HListOf([]tex.Node{
|
||||
root,
|
||||
}, true),
|
||||
})
|
||||
vl.SetShift(-height * 0.6)
|
||||
|
||||
hl := tex.HListOf([]tex.Node{
|
||||
vl, // root
|
||||
// negative kerning to put root over tick
|
||||
tex.NewKern(-check.Width() * 0.5),
|
||||
check,
|
||||
rhs,
|
||||
}, true)
|
||||
|
||||
return hl
|
||||
}
|
||||
|
||||
func handleOverline(p *parser, node ast.Node, state tex.State, math bool) tex.Node {
|
||||
macro := node.(*ast.Macro)
|
||||
body := p.handleNode(
|
||||
ast.List(macro.Args[0].(*ast.Arg).List),
|
||||
state, math,
|
||||
).(*tex.HList)
|
||||
|
||||
thickness := state.Backend().UnderlineThickness(state.Font, state.DPI)
|
||||
|
||||
height := body.Height() - body.Shift() + 3*thickness
|
||||
depth := body.Depth() + body.Shift()
|
||||
|
||||
// place overline above body
|
||||
rhs := tex.VListOf([]tex.Node{
|
||||
tex.HRule(state, -1),
|
||||
tex.NewGlue("fill"),
|
||||
tex.HListOf([]tex.Node{body}, true),
|
||||
})
|
||||
|
||||
// stretch the glue between the HRule and the body
|
||||
const additional = false
|
||||
rhs.VPack(height+(state.Font.Size*state.DPI)/(100*12), additional, depth)
|
||||
|
||||
hl := tex.HListOf([]tex.Node{rhs}, true)
|
||||
return hl
|
||||
}
|
||||
|
||||
func (p *parser) makeSpace(state tex.State, percentage float64) *tex.Kern {
|
||||
const math = true
|
||||
fnt := state.Font
|
||||
fnt.Name = "it"
|
||||
fnt.Type = rcparams("mathtext.default").(string)
|
||||
width := p.be.Metrics("m", fnt, state.DPI, math).Advance
|
||||
return tex.NewKern(width * percentage)
|
||||
}
|
||||
|
||||
func (p *parser) autoSizedDelimiter(left string, middle []tex.Node, right string, state tex.State) tex.Node {
|
||||
var (
|
||||
height float64
|
||||
depth float64
|
||||
factor float64 = 1
|
||||
)
|
||||
|
||||
if len(middle) > 0 {
|
||||
for _, node := range middle {
|
||||
height = math.Max(height, node.Height())
|
||||
depth = math.Max(depth, node.Depth())
|
||||
}
|
||||
factor = 0
|
||||
}
|
||||
|
||||
var parts []tex.Node
|
||||
if left != "." {
|
||||
// \left. isn't supposed to produce any symbol
|
||||
ahc := tex.AutoHeightChar(left, height, depth, state, factor)
|
||||
parts = append(parts, ahc)
|
||||
}
|
||||
parts = append(parts, middle...)
|
||||
if right != "." {
|
||||
// \right. isn't supposed to produce any symbol
|
||||
ahc := tex.AutoHeightChar(right, height, depth, state, factor)
|
||||
parts = append(parts, ahc)
|
||||
}
|
||||
return tex.HListOf(parts, true)
|
||||
}
|
||||
|
||||
type mathStyleKind int
|
||||
|
||||
const (
|
||||
displayStyle mathStyleKind = iota
|
||||
textStyle
|
||||
//scriptStyle // FIXME
|
||||
//scriptScriptStyle // FIXME
|
||||
)
|
||||
|
||||
func rcparams(k string) interface{} {
|
||||
switch k {
|
||||
case "mathtext.default":
|
||||
return "it"
|
||||
default:
|
||||
panic("unknown rc.params key [" + k + "]")
|
||||
}
|
||||
}
|
||||
|
||||
func isdigit(v byte) bool {
|
||||
switch v {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user