1227 lines
30 KiB
Go
1227 lines
30 KiB
Go
// 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 tex
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
|
|
"github.com/go-latex/latex/font"
|
|
)
|
|
|
|
const (
|
|
// How much text shrinks when going to the next-smallest level. growFactor
|
|
// must be the inverse of shrinkFactor.
|
|
shrinkFactor = 0.7
|
|
growFactor = 1.0 / shrinkFactor
|
|
|
|
// The number of different sizes of chars to use, beyond which they will not
|
|
// get any smaller
|
|
numSizeLevels = 6
|
|
)
|
|
|
|
// FontConstants is a set of magical values that control how certain things,
|
|
// such as sub- and superscripts are laid out.
|
|
// These are all metrics that can't be reliably retrieved from the font metrics
|
|
// in the font itself.
|
|
type FontConstants struct {
|
|
// Percentage of x-height of additional horiz. space after sub/superscripts
|
|
ScriptSpace float64
|
|
|
|
// Percentage of x-height that sub/superscripts drop below the baseline
|
|
SubDrop float64
|
|
|
|
// Percentage of x-height that superscripts are raised from the baseline
|
|
Sup1 float64
|
|
|
|
// Percentage of x-height that subscripts drop below the baseline
|
|
Sub1 float64
|
|
|
|
// Percentage of x-height that subscripts drop below the baseline when a
|
|
// superscript is present
|
|
Sub2 float64
|
|
|
|
// Percentage of x-height that sub/supercripts are offset relative to the
|
|
// nucleus edge for non-slanted nuclei
|
|
Delta float64
|
|
|
|
// Additional percentage of last character height above 2/3 of the
|
|
// x-height that supercripts are offset relative to the subscript
|
|
// for slanted nuclei
|
|
DeltaSlanted float64
|
|
|
|
// Percentage of x-height that supercripts and subscripts are offset for
|
|
// integrals
|
|
DeltaIntegral float64
|
|
}
|
|
|
|
var DefaultFontConstants = FontConstants{
|
|
ScriptSpace: 0.05,
|
|
SubDrop: 0.4,
|
|
Sup1: 0.7,
|
|
Sub1: 0.3,
|
|
Sub2: 0.5,
|
|
Delta: 0.025,
|
|
DeltaSlanted: 0.2,
|
|
DeltaIntegral: 0.1,
|
|
}
|
|
|
|
// Node represents a node in the TeX box model.
|
|
type Node interface {
|
|
// Kerning returns the amount of kerning between this and the next node.
|
|
Kerning(next Node) float64
|
|
|
|
// Shrinks one level smaller.
|
|
// There are only three levels of sizes, after which things
|
|
// will no longer get smaller.
|
|
Shrink()
|
|
|
|
// Grows one level larger.
|
|
// There is no limit to how big something can get.
|
|
Grow()
|
|
|
|
// Render renders the node at (x,y) on the canvas.
|
|
Render(x, y float64)
|
|
|
|
// Width returns the width of this node.
|
|
Width() float64
|
|
|
|
// Height returns the height of this node.
|
|
Height() float64
|
|
|
|
// Depth returns the depth of this node.
|
|
Depth() float64
|
|
}
|
|
|
|
// Box is a node with a physical location
|
|
type Box struct {
|
|
size int
|
|
width float64
|
|
height float64
|
|
depth float64
|
|
}
|
|
|
|
func newBox(w, h, d float64) *Box {
|
|
return &Box{width: w, height: h, depth: d}
|
|
}
|
|
|
|
func (*Box) Kerning(next Node) float64 { return 0 }
|
|
|
|
func (box *Box) Shrink() {
|
|
box.size--
|
|
if box.size < numSizeLevels {
|
|
box.width *= shrinkFactor
|
|
box.height *= shrinkFactor
|
|
box.depth *= shrinkFactor
|
|
}
|
|
}
|
|
|
|
func (box *Box) Grow() {
|
|
box.size++
|
|
box.width *= growFactor
|
|
box.height *= growFactor
|
|
box.depth *= growFactor
|
|
}
|
|
|
|
func (*Box) Render(x, y float64) {}
|
|
|
|
// Width returns the width of this node.
|
|
func (box *Box) Width() float64 { return box.width }
|
|
|
|
// Height returns the height of this node.
|
|
func (box *Box) Height() float64 { return box.height }
|
|
|
|
// Depth returns the depth of this node.
|
|
func (box *Box) Depth() float64 { return box.depth }
|
|
|
|
func (box *Box) hpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
*width += box.width
|
|
if math.IsInf(box.height, 0) || math.IsInf(box.depth, 0) {
|
|
return
|
|
}
|
|
*height = math.Max(*height, box.height)
|
|
*depth = math.Max(*depth, box.depth)
|
|
}
|
|
|
|
func (box *Box) vpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
*height += *depth + box.height
|
|
*depth = box.depth
|
|
if math.IsInf(box.width, 0) {
|
|
return
|
|
}
|
|
*width = math.Max(*width, box.width)
|
|
}
|
|
|
|
// VBox is a box with a height but no width.
|
|
func VBox(h, d float64) *Box {
|
|
return newBox(0, h, d)
|
|
}
|
|
|
|
// HBox is a box with a width but no height nor depth.
|
|
func HBox(w float64) *Box {
|
|
return newBox(w, 0, 0)
|
|
}
|
|
|
|
// Char is a single character.
|
|
//
|
|
// Unlike TeX, the font information and metrics are stored with each `Char`
|
|
// to make it easier to lookup the font metrics when needed. Note that TeX
|
|
// boxes have a width, height, and depth, unlike Type1 and TrueType which use
|
|
// a full bounding box and an advance in the x-direction. The metrics must
|
|
// be converted to the TeX model, and the advance (if different from width)
|
|
// must be converted into a `Kern` node when the `Char` is added to its parent
|
|
// `HList`.
|
|
type Char struct {
|
|
c string
|
|
|
|
size int
|
|
width float64
|
|
height float64
|
|
depth float64
|
|
metrics font.Metrics
|
|
|
|
be font.Backend
|
|
font font.Font
|
|
dpi float64
|
|
math bool
|
|
}
|
|
|
|
func NewChar(c string, state State, math bool) *Char {
|
|
ch := &Char{
|
|
c: c,
|
|
be: state.Backend(),
|
|
font: state.Font,
|
|
dpi: state.DPI,
|
|
math: math,
|
|
}
|
|
ch.updateMetrics()
|
|
return ch
|
|
}
|
|
|
|
func (ch *Char) updateMetrics() {
|
|
ch.metrics = ch.be.Metrics(
|
|
ch.c, ch.font, ch.dpi,
|
|
ch.math,
|
|
)
|
|
switch ch.c {
|
|
case " ":
|
|
ch.width = ch.metrics.Advance
|
|
default:
|
|
ch.width = ch.metrics.Width
|
|
}
|
|
ch.height = ch.metrics.Iceberg
|
|
ch.depth = -(ch.metrics.Iceberg - ch.metrics.Height)
|
|
}
|
|
|
|
func (c *Char) String() string { return c.c }
|
|
|
|
func (c *Char) Kerning(next Node) float64 {
|
|
adv := c.metrics.Advance - c.Width()
|
|
kern := 0.0
|
|
switch next := next.(type) {
|
|
case *Char:
|
|
kern = c.be.Kern(c.font, c.c, next.font, next.c, c.dpi)
|
|
case *Accent:
|
|
kern = c.be.Kern(c.font, c.c, next.char.font, next.char.c, c.dpi)
|
|
}
|
|
return adv + kern
|
|
}
|
|
|
|
func (box *Char) Shrink() {
|
|
box.size--
|
|
if box.size < numSizeLevels {
|
|
box.font.Size *= shrinkFactor
|
|
box.width *= shrinkFactor
|
|
box.height *= shrinkFactor
|
|
box.depth *= shrinkFactor
|
|
}
|
|
}
|
|
|
|
func (box *Char) Grow() {
|
|
box.size++
|
|
box.font.Size *= growFactor
|
|
box.width *= growFactor
|
|
box.height *= growFactor
|
|
box.depth *= growFactor
|
|
}
|
|
|
|
func (c *Char) Render(x, y float64) {
|
|
c.be.RenderGlyph(x, y, c.font, c.c, c.dpi)
|
|
}
|
|
|
|
// Width returns the width of this node.
|
|
func (c *Char) Width() float64 { return c.width }
|
|
|
|
// Height returns the height of this node.
|
|
func (c *Char) Height() float64 { return c.height }
|
|
|
|
// Depth returns the depth of this node.
|
|
func (c *Char) Depth() float64 { return c.depth }
|
|
|
|
func (c Char) hpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
*width += c.width
|
|
*height = math.Max(*height, c.height)
|
|
*depth = math.Max(*depth, c.depth)
|
|
}
|
|
|
|
func (*Char) vpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
panic("Char node in VList")
|
|
}
|
|
|
|
// Accent is a character with an accent.
|
|
// Accents need to be dealt with separately as they are already offset
|
|
// from the baseline in TrueType fonts.
|
|
type Accent struct {
|
|
char Char
|
|
}
|
|
|
|
func NewAccent(c string, state State, math bool) *Accent {
|
|
acc := &Accent{
|
|
char: Char{
|
|
c: c,
|
|
be: state.Backend(),
|
|
font: state.Font,
|
|
dpi: state.DPI,
|
|
math: math,
|
|
},
|
|
}
|
|
acc.updateMetrics()
|
|
return acc
|
|
}
|
|
|
|
func (acc *Accent) updateMetrics() {
|
|
acc.char.metrics = acc.char.be.Metrics(
|
|
acc.char.c, acc.char.font, acc.char.dpi,
|
|
acc.char.math,
|
|
)
|
|
acc.char.width = acc.char.metrics.XMax - acc.char.metrics.XMin
|
|
acc.char.height = acc.char.metrics.YMax - acc.char.metrics.YMin
|
|
acc.char.depth = 0
|
|
}
|
|
|
|
func (acc *Accent) String() string { return acc.char.String() }
|
|
func (acc *Accent) Kerning(next Node) float64 { return acc.char.Kerning(next) }
|
|
|
|
func (acc *Accent) Shrink() {
|
|
acc.char.Shrink()
|
|
acc.updateMetrics()
|
|
}
|
|
|
|
func (acc *Accent) Grow() {
|
|
acc.char.Grow()
|
|
acc.updateMetrics()
|
|
}
|
|
|
|
func (acc *Accent) Render(x, y float64) {
|
|
acc.char.be.RenderGlyph(
|
|
x-acc.char.metrics.XMin,
|
|
y+acc.char.metrics.YMin,
|
|
acc.char.font,
|
|
acc.char.c,
|
|
acc.char.dpi,
|
|
)
|
|
}
|
|
|
|
// Width returns the width of this node.
|
|
func (acc *Accent) Width() float64 { return acc.char.width }
|
|
|
|
// Height returns the height of this node.
|
|
func (acc *Accent) Height() float64 { return acc.char.height }
|
|
|
|
// Depth returns the depth of this node.
|
|
func (acc *Accent) Depth() float64 { return acc.char.depth }
|
|
|
|
func (acc *Accent) hpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
acc.char.hpackDims(width, height, depth, stretch, shrink)
|
|
}
|
|
|
|
func (*Accent) vpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
panic("Accent node in VList")
|
|
}
|
|
|
|
// List is a list of vertical or horizontal nodes.
|
|
type List struct {
|
|
box Box
|
|
shift float64 // shift is an arbitrary offset.
|
|
children []Node // children nodes of this list.
|
|
|
|
glue struct {
|
|
set float64 // glue setting of this list
|
|
sign int // 0: normal, -1: shrinking, 1: stretching
|
|
order int // the order of infinity (0 - 3) for the glue.
|
|
ratio float64
|
|
}
|
|
}
|
|
|
|
func ListOf(elements []Node) *List {
|
|
list := &List{children: make([]Node, len(elements))}
|
|
copy(list.children, elements)
|
|
return list
|
|
}
|
|
|
|
// determineOrder determines the highest order of glue used by the members
|
|
// of a List.
|
|
//
|
|
// used by VPack and HPack.
|
|
func determineOrder(totals []float64) int {
|
|
for i := len(totals) - 1; i >= 0; i-- {
|
|
if totals[i] != 0 {
|
|
return i
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (lst *List) setGlue(x float64, sign int, totals []float64, errMsg, typ string) {
|
|
o := determineOrder(totals)
|
|
lst.glue.order = o
|
|
lst.glue.sign = sign
|
|
switch {
|
|
case totals[o] != 0:
|
|
lst.glue.set = x / totals[o]
|
|
default:
|
|
lst.glue.sign = 0
|
|
lst.glue.ratio = 0
|
|
}
|
|
if o == 0 {
|
|
if len(lst.children) > 0 {
|
|
log.Printf("%s %s: %v", errMsg, typ, lst.children)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (lst *List) Kerning(next Node) float64 {
|
|
return lst.box.Kerning(next)
|
|
}
|
|
|
|
func (lst *List) Shrink() {
|
|
for _, node := range lst.children {
|
|
node.Shrink()
|
|
}
|
|
lst.box.Shrink()
|
|
if lst.box.size < numSizeLevels {
|
|
lst.shift *= shrinkFactor
|
|
lst.glue.set *= shrinkFactor
|
|
}
|
|
}
|
|
|
|
func (lst *List) Grow() {
|
|
for _, node := range lst.children {
|
|
node.Grow()
|
|
}
|
|
lst.box.Grow()
|
|
lst.shift *= growFactor
|
|
lst.glue.set *= growFactor
|
|
}
|
|
|
|
func (lst *List) Render(x, y float64) {
|
|
lst.box.Render(x, y)
|
|
}
|
|
|
|
// Width returns the width of this node.
|
|
func (lst *List) Width() float64 { return lst.box.Width() }
|
|
|
|
// Height returns the height of this node.
|
|
func (lst *List) Height() float64 { return lst.box.Height() }
|
|
|
|
// Depth returns the depth of this node.
|
|
func (lst *List) Depth() float64 { return lst.box.Depth() }
|
|
|
|
func (lst *List) Nodes() []Node { return lst.children }
|
|
func (lst *List) GlueOrder() int { return lst.glue.order }
|
|
func (lst *List) GlueSign() int { return lst.glue.sign }
|
|
func (lst *List) GlueSet() float64 { return lst.glue.set }
|
|
|
|
func (lst *List) hpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
*width += lst.box.width
|
|
if math.IsInf(lst.box.height, 0) || math.IsInf(lst.box.depth, 0) {
|
|
return
|
|
}
|
|
*height = math.Max(*height, lst.box.height-lst.shift)
|
|
*depth = math.Max(*depth, lst.box.depth+lst.shift)
|
|
}
|
|
|
|
func (lst *List) vpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
*height += *depth + lst.box.height
|
|
*depth = lst.box.depth
|
|
if math.IsInf(lst.box.width, 0) {
|
|
return
|
|
}
|
|
*width = math.Max(*width, lst.box.width)
|
|
}
|
|
|
|
// HList is a horizontal list of boxes.
|
|
type HList struct {
|
|
lst List
|
|
}
|
|
|
|
func HListOf(elements []Node, doKern bool) *HList {
|
|
lst := &HList{
|
|
lst: *ListOf(elements),
|
|
}
|
|
if doKern {
|
|
lst.kern()
|
|
}
|
|
const (
|
|
width = 0
|
|
additional = true
|
|
)
|
|
lst.HPack(width, additional)
|
|
return lst
|
|
}
|
|
|
|
// kern inserts Kern nodes between Char nodes to set kerning.
|
|
//
|
|
// The Char nodes themselves determine the amount of kerning they need.
|
|
// This method just creates the correct list.
|
|
func (lst *HList) kern() {
|
|
if len(lst.lst.children) == 0 {
|
|
return
|
|
}
|
|
var (
|
|
n = len(lst.lst.children)
|
|
children = make([]Node, 0, n)
|
|
)
|
|
for i := range lst.lst.children {
|
|
var (
|
|
elem = lst.lst.children[i]
|
|
next Node
|
|
dist float64
|
|
)
|
|
if i < n-1 {
|
|
next = lst.lst.children[i+1]
|
|
}
|
|
dist = elem.Kerning(next)
|
|
children = append(children, elem)
|
|
if dist != 0 {
|
|
children = append(children, NewKern(dist))
|
|
}
|
|
}
|
|
lst.lst.children = children
|
|
}
|
|
|
|
// HPack computes the dimensions of the resulting boxes, and adjusts the glue
|
|
// if one of those dimensions is pre-specified.
|
|
//
|
|
// The computed sizes normally enclose all of the material inside the new box;
|
|
// but some items may stick out if negative glue is used, if the box is
|
|
// overfull, or if a `\vbox` includes other boxes that have been shifted left.
|
|
//
|
|
// If additional is false, HPack will produce a box whose width is exactly as
|
|
// wide as the given 'width'.
|
|
// Otherwise, HPack will produce a box with the natural width of the contents,
|
|
// plus the given 'width'.
|
|
func (lst *HList) HPack(width float64, additional bool) {
|
|
var (
|
|
h float64
|
|
d float64
|
|
x float64
|
|
|
|
totStretch = make([]float64, 4)
|
|
totShrink = make([]float64, 4)
|
|
)
|
|
|
|
for _, node := range lst.lst.children {
|
|
switch node := node.(type) {
|
|
case hpacker:
|
|
node.hpackDims(&x, &h, &d, totStretch, totShrink)
|
|
default:
|
|
panic(fmt.Errorf("unknown node type %T", node))
|
|
}
|
|
}
|
|
lst.lst.box.height = h
|
|
lst.lst.box.depth = d
|
|
|
|
if additional {
|
|
width += x
|
|
}
|
|
lst.lst.box.width = width
|
|
x = width - x
|
|
switch {
|
|
case x == 0:
|
|
lst.lst.glue.sign = 0
|
|
lst.lst.glue.order = 0
|
|
lst.lst.glue.ratio = 0
|
|
case x > 0:
|
|
lst.lst.setGlue(x, 1, totStretch, "overfull", "HList")
|
|
default:
|
|
lst.lst.setGlue(x, -1, totShrink, "underfull", "HList")
|
|
}
|
|
}
|
|
|
|
func (lst *HList) Kerning(next Node) float64 { return lst.lst.Kerning(next) }
|
|
func (lst *HList) Shrink() { lst.lst.Shrink() }
|
|
func (lst *HList) Grow() { lst.lst.Grow() }
|
|
func (lst *HList) Render(x, y float64) { lst.lst.Render(x, x) }
|
|
|
|
// Width returns the width of this node.
|
|
func (lst *HList) Width() float64 { return lst.lst.Width() }
|
|
|
|
// Height returns the height of this node.
|
|
func (lst *HList) Height() float64 { return lst.lst.Height() }
|
|
|
|
// Depth returns the depth of this node.
|
|
func (lst *HList) Depth() float64 { return lst.lst.Depth() }
|
|
|
|
func (lst *HList) Nodes() []Node { return lst.lst.Nodes() }
|
|
func (lst *HList) GlueOrder() int { return lst.lst.GlueOrder() }
|
|
func (lst *HList) GlueSign() int { return lst.lst.GlueSign() }
|
|
func (lst *HList) GlueSet() float64 { return lst.lst.GlueSet() }
|
|
func (lst *HList) Shift() float64 { return lst.lst.shift }
|
|
|
|
func (lst *HList) hpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
lst.lst.hpackDims(width, height, depth, stretch, shrink)
|
|
}
|
|
|
|
func (lst *HList) vpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
lst.lst.vpackDims(width, height, depth, stretch, shrink)
|
|
}
|
|
|
|
// VList is a vertical list of boxes.
|
|
type VList struct {
|
|
lst List
|
|
}
|
|
|
|
func (lst *VList) SetShift(s float64) { lst.lst.shift = s }
|
|
|
|
func VListOf(elements []Node) *VList {
|
|
lst := &VList{lst: *ListOf(elements)}
|
|
var (
|
|
height float64
|
|
additional = true
|
|
max = math.Inf(+1)
|
|
)
|
|
lst.VPack(height, additional, max)
|
|
return lst
|
|
}
|
|
|
|
// VPack computes the dimensions of the resulting boxes, and adjusts the
|
|
// glue if one of those dimensions is pre-specified.
|
|
//
|
|
// If additional is false, VPack will produce a box whose height is exactly as
|
|
// tall as the given 'height'.
|
|
// Otherwise, VPack will produce a box with the natural height of the contents,
|
|
// plus the given 'height'.
|
|
func (lst *VList) VPack(height float64, additional bool, l float64) {
|
|
var (
|
|
w float64
|
|
d float64
|
|
x float64
|
|
|
|
totStretch = make([]float64, 4)
|
|
totShrink = make([]float64, 4)
|
|
)
|
|
|
|
for _, node := range lst.lst.children {
|
|
switch node := node.(type) {
|
|
case vpacker:
|
|
node.vpackDims(&w, &x, &d, totStretch, totShrink)
|
|
}
|
|
}
|
|
|
|
lst.lst.box.width = w
|
|
switch {
|
|
case d > l:
|
|
x += d - l
|
|
lst.lst.box.depth = l
|
|
default:
|
|
lst.lst.box.depth = d
|
|
}
|
|
|
|
if additional {
|
|
height += x
|
|
}
|
|
lst.lst.box.height = height
|
|
x = height - x
|
|
|
|
switch {
|
|
case x == 0:
|
|
lst.lst.glue.sign = 0
|
|
lst.lst.glue.order = 0
|
|
lst.lst.glue.ratio = 0
|
|
case x > 0:
|
|
lst.lst.setGlue(x, +1, totStretch, "overfull", "VList")
|
|
default:
|
|
lst.lst.setGlue(x, -1, totShrink, "underfull", "VList")
|
|
}
|
|
}
|
|
|
|
func (lst *VList) Kerning(next Node) float64 { return lst.lst.Kerning(next) }
|
|
func (lst *VList) Shrink() { lst.lst.Shrink() }
|
|
func (lst *VList) Grow() { lst.lst.Grow() }
|
|
func (lst *VList) Render(x, y float64) { lst.lst.Render(x, y) }
|
|
|
|
// Width returns the width of this node.
|
|
func (lst *VList) Width() float64 { return lst.lst.Width() }
|
|
|
|
// Height returns the height of this node.
|
|
func (lst *VList) Height() float64 { return lst.lst.Height() }
|
|
|
|
// Depth returns the depth of this node.
|
|
func (lst *VList) Depth() float64 { return lst.lst.Depth() }
|
|
|
|
func (lst *VList) Nodes() []Node { return lst.lst.Nodes() }
|
|
func (lst *VList) GlueOrder() int { return lst.lst.GlueOrder() }
|
|
func (lst *VList) GlueSign() int { return lst.lst.GlueSign() }
|
|
func (lst *VList) GlueSet() float64 { return lst.lst.GlueSet() }
|
|
|
|
func (lst *VList) hpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
lst.lst.hpackDims(width, height, depth, stretch, shrink)
|
|
}
|
|
|
|
func (lst *VList) vpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
lst.lst.vpackDims(width, height, depth, stretch, shrink)
|
|
}
|
|
|
|
// Rule is a solid black rectangle.
|
|
//
|
|
// Like a HList, Rule has a width, a depth and a height.
|
|
// However, if any of these dimensions is ∞, the actual value will be
|
|
// determined by running the rule up to the boundary of the innermost
|
|
// enclosing box.
|
|
// This is called a "running dimension".
|
|
// The width is never running in an HList; the height and depth are never
|
|
// running in a VList.
|
|
type Rule struct {
|
|
box Box
|
|
out font.Backend
|
|
}
|
|
|
|
func NewRule(w, h, d float64, state State) *Rule {
|
|
return &Rule{
|
|
box: *newBox(w, h, d),
|
|
out: state.Backend(),
|
|
}
|
|
}
|
|
|
|
func (rule *Rule) String() string {
|
|
return fmt.Sprintf(
|
|
"Rule{w=%g, h=%g, d=%g}",
|
|
rule.Width(), rule.Height(), rule.Depth(),
|
|
)
|
|
}
|
|
|
|
func (rule *Rule) render(x, y, w, h float64) {
|
|
rule.out.RenderRectFilled(x, y, x+w, y+h)
|
|
}
|
|
|
|
func (rule *Rule) Kerning(next Node) float64 { return rule.box.Kerning(next) }
|
|
func (rule *Rule) Shrink() { rule.box.Shrink() }
|
|
func (rule *Rule) Grow() { rule.box.Grow() }
|
|
func (rule *Rule) Render(x, y float64) { rule.box.Render(x, y) }
|
|
|
|
// Width returns the width of this node.
|
|
func (rule *Rule) Width() float64 { return rule.box.Width() }
|
|
|
|
// Height returns the height of this node.
|
|
func (rule *Rule) Height() float64 { return rule.box.Height() }
|
|
|
|
// Depth returns the depth of this node.
|
|
func (rule *Rule) Depth() float64 { return rule.box.Depth() }
|
|
|
|
func (rule *Rule) hpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
rule.box.hpackDims(width, height, depth, stretch, shrink)
|
|
}
|
|
|
|
func (rule *Rule) vpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
rule.box.vpackDims(width, height, depth, stretch, shrink)
|
|
}
|
|
|
|
// HRule is a horizontal rule.
|
|
func HRule(state State, thickness float64) *Rule {
|
|
if thickness < 0 {
|
|
thickness = state.Backend().UnderlineThickness(state.Font, state.DPI)
|
|
}
|
|
var (
|
|
height = 0.5 * thickness
|
|
depth = 0.5 * thickness
|
|
)
|
|
return NewRule(math.Inf(+1), height, depth, state)
|
|
}
|
|
|
|
// VRule is a vertical rule.
|
|
func VRule(state State) *Rule {
|
|
thickness := state.Backend().UnderlineThickness(state.Font, state.DPI)
|
|
return NewRule(thickness, math.Inf(+1), math.Inf(+1), state)
|
|
}
|
|
|
|
type Glue struct {
|
|
size int
|
|
width float64
|
|
stretch float64
|
|
stretchOrder int
|
|
shrink float64
|
|
shrinkOrder int
|
|
}
|
|
|
|
func NewGlue(typ string) *Glue {
|
|
switch typ {
|
|
case "fil":
|
|
return newGlue(0, 1, 1, 0, 0)
|
|
case "fill":
|
|
return newGlue(0, 1, 2, 0, 0)
|
|
case "filll":
|
|
return newGlue(0, 1, 3, 0, 0)
|
|
case "neg_fil":
|
|
return newGlue(0, 0, 0, 1, 1)
|
|
case "neg_fill":
|
|
return newGlue(0, 0, 0, 1, 2)
|
|
case "neg_filll":
|
|
return newGlue(0, 0, 0, 1, 3)
|
|
case "empty":
|
|
return &Glue{}
|
|
case "ss":
|
|
return newGlue(0, 1, 1, -1, 1)
|
|
default:
|
|
panic(fmt.Errorf("tex: unknown Glue spec %q", typ))
|
|
}
|
|
}
|
|
|
|
func newGlue(w, st float64, sto int, sh float64, sho int) *Glue {
|
|
return &Glue{
|
|
size: 0,
|
|
width: w,
|
|
stretch: st,
|
|
stretchOrder: sto,
|
|
shrink: sh,
|
|
shrinkOrder: sho,
|
|
}
|
|
}
|
|
|
|
func (g *Glue) Kerning(next Node) float64 { return 0 }
|
|
|
|
func (g *Glue) Shrink() {
|
|
g.size--
|
|
if g.size < numSizeLevels {
|
|
g.width *= shrinkFactor
|
|
}
|
|
}
|
|
|
|
func (g *Glue) Grow() {
|
|
g.size++
|
|
g.width *= growFactor
|
|
}
|
|
|
|
func (g *Glue) Render(x, y float64) {}
|
|
|
|
// Width returns the width of this node.
|
|
func (g *Glue) Width() float64 { return g.width }
|
|
|
|
// Height returns the height of this node.
|
|
func (g *Glue) Height() float64 { return 0 }
|
|
|
|
// Depth returns the depth of this node.
|
|
func (g *Glue) Depth() float64 { return 0 }
|
|
|
|
func (g *Glue) hpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
*width += g.width
|
|
stretch[g.stretchOrder] += g.stretch
|
|
shrink[g.shrinkOrder] += g.shrink
|
|
}
|
|
|
|
func (g *Glue) vpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
*height += *depth
|
|
*depth = 0
|
|
*height += g.width
|
|
stretch[g.stretchOrder] += g.stretch
|
|
shrink[g.shrinkOrder] += g.shrink
|
|
}
|
|
|
|
// HCentered creates an HList whose contents are centered within
|
|
// its enclosing box.
|
|
func HCentered(elements []Node) *HList {
|
|
const doKern = false
|
|
nodes := make([]Node, 0, len(elements)+2)
|
|
nodes = append(nodes, NewGlue("ss"))
|
|
nodes = append(nodes, elements...)
|
|
nodes = append(nodes, NewGlue("ss"))
|
|
return HListOf(nodes, doKern)
|
|
}
|
|
|
|
// VCentered creates a VList whose contents are centered within
|
|
// its enclosing box.
|
|
func VCentered(elements []Node) *VList {
|
|
nodes := make([]Node, 0, len(elements)+2)
|
|
nodes = append(nodes, NewGlue("ss"))
|
|
nodes = append(nodes, elements...)
|
|
nodes = append(nodes, NewGlue("ss"))
|
|
return VListOf(nodes)
|
|
}
|
|
|
|
// Kern is a node with a width to specify a (normally negative) amount of spacing.
|
|
//
|
|
// This spacing correction appears in horizontal lists between letters
|
|
// like A and V, when the font designer decided it looks better to move them
|
|
// closer together or further apart.
|
|
// A Kern node can also appear in a vertical list, when its width denotes
|
|
// spacing in the vertical direction.
|
|
type Kern struct {
|
|
size int
|
|
width float64
|
|
}
|
|
|
|
func NewKern(width float64) *Kern {
|
|
return &Kern{width: width}
|
|
}
|
|
|
|
func (k *Kern) String() string { return fmt.Sprintf("k%.02f", k.width) }
|
|
|
|
func (k *Kern) Kerning(next Node) float64 { return 0 }
|
|
|
|
func (k *Kern) Shrink() {
|
|
k.size--
|
|
if k.size < numSizeLevels {
|
|
k.width *= shrinkFactor
|
|
}
|
|
}
|
|
|
|
func (k *Kern) Grow() {
|
|
k.size++
|
|
k.width *= growFactor
|
|
}
|
|
|
|
func (k *Kern) Render(x, y float64) {}
|
|
|
|
// Width returns the width of this node.
|
|
func (k *Kern) Width() float64 { return k.width }
|
|
|
|
// Height returns the height of this node.
|
|
func (k *Kern) Height() float64 { return 0 }
|
|
|
|
// Depth returns the depth of this node.
|
|
func (k *Kern) Depth() float64 { return 0 }
|
|
|
|
func (k *Kern) hpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
*width += k.width
|
|
}
|
|
|
|
func (k *Kern) vpackDims(width, height, depth *float64, stretch, shrink []float64) {
|
|
*height += *depth + k.width
|
|
*depth = 0
|
|
}
|
|
|
|
type SubSuperCluster struct {
|
|
*HList
|
|
// nucleus interface{} // FIXME
|
|
// sub interface{} // FIXME
|
|
// super interface{} // FIXME
|
|
}
|
|
|
|
// AutoHeightChar creats a character as close to the given height and depth
|
|
// as possible.
|
|
func AutoHeightChar(c string, height, depth float64, state State, factor float64) *HList {
|
|
// FIXME(sbinet): implement sized-alternatives-for-symbol
|
|
alts := []struct {
|
|
font string
|
|
sym string
|
|
}{
|
|
{state.Font.Name, c},
|
|
}
|
|
|
|
const math = true
|
|
var (
|
|
xheight = state.Backend().XHeight(state.Font, state.DPI)
|
|
target = height + depth
|
|
|
|
ch *Char
|
|
shift float64
|
|
)
|
|
|
|
for _, v := range alts {
|
|
state.Font.Name = v.font
|
|
ch = NewChar(c, state, math)
|
|
// ensure that size 0 is chosen when the text is regular sized
|
|
// but with descender glyphs by subtracting 0.2*xheight
|
|
if ch.Height()+ch.Depth() >= target-0.2*xheight {
|
|
break
|
|
}
|
|
}
|
|
|
|
if state.Font.Name != "" {
|
|
if factor == 0 {
|
|
factor = target / (ch.Height() + ch.Depth())
|
|
}
|
|
state.Font.Size *= factor
|
|
|
|
ch = NewChar(c, state, math)
|
|
shift = depth - ch.Depth()
|
|
}
|
|
|
|
hlist := HListOf([]Node{ch}, true)
|
|
hlist.lst.shift = shift
|
|
|
|
return hlist
|
|
}
|
|
|
|
// Ship boxes to output once boxes have been set up.
|
|
//
|
|
// Since boxes can be inside of boxes inside of boxes... the main work of
|
|
// Ship is done by two mutually recursive routines, hlistOut and vlistOut,
|
|
// which traverse the HList and VList nodes inside of horizontal and vertical
|
|
// boxes.
|
|
type Ship struct {
|
|
maxPush int // deepest nesting of push commands, so far.
|
|
cur struct {
|
|
s int
|
|
v float64
|
|
h float64
|
|
}
|
|
off struct {
|
|
h float64
|
|
v float64
|
|
}
|
|
}
|
|
|
|
func (ship *Ship) Call(ox, oy float64, box Tree) {
|
|
ship.maxPush = 0
|
|
ship.cur.s = 0
|
|
ship.cur.v = 0
|
|
ship.cur.h = 0
|
|
ship.off.h = ox
|
|
ship.off.v = oy + box.Height()
|
|
ship.hlistOut(box)
|
|
}
|
|
|
|
func (ship *Ship) hlistOut(box Tree) {
|
|
var (
|
|
curG int
|
|
curGlue float64
|
|
glueOrder = box.GlueOrder()
|
|
glueSign = box.GlueSign()
|
|
baseLine = ship.cur.v
|
|
)
|
|
|
|
ship.cur.s++
|
|
ship.maxPush = maxInt(ship.cur.s, ship.maxPush)
|
|
|
|
for _, node := range box.Nodes() {
|
|
switch node := node.(type) {
|
|
case *Char:
|
|
node.Render(ship.cur.h+ship.off.h, ship.cur.v+ship.off.v)
|
|
ship.cur.h += node.Width()
|
|
case *Accent:
|
|
node.Render(ship.cur.h+ship.off.h, ship.cur.v+ship.off.v)
|
|
ship.cur.h += node.Width()
|
|
case *Kern:
|
|
ship.cur.h += node.Width()
|
|
case *HList:
|
|
// node623
|
|
switch len(node.Nodes()) {
|
|
case 0:
|
|
ship.cur.h += node.Width()
|
|
default:
|
|
edge := ship.cur.h
|
|
ship.cur.v = baseLine + node.lst.shift
|
|
ship.hlistOut(node)
|
|
ship.cur.h = edge + node.Width()
|
|
ship.cur.v = baseLine
|
|
}
|
|
case *VList:
|
|
// node623
|
|
switch len(node.Nodes()) {
|
|
case 0:
|
|
ship.cur.h += node.Width()
|
|
default:
|
|
edge := ship.cur.h
|
|
ship.cur.v = baseLine + node.lst.shift
|
|
ship.vlistOut(node)
|
|
ship.cur.h = edge + node.Width()
|
|
ship.cur.v = baseLine
|
|
}
|
|
case *Glue:
|
|
// node625
|
|
ruleWidth := node.width - float64(curG)
|
|
if glueSign != 0 { // normal
|
|
switch {
|
|
case glueSign == 1: // stretching
|
|
if node.stretchOrder == glueOrder {
|
|
curGlue += node.stretch
|
|
curG = int(math.Round(clamp(box.GlueSet() * curGlue)))
|
|
}
|
|
case node.shrinkOrder == glueOrder: // shrinking
|
|
curGlue += node.shrink
|
|
curG = int(math.Round(clamp(box.GlueSet() * curGlue)))
|
|
}
|
|
}
|
|
ruleWidth += float64(curG)
|
|
ship.cur.h += ruleWidth
|
|
case Node:
|
|
// node624
|
|
ruleHeight := node.Height()
|
|
ruleDepth := node.Depth()
|
|
ruleWidth := node.Width()
|
|
if math.IsInf(ruleHeight, 0) {
|
|
ruleHeight = box.Height()
|
|
}
|
|
if math.IsInf(ruleDepth, 0) {
|
|
ruleDepth = box.Depth()
|
|
}
|
|
if ruleHeight > 0 && ruleWidth > 0 {
|
|
ship.cur.v = baseLine + ruleDepth
|
|
type renderXYWH interface {
|
|
render(x, y, w, h float64)
|
|
}
|
|
node.(renderXYWH).render(
|
|
ship.cur.h+ship.off.h,
|
|
ship.cur.v+ship.off.v,
|
|
ruleWidth, ruleHeight,
|
|
)
|
|
ship.cur.v = baseLine
|
|
}
|
|
ship.cur.h += ruleWidth
|
|
}
|
|
}
|
|
ship.cur.s--
|
|
}
|
|
|
|
func (ship *Ship) vlistOut(box Tree) {
|
|
var (
|
|
curG int
|
|
curGlue float64
|
|
glueOrder = box.GlueOrder()
|
|
glueSign = box.GlueSign()
|
|
leftEdge = ship.cur.h
|
|
)
|
|
|
|
ship.cur.s++
|
|
ship.maxPush = maxInt(ship.cur.s, ship.maxPush)
|
|
ship.cur.v -= box.Height()
|
|
|
|
for _, node := range box.Nodes() {
|
|
switch node := node.(type) {
|
|
case *Kern:
|
|
ship.cur.v += node.Width()
|
|
case *HList:
|
|
switch len(node.Nodes()) {
|
|
case 0:
|
|
ship.cur.v += node.Height() + node.Depth()
|
|
default:
|
|
ship.cur.v += node.Height()
|
|
ship.cur.h = leftEdge + node.lst.shift
|
|
curV := ship.cur.v
|
|
node.lst.box.width = box.Width()
|
|
ship.hlistOut(node)
|
|
ship.cur.v = curV + node.Depth()
|
|
ship.cur.h = leftEdge
|
|
}
|
|
case *VList:
|
|
switch len(node.Nodes()) {
|
|
case 0:
|
|
ship.cur.v += node.Height() + node.Depth()
|
|
default:
|
|
ship.cur.v += node.Height()
|
|
ship.cur.h = leftEdge + node.lst.shift
|
|
curV := ship.cur.v
|
|
node.lst.box.width = box.Width()
|
|
ship.vlistOut(node)
|
|
ship.cur.v = curV + node.Depth()
|
|
ship.cur.h = leftEdge
|
|
}
|
|
|
|
case *Glue:
|
|
ruleHeight := node.width - float64(curG)
|
|
if glueSign != 0 { // normal
|
|
switch {
|
|
case glueSign == 1: // stretching
|
|
if node.stretchOrder == glueOrder {
|
|
curGlue += node.stretch
|
|
curG = int(math.Round(clamp(box.GlueSet() * curGlue)))
|
|
}
|
|
case glueOrder == node.shrinkOrder: // shrinking
|
|
curGlue += node.shrink
|
|
curG = int(math.Round(clamp(box.GlueSet() * curGlue)))
|
|
}
|
|
}
|
|
ruleHeight += float64(curG)
|
|
ship.cur.v += ruleHeight
|
|
case *Char:
|
|
panic("tex: Char node found in vlist")
|
|
case *Accent:
|
|
panic("tex: Accent node found in vlist")
|
|
case Node:
|
|
var (
|
|
ruleHeight = node.Height()
|
|
ruleDepth = node.Depth()
|
|
ruleWidth = node.Width()
|
|
)
|
|
if math.IsInf(ruleWidth, 0) {
|
|
ruleWidth = box.Width()
|
|
}
|
|
ruleHeight += ruleDepth
|
|
if ruleHeight > 0 && ruleDepth > 0 {
|
|
ship.cur.v += ruleHeight
|
|
type renderXYWH interface {
|
|
render(x, y, w, h float64)
|
|
}
|
|
if p, ok := node.(renderXYWH); ok {
|
|
p.render(
|
|
ship.cur.h+ship.off.h,
|
|
ship.cur.v+ship.off.v,
|
|
ruleWidth, ruleHeight,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ship.cur.s--
|
|
}
|
|
|
|
type hpacker interface {
|
|
hpackDims(width, height, depth *float64, stretch, shrink []float64)
|
|
}
|
|
|
|
type vpacker interface {
|
|
vpackDims(width, height, depth *float64, stretch, shrink []float64)
|
|
}
|
|
|
|
type Tree interface {
|
|
Node
|
|
|
|
Nodes() []Node
|
|
GlueOrder() int
|
|
GlueSign() int
|
|
GlueSet() float64
|
|
}
|
|
|
|
var (
|
|
_ Node = (*Box)(nil)
|
|
_ Node = (*Char)(nil)
|
|
_ Node = (*Accent)(nil)
|
|
_ Node = (*List)(nil)
|
|
_ Node = (*HList)(nil)
|
|
_ Node = (*VList)(nil)
|
|
_ Node = (*Rule)(nil)
|
|
_ Node = (*Glue)(nil)
|
|
_ Node = (*Kern)(nil)
|
|
_ Node = (*SubSuperCluster)(nil)
|
|
|
|
_ hpacker = (*Box)(nil)
|
|
_ hpacker = (*Char)(nil)
|
|
_ hpacker = (*Accent)(nil)
|
|
_ hpacker = (*List)(nil)
|
|
_ hpacker = (*HList)(nil)
|
|
_ hpacker = (*VList)(nil)
|
|
_ hpacker = (*Rule)(nil)
|
|
_ hpacker = (*Glue)(nil)
|
|
_ hpacker = (*Kern)(nil)
|
|
_ hpacker = (*SubSuperCluster)(nil)
|
|
|
|
_ vpacker = (*Box)(nil)
|
|
_ vpacker = (*Char)(nil)
|
|
_ vpacker = (*Accent)(nil)
|
|
_ vpacker = (*List)(nil)
|
|
_ vpacker = (*HList)(nil)
|
|
_ vpacker = (*VList)(nil)
|
|
_ vpacker = (*Rule)(nil)
|
|
_ vpacker = (*Glue)(nil)
|
|
_ vpacker = (*Kern)(nil)
|
|
_ vpacker = (*SubSuperCluster)(nil)
|
|
|
|
_ Tree = (*List)(nil)
|
|
_ Tree = (*HList)(nil)
|
|
_ Tree = (*VList)(nil)
|
|
)
|