Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
A .github/ISSUE_TEMPLATE/bug_report.md
1
---
2
name: Bug report
3
about: Create a report to help us improve
4
title: ''
5
labels: bug
6
assignees: ''
7
8
---
9
10
**Description**
11
A concise problem description.
12
13
**Replicate**
14
Exact and complete steps to reproduce the problem 100% of the time:
15
16
1. Open '...'
17
1. Click '....'
18
1. Click '....'
19
20
**Expected**
21
Describe the expected behaviour.
22
23
**Actual**
24
Describe the actual behaviour.
25
26
**Screenshots**
27
Add screenshots to show the problem, if applicable.
28
29
**Environment**
30
 - Operating System: (Windows, Linux, Mac)
31
 - Application: e.g., 1.7.16
32
33
**Details**
34
Add additional information, if applicable.
135
M .idea/dictionaries/jarvisd.xml
33
    <words>
44
      <w>blockquotes</w>
5
      <w>sigil</w>
6
      <w>transcoded</w>
57
    </words>
68
  </dictionary>
M LICENSE.md
11
# License
22
3
Copyright 2015 Karl Tauber
4
All rights reserved.
5
36
Copyright 2020 White Magic Software, Ltd.
47
All rights reserved.
M README.md
1
![Logo](images/logo64.png)
2
3
# $application.title$
1
# ![Logo](images/logo64.png) Scrivenvar
42
53
A text editor that uses [interpolated strings](https://en.wikipedia.org/wiki/String_interpolation) to reference externally defined values.
...
2018
2119
On Windows, double-click the application to start. You will have to give the application permission to run.
20
21
When upgrading to a new version, delete the following directory;
22
23
    C:\Users\%USERNAME%\AppData\Local\warp\packages\scrivenvar.exe
2224
2325
### Linux
...
4143
* Platform independent (Windows, Linux, MacOS)
4244
* Spellcheck while typing
45
* Write mathematical formulas using a subset of TeX
4346
* R integration
4447
4548
## Usage
4649
4750
See the [detailed documentation](docs/README.md) for information about
4851
using the application.
49
50
## Future Features
5152
52
* Search and replace using variables
53
* Reorganize variable names
53
## Screenshots
5454
55
## Screenshot
55
![Screenshot with Formulas](docs/images/equations.png)
5656
57
![Screenshot](docs/images/screenshot.png)
57
![Screenshot with Hyperlinks](docs/images/screenshot.png)
5858
5959
## License
M build.gradle
9090
  implementation 'org.apache.xmlgraphics:batik-xml:1.13'
9191
92
  // Spelling
92
  // Spelling, TeX
9393
  implementation fileTree(include: ['**/*.jar'], dir: 'libs')
9494
M docs/README.md
55
* [definitions.md](definitions.md) -- Definitions and interpolation
66
* [r.md](r.md) -- Call R functions within R Markdown documents
7
* [texample.md](texample.md) -- Numerous examples of formulas
78
* [svg.md](svg.md) -- Fix known issues with displaying SVG files
89
* [credits.md](credits.md) -- Thanks to authors of contributing projects
A docs/images/equations.png
Binary file
A docs/texample.Rmd
1
# Real-time equation rendering
2
3
With interpolated variables and R calculations:
4
5
$\sqrt{`r#x( v$formula$sqrt$value)`} = `r# round(sqrt(x( v$formula$sqrt$value )),5)`$
6
7
# Maxwell's equations
8
9
$rot \vec{E} = \frac{1}{c} \frac{\partial{\vec{B}}}{\partial t}, div \vec{B} = 0$
10
11
$rot \vec{B} = \frac{1}{c} \frac{\partial{\vec{E}}}{\partial t} + \frac{4\pi}{c} \vec{j}, div \vec{E} = 4 \pi \rho_{\varepsilon}$
12
13
# Time-dependent Schrödinger equation
14
15
$- \frac{{\hbar ^2 }}{{2m}}\frac{{\partial ^2 \psi (x,t)}}{{\partial x^2 }} + U(x)\psi (x,t) = i\hbar \frac{{\partial \psi (x,t)}}{{\partial t}}$
16
17
# Discrete-time Fourier transforms
18
19
Unit step function: $u(n) \Leftrightarrow \frac{1}{1-e^{-jw}} + \sum_{k=-\infty}^{\infty} \pi \delta (\omega + 2\pi k)$
20
21
Shifted delta: $\delta (n - n_o ) \Leftrightarrow e^{ - j\omega n_o }$
22
23
# Faraday's Law
24
25
$\oint_C {E \cdot d\ell  =  - \frac{d}{{dt}}} \int_S {B_n dA}$
26
27
# Infinite series
28
29
$sin(x) = \sum_{n = 1}^{\infty}  {\frac{{( { - 1})^{n - 1} x^{2n - 1} }}{{( {2n - 1})!}}}$
30
31
# Magnetic flux
32
33
$\phi _m  = \int_S {N{{B}} \cdot {{\hat n}}dA = } \int_S {NB_n dA}$
34
35
# Driven oscillation amplitude
36
37
$A = \frac{{F_0 }}{{\sqrt {m^2 ( {\omega _0^2  - \omega ^2 } )^2  + b^2 \omega ^2 } }}$
38
39
# Optics
40
41
$\phi  = \frac{{2\pi }}{\lambda }a sin(\theta)$
142
A docs/variables.yaml
1
formula:
2
  sqrt:
3
    value: 603
4
15
A libs/jmathtex/jmathtex.jar
Binary file
D scripts/.gitignore
1
*.class
21
D scripts/demo.sikuli/1594187265140.png
Binary file
D scripts/demo.sikuli/1594592396134.png
Binary file
D scripts/demo.sikuli/1594593710440.png
Binary file
D scripts/demo.sikuli/1594593794335.png
Binary file
D scripts/demo.sikuli/1594594984108.png
Binary file
D scripts/demo.sikuli/1594689573764.png
Binary file
D scripts/demo.sikuli/demo.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# Runs all scripts
26
# -----------------------------------------------------------------------------
27
28
import s01
29
import s02
30
import s03
31
import s04
321
D scripts/demo.sikuli/s01.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script introduces the editor and its purpose.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
# ---------------------------------------------------------------
36
# Fresh start
37
# ---------------------------------------------------------------
38
rm( app_home + "/variables.yaml" )
39
rm( app_home + "/untitled.md" )
40
rm( dir_home + "/.scrivenvar" )
41
42
# ---------------------------------------------------------------
43
# Wait for application to launch
44
# ---------------------------------------------------------------
45
openApp( "java -jar " + app_bin )
46
47
wait("1594187265140.png", 30)
48
49
# Breathing room for video recording.
50
wait( 4 )
51
52
# ---------------------------------------------------------------
53
# Introduction
54
# ---------------------------------------------------------------
55
set_typing_speed( 240 )
56
57
heading( "What is this application?" )
58
typer( "Well, this application is a text editor that supports interpolated definitions, ")
59
typer( "a few different text formats, real-time preview, spell check ") 
60
typer( "as you tipe" ) 
61
wait( 0.5 )
62
recur( 3, backspace )
63
typer( "ype, and R statements." )
64
paragraph()
65
wait( 1 )
66
67
# ---------------------------------------------------------------
68
# Definition demo
69
# ---------------------------------------------------------------
70
heading( "What are definitions?" )
71
typer( "Watch. " )
72
wait( .5 )
73
74
# Focus the definition editor.
75
click_create()
76
recur( 4, tab )
77
78
wait( .5 )
79
rename_definition( "application" )
80
81
insert()
82
rename_definition( "title" )
83
84
insert()
85
rename_definition( "Scrivenvar" )
86
87
# Set focus to the text editor.
88
tab()
89
90
typer( "The left-hand pane contains a nested, folder-like structure of names " )
91
typer( "and values that are called *definitions*. " )
92
wait( .5 )
93
typer( "Such definitions can simplify updating documents. " )
94
wait( 1 )
95
96
edit_find( "this application" )
97
typer( "$application.title$" )
98
99
edit_find_next()
100
typer( "$application.title$" )
101
102
type( Key.END, Key.CTRL )
103
104
typer( "The right-hand pane shows the result after having substituted definition " )
105
typer( "values into the document." ) 
106
107
paragraph()
108
typer( "Now nobody wants to type definition names all the time. Instead, type any " )
109
typer( "partial definition value followed by `Ctrl+Space`, such as: scr" )
110
wait( 0.5 )
111
autoinsert()
112
wait( 1 )
113
typer( ". *Much* better!" )
114
paragraph()
115
116
heading( "What is interpolation?" )
117
typer( "Definition values can reference definition names. " )
118
wait( .5 )
119
typer( "The definition names act as placeholders. Substituting placeholders with " )
120
typer( "their definition value is called *interpolation*. Let's see how it works." )
121
wait( 2 )
1221
D scripts/demo.sikuli/s02.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script demonstrates how to use interpolated strings.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
# -----------------------------------------------------------------------------
36
# Open sample chapter.
37
# -----------------------------------------------------------------------------
38
file_open()
39
type( Key.UP, Key.ALT )
40
wait( 1 )
41
typer( Key.END )
42
wait( 1 )
43
enter()
44
wait( 0.5 )
45
enter()
46
wait( 1 )
47
48
# -----------------------------------------------------------------------------
49
# Open the corresponding definition file.
50
# -----------------------------------------------------------------------------
51
file_open()
52
recur( 2, down )
53
wait( 1 )
54
enter()
55
wait( 1 )
56
57
# -----------------------------------------------------------------------------
58
# Edit the sample document.
59
# -----------------------------------------------------------------------------
60
set_typing_speed( 80 )
61
62
type( Key.HOME, Key.CTRL )
63
recur( 2, down )
64
65
# Grey
66
recur( 3, skip_right )
67
autoinsert()
68
69
# 34
70
recur( 4, skip_right )
71
autoinsert()
72
73
# Central
74
recur( 10, skip_right )
75
autoinsert()
76
77
# London
78
skip_right()
79
autoinsert()
80
81
# Hatchery
82
skip_right()
83
autoinsert()
84
85
# and Conditioning
86
recur( 2, select_word_right )
87
delete()
88
89
# Centre
90
skip_right()
91
autoinsert()
92
93
set_typing_speed( 220 )
94
95
typer( " Let's interpolate those four definitions instead!" )
96
wait( 4 )
97
recur( 13, type, Key.BACKSPACE, Key.CTRL )
98
recur( 9, backspace )
99
100
set_typing_speed( 60 )
101
102
typer( "name$" )
103
wait( 2 )
104
105
# Collapse all definitions
106
tab()
107
recur( 8, typer, Key.LEFT )
108
109
# Expand to city
110
recur( 4, typer, Key.RIGHT )
111
112
# Jump to name
113
recur( 2, down )
114
recur( 2, typer, Key.RIGHT )
115
116
# Open the text field to show the full value
117
typer( Key.F2 )
118
119
# Traverse the text field
120
home()
121
recur( 16, type, Key.RIGHT, Key.CTRL )
122
esc()
123
124
restore_typing_speed()
125
126
tab()
127
type( Key.HOME, Key.CTRL )
128
edit_find( "Director" )
129
autoinsert()
130
131
edit_find_next()
132
autoinsert()
133
134
edit_find_next()
135
typer( Key.RIGHT )
136
recur( 2, delete )
137
autoinsert()
138
typer( "'s" )
139
140
edit_find( "Hatcheries" )
141
autoinsert()
142
143
# and Conditioning
144
recur( 2, select_word_right )
145
delete()
146
147
edit_find( "Central" )
148
autoinsert()
149
150
skip_right()
151
autoinsert()
152
153
typer( " How about a different city?" )
154
wait( 2 )
155
recur( 5, type, Key.BACKSPACE, Key.CTRL )
156
wait( 1 )
157
tab()
158
typer( Key.F2 )
159
typer( "Seattle" )
160
enter()
161
tab()
162
wait( 2 )
163
164
type( Key.END, Key.CTRL )
165
paragraph()
166
typer( "No?" )
167
paragraph()
168
169
tab()
170
typer( Key.F2 )
171
typer( "London" )
172
enter()
173
174
tab()
175
typer( "Organizing definitions is left to your ")
176
typer( "doub" )
177
autoinsert()
178
typer( " Good imagination." )
179
tab()
180
181
# Jump to "char" definition
182
home()
183
184
# Jump to "char.a.primary.name" definition
185
recur( 6, typer, Key.RIGHT )
186
187
# Jump to "char.a.primary.caste" definition
188
down()
189
typer( Key.RIGHT )
190
191
# Jump to root-level "caste" definition
192
recur( 7, down )
193
194
# Reselect "super"
195
recur( 5, typer, Key.RIGHT )
196
wait( 2 )
197
198
# Close the window, no save
199
type( "w", Key.CTRL )
200
wait( 0.5 )
201
tab()
202
wait( 0.5 )
203
typer( Key.SPACE )
204
wait( 1 )
2051
D scripts/demo.sikuli/s03.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script introduces images and R.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
set_typing_speed( 80 )
36
37
file_open()
38
type( Key.UP, Key.ALT )
39
wait( 0.5 )
40
home()
41
wait( 0.25 )
42
enter()
43
wait( 1 )
44
end()
45
wait( 0.25 )
46
enter()
47
wait( 1 )
48
49
set_typing_speed( 200 )
50
51
paragraph()
52
heading( "What text formats are supported?" )
53
54
typer( "Scr" )
55
autoinsert()
56
typer( " supports Markdown, R Markdown, XML, and R XML; however, the software " )
57
typer( "architecture enables it to easily add new formats. The following figure " )
58
typer( "depicts the overall architecture: " )
59
paragraph()
60
typer( "![](../writing/images/architecture)" )
61
paragraph()
62
typer( "Many text editors can only open one type of plain text markup format that is " )
63
typer( "only output as HTML. With a little more effort, text editors could support " )
64
typer( "multiple input and output formats. Scr" )
65
autoinsert()
66
typer( " does so and goes one step further by introducing interpolated definitions." )
67
paragraph()
68
typer( "Kitten interlude:" )
69
paragraph()
70
typer( "![](https://i.imgur.com/jboueQH.jpg)" )
71
paragraph()
72
73
heading( "What is R?" )
74
typer( "R is a programming language. You might have noticed a few potential grammar " )
75
typer( "problems with direct substitution. Rules for possessive forms, numbers, and " )
76
typer( "other quirks can be tackled using R." )
77
78
# -----------------------------------------------------------------------------
79
# Demo bootstrapping
80
# -----------------------------------------------------------------------------
81
82
# Jump to the end
83
type( Key.END, Key.CTRL )
84
paragraph()
85
86
set_typing_speed( 300 )
87
heading( "How is R used?" )
88
typer( "R must be instructed where to find script files and what ones to load. The " )
89
typer( "*working directory* is the full path to those R files; the *startup script* " )
90
typer( "defines what R files to load. Both preferences must be changed before prose " )
91
typer( "may be processed. Preferences can be opened using either the " )
92
typeln( "**Edit > Preferences** menu or by pressing `Ctrl+Alt+s`. Here goes!" ) 
93
wait( 2 )
94
95
# -----------------------------------------------------------------------------
96
# Select the R script directory
97
# -----------------------------------------------------------------------------
98
99
# Change the working directory by clicking "Browse"
100
type( "s", Key.CTRL + Key.ALT )
101
wait("1594592396134.png", 1)
102
click("1594592396134.png")
103
wait( 0.5 )
104
105
# Navigate to and select the "r" directory
106
type( Key.UP, Key.ALT )
107
wait( 0.5 )
108
end()
109
wait( 0.5 )
110
enter()
111
wait( 0.5 )
112
end()
113
wait( 0.5 )
114
type( Key.UP )
115
wait( 0.5 )
116
recur( 2, tab )
117
wait( 0.5 )
118
enter()
119
wait( 1 )
120
121
# -----------------------------------------------------------------------------
122
# Set the R startup script instructions
123
# -----------------------------------------------------------------------------
124
125
wait("1594593710440.png", 5)
126
click("1594593710440.png")
127
128
set_typing_speed( 440 )
129
130
typeln( "setwd( '$application.r.working.directory$' )" )
131
typeln( "assign( 'anchor', '$date.anchor$', envir = .GlobalEnv )" )
132
typeln( "source( 'pluralize.R' )" )
133
typeln( "source( 'possessive.R' )" )
134
typeln( "source( 'conversion.R' )" )
135
typeln( "source( 'csv.R' )" )
136
137
wait("1594593794335.png", 3)
138
click("1594593794335.png")
139
140
paragraph()
141
set_typing_speed( 220 )
142
143
typer( "R is now configured. The startup script and other R " )
144
typer( "files can be found in the " )
145
typer( "[repository](https://github.com/DaveJarvis/scrivenvar/tree/master/R). " )
146
wait( 1.5 )
147
148
# Wait for the browser to appear.
149
wait("1594594984108.png", 5)
150
click("1594594984108.png")
151
152
wait( 5 )
153
click("1594689573764.png")
154
155
paragraph()
156
typer( "Next, we'll see how definitions and R can work together." )
157
wait( 2 )
1581
D scripts/demo.sikuli/s04.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script demonstrates using R.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
set_typing_speed( 220 )
36
37
# -----------------------------------------------------------------------------
38
# Open the demo text.
39
# -----------------------------------------------------------------------------
40
file_open()
41
type( Key.UP, Key.ALT )
42
wait( 0.5 )
43
end()
44
wait( 0.5 )
45
enter()
46
wait( 0.5 )
47
down()
48
wait( 0.5 )
49
enter()
50
wait( 2 )
51
52
# -----------------------------------------------------------------------------
53
# Re-open the corresponding definition file.
54
# -----------------------------------------------------------------------------
55
file_open()
56
recur( 2, down )
57
wait( 1 )
58
enter()
59
wait( 2 )
60
61
# -----------------------------------------------------------------------------
62
# Brief introduction to R
63
# -----------------------------------------------------------------------------
64
type( Key.HOME, Key.CTRL )
65
end()
66
paragraph()
67
68
typer( "## Using R" )
69
paragraph()
70
typer( "Insert R code into documents as follows: `r# 1+1`. " )
71
wait( 1.5 )
72
typer( "Notice how the right-hand pane shows the computed result. I'll wait. " )
73
wait( 3 )
74
typer( "The syntax is: open backtick, r#, *computable expression*, close " )
75
typer( "backtick. That expression can be any valid R statement. The status bar " ) 
76
typer( "will provide clues when an R expression cannot be computed by the " )
77
typer( "editor. `r# glitch`" )
78
wait( 4 )
79
recur( 11, backspace )
80
typer( "Let's swap 34 storeys for a definition value and replace the number " )
81
typer( "according to the Chicago Manual of Style (cms) rules." )
82
83
# -----------------------------------------------------------------------------
84
# Demo pluralization
85
# -----------------------------------------------------------------------------
86
set_typing_speed( 80 )
87
88
edit_find( "34" )
89
autoinsert()
90
91
edit_find( "x(" )
92
typer( "cms(" )
93
94
edit_find( "storeys." )
95
typer( "34." )
96
autoinsert()
97
edit_find( "x(" )
98
typer( "pl( 'storey'," )
99
wait( 4 )
100
101
tab()
102
rename_definition( "1" )
103
wait( 4 )
104
rename_definition( "142" )
105
wait( 4 )
106
rename_definition( "34" )
107
wait( 4 )
108
tab()
109
110
# -----------------------------------------------------------------------------
111
# Demo possessives (it, her, his, Director)
112
# -----------------------------------------------------------------------------
113
type( Key.HOME, Key.CTRL )
114
edit_find( "Director" )
115
autoinsert()
116
edit_find_next()
117
autoinsert()
118
edit_find_next()
119
autoinsert()
120
type( Key.RIGHT )
121
recur( 2, delete )
122
autoinsert()
123
home()
124
edit_find( "x(" )
125
typer( "pos(" )
126
wait( 2 )
127
128
tab()
129
rename_definition( "Headmistress" )
130
wait( 4 )
131
rename_definition( "Director" )
132
wait( 2 )
133
tab()
134
135
type( Key.END, Key.CTRL )
136
paragraph()
137
typer( "Other possessives: `r# pos( 'it' )`, `r# pos( 'her' )`, `r# pos( 'his' )`, " )
138
typer( "and `r# pos( 'my' )`." )
139
140
# -----------------------------------------------------------------------------
141
# Demo conversion, including ordinal numbers
142
# -----------------------------------------------------------------------------
143
set_typing_speed( 160 )
144
145
paragraph()
146
heading( "Date Conversions" )
147
typer( "Mixing R code with definitions invites endless possibilities. " )
148
typer( "Imagine someone racing to the " ) 
149
typer( "`r#cms( v$location$breeder$storeys, ordinal=TRUE )` floor, whereby that " )
150
typer( "ordinal stems from the Hatchery's storeys' definition. Or how about " )
151
typer( "a complex timeline where dates are expressed in days relative to one " )
152
typer( "point in time. Let's call this the *anchor date* and define it." )
153
154
tab()
155
home()
156
typer( Key.SPACE )
157
insert()
158
rename_definition( "date" )
159
insert()
160
rename_definition( "anchor" )
161
insert()
162
rename_definition( "1969-10-29" )
163
tab()
164
165
paragraph()
166
typer( "Next, set an R variable named `now` to the current date" )
167
typer( "`r# now = format( Sys.time(), '%Y-%m-%d' ); ''`--- the empty single quotes " )
168
typer( "prevent the date from appearing in the output document. " )
169
170
paragraph()
171
typer( "We set the anchor date to `r# annal()`, which was " )
172
typer( "`r# elapsed( 0, days( v$date$anchor, format( Sys.time(), '%Y-%m-%d' ) ) )` " )
173
typer( "ago from `r# format( as.Date( now ), '%B %d, %Y' )`. " )
174
175
# -----------------------------------------------------------------------------
176
# Demo CSV file import
177
# -----------------------------------------------------------------------------
178
paragraph()
179
heading( "Tabular Data" )
180
typer( "The following table shows average Canadian lifespans by birth " )
181
typer( "year and sex:" )
182
paragraph()
183
typer( "`r# csv2md( '../data.csv', total=FALSE )`" )
184
paragraph()
185
typer( "Calling `csv2md` converts the comma-separated values in the spreadsheet " )
186
typer( "to a table formatted using Markdown. The HTML preview pane changes the " )
187
typer( "appearance of the resulting table. Using `../data.csv` instructs R to " )
188
typer( "open `data.csv` from one directory above the *working directory*." )
189
190
# -----------------------------------------------------------------------------
191
# Demo HTML export
192
# -----------------------------------------------------------------------------
193
paragraph()
194
heading( "Export" )
195
typer( "Retrieve the output HTML by using the **Edit > Copy HTML** menu. Let's " )
196
typer( "peek at the output." )
197
wait( 2 )
198
199
type( "e", Key.ALT )
200
wait( 0.5 )
201
down()
202
wait( 0.25 )
203
enter()
204
wait( 0.25 )
205
206
type( "a", Key.CTRL )
207
wait( 0.25 )
208
type( "v", Key.CTRL )
209
wait( 5 )
210
211
set_typing_speed( 40 )
212
213
# Jump to page bottom (should already be there, but just in case)
214
type( Key.END, Key.CTRL )
215
recur( 3, typer, Key.PAGE_UP )
216
type( Key.HOME, Key.CTRL )
217
wait( 3 )
218
219
set_typing_speed( 220 )
220
type( "z", Key.CTRL )
221
type( Key.END, Key.CTRL )
222
223
paragraph()
224
typer( "That's all for now, thank you!" )
225
wait( 5 )
226
227
# Delete the anchor date.
228
tab()
229
end()
230
recur( 2, type, Key.UP )
231
delete()
232
tab()
2331
D scripts/demo.sikuli/test.py
1
from sikuli import *
2
3
import sys
4
import os
5
6
def set_class_path():
7
    path_script = getBundlePath()
8
    dir_script = os.path.dirname( path_script )
9
    path_lib = dir_script + "/keycast/build/libs/keycast.jar"
10
    
11
    sys.path.append( path_lib )
12
13
def launch():
14
    from com.whitemagicsoftware.keycast import KeyCast
15
    kc = KeyCast()
16
    kc.show()
17
18
def main():
19
    set_class_path()
20
    launch()
21
   
22
23
if __name__ == "__main__":
24
    main()
251
D scripts/editor.sikuli/1594187923258.png
Binary file
D scripts/editor.sikuli/editor.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script contains helper functions used by the other scripts.
26
#
27
# Do not run this script.
28
# -----------------------------------------------------------------------------
29
30
from sikuli import *
31
import sys
32
import os
33
from os.path import expanduser
34
35
dir_home = expanduser( "~" )
36
app_home = dir_home + "/bin"
37
app_bin = app_home + "/scrivenvar.jar"
38
39
wpm_typing_speed = 80
40
41
# -----------------------------------------------------------------------------
42
# Try to delete the file pointed to by the path variable. If there is no such
43
# file, this will silently ignore the exception.
44
# -----------------------------------------------------------------------------
45
def rm( path ):
46
    try:
47
        os.remove( path )
48
    except:
49
        print "Ignored"
50
51
# -----------------------------------------------------------------------------
52
# Changes the current typing speed, where speed is given in words per minute.
53
# -----------------------------------------------------------------------------
54
def set_typing_speed( wpm ):
55
    global wpm_typing_speed
56
    wpm_typing_speed = wpm
57
58
# -----------------------------------------------------------------------------
59
# Creates a delay between keystrokes to emulate typing at a particular speed.
60
# -----------------------------------------------------------------------------
61
def random_wait():
62
    from time import sleep
63
    from random import uniform
64
    cpm = wpm_typing_speed * 5.1
65
    cps = cpm / 60.0
66
    ms_per_char = 1000.0 / cps
67
    ms_per_stroke = ms_per_char / 2.0
68
69
    noise = uniform( 0, ms_per_stroke / 2 )
70
    duration = (ms_per_stroke + noise ) / 1000
71
    
72
    sleep( duration )
73
74
# -----------------------------------------------------------------------------
75
# Repeats a function call, f, n times.
76
# -----------------------------------------------------------------------------
77
def recur( n, f, *args ):
78
    for i in range( n ):
79
        f( *args )
80
        random_wait()
81
82
# -----------------------------------------------------------------------------
83
# Emulate a typist who is typing in the given text.
84
# -----------------------------------------------------------------------------
85
def typer( text ):
86
    for c in text:
87
        type( c )
88
        random_wait()
89
90
# -----------------------------------------------------------------------------
91
# Type a line of text followed by typing the ENTER key.
92
# -----------------------------------------------------------------------------
93
def typeln( text ):
94
    typer( text )
95
    enter()
96
97
# -----------------------------------------------------------------------------
98
# Injects a definition.
99
# -----------------------------------------------------------------------------
100
def autoinsert():
101
    type( Key.SPACE, Key.CTRL )
102
    random_wait()
103
104
# -----------------------------------------------------------------------------
105
# Types the TAB key.
106
# -----------------------------------------------------------------------------
107
def tab():
108
    typer( Key.TAB )
109
110
# -----------------------------------------------------------------------------
111
# Types the ENTER key.
112
# -----------------------------------------------------------------------------
113
def enter():
114
    typer( Key.ENTER )
115
116
# -----------------------------------------------------------------------------
117
# Types the ESC key.
118
# -----------------------------------------------------------------------------
119
def esc():
120
    typer( Key.ESC )
121
122
# -----------------------------------------------------------------------------
123
# Types the DOWN arrow key.
124
# -----------------------------------------------------------------------------
125
def down():
126
    typer( Key.DOWN )
127
128
# -----------------------------------------------------------------------------
129
# Types the HOME key.
130
# -----------------------------------------------------------------------------
131
def home():
132
    typer( Key.HOME )
133
134
# -----------------------------------------------------------------------------
135
# Types the END key.
136
# -----------------------------------------------------------------------------
137
def end():
138
    typer( Key.END )
139
140
# -----------------------------------------------------------------------------
141
# Types the BACKSPACE key.
142
# -----------------------------------------------------------------------------
143
def backspace():
144
    typer( Key.BACKSPACE )
145
146
# -----------------------------------------------------------------------------
147
# Types the INSERT key, often to insert a new definition.
148
# -----------------------------------------------------------------------------
149
def insert():
150
    typer( Key.INSERT )
151
152
# -----------------------------------------------------------------------------
153
# Types the DELETE key, often to remove selected text.
154
# -----------------------------------------------------------------------------
155
def delete():
156
    typer( Key.DELETE )
157
158
# -----------------------------------------------------------------------------
159
# Moves the cursor one word to the right.
160
# -----------------------------------------------------------------------------
161
def skip_right():
162
    type( Key.RIGHT, Key.CTRL )
163
    random_wait()
164
165
def select_word_right():
166
    type( Key.RIGHT, Key.CTRL + Key.SHIFT )
167
    random_wait()
168
169
# -----------------------------------------------------------------------------
170
# Types ENTER twice to begin a new paragraph.
171
# -----------------------------------------------------------------------------
172
def paragraph():
173
    recur( 2, enter )
174
    wait( 1.5 )
175
176
# -----------------------------------------------------------------------------
177
# Writes a heading to the document using the given text value as the content.
178
# -----------------------------------------------------------------------------
179
def heading( text ):
180
    typer( "# " + text )
181
    paragraph()
182
183
# -----------------------------------------------------------------------------
184
# Clicks the "Create" button to add a new definition.
185
# -----------------------------------------------------------------------------
186
def click_create():
187
    click("1594187923258.png")
188
    wait( .5 )
189
190
# -----------------------------------------------------------------------------
191
# Changes the text for the actively selected definition.
192
# -----------------------------------------------------------------------------
193
def rename_definition( text ):
194
    typer( Key.F2 )
195
    typer( text )
196
    enter()
197
    wait( .5 )
198
199
# -----------------------------------------------------------------------------
200
# Searches for the given text within the document.
201
# -----------------------------------------------------------------------------
202
def edit_find( text ):
203
    type( "f", Key.CTRL )
204
    typer( text )
205
    enter()
206
    wait( .25 )
207
    esc()
208
    wait( .5 )
209
210
# -----------------------------------------------------------------------------
211
# Searches for the next occurrence of the previous search term.
212
# -----------------------------------------------------------------------------
213
def edit_find_next():
214
    typer( Key.F3 )
215
    wait( .5 )
216
217
# -----------------------------------------------------------------------------
218
# Opens a dialog for selecting a file.
219
# -----------------------------------------------------------------------------
220
def file_open():
221
    type( "o", Key.CTRL )
222
    wait( 1 )
2231
M src/main/java/com/scrivenvar/AbstractFileFactory.java
2929
3030
import com.scrivenvar.service.Settings;
31
import com.scrivenvar.util.ProtocolScheme;
3132
3233
import java.nio.file.Path;
...
100101
   * @param path The path to a source of definitions.
101102
   */
102
  protected void unknownFileType( final String type, final String path ) {
103
  protected void unknownFileType(
104
      final ProtocolScheme type, final String path ) {
103105
    final String msg = format( MSG_UNKNOWN_FILE_TYPE, type, path );
104106
    throw new IllegalArgumentException( msg );
M src/main/java/com/scrivenvar/Constants.java
3838
public class Constants {
3939
40
  public final static Settings SETTINGS = Services.load( Settings.class );
40
  public static final Settings SETTINGS = Services.load( Settings.class );
4141
4242
  /**
...
8383
  public static final String PREFS_STATE = get( "preferences.root.state" );
8484
85
  // Refer to filename extension settings in the configuration file. Do not
86
  // terminate these prefixes with a period.
85
  /**
86
   * Refer to filename extension settings in the configuration file. Do not
87
   * terminate these prefixes with a period.
88
   */
8789
  public static final String GLOB_PREFIX_FILE = "file.ext";
8890
  public static final String GLOB_PREFIX_DEFINITION =
8991
      "definition." + GLOB_PREFIX_FILE;
9092
91
  // Different definition source protocols.
92
  public static final String DEFINITION_PROTOCOL_UNKNOWN = "unknown";
93
  public static final String DEFINITION_PROTOCOL_FILE = "file";
93
  /**
94
   * Three parameters: line number, column number, and offset.
95
   */
96
  public static final String STATUS_BAR_LINE = "Main.status.line";
9497
95
  // Three parameters: line number, column number, and offset
96
  public static final String STATUS_BAR_LINE = "Main.statusbar.line";
98
  public static final String STATUS_BAR_OK = "Main.status.state.default";
9799
98
  // "OK" text
99
  public static final String STATUS_BAR_OK = "Main.statusbar.state.default";
100
  public static final String STATUS_PARSE_ERROR = "Main.statusbar.parse.error";
100
  /**
101
   * Used to show an error while parsing, usually syntactical.
102
   */
103
  public static final String STATUS_PARSE_ERROR = "Main.status.error.parse";
104
  public static final String STATUS_DEFINITION_BLANK = "Main.status.error.def.blank";
105
  public static final String STATUS_DEFINITION_EMPTY = "Main.status.error.def.empty";
106
107
  /**
108
   * One parameter: the word under the cursor that could not be found.
109
   */
110
  public static final String STATUS_DEFINITION_MISSING = "Main.status.error.def.missing";
101111
102112
  /**
...
120130
   */
121131
  public static final Path DEFAULT_DIRECTORY = Paths.get( USER_DIRECTORY );
132
133
  /**
134
   * Default starting delimiter for definition variables.
135
   */
136
  public static final String DEF_DELIM_BEGAN_DEFAULT = "${";
137
138
  /**
139
   * Default ending delimiter for definition variables.
140
   */
141
  public static final String DEF_DELIM_ENDED_DEFAULT = "}";
122142
123143
  /**
124144
   * Default starting delimiter when inserting R variables.
125145
   */
126
  public static final String R_DELIMITER_BEGAN_DEFAULT = "x( ";
146
  public static final String R_DELIM_BEGAN_DEFAULT = "x( ";
127147
128148
  /**
129149
   * Default ending delimiter when inserting R variables.
130150
   */
131
  public static final String R_DELIMITER_ENDED_DEFAULT = " )";
151
  public static final String R_DELIM_ENDED_DEFAULT = " )";
132152
133153
  /**
M src/main/java/com/scrivenvar/FileEditorTab.java
5656
5757
import static com.scrivenvar.Messages.get;
58
import static java.nio.charset.StandardCharsets.UTF_8;
59
import static java.util.Locale.ENGLISH;
60
import static javafx.application.Platform.runLater;
61
62
/**
63
 * Editor for a single file.
64
 */
65
public final class FileEditorTab extends Tab {
66
67
  private final Notifier mNotifier = Services.load( Notifier.class );
68
  private final MarkdownEditorPane mEditorPane = new MarkdownEditorPane();
69
70
  private final ReadOnlyBooleanWrapper mModified = new ReadOnlyBooleanWrapper();
71
  private final BooleanProperty canUndo = new SimpleBooleanProperty();
72
  private final BooleanProperty canRedo = new SimpleBooleanProperty();
73
74
  /**
75
   * Character encoding used by the file (or default encoding if none found).
76
   */
77
  private Charset mEncoding = UTF_8;
78
79
  /**
80
   * File to load into the editor.
81
   */
82
  private Path mPath;
83
84
  public FileEditorTab( final Path path ) {
85
    setPath( path );
86
87
    mModified.addListener( ( observable, oldPath, newPath ) -> updateTab() );
88
89
    setOnSelectionChanged( e -> {
90
      if( isSelected() ) {
91
        runLater( this::activated );
92
        requestFocus();
93
      }
94
    } );
95
  }
96
97
  private void updateTab() {
98
    setText( getTabTitle() );
99
    setGraphic( getModifiedMark() );
100
    setTooltip( getTabTooltip() );
101
  }
102
103
  /**
104
   * Returns the base filename (without the directory names).
105
   *
106
   * @return The untitled text if the path hasn't been set.
107
   */
108
  private String getTabTitle() {
109
    return getPath().getFileName().toString();
110
  }
111
112
  /**
113
   * Returns the full filename represented by the path.
114
   *
115
   * @return The untitled text if the path hasn't been set.
116
   */
117
  private Tooltip getTabTooltip() {
118
    final Path filePath = getPath();
119
    return new Tooltip( filePath == null ? "" : filePath.toString() );
120
  }
121
122
  /**
123
   * Returns a marker to indicate whether the file has been modified.
124
   *
125
   * @return "*" when the file has changed; otherwise null.
126
   */
127
  private Text getModifiedMark() {
128
    return isModified() ? new Text( "*" ) : null;
129
  }
130
131
  /**
132
   * Called when the user switches tab.
133
   */
134
  private void activated() {
135
    // Tab is closed or no longer active.
136
    if( getTabPane() == null || !isSelected() ) {
137
      return;
138
    }
139
140
    // If the tab is devoid of content, load it.
141
    if( getContent() == null ) {
142
      readFile();
143
      initLayout();
144
      initUndoManager();
145
    }
146
  }
147
148
  private void initLayout() {
149
    setContent( getScrollPane() );
150
  }
151
152
  /**
153
   * Tracks undo requests, but can only be called <em>after</em> load.
154
   */
155
  private void initUndoManager() {
156
    final UndoManager<?> undoManager = getUndoManager();
157
    undoManager.forgetHistory();
158
159
    // Bind the editor undo manager to the properties.
160
    mModified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) );
161
    canUndo.bind( undoManager.undoAvailableProperty() );
162
    canRedo.bind( undoManager.redoAvailableProperty() );
163
  }
164
165
  private void requestFocus() {
166
    getEditorPane().requestFocus();
167
  }
168
169
  /**
170
   * Searches from the caret position forward for the given string.
171
   *
172
   * @param needle The text string to match.
173
   */
174
  public void searchNext( final String needle ) {
175
    final String haystack = getEditorText();
176
    int index = haystack.indexOf( needle, getCaretPosition() );
177
178
    // Wrap around.
179
    if( index == -1 ) {
180
      index = haystack.indexOf( needle );
181
    }
182
183
    if( index >= 0 ) {
184
      setCaretPosition( index );
185
      getEditor().selectRange( index, index + needle.length() );
186
    }
187
  }
188
189
  /**
190
   * Gets a reference to the scroll pane that houses the editor.
191
   *
192
   * @return The editor's scroll pane, containing a vertical scrollbar.
193
   */
194
  public VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() {
195
    return getEditorPane().getScrollPane();
196
  }
197
198
  /**
199
   * Returns the index into the text where the caret blinks happily away.
200
   *
201
   * @return A number from 0 to the editor's document text length.
202
   */
203
  public int getCaretPosition() {
204
    return getEditor().getCaretPosition();
205
  }
206
207
  /**
208
   * Moves the caret to a given offset.
209
   *
210
   * @param offset The new caret offset.
211
   */
212
  private void setCaretPosition( final int offset ) {
213
    getEditor().moveTo( offset );
214
    getEditor().requestFollowCaret();
215
  }
216
217
  /**
218
   * Returns the text area associated with this tab.
219
   *
220
   * @return A text editor.
221
   */
222
  private StyleClassedTextArea getEditor() {
223
    return getEditorPane().getEditor();
224
  }
225
226
  /**
227
   * Returns true if the given path exactly matches this tab's path.
228
   *
229
   * @param check The path to compare against.
230
   * @return true The paths are the same.
231
   */
232
  public boolean isPath( final Path check ) {
233
    final Path filePath = getPath();
234
235
    return filePath != null && filePath.equals( check );
236
  }
237
238
  /**
239
   * Reads the entire file contents from the path associated with this tab.
240
   */
241
  private void readFile() {
242
    final Path path = getPath();
243
    final File file = path.toFile();
244
245
    try {
246
      if( file.exists() ) {
247
        if( file.canWrite() && file.canRead() ) {
248
          final EditorPane pane = getEditorPane();
249
          pane.setText( asString( Files.readAllBytes( path ) ) );
250
          pane.scrollToTop();
251
        }
252
        else {
253
          final String msg = get(
254
              "FileEditor.loadFailed.message",
255
              file.toString(),
256
              get( "FileEditor.loadFailed.reason.permissions" )
257
          );
258
          getNotifier().notify( msg );
259
        }
260
      }
261
    } catch( final Exception ex ) {
262
      getNotifier().notify( ex );
263
    }
264
  }
265
266
  /**
267
   * Saves the entire file contents from the path associated with this tab.
268
   *
269
   * @return true The file has been saved.
270
   */
271
  public boolean save() {
272
    try {
273
      final EditorPane editor = getEditorPane();
274
      Files.write( getPath(), asBytes( editor.getText() ) );
275
      editor.getUndoManager().mark();
276
      return true;
277
    } catch( final Exception ex ) {
278
      return alert(
279
          "FileEditor.saveFailed.title",
280
          "FileEditor.saveFailed.message",
281
          ex
282
      );
283
    }
284
  }
285
286
  /**
287
   * Creates an alert dialog and waits for it to close.
288
   *
289
   * @param titleKey   Resource bundle key for the alert dialog title.
290
   * @param messageKey Resource bundle key for the alert dialog message.
291
   * @param e          The unexpected happening.
292
   * @return false
293
   */
294
  @SuppressWarnings("SameParameterValue")
295
  private boolean alert(
296
      final String titleKey, final String messageKey, final Exception e ) {
297
    final Notifier service = getNotifier();
298
    final Path filePath = getPath();
299
300
    final Notification message = service.createNotification(
301
        get( titleKey ),
302
        get( messageKey ),
303
        filePath == null ? "" : filePath,
304
        e.getMessage()
305
    );
306
307
    try {
308
      service.createError( getWindow(), message ).showAndWait();
309
    } catch( final Exception ex ) {
310
      getNotifier().notify( ex );
311
    }
312
313
    return false;
314
  }
315
316
  private Window getWindow() {
317
    final Scene scene = getEditorPane().getScene();
318
319
    if( scene == null ) {
320
      throw new UnsupportedOperationException( "No scene window available" );
321
    }
322
323
    return scene.getWindow();
324
  }
325
326
  /**
327
   * Returns a best guess at the file encoding. If the encoding could not be
328
   * detected, this will return the default charset for the JVM.
329
   *
330
   * @param bytes The bytes to perform character encoding detection.
331
   * @return The character encoding.
332
   */
333
  private Charset detectEncoding( final byte[] bytes ) {
334
    final var detector = new UniversalDetector( null );
335
    detector.handleData( bytes, 0, bytes.length );
336
    detector.dataEnd();
337
338
    final String charset = detector.getDetectedCharset();
339
340
    return charset == null
341
        ? Charset.defaultCharset()
342
        : Charset.forName( charset.toUpperCase( ENGLISH ) );
343
  }
344
345
  /**
346
   * Converts the given string to an array of bytes using the encoding that was
347
   * originally detected (if any) and associated with this file.
348
   *
349
   * @param text The text to convert into the original file encoding.
350
   * @return A series of bytes ready for writing to a file.
351
   */
352
  private byte[] asBytes( final String text ) {
353
    return text.getBytes( getEncoding() );
354
  }
355
356
  /**
357
   * Converts the given bytes into a Java String. This will call setEncoding
358
   * with the encoding detected by the CharsetDetector.
359
   *
360
   * @param text The text of unknown character encoding.
361
   * @return The text, in its auto-detected encoding, as a String.
362
   */
363
  private String asString( final byte[] text ) {
364
    setEncoding( detectEncoding( text ) );
365
    return new String( text, getEncoding() );
366
  }
367
368
  /**
369
   * Returns the path to the file being edited in this tab.
370
   *
371
   * @return A non-null instance.
372
   */
373
  public Path getPath() {
374
    return mPath;
375
  }
376
377
  /**
378
   * Sets the path to a file for editing and then updates the tab with the
379
   * file contents.
380
   *
381
   * @param path A non-null instance.
382
   */
383
  public void setPath( final Path path ) {
384
    assert path != null;
385
    mPath = path;
386
387
    updateTab();
388
  }
389
390
  public boolean isModified() {
391
    return mModified.get();
392
  }
393
394
  ReadOnlyBooleanProperty modifiedProperty() {
395
    return mModified.getReadOnlyProperty();
396
  }
397
398
  BooleanProperty canUndoProperty() {
399
    return this.canUndo;
400
  }
401
402
  BooleanProperty canRedoProperty() {
403
    return this.canRedo;
404
  }
405
406
  private UndoManager<?> getUndoManager() {
407
    return getEditorPane().getUndoManager();
408
  }
409
410
  /**
411
   * Forwards to the editor pane's listeners for text change events.
412
   *
413
   * @param listener The listener to notify when the text changes.
414
   */
415
  public void addTextChangeListener( final ChangeListener<String> listener ) {
416
    getEditorPane().addTextChangeListener( listener );
417
  }
418
419
  /**
420
   * Forwards to the editor pane's listeners for caret change events.
421
   *
422
   * @param listener Notified when the caret position changes.
423
   */
424
  public void addCaretPositionListener(
425
      final ChangeListener<? super Integer> listener ) {
426
    getEditorPane().addCaretPositionListener( listener );
427
  }
428
429
  /**
430
   * Forwards to the editor pane's listeners for paragraph index change events.
431
   *
432
   * @param listener Notified when the caret's paragraph index changes.
433
   */
434
  public void addCaretParagraphListener(
435
      final ChangeListener<? super Integer> listener ) {
436
    getEditorPane().addCaretParagraphListener( listener );
437
  }
438
439
  public <T extends Event> void addEventFilter(
440
      final EventType<T> eventType,
441
      final EventHandler<? super T> eventFilter ) {
442
    getEditorPane().getEditor().addEventFilter( eventType, eventFilter );
443
  }
444
445
  /**
446
   * Forwards the request to the editor pane.
447
   *
448
   * @return The text to process.
449
   */
450
  public String getEditorText() {
451
    return getEditorPane().getText();
452
  }
453
454
  /**
455
   * Returns the editor pane, or creates one if it doesn't yet exist.
456
   *
457
   * @return The editor pane, never null.
458
   */
459
  @NotNull
460
  public MarkdownEditorPane getEditorPane() {
461
    return mEditorPane;
462
  }
463
464
  /**
465
   * Returns the encoding for the file, defaulting to UTF-8 if it hasn't been
466
   * determined.
467
   *
468
   * @return The file encoding or UTF-8 if unknown.
469
   */
470
  private Charset getEncoding() {
471
    return mEncoding;
472
  }
473
474
  private void setEncoding( final Charset encoding ) {
475
    assert encoding != null;
476
    mEncoding = encoding;
477
  }
478
479
  private Notifier getNotifier() {
480
    return mNotifier;
58
import static com.scrivenvar.StatusBarNotifier.alert;
59
import static com.scrivenvar.StatusBarNotifier.getNotifier;
60
import static java.nio.charset.StandardCharsets.UTF_8;
61
import static java.util.Locale.ENGLISH;
62
import static javafx.application.Platform.runLater;
63
64
/**
65
 * Editor for a single file.
66
 */
67
public final class FileEditorTab extends Tab {
68
69
  private final MarkdownEditorPane mEditorPane = new MarkdownEditorPane();
70
71
  private final ReadOnlyBooleanWrapper mModified = new ReadOnlyBooleanWrapper();
72
  private final BooleanProperty canUndo = new SimpleBooleanProperty();
73
  private final BooleanProperty canRedo = new SimpleBooleanProperty();
74
75
  /**
76
   * Character encoding used by the file (or default encoding if none found).
77
   */
78
  private Charset mEncoding = UTF_8;
79
80
  /**
81
   * File to load into the editor.
82
   */
83
  private Path mPath;
84
85
  public FileEditorTab( final Path path ) {
86
    setPath( path );
87
88
    mModified.addListener( ( observable, oldPath, newPath ) -> updateTab() );
89
90
    setOnSelectionChanged( e -> {
91
      if( isSelected() ) {
92
        runLater( this::activated );
93
        requestFocus();
94
      }
95
    } );
96
  }
97
98
  private void updateTab() {
99
    setText( getTabTitle() );
100
    setGraphic( getModifiedMark() );
101
    setTooltip( getTabTooltip() );
102
  }
103
104
  /**
105
   * Returns the base filename (without the directory names).
106
   *
107
   * @return The untitled text if the path hasn't been set.
108
   */
109
  private String getTabTitle() {
110
    return getPath().getFileName().toString();
111
  }
112
113
  /**
114
   * Returns the full filename represented by the path.
115
   *
116
   * @return The untitled text if the path hasn't been set.
117
   */
118
  private Tooltip getTabTooltip() {
119
    final Path filePath = getPath();
120
    return new Tooltip( filePath == null ? "" : filePath.toString() );
121
  }
122
123
  /**
124
   * Returns a marker to indicate whether the file has been modified.
125
   *
126
   * @return "*" when the file has changed; otherwise null.
127
   */
128
  private Text getModifiedMark() {
129
    return isModified() ? new Text( "*" ) : null;
130
  }
131
132
  /**
133
   * Called when the user switches tab.
134
   */
135
  private void activated() {
136
    // Tab is closed or no longer active.
137
    if( getTabPane() == null || !isSelected() ) {
138
      return;
139
    }
140
141
    // If the tab is devoid of content, load it.
142
    if( getContent() == null ) {
143
      readFile();
144
      initLayout();
145
      initUndoManager();
146
    }
147
  }
148
149
  private void initLayout() {
150
    setContent( getScrollPane() );
151
  }
152
153
  /**
154
   * Tracks undo requests, but can only be called <em>after</em> load.
155
   */
156
  private void initUndoManager() {
157
    final UndoManager<?> undoManager = getUndoManager();
158
    undoManager.forgetHistory();
159
160
    // Bind the editor undo manager to the properties.
161
    mModified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) );
162
    canUndo.bind( undoManager.undoAvailableProperty() );
163
    canRedo.bind( undoManager.redoAvailableProperty() );
164
  }
165
166
  private void requestFocus() {
167
    getEditorPane().requestFocus();
168
  }
169
170
  /**
171
   * Searches from the caret position forward for the given string.
172
   *
173
   * @param needle The text string to match.
174
   */
175
  public void searchNext( final String needle ) {
176
    final String haystack = getEditorText();
177
    int index = haystack.indexOf( needle, getCaretPosition() );
178
179
    // Wrap around.
180
    if( index == -1 ) {
181
      index = haystack.indexOf( needle );
182
    }
183
184
    if( index >= 0 ) {
185
      setCaretPosition( index );
186
      getEditor().selectRange( index, index + needle.length() );
187
    }
188
  }
189
190
  /**
191
   * Gets a reference to the scroll pane that houses the editor.
192
   *
193
   * @return The editor's scroll pane, containing a vertical scrollbar.
194
   */
195
  public VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() {
196
    return getEditorPane().getScrollPane();
197
  }
198
199
  /**
200
   * Returns the index into the text where the caret blinks happily away.
201
   *
202
   * @return A number from 0 to the editor's document text length.
203
   */
204
  public int getCaretPosition() {
205
    return getEditor().getCaretPosition();
206
  }
207
208
  /**
209
   * Moves the caret to a given offset.
210
   *
211
   * @param offset The new caret offset.
212
   */
213
  private void setCaretPosition( final int offset ) {
214
    getEditor().moveTo( offset );
215
    getEditor().requestFollowCaret();
216
  }
217
218
  /**
219
   * Returns the text area associated with this tab.
220
   *
221
   * @return A text editor.
222
   */
223
  private StyleClassedTextArea getEditor() {
224
    return getEditorPane().getEditor();
225
  }
226
227
  /**
228
   * Returns true if the given path exactly matches this tab's path.
229
   *
230
   * @param check The path to compare against.
231
   * @return true The paths are the same.
232
   */
233
  public boolean isPath( final Path check ) {
234
    final Path filePath = getPath();
235
236
    return filePath != null && filePath.equals( check );
237
  }
238
239
  /**
240
   * Reads the entire file contents from the path associated with this tab.
241
   */
242
  private void readFile() {
243
    final Path path = getPath();
244
    final File file = path.toFile();
245
246
    try {
247
      if( file.exists() ) {
248
        if( file.canWrite() && file.canRead() ) {
249
          final EditorPane pane = getEditorPane();
250
          pane.setText( asString( Files.readAllBytes( path ) ) );
251
          pane.scrollToTop();
252
        }
253
        else {
254
          final String msg = get( "FileEditor.loadFailed.reason.permissions" );
255
          alert( "FileEditor.loadFailed.message", file.toString(), msg );
256
        }
257
      }
258
    } catch( final Exception ex ) {
259
      alert( ex );
260
    }
261
  }
262
263
  /**
264
   * Saves the entire file contents from the path associated with this tab.
265
   *
266
   * @return true The file has been saved.
267
   */
268
  public boolean save() {
269
    try {
270
      final EditorPane editor = getEditorPane();
271
      Files.write( getPath(), asBytes( editor.getText() ) );
272
      editor.getUndoManager().mark();
273
      return true;
274
    } catch( final Exception ex ) {
275
      return popupAlert(
276
          "FileEditor.saveFailed.title",
277
          "FileEditor.saveFailed.message",
278
          ex
279
      );
280
    }
281
  }
282
283
  /**
284
   * Creates an alert dialog and waits for it to close.
285
   *
286
   * @param titleKey   Resource bundle key for the alert dialog title.
287
   * @param messageKey Resource bundle key for the alert dialog message.
288
   * @param e          The unexpected happening.
289
   * @return false
290
   */
291
  @SuppressWarnings("SameParameterValue")
292
  private boolean popupAlert(
293
      final String titleKey, final String messageKey, final Exception e ) {
294
    final Notifier service = getNotifier();
295
    final Path filePath = getPath();
296
297
    final Notification message = service.createNotification(
298
        get( titleKey ),
299
        get( messageKey ),
300
        filePath == null ? "" : filePath,
301
        e.getMessage()
302
    );
303
304
    try {
305
      service.createError( getWindow(), message ).showAndWait();
306
    } catch( final Exception ex ) {
307
      alert( ex );
308
    }
309
310
    return false;
311
  }
312
313
  private Window getWindow() {
314
    final Scene scene = getEditorPane().getScene();
315
316
    if( scene == null ) {
317
      throw new UnsupportedOperationException( "No scene window available" );
318
    }
319
320
    return scene.getWindow();
321
  }
322
323
  /**
324
   * Returns a best guess at the file encoding. If the encoding could not be
325
   * detected, this will return the default charset for the JVM.
326
   *
327
   * @param bytes The bytes to perform character encoding detection.
328
   * @return The character encoding.
329
   */
330
  private Charset detectEncoding( final byte[] bytes ) {
331
    final var detector = new UniversalDetector( null );
332
    detector.handleData( bytes, 0, bytes.length );
333
    detector.dataEnd();
334
335
    final String charset = detector.getDetectedCharset();
336
337
    return charset == null
338
        ? Charset.defaultCharset()
339
        : Charset.forName( charset.toUpperCase( ENGLISH ) );
340
  }
341
342
  /**
343
   * Converts the given string to an array of bytes using the encoding that was
344
   * originally detected (if any) and associated with this file.
345
   *
346
   * @param text The text to convert into the original file encoding.
347
   * @return A series of bytes ready for writing to a file.
348
   */
349
  private byte[] asBytes( final String text ) {
350
    return text.getBytes( getEncoding() );
351
  }
352
353
  /**
354
   * Converts the given bytes into a Java String. This will call setEncoding
355
   * with the encoding detected by the CharsetDetector.
356
   *
357
   * @param text The text of unknown character encoding.
358
   * @return The text, in its auto-detected encoding, as a String.
359
   */
360
  private String asString( final byte[] text ) {
361
    setEncoding( detectEncoding( text ) );
362
    return new String( text, getEncoding() );
363
  }
364
365
  /**
366
   * Returns the path to the file being edited in this tab.
367
   *
368
   * @return A non-null instance.
369
   */
370
  public Path getPath() {
371
    return mPath;
372
  }
373
374
  /**
375
   * Sets the path to a file for editing and then updates the tab with the
376
   * file contents.
377
   *
378
   * @param path A non-null instance.
379
   */
380
  public void setPath( final Path path ) {
381
    assert path != null;
382
    mPath = path;
383
384
    updateTab();
385
  }
386
387
  public boolean isModified() {
388
    return mModified.get();
389
  }
390
391
  ReadOnlyBooleanProperty modifiedProperty() {
392
    return mModified.getReadOnlyProperty();
393
  }
394
395
  BooleanProperty canUndoProperty() {
396
    return this.canUndo;
397
  }
398
399
  BooleanProperty canRedoProperty() {
400
    return this.canRedo;
401
  }
402
403
  private UndoManager<?> getUndoManager() {
404
    return getEditorPane().getUndoManager();
405
  }
406
407
  /**
408
   * Forwards to the editor pane's listeners for text change events.
409
   *
410
   * @param listener The listener to notify when the text changes.
411
   */
412
  public void addTextChangeListener( final ChangeListener<String> listener ) {
413
    getEditorPane().addTextChangeListener( listener );
414
  }
415
416
  /**
417
   * Forwards to the editor pane's listeners for caret change events.
418
   *
419
   * @param listener Notified when the caret position changes.
420
   */
421
  public void addCaretPositionListener(
422
      final ChangeListener<? super Integer> listener ) {
423
    getEditorPane().addCaretPositionListener( listener );
424
  }
425
426
  /**
427
   * Forwards to the editor pane's listeners for paragraph index change events.
428
   *
429
   * @param listener Notified when the caret's paragraph index changes.
430
   */
431
  public void addCaretParagraphListener(
432
      final ChangeListener<? super Integer> listener ) {
433
    getEditorPane().addCaretParagraphListener( listener );
434
  }
435
436
  public <T extends Event> void addEventFilter(
437
      final EventType<T> eventType,
438
      final EventHandler<? super T> eventFilter ) {
439
    getEditor().addEventFilter( eventType, eventFilter );
440
  }
441
442
  /**
443
   * Forwards the request to the editor pane.
444
   *
445
   * @return The text to process.
446
   */
447
  public String getEditorText() {
448
    return getEditorPane().getText();
449
  }
450
451
  /**
452
   * Returns the editor pane, or creates one if it doesn't yet exist.
453
   *
454
   * @return The editor pane, never null.
455
   */
456
  @NotNull
457
  public MarkdownEditorPane getEditorPane() {
458
    return mEditorPane;
459
  }
460
461
  /**
462
   * Returns the encoding for the file, defaulting to UTF-8 if it hasn't been
463
   * determined.
464
   *
465
   * @return The file encoding or UTF-8 if unknown.
466
   */
467
  private Charset getEncoding() {
468
    return mEncoding;
469
  }
470
471
  private void setEncoding( final Charset encoding ) {
472
    assert encoding != null;
473
    mEncoding = encoding;
481474
  }
482475
M src/main/java/com/scrivenvar/FileEditorTabPane.java
7171
public final class FileEditorTabPane extends TabPane {
7272
73
  private final static String FILTER_EXTENSION_TITLES =
73
  private static final String FILTER_EXTENSION_TITLES =
7474
      "Dialog.file.choose.filter";
7575
76
  private final static Options sOptions = Services.load( Options.class );
77
  private final static Notifier sNotifier = Services.load( Notifier.class );
76
  private static final Options sOptions = Services.load( Options.class );
77
  private static final Notifier sNotifier = Services.load( Notifier.class );
7878
7979
  private final ReadOnlyObjectWrapper<Path> mOpenDefinition =
...
127127
                  ( tab ) -> {
128128
                    final var fet = (FileEditorTab) tab;
129
                    fet.modifiedProperty()
130
                       .addListener( modifiedListener );
129
                    fet.modifiedProperty().addListener( modifiedListener );
131130
                  } );
132131
            }
133132
            else if( change.wasRemoved() ) {
134133
              change.getRemoved().forEach(
135
                  ( tab ) ->
136
                      ((FileEditorTab) tab).modifiedProperty()
137
                                           .removeListener( modifiedListener ) );
134
                  ( tab ) -> {
135
                    final var fet = (FileEditorTab) tab;
136
                    fet.modifiedProperty().removeListener( modifiedListener );
137
                  }
138
              );
138139
            }
139140
          }
M src/main/java/com/scrivenvar/Main.java
3131
import com.scrivenvar.service.Options;
3232
import com.scrivenvar.service.Snitch;
33
import com.scrivenvar.service.events.Notifier;
3433
import com.scrivenvar.util.ResourceWalker;
3534
import com.scrivenvar.util.StageState;
...
4948
import static com.scrivenvar.Constants.*;
5049
import static com.scrivenvar.Messages.get;
50
import static com.scrivenvar.StatusBarNotifier.alert;
5151
import static java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment;
5252
import static java.awt.font.TextAttribute.*;
...
6868
  }
6969
70
  /**
71
   * Must be static, otherwise instant crash.
72
   */
73
  private final static Notifier sNotifier = Services.load( Notifier.class );
7470
  private final Options mOptions = Services.load( Options.class );
7571
  private final Snitch mSnitch = Services.load( Snitch.class );
...
132128
              ge.registerFont( font.deriveFont( attributes ) );
133129
            } catch( final Exception e ) {
134
              getNotifier().notify( e );
130
              alert( e );
135131
            }
136132
          }
137133
      );
138134
    } catch( final Exception e ) {
139
      getNotifier().notify( e );
135
      alert( e );
140136
    }
141137
  }
...
198194
    thread.interrupt();
199195
    thread.join();
200
  }
201
202
  private static Notifier getNotifier() {
203
    return sNotifier;
204196
  }
205197
M src/main/java/com/scrivenvar/MainWindow.java
3434
import com.scrivenvar.definition.MapInterpolator;
3535
import com.scrivenvar.definition.yaml.YamlDefinitionSource;
36
import com.scrivenvar.editors.EditorPane;
37
import com.scrivenvar.editors.VariableNameInjector;
38
import com.scrivenvar.editors.markdown.MarkdownEditorPane;
39
import com.scrivenvar.preferences.UserPreferences;
40
import com.scrivenvar.preview.HTMLPreviewPane;
41
import com.scrivenvar.processors.HtmlPreviewProcessor;
42
import com.scrivenvar.processors.Processor;
43
import com.scrivenvar.processors.ProcessorFactory;
44
import com.scrivenvar.service.Options;
45
import com.scrivenvar.service.Snitch;
46
import com.scrivenvar.service.events.Notifier;
47
import com.scrivenvar.spelling.api.SpellCheckListener;
48
import com.scrivenvar.spelling.api.SpellChecker;
49
import com.scrivenvar.spelling.impl.PermissiveSpeller;
50
import com.scrivenvar.spelling.impl.SymSpellSpeller;
51
import com.scrivenvar.util.Action;
52
import com.scrivenvar.util.ActionBuilder;
53
import com.scrivenvar.util.ActionUtils;
54
import com.vladsch.flexmark.parser.Parser;
55
import com.vladsch.flexmark.util.ast.NodeVisitor;
56
import com.vladsch.flexmark.util.ast.VisitHandler;
57
import javafx.beans.binding.Bindings;
58
import javafx.beans.binding.BooleanBinding;
59
import javafx.beans.property.BooleanProperty;
60
import javafx.beans.property.SimpleBooleanProperty;
61
import javafx.beans.value.ChangeListener;
62
import javafx.beans.value.ObservableBooleanValue;
63
import javafx.beans.value.ObservableValue;
64
import javafx.collections.ListChangeListener.Change;
65
import javafx.collections.ObservableList;
66
import javafx.event.Event;
67
import javafx.event.EventHandler;
68
import javafx.geometry.Pos;
69
import javafx.scene.Node;
70
import javafx.scene.Scene;
71
import javafx.scene.control.*;
72
import javafx.scene.control.Alert.AlertType;
73
import javafx.scene.image.Image;
74
import javafx.scene.image.ImageView;
75
import javafx.scene.input.Clipboard;
76
import javafx.scene.input.ClipboardContent;
77
import javafx.scene.input.KeyEvent;
78
import javafx.scene.layout.BorderPane;
79
import javafx.scene.layout.VBox;
80
import javafx.scene.text.Text;
81
import javafx.stage.Window;
82
import javafx.stage.WindowEvent;
83
import javafx.util.Duration;
84
import org.apache.commons.lang3.SystemUtils;
85
import org.controlsfx.control.StatusBar;
86
import org.fxmisc.richtext.StyleClassedTextArea;
87
import org.fxmisc.richtext.model.StyleSpansBuilder;
88
import org.reactfx.value.Val;
89
90
import java.io.BufferedReader;
91
import java.io.InputStreamReader;
92
import java.nio.file.Path;
93
import java.nio.file.Paths;
94
import java.util.*;
95
import java.util.concurrent.atomic.AtomicInteger;
96
import java.util.function.Consumer;
97
import java.util.function.Function;
98
import java.util.prefs.Preferences;
99
import java.util.stream.Collectors;
100
101
import static com.scrivenvar.Constants.*;
102
import static com.scrivenvar.Messages.get;
103
import static com.scrivenvar.util.StageState.*;
104
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
105
import static java.nio.charset.StandardCharsets.UTF_8;
106
import static java.util.Collections.emptyList;
107
import static java.util.Collections.singleton;
108
import static javafx.application.Platform.runLater;
109
import static javafx.event.Event.fireEvent;
110
import static javafx.scene.input.KeyCode.ENTER;
111
import static javafx.scene.input.KeyCode.TAB;
112
import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
113
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward;
114
115
/**
116
 * Main window containing a tab pane in the center for file editors.
117
 */
118
public class MainWindow implements Observer {
119
  /**
120
   * The {@code OPTIONS} variable must be declared before all other variables
121
   * to prevent subsequent initializations from failing due to missing user
122
   * preferences.
123
   */
124
  private final static Options sOptions = Services.load( Options.class );
125
  private final static Snitch SNITCH = Services.load( Snitch.class );
126
  private final static Notifier sNotifier = Services.load( Notifier.class );
127
128
  private final Scene mScene;
129
  private final StatusBar mStatusBar;
130
  private final Text mLineNumberText;
131
  private final TextField mFindTextField;
132
  private final SpellChecker mSpellChecker;
133
134
  private final Object mMutex = new Object();
135
136
  /**
137
   * Prevents re-instantiation of processing classes.
138
   */
139
  private final Map<FileEditorTab, Processor<String>> mProcessors =
140
      new HashMap<>();
141
142
  private final Map<String, String> mResolvedMap =
143
      new HashMap<>( DEFAULT_MAP_SIZE );
144
145
  private final EventHandler<PreferencesFxEvent> mRPreferencesListener =
146
      event -> rerender();
147
148
  /**
149
   * Called when the definition data is changed.
150
   */
151
  private final EventHandler<TreeItem.TreeModificationEvent<Event>>
152
      mTreeHandler = event -> {
153
    exportDefinitions( getDefinitionPath() );
154
    interpolateResolvedMap();
155
    rerender();
156
  };
157
158
  /**
159
   * Called to switch to the definition pane when the user presses the TAB key.
160
   */
161
  private final EventHandler<? super KeyEvent> mTabKeyHandler =
162
      (EventHandler<KeyEvent>) event -> {
163
        if( event.getCode() == TAB ) {
164
          getDefinitionPane().requestFocus();
165
          event.consume();
166
        }
167
      };
168
169
  /**
170
   * Called to inject the selected item when the user presses ENTER in the
171
   * definition pane.
172
   */
173
  private final EventHandler<? super KeyEvent> mDefinitionKeyHandler =
174
      event -> {
175
        if( event.getCode() == ENTER ) {
176
          getVariableNameInjector().injectSelectedItem();
177
        }
178
      };
179
180
  private final ChangeListener<Integer> mCaretPositionListener =
181
      ( observable, oldPosition, newPosition ) -> {
182
        final FileEditorTab tab = getActiveFileEditorTab();
183
        final EditorPane pane = tab.getEditorPane();
184
        final StyleClassedTextArea editor = pane.getEditor();
185
186
        getLineNumberText().setText(
187
            get( STATUS_BAR_LINE,
188
                 editor.getCurrentParagraph() + 1,
189
                 editor.getParagraphs().size(),
190
                 editor.getCaretPosition()
191
            )
192
        );
193
      };
194
195
  private final ChangeListener<Integer> mCaretParagraphListener =
196
      ( observable, oldIndex, newIndex ) ->
197
          scrollToParagraph( newIndex, true );
198
199
  private DefinitionSource mDefinitionSource = createDefaultDefinitionSource();
200
  private final DefinitionPane mDefinitionPane = new DefinitionPane();
201
  private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane();
202
  private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane(
203
      mCaretPositionListener,
204
      mCaretParagraphListener );
205
206
  /**
207
   * Listens on the definition pane for double-click events.
208
   */
209
  private final VariableNameInjector mVariableNameInjector
210
      = new VariableNameInjector( mDefinitionPane );
211
212
  public MainWindow() {
213
    sNotifier.addObserver( this );
214
215
    mStatusBar = createStatusBar();
216
    mLineNumberText = createLineNumberText();
217
    mFindTextField = createFindTextField();
218
    mScene = createScene();
219
    mSpellChecker = createSpellChecker();
220
221
    // Add the close request listener before the window is shown.
222
    initLayout();
223
  }
224
225
  /**
226
   * Called after the stage is shown.
227
   */
228
  public void init() {
229
    initFindInput();
230
    initSnitch();
231
    initDefinitionListener();
232
    initTabAddedListener();
233
    initTabChangedListener();
234
    initPreferences();
235
    initVariableNameInjector();
236
  }
237
238
  private void initLayout() {
239
    final var scene = getScene();
240
241
    scene.getStylesheets().add( STYLESHEET_SCENE );
242
    scene.windowProperty().addListener(
243
        ( unused, oldWindow, newWindow ) ->
244
            newWindow.setOnCloseRequest(
245
                e -> {
246
                  if( !getFileEditorPane().closeAllEditors() ) {
247
                    e.consume();
248
                  }
249
                }
250
            )
251
    );
252
  }
253
254
  /**
255
   * Initialize the find input text field to listen on F3, ENTER, and
256
   * ESCAPE key presses.
257
   */
258
  private void initFindInput() {
259
    final TextField input = getFindTextField();
260
261
    input.setOnKeyPressed( ( KeyEvent event ) -> {
262
      switch( event.getCode() ) {
263
        case F3:
264
        case ENTER:
265
          editFindNext();
266
          break;
267
        case F:
268
          if( !event.isControlDown() ) {
269
            break;
270
          }
271
        case ESCAPE:
272
          getStatusBar().setGraphic( null );
273
          getActiveFileEditorTab().getEditorPane().requestFocus();
274
          break;
275
      }
276
    } );
277
278
    // Remove when the input field loses focus.
279
    input.focusedProperty().addListener(
280
        ( focused, oldFocus, newFocus ) -> {
281
          if( !newFocus ) {
282
            getStatusBar().setGraphic( null );
283
          }
284
        }
285
    );
286
  }
287
288
  /**
289
   * Watch for changes to external files. In particular, this awaits
290
   * modifications to any XSL files associated with XML files being edited.
291
   * When
292
   * an XSL file is modified (external to the application), the snitch's ears
293
   * perk up and the file is reloaded. This keeps the XSL transformation up to
294
   * date with what's on the file system.
295
   */
296
  private void initSnitch() {
297
    SNITCH.addObserver( this );
298
  }
299
300
  /**
301
   * Listen for {@link FileEditorTabPane} to receive open definition file
302
   * event.
303
   */
304
  private void initDefinitionListener() {
305
    getFileEditorPane().onOpenDefinitionFileProperty().addListener(
306
        ( final ObservableValue<? extends Path> file,
307
          final Path oldPath, final Path newPath ) -> {
308
          openDefinitions( newPath );
309
          rerender();
310
        }
311
    );
312
  }
313
314
  /**
315
   * Re-instantiates all processors then re-renders the active tab. This
316
   * will refresh the resolved map, force R to re-initialize, and brute-force
317
   * XSLT file reloads.
318
   */
319
  private void rerender() {
320
    runLater(
321
        () -> {
322
          resetProcessors();
323
          renderActiveTab();
324
        }
325
    );
326
  }
327
328
  /**
329
   * When tabs are added, hook the various change listeners onto the new
330
   * tab sothat the preview pane refreshes as necessary.
331
   */
332
  private void initTabAddedListener() {
333
    final FileEditorTabPane editorPane = getFileEditorPane();
334
335
    // Make sure the text processor kicks off when new files are opened.
336
    final ObservableList<Tab> tabs = editorPane.getTabs();
337
338
    // Update the preview pane on tab changes.
339
    tabs.addListener(
340
        ( final Change<? extends Tab> change ) -> {
341
          while( change.next() ) {
342
            if( change.wasAdded() ) {
343
              // Multiple tabs can be added simultaneously.
344
              for( final Tab newTab : change.getAddedSubList() ) {
345
                final FileEditorTab tab = (FileEditorTab) newTab;
346
347
                initTextChangeListener( tab );
348
                initTabKeyEventListener( tab );
349
                initScrollEventListener( tab );
350
                initSpellCheckListener( tab );
351
//              initSyntaxListener( tab );
352
              }
353
            }
354
          }
355
        }
356
    );
357
  }
358
359
  private void initTextChangeListener( final FileEditorTab tab ) {
360
    tab.addTextChangeListener(
361
        ( editor, oldValue, newValue ) -> {
362
          process( tab );
363
          scrollToParagraph( getCurrentParagraphIndex() );
364
        }
365
    );
366
  }
367
368
  /**
369
   * Ensure that the keyboard events are received when a new tab is added
370
   * to the user interface.
371
   *
372
   * @param tab The tab editor that can trigger keyboard events.
373
   */
374
  private void initTabKeyEventListener( final FileEditorTab tab ) {
375
    tab.addEventFilter( KeyEvent.KEY_PRESSED, mTabKeyHandler );
376
  }
377
378
  private void initScrollEventListener( final FileEditorTab tab ) {
379
    final var scrollPane = tab.getScrollPane();
380
    final var scrollBar = getPreviewPane().getVerticalScrollBar();
381
382
    addShowListener( scrollPane, ( __ ) -> {
383
      final var handler = new ScrollEventHandler( scrollPane, scrollBar );
384
      handler.enabledProperty().bind( tab.selectedProperty() );
385
    } );
386
  }
387
388
  /**
389
   * Listen for changes to the any particular paragraph and perform a quick
390
   * spell check upon it. The style classes in the editor will be changed to
391
   * mark any spelling mistakes in the paragraph. The user may then interact
392
   * with any misspelled word (i.e., any piece of text that is marked) to
393
   * revise the spelling.
394
   *
395
   * @param tab The tab to spellcheck.
396
   */
397
  private void initSpellCheckListener( final FileEditorTab tab ) {
398
    final var editor = tab.getEditorPane().getEditor();
399
400
    // When the editor first appears, run a full spell check. This allows
401
    // spell checking while typing to be restricted to the active paragraph,
402
    // which is usually substantially smaller than the whole document.
403
    addShowListener(
404
        editor, ( __ ) -> spellcheck( editor, editor.getText() )
405
    );
406
407
    // Use the plain text changes so that notifications of style changes
408
    // are suppressed. Checking against the identity ensures that only
409
    // new text additions or deletions trigger proofreading.
410
    editor.plainTextChanges()
411
          .filter( p -> !p.isIdentity() ).subscribe( change -> {
412
413
      // Only perform a spell check on the current paragraph. The
414
      // entire document is processed once, when opened.
415
      final var offset = change.getPosition();
416
      final var position = editor.offsetToPosition( offset, Forward );
417
      final var paraId = position.getMajor();
418
      final var paragraph = editor.getParagraph( paraId );
419
      final var text = paragraph.getText();
420
421
      // Ensure that styles aren't doubled-up.
422
      editor.clearStyle( paraId );
423
424
      spellcheck( editor, text, paraId );
425
    } );
426
  }
427
428
  /**
429
   * Listen for new tab selection events.
430
   */
431
  private void initTabChangedListener() {
432
    final FileEditorTabPane editorPane = getFileEditorPane();
433
434
    // Update the preview pane changing tabs.
435
    editorPane.addTabSelectionListener(
436
        ( tabPane, oldTab, newTab ) -> {
437
          if( newTab == null ) {
438
            // Clear the preview pane when closing an editor. When the last
439
            // tab is closed, this ensures that the preview pane is empty.
440
            getPreviewPane().clear();
441
          }
442
          else {
443
            final var tab = (FileEditorTab) newTab;
444
            updateVariableNameInjector( tab );
445
            process( tab );
446
          }
447
        }
448
    );
449
  }
450
451
  /**
452
   * Reloads the preferences from the previous session.
453
   */
454
  private void initPreferences() {
455
    initDefinitionPane();
456
    getFileEditorPane().initPreferences();
457
    getUserPreferences().addSaveEventHandler( mRPreferencesListener );
458
  }
459
460
  private void initVariableNameInjector() {
461
    updateVariableNameInjector( getActiveFileEditorTab() );
462
  }
463
464
  /**
465
   * Calls the listener when the given node is shown for the first time. The
466
   * visible property is not the same as the initial showing event; visibility
467
   * can be triggered numerous times (such as going off screen).
468
   * <p>
469
   * This is called, for example, before the drag handler can be attached,
470
   * because the scrollbar for the text editor pane must be visible.
471
   * </p>
472
   *
473
   * @param node     The node to watch for showing.
474
   * @param consumer The consumer to invoke when the event fires.
475
   */
476
  private void addShowListener(
477
      final Node node, final Consumer<Void> consumer ) {
478
    final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) ->
479
        runLater( () -> {
480
          if( newShow ) {
481
            try {
482
              consumer.accept( null );
483
            } catch( final Exception ex ) {
484
              error( ex );
485
            }
486
          }
487
        } );
488
489
    Val.flatMap( node.sceneProperty(), Scene::windowProperty )
490
       .flatMap( Window::showingProperty )
491
       .addListener( listener );
492
  }
493
494
  private void scrollToParagraph( final int id ) {
495
    scrollToParagraph( id, false );
496
  }
497
498
  /**
499
   * @param id    The paragraph to scroll to, will be approximated if it doesn't
500
   *              exist.
501
   * @param force {@code true} means to force scrolling immediately, which
502
   *              should only be attempted when it is known that the document
503
   *              has been fully rendered. Otherwise the internal map of ID
504
   *              attributes will be incomplete and scrolling will flounder.
505
   */
506
  private void scrollToParagraph( final int id, final boolean force ) {
507
    synchronized( mMutex ) {
508
      final var previewPane = getPreviewPane();
509
      final var scrollPane = previewPane.getScrollPane();
510
      final int approxId = getActiveEditorPane().approximateParagraphId( id );
511
512
      if( force ) {
513
        previewPane.scrollTo( approxId );
514
      }
515
      else {
516
        previewPane.tryScrollTo( approxId );
517
      }
518
519
      scrollPane.repaint();
520
    }
521
  }
522
523
  private void updateVariableNameInjector( final FileEditorTab tab ) {
524
    getVariableNameInjector().addListener( tab );
525
  }
526
527
  /**
528
   * Called whenever the preview pane becomes out of sync with the file editor
529
   * tab. This can be called when the text changes, the caret paragraph
530
   * changes, or the file tab changes.
531
   *
532
   * @param tab The file editor tab that has been changed in some fashion.
533
   */
534
  private void process( final FileEditorTab tab ) {
535
    if( tab != null ) {
536
      getPreviewPane().setPath( tab.getPath() );
537
538
      final Processor<String> processor = getProcessors().computeIfAbsent(
539
          tab, p -> createProcessors( tab )
540
      );
541
542
      try {
543
        processChain( processor, tab.getEditorText() );
544
      } catch( final Exception ex ) {
545
        error( ex );
546
      }
547
    }
548
  }
549
550
  /**
551
   * Executes the processing chain, operating on the given string.
552
   *
553
   * @param handler The first processor in the chain to call.
554
   * @param text    The initial value of the text to process.
555
   * @return The final value of the text that was processed by the chain.
556
   */
557
  private String processChain( Processor<String> handler, String text ) {
558
    while( handler != null && text != null ) {
559
      text = handler.process( text );
560
      handler = handler.next();
561
    }
562
563
    return text;
564
  }
565
566
  private void renderActiveTab() {
567
    process( getActiveFileEditorTab() );
568
  }
569
570
  /**
571
   * Called when a definition source is opened.
572
   *
573
   * @param path Path to the definition source that was opened.
574
   */
575
  private void openDefinitions( final Path path ) {
576
    try {
577
      final var ds = createDefinitionSource( path );
578
      setDefinitionSource( ds );
579
580
      final var prefs = getUserPreferences();
581
      prefs.definitionPathProperty().setValue( path.toFile() );
582
      prefs.save();
583
584
      final var tooltipPath = new Tooltip( path.toString() );
585
      tooltipPath.setShowDelay( Duration.millis( 200 ) );
586
587
      final var pane = getDefinitionPane();
588
      pane.update( ds );
589
      pane.addTreeChangeHandler( mTreeHandler );
590
      pane.addKeyEventHandler( mDefinitionKeyHandler );
591
      pane.filenameProperty().setValue( path.getFileName().toString() );
592
      pane.setTooltip( tooltipPath );
593
594
      interpolateResolvedMap();
595
    } catch( final Exception ex ) {
596
      error( ex );
597
    }
598
  }
599
600
  private void exportDefinitions( final Path path ) {
601
    try {
602
      final DefinitionPane pane = getDefinitionPane();
603
      final TreeItem<String> root = pane.getTreeView().getRoot();
604
      final TreeItem<String> problemChild = pane.isTreeWellFormed();
605
606
      if( problemChild == null ) {
607
        getDefinitionSource().getTreeAdapter().export( root, path );
608
        getNotifier().clear();
609
      }
610
      else {
611
        final String msg = get(
612
            "yaml.error.tree.form", problemChild.getValue() );
613
        error( msg );
614
      }
615
    } catch( final Exception ex ) {
616
      error( ex );
617
    }
618
  }
619
620
  private void interpolateResolvedMap() {
621
    final Map<String, String> treeMap = getDefinitionPane().toMap();
622
    final Map<String, String> map = new HashMap<>( treeMap );
623
    MapInterpolator.interpolate( map );
624
625
    getResolvedMap().clear();
626
    getResolvedMap().putAll( map );
627
  }
628
629
  private void initDefinitionPane() {
630
    openDefinitions( getDefinitionPath() );
631
  }
632
633
  /**
634
   * Called when an exception occurs that warrants the user's attention.
635
   *
636
   * @param ex The exception with a message that the user should know about.
637
   */
638
  private void error( final Exception ex ) {
639
    getNotifier().notify( ex );
640
  }
641
642
  private void error( final String msg ) {
643
    getNotifier().notify( msg );
644
  }
645
646
  //---- File actions -------------------------------------------------------
647
648
  /**
649
   * Called when an {@link Observable} instance has changed. This is called
650
   * by both the {@link Snitch} service and the notify service. The @link
651
   * Snitch} service can be called for different file types, including
652
   * {@link DefinitionSource} instances.
653
   *
654
   * @param observable The observed instance.
655
   * @param value      The noteworthy item.
656
   */
657
  @Override
658
  public void update( final Observable observable, final Object value ) {
659
    if( value != null ) {
660
      if( observable instanceof Snitch && value instanceof Path ) {
661
        updateSelectedTab();
662
      }
663
      else if( observable instanceof Notifier && value instanceof String ) {
664
        updateStatusBar( (String) value );
665
      }
666
    }
667
  }
668
669
  /**
670
   * Updates the status bar to show the given message.
671
   *
672
   * @param s The message to show in the status bar.
673
   */
674
  private void updateStatusBar( final String s ) {
675
    runLater(
676
        () -> {
677
          final int index = s.indexOf( '\n' );
678
          final String message = s.substring(
679
              0, index > 0 ? index : s.length() );
680
681
          getStatusBar().setText( message );
682
        }
683
    );
684
  }
685
686
  /**
687
   * Called when a file has been modified.
688
   */
689
  private void updateSelectedTab() {
690
    rerender();
691
  }
692
693
  /**
694
   * After resetting the processors, they will refresh anew to be up-to-date
695
   * with the files (text and definition) currently loaded into the editor.
696
   */
697
  private void resetProcessors() {
698
    getProcessors().clear();
699
  }
700
701
  //---- File actions -------------------------------------------------------
702
703
  private void fileNew() {
704
    getFileEditorPane().newEditor();
705
  }
706
707
  private void fileOpen() {
708
    getFileEditorPane().openFileDialog();
709
  }
710
711
  private void fileClose() {
712
    getFileEditorPane().closeEditor( getActiveFileEditorTab(), true );
713
  }
714
715
  /**
716
   * TODO: Upon closing, first remove the tab change listeners. (There's no
717
   * need to re-render each tab when all are being closed.)
718
   */
719
  private void fileCloseAll() {
720
    getFileEditorPane().closeAllEditors();
721
  }
722
723
  private void fileSave() {
724
    getFileEditorPane().saveEditor( getActiveFileEditorTab() );
725
  }
726
727
  private void fileSaveAs() {
728
    final FileEditorTab editor = getActiveFileEditorTab();
729
    getFileEditorPane().saveEditorAs( editor );
730
    getProcessors().remove( editor );
731
732
    try {
733
      process( editor );
734
    } catch( final Exception ex ) {
735
      error( ex );
736
    }
737
  }
738
739
  private void fileSaveAll() {
740
    getFileEditorPane().saveAllEditors();
741
  }
742
743
  private void fileExit() {
744
    final Window window = getWindow();
745
    fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
746
  }
747
748
  //---- Edit actions -------------------------------------------------------
749
750
  /**
751
   * Transform the Markdown into HTML then copy that HTML into the copy
752
   * buffer.
753
   */
754
  private void copyHtml() {
755
    final var markdown = getActiveEditorPane().getText();
756
    final var processors = createProcessorFactory().createProcessors(
757
        getActiveFileEditorTab()
758
    );
759
760
    final var chain = processors.remove( HtmlPreviewProcessor.class );
761
762
    final String html = processChain( chain, markdown );
763
764
    final Clipboard clipboard = Clipboard.getSystemClipboard();
765
    final ClipboardContent content = new ClipboardContent();
766
    content.putString( html );
767
    clipboard.setContent( content );
768
  }
769
770
  /**
771
   * Used to find text in the active file editor window.
772
   */
773
  private void editFind() {
774
    final TextField input = getFindTextField();
775
    getStatusBar().setGraphic( input );
776
    input.requestFocus();
777
  }
778
779
  public void editFindNext() {
780
    getActiveFileEditorTab().searchNext( getFindTextField().getText() );
781
  }
782
783
  public void editPreferences() {
784
    getUserPreferences().show();
785
  }
786
787
  //---- Insert actions -----------------------------------------------------
788
789
  /**
790
   * Delegates to the active editor to handle wrapping the current text
791
   * selection with leading and trailing strings.
792
   *
793
   * @param leading  The string to put before the selection.
794
   * @param trailing The string to put after the selection.
795
   */
796
  private void insertMarkdown(
797
      final String leading, final String trailing ) {
798
    getActiveEditorPane().surroundSelection( leading, trailing );
799
  }
800
801
  private void insertMarkdown(
802
      final String leading, final String trailing, final String hint ) {
803
    getActiveEditorPane().surroundSelection( leading, trailing, hint );
804
  }
805
806
  //---- View actions -------------------------------------------------------
807
808
  private void viewRefresh() {
809
    rerender();
810
  }
811
812
  //---- Help actions -------------------------------------------------------
813
814
  private void helpAbout() {
815
    final Alert alert = new Alert( AlertType.INFORMATION );
816
    alert.setTitle( get( "Dialog.about.title" ) );
817
    alert.setHeaderText( get( "Dialog.about.header" ) );
818
    alert.setContentText( get( "Dialog.about.content" ) );
819
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
820
    alert.initOwner( getWindow() );
821
822
    alert.showAndWait();
823
  }
824
825
  //---- Member creators ----------------------------------------------------
826
827
  private SpellChecker createSpellChecker() {
828
    try {
829
      final Collection<String> lexicon = readLexicon( "en.txt" );
830
      return SymSpellSpeller.forLexicon( lexicon );
831
    } catch( final Exception ex ) {
832
      error( ex );
833
      return new PermissiveSpeller();
834
    }
835
  }
836
837
  /**
838
   * Factory to create processors that are suited to different file types.
839
   *
840
   * @param tab The tab that is subjected to processing.
841
   * @return A processor suited to the file type specified by the tab's path.
842
   */
843
  private Processor<String> createProcessors( final FileEditorTab tab ) {
844
    return createProcessorFactory().createProcessors( tab );
845
  }
846
847
  private ProcessorFactory createProcessorFactory() {
848
    return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
849
  }
850
851
  private HTMLPreviewPane createHTMLPreviewPane() {
852
    return new HTMLPreviewPane();
853
  }
854
855
  private DefinitionSource createDefaultDefinitionSource() {
856
    return new YamlDefinitionSource( getDefinitionPath() );
857
  }
858
859
  private DefinitionSource createDefinitionSource( final Path path ) {
860
    try {
861
      return createDefinitionFactory().createDefinitionSource( path );
862
    } catch( final Exception ex ) {
863
      error( ex );
864
      return createDefaultDefinitionSource();
865
    }
866
  }
867
868
  private TextField createFindTextField() {
869
    return new TextField();
870
  }
871
872
  private DefinitionFactory createDefinitionFactory() {
873
    return new DefinitionFactory();
874
  }
875
876
  private StatusBar createStatusBar() {
877
    return new StatusBar();
878
  }
879
880
  private Scene createScene() {
881
    final SplitPane splitPane = new SplitPane(
882
        getDefinitionPane(),
883
        getFileEditorPane(),
884
        getPreviewPane() );
885
886
    splitPane.setDividerPositions(
887
        getFloat( K_PANE_SPLIT_DEFINITION, .22f ),
888
        getFloat( K_PANE_SPLIT_EDITOR, .60f ),
889
        getFloat( K_PANE_SPLIT_PREVIEW, .18f ) );
890
891
    getDefinitionPane().prefHeightProperty()
892
                       .bind( splitPane.heightProperty() );
893
894
    final BorderPane borderPane = new BorderPane();
895
    borderPane.setPrefSize( 1280, 800 );
896
    borderPane.setTop( createMenuBar() );
897
    borderPane.setBottom( getStatusBar() );
898
    borderPane.setCenter( splitPane );
899
900
    final VBox statusBar = new VBox();
901
    statusBar.setAlignment( Pos.BASELINE_CENTER );
902
    statusBar.getChildren().add( getLineNumberText() );
903
    getStatusBar().getRightItems().add( statusBar );
904
905
    // Force preview pane refresh on Windows.
906
    if( SystemUtils.IS_OS_WINDOWS ) {
907
      splitPane.getDividers().get( 1 ).positionProperty().addListener(
908
          ( l, oValue, nValue ) -> runLater(
909
              () -> getPreviewPane().getScrollPane().repaint()
910
          )
911
      );
912
    }
913
914
    return new Scene( borderPane );
915
  }
916
917
  private Text createLineNumberText() {
918
    return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) );
919
  }
920
921
  private Node createMenuBar() {
922
    final BooleanBinding activeFileEditorIsNull =
923
        getFileEditorPane().activeFileEditorProperty().isNull();
924
925
    // File actions
926
    final Action fileNewAction = new ActionBuilder()
927
        .setText( "Main.menu.file.new" )
928
        .setAccelerator( "Shortcut+N" )
929
        .setIcon( FILE_ALT )
930
        .setAction( e -> fileNew() )
931
        .build();
932
    final Action fileOpenAction = new ActionBuilder()
933
        .setText( "Main.menu.file.open" )
934
        .setAccelerator( "Shortcut+O" )
935
        .setIcon( FOLDER_OPEN_ALT )
936
        .setAction( e -> fileOpen() )
937
        .build();
938
    final Action fileCloseAction = new ActionBuilder()
939
        .setText( "Main.menu.file.close" )
940
        .setAccelerator( "Shortcut+W" )
941
        .setAction( e -> fileClose() )
942
        .setDisable( activeFileEditorIsNull )
943
        .build();
944
    final Action fileCloseAllAction = new ActionBuilder()
945
        .setText( "Main.menu.file.close_all" )
946
        .setAction( e -> fileCloseAll() )
947
        .setDisable( activeFileEditorIsNull )
948
        .build();
949
    final Action fileSaveAction = new ActionBuilder()
950
        .setText( "Main.menu.file.save" )
951
        .setAccelerator( "Shortcut+S" )
952
        .setIcon( FLOPPY_ALT )
953
        .setAction( e -> fileSave() )
954
        .setDisable( createActiveBooleanProperty(
955
            FileEditorTab::modifiedProperty ).not() )
956
        .build();
957
    final Action fileSaveAsAction = new ActionBuilder()
958
        .setText( "Main.menu.file.save_as" )
959
        .setAction( e -> fileSaveAs() )
960
        .setDisable( activeFileEditorIsNull )
961
        .build();
962
    final Action fileSaveAllAction = new ActionBuilder()
963
        .setText( "Main.menu.file.save_all" )
964
        .setAccelerator( "Shortcut+Shift+S" )
965
        .setAction( e -> fileSaveAll() )
966
        .setDisable( Bindings.not(
967
            getFileEditorPane().anyFileEditorModifiedProperty() ) )
968
        .build();
969
    final Action fileExitAction = new ActionBuilder()
970
        .setText( "Main.menu.file.exit" )
971
        .setAction( e -> fileExit() )
972
        .build();
973
974
    // Edit actions
975
    final Action editCopyHtmlAction = new ActionBuilder()
976
        .setText( Messages.get( "Main.menu.edit.copy.html" ) )
977
        .setIcon( HTML5 )
978
        .setAction( e -> copyHtml() )
979
        .setDisable( activeFileEditorIsNull )
980
        .build();
981
982
    final Action editUndoAction = new ActionBuilder()
983
        .setText( "Main.menu.edit.undo" )
984
        .setAccelerator( "Shortcut+Z" )
985
        .setIcon( UNDO )
986
        .setAction( e -> getActiveEditorPane().undo() )
987
        .setDisable( createActiveBooleanProperty(
988
            FileEditorTab::canUndoProperty ).not() )
989
        .build();
990
    final Action editRedoAction = new ActionBuilder()
991
        .setText( "Main.menu.edit.redo" )
992
        .setAccelerator( "Shortcut+Y" )
993
        .setIcon( REPEAT )
994
        .setAction( e -> getActiveEditorPane().redo() )
995
        .setDisable( createActiveBooleanProperty(
996
            FileEditorTab::canRedoProperty ).not() )
997
        .build();
998
999
    final Action editCutAction = new ActionBuilder()
1000
        .setText( Messages.get( "Main.menu.edit.cut" ) )
1001
        .setAccelerator( "Shortcut+X" )
1002
        .setIcon( CUT )
1003
        .setAction( e -> getActiveEditorPane().cut() )
1004
        .setDisable( activeFileEditorIsNull )
1005
        .build();
1006
    final Action editCopyAction = new ActionBuilder()
1007
        .setText( Messages.get( "Main.menu.edit.copy" ) )
1008
        .setAccelerator( "Shortcut+C" )
1009
        .setIcon( COPY )
1010
        .setAction( e -> getActiveEditorPane().copy() )
1011
        .setDisable( activeFileEditorIsNull )
1012
        .build();
1013
    final Action editPasteAction = new ActionBuilder()
1014
        .setText( Messages.get( "Main.menu.edit.paste" ) )
1015
        .setAccelerator( "Shortcut+V" )
1016
        .setIcon( PASTE )
1017
        .setAction( e -> getActiveEditorPane().paste() )
1018
        .setDisable( activeFileEditorIsNull )
1019
        .build();
1020
    final Action editSelectAllAction = new ActionBuilder()
1021
        .setText( Messages.get( "Main.menu.edit.selectAll" ) )
1022
        .setAccelerator( "Shortcut+A" )
1023
        .setAction( e -> getActiveEditorPane().selectAll() )
1024
        .setDisable( activeFileEditorIsNull )
1025
        .build();
1026
1027
    final Action editFindAction = new ActionBuilder()
1028
        .setText( "Main.menu.edit.find" )
1029
        .setAccelerator( "Ctrl+F" )
1030
        .setIcon( SEARCH )
1031
        .setAction( e -> editFind() )
1032
        .setDisable( activeFileEditorIsNull )
1033
        .build();
1034
    final Action editFindNextAction = new ActionBuilder()
1035
        .setText( "Main.menu.edit.find.next" )
1036
        .setAccelerator( "F3" )
1037
        .setIcon( null )
1038
        .setAction( e -> editFindNext() )
1039
        .setDisable( activeFileEditorIsNull )
1040
        .build();
1041
    final Action editPreferencesAction = new ActionBuilder()
1042
        .setText( "Main.menu.edit.preferences" )
1043
        .setAccelerator( "Ctrl+Alt+S" )
1044
        .setAction( e -> editPreferences() )
1045
        .build();
1046
1047
    // Insert actions
1048
    final Action insertBoldAction = new ActionBuilder()
1049
        .setText( "Main.menu.insert.bold" )
1050
        .setAccelerator( "Shortcut+B" )
1051
        .setIcon( BOLD )
1052
        .setAction( e -> insertMarkdown( "**", "**" ) )
1053
        .setDisable( activeFileEditorIsNull )
1054
        .build();
1055
    final Action insertItalicAction = new ActionBuilder()
1056
        .setText( "Main.menu.insert.italic" )
1057
        .setAccelerator( "Shortcut+I" )
1058
        .setIcon( ITALIC )
1059
        .setAction( e -> insertMarkdown( "*", "*" ) )
1060
        .setDisable( activeFileEditorIsNull )
1061
        .build();
1062
    final Action insertSuperscriptAction = new ActionBuilder()
1063
        .setText( "Main.menu.insert.superscript" )
1064
        .setAccelerator( "Shortcut+[" )
1065
        .setIcon( SUPERSCRIPT )
1066
        .setAction( e -> insertMarkdown( "^", "^" ) )
1067
        .setDisable( activeFileEditorIsNull )
1068
        .build();
1069
    final Action insertSubscriptAction = new ActionBuilder()
1070
        .setText( "Main.menu.insert.subscript" )
1071
        .setAccelerator( "Shortcut+]" )
1072
        .setIcon( SUBSCRIPT )
1073
        .setAction( e -> insertMarkdown( "~", "~" ) )
1074
        .setDisable( activeFileEditorIsNull )
1075
        .build();
1076
    final Action insertStrikethroughAction = new ActionBuilder()
1077
        .setText( "Main.menu.insert.strikethrough" )
1078
        .setAccelerator( "Shortcut+T" )
1079
        .setIcon( STRIKETHROUGH )
1080
        .setAction( e -> insertMarkdown( "~~", "~~" ) )
1081
        .setDisable( activeFileEditorIsNull )
1082
        .build();
1083
    final Action insertBlockquoteAction = new ActionBuilder()
1084
        .setText( "Main.menu.insert.blockquote" )
1085
        .setAccelerator( "Ctrl+Q" )
1086
        .setIcon( QUOTE_LEFT )
1087
        .setAction( e -> insertMarkdown( "\n\n> ", "" ) )
1088
        .setDisable( activeFileEditorIsNull )
1089
        .build();
1090
    final Action insertCodeAction = new ActionBuilder()
1091
        .setText( "Main.menu.insert.code" )
1092
        .setAccelerator( "Shortcut+K" )
1093
        .setIcon( CODE )
1094
        .setAction( e -> insertMarkdown( "`", "`" ) )
1095
        .setDisable( activeFileEditorIsNull )
1096
        .build();
1097
    final Action insertFencedCodeBlockAction = new ActionBuilder()
1098
        .setText( "Main.menu.insert.fenced_code_block" )
1099
        .setAccelerator( "Shortcut+Shift+K" )
1100
        .setIcon( FILE_CODE_ALT )
1101
        .setAction( e -> insertMarkdown(
1102
            "\n\n```\n",
1103
            "\n```\n\n",
1104
            get( "Main.menu.insert.fenced_code_block.prompt" ) ) )
1105
        .setDisable( activeFileEditorIsNull )
1106
        .build();
1107
    final Action insertLinkAction = new ActionBuilder()
1108
        .setText( "Main.menu.insert.link" )
1109
        .setAccelerator( "Shortcut+L" )
1110
        .setIcon( LINK )
1111
        .setAction( e -> getActiveEditorPane().insertLink() )
1112
        .setDisable( activeFileEditorIsNull )
1113
        .build();
1114
    final Action insertImageAction = new ActionBuilder()
1115
        .setText( "Main.menu.insert.image" )
1116
        .setAccelerator( "Shortcut+G" )
1117
        .setIcon( PICTURE_ALT )
1118
        .setAction( e -> getActiveEditorPane().insertImage() )
1119
        .setDisable( activeFileEditorIsNull )
1120
        .build();
1121
1122
    // Number of heading actions (H1 ... H3)
1123
    final int HEADINGS = 3;
1124
    final Action[] headings = new Action[ HEADINGS ];
1125
1126
    for( int i = 1; i <= HEADINGS; i++ ) {
1127
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
1128
      final String markup = String.format( "%n%n%s ", hashes );
1129
      final String text = "Main.menu.insert.heading." + i;
1130
      final String accelerator = "Shortcut+" + i;
1131
      final String prompt = text + ".prompt";
1132
1133
      headings[ i - 1 ] = new ActionBuilder()
1134
          .setText( text )
1135
          .setAccelerator( accelerator )
1136
          .setIcon( HEADER )
1137
          .setAction( e -> insertMarkdown( markup, "", get( prompt ) ) )
1138
          .setDisable( activeFileEditorIsNull )
1139
          .build();
1140
    }
1141
1142
    final Action insertUnorderedListAction = new ActionBuilder()
1143
        .setText( "Main.menu.insert.unordered_list" )
1144
        .setAccelerator( "Shortcut+U" )
1145
        .setIcon( LIST_UL )
1146
        .setAction( e -> insertMarkdown( "\n\n* ", "" ) )
1147
        .setDisable( activeFileEditorIsNull )
1148
        .build();
1149
    final Action insertOrderedListAction = new ActionBuilder()
1150
        .setText( "Main.menu.insert.ordered_list" )
1151
        .setAccelerator( "Shortcut+Shift+O" )
1152
        .setIcon( LIST_OL )
1153
        .setAction( e -> insertMarkdown(
1154
            "\n\n1. ", "" ) )
1155
        .setDisable( activeFileEditorIsNull )
1156
        .build();
1157
    final Action insertHorizontalRuleAction = new ActionBuilder()
1158
        .setText( "Main.menu.insert.horizontal_rule" )
1159
        .setAccelerator( "Shortcut+H" )
1160
        .setAction( e -> insertMarkdown(
1161
            "\n\n---\n\n", "" ) )
1162
        .setDisable( activeFileEditorIsNull )
1163
        .build();
1164
1165
    // View actions
1166
    final Action viewRefreshAction = new ActionBuilder()
1167
        .setText( "Main.menu.view.refresh" )
1168
        .setAccelerator( "F5" )
1169
        .setAction( e -> viewRefresh() )
1170
        .build();
1171
1172
    // Help actions
1173
    final Action helpAboutAction = new ActionBuilder()
1174
        .setText( "Main.menu.help.about" )
1175
        .setAction( e -> helpAbout() )
1176
        .build();
1177
1178
    //---- MenuBar ----
1179
    final Menu fileMenu = ActionUtils.createMenu(
1180
        get( "Main.menu.file" ),
1181
        fileNewAction,
1182
        fileOpenAction,
1183
        null,
1184
        fileCloseAction,
1185
        fileCloseAllAction,
1186
        null,
1187
        fileSaveAction,
1188
        fileSaveAsAction,
1189
        fileSaveAllAction,
1190
        null,
1191
        fileExitAction );
1192
1193
    final Menu editMenu = ActionUtils.createMenu(
1194
        get( "Main.menu.edit" ),
1195
        editCopyHtmlAction,
1196
        null,
1197
        editUndoAction,
1198
        editRedoAction,
1199
        null,
1200
        editCutAction,
1201
        editCopyAction,
1202
        editPasteAction,
1203
        editSelectAllAction,
1204
        null,
1205
        editFindAction,
1206
        editFindNextAction,
1207
        null,
1208
        editPreferencesAction );
1209
1210
    final Menu insertMenu = ActionUtils.createMenu(
1211
        get( "Main.menu.insert" ),
1212
        insertBoldAction,
1213
        insertItalicAction,
1214
        insertSuperscriptAction,
1215
        insertSubscriptAction,
1216
        insertStrikethroughAction,
1217
        insertBlockquoteAction,
1218
        insertCodeAction,
1219
        insertFencedCodeBlockAction,
1220
        null,
1221
        insertLinkAction,
1222
        insertImageAction,
1223
        null,
1224
        headings[ 0 ],
1225
        headings[ 1 ],
1226
        headings[ 2 ],
1227
        null,
1228
        insertUnorderedListAction,
1229
        insertOrderedListAction,
1230
        insertHorizontalRuleAction
1231
    );
1232
1233
    final Menu viewMenu = ActionUtils.createMenu(
1234
        get( "Main.menu.view" ),
1235
        viewRefreshAction );
1236
1237
    final Menu helpMenu = ActionUtils.createMenu(
1238
        get( "Main.menu.help" ),
1239
        helpAboutAction );
1240
1241
    final MenuBar menuBar = new MenuBar(
1242
        fileMenu,
1243
        editMenu,
1244
        insertMenu,
1245
        viewMenu,
1246
        helpMenu );
1247
1248
    //---- ToolBar ----
1249
    final ToolBar toolBar = ActionUtils.createToolBar(
1250
        fileNewAction,
1251
        fileOpenAction,
1252
        fileSaveAction,
1253
        null,
1254
        editUndoAction,
1255
        editRedoAction,
1256
        editCutAction,
1257
        editCopyAction,
1258
        editPasteAction,
1259
        null,
1260
        insertBoldAction,
1261
        insertItalicAction,
1262
        insertSuperscriptAction,
1263
        insertSubscriptAction,
1264
        insertBlockquoteAction,
1265
        insertCodeAction,
1266
        insertFencedCodeBlockAction,
1267
        null,
1268
        insertLinkAction,
1269
        insertImageAction,
1270
        null,
1271
        headings[ 0 ],
1272
        null,
1273
        insertUnorderedListAction,
1274
        insertOrderedListAction );
1275
1276
    return new VBox( menuBar, toolBar );
1277
  }
1278
1279
  /**
1280
   * Creates a boolean property that is bound to another boolean value of the
1281
   * active editor.
1282
   */
1283
  private BooleanProperty createActiveBooleanProperty(
1284
      final Function<FileEditorTab, ObservableBooleanValue> func ) {
1285
1286
    final BooleanProperty b = new SimpleBooleanProperty();
1287
    final FileEditorTab tab = getActiveFileEditorTab();
1288
1289
    if( tab != null ) {
1290
      b.bind( func.apply( tab ) );
1291
    }
1292
1293
    getFileEditorPane().activeFileEditorProperty().addListener(
1294
        ( observable, oldFileEditor, newFileEditor ) -> {
1295
          b.unbind();
1296
1297
          if( newFileEditor == null ) {
1298
            b.set( false );
1299
          }
1300
          else {
1301
            b.bind( func.apply( newFileEditor ) );
1302
          }
1303
        }
1304
    );
1305
1306
    return b;
1307
  }
1308
1309
  //---- Convenience accessors ----------------------------------------------
1310
1311
  private Preferences getPreferences() {
1312
    return sOptions.getState();
1313
  }
1314
1315
  private int getCurrentParagraphIndex() {
1316
    return getActiveEditorPane().getCurrentParagraphIndex();
1317
  }
1318
1319
  private float getFloat( final String key, final float defaultValue ) {
1320
    return getPreferences().getFloat( key, defaultValue );
1321
  }
1322
1323
  public Window getWindow() {
1324
    return getScene().getWindow();
1325
  }
1326
1327
  private MarkdownEditorPane getActiveEditorPane() {
1328
    return getActiveFileEditorTab().getEditorPane();
1329
  }
1330
1331
  private FileEditorTab getActiveFileEditorTab() {
1332
    return getFileEditorPane().getActiveFileEditor();
1333
  }
1334
1335
  //---- Member accessors ---------------------------------------------------
1336
1337
  protected Scene getScene() {
1338
    return mScene;
1339
  }
1340
1341
  private SpellChecker getSpellChecker() {
1342
    return mSpellChecker;
1343
  }
1344
1345
  private Map<FileEditorTab, Processor<String>> getProcessors() {
1346
    return mProcessors;
1347
  }
1348
1349
  private FileEditorTabPane getFileEditorPane() {
1350
    return mFileEditorPane;
1351
  }
1352
1353
  private HTMLPreviewPane getPreviewPane() {
1354
    return mPreviewPane;
1355
  }
1356
1357
  private void setDefinitionSource(
1358
      final DefinitionSource definitionSource ) {
1359
    assert definitionSource != null;
1360
    mDefinitionSource = definitionSource;
1361
  }
1362
1363
  private DefinitionSource getDefinitionSource() {
1364
    return mDefinitionSource;
1365
  }
1366
1367
  private DefinitionPane getDefinitionPane() {
1368
    return mDefinitionPane;
1369
  }
1370
1371
  private Text getLineNumberText() {
1372
    return mLineNumberText;
1373
  }
1374
1375
  private StatusBar getStatusBar() {
1376
    return mStatusBar;
1377
  }
1378
1379
  private TextField getFindTextField() {
1380
    return mFindTextField;
1381
  }
1382
1383
  private VariableNameInjector getVariableNameInjector() {
1384
    return mVariableNameInjector;
1385
  }
1386
1387
  /**
1388
   * Returns the variable map of interpolated definitions.
1389
   *
1390
   * @return A map to help dereference variables.
1391
   */
1392
  private Map<String, String> getResolvedMap() {
1393
    return mResolvedMap;
1394
  }
1395
1396
  private Notifier getNotifier() {
1397
    return sNotifier;
1398
  }
1399
1400
  //---- Persistence accessors ----------------------------------------------
1401
1402
  private UserPreferences getUserPreferences() {
1403
    return sOptions.getUserPreferences();
1404
  }
1405
1406
  private Path getDefinitionPath() {
1407
    return getUserPreferences().getDefinitionPath();
1408
  }
1409
1410
  //---- Spelling -----------------------------------------------------------
1411
1412
  /**
1413
   * Delegates to {@link #spellcheck(StyleClassedTextArea, String, int)}.
1414
   * This is called to spell check the document, rather than a single paragraph.
1415
   *
1416
   * @param text The full document text.
1417
   */
1418
  private void spellcheck(
1419
      final StyleClassedTextArea editor, final String text ) {
1420
    spellcheck( editor, text, -1 );
1421
  }
1422
1423
  /**
1424
   * Spellchecks a subset of the entire document.
1425
   *
1426
   * @param text   Look up words for this text in the lexicon.
1427
   * @param paraId Set to -1 to apply resulting style spans to the entire
1428
   *               text.
1429
   */
1430
  private void spellcheck(
1431
      final StyleClassedTextArea editor, final String text, final int paraId ) {
1432
    final var builder = new StyleSpansBuilder<Collection<String>>();
1433
    final var runningIndex = new AtomicInteger( 0 );
1434
    final var checker = getSpellChecker();
1435
1436
    // The text nodes must be relayed through a contextual "visitor" that
1437
    // can return text in chunks with correlative offsets into the string.
1438
    // This allows Markdown, R Markdown, XML, and R XML documents to return
1439
    // sets of words to check.
1440
1441
    final var node = mParser.parse( text );
1442
    final var visitor = new TextVisitor( ( visited, bIndex, eIndex ) -> {
1443
      // Treat hyphenated compound words as individual words.
1444
      final var check = visited.replace( '-', ' ' );
1445
1446
      checker.proofread( check, ( misspelled, prevIndex, currIndex ) -> {
1447
        prevIndex += bIndex;
1448
        currIndex += bIndex;
1449
1450
        // Clear styling between lexiconically absent words.
1451
        builder.add( emptyList(), prevIndex - runningIndex.get() );
1452
        builder.add( singleton( "spelling" ), currIndex - prevIndex );
1453
        runningIndex.set( currIndex );
1454
      } );
1455
    } );
1456
1457
    visitor.visit( node );
1458
1459
    // If the running index was set, at least one word triggered the listener.
1460
    if( runningIndex.get() > 0 ) {
1461
      // Clear styling after the last lexiconically absent word.
1462
      builder.add( emptyList(), text.length() - runningIndex.get() );
1463
1464
      final var spans = builder.create();
1465
1466
      if( paraId >= 0 ) {
1467
        editor.setStyleSpans( paraId, 0, spans );
1468
      }
1469
      else {
1470
        editor.setStyleSpans( 0, spans );
1471
      }
1472
    }
1473
  }
1474
1475
  @SuppressWarnings("SameParameterValue")
1476
  private Collection<String> readLexicon( final String filename )
1477
      throws Exception {
1478
    final var path = Paths.get( LEXICONS_DIRECTORY, filename ).toString();
1479
    final var classLoader = MainWindow.class.getClassLoader();
1480
1481
    try( final var resource = classLoader.getResourceAsStream( path ) ) {
1482
      assert resource != null;
1483
1484
      return new BufferedReader( new InputStreamReader( resource, UTF_8 ) )
1485
          .lines()
1486
          .collect( Collectors.toList() );
1487
    }
1488
  }
1489
1490
  // TODO: Replace using Markdown processor instantiated for Markdown files.
1491
  // FIXME: https://github.com/DaveJarvis/scrivenvar/issues/59
1492
  private final Parser mParser = Parser.builder().build();
1493
1494
  // TODO: Replace with generic interface; provide Markdown/XML implementations.
1495
  // FIXME: https://github.com/DaveJarvis/scrivenvar/issues/59
1496
  private final static class TextVisitor {
36
import com.scrivenvar.editors.DefinitionNameInjector;
37
import com.scrivenvar.editors.EditorPane;
38
import com.scrivenvar.editors.markdown.MarkdownEditorPane;
39
import com.scrivenvar.preferences.UserPreferences;
40
import com.scrivenvar.preview.HTMLPreviewPane;
41
import com.scrivenvar.processors.HtmlPreviewProcessor;
42
import com.scrivenvar.processors.Processor;
43
import com.scrivenvar.processors.ProcessorFactory;
44
import com.scrivenvar.service.Options;
45
import com.scrivenvar.service.Snitch;
46
import com.scrivenvar.spelling.api.SpellCheckListener;
47
import com.scrivenvar.spelling.api.SpellChecker;
48
import com.scrivenvar.spelling.impl.PermissiveSpeller;
49
import com.scrivenvar.spelling.impl.SymSpellSpeller;
50
import com.scrivenvar.util.Action;
51
import com.scrivenvar.util.ActionBuilder;
52
import com.scrivenvar.util.ActionUtils;
53
import com.vladsch.flexmark.parser.Parser;
54
import com.vladsch.flexmark.util.ast.NodeVisitor;
55
import com.vladsch.flexmark.util.ast.VisitHandler;
56
import javafx.beans.binding.Bindings;
57
import javafx.beans.binding.BooleanBinding;
58
import javafx.beans.property.BooleanProperty;
59
import javafx.beans.property.SimpleBooleanProperty;
60
import javafx.beans.value.ChangeListener;
61
import javafx.beans.value.ObservableBooleanValue;
62
import javafx.beans.value.ObservableValue;
63
import javafx.collections.ListChangeListener.Change;
64
import javafx.collections.ObservableList;
65
import javafx.event.Event;
66
import javafx.event.EventHandler;
67
import javafx.geometry.Pos;
68
import javafx.scene.Node;
69
import javafx.scene.Scene;
70
import javafx.scene.control.*;
71
import javafx.scene.control.Alert.AlertType;
72
import javafx.scene.image.Image;
73
import javafx.scene.image.ImageView;
74
import javafx.scene.input.Clipboard;
75
import javafx.scene.input.ClipboardContent;
76
import javafx.scene.input.KeyEvent;
77
import javafx.scene.layout.BorderPane;
78
import javafx.scene.layout.VBox;
79
import javafx.scene.text.Text;
80
import javafx.stage.Window;
81
import javafx.stage.WindowEvent;
82
import javafx.util.Duration;
83
import org.apache.commons.lang3.SystemUtils;
84
import org.controlsfx.control.StatusBar;
85
import org.fxmisc.richtext.StyleClassedTextArea;
86
import org.fxmisc.richtext.model.StyleSpansBuilder;
87
import org.reactfx.value.Val;
88
89
import java.io.BufferedReader;
90
import java.io.InputStreamReader;
91
import java.nio.file.Path;
92
import java.nio.file.Paths;
93
import java.util.*;
94
import java.util.concurrent.atomic.AtomicInteger;
95
import java.util.function.Consumer;
96
import java.util.function.Function;
97
import java.util.prefs.Preferences;
98
import java.util.stream.Collectors;
99
100
import static com.scrivenvar.Constants.*;
101
import static com.scrivenvar.Messages.get;
102
import static com.scrivenvar.StatusBarNotifier.alert;
103
import static com.scrivenvar.util.StageState.*;
104
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
105
import static java.nio.charset.StandardCharsets.UTF_8;
106
import static java.util.Collections.emptyList;
107
import static java.util.Collections.singleton;
108
import static javafx.application.Platform.runLater;
109
import static javafx.event.Event.fireEvent;
110
import static javafx.scene.input.KeyCode.ENTER;
111
import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
112
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward;
113
114
/**
115
 * Main window containing a tab pane in the center for file editors.
116
 */
117
public class MainWindow implements Observer {
118
  /**
119
   * The {@code OPTIONS} variable must be declared before all other variables
120
   * to prevent subsequent initializations from failing due to missing user
121
   * preferences.
122
   */
123
  private static final Options sOptions = Services.load( Options.class );
124
  private static final Snitch SNITCH = Services.load( Snitch.class );
125
126
  private final Scene mScene;
127
  private final StatusBar mStatusBar;
128
  private final Text mLineNumberText;
129
  private final TextField mFindTextField;
130
  private final SpellChecker mSpellChecker;
131
132
  private final Object mMutex = new Object();
133
134
  /**
135
   * Prevents re-instantiation of processing classes.
136
   */
137
  private final Map<FileEditorTab, Processor<String>> mProcessors =
138
      new HashMap<>();
139
140
  private final Map<String, String> mResolvedMap =
141
      new HashMap<>( DEFAULT_MAP_SIZE );
142
143
  private final EventHandler<PreferencesFxEvent> mRPreferencesListener =
144
      event -> rerender();
145
146
  /**
147
   * Called when the definition data is changed.
148
   */
149
  private final EventHandler<TreeItem.TreeModificationEvent<Event>>
150
      mTreeHandler = event -> {
151
    exportDefinitions( getDefinitionPath() );
152
    interpolateResolvedMap();
153
    rerender();
154
  };
155
156
  /**
157
   * Called to inject the selected item when the user presses ENTER in the
158
   * definition pane.
159
   */
160
  private final EventHandler<? super KeyEvent> mDefinitionKeyHandler =
161
      event -> {
162
        if( event.getCode() == ENTER ) {
163
          getVariableNameInjector().injectSelectedItem();
164
        }
165
      };
166
167
  private final ChangeListener<Integer> mCaretPositionListener =
168
      ( observable, oldPosition, newPosition ) -> {
169
        final FileEditorTab tab = getActiveFileEditorTab();
170
        final EditorPane pane = tab.getEditorPane();
171
        final StyleClassedTextArea editor = pane.getEditor();
172
173
        getLineNumberText().setText(
174
            get( STATUS_BAR_LINE,
175
                 editor.getCurrentParagraph() + 1,
176
                 editor.getParagraphs().size(),
177
                 editor.getCaretPosition()
178
            )
179
        );
180
      };
181
182
  private final ChangeListener<Integer> mCaretParagraphListener =
183
      ( observable, oldIndex, newIndex ) ->
184
          scrollToParagraph( newIndex, true );
185
186
  private DefinitionSource mDefinitionSource = createDefaultDefinitionSource();
187
  private final DefinitionPane mDefinitionPane = new DefinitionPane();
188
  private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane();
189
  private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane(
190
      mCaretPositionListener,
191
      mCaretParagraphListener );
192
193
  /**
194
   * Listens on the definition pane for double-click events.
195
   */
196
  private final DefinitionNameInjector mVariableNameInjector
197
      = new DefinitionNameInjector( mDefinitionPane );
198
199
  public MainWindow() {
200
    mStatusBar = createStatusBar();
201
    mLineNumberText = createLineNumberText();
202
    mFindTextField = createFindTextField();
203
    mScene = createScene();
204
    mSpellChecker = createSpellChecker();
205
206
    // Add the close request listener before the window is shown.
207
    initLayout();
208
    StatusBarNotifier.setStatusBar( mStatusBar );
209
  }
210
211
  /**
212
   * Called after the stage is shown.
213
   */
214
  public void init() {
215
    initFindInput();
216
    initSnitch();
217
    initDefinitionListener();
218
    initTabAddedListener();
219
    initTabChangedListener();
220
    initPreferences();
221
    initVariableNameInjector();
222
  }
223
224
  private void initLayout() {
225
    final var scene = getScene();
226
227
    scene.getStylesheets().add( STYLESHEET_SCENE );
228
    scene.windowProperty().addListener(
229
        ( unused, oldWindow, newWindow ) ->
230
            newWindow.setOnCloseRequest(
231
                e -> {
232
                  if( !getFileEditorPane().closeAllEditors() ) {
233
                    e.consume();
234
                  }
235
                }
236
            )
237
    );
238
  }
239
240
  /**
241
   * Initialize the find input text field to listen on F3, ENTER, and
242
   * ESCAPE key presses.
243
   */
244
  private void initFindInput() {
245
    final TextField input = getFindTextField();
246
247
    input.setOnKeyPressed( ( KeyEvent event ) -> {
248
      switch( event.getCode() ) {
249
        case F3:
250
        case ENTER:
251
          editFindNext();
252
          break;
253
        case F:
254
          if( !event.isControlDown() ) {
255
            break;
256
          }
257
        case ESCAPE:
258
          getStatusBar().setGraphic( null );
259
          getActiveFileEditorTab().getEditorPane().requestFocus();
260
          break;
261
      }
262
    } );
263
264
    // Remove when the input field loses focus.
265
    input.focusedProperty().addListener(
266
        ( focused, oldFocus, newFocus ) -> {
267
          if( !newFocus ) {
268
            getStatusBar().setGraphic( null );
269
          }
270
        }
271
    );
272
  }
273
274
  /**
275
   * Watch for changes to external files. In particular, this awaits
276
   * modifications to any XSL files associated with XML files being edited.
277
   * When
278
   * an XSL file is modified (external to the application), the snitch's ears
279
   * perk up and the file is reloaded. This keeps the XSL transformation up to
280
   * date with what's on the file system.
281
   */
282
  private void initSnitch() {
283
    SNITCH.addObserver( this );
284
  }
285
286
  /**
287
   * Listen for {@link FileEditorTabPane} to receive open definition file
288
   * event.
289
   */
290
  private void initDefinitionListener() {
291
    getFileEditorPane().onOpenDefinitionFileProperty().addListener(
292
        ( final ObservableValue<? extends Path> file,
293
          final Path oldPath, final Path newPath ) -> {
294
          openDefinitions( newPath );
295
          rerender();
296
        }
297
    );
298
  }
299
300
  /**
301
   * Re-instantiates all processors then re-renders the active tab. This
302
   * will refresh the resolved map, force R to re-initialize, and brute-force
303
   * XSLT file reloads.
304
   */
305
  private void rerender() {
306
    runLater(
307
        () -> {
308
          resetProcessors();
309
          renderActiveTab();
310
        }
311
    );
312
  }
313
314
  /**
315
   * When tabs are added, hook the various change listeners onto the new
316
   * tab sothat the preview pane refreshes as necessary.
317
   */
318
  private void initTabAddedListener() {
319
    final FileEditorTabPane editorPane = getFileEditorPane();
320
321
    // Make sure the text processor kicks off when new files are opened.
322
    final ObservableList<Tab> tabs = editorPane.getTabs();
323
324
    // Update the preview pane on tab changes.
325
    tabs.addListener(
326
        ( final Change<? extends Tab> change ) -> {
327
          while( change.next() ) {
328
            if( change.wasAdded() ) {
329
              // Multiple tabs can be added simultaneously.
330
              for( final Tab newTab : change.getAddedSubList() ) {
331
                final FileEditorTab tab = (FileEditorTab) newTab;
332
333
                initTextChangeListener( tab );
334
                initScrollEventListener( tab );
335
                initSpellCheckListener( tab );
336
//              initSyntaxListener( tab );
337
              }
338
            }
339
          }
340
        }
341
    );
342
  }
343
344
  private void initTextChangeListener( final FileEditorTab tab ) {
345
    tab.addTextChangeListener(
346
        ( __, ov, nv ) -> {
347
          process( tab );
348
          scrollToParagraph( getCurrentParagraphIndex() );
349
        }
350
    );
351
  }
352
353
  private void initScrollEventListener( final FileEditorTab tab ) {
354
    final var scrollPane = tab.getScrollPane();
355
    final var scrollBar = getPreviewPane().getVerticalScrollBar();
356
357
    addShowListener( scrollPane, ( __ ) -> {
358
      final var handler = new ScrollEventHandler( scrollPane, scrollBar );
359
      handler.enabledProperty().bind( tab.selectedProperty() );
360
    } );
361
  }
362
363
  /**
364
   * Listen for changes to the any particular paragraph and perform a quick
365
   * spell check upon it. The style classes in the editor will be changed to
366
   * mark any spelling mistakes in the paragraph. The user may then interact
367
   * with any misspelled word (i.e., any piece of text that is marked) to
368
   * revise the spelling.
369
   *
370
   * @param tab The tab to spellcheck.
371
   */
372
  private void initSpellCheckListener( final FileEditorTab tab ) {
373
    final var editor = tab.getEditorPane().getEditor();
374
375
    // When the editor first appears, run a full spell check. This allows
376
    // spell checking while typing to be restricted to the active paragraph,
377
    // which is usually substantially smaller than the whole document.
378
    addShowListener(
379
        editor, ( __ ) -> spellcheck( editor, editor.getText() )
380
    );
381
382
    // Use the plain text changes so that notifications of style changes
383
    // are suppressed. Checking against the identity ensures that only
384
    // new text additions or deletions trigger proofreading.
385
    editor.plainTextChanges()
386
          .filter( p -> !p.isIdentity() ).subscribe( change -> {
387
388
      // Only perform a spell check on the current paragraph. The
389
      // entire document is processed once, when opened.
390
      final var offset = change.getPosition();
391
      final var position = editor.offsetToPosition( offset, Forward );
392
      final var paraId = position.getMajor();
393
      final var paragraph = editor.getParagraph( paraId );
394
      final var text = paragraph.getText();
395
396
      // Ensure that styles aren't doubled-up.
397
      editor.clearStyle( paraId );
398
399
      spellcheck( editor, text, paraId );
400
    } );
401
  }
402
403
  /**
404
   * Listen for new tab selection events.
405
   */
406
  private void initTabChangedListener() {
407
    final FileEditorTabPane editorPane = getFileEditorPane();
408
409
    // Update the preview pane changing tabs.
410
    editorPane.addTabSelectionListener(
411
        ( tabPane, oldTab, newTab ) -> {
412
          if( newTab == null ) {
413
            // Clear the preview pane when closing an editor. When the last
414
            // tab is closed, this ensures that the preview pane is empty.
415
            getPreviewPane().clear();
416
          }
417
          else {
418
            final var tab = (FileEditorTab) newTab;
419
            updateVariableNameInjector( tab );
420
            process( tab );
421
          }
422
        }
423
    );
424
  }
425
426
  /**
427
   * Reloads the preferences from the previous session.
428
   */
429
  private void initPreferences() {
430
    initDefinitionPane();
431
    getFileEditorPane().initPreferences();
432
    getUserPreferences().addSaveEventHandler( mRPreferencesListener );
433
  }
434
435
  private void initVariableNameInjector() {
436
    updateVariableNameInjector( getActiveFileEditorTab() );
437
  }
438
439
  /**
440
   * Calls the listener when the given node is shown for the first time. The
441
   * visible property is not the same as the initial showing event; visibility
442
   * can be triggered numerous times (such as going off screen).
443
   * <p>
444
   * This is called, for example, before the drag handler can be attached,
445
   * because the scrollbar for the text editor pane must be visible.
446
   * </p>
447
   *
448
   * @param node     The node to watch for showing.
449
   * @param consumer The consumer to invoke when the event fires.
450
   */
451
  private void addShowListener(
452
      final Node node, final Consumer<Void> consumer ) {
453
    final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) ->
454
        runLater( () -> {
455
          if( newShow ) {
456
            try {
457
              consumer.accept( null );
458
            } catch( final Exception ex ) {
459
              alert( ex );
460
            }
461
          }
462
        } );
463
464
    Val.flatMap( node.sceneProperty(), Scene::windowProperty )
465
       .flatMap( Window::showingProperty )
466
       .addListener( listener );
467
  }
468
469
  private void scrollToParagraph( final int id ) {
470
    scrollToParagraph( id, false );
471
  }
472
473
  /**
474
   * @param id    The paragraph to scroll to, will be approximated if it doesn't
475
   *              exist.
476
   * @param force {@code true} means to force scrolling immediately, which
477
   *              should only be attempted when it is known that the document
478
   *              has been fully rendered. Otherwise the internal map of ID
479
   *              attributes will be incomplete and scrolling will flounder.
480
   */
481
  private void scrollToParagraph( final int id, final boolean force ) {
482
    synchronized( mMutex ) {
483
      final var previewPane = getPreviewPane();
484
      final var scrollPane = previewPane.getScrollPane();
485
      final int approxId = getActiveEditorPane().approximateParagraphId( id );
486
487
      if( force ) {
488
        previewPane.scrollTo( approxId );
489
      }
490
      else {
491
        previewPane.tryScrollTo( approxId );
492
      }
493
494
      scrollPane.repaint();
495
    }
496
  }
497
498
  private void updateVariableNameInjector( final FileEditorTab tab ) {
499
    getVariableNameInjector().addListener( tab );
500
  }
501
502
  /**
503
   * Called whenever the preview pane becomes out of sync with the file editor
504
   * tab. This can be called when the text changes, the caret paragraph
505
   * changes, or the file tab changes.
506
   *
507
   * @param tab The file editor tab that has been changed in some fashion.
508
   */
509
  private void process( final FileEditorTab tab ) {
510
    if( tab != null ) {
511
      getPreviewPane().setPath( tab.getPath() );
512
513
      final Processor<String> processor = getProcessors().computeIfAbsent(
514
          tab, p -> createProcessors( tab )
515
      );
516
517
      try {
518
        processChain( processor, tab.getEditorText() );
519
      } catch( final Exception ex ) {
520
        alert( ex );
521
      }
522
    }
523
  }
524
525
  /**
526
   * Executes the processing chain, operating on the given string.
527
   *
528
   * @param handler The first processor in the chain to call.
529
   * @param text    The initial value of the text to process.
530
   * @return The final value of the text that was processed by the chain.
531
   */
532
  private String processChain( Processor<String> handler, String text ) {
533
    while( handler != null && text != null ) {
534
      text = handler.apply( text );
535
      handler = handler.next();
536
    }
537
538
    return text;
539
  }
540
541
  private void renderActiveTab() {
542
    process( getActiveFileEditorTab() );
543
  }
544
545
  /**
546
   * Called when a definition source is opened.
547
   *
548
   * @param path Path to the definition source that was opened.
549
   */
550
  private void openDefinitions( final Path path ) {
551
    try {
552
      final var ds = createDefinitionSource( path );
553
      setDefinitionSource( ds );
554
555
      final var prefs = getUserPreferences();
556
      prefs.definitionPathProperty().setValue( path.toFile() );
557
      prefs.save();
558
559
      final var tooltipPath = new Tooltip( path.toString() );
560
      tooltipPath.setShowDelay( Duration.millis( 200 ) );
561
562
      final var pane = getDefinitionPane();
563
      pane.update( ds );
564
      pane.addTreeChangeHandler( mTreeHandler );
565
      pane.addKeyEventHandler( mDefinitionKeyHandler );
566
      pane.filenameProperty().setValue( path.getFileName().toString() );
567
      pane.setTooltip( tooltipPath );
568
569
      interpolateResolvedMap();
570
    } catch( final Exception ex ) {
571
      alert( ex );
572
    }
573
  }
574
575
  private void exportDefinitions( final Path path ) {
576
    try {
577
      final var pane = getDefinitionPane();
578
      final var root = pane.getTreeView().getRoot();
579
      final var problemChild = pane.isTreeWellFormed();
580
581
      if( problemChild == null ) {
582
        getDefinitionSource().getTreeAdapter().export( root, path );
583
      }
584
      else {
585
        alert( "yaml.error.tree.form", problemChild.getValue() );
586
      }
587
    } catch( final Exception ex ) {
588
      alert( ex );
589
    }
590
  }
591
592
  private void interpolateResolvedMap() {
593
    final var treeMap = getDefinitionPane().toMap();
594
    final var map = new HashMap<>( treeMap );
595
    MapInterpolator.interpolate( map );
596
597
    getResolvedMap().clear();
598
    getResolvedMap().putAll( map );
599
  }
600
601
  private void initDefinitionPane() {
602
    openDefinitions( getDefinitionPath() );
603
  }
604
605
  //---- File actions -------------------------------------------------------
606
607
  /**
608
   * Called when an {@link Observable} instance has changed. This is called
609
   * by both the {@link Snitch} service and the notify service. The @link
610
   * Snitch} service can be called for different file types, including
611
   * {@link DefinitionSource} instances.
612
   *
613
   * @param observable The observed instance.
614
   * @param value      The noteworthy item.
615
   */
616
  @Override
617
  public void update( final Observable observable, final Object value ) {
618
    if( value instanceof Path && observable instanceof Snitch ) {
619
      updateSelectedTab();
620
    }
621
  }
622
623
  /**
624
   * Called when a file has been modified.
625
   */
626
  private void updateSelectedTab() {
627
    rerender();
628
  }
629
630
  /**
631
   * After resetting the processors, they will refresh anew to be up-to-date
632
   * with the files (text and definition) currently loaded into the editor.
633
   */
634
  private void resetProcessors() {
635
    getProcessors().clear();
636
  }
637
638
  //---- File actions -------------------------------------------------------
639
640
  private void fileNew() {
641
    getFileEditorPane().newEditor();
642
  }
643
644
  private void fileOpen() {
645
    getFileEditorPane().openFileDialog();
646
  }
647
648
  private void fileClose() {
649
    getFileEditorPane().closeEditor( getActiveFileEditorTab(), true );
650
  }
651
652
  /**
653
   * TODO: Upon closing, first remove the tab change listeners. (There's no
654
   * need to re-render each tab when all are being closed.)
655
   */
656
  private void fileCloseAll() {
657
    getFileEditorPane().closeAllEditors();
658
  }
659
660
  private void fileSave() {
661
    getFileEditorPane().saveEditor( getActiveFileEditorTab() );
662
  }
663
664
  private void fileSaveAs() {
665
    final FileEditorTab editor = getActiveFileEditorTab();
666
    getFileEditorPane().saveEditorAs( editor );
667
    getProcessors().remove( editor );
668
669
    try {
670
      process( editor );
671
    } catch( final Exception ex ) {
672
      alert( ex );
673
    }
674
  }
675
676
  private void fileSaveAll() {
677
    getFileEditorPane().saveAllEditors();
678
  }
679
680
  private void fileExit() {
681
    final Window window = getWindow();
682
    fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
683
  }
684
685
  //---- Edit actions -------------------------------------------------------
686
687
  /**
688
   * Transform the Markdown into HTML then copy that HTML into the copy
689
   * buffer.
690
   */
691
  private void copyHtml() {
692
    final var markdown = getActiveEditorPane().getText();
693
    final var processors = createProcessorFactory().createProcessors(
694
        getActiveFileEditorTab()
695
    );
696
697
    final var chain = processors.remove( HtmlPreviewProcessor.class );
698
699
    final String html = processChain( chain, markdown );
700
701
    final Clipboard clipboard = Clipboard.getSystemClipboard();
702
    final ClipboardContent content = new ClipboardContent();
703
    content.putString( html );
704
    clipboard.setContent( content );
705
  }
706
707
  /**
708
   * Used to find text in the active file editor window.
709
   */
710
  private void editFind() {
711
    final TextField input = getFindTextField();
712
    getStatusBar().setGraphic( input );
713
    input.requestFocus();
714
  }
715
716
  public void editFindNext() {
717
    getActiveFileEditorTab().searchNext( getFindTextField().getText() );
718
  }
719
720
  public void editPreferences() {
721
    getUserPreferences().show();
722
  }
723
724
  //---- Insert actions -----------------------------------------------------
725
726
  /**
727
   * Delegates to the active editor to handle wrapping the current text
728
   * selection with leading and trailing strings.
729
   *
730
   * @param leading  The string to put before the selection.
731
   * @param trailing The string to put after the selection.
732
   */
733
  private void insertMarkdown(
734
      final String leading, final String trailing ) {
735
    getActiveEditorPane().surroundSelection( leading, trailing );
736
  }
737
738
  private void insertMarkdown(
739
      final String leading, final String trailing, final String hint ) {
740
    getActiveEditorPane().surroundSelection( leading, trailing, hint );
741
  }
742
743
  //---- View actions -------------------------------------------------------
744
745
  private void viewRefresh() {
746
    rerender();
747
  }
748
749
  //---- Help actions -------------------------------------------------------
750
751
  private void helpAbout() {
752
    final Alert alert = new Alert( AlertType.INFORMATION );
753
    alert.setTitle( get( "Dialog.about.title" ) );
754
    alert.setHeaderText( get( "Dialog.about.header" ) );
755
    alert.setContentText( get( "Dialog.about.content" ) );
756
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
757
    alert.initOwner( getWindow() );
758
759
    alert.showAndWait();
760
  }
761
762
  //---- Member creators ----------------------------------------------------
763
764
  private SpellChecker createSpellChecker() {
765
    try {
766
      final Collection<String> lexicon = readLexicon( "en.txt" );
767
      return SymSpellSpeller.forLexicon( lexicon );
768
    } catch( final Exception ex ) {
769
      alert( ex );
770
      return new PermissiveSpeller();
771
    }
772
  }
773
774
  /**
775
   * Factory to create processors that are suited to different file types.
776
   *
777
   * @param tab The tab that is subjected to processing.
778
   * @return A processor suited to the file type specified by the tab's path.
779
   */
780
  private Processor<String> createProcessors( final FileEditorTab tab ) {
781
    return createProcessorFactory().createProcessors( tab );
782
  }
783
784
  private ProcessorFactory createProcessorFactory() {
785
    return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
786
  }
787
788
  private HTMLPreviewPane createHTMLPreviewPane() {
789
    return new HTMLPreviewPane();
790
  }
791
792
  private DefinitionSource createDefaultDefinitionSource() {
793
    return new YamlDefinitionSource( getDefinitionPath() );
794
  }
795
796
  private DefinitionSource createDefinitionSource( final Path path ) {
797
    try {
798
      return createDefinitionFactory().createDefinitionSource( path );
799
    } catch( final Exception ex ) {
800
      alert( ex );
801
      return createDefaultDefinitionSource();
802
    }
803
  }
804
805
  private TextField createFindTextField() {
806
    return new TextField();
807
  }
808
809
  private DefinitionFactory createDefinitionFactory() {
810
    return new DefinitionFactory();
811
  }
812
813
  private StatusBar createStatusBar() {
814
    return new StatusBar();
815
  }
816
817
  private Scene createScene() {
818
    final SplitPane splitPane = new SplitPane(
819
        getDefinitionPane(),
820
        getFileEditorPane(),
821
        getPreviewPane() );
822
823
    splitPane.setDividerPositions(
824
        getFloat( K_PANE_SPLIT_DEFINITION, .22f ),
825
        getFloat( K_PANE_SPLIT_EDITOR, .60f ),
826
        getFloat( K_PANE_SPLIT_PREVIEW, .18f ) );
827
828
    getDefinitionPane().prefHeightProperty()
829
                       .bind( splitPane.heightProperty() );
830
831
    final BorderPane borderPane = new BorderPane();
832
    borderPane.setPrefSize( 1280, 800 );
833
    borderPane.setTop( createMenuBar() );
834
    borderPane.setBottom( getStatusBar() );
835
    borderPane.setCenter( splitPane );
836
837
    final VBox statusBar = new VBox();
838
    statusBar.setAlignment( Pos.BASELINE_CENTER );
839
    statusBar.getChildren().add( getLineNumberText() );
840
    getStatusBar().getRightItems().add( statusBar );
841
842
    // Force preview pane refresh on Windows.
843
    if( SystemUtils.IS_OS_WINDOWS ) {
844
      splitPane.getDividers().get( 1 ).positionProperty().addListener(
845
          ( l, oValue, nValue ) -> runLater(
846
              () -> getPreviewPane().getScrollPane().repaint()
847
          )
848
      );
849
    }
850
851
    return new Scene( borderPane );
852
  }
853
854
  private Text createLineNumberText() {
855
    return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) );
856
  }
857
858
  private Node createMenuBar() {
859
    final BooleanBinding activeFileEditorIsNull =
860
        getFileEditorPane().activeFileEditorProperty().isNull();
861
862
    // File actions
863
    final Action fileNewAction = new ActionBuilder()
864
        .setText( "Main.menu.file.new" )
865
        .setAccelerator( "Shortcut+N" )
866
        .setIcon( FILE_ALT )
867
        .setAction( e -> fileNew() )
868
        .build();
869
    final Action fileOpenAction = new ActionBuilder()
870
        .setText( "Main.menu.file.open" )
871
        .setAccelerator( "Shortcut+O" )
872
        .setIcon( FOLDER_OPEN_ALT )
873
        .setAction( e -> fileOpen() )
874
        .build();
875
    final Action fileCloseAction = new ActionBuilder()
876
        .setText( "Main.menu.file.close" )
877
        .setAccelerator( "Shortcut+W" )
878
        .setAction( e -> fileClose() )
879
        .setDisable( activeFileEditorIsNull )
880
        .build();
881
    final Action fileCloseAllAction = new ActionBuilder()
882
        .setText( "Main.menu.file.close_all" )
883
        .setAction( e -> fileCloseAll() )
884
        .setDisable( activeFileEditorIsNull )
885
        .build();
886
    final Action fileSaveAction = new ActionBuilder()
887
        .setText( "Main.menu.file.save" )
888
        .setAccelerator( "Shortcut+S" )
889
        .setIcon( FLOPPY_ALT )
890
        .setAction( e -> fileSave() )
891
        .setDisable( createActiveBooleanProperty(
892
            FileEditorTab::modifiedProperty ).not() )
893
        .build();
894
    final Action fileSaveAsAction = new ActionBuilder()
895
        .setText( "Main.menu.file.save_as" )
896
        .setAction( e -> fileSaveAs() )
897
        .setDisable( activeFileEditorIsNull )
898
        .build();
899
    final Action fileSaveAllAction = new ActionBuilder()
900
        .setText( "Main.menu.file.save_all" )
901
        .setAccelerator( "Shortcut+Shift+S" )
902
        .setAction( e -> fileSaveAll() )
903
        .setDisable( Bindings.not(
904
            getFileEditorPane().anyFileEditorModifiedProperty() ) )
905
        .build();
906
    final Action fileExitAction = new ActionBuilder()
907
        .setText( "Main.menu.file.exit" )
908
        .setAction( e -> fileExit() )
909
        .build();
910
911
    // Edit actions
912
    final Action editCopyHtmlAction = new ActionBuilder()
913
        .setText( "Main.menu.edit.copy.html" )
914
        .setIcon( HTML5 )
915
        .setAction( e -> copyHtml() )
916
        .setDisable( activeFileEditorIsNull )
917
        .build();
918
919
    final Action editUndoAction = new ActionBuilder()
920
        .setText( "Main.menu.edit.undo" )
921
        .setAccelerator( "Shortcut+Z" )
922
        .setIcon( UNDO )
923
        .setAction( e -> getActiveEditorPane().undo() )
924
        .setDisable( createActiveBooleanProperty(
925
            FileEditorTab::canUndoProperty ).not() )
926
        .build();
927
    final Action editRedoAction = new ActionBuilder()
928
        .setText( "Main.menu.edit.redo" )
929
        .setAccelerator( "Shortcut+Y" )
930
        .setIcon( REPEAT )
931
        .setAction( e -> getActiveEditorPane().redo() )
932
        .setDisable( createActiveBooleanProperty(
933
            FileEditorTab::canRedoProperty ).not() )
934
        .build();
935
936
    final Action editCutAction = new ActionBuilder()
937
        .setText( "Main.menu.edit.cut" )
938
        .setAccelerator( "Shortcut+X" )
939
        .setIcon( CUT )
940
        .setAction( e -> getActiveEditorPane().cut() )
941
        .setDisable( activeFileEditorIsNull )
942
        .build();
943
    final Action editCopyAction = new ActionBuilder()
944
        .setText( "Main.menu.edit.copy" )
945
        .setAccelerator( "Shortcut+C" )
946
        .setIcon( COPY )
947
        .setAction( e -> getActiveEditorPane().copy() )
948
        .setDisable( activeFileEditorIsNull )
949
        .build();
950
    final Action editPasteAction = new ActionBuilder()
951
        .setText( "Main.menu.edit.paste" )
952
        .setAccelerator( "Shortcut+V" )
953
        .setIcon( PASTE )
954
        .setAction( e -> getActiveEditorPane().paste() )
955
        .setDisable( activeFileEditorIsNull )
956
        .build();
957
    final Action editSelectAllAction = new ActionBuilder()
958
        .setText( "Main.menu.edit.selectAll" )
959
        .setAccelerator( "Shortcut+A" )
960
        .setAction( e -> getActiveEditorPane().selectAll() )
961
        .setDisable( activeFileEditorIsNull )
962
        .build();
963
964
    final Action editFindAction = new ActionBuilder()
965
        .setText( "Main.menu.edit.find" )
966
        .setAccelerator( "Ctrl+F" )
967
        .setIcon( SEARCH )
968
        .setAction( e -> editFind() )
969
        .setDisable( activeFileEditorIsNull )
970
        .build();
971
    final Action editFindNextAction = new ActionBuilder()
972
        .setText( "Main.menu.edit.find.next" )
973
        .setAccelerator( "F3" )
974
        .setIcon( null )
975
        .setAction( e -> editFindNext() )
976
        .setDisable( activeFileEditorIsNull )
977
        .build();
978
    final Action editPreferencesAction = new ActionBuilder()
979
        .setText( "Main.menu.edit.preferences" )
980
        .setAccelerator( "Ctrl+Alt+S" )
981
        .setAction( e -> editPreferences() )
982
        .build();
983
984
    // Format actions
985
    final Action formatBoldAction = new ActionBuilder()
986
        .setText( "Main.menu.format.bold" )
987
        .setAccelerator( "Shortcut+B" )
988
        .setIcon( BOLD )
989
        .setAction( e -> insertMarkdown( "**", "**" ) )
990
        .setDisable( activeFileEditorIsNull )
991
        .build();
992
    final Action formatItalicAction = new ActionBuilder()
993
        .setText( "Main.menu.format.italic" )
994
        .setAccelerator( "Shortcut+I" )
995
        .setIcon( ITALIC )
996
        .setAction( e -> insertMarkdown( "*", "*" ) )
997
        .setDisable( activeFileEditorIsNull )
998
        .build();
999
    final Action formatSuperscriptAction = new ActionBuilder()
1000
        .setText( "Main.menu.format.superscript" )
1001
        .setAccelerator( "Shortcut+[" )
1002
        .setIcon( SUPERSCRIPT )
1003
        .setAction( e -> insertMarkdown( "^", "^" ) )
1004
        .setDisable( activeFileEditorIsNull )
1005
        .build();
1006
    final Action formatSubscriptAction = new ActionBuilder()
1007
        .setText( "Main.menu.format.subscript" )
1008
        .setAccelerator( "Shortcut+]" )
1009
        .setIcon( SUBSCRIPT )
1010
        .setAction( e -> insertMarkdown( "~", "~" ) )
1011
        .setDisable( activeFileEditorIsNull )
1012
        .build();
1013
    final Action formatStrikethroughAction = new ActionBuilder()
1014
        .setText( "Main.menu.format.strikethrough" )
1015
        .setAccelerator( "Shortcut+T" )
1016
        .setIcon( STRIKETHROUGH )
1017
        .setAction( e -> insertMarkdown( "~~", "~~" ) )
1018
        .setDisable( activeFileEditorIsNull )
1019
        .build();
1020
1021
    // Insert actions
1022
    final Action insertBlockquoteAction = new ActionBuilder()
1023
        .setText( "Main.menu.insert.blockquote" )
1024
        .setAccelerator( "Ctrl+Q" )
1025
        .setIcon( QUOTE_LEFT )
1026
        .setAction( e -> insertMarkdown( "\n\n> ", "" ) )
1027
        .setDisable( activeFileEditorIsNull )
1028
        .build();
1029
    final Action insertCodeAction = new ActionBuilder()
1030
        .setText( "Main.menu.insert.code" )
1031
        .setAccelerator( "Shortcut+K" )
1032
        .setIcon( CODE )
1033
        .setAction( e -> insertMarkdown( "`", "`" ) )
1034
        .setDisable( activeFileEditorIsNull )
1035
        .build();
1036
    final Action insertFencedCodeBlockAction = new ActionBuilder()
1037
        .setText( "Main.menu.insert.fenced_code_block" )
1038
        .setAccelerator( "Shortcut+Shift+K" )
1039
        .setIcon( FILE_CODE_ALT )
1040
        .setAction( e -> insertMarkdown(
1041
            "\n\n```\n",
1042
            "\n```\n\n",
1043
            get( "Main.menu.insert.fenced_code_block.prompt" ) ) )
1044
        .setDisable( activeFileEditorIsNull )
1045
        .build();
1046
    final Action insertLinkAction = new ActionBuilder()
1047
        .setText( "Main.menu.insert.link" )
1048
        .setAccelerator( "Shortcut+L" )
1049
        .setIcon( LINK )
1050
        .setAction( e -> getActiveEditorPane().insertLink() )
1051
        .setDisable( activeFileEditorIsNull )
1052
        .build();
1053
    final Action insertImageAction = new ActionBuilder()
1054
        .setText( "Main.menu.insert.image" )
1055
        .setAccelerator( "Shortcut+G" )
1056
        .setIcon( PICTURE_ALT )
1057
        .setAction( e -> getActiveEditorPane().insertImage() )
1058
        .setDisable( activeFileEditorIsNull )
1059
        .build();
1060
1061
    // Number of heading actions (H1 ... H3)
1062
    final int HEADINGS = 3;
1063
    final Action[] headings = new Action[ HEADINGS ];
1064
1065
    for( int i = 1; i <= HEADINGS; i++ ) {
1066
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
1067
      final String markup = String.format( "%n%n%s ", hashes );
1068
      final String text = "Main.menu.insert.heading." + i;
1069
      final String accelerator = "Shortcut+" + i;
1070
      final String prompt = text + ".prompt";
1071
1072
      headings[ i - 1 ] = new ActionBuilder()
1073
          .setText( text )
1074
          .setAccelerator( accelerator )
1075
          .setIcon( HEADER )
1076
          .setAction( e -> insertMarkdown( markup, "", get( prompt ) ) )
1077
          .setDisable( activeFileEditorIsNull )
1078
          .build();
1079
    }
1080
1081
    final Action insertUnorderedListAction = new ActionBuilder()
1082
        .setText( "Main.menu.insert.unordered_list" )
1083
        .setAccelerator( "Shortcut+U" )
1084
        .setIcon( LIST_UL )
1085
        .setAction( e -> insertMarkdown( "\n\n* ", "" ) )
1086
        .setDisable( activeFileEditorIsNull )
1087
        .build();
1088
    final Action insertOrderedListAction = new ActionBuilder()
1089
        .setText( "Main.menu.insert.ordered_list" )
1090
        .setAccelerator( "Shortcut+Shift+O" )
1091
        .setIcon( LIST_OL )
1092
        .setAction( e -> insertMarkdown(
1093
            "\n\n1. ", "" ) )
1094
        .setDisable( activeFileEditorIsNull )
1095
        .build();
1096
    final Action insertHorizontalRuleAction = new ActionBuilder()
1097
        .setText( "Main.menu.insert.horizontal_rule" )
1098
        .setAccelerator( "Shortcut+H" )
1099
        .setAction( e -> insertMarkdown(
1100
            "\n\n---\n\n", "" ) )
1101
        .setDisable( activeFileEditorIsNull )
1102
        .build();
1103
1104
    // Definition actions
1105
    final Action definitionCreateAction = new ActionBuilder()
1106
        .setText( "Main.menu.definition.create" )
1107
        .setIcon( TREE )
1108
        .setAction( e -> getDefinitionPane().addItem() )
1109
        .build();
1110
    final Action definitionInsertAction = new ActionBuilder()
1111
        .setText( "Main.menu.definition.insert" )
1112
        .setAccelerator( "Ctrl+Space" )
1113
        .setIcon( STAR )
1114
        .setAction( e -> definitionInsert() )
1115
        .build();
1116
1117
    // Help actions
1118
    final Action helpAboutAction = new ActionBuilder()
1119
        .setText( "Main.menu.help.about" )
1120
        .setAction( e -> helpAbout() )
1121
        .build();
1122
1123
    //---- MenuBar ----
1124
1125
    // File Menu
1126
    final var fileMenu = ActionUtils.createMenu(
1127
        get( "Main.menu.file" ),
1128
        fileNewAction,
1129
        fileOpenAction,
1130
        null,
1131
        fileCloseAction,
1132
        fileCloseAllAction,
1133
        null,
1134
        fileSaveAction,
1135
        fileSaveAsAction,
1136
        fileSaveAllAction,
1137
        null,
1138
        fileExitAction );
1139
1140
    // Edit Menu
1141
    final var editMenu = ActionUtils.createMenu(
1142
        get( "Main.menu.edit" ),
1143
        editCopyHtmlAction,
1144
        null,
1145
        editUndoAction,
1146
        editRedoAction,
1147
        null,
1148
        editCutAction,
1149
        editCopyAction,
1150
        editPasteAction,
1151
        editSelectAllAction,
1152
        null,
1153
        editFindAction,
1154
        editFindNextAction,
1155
        null,
1156
        editPreferencesAction );
1157
1158
    // Format Menu
1159
    final var formatMenu = ActionUtils.createMenu(
1160
        get( "Main.menu.format" ),
1161
        formatBoldAction,
1162
        formatItalicAction,
1163
        formatSuperscriptAction,
1164
        formatSubscriptAction,
1165
        formatStrikethroughAction
1166
    );
1167
1168
    // Insert Menu
1169
    final var insertMenu = ActionUtils.createMenu(
1170
        get( "Main.menu.insert" ),
1171
        insertBlockquoteAction,
1172
        insertCodeAction,
1173
        insertFencedCodeBlockAction,
1174
        null,
1175
        insertLinkAction,
1176
        insertImageAction,
1177
        null,
1178
        headings[ 0 ],
1179
        headings[ 1 ],
1180
        headings[ 2 ],
1181
        null,
1182
        insertUnorderedListAction,
1183
        insertOrderedListAction,
1184
        insertHorizontalRuleAction
1185
    );
1186
1187
    // Definition Menu
1188
    final var definitionMenu = ActionUtils.createMenu(
1189
        get( "Main.menu.definition" ),
1190
        definitionCreateAction,
1191
        definitionInsertAction );
1192
1193
    // Help Menu
1194
    final var helpMenu = ActionUtils.createMenu(
1195
        get( "Main.menu.help" ),
1196
        helpAboutAction );
1197
1198
    //---- MenuBar ----
1199
    final var menuBar = new MenuBar(
1200
        fileMenu,
1201
        editMenu,
1202
        formatMenu,
1203
        insertMenu,
1204
        definitionMenu,
1205
        helpMenu );
1206
1207
    //---- ToolBar ----
1208
    final var toolBar = ActionUtils.createToolBar(
1209
        fileNewAction,
1210
        fileOpenAction,
1211
        fileSaveAction,
1212
        null,
1213
        editUndoAction,
1214
        editRedoAction,
1215
        editCutAction,
1216
        editCopyAction,
1217
        editPasteAction,
1218
        null,
1219
        formatBoldAction,
1220
        formatItalicAction,
1221
        formatSuperscriptAction,
1222
        formatSubscriptAction,
1223
        insertBlockquoteAction,
1224
        insertCodeAction,
1225
        insertFencedCodeBlockAction,
1226
        null,
1227
        insertLinkAction,
1228
        insertImageAction,
1229
        null,
1230
        headings[ 0 ],
1231
        null,
1232
        insertUnorderedListAction,
1233
        insertOrderedListAction );
1234
1235
    return new VBox( menuBar, toolBar );
1236
  }
1237
1238
  /**
1239
   * Performs the autoinsert function on the active file editor.
1240
   */
1241
  private void definitionInsert() {
1242
  }
1243
1244
  /**
1245
   * Creates a boolean property that is bound to another boolean value of the
1246
   * active editor.
1247
   */
1248
  private BooleanProperty createActiveBooleanProperty(
1249
      final Function<FileEditorTab, ObservableBooleanValue> func ) {
1250
1251
    final BooleanProperty b = new SimpleBooleanProperty();
1252
    final FileEditorTab tab = getActiveFileEditorTab();
1253
1254
    if( tab != null ) {
1255
      b.bind( func.apply( tab ) );
1256
    }
1257
1258
    getFileEditorPane().activeFileEditorProperty().addListener(
1259
        ( observable, oldFileEditor, newFileEditor ) -> {
1260
          b.unbind();
1261
1262
          if( newFileEditor == null ) {
1263
            b.set( false );
1264
          }
1265
          else {
1266
            b.bind( func.apply( newFileEditor ) );
1267
          }
1268
        }
1269
    );
1270
1271
    return b;
1272
  }
1273
1274
  //---- Convenience accessors ----------------------------------------------
1275
1276
  private Preferences getPreferences() {
1277
    return sOptions.getState();
1278
  }
1279
1280
  private int getCurrentParagraphIndex() {
1281
    return getActiveEditorPane().getCurrentParagraphIndex();
1282
  }
1283
1284
  private float getFloat( final String key, final float defaultValue ) {
1285
    return getPreferences().getFloat( key, defaultValue );
1286
  }
1287
1288
  public Window getWindow() {
1289
    return getScene().getWindow();
1290
  }
1291
1292
  private MarkdownEditorPane getActiveEditorPane() {
1293
    return getActiveFileEditorTab().getEditorPane();
1294
  }
1295
1296
  private FileEditorTab getActiveFileEditorTab() {
1297
    return getFileEditorPane().getActiveFileEditor();
1298
  }
1299
1300
  //---- Member accessors ---------------------------------------------------
1301
1302
  protected Scene getScene() {
1303
    return mScene;
1304
  }
1305
1306
  private SpellChecker getSpellChecker() {
1307
    return mSpellChecker;
1308
  }
1309
1310
  private Map<FileEditorTab, Processor<String>> getProcessors() {
1311
    return mProcessors;
1312
  }
1313
1314
  private FileEditorTabPane getFileEditorPane() {
1315
    return mFileEditorPane;
1316
  }
1317
1318
  private HTMLPreviewPane getPreviewPane() {
1319
    return mPreviewPane;
1320
  }
1321
1322
  private void setDefinitionSource(
1323
      final DefinitionSource definitionSource ) {
1324
    assert definitionSource != null;
1325
    mDefinitionSource = definitionSource;
1326
  }
1327
1328
  private DefinitionSource getDefinitionSource() {
1329
    return mDefinitionSource;
1330
  }
1331
1332
  private DefinitionPane getDefinitionPane() {
1333
    return mDefinitionPane;
1334
  }
1335
1336
  private Text getLineNumberText() {
1337
    return mLineNumberText;
1338
  }
1339
1340
  private StatusBar getStatusBar() {
1341
    return mStatusBar;
1342
  }
1343
1344
  private TextField getFindTextField() {
1345
    return mFindTextField;
1346
  }
1347
1348
  private DefinitionNameInjector getVariableNameInjector() {
1349
    return mVariableNameInjector;
1350
  }
1351
1352
  /**
1353
   * Returns the variable map of interpolated definitions.
1354
   *
1355
   * @return A map to help dereference variables.
1356
   */
1357
  private Map<String, String> getResolvedMap() {
1358
    return mResolvedMap;
1359
  }
1360
1361
  //---- Persistence accessors ----------------------------------------------
1362
1363
  private UserPreferences getUserPreferences() {
1364
    return sOptions.getUserPreferences();
1365
  }
1366
1367
  private Path getDefinitionPath() {
1368
    return getUserPreferences().getDefinitionPath();
1369
  }
1370
1371
  //---- Spelling -----------------------------------------------------------
1372
1373
  /**
1374
   * Delegates to {@link #spellcheck(StyleClassedTextArea, String, int)}.
1375
   * This is called to spell check the document, rather than a single paragraph.
1376
   *
1377
   * @param text The full document text.
1378
   */
1379
  private void spellcheck(
1380
      final StyleClassedTextArea editor, final String text ) {
1381
    spellcheck( editor, text, -1 );
1382
  }
1383
1384
  /**
1385
   * Spellchecks a subset of the entire document.
1386
   *
1387
   * @param text   Look up words for this text in the lexicon.
1388
   * @param paraId Set to -1 to apply resulting style spans to the entire
1389
   *               text.
1390
   */
1391
  private void spellcheck(
1392
      final StyleClassedTextArea editor, final String text, final int paraId ) {
1393
    final var builder = new StyleSpansBuilder<Collection<String>>();
1394
    final var runningIndex = new AtomicInteger( 0 );
1395
    final var checker = getSpellChecker();
1396
1397
    // The text nodes must be relayed through a contextual "visitor" that
1398
    // can return text in chunks with correlative offsets into the string.
1399
    // This allows Markdown, R Markdown, XML, and R XML documents to return
1400
    // sets of words to check.
1401
1402
    final var node = mParser.parse( text );
1403
    final var visitor = new TextVisitor( ( visited, bIndex, eIndex ) -> {
1404
      // Treat hyphenated compound words as individual words.
1405
      final var check = visited.replace( '-', ' ' );
1406
1407
      checker.proofread( check, ( misspelled, prevIndex, currIndex ) -> {
1408
        prevIndex += bIndex;
1409
        currIndex += bIndex;
1410
1411
        // Clear styling between lexiconically absent words.
1412
        builder.add( emptyList(), prevIndex - runningIndex.get() );
1413
        builder.add( singleton( "spelling" ), currIndex - prevIndex );
1414
        runningIndex.set( currIndex );
1415
      } );
1416
    } );
1417
1418
    visitor.visit( node );
1419
1420
    // If the running index was set, at least one word triggered the listener.
1421
    if( runningIndex.get() > 0 ) {
1422
      // Clear styling after the last lexiconically absent word.
1423
      builder.add( emptyList(), text.length() - runningIndex.get() );
1424
1425
      final var spans = builder.create();
1426
1427
      if( paraId >= 0 ) {
1428
        editor.setStyleSpans( paraId, 0, spans );
1429
      }
1430
      else {
1431
        editor.setStyleSpans( 0, spans );
1432
      }
1433
    }
1434
  }
1435
1436
  @SuppressWarnings("SameParameterValue")
1437
  private Collection<String> readLexicon( final String filename )
1438
      throws Exception {
1439
    final var path = Paths.get( LEXICONS_DIRECTORY, filename ).toString();
1440
    final var classLoader = MainWindow.class.getClassLoader();
1441
1442
    try( final var resource = classLoader.getResourceAsStream( path ) ) {
1443
      assert resource != null;
1444
1445
      return new BufferedReader( new InputStreamReader( resource, UTF_8 ) )
1446
          .lines()
1447
          .collect( Collectors.toList() );
1448
    }
1449
  }
1450
1451
  // TODO: Replace using Markdown processor instantiated for Markdown files.
1452
  // FIXME: https://github.com/DaveJarvis/scrivenvar/issues/59
1453
  private final Parser mParser = Parser.builder().build();
1454
1455
  // TODO: Replace with generic interface; provide Markdown/XML implementations.
1456
  // FIXME: https://github.com/DaveJarvis/scrivenvar/issues/59
1457
  private static final class TextVisitor {
14971458
    private final NodeVisitor mVisitor = new NodeVisitor( new VisitHandler<>(
14981459
        com.vladsch.flexmark.ast.Text.class, this::visit )
A src/main/java/com/scrivenvar/StatusBarNotifier.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import com.scrivenvar.service.events.Notifier;
31
import org.controlsfx.control.StatusBar;
32
33
import static com.scrivenvar.Constants.STATUS_BAR_OK;
34
import static com.scrivenvar.Messages.get;
35
import static javafx.application.Platform.runLater;
36
37
/**
38
 * Responsible for passing notifications about exceptions (or other error
39
 * messages) through the application. Once the Event Bus is implemented, this
40
 * class can go away.
41
 */
42
public class StatusBarNotifier {
43
  private static final String OK = get( STATUS_BAR_OK, "OK" );
44
45
  private static final Notifier sNotifier = Services.load( Notifier.class );
46
  private static StatusBar sStatusBar;
47
48
  public static void setStatusBar( final StatusBar statusBar ) {
49
    sStatusBar = statusBar;
50
  }
51
52
  /**
53
   * Resets the status bar to a default message.
54
   */
55
  public static void clearAlert() {
56
    // Don't burden the repaint thread if there's no status bar change.
57
    if( !OK.equals( sStatusBar.getText() ) ) {
58
      update( OK );
59
    }
60
  }
61
62
  /**
63
   * Updates the status bar with a custom message.
64
   *
65
   * @param key The resource bundle key associated with a message (typically
66
   *            to inform the user about an error).
67
   */
68
  public static void alert( final String key ) {
69
    update( get( key ) );
70
  }
71
72
  /**
73
   * Updates the status bar with a custom message.
74
   *
75
   * @param key  The property key having a value to populate with arguments.
76
   * @param args The placeholder values to substitute into the key's value.
77
   */
78
  public static void alert( final String key, final Object... args ) {
79
    update( get( key, args ) );
80
  }
81
82
  /**
83
   * Called when an exception occurs that warrants the user's attention.
84
   *
85
   * @param ex The exception with a message that the user should know about.
86
   */
87
  public static void alert( final Exception ex ) {
88
    update( ex.getMessage() );
89
  }
90
91
  /**
92
   * Updates the status bar to show the given message.
93
   *
94
   * @param s The message to show in the status bar.
95
   */
96
  private static void update( final String s ) {
97
    runLater(
98
        () -> {
99
          final var i = s.indexOf( '\n' );
100
          sStatusBar.setText( s.substring( 0, i > 0 ? i : s.length() ) );
101
        }
102
    );
103
  }
104
105
  /**
106
   * Returns the global {@link Notifier} instance that can be used for opening
107
   * pop-up alert messages.
108
   *
109
   * @return The pop-up {@link Notifier} dispatcher.
110
   */
111
  public static Notifier getNotifier() {
112
    return sNotifier;
113
  }
114
}
1115
D src/main/java/com/scrivenvar/decorators/RVariableDecorator.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.decorators;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.preferences.UserPreferences;
32
import com.scrivenvar.service.Options;
33
34
/**
35
 * Brackets variable names with {@code `r#} and {@code `}.
36
 */
37
public class RVariableDecorator implements VariableDecorator {
38
  private static final Options sOptions = Services.load( Options.class );
39
40
  public static final String PREFIX = "`r#";
41
  public static final char SUFFIX = '`';
42
43
  private final String mDelimiterBegan =
44
      getUserPreferences().getRDelimiterBegan();
45
  private final String mDelimiterEnded =
46
      getUserPreferences().getRDelimiterEnded();
47
48
  /**
49
   * Returns the given string R-escaping backticks prepended and appended. This
50
   * is not null safe. Do not pass null into this method.
51
   *
52
   * @param variableName The string to decorate.
53
   * @return "`r#" + delimiterBegan + variableName+ delimiterEnded + "`".
54
   */
55
  @Override
56
  public String decorate( String variableName ) {
57
    assert variableName != null;
58
59
    // Delete the $ $ sigils from Markdown variables.
60
    if( variableName.length() > 1 ) {
61
      variableName = variableName.substring( 1, variableName.length() - 1 );
62
    }
63
64
    return PREFIX
65
        + mDelimiterBegan
66
        + "v$"
67
        + variableName.replace( '.', '$' )
68
        + mDelimiterEnded
69
        + SUFFIX;
70
  }
71
72
  private UserPreferences getUserPreferences() {
73
    return sOptions.getUserPreferences();
74
  }
75
}
761
D src/main/java/com/scrivenvar/decorators/VariableDecorator.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.decorators;
29
30
/**
31
 * Responsible for updating variable names to use a machine-readable format
32
 * corresponding to the type of file being edited.
33
 */
34
public interface VariableDecorator {
35
36
  /**
37
   * This decorates a variable name based on some criteria determined by the
38
   * factory that creates implementations of this interface.
39
   *
40
   * @param variableName The text to decorate as per the filename extension
41
   *                     would indicate (e.g., ".md" goes to $VAR$ while "
42
   *                     .Rmd" goes to `r#VAR`).
43
   * @return The given variable name modified with its requisite delimiters.
44
   */
45
  String decorate( String variableName );
46
}
471
D src/main/java/com/scrivenvar/decorators/YamlVariableDecorator.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.decorators;
29
30
/**
31
 * Brackets variable names with dollar symbols.
32
 */
33
public class YamlVariableDecorator implements VariableDecorator {
34
35
  /**
36
   * Returns the given {@link String} verbatim because variables in YAML
37
   * documents and plain Markdown documents already have the appropriate
38
   * tokenizable syntax wrapped around the text.
39
   *
40
   * @param variableName Returned verbatim.
41
   */
42
  @Override
43
  public String decorate( final String variableName ) {
44
    assert variableName != null;
45
    return variableName;
46
  }
47
48
  /**
49
   * Sigilifies the given key.
50
   *
51
   * @param key The key to adorn with YAML variable sigil characters.
52
   * @return The given key bracketed by dollar symbols.
53
   */
54
  public static String entoken( final String key ) {
55
    assert key != null;
56
    return '$' + key + '$';
57
  }
58
}
591
M src/main/java/com/scrivenvar/definition/DefinitionFactory.java
3131
import com.scrivenvar.FileType;
3232
import com.scrivenvar.definition.yaml.YamlDefinitionSource;
33
import com.scrivenvar.util.ProtocolResolver;
3433
3534
import java.nio.file.Path;
3635
37
import static com.scrivenvar.Constants.DEFINITION_PROTOCOL_FILE;
3836
import static com.scrivenvar.Constants.GLOB_PREFIX_DEFINITION;
3937
import static com.scrivenvar.FileType.YAML;
38
import static com.scrivenvar.util.ProtocolResolver.getProtocol;
4039
4140
/**
...
6261
    assert path != null;
6362
64
    final String protocol = ProtocolResolver.getProtocol( path.toString() );
63
    final var protocol = getProtocol( path.toString() );
6564
    DefinitionSource result = null;
6665
67
    if( DEFINITION_PROTOCOL_FILE.equals( protocol ) ) {
66
    if( protocol.isFile() ) {
6867
      final FileType filetype = lookup( path, GLOB_PREFIX_DEFINITION );
6968
      result = createFileDefinitionSource( filetype, path );
M src/main/java/com/scrivenvar/definition/DefinitionPane.java
243243
244244
  /**
245
   * Delegates to {@link VariableTreeItem#findLeafExact(String)}.
245
   * Delegates to {@link DefinitionTreeItem#findLeafExact(String)}.
246246
   *
247247
   * @param text The value to find, never {@code null}.
248248
   * @return The leaf that contains the given value, or {@code null} if
249249
   * not found.
250250
   */
251
  public VariableTreeItem<String> findLeafExact( final String text ) {
251
  public DefinitionTreeItem<String> findLeafExact( final String text ) {
252252
    return getTreeRoot().findLeafExact( text );
253253
  }
254254
255255
  /**
256
   * Delegates to {@link VariableTreeItem#findLeafContains(String)}.
256
   * Delegates to {@link DefinitionTreeItem#findLeafContains(String)}.
257257
   *
258258
   * @param text The value to find, never {@code null}.
259259
   * @return The leaf that contains the given value, or {@code null} if
260260
   * not found.
261261
   */
262
  public VariableTreeItem<String> findLeafContains( final String text ) {
262
  public DefinitionTreeItem<String> findLeafContains( final String text ) {
263263
    return getTreeRoot().findLeafContains( text );
264264
  }
265265
266266
  /**
267
   * Delegates to {@link VariableTreeItem#findLeafContains(String)}.
267
   * Delegates to {@link DefinitionTreeItem#findLeafContains(String)}.
268268
   *
269269
   * @param text The value to find, never {@code null}.
270270
   * @return The leaf that contains the given value, or {@code null} if
271271
   * not found.
272272
   */
273
  public VariableTreeItem<String> findLeafContainsNoCase( final String text ) {
273
  public DefinitionTreeItem<String> findLeafContainsNoCase(
274
      final String text ) {
274275
    return getTreeRoot().findLeafContainsNoCase( text );
275276
  }
276277
277278
  /**
278
   * Delegates to {@link VariableTreeItem#findLeafStartsWith(String)}.
279
   * Delegates to {@link DefinitionTreeItem#findLeafStartsWith(String)}.
279280
   *
280281
   * @param text The value to find, never {@code null}.
281282
   * @return The leaf that contains the given value, or {@code null} if
282283
   * not found.
283284
   */
284
  public VariableTreeItem<String> findLeafStartsWith( final String text ) {
285
  public DefinitionTreeItem<String> findLeafStartsWith( final String text ) {
285286
    return getTreeRoot().findLeafStartsWith( text );
286287
  }
...
368369
   * root must contain two items: a key and a value.
369370
   */
370
  private void addItem() {
371
  public void addItem() {
371372
    final var value = createTreeItem();
372373
    getSelectedItem().getChildren().add( value );
...
444445
  }
445446
446
  private VariableTreeItem<String> createTreeItem() {
447
    return new VariableTreeItem<>( get( "Definition.menu.add.default" ) );
447
  private DefinitionTreeItem<String> createTreeItem() {
448
    return new DefinitionTreeItem<>( get( "Definition.menu.add.default" ) );
448449
  }
449450
...
511512
   * @return The first node added to the definition tree.
512513
   */
513
  private VariableTreeItem<String> getTreeRoot() {
514
    final TreeItem<String> root = getTreeView().getRoot();
514
  private DefinitionTreeItem<String> getTreeRoot() {
515
    final var root = getTreeView().getRoot();
515516
516
    return root instanceof VariableTreeItem ?
517
        (VariableTreeItem<String>) root : new VariableTreeItem<>( "root" );
517
    return root instanceof DefinitionTreeItem
518
        ? (DefinitionTreeItem<String>) root
519
        : new DefinitionTreeItem<>( "root" );
518520
  }
519521
...
547549
  private Set<EventHandler<? super KeyEvent>> getKeyEventHandlers() {
548550
    return mKeyEventHandlers;
551
  }
552
553
  /**
554
   * Answers whether there are any definitions in the tree.
555
   *
556
   * @return {@code true} when there are no definitions; {@code false} when
557
   * there's at least one definition.
558
   */
559
  public boolean isEmpty() {
560
    return getTreeRoot().isEmpty();
549561
  }
550562
}
A src/main/java/com/scrivenvar/definition/DefinitionTreeItem.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import javafx.scene.control.TreeItem;
31
32
import java.util.Stack;
33
import java.util.function.BiFunction;
34
35
import static java.text.Normalizer.Form.NFD;
36
import static java.text.Normalizer.normalize;
37
38
/**
39
 * Provides behaviour afforded to definition keys and corresponding value.
40
 *
41
 * @param <T> The type of {@link TreeItem} (usually string).
42
 */
43
public class DefinitionTreeItem<T> extends TreeItem<T> {
44
45
  /**
46
   * Constructs a new item with a default value.
47
   *
48
   * @param value Passed up to superclass.
49
   */
50
  public DefinitionTreeItem( final T value ) {
51
    super( value );
52
  }
53
54
  /**
55
   * Finds a leaf starting at the current node with text that matches the given
56
   * value. Search is performed case-sensitively.
57
   *
58
   * @param text The text to match against each leaf in the tree.
59
   * @return The leaf that has a value exactly matching the given text.
60
   */
61
  public DefinitionTreeItem<T> findLeafExact( final String text ) {
62
    return findLeaf( text, DefinitionTreeItem::valueEquals );
63
  }
64
65
  /**
66
   * Finds a leaf starting at the current node with text that matches the given
67
   * value. Search is performed case-sensitively.
68
   *
69
   * @param text The text to match against each leaf in the tree.
70
   * @return The leaf that has a value that contains the given text.
71
   */
72
  public DefinitionTreeItem<T> findLeafContains( final String text ) {
73
    return findLeaf( text, DefinitionTreeItem::valueContains );
74
  }
75
76
  /**
77
   * Finds a leaf starting at the current node with text that matches the given
78
   * value. Search is performed case-insensitively.
79
   *
80
   * @param text The text to match against each leaf in the tree.
81
   * @return The leaf that has a value that contains the given text.
82
   */
83
  public DefinitionTreeItem<T> findLeafContainsNoCase( final String text ) {
84
    return findLeaf( text, DefinitionTreeItem::valueContainsNoCase );
85
  }
86
87
  /**
88
   * Finds a leaf starting at the current node with text that matches the given
89
   * value. Search is performed case-sensitively.
90
   *
91
   * @param text The text to match against each leaf in the tree.
92
   * @return The leaf that has a value that starts with the given text.
93
   */
94
  public DefinitionTreeItem<T> findLeafStartsWith( final String text ) {
95
    return findLeaf( text, DefinitionTreeItem::valueStartsWith );
96
  }
97
98
  /**
99
   * Finds a leaf starting at the current node with text that matches the given
100
   * value.
101
   *
102
   * @param text     The text to match against each leaf in the tree.
103
   * @param findMode What algorithm is used to match the given text.
104
   * @return The leaf that has a value starting with the given text, or {@code
105
   * null} if there was no match found.
106
   */
107
  public DefinitionTreeItem<T> findLeaf(
108
      final String text,
109
      final BiFunction<DefinitionTreeItem<T>, String, Boolean> findMode ) {
110
    final var stack = new Stack<DefinitionTreeItem<T>>();
111
    stack.push( this );
112
113
    // Don't hunt for blank (empty) keys.
114
    boolean found = text.isBlank();
115
116
    while( !found && !stack.isEmpty() ) {
117
      final var node = stack.pop();
118
119
      for( final var child : node.getChildren() ) {
120
        final var result = (DefinitionTreeItem<T>) child;
121
122
        if( result.isLeaf() ) {
123
          if( found = findMode.apply( result, text ) ) {
124
            return result;
125
          }
126
        }
127
        else {
128
          stack.push( result );
129
        }
130
      }
131
    }
132
133
    return null;
134
  }
135
136
  /**
137
   * Returns the value of the string without diacritic marks.
138
   *
139
   * @return A non-null, possibly empty string.
140
   */
141
  private String getDiacriticlessValue() {
142
    return normalize( getValue().toString(), NFD )
143
        .replaceAll( "\\p{M}", "" );
144
  }
145
146
  /**
147
   * Returns true if this node is a leaf and its value equals the given text.
148
   *
149
   * @param s The text to compare against the node value.
150
   * @return true Node is a leaf and its value equals the given value.
151
   */
152
  private boolean valueEquals( final String s ) {
153
    return isLeaf() && getValue().equals( s );
154
  }
155
156
  /**
157
   * Returns true if this node is a leaf and its value contains the given text.
158
   *
159
   * @param s The text to compare against the node value.
160
   * @return true Node is a leaf and its value contains the given value.
161
   */
162
  private boolean valueContains( final String s ) {
163
    return isLeaf() && getDiacriticlessValue().contains( s );
164
  }
165
166
  /**
167
   * Returns true if this node is a leaf and its value contains the given text.
168
   *
169
   * @param s The text to compare against the node value.
170
   * @return true Node is a leaf and its value contains the given value.
171
   */
172
  private boolean valueContainsNoCase( final String s ) {
173
    return isLeaf() && getDiacriticlessValue()
174
        .toLowerCase().contains( s.toLowerCase() );
175
  }
176
177
  /**
178
   * Returns true if this node is a leaf and its value starts with the given
179
   * text.
180
   *
181
   * @param s The text to compare against the node value.
182
   * @return true Node is a leaf and its value starts with the given value.
183
   */
184
  private boolean valueStartsWith( final String s ) {
185
    return isLeaf() && getDiacriticlessValue().startsWith( s );
186
  }
187
188
  /**
189
   * Returns the path for this node, with nodes made distinct using the
190
   * separator character. This uses two loops: one for pushing nodes onto a
191
   * stack and one for popping them off to create the path in desired order.
192
   *
193
   * @return A non-null string, possibly empty.
194
   */
195
  public String toPath() {
196
    return TreeItemAdapter.toPath( getParent() );
197
  }
198
199
  /**
200
   * Answers whether there are any definitions in this tree.
201
   *
202
   * @return {@code true} when there are no definitions in the tree; {@code
203
   * false} when there is at least one definition present.
204
   */
205
  public boolean isEmpty() {
206
    return getChildren().isEmpty();
207
  }
208
}
1209
M src/main/java/com/scrivenvar/definition/MapInterpolator.java
2828
package com.scrivenvar.definition;
2929
30
import com.scrivenvar.sigils.YamlSigilOperator;
31
3032
import java.util.Map;
3133
import java.util.regex.Matcher;
32
import java.util.regex.Pattern;
34
35
import static com.scrivenvar.sigils.YamlSigilOperator.REGEX_PATTERN;
3336
3437
/**
3538
 * Responsible for performing string interpolation on key/value pairs stored
3639
 * in a map. The values in the map can use a delimited syntax to refer to
3740
 * keys in the map.
3841
 */
3942
public class MapInterpolator {
40
41
  /**
42
   * Matches variables delimited by dollar symbols.
43
   */
44
  private final static String REGEX = "(\\$.*?\\$)";
45
46
  /**
47
   * Compiled regular expression for matching delimited references.
48
   */
49
  private final static Pattern REGEX_PATTERN = Pattern.compile( REGEX );
50
51
  private final static int GROUP_DELIMITED = 1;
43
  private static final int GROUP_DELIMITED = 1;
5244
5345
  /**
...
6052
   * Performs string interpolation on the values in the given map. This will
6153
   * change any value in the map that contains a variable that matches
62
   * {@link #REGEX_PATTERN}.
54
   * {@link YamlSigilOperator#REGEX_PATTERN}.
6355
   *
6456
   * @param map Contains values that represent references to keys.
M src/main/java/com/scrivenvar/definition/RootTreeItem.java
4444
 * @param <T> The type of {@link TreeItem} to store in the {@link TreeView}.
4545
 */
46
public class RootTreeItem<T> extends VariableTreeItem<T> {
46
public class RootTreeItem<T> extends DefinitionTreeItem<T> {
4747
  /**
4848
   * Default constructor, calls the superclass, no other behaviour.
M src/main/java/com/scrivenvar/definition/TreeItemAdapter.java
2929
3030
import com.fasterxml.jackson.databind.JsonNode;
31
import com.scrivenvar.decorators.YamlVariableDecorator;
31
import com.scrivenvar.sigils.YamlSigilOperator;
3232
import com.scrivenvar.preview.HTMLPreviewPane;
3333
import javafx.scene.control.TreeItem;
...
6161
public class TreeItemAdapter {
6262
  /**
63
   * Separates YAML variable nodes (e.g., the dots in {@code $root.node.var$}).
63
   * Separates YAML definition keys (e.g., the dots in {@code $root.node.var$}).
6464
   */
6565
  public static final String SEPARATOR = ".";
...
150150
    }
151151
152
    return YamlVariableDecorator.entoken( key.toString() );
152
    return YamlSigilOperator.entoken( key.toString() );
153153
  }
154154
}
D src/main/java/com/scrivenvar/definition/VariableTreeItem.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import javafx.scene.control.TreeItem;
31
32
import java.text.Normalizer;
33
import java.util.Stack;
34
import java.util.function.BiFunction;
35
36
import static java.text.Normalizer.Form.NFD;
37
38
/**
39
 * Provides behaviour afforded to variable names and their corresponding value.
40
 *
41
 * @param <T> The type of TreeItem (usually String).
42
 */
43
public class VariableTreeItem<T> extends TreeItem<T> {
44
45
  /**
46
   * Constructs a new item with a default value.
47
   *
48
   * @param value Passed up to superclass.
49
   */
50
  public VariableTreeItem( final T value ) {
51
    super( value );
52
  }
53
54
  /**
55
   * Finds a leaf starting at the current node with text that matches the given
56
   * value. Search is performed case-sensitively.
57
   *
58
   * @param text The text to match against each leaf in the tree.
59
   * @return The leaf that has a value exactly matching the given text.
60
   */
61
  public VariableTreeItem<T> findLeafExact( final String text ) {
62
    return findLeaf( text, VariableTreeItem::valueEquals );
63
  }
64
65
  /**
66
   * Finds a leaf starting at the current node with text that matches the given
67
   * value. Search is performed case-sensitively.
68
   *
69
   * @param text The text to match against each leaf in the tree.
70
   * @return The leaf that has a value that contains the given text.
71
   */
72
  public VariableTreeItem<T> findLeafContains( final String text ) {
73
    return findLeaf( text, VariableTreeItem::valueContains );
74
  }
75
76
  /**
77
   * Finds a leaf starting at the current node with text that matches the given
78
   * value. Search is performed case-insensitively.
79
   *
80
   * @param text The text to match against each leaf in the tree.
81
   * @return The leaf that has a value that contains the given text.
82
   */
83
  public VariableTreeItem<T> findLeafContainsNoCase( final String text ) {
84
    return findLeaf( text, VariableTreeItem::valueContainsNoCase );
85
  }
86
87
  /**
88
   * Finds a leaf starting at the current node with text that matches the given
89
   * value. Search is performed case-sensitively.
90
   *
91
   * @param text The text to match against each leaf in the tree.
92
   * @return The leaf that has a value that starts with the given text.
93
   */
94
  public VariableTreeItem<T> findLeafStartsWith( final String text ) {
95
    return findLeaf( text, VariableTreeItem::valueStartsWith );
96
  }
97
98
  /**
99
   * Finds a leaf starting at the current node with text that matches the given
100
   * value.
101
   *
102
   * @param text     The text to match against each leaf in the tree.
103
   * @param findMode What algorithm is used to match the given text.
104
   * @return The leaf that has a value starting with the given text, or {@code
105
   * null} if there was no match found.
106
   */
107
  public VariableTreeItem<T> findLeaf(
108
      final String text,
109
      final BiFunction<VariableTreeItem<T>, String, Boolean> findMode ) {
110
    final Stack<VariableTreeItem<T>> stack = new Stack<>();
111
    stack.push( this );
112
113
    // Don't hunt for blank (empty) keys.
114
    boolean found = text.isBlank();
115
116
    while( !found && !stack.isEmpty() ) {
117
      final VariableTreeItem<T> node = stack.pop();
118
119
      for( final TreeItem<T> child : node.getChildren() ) {
120
        final VariableTreeItem<T> result = (VariableTreeItem<T>) child;
121
122
        if( result.isLeaf() ) {
123
          if( found = findMode.apply( result, text ) ) {
124
            return result;
125
          }
126
        }
127
        else {
128
          stack.push( result );
129
        }
130
      }
131
    }
132
133
    return null;
134
  }
135
136
  /**
137
   * Returns the value of the string without diacritic marks.
138
   *
139
   * @return A non-null, possibly empty string.
140
   */
141
  private String getDiacriticlessValue() {
142
    return Normalizer.normalize( getValue().toString(), NFD )
143
                     .replaceAll( "\\p{M}", "" );
144
  }
145
146
  /**
147
   * Returns true if this node is a leaf and its value equals the given text.
148
   *
149
   * @param s The text to compare against the node value.
150
   * @return true Node is a leaf and its value equals the given value.
151
   */
152
  private boolean valueEquals( final String s ) {
153
    return isLeaf() && getValue().equals( s );
154
  }
155
156
  /**
157
   * Returns true if this node is a leaf and its value contains the given text.
158
   *
159
   * @param s The text to compare against the node value.
160
   * @return true Node is a leaf and its value contains the given value.
161
   */
162
  private boolean valueContains( final String s ) {
163
    return isLeaf() && getDiacriticlessValue().contains( s );
164
  }
165
166
  /**
167
   * Returns true if this node is a leaf and its value contains the given text.
168
   *
169
   * @param s The text to compare against the node value.
170
   * @return true Node is a leaf and its value contains the given value.
171
   */
172
  private boolean valueContainsNoCase( final String s ) {
173
    return isLeaf() && getDiacriticlessValue()
174
        .toLowerCase()
175
        .contains( s.toLowerCase() );
176
  }
177
178
  /**
179
   * Returns true if this node is a leaf and its value starts with the given
180
   * text.
181
   *
182
   * @param s The text to compare against the node value.
183
   * @return true Node is a leaf and its value starts with the given value.
184
   */
185
  private boolean valueStartsWith( final String s ) {
186
    return isLeaf() && getDiacriticlessValue().startsWith( s );
187
  }
188
189
  /**
190
   * Returns the path for this node, with nodes made distinct using the
191
   * separator character. This uses two loops: one for pushing nodes onto a
192
   * stack and one for popping them off to create the path in desired order.
193
   *
194
   * @return A non-null string, possibly empty.
195
   */
196
  public String toPath() {
197
    return TreeItemAdapter.toPath( getParent() );
198
  }
199
}
2001
M src/main/java/com/scrivenvar/definition/yaml/YamlTreeAdapter.java
3333
import com.scrivenvar.definition.RootTreeItem;
3434
import com.scrivenvar.definition.TreeAdapter;
35
import com.scrivenvar.definition.VariableTreeItem;
35
import com.scrivenvar.definition.DefinitionTreeItem;
3636
import javafx.scene.control.TreeItem;
3737
import javafx.scene.control.TreeView;
...
164164
   */
165165
  private TreeItem<String> createTreeItem( final String value ) {
166
    return new VariableTreeItem<>( value );
166
    return new DefinitionTreeItem<>( value );
167167
  }
168168
A src/main/java/com/scrivenvar/editors/DefinitionDecoratorFactory.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editors;
29
30
import com.scrivenvar.AbstractFileFactory;
31
import com.scrivenvar.sigils.RSigilOperator;
32
import com.scrivenvar.sigils.SigilOperator;
33
import com.scrivenvar.sigils.YamlSigilOperator;
34
35
import java.nio.file.Path;
36
37
/**
38
 * Responsible for creating a definition name decorator suited to a particular
39
 * file type.
40
 */
41
public class DefinitionDecoratorFactory extends AbstractFileFactory {
42
43
  private DefinitionDecoratorFactory() {
44
  }
45
46
  public static SigilOperator newInstance( final Path path ) {
47
    final var factory = new DefinitionDecoratorFactory();
48
49
    return switch( factory.lookup( path ) ) {
50
      case RMARKDOWN, RXML -> new RSigilOperator();
51
      default -> new YamlSigilOperator();
52
    };
53
  }
54
}
155
A src/main/java/com/scrivenvar/editors/DefinitionNameInjector.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editors;
29
30
import com.scrivenvar.FileEditorTab;
31
import com.scrivenvar.definition.DefinitionPane;
32
import com.scrivenvar.definition.DefinitionTreeItem;
33
import com.scrivenvar.sigils.SigilOperator;
34
import javafx.scene.control.TreeItem;
35
import javafx.scene.input.KeyEvent;
36
import org.fxmisc.richtext.StyledTextArea;
37
38
import java.nio.file.Path;
39
import java.text.BreakIterator;
40
41
import static com.scrivenvar.Constants.*;
42
import static com.scrivenvar.StatusBarNotifier.alert;
43
import static java.lang.Character.isWhitespace;
44
import static javafx.scene.input.KeyCode.SPACE;
45
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
46
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
47
48
/**
49
 * Provides the logic for injecting variable names within the editor.
50
 */
51
public final class DefinitionNameInjector {
52
53
  /**
54
   * Recipient of name injections.
55
   */
56
  private FileEditorTab mTab;
57
58
  /**
59
   * Initiates double-click events.
60
   */
61
  private final DefinitionPane mDefinitionPane;
62
63
  /**
64
   * Initializes the variable name injector against the given pane.
65
   *
66
   * @param pane The definition panel to listen to for double-click events.
67
   */
68
  public DefinitionNameInjector( final DefinitionPane pane ) {
69
    mDefinitionPane = pane;
70
  }
71
72
  /**
73
   * Trap Control+Space.
74
   *
75
   * @param tab Editor where variable names get injected.
76
   */
77
  public void addListener( final FileEditorTab tab ) {
78
    assert tab != null;
79
    mTab = tab;
80
81
    tab.getEditorPane().addKeyboardListener(
82
        keyPressed( SPACE, CONTROL_DOWN ),
83
        this::autoinsert
84
    );
85
  }
86
87
  /**
88
   * Inserts the currently selected variable from the {@link DefinitionPane}.
89
   */
90
  public void injectSelectedItem() {
91
    final var pane = getDefinitionPane();
92
    final TreeItem<String> item = pane.getSelectedItem();
93
94
    if( item.isLeaf() ) {
95
      final var leaf = pane.findLeafExact( item.getValue() );
96
      final var editor = getEditor();
97
98
      editor.insertText( editor.getCaretPosition(), decorate( leaf ) );
99
    }
100
  }
101
102
  /**
103
   * Pressing Control+SPACE will find a node that matches the current word and
104
   * substitute the definition reference.
105
   */
106
  private void autoinsert() {
107
    final String paragraph = getCaretParagraph();
108
    final int[] bounds = getWordBoundariesAtCaret();
109
110
    try {
111
      if( isEmptyDefinitionPane() ) {
112
        alert( STATUS_DEFINITION_EMPTY );
113
      }
114
      else {
115
        final String word = paragraph.substring( bounds[ 0 ], bounds[ 1 ] );
116
117
        if( word.isBlank() ) {
118
          alert( STATUS_DEFINITION_BLANK );
119
        }
120
        else {
121
          final var leaf = findLeaf( word );
122
123
          if( leaf == null ) {
124
            alert( STATUS_DEFINITION_MISSING, word );
125
          }
126
          else {
127
            replaceText( bounds[ 0 ], bounds[ 1 ], decorate( leaf ) );
128
            expand( leaf );
129
          }
130
        }
131
      }
132
    } catch( final Exception ignored ) {
133
      alert( STATUS_DEFINITION_BLANK );
134
    }
135
  }
136
137
  /**
138
   * Pressing Control+SPACE will find a node that matches the current word and
139
   * substitute the definition reference.
140
   *
141
   * @param e Ignored -- it can only be Control+SPACE.
142
   */
143
  private void autoinsert( final KeyEvent e ) {
144
    autoinsert();
145
  }
146
147
  /**
148
   * Finds the start and end indexes for the word in the current paragraph
149
   * where the caret is located. There are a few different scenarios, where
150
   * the caret can be at: the start, end, or middle of a word; also, the
151
   * caret can be at the end or beginning of a punctuated word.
152
   */
153
  private int[] getWordBoundariesAtCaret() {
154
    final var paragraph = getCaretParagraph();
155
    final var length = paragraph.length();
156
    int offset = getCurrentCaretColumn();
157
158
    int began = offset;
159
    int ended = offset;
160
161
    while( began > 0 && !isWhitespace( paragraph.charAt( began - 1 ) ) ) {
162
      began--;
163
    }
164
165
    while( ended < length && !isWhitespace( paragraph.charAt( ended ) ) ) {
166
      ended++;
167
    }
168
169
    final var iterator = BreakIterator.getWordInstance();
170
    iterator.setText( paragraph );
171
172
    while( began < length && iterator.isBoundary( began + 1 ) ) {
173
      began++;
174
    }
175
176
    while( ended > 0 && iterator.isBoundary( ended - 1 ) ) {
177
      ended--;
178
    }
179
180
    return new int[]{began, ended};
181
  }
182
183
  /**
184
   * Decorates a {@link TreeItem} using the syntax specific to the type of
185
   * document being edited.
186
   *
187
   * @param leaf The path to the leaf (the definition key) to be decorated.
188
   */
189
  private String decorate( final DefinitionTreeItem<String> leaf ) {
190
    return decorate( leaf.toPath() );
191
  }
192
193
  /**
194
   * Decorates a variable using the syntax specific to the type of document
195
   * being edited.
196
   *
197
   * @param variable The variable to decorate in dot-notation without any
198
   *                 start or end sigils present.
199
   */
200
  private String decorate( final String variable ) {
201
    return getVariableDecorator().apply( variable );
202
  }
203
204
  /**
205
   * Updates the text at the given position within the current paragraph.
206
   *
207
   * @param posBegan The starting index in the paragraph text to replace.
208
   * @param posEnded The ending index in the paragraph text to replace.
209
   * @param text     Overwrite the paragraph substring with this text.
210
   */
211
  private void replaceText(
212
      final int posBegan, final int posEnded, final String text ) {
213
    final int p = getCurrentParagraph();
214
215
    getEditor().replaceText( p, posBegan, p, posEnded, text );
216
  }
217
218
  /**
219
   * Returns the caret's current paragraph position.
220
   *
221
   * @return A number greater than or equal to 0.
222
   */
223
  private int getCurrentParagraph() {
224
    return getEditor().getCurrentParagraph();
225
  }
226
227
  /**
228
   * Returns the text for the paragraph that contains the caret.
229
   *
230
   * @return A non-null string, possibly empty.
231
   */
232
  private String getCaretParagraph() {
233
    return getEditor().getText( getCurrentParagraph() );
234
  }
235
236
  /**
237
   * Returns the caret position within the current paragraph.
238
   *
239
   * @return A value from 0 to the length of the current paragraph.
240
   */
241
  private int getCurrentCaretColumn() {
242
    return getEditor().getCaretColumn();
243
  }
244
245
  /**
246
   * Looks for the given word, matching first by exact, next by a starts-with
247
   * condition with diacritics replaced, then by containment.
248
   *
249
   * @param word The word to match by: exact, at the beginning, or containment.
250
   * @return The matching {@link DefinitionTreeItem} for the given word, or
251
   * {@code null} if none found.
252
   */
253
  @SuppressWarnings("ConstantConditions")
254
  private DefinitionTreeItem<String> findLeaf( final String word ) {
255
    assert word != null;
256
257
    final var pane = getDefinitionPane();
258
    DefinitionTreeItem<String> leaf = null;
259
260
    leaf = leaf == null ? pane.findLeafExact( word ) : leaf;
261
    leaf = leaf == null ? pane.findLeafStartsWith( word ) : leaf;
262
    leaf = leaf == null ? pane.findLeafContains( word ) : leaf;
263
    leaf = leaf == null ? pane.findLeafContainsNoCase( word ) : leaf;
264
265
    return leaf;
266
  }
267
268
  /**
269
   * Answers whether there are any definitions in the tree.
270
   *
271
   * @return {@code true} when there are no definitions; {@code false} when
272
   * there's at least one definition.
273
   */
274
  private boolean isEmptyDefinitionPane() {
275
    return getDefinitionPane().isEmpty();
276
  }
277
278
  /**
279
   * Collapses the tree then expands and selects the given node.
280
   *
281
   * @param node The node to expand.
282
   */
283
  private void expand( final TreeItem<String> node ) {
284
    final DefinitionPane pane = getDefinitionPane();
285
    pane.collapse();
286
    pane.expand( node );
287
    pane.select( node );
288
  }
289
290
  /**
291
   * @return A variable decorator that corresponds to the given file type.
292
   */
293
  private SigilOperator getVariableDecorator() {
294
    return DefinitionDecoratorFactory.newInstance( getFilename() );
295
  }
296
297
  private Path getFilename() {
298
    return getFileEditorTab().getPath();
299
  }
300
301
  private EditorPane getEditorPane() {
302
    return getFileEditorTab().getEditorPane();
303
  }
304
305
  private StyledTextArea<?, ?> getEditor() {
306
    return getEditorPane().getEditor();
307
  }
308
309
  public FileEditorTab getFileEditorTab() {
310
    return mTab;
311
  }
312
313
  private DefinitionPane getDefinitionPane() {
314
    return mDefinitionPane;
315
  }
316
}
1317
M src/main/java/com/scrivenvar/editors/EditorPane.java
4646
import java.util.function.Consumer;
4747
48
import static com.scrivenvar.StatusBarNotifier.clearAlert;
4849
import static java.lang.String.format;
4950
import static javafx.application.Platform.runLater;
5051
import static org.fxmisc.wellbehaved.event.InputMap.consume;
5152
5253
/**
5354
 * Represents common editing features for various types of text editors.
5455
 */
5556
public class EditorPane extends Pane {
5657
57
  private final static Options sOptions = Services.load( Options.class );
58
  private static final Options sOptions = Services.load( Options.class );
5859
5960
  /**
...
7273
    fontsSizeProperty().addListener(
7374
        ( l, o, n ) -> setFontSize( n.intValue() )
75
    );
76
77
    // Clear out any previous alerts after the user has typed. If the problem
78
    // persists, re-rendering the document will re-raise the error. If there
79
    // was no previous error, clearing the alert is essentially a no-op.
80
    mEditor.textProperty().addListener(
81
        ( l, o, n ) -> clearAlert()
7482
    );
7583
  }
D src/main/java/com/scrivenvar/editors/VariableNameDecoratorFactory.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editors;
29
30
import com.scrivenvar.AbstractFileFactory;
31
import com.scrivenvar.decorators.RVariableDecorator;
32
import com.scrivenvar.decorators.VariableDecorator;
33
import com.scrivenvar.decorators.YamlVariableDecorator;
34
import java.nio.file.Path;
35
36
/**
37
 * Responsible for creating a variable name decorator suited to a particular
38
 * file type.
39
 */
40
public class VariableNameDecoratorFactory extends AbstractFileFactory {
41
42
  private VariableNameDecoratorFactory() {
43
  }
44
45
  public static VariableDecorator newInstance( final Path path ) {
46
    final var factory = new VariableNameDecoratorFactory();
47
    final VariableDecorator result;
48
49
    switch( factory.lookup( path ) ) {
50
      case RMARKDOWN:
51
      case RXML:
52
        result = new RVariableDecorator();
53
        break;
54
55
      default:
56
        result = new YamlVariableDecorator();
57
        break;
58
    }
59
60
    return result;
61
  }
62
}
631
D src/main/java/com/scrivenvar/editors/VariableNameInjector.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editors;
29
30
import com.scrivenvar.FileEditorTab;
31
import com.scrivenvar.decorators.VariableDecorator;
32
import com.scrivenvar.definition.DefinitionPane;
33
import com.scrivenvar.definition.VariableTreeItem;
34
import javafx.scene.control.TreeItem;
35
import javafx.scene.input.KeyEvent;
36
import org.fxmisc.richtext.StyledTextArea;
37
38
import java.nio.file.Path;
39
import java.text.BreakIterator;
40
41
import static javafx.scene.input.KeyCode.SPACE;
42
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
43
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
44
45
/**
46
 * Provides the logic for injecting variable names within the editor.
47
 */
48
public final class VariableNameInjector {
49
50
  /**
51
   * Recipient of name injections.
52
   */
53
  private FileEditorTab mTab;
54
55
  /**
56
   * Initiates double-click events.
57
   */
58
  private final DefinitionPane mDefinitionPane;
59
60
  /**
61
   * Initializes the variable name injector against the given pane.
62
   *
63
   * @param pane The definition panel to listen to for double-click events.
64
   */
65
  public VariableNameInjector( final DefinitionPane pane ) {
66
    mDefinitionPane = pane;
67
  }
68
69
  /**
70
   * Trap Control+Space.
71
   *
72
   * @param tab Editor where variable names get injected.
73
   */
74
  public void addListener( final FileEditorTab tab ) {
75
    assert tab != null;
76
    mTab = tab;
77
78
    tab.getEditorPane().addKeyboardListener(
79
        keyPressed( SPACE, CONTROL_DOWN ),
80
        this::autoinsert
81
    );
82
  }
83
84
  /**
85
   * Inserts the currently selected variable from the {@link DefinitionPane}.
86
   */
87
  public void injectSelectedItem() {
88
    final var pane = getDefinitionPane();
89
    final TreeItem<String> item = pane.getSelectedItem();
90
91
    if( item.isLeaf() ) {
92
      final var leaf = pane.findLeafExact( item.getValue() );
93
      final var editor = getEditor();
94
95
      editor.insertText( editor.getCaretPosition(), decorate( leaf ) );
96
    }
97
  }
98
99
  /**
100
   * Pressing Control+SPACE will find a node that matches the current word and
101
   * substitute the YAML variable reference.
102
   *
103
   * @param e Ignored -- it can only be Control+SPACE.
104
   */
105
  private void autoinsert( final KeyEvent e ) {
106
    final String paragraph = getCaretParagraph();
107
    final int[] boundaries = getWordBoundariesAtCaret();
108
    final String word = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] );
109
    final VariableTreeItem<String> leaf = findLeaf( word );
110
111
    if( leaf != null ) {
112
      replaceText( boundaries[ 0 ], boundaries[ 1 ], decorate( leaf ) );
113
      expand( leaf );
114
    }
115
  }
116
117
  private int[] getWordBoundariesAtCaret() {
118
    final String paragraph = getCaretParagraph();
119
    int offset = getCurrentCaretColumn();
120
121
    final BreakIterator wordBreaks = BreakIterator.getWordInstance();
122
    wordBreaks.setText( paragraph );
123
124
    // Scan back until the first word is found.
125
    while( offset > 0 && wordBreaks.isBoundary( offset ) ) {
126
      offset--;
127
    }
128
129
    final int[] boundaries = new int[ 2 ];
130
    boundaries[ 1 ] = wordBreaks.following( offset );
131
    boundaries[ 0 ] = wordBreaks.previous();
132
133
    return boundaries;
134
  }
135
136
  /**
137
   * Decorates a {@link TreeItem} using the syntax specific to the type of
138
   * document being edited.
139
   *
140
   * @param leaf The path to the leaf (the definition key) to be decorated.
141
   */
142
  private String decorate( final VariableTreeItem<String> leaf ) {
143
    return decorate( leaf.toPath() );
144
  }
145
146
  /**
147
   * Decorates a variable using the syntax specific to the type of document
148
   * being edited.
149
   *
150
   * @param variable The variable to decorate in dot-notation without any
151
   *                 start or end sigils present.
152
   */
153
  private String decorate( final String variable ) {
154
    return getVariableDecorator().decorate( variable );
155
  }
156
157
  /**
158
   * Updates the text at the given position within the current paragraph.
159
   *
160
   * @param posBegan The starting index in the paragraph text to replace.
161
   * @param posEnded The ending index in the paragraph text to replace.
162
   * @param text     Overwrite the paragraph substring with this text.
163
   */
164
  private void replaceText(
165
      final int posBegan, final int posEnded, final String text ) {
166
    final int p = getCurrentParagraph();
167
168
    getEditor().replaceText( p, posBegan, p, posEnded, text );
169
  }
170
171
  /**
172
   * Returns the caret's current paragraph position.
173
   *
174
   * @return A number greater than or equal to 0.
175
   */
176
  private int getCurrentParagraph() {
177
    return getEditor().getCurrentParagraph();
178
  }
179
180
  /**
181
   * Returns the text for the paragraph that contains the caret.
182
   *
183
   * @return A non-null string, possibly empty.
184
   */
185
  private String getCaretParagraph() {
186
    return getEditor().getText( getCurrentParagraph() );
187
  }
188
189
  /**
190
   * Returns the caret position within the current paragraph.
191
   *
192
   * @return A value from 0 to the length of the current paragraph.
193
   */
194
  private int getCurrentCaretColumn() {
195
    return getEditor().getCaretColumn();
196
  }
197
198
  /**
199
   * Looks for the given word, matching first by exact, next by a starts-with
200
   * condition with diacritics replaced, then by containment.
201
   *
202
   * @param word
203
   * @return
204
   */
205
  @SuppressWarnings("ConstantConditions")
206
  private VariableTreeItem<String> findLeaf( final String word ) {
207
    assert word != null;
208
209
    final var pane = getDefinitionPane();
210
    VariableTreeItem<String> leaf = null;
211
212
    leaf = leaf == null ? pane.findLeafExact( word ) : leaf;
213
    leaf = leaf == null ? pane.findLeafStartsWith( word ) : leaf;
214
    leaf = leaf == null ? pane.findLeafContains( word ) : leaf;
215
    leaf = leaf == null ? pane.findLeafContainsNoCase( word ) : leaf;
216
217
    return leaf;
218
  }
219
220
  /**
221
   * Collapses the tree then expands and selects the given node.
222
   *
223
   * @param node The node to expand.
224
   */
225
  private void expand( final TreeItem<String> node ) {
226
    final DefinitionPane pane = getDefinitionPane();
227
    pane.collapse();
228
    pane.expand( node );
229
    pane.select( node );
230
  }
231
232
  /**
233
   * @return A variable decorator that corresponds to the given file type.
234
   */
235
  private VariableDecorator getVariableDecorator() {
236
    return VariableNameDecoratorFactory.newInstance( getFilename() );
237
  }
238
239
  private Path getFilename() {
240
    return getFileEditorTab().getPath();
241
  }
242
243
  private EditorPane getEditorPane() {
244
    return getFileEditorTab().getEditorPane();
245
  }
246
247
  private StyledTextArea<?, ?> getEditor() {
248
    return getEditorPane().getEditor();
249
  }
250
251
  public FileEditorTab getFileEditorTab() {
252
    return mTab;
253
  }
254
255
  private DefinitionPane getDefinitionPane() {
256
    return mDefinitionPane;
257
  }
258
}
2591
D src/main/java/com/scrivenvar/graphics/RenderingSettings.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.graphics;
29
30
import java.util.HashMap;
31
import java.util.Map;
32
33
import static java.awt.RenderingHints.*;
34
import static java.awt.Toolkit.getDefaultToolkit;
35
36
/**
37
 * Responsible for supplying consistent rendering hints throughout the
38
 * application, such as image rendering for {@link SVGRasterizer}.
39
 */
40
@SuppressWarnings("rawtypes")
41
public class RenderingSettings {
42
43
  /**
44
   * Default hints for high-quality rendering that may be changed by
45
   * the system's rendering hints.
46
   */
47
  private final static Map<Object, Object> DEFAULT_HINTS = Map.of(
48
      KEY_ANTIALIASING,
49
      VALUE_ANTIALIAS_ON,
50
      KEY_ALPHA_INTERPOLATION,
51
      VALUE_ALPHA_INTERPOLATION_QUALITY,
52
      KEY_COLOR_RENDERING,
53
      VALUE_COLOR_RENDER_QUALITY,
54
      KEY_DITHERING,
55
      VALUE_DITHER_DISABLE,
56
      KEY_FRACTIONALMETRICS,
57
      VALUE_FRACTIONALMETRICS_ON,
58
      KEY_INTERPOLATION,
59
      VALUE_INTERPOLATION_BICUBIC,
60
      KEY_RENDERING,
61
      VALUE_RENDER_QUALITY,
62
      KEY_STROKE_CONTROL,
63
      VALUE_STROKE_PURE,
64
      KEY_TEXT_ANTIALIASING,
65
      VALUE_TEXT_ANTIALIAS_ON
66
  );
67
68
  /**
69
   * Shared hints for high-quality rendering.
70
   */
71
  public final static Map<Object, Object> RENDERING_HINTS = new HashMap<>(
72
      DEFAULT_HINTS
73
  );
74
75
  static {
76
    final var toolkit = getDefaultToolkit();
77
    final var hints = toolkit.getDesktopProperty( "awt.font.desktophints" );
78
79
    if( hints instanceof Map ) {
80
      final var map = (Map) hints;
81
      for( final var key : map.keySet() ) {
82
        final var hint = map.get( key );
83
        RENDERING_HINTS.put( key, hint );
84
      }
85
    }
86
  }
87
88
  /**
89
   * Prevent instantiation as per Joshua Bloch's recommendation.
90
   */
91
  private RenderingSettings() {
92
  }
93
}
941
D src/main/java/com/scrivenvar/graphics/SVGRasterizer.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.graphics;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.events.Notifier;
32
import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
33
import org.apache.batik.gvt.renderer.ImageRenderer;
34
import org.apache.batik.transcoder.TranscoderException;
35
import org.apache.batik.transcoder.TranscoderInput;
36
import org.apache.batik.transcoder.TranscoderOutput;
37
import org.apache.batik.transcoder.image.ImageTranscoder;
38
import org.apache.batik.util.XMLResourceDescriptor;
39
import org.w3c.dom.Document;
40
41
import java.awt.*;
42
import java.awt.image.BufferedImage;
43
import java.io.IOException;
44
import java.io.StringReader;
45
import java.net.URL;
46
47
import static com.scrivenvar.graphics.RenderingSettings.RENDERING_HINTS;
48
import static java.awt.Color.WHITE;
49
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
50
import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH;
51
import static org.apache.batik.transcoder.image.ImageTranscoder.KEY_BACKGROUND_COLOR;
52
import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName;
53
54
/**
55
 * Responsible for converting SVG images into rasterized PNG images.
56
 */
57
public class SVGRasterizer {
58
  private final static Notifier NOTIFIER = Services.load( Notifier.class );
59
60
  private final static SAXSVGDocumentFactory mFactory =
61
      new SAXSVGDocumentFactory( getXMLParserClassName() );
62
63
  public final static Image BROKEN_IMAGE_PLACEHOLDER;
64
65
  static {
66
    // A FontAwesome camera icon, cleft asunder.
67
    final String BROKEN_IMAGE_SVG = "<svg height='19pt' viewBox='0 0 25 19' " +
68
        "width='25pt' xmlns='http://www.w3.org/2000/svg'><g " +
69
        "fill='#454545'><path d='m8.042969 11.085938c.332031 1.445312 1" +
70
        ".660156 2.503906 3.214843 2.558593zm0 0'/><path d='m6.792969 9" +
71
        ".621094-.300781.226562.242187.195313c.015625-.144531.03125-.28125" +
72
        ".058594-.421875zm0 0'/><path d='m10.597656.949219-2.511718.207031c-" +
73
        ".777344.066406-1.429688.582031-1.636719 1.292969l-.367188 1.253906-3" +
74
        ".414062.28125c-1.027344.085937-1.792969.949219-1.699219 1.925781l" +
75
        ".976562 10.621094c.089844.976562.996094 1.699219 2.023438 1" +
76
        ".613281l11.710938-.972656-3.117188-2.484375c-.246094.0625-.5.109375-" +
77
        ".765625.132812-2.566406.210938-4.835937-1.597656-5.0625-4.039062-" +
78
        ".023437-.25-.019531-.496094 0-.738281l-.242187-.195313.300781-" +
79
        ".226562c.359375-1.929688 2.039062-3.472656 4.191406-3.652344.207031-" +
80
        ".015625.414063-.015625.617187-.007812l.933594-.707032zm0 0'/><path " +
81
        "d='m10.234375 11.070312 2.964844 2.820313c.144531.015625.285156" +
82
        ".027344.433593.027344 1.890626 0 3.429688-1.460938 3.429688-3.257813" +
83
        " 0-1.792968-1.539062-3.257812-3.429688-3.257812-1.890624 0-3.429687 " +
84
        "1.464844-3.429687 3.257812 0 .140625.011719.277344.03125.410156zm0 " +
85
        "0'/><path d='m14.488281.808594 1.117188 4.554687-1.042969.546875c2" +
86
        ".25.476563 3.84375 2.472656 3.636719 4.714844-.199219 2.191406-2" +
87
        ".050781 3.871094-4.285157 4.039062l2.609376 2.957032 4.4375.371094c1" +
88
        ".03125.085937 1.9375-.640626 2.027343-1.617188l.976563-10.617188c" +
89
        ".089844-.980468-.667969-1.839843-1.699219-1.925781l-3.414063-" +
90
        ".285156-.371093-1.253906c-.207031-.710938-.859375-1.226563-1" +
91
        ".636719-1.289063zm0 0'/></g></svg>";
92
93
    // The width and height cannot be embedded in the SVG above because the
94
    // path element values are relative to the viewBox dimensions.
95
    final int w = 75;
96
    final int h = 75;
97
    Image image;
98
99
    try( final StringReader reader = new StringReader( BROKEN_IMAGE_SVG ) ) {
100
      final String parser = XMLResourceDescriptor.getXMLParserClassName();
101
      final SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory( parser );
102
      final Document document = factory.createDocument( "", reader );
103
104
      image = rasterize( document, w );
105
    } catch( final Exception e ) {
106
      image = new BufferedImage( w, h, TYPE_INT_RGB );
107
      final var graphics = (Graphics2D) image.getGraphics();
108
      graphics.setRenderingHints( RENDERING_HINTS );
109
110
      // Fall back to a (\) symbol.
111
      graphics.setColor( new Color( 204, 204, 204 ) );
112
      graphics.fillRect( 0, 0, w, h );
113
      graphics.setColor( new Color( 255, 204, 204 ) );
114
      graphics.setStroke( new BasicStroke( 4 ) );
115
      graphics.drawOval( w / 4, h / 4, w / 2, h / 2 );
116
      graphics.drawLine( w / 4 + (int) (w / 4 / Math.PI),
117
                         h / 4 + (int) (w / 4 / Math.PI),
118
                         w / 2 + w / 4 - (int) (w / 4 / Math.PI),
119
                         h / 2 + h / 4 - (int) (w / 4 / Math.PI) );
120
    }
121
122
    BROKEN_IMAGE_PLACEHOLDER = image;
123
  }
124
125
  private static class BufferedImageTranscoder extends ImageTranscoder {
126
    private BufferedImage mImage;
127
128
    @Override
129
    public BufferedImage createImage( final int w, final int h ) {
130
      return new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB );
131
    }
132
133
    @Override
134
    public void writeImage(
135
        final BufferedImage image, final TranscoderOutput output ) {
136
      mImage = image;
137
    }
138
139
    public Image getImage() {
140
      return mImage;
141
    }
142
143
    @Override
144
    protected ImageRenderer createRenderer() {
145
      final ImageRenderer renderer = super.createRenderer();
146
      final RenderingHints hints = renderer.getRenderingHints();
147
      hints.putAll( RENDERING_HINTS );
148
149
      renderer.setRenderingHints( hints );
150
151
      return renderer;
152
    }
153
  }
154
155
  /**
156
   * Rasterizes the vector graphic file at the given URL. If any exception
157
   * happens, a red circle is returned instead.
158
   *
159
   * @param url   The URL to a vector graphic file, which must include the
160
   *              protocol scheme (such as file:// or https://).
161
   * @param width The number of pixels wide to render the image. The aspect
162
   *              ratio is maintained.
163
   * @return Either the rasterized image upon success or a red circle.
164
   */
165
  public static Image rasterize( final String url, final int width ) {
166
    try {
167
      return rasterize( new URL( url ), width );
168
    } catch( final Exception e ) {
169
      NOTIFIER.notify( e );
170
      return BROKEN_IMAGE_PLACEHOLDER;
171
    }
172
  }
173
174
  /**
175
   * Converts an SVG drawing into a rasterized image that can be drawn on
176
   * a graphics context.
177
   *
178
   * @param url   The path to the image (can be web address).
179
   * @param width Scale the image width to this size (aspect ratio is
180
   *              maintained).
181
   * @return The vector graphic transcoded into a raster image format.
182
   * @throws IOException         Could not read the vector graphic.
183
   * @throws TranscoderException Could not convert the vector graphic to an
184
   *                             instance of {@link Image}.
185
   */
186
  public static Image rasterize( final URL url, final int width )
187
      throws IOException, TranscoderException {
188
    return rasterize(
189
        mFactory.createDocument( url.toString() ), width );
190
  }
191
192
  public static Image rasterize(
193
      final Document svg, final int width ) throws TranscoderException {
194
    final var transcoder = new BufferedImageTranscoder();
195
    final var input = new TranscoderInput( svg );
196
197
    transcoder.addTranscodingHint( KEY_BACKGROUND_COLOR, WHITE );
198
    transcoder.addTranscodingHint( KEY_WIDTH, (float) width );
199
    transcoder.transcode( input, null );
200
201
    return transcoder.getImage();
202
  }
203
}
2041
D src/main/java/com/scrivenvar/graphics/SVGReplacedElementFactory.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.graphics;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.events.Notifier;
32
import org.apache.commons.io.FilenameUtils;
33
import org.w3c.dom.Element;
34
import org.xhtmlrenderer.extend.ReplacedElement;
35
import org.xhtmlrenderer.extend.ReplacedElementFactory;
36
import org.xhtmlrenderer.extend.UserAgentCallback;
37
import org.xhtmlrenderer.layout.LayoutContext;
38
import org.xhtmlrenderer.render.BlockBox;
39
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;
40
import org.xhtmlrenderer.swing.ImageReplacedElement;
41
42
import java.awt.*;
43
import java.util.LinkedHashMap;
44
import java.util.Map;
45
46
import static com.scrivenvar.graphics.SVGRasterizer.rasterize;
47
48
/**
49
 * Responsible for running {@link SVGRasterizer} on SVG images detected within
50
 * a document to transform them into rasterized versions.
51
 */
52
public class SVGReplacedElementFactory
53
    implements ReplacedElementFactory {
54
55
  private final static Notifier sNotifier = Services.load( Notifier.class );
56
57
  /**
58
   * SVG filename extension.
59
   */
60
  private static final String SVG_FILE = "svg";
61
  private static final String HTML_IMAGE = "img";
62
  private static final String HTML_IMAGE_SRC = "src";
63
64
  /**
65
   * Constrain memory.
66
   */
67
  private static final int MAX_CACHED_IMAGES = 100;
68
69
  /**
70
   * Where to put cached image files.
71
   */
72
  private final Map<String, Image> mImageCache = new LinkedHashMap<>() {
73
    @Override
74
    protected boolean removeEldestEntry(
75
        final Map.Entry<String, Image> eldest ) {
76
      return size() > MAX_CACHED_IMAGES;
77
    }
78
  };
79
80
  @Override
81
  public ReplacedElement createReplacedElement(
82
      final LayoutContext c,
83
      final BlockBox box,
84
      final UserAgentCallback uac,
85
      final int cssWidth,
86
      final int cssHeight ) {
87
    final Element e = box.getElement();
88
89
    if( e != null ) {
90
      final String nodeName = e.getNodeName();
91
92
      if( HTML_IMAGE.equals( nodeName ) ) {
93
        final String src = e.getAttribute( HTML_IMAGE_SRC );
94
        final String ext = FilenameUtils.getExtension( src );
95
96
        if( SVG_FILE.equalsIgnoreCase( ext ) ) {
97
          try {
98
            final int width = box.getContentWidth();
99
            final Image image = getImage( src, width );
100
101
            final int w = image.getWidth( null );
102
            final int h = image.getHeight( null );
103
104
            return new ImageReplacedElement( image, w, h );
105
          } catch( final Exception ex ) {
106
            getNotifier().notify( ex );
107
          }
108
        }
109
      }
110
    }
111
112
    return null;
113
  }
114
115
  @Override
116
  public void reset() {
117
  }
118
119
  @Override
120
  public void remove( final Element e ) {
121
  }
122
123
  @Override
124
  public void setFormSubmissionListener( FormSubmissionListener listener ) {
125
  }
126
127
  private Image getImage( final String src, final int width ) {
128
    return mImageCache.computeIfAbsent( src, v -> rasterize( src, width ) );
129
  }
130
131
  private Notifier getNotifier() {
132
    return sNotifier;
133
  }
134
}
1351
M src/main/java/com/scrivenvar/preferences/FilePreferences.java
2828
package com.scrivenvar.preferences;
2929
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.events.Notifier;
32
3330
import java.io.File;
3431
import java.io.FileInputStream;
3532
import java.io.FileOutputStream;
3633
import java.util.*;
3734
import java.util.prefs.AbstractPreferences;
3835
import java.util.prefs.BackingStoreException;
36
37
import static com.scrivenvar.StatusBarNotifier.alert;
3938
4039
/**
...
4746
 */
4847
public class FilePreferences extends AbstractPreferences {
49
  private final Notifier mNotifier = Services.load( Notifier.class );
5048
5149
  private final Map<String, String> mRoot = new TreeMap<>();
...
6260
      sync();
6361
    } catch( final BackingStoreException ex ) {
64
      error( ex );
62
      alert( ex );
6563
    }
6664
  }
...
7573
      flush();
7674
    } catch( final BackingStoreException ex ) {
77
      error( ex );
75
      alert( ex );
7876
    }
7977
  }
...
9593
      flush();
9694
    } catch( final BackingStoreException ex ) {
97
      error( ex );
95
      alert( ex );
9896
    }
9997
  }
...
163161
        }
164162
      } catch( final Exception ex ) {
165
        error( new BackingStoreException( ex ) );
163
        alert( ex );
166164
      }
167165
    }
...
223221
        }
224222
      } catch( final Exception ex ) {
225
        error( new BackingStoreException( ex ) );
223
        alert( ex );
226224
      }
227225
    }
228
  }
229
230
  private void error( final BackingStoreException ex ) {
231
    getNotifier().notify( ex );
232
  }
233
234
  private Notifier getNotifier() {
235
    return mNotifier;
236226
  }
237227
}
M src/main/java/com/scrivenvar/preferences/UserPreferences.java
5959
  private final StringProperty mRDelimiterBegan;
6060
  private final StringProperty mRDelimiterEnded;
61
  private final StringProperty mDefDelimiterBegan;
62
  private final StringProperty mDefDelimiterEnded;
6163
  private final IntegerProperty mPropFontsSizeEditor;
6264
...
7274
    );
7375
74
    mRDelimiterBegan = new SimpleStringProperty( R_DELIMITER_BEGAN_DEFAULT );
75
    mRDelimiterEnded = new SimpleStringProperty( R_DELIMITER_ENDED_DEFAULT );
76
    mDefDelimiterBegan = new SimpleStringProperty( DEF_DELIM_BEGAN_DEFAULT );
77
    mDefDelimiterEnded = new SimpleStringProperty( DEF_DELIM_ENDED_DEFAULT );
78
79
    mRDelimiterBegan = new SimpleStringProperty( R_DELIM_BEGAN_DEFAULT );
80
    mRDelimiterEnded = new SimpleStringProperty( R_DELIM_ENDED_DEFAULT );
7681
7782
    mPropFontsSizeEditor = new SimpleIntegerProperty( FONT_SIZE_EDITOR );
...
8590
   */
8691
  public void show() {
87
    mPreferencesFx.show( false );
92
    getPreferencesFx().show( false );
8893
  }
8994
9095
  /**
9196
   * Call to persist the settings. Strictly speaking, this could watch on
9297
   * all values for external changes then save automatically.
9398
   */
9499
  public void save() {
95
    mPreferencesFx.saveSettings();
100
    getPreferencesFx().saveSettings();
96101
  }
97102
...
156161
                Setting.of( label( "Preferences.definitions.path.desc" ) ),
157162
                Setting.of( "Path", mPropDefinitionPath, false )
163
            ),
164
            Group.of(
165
                get( "Preferences.definitions.delimiter.began" ),
166
                Setting.of( label( "Preferences.definitions.delimiter.began.desc" ) ),
167
                Setting.of( "Opening", mDefDelimiterBegan )
168
            ),
169
            Group.of(
170
                get( "Preferences.definitions.delimiter.ended" ),
171
                Setting.of( label( "Preferences.definitions.delimiter.ended.desc" ) ),
172
                Setting.of( "Closing", mDefDelimiterEnded )
158173
            )
159174
        ),
...
212227
      final EventHandler<? super PreferencesFxEvent> eventHandler ) {
213228
    final var eventType = PreferencesFxEvent.EVENT_PREFERENCES_SAVED;
214
    mPreferencesFx.addEventHandler( eventType, eventHandler );
229
    getPreferencesFx().addEventHandler( eventType, eventHandler );
215230
  }
216231
...
234249
  public Path getDefinitionPath() {
235250
    return definitionPathProperty().getValue().toPath();
251
  }
252
253
  private StringProperty defDelimiterBegan() {
254
    return mDefDelimiterBegan;
255
  }
256
257
  public String getDefDelimiterBegan() {
258
    return defDelimiterBegan().get();
259
  }
260
261
  private StringProperty defDelimiterEnded() {
262
    return mDefDelimiterEnded;
263
  }
264
265
  public String getDefDelimiterEnded() {
266
    return defDelimiterEnded().get();
236267
  }
237268
...
295326
  public int getFontsSizeEditor() {
296327
    return mPropFontsSizeEditor.intValue();
328
  }
329
330
  private PreferencesFx getPreferencesFx() {
331
    return mPreferencesFx;
297332
  }
298333
}
M src/main/java/com/scrivenvar/preview/ChainedReplacedElementFactory.java
2828
import org.xhtmlrenderer.render.BlockBox;
2929
30
import java.util.ArrayList;
31
import java.util.List;
30
import java.util.HashSet;
31
import java.util.Set;
3232
3333
public class ChainedReplacedElementFactory extends ReplacedElementAdapter {
34
  private final List<ReplacedElementFactory> mFactoryList = new ArrayList<>();
34
  private final Set<ReplacedElementFactory> mFactoryList = new HashSet<>();
3535
3636
  @Override
M src/main/java/com/scrivenvar/preview/CustomImageLoader.java
2828
package com.scrivenvar.preview;
2929
30
import com.scrivenvar.util.ProtocolResolver;
3130
import javafx.beans.property.IntegerProperty;
3231
import javafx.beans.property.SimpleIntegerProperty;
...
3938
import java.nio.file.Paths;
4039
41
import static com.scrivenvar.graphics.SVGRasterizer.BROKEN_IMAGE_PLACEHOLDER;
40
import static com.scrivenvar.preview.SvgRasterizer.BROKEN_IMAGE_PLACEHOLDER;
41
import static com.scrivenvar.util.ProtocolResolver.getProtocol;
4242
import static org.xhtmlrenderer.swing.AWTFSImage.createImage;
4343
...
8686
8787
    try {
88
      final String protocol = ProtocolResolver.getProtocol( uri );
88
      final var protocol = getProtocol( uri );
8989
90
      if( "file".equals( protocol ) ) {
90
      if( protocol.isFile() ) {
9191
        exists = Files.exists( Paths.get( new URI( uri ) ) );
9292
      }
M src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
2828
package com.scrivenvar.preview;
2929
30
import com.scrivenvar.Services;
3130
import com.scrivenvar.adapters.DocumentAdapter;
32
import com.scrivenvar.graphics.SVGReplacedElementFactory;
33
import com.scrivenvar.service.events.Notifier;
3431
import javafx.beans.property.BooleanProperty;
3532
import javafx.beans.property.SimpleBooleanProperty;
...
5552
5653
import static com.scrivenvar.Constants.*;
54
import static com.scrivenvar.StatusBarNotifier.alert;
55
import static com.scrivenvar.util.ProtocolResolver.getProtocol;
5756
import static java.awt.Desktop.Action.BROWSE;
5857
import static java.awt.Desktop.getDesktop;
...
6564
 */
6665
public final class HTMLPreviewPane extends SwingNode {
67
  private final static Notifier sNotifier = Services.load( Notifier.class );
6866
67
  /**
68
   * Suppresses scrolling to the top on every key press.
69
   */
6970
  private static class HTMLPanel extends XHTMLPanel {
70
    /**
71
     * Suppresses scrolling to the top on every key press.
72
     */
7371
    @Override
7472
    public void resetScrollPosition() {
...
9896
9997
  /**
100
   * Responsible for ensuring that images are constrained to the panel width
101
   * upon resizing.
98
   * Ensure that images are constrained to the panel width upon resizing.
10299
   */
103100
  private final class ResizeListener extends ComponentAdapter {
...
117114
     * to prevent the horizontal scrollbar from appearing.
118115
     *
119
     * @param e The component that defines the image scaling width.
116
     * @param event The component that defines the image scaling width.
120117
     */
121
    private void setWidth( final ComponentEvent e ) {
122
      final int width = (int) (e.getComponent().getWidth() * .95);
118
    private void setWidth( final ComponentEvent event ) {
119
      final int width = (int) (event.getComponent().getWidth() * .95);
123120
      HTMLPreviewPane.this.mImageLoader.widthProperty().set( width );
124121
    }
125122
  }
126123
127124
  /**
128
   * Responsible for launching hyperlinks in the system's default browser.
125
   * Responsible for opening hyperlinks. External hyperlinks are opened in
126
   * the system's default browser; local file system links are opened in the
127
   * editor.
129128
   */
130129
  private static class HyperlinkListener extends LinkListener {
131130
    @Override
132
    public void linkClicked( final BasicPanel panel, final String uri ) {
131
    public void linkClicked( final BasicPanel panel, final String link ) {
133132
      try {
134
        final var desktop = getDesktop();
133
        final var protocol = getProtocol( link );
135134
136
        if( desktop.isSupported( BROWSE ) ) {
137
          desktop.browse( new URI( uri ) );
135
        switch( protocol ) {
136
          case HTTP:
137
            final var desktop = getDesktop();
138
139
            if( desktop.isSupported( BROWSE ) ) {
140
              desktop.browse( new URI( link ) );
141
            }
142
            break;
143
          case FILE:
144
            // TODO: #88 -- publish a message to the event bus.
145
            break;
138146
        }
139
      } catch( final Exception e ) {
140
        sNotifier.notify( e );
147
      } catch( final Exception ex ) {
148
        alert( ex );
141149
      }
142150
    }
143151
  }
144152
145153
  /**
146154
   * The CSS must be rendered in points (pt) not pixels (px) to avoid blurry
147155
   * rendering on some platforms.
148156
   */
149
  private final static String HTML_PREFIX = "<!DOCTYPE html>"
157
  private static final String HTML_PREFIX = "<!DOCTYPE html>"
150158
      + "<html>"
151159
      + "<head>"
152160
      + "<link rel='stylesheet' href='" +
153161
      HTMLPreviewPane.class.getResource( STYLESHEET_PREVIEW ) + "'/>"
154162
      + "</head>"
155163
      + "<body>";
156164
157165
  // Provide some extra space at the end for scrolling past the last line.
158
  private final static String HTML_SUFFIX =
166
  private static final String HTML_SUFFIX =
159167
      "<p style='height=2em'>&nbsp;</p></body></html>";
160168
161
  private final static W3CDom W3C_DOM = new W3CDom();
162
  private final static XhtmlNamespaceHandler NS_HANDLER =
169
  private static final W3CDom W3C_DOM = new W3CDom();
170
  private static final XhtmlNamespaceHandler NS_HANDLER =
163171
      new XhtmlNamespaceHandler();
164172
...
186194
    // Inject an SVG renderer that produces high-quality SVG buffered images.
187195
    final var factory = new ChainedReplacedElementFactory();
188
    factory.addFactory( new SVGReplacedElementFactory() );
196
    factory.addFactory( new SvgReplacedElementFactory() );
189197
    factory.addFactory( new SwingReplacedElementFactory(
190198
        NO_OP_REPAINT_LISTENER, mImageLoader ) );
...
220228
    final Document jsoupDoc = Jsoup.parse( decorate( html ) );
221229
    final org.w3c.dom.Document w3cDoc = W3C_DOM.fromJsoup( jsoupDoc );
230
222231
223232
    // Access to a Swing component must occur from the Event Dispatch
A src/main/java/com/scrivenvar/preview/MathRenderer.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.preview;
29
30
import com.whitemagicsoftware.tex.*;
31
import com.whitemagicsoftware.tex.graphics.SvgDomGraphics2D;
32
import org.w3c.dom.Document;
33
34
/**
35
 * Responsible for rendering formulas as scalable vector graphics (SVG).
36
 */
37
public class MathRenderer {
38
39
  private static final float mSize = 20f;
40
41
  private final TeXFont mTeXFont = new DefaultTeXFont( mSize );
42
  private final TeXEnvironment mEnvironment = new TeXEnvironment( mTeXFont );
43
  private final SvgDomGraphics2D mGraphics = new SvgDomGraphics2D();
44
45
  public MathRenderer() {
46
    mGraphics.scale( mSize, mSize );
47
  }
48
49
  /**
50
   * This method only takes a few seconds to generate
51
   *
52
   * @param equation A mathematical expression to render.
53
   * @return The given string with all formulas transformed into SVG format.
54
   */
55
  public Document render( final String equation ) {
56
    final var formula = new TeXFormula( equation );
57
    final var box = formula.createBox( mEnvironment );
58
    final var l = new TeXLayout( box, mSize );
59
60
    mGraphics.initialize( l.getWidth(), l.getHeight() );
61
    box.draw( mGraphics, l.getX(), l.getY() );
62
    return mGraphics.toDom();
63
  }
64
}
165
A src/main/java/com/scrivenvar/preview/RenderingSettings.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.preview;
29
30
import java.util.HashMap;
31
import java.util.Map;
32
33
import static java.awt.RenderingHints.*;
34
import static java.awt.Toolkit.getDefaultToolkit;
35
36
/**
37
 * Responsible for supplying consistent rendering hints throughout the
38
 * application, such as image rendering for {@link SvgRasterizer}.
39
 */
40
@SuppressWarnings("rawtypes")
41
public class RenderingSettings {
42
43
  /**
44
   * Default hints for high-quality rendering that may be changed by
45
   * the system's rendering hints.
46
   */
47
  private static final Map<Object, Object> DEFAULT_HINTS = Map.of(
48
      KEY_ANTIALIASING,
49
      VALUE_ANTIALIAS_ON,
50
      KEY_ALPHA_INTERPOLATION,
51
      VALUE_ALPHA_INTERPOLATION_QUALITY,
52
      KEY_COLOR_RENDERING,
53
      VALUE_COLOR_RENDER_QUALITY,
54
      KEY_DITHERING,
55
      VALUE_DITHER_DISABLE,
56
      KEY_FRACTIONALMETRICS,
57
      VALUE_FRACTIONALMETRICS_ON,
58
      KEY_INTERPOLATION,
59
      VALUE_INTERPOLATION_BICUBIC,
60
      KEY_RENDERING,
61
      VALUE_RENDER_QUALITY,
62
      KEY_STROKE_CONTROL,
63
      VALUE_STROKE_PURE,
64
      KEY_TEXT_ANTIALIASING,
65
      VALUE_TEXT_ANTIALIAS_ON
66
  );
67
68
  /**
69
   * Shared hints for high-quality rendering.
70
   */
71
  public static final Map<Object, Object> RENDERING_HINTS = new HashMap<>(
72
      DEFAULT_HINTS
73
  );
74
75
  static {
76
    final var toolkit = getDefaultToolkit();
77
    final var hints = toolkit.getDesktopProperty( "awt.font.desktophints" );
78
79
    if( hints instanceof Map ) {
80
      final var map = (Map) hints;
81
      for( final var key : map.keySet() ) {
82
        final var hint = map.get( key );
83
        RENDERING_HINTS.put( key, hint );
84
      }
85
    }
86
  }
87
88
  /**
89
   * Prevent instantiation as per Joshua Bloch's recommendation.
90
   */
91
  private RenderingSettings() {
92
  }
93
}
194
A src/main/java/com/scrivenvar/preview/SvgRasterizer.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.preview;
29
30
import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
31
import org.apache.batik.gvt.renderer.ImageRenderer;
32
import org.apache.batik.transcoder.TranscoderException;
33
import org.apache.batik.transcoder.TranscoderInput;
34
import org.apache.batik.transcoder.TranscoderOutput;
35
import org.apache.batik.transcoder.image.ImageTranscoder;
36
import org.w3c.dom.Document;
37
import org.w3c.dom.Element;
38
39
import javax.xml.transform.Transformer;
40
import javax.xml.transform.TransformerConfigurationException;
41
import javax.xml.transform.TransformerFactory;
42
import javax.xml.transform.dom.DOMSource;
43
import javax.xml.transform.stream.StreamResult;
44
import java.awt.*;
45
import java.awt.image.BufferedImage;
46
import java.io.IOException;
47
import java.io.StringReader;
48
import java.io.StringWriter;
49
import java.net.URL;
50
import java.text.NumberFormat;
51
52
import static com.scrivenvar.StatusBarNotifier.alert;
53
import static com.scrivenvar.preview.RenderingSettings.RENDERING_HINTS;
54
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
55
import static java.nio.charset.StandardCharsets.UTF_8;
56
import static java.text.NumberFormat.getIntegerInstance;
57
import static javax.xml.transform.OutputKeys.*;
58
import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH;
59
import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName;
60
61
/**
62
 * Responsible for converting SVG images into rasterized PNG images.
63
 */
64
public class SvgRasterizer {
65
  private static final SAXSVGDocumentFactory FACTORY_DOM =
66
      new SAXSVGDocumentFactory( getXMLParserClassName() );
67
68
  private static final TransformerFactory FACTORY_TRANSFORM =
69
      TransformerFactory.newInstance();
70
71
  private static final Transformer sTransformer;
72
73
  static {
74
    Transformer t;
75
76
    try {
77
      t = FACTORY_TRANSFORM.newTransformer();
78
      t.setOutputProperty( OMIT_XML_DECLARATION, "yes" );
79
      t.setOutputProperty( METHOD, "xml" );
80
      t.setOutputProperty( INDENT, "no" );
81
      t.setOutputProperty( ENCODING, UTF_8.name() );
82
    } catch( final TransformerConfigurationException e ) {
83
      t = null;
84
    }
85
86
    sTransformer = t;
87
  }
88
89
  private static final NumberFormat INT_FORMAT = getIntegerInstance();
90
91
  public static final BufferedImage BROKEN_IMAGE_PLACEHOLDER;
92
93
  /**
94
   * A FontAwesome camera icon, cleft asunder.
95
   */
96
  public static final String BROKEN_IMAGE_SVG =
97
      "<svg height='19pt' viewBox='0 0 25 19' width='25pt' xmlns='http://www" +
98
          ".w3.org/2000/svg'><g fill='#454545'><path d='m8.042969 11.085938c" +
99
          ".332031 1.445312 1.660156 2.503906 3.214843 2.558593zm0 0'/><path " +
100
          "d='m6.792969 9.621094-.300781.226562.242187.195313c.015625-.144531" +
101
          ".03125-.28125.058594-.421875zm0 0'/><path d='m10.597656.949219-2" +
102
          ".511718.207031c-.777344.066406-1.429688.582031-1.636719 1.292969l-" +
103
          ".367188 1.253906-3.414062.28125c-1.027344.085937-1.792969.949219-1" +
104
          ".699219 1.925781l.976562 10.621094c.089844.976562.996094 1.699219 " +
105
          "2.023438 1.613281l11.710938-.972656-3.117188-2.484375c-.246094" +
106
          ".0625-.5.109375-.765625.132812-2.566406.210938-4.835937-1.597656-5" +
107
          ".0625-4.039062-.023437-.25-.019531-.496094 0-.738281l-.242187-" +
108
          ".195313.300781-.226562c.359375-1.929688 2.039062-3.472656 4" +
109
          ".191406-3.652344.207031-.015625.414063-.015625.617187-.007812l" +
110
          ".933594-.707032zm0 0'/><path d='m10.234375 11.070312 2.964844 2" +
111
          ".820313c.144531.015625.285156.027344.433593.027344 1.890626 0 3" +
112
          ".429688-1.460938 3.429688-3.257813 0-1.792968-1.539062-3.257812-3" +
113
          ".429688-3.257812-1.890624 0-3.429687 1.464844-3.429687 3.257812 0 " +
114
          ".140625.011719.277344.03125.410156zm0 0'/><path d='m14.488281" +
115
          ".808594 1.117188 4.554687-1.042969.546875c2.25.476563 3.84375 2" +
116
          ".472656 3.636719 4.714844-.199219 2.191406-2.050781 3.871094-4" +
117
          ".285157 4.039062l2.609376 2.957032 4.4375.371094c1.03125.085937 1" +
118
          ".9375-.640626 2.027343-1.617188l.976563-10.617188c.089844-.980468-" +
119
          ".667969-1.839843-1.699219-1.925781l-3.414063-.285156-.371093-1" +
120
          ".253906c-.207031-.710938-.859375-1.226563-1.636719-1.289063zm0 " +
121
          "0'/></g></svg>";
122
123
  static {
124
    // The width and height cannot be embedded in the SVG above because the
125
    // path element values are relative to the viewBox dimensions.
126
    final int w = 75;
127
    final int h = 75;
128
    BufferedImage image;
129
130
    try {
131
      image = rasterizeString( BROKEN_IMAGE_SVG, w );
132
    } catch( final Exception e ) {
133
      image = new BufferedImage( w, h, TYPE_INT_RGB );
134
      final var graphics = (Graphics2D) image.getGraphics();
135
      graphics.setRenderingHints( RENDERING_HINTS );
136
137
      // Fall back to a (\) symbol.
138
      graphics.setColor( new Color( 204, 204, 204 ) );
139
      graphics.fillRect( 0, 0, w, h );
140
      graphics.setColor( new Color( 255, 204, 204 ) );
141
      graphics.setStroke( new BasicStroke( 4 ) );
142
      graphics.drawOval( w / 4, h / 4, w / 2, h / 2 );
143
      graphics.drawLine( w / 4 + (int) (w / 4 / Math.PI),
144
                         h / 4 + (int) (w / 4 / Math.PI),
145
                         w / 2 + w / 4 - (int) (w / 4 / Math.PI),
146
                         h / 2 + h / 4 - (int) (w / 4 / Math.PI) );
147
    }
148
149
    BROKEN_IMAGE_PLACEHOLDER = image;
150
  }
151
152
  /**
153
   * Responsible for creating a new {@link ImageRenderer} implementation that
154
   * can render a DOM as an SVG image.
155
   */
156
  private static class BufferedImageTranscoder extends ImageTranscoder {
157
    private BufferedImage mImage;
158
159
    @Override
160
    public BufferedImage createImage( final int w, final int h ) {
161
      return new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB );
162
    }
163
164
    @Override
165
    public void writeImage(
166
        final BufferedImage image, final TranscoderOutput output ) {
167
      mImage = image;
168
    }
169
170
    public BufferedImage getImage() {
171
      return mImage;
172
    }
173
174
    @Override
175
    protected ImageRenderer createRenderer() {
176
      final ImageRenderer renderer = super.createRenderer();
177
      final RenderingHints hints = renderer.getRenderingHints();
178
      hints.putAll( RENDERING_HINTS );
179
180
      renderer.setRenderingHints( hints );
181
182
      return renderer;
183
    }
184
  }
185
186
  /**
187
   * Rasterizes the vector graphic file at the given URL. If any exception
188
   * happens, a red circle is returned instead.
189
   *
190
   * @param url   The URL to a vector graphic file, which must include the
191
   *              protocol scheme (such as file:// or https://).
192
   * @param width The number of pixels wide to render the image. The aspect
193
   *              ratio is maintained.
194
   * @return Either the rasterized image upon success or a red circle.
195
   */
196
  public static BufferedImage rasterize( final String url, final int width ) {
197
    try {
198
      return rasterize( new URL( url ), width );
199
    } catch( final Exception ex ) {
200
      alert( ex );
201
      return BROKEN_IMAGE_PLACEHOLDER;
202
    }
203
  }
204
205
  /**
206
   * Rasterizes the given document into an image.
207
   *
208
   * @param svg   The SVG {@link Document} to rasterize.
209
   * @param width The rasterized image's width (in pixels).
210
   * @return The rasterized image.
211
   * @throws TranscoderException Signifies an issue with the input document.
212
   */
213
  public static BufferedImage rasterize( final Document svg, final int width )
214
      throws TranscoderException {
215
    final var transcoder = new BufferedImageTranscoder();
216
    final var input = new TranscoderInput( svg );
217
218
    transcoder.addTranscodingHint( KEY_WIDTH, (float) width );
219
    transcoder.transcode( input, null );
220
221
    return transcoder.getImage();
222
  }
223
224
  /**
225
   * Converts an SVG drawing into a rasterized image that can be drawn on
226
   * a graphics context.
227
   *
228
   * @param url   The path to the image (can be web address).
229
   * @param width Scale the image width to this size (aspect ratio is
230
   *              maintained).
231
   * @return The vector graphic transcoded into a raster image format.
232
   * @throws IOException         Could not read the vector graphic.
233
   * @throws TranscoderException Could not convert the vector graphic to an
234
   *                             instance of {@link Image}.
235
   */
236
  public static BufferedImage rasterize( final URL url, final int width )
237
      throws IOException, TranscoderException {
238
    return rasterize( FACTORY_DOM.createDocument( url.toString() ), width );
239
  }
240
241
  public static BufferedImage rasterize( final Document document ) {
242
    try {
243
      final var root = document.getDocumentElement();
244
      final var width = root.getAttribute( "width" );
245
      return rasterize( document, INT_FORMAT.parse( width ).intValue() );
246
    } catch( final Exception ex ) {
247
      alert( ex );
248
      return BROKEN_IMAGE_PLACEHOLDER;
249
    }
250
  }
251
252
  /**
253
   * Converts an SVG string into a rasterized image that can be drawn on
254
   * a graphics context.
255
   *
256
   * @param svg The SVG xml document.
257
   * @param w   Scale the image width to this size (aspect ratio is
258
   *            maintained).
259
   * @return The vector graphic transcoded into a raster image format.
260
   * @throws TranscoderException Could not convert the vector graphic to an
261
   *                             instance of {@link Image}.
262
   */
263
  public static BufferedImage rasterizeString( final String svg, final int w )
264
      throws IOException, TranscoderException {
265
    return rasterize( toDocument( svg ), w );
266
  }
267
268
  /**
269
   * Converts an SVG string into a rasterized image that can be drawn on
270
   * a graphics context. The dimensions are determined from the document.
271
   *
272
   * @param xml The SVG xml document.
273
   * @return The vector graphic transcoded into a raster image format.
274
   */
275
  public static BufferedImage rasterizeString( final String xml ) {
276
    try {
277
      final var document = toDocument( xml );
278
      final var root = document.getDocumentElement();
279
      final var width = root.getAttribute( "width" );
280
      return rasterizeString( xml, INT_FORMAT.parse( width ).intValue() );
281
    } catch( final Exception ex ) {
282
      alert( ex );
283
      return BROKEN_IMAGE_PLACEHOLDER;
284
    }
285
  }
286
287
  /**
288
   * Converts an SVG XML string into a new {@link Document} instance.
289
   *
290
   * @param xml The XML containing SVG elements.
291
   * @return The SVG contents parsed into a {@link Document} object model.
292
   * @throws IOException Could
293
   */
294
  private static Document toDocument( final String xml ) throws IOException {
295
    try( final var reader = new StringReader( xml ) ) {
296
      return FACTORY_DOM.createSVGDocument(
297
          "http://www.w3.org/2000/svg", reader );
298
    }
299
  }
300
301
  /**
302
   * Given a document object model (DOM) {@link Element}, this will convert that
303
   * element to a string.
304
   *
305
   * @param e The DOM node to convert to a string.
306
   * @return The DOM node as an escaped, plain text string.
307
   */
308
  public static String toSvg( final Element e ) {
309
    try( final var writer = new StringWriter() ) {
310
      sTransformer.transform( new DOMSource( e ), new StreamResult( writer ) );
311
      return writer.toString().replaceAll( "xmlns=\"\" ", "" );
312
    } catch( final Exception ex ) {
313
      alert( ex );
314
    }
315
316
    return BROKEN_IMAGE_SVG;
317
  }
318
}
1319
A src/main/java/com/scrivenvar/preview/SvgReplacedElementFactory.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.preview;
29
30
import com.scrivenvar.util.BoundedCache;
31
import org.apache.commons.io.FilenameUtils;
32
import org.w3c.dom.Element;
33
import org.xhtmlrenderer.extend.ReplacedElement;
34
import org.xhtmlrenderer.extend.ReplacedElementFactory;
35
import org.xhtmlrenderer.extend.UserAgentCallback;
36
import org.xhtmlrenderer.layout.LayoutContext;
37
import org.xhtmlrenderer.render.BlockBox;
38
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;
39
import org.xhtmlrenderer.swing.ImageReplacedElement;
40
41
import java.awt.image.BufferedImage;
42
import java.util.Map;
43
import java.util.function.Function;
44
45
import static com.scrivenvar.StatusBarNotifier.alert;
46
import static com.scrivenvar.preview.SvgRasterizer.rasterize;
47
48
/**
49
 * Responsible for running {@link SvgRasterizer} on SVG images detected within
50
 * a document to transform them into rasterized versions.
51
 */
52
public class SvgReplacedElementFactory
53
    implements ReplacedElementFactory {
54
55
  /**
56
   * SVG filename extension maps to an SVG image element.
57
   */
58
  private static final String SVG_FILE = "svg";
59
60
  /**
61
   * TeX expression wrapped in a {@code <tex>} element.
62
   */
63
  private static final String HTML_TEX = "tex";
64
65
  private static final String HTML_IMAGE = "img";
66
  private static final String HTML_IMAGE_SRC = "src";
67
68
  private static final MathRenderer sMathRenderer = new MathRenderer();
69
70
  /**
71
   * A bounded cache that removes the oldest image if the maximum number of
72
   * cached images has been reached. This constrains the number of images
73
   * loaded into memory.
74
   */
75
  private final Map<String, BufferedImage> mImageCache =
76
      new BoundedCache<>( 150 );
77
78
  @Override
79
  public ReplacedElement createReplacedElement(
80
      final LayoutContext c,
81
      final BlockBox box,
82
      final UserAgentCallback uac,
83
      final int cssWidth,
84
      final int cssHeight ) {
85
    BufferedImage image = null;
86
    final var e = box.getElement();
87
88
    if( e != null ) {
89
      try {
90
        final var nodeName = e.getNodeName();
91
92
        if( HTML_IMAGE.equals( nodeName ) ) {
93
          final var src = e.getAttribute( HTML_IMAGE_SRC );
94
          final var ext = FilenameUtils.getExtension( src );
95
96
          if( SVG_FILE.equalsIgnoreCase( ext ) ) {
97
            image = getCachedImage(
98
                src, svg -> rasterize( svg, box.getContentWidth() ) );
99
          }
100
        }
101
        else if( HTML_TEX.equals( nodeName ) ) {
102
          // Convert the <svg> element to a raster graphic if not yet cached.
103
          final var src = e.getTextContent();
104
          image = getCachedImage(
105
              src, __ -> rasterize( sMathRenderer.render( src ) )
106
          );
107
        }
108
      } catch( final Exception ex ) {
109
        alert( ex );
110
      }
111
    }
112
113
    if( image != null ) {
114
      final var w = image.getWidth( null );
115
      final var h = image.getHeight( null );
116
117
      return new ImageReplacedElement( image, w, h );
118
    }
119
120
    return null;
121
  }
122
123
  @Override
124
  public void reset() {
125
  }
126
127
  @Override
128
  public void remove( final Element e ) {
129
  }
130
131
  @Override
132
  public void setFormSubmissionListener( FormSubmissionListener listener ) {
133
  }
134
135
  /**
136
   * Returns an image associated with a string; the string's pre-computed
137
   * hash code is returned as the string value, making this operation very
138
   * quick to return the corresponding {@link BufferedImage}.
139
   *
140
   * @param src        The SVG used for the key into the image cache.
141
   * @param rasterizer {@link Function} to call to convert SVG to an image.
142
   * @return The image that corresponds to the given source string.
143
   */
144
  private BufferedImage getCachedImage(
145
      final String src, final Function<String, BufferedImage> rasterizer ) {
146
    return mImageCache.computeIfAbsent( src, __ -> rasterizer.apply( src ) );
147
  }
148
}
1149
M src/main/java/com/scrivenvar/processors/AbstractProcessor.java
115115
116116
    @Override
117
    public T process( T t ) {
118
      return mDelegate.process( t );
117
    public T apply( T t ) {
118
      return mDelegate.apply( t );
119119
    }
120120
M src/main/java/com/scrivenvar/processors/DefinitionProcessor.java
5555
   */
5656
  @Override
57
  public String process( final String text ) {
57
  public String apply( final String text ) {
5858
    return replace( text, getDefinitions() );
5959
  }
M src/main/java/com/scrivenvar/processors/HtmlPreviewProcessor.java
6060
   */
6161
  @Override
62
  public String process( final String html ) {
62
  public String apply( final String html ) {
6363
    getHtmlPreviewPane().process( html );
6464
M src/main/java/com/scrivenvar/processors/IdentityProcessor.java
3737
   * Passes the link to the super constructor.
3838
   *
39
   * @param link The next processor in the chain to use for text processing.
39
   * @param successor The next processor in the chain to use for text
40
   *                  processing.
4041
   */
41
  public IdentityProcessor( final Processor<String> link ) {
42
    super( link );
42
  public IdentityProcessor( final Processor<String> successor ) {
43
    super( successor );
4344
  }
4445
4546
  /**
4647
   * Returns the given string, modified with "pre" tags.
4748
   *
4849
   * @param t The string to return, enclosed in "pre" tags.
4950
   * @return The value of t wrapped in "pre" tags.
5051
   */
5152
  @Override
52
  public String process( final String t ) {
53
  public String apply( final String t ) {
5354
    return "<pre>" + t + "</pre>";
5455
  }
M src/main/java/com/scrivenvar/processors/InlineRProcessor.java
3131
import com.scrivenvar.preferences.UserPreferences;
3232
import com.scrivenvar.service.Options;
33
import com.scrivenvar.service.events.Notifier;
3433
import javafx.beans.property.ObjectProperty;
3534
import javafx.beans.property.StringProperty;
3635
3736
import javax.script.ScriptEngine;
3837
import javax.script.ScriptEngineManager;
39
import javax.script.ScriptException;
4038
import java.io.File;
4139
import java.nio.file.Path;
4240
import java.util.LinkedHashMap;
4341
import java.util.Map;
4442
import java.util.concurrent.atomic.AtomicBoolean;
4543
4644
import static com.scrivenvar.Constants.STATUS_PARSE_ERROR;
47
import static com.scrivenvar.Messages.get;
48
import static com.scrivenvar.decorators.RVariableDecorator.PREFIX;
49
import static com.scrivenvar.decorators.RVariableDecorator.SUFFIX;
45
import static com.scrivenvar.StatusBarNotifier.alert;
5046
import static com.scrivenvar.processors.text.TextReplacementFactory.replace;
47
import static com.scrivenvar.sigils.RSigilOperator.PREFIX;
48
import static com.scrivenvar.sigils.RSigilOperator.SUFFIX;
5149
import static java.lang.Math.min;
52
import static java.lang.String.format;
5350
5451
/**
5552
 * Transforms a document containing R statements into Markdown.
5653
 */
5754
public final class InlineRProcessor extends DefinitionProcessor {
5855
59
  private static final Notifier sNotifier = Services.load( Notifier.class );
6056
  private static final Options sOptions = Services.load( Options.class );
6157
...
8985
   * Constructs a processor capable of evaluating R statements.
9086
   *
91
   * @param processor Subsequent link in the processing chain.
87
   * @param successor Subsequent link in the processing chain.
9288
   * @param map       Resolved definitions map.
9389
   */
9490
  public InlineRProcessor(
95
      final Processor<String> processor,
91
      final Processor<String> successor,
9692
      final Map<String, String> map ) {
97
    super( processor, map );
93
    super( successor, map );
9894
9995
    bootstrapScriptProperty().addListener(
...
118114
   */
119115
  private void init() {
120
    getNotifier().clear();
121
122
    try {
123
      final var bootstrap = getBootstrapScript();
116
    final var bootstrap = getBootstrapScript();
124117
125
      if( !bootstrap.isBlank() ) {
126
        final var wd = getWorkingDirectory();
127
        final var dir = wd.toString().replace( '\\', '/' );
128
        final var map = getDefinitions();
129
        map.put( "$application.r.working.directory$", dir );
118
    if( !bootstrap.isBlank() ) {
119
      final var wd = getWorkingDirectory();
120
      final var dir = wd.toString().replace( '\\', '/' );
121
      final var map = getDefinitions();
122
      map.put( "$application.r.working.directory$", dir );
130123
131
        eval( replace( bootstrap, map ) );
132
      }
133
    } catch( final Exception ex ) {
134
      getNotifier().notify( ex );
124
      eval( replace( bootstrap, map ) );
135125
    }
136126
  }
...
166156
   */
167157
  @Override
168
  public String process( final String text ) {
169
    getNotifier().clear();
170
158
  public String apply( final String text ) {
171159
    final int length = text.length();
172160
...
205193
206194
          // Tell the user that there was a problem.
207
          getNotifier().notify(
208
              get( STATUS_PARSE_ERROR, e.getMessage(), currIndex )
209
          );
195
          alert( STATUS_PARSE_ERROR, e.getMessage(), currIndex );
210196
        }
211197
...
242228
    try {
243229
      return getScriptEngine().eval( r );
244
    } catch( final ScriptException ex ) {
230
    } catch( final Exception ex ) {
245231
      final String expr = r.substring( 0, min( r.length(), 30 ) );
246
      final String msg = format(
247
          "Error with [%s...]: %s", expr, ex.getMessage() );
248
      getNotifier().notify( msg );
232
      alert( "Main.status.error.r", expr, ex.getMessage() );
249233
    }
250234
...
285269
  private ScriptEngine getScriptEngine() {
286270
    return ENGINE;
287
  }
288
289
  private Notifier getNotifier() {
290
    return sNotifier;
291271
  }
292272
}
M src/main/java/com/scrivenvar/processors/Processor.java
2828
package com.scrivenvar.processors;
2929
30
import java.util.function.UnaryOperator;
31
3032
/**
3133
 * Responsible for processing documents from one known format to another.
34
 * Processes the given content providing a transformation from one document
35
 * format into another. For example, this could convert from XML to text using
36
 * an XSLT processor, or from markdown to HTML.
3237
 *
3338
 * @param <T> The type of processor to create.
3439
 */
35
public interface Processor<T> {
40
public interface Processor<T> extends UnaryOperator<T> {
3641
3742
  /**
38
   * Processes the given content providing a transformation from one document
39
   * format into another. For example, this could convert from XML to text using
40
   * an XSLT processor, or from markdown to HTML.
43
   * Removes the given processor from the chain, returning a new immutable
44
   * chain equivalent to this chain, but without the given processor.
4145
   *
42
   * @param t The type of object to process.
43
   * @return The post-processed document, or null if processing should stop.
46
   * @param processor The {@link Processor} to remove from the chain.
47
   * @return A delegating processor chain starting from this processor
48
   * onwards with the given processor removed from the chain.
4449
   */
45
  T process( T t );
46
4750
  Processor<T> remove( Class<? extends Processor<T>> processor );
4851
M src/main/java/com/scrivenvar/processors/ProcessorFactory.java
3333
import com.scrivenvar.processors.markdown.MarkdownProcessor;
3434
35
import java.nio.file.Path;
3635
import java.util.Map;
3736
M src/main/java/com/scrivenvar/processors/RVariableProcessor.java
2828
package com.scrivenvar.processors;
2929
30
import com.scrivenvar.sigils.RSigilOperator;
31
3032
import java.util.HashMap;
3133
import java.util.Map;
...
6062
   */
6163
  private Map<String, String> toR( final Map<String, String> map ) {
62
    final Map<String, String> rMap = new HashMap<>( map.size() );
64
    final var rMap = new HashMap<String, String>( map.size() );
6365
64
    for( final Map.Entry<String, String> entry : map.entrySet() ) {
66
    for( final var entry : map.entrySet() ) {
6567
      final var key = entry.getKey();
66
      rMap.put( toRKey( key ), toRValue( map.get( key ) ) );
68
      rMap.put( RSigilOperator.entoken( key ), toRValue( map.get( key ) ) );
6769
    }
6870
6971
    return rMap;
70
  }
71
72
  /**
73
   * Transforms a variable name from $tree.branch.leaf$ to v$tree$branch$leaf
74
   * form.
75
   *
76
   * @param key The variable name to transform, can be empty but not null.
77
   * @return The transformed variable name.
78
   */
79
  private String toRKey( final String key ) {
80
    // Replace all the periods with dollar symbols.
81
    final StringBuilder sb = new StringBuilder( 'v' + key );
82
    final int length = sb.length();
83
84
    // Replace all periods with dollar symbols. Normally we'd check i >= 0,
85
    // but the prepended 'v' is always going to be a 'v', not a dot.
86
    for( int i = length - 1; i > 0; i-- ) {
87
      if( sb.charAt( i ) == '.' ) {
88
        sb.setCharAt( i, '$' );
89
      }
90
    }
91
92
    // The length is always at least 1 (the 'v'), so bounds aren't broken here.
93
    sb.setLength( length - 1 );
94
95
    return sb.toString();
9672
  }
9773
M src/main/java/com/scrivenvar/processors/XmlProcessor.java
9393
   */
9494
  @Override
95
  public String process( final String text ) {
95
  public String apply( final String text ) {
9696
    try {
9797
      return text.isEmpty() ? text : transform( text );
M src/main/java/com/scrivenvar/processors/markdown/BlockExtension.java
55
import com.vladsch.flexmark.html.AttributeProvider;
66
import com.vladsch.flexmark.html.AttributeProviderFactory;
7
import com.vladsch.flexmark.html.HtmlRenderer;
87
import com.vladsch.flexmark.html.IndependentAttributeProviderFactory;
98
import com.vladsch.flexmark.html.renderer.AttributablePart;
...
1615
1716
import static com.scrivenvar.Constants.PARAGRAPH_ID_PREFIX;
17
import static com.vladsch.flexmark.html.HtmlRenderer.Builder;
18
import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
1819
import static com.vladsch.flexmark.html.renderer.CoreNodeRenderer.CODE_CONTENT;
1920
2021
/**
2122
 * Responsible for giving most block-level elements a unique identifier
2223
 * attribute. The identifier is used to coordinate scrolling.
2324
 */
24
public class BlockExtension implements HtmlRenderer.HtmlRendererExtension {
25
public class BlockExtension implements HtmlRendererExtension {
2526
  /**
2627
   * Responsible for creating the id attribute. This class is instantiated
...
6465
6566
  private BlockExtension() {
66
  }
67
68
  @Override
69
  public void rendererOptions( @NotNull final MutableDataHolder options ) {
7067
  }
7168
7269
  @Override
73
  public void extend( final HtmlRenderer.Builder builder,
70
  public void extend( final Builder builder,
7471
                      @NotNull final String rendererType ) {
7572
    builder.attributeProviderFactory( IdAttributeProvider.createFactory() );
7673
  }
7774
7875
  public static BlockExtension create() {
7976
    return new BlockExtension();
77
  }
78
79
  @Override
80
  public void rendererOptions( @NotNull final MutableDataHolder options ) {
8081
  }
8182
}
M src/main/java/com/scrivenvar/processors/markdown/ImageLinkExtension.java
3131
import com.scrivenvar.preferences.UserPreferences;
3232
import com.scrivenvar.service.Options;
33
import com.scrivenvar.service.events.Notifier;
34
import com.scrivenvar.util.ProtocolResolver;
3533
import com.vladsch.flexmark.ast.Image;
36
import com.vladsch.flexmark.html.HtmlRenderer;
3734
import com.vladsch.flexmark.html.IndependentLinkResolverFactory;
3835
import com.vladsch.flexmark.html.LinkResolver;
...
4946
import java.nio.file.Path;
5047
48
import static com.scrivenvar.StatusBarNotifier.alert;
49
import static com.scrivenvar.util.ProtocolResolver.getProtocol;
50
import static com.vladsch.flexmark.html.HtmlRenderer.Builder;
51
import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
5152
import static java.lang.String.format;
5253
5354
/**
5455
 * Responsible for ensuring that images can be rendered relative to a path.
5556
 * This allows images to be located virtually anywhere.
5657
 */
57
public class ImageLinkExtension implements HtmlRenderer.HtmlRendererExtension {
58
public class ImageLinkExtension implements HtmlRendererExtension {
5859
  /**
5960
   * Used for image directory preferences.
6061
   */
61
  private final static Options sOptions = Services.load( Options.class );
62
  private final static Notifier sNotifier = Services.load( Notifier.class );
62
  private static final Options sOptions = Services.load( Options.class );
6363
6464
  /**
...
104104
105105
    private ResolvedLink resolve( final ResolvedLink link ) {
106
      String url = link.getUrl();
107
      final String protocol = ProtocolResolver.getProtocol( url );
106
      var url = link.getUrl();
107
      final var protocol = getProtocol( url );
108108
109109
      try {
110110
        // If the direct file name exists, then use it directly.
111
        if( ("file".equals( protocol ) && Path.of( url ).toFile().exists()) ||
112
            protocol.startsWith( "http" ) ) {
111
        if( (protocol.isFile() && Path.of( url ).toFile().exists()) ||
112
            protocol.isHttp() ) {
113113
          return valid( link, url );
114114
        }
...
154154
        }
155155
156
        if( "file".equals( protocol ) ) {
156
        if( protocol.isFile() ) {
157157
          url = "file://" + url;
158158
        }
159
160
        getNotifier().clear();
161159
162160
        return valid( link, url );
163
      } catch( final Exception e ) {
164
        getNotifier().notify( "File not found: " + e.getLocalizedMessage() );
161
      } catch( final Exception ex ) {
162
        alert( ex );
165163
      }
166164
...
196194
197195
  @Override
198
  public void extend(
199
      final HtmlRenderer.Builder rendererBuilder,
200
      @NotNull final String rendererType ) {
201
    rendererBuilder.linkResolverFactory( new Factory() );
196
  public void extend( @NotNull final Builder builder,
197
                      @NotNull final String rendererType ) {
198
    builder.linkResolverFactory( new Factory() );
202199
  }
203200
204201
  private UserPreferences getUserPreferences() {
205202
    return getOptions().getUserPreferences();
206203
  }
207204
208
  private Options getOptions() {
205
  private static Options getOptions() {
209206
    return sOptions;
210
  }
211
212
  private Notifier getNotifier() {
213
    return sNotifier;
214207
  }
215208
}
M src/main/java/com/scrivenvar/processors/markdown/LigatureExtension.java
3535
   * ligature, not the "ff" ligature.
3636
   */
37
  private final static Map<String, String> LIGATURES = new LinkedHashMap<>();
37
  private static final Map<String, String> LIGATURES = new LinkedHashMap<>();
3838
3939
  static {
M src/main/java/com/scrivenvar/processors/markdown/MarkdownProcessor.java
8080
    extensions.add( ImageLinkExtension.create( path ) );
8181
    extensions.add( BlockExtension.create() );
82
    extensions.add( TeXExtension.create() );
8283
8384
    // TODO: https://github.com/FAlthausen/Vollkorn-Typeface/issues/38
8485
    // TODO: Uncomment when Vollkorn ligatures are fixed.
8586
    // extensions.add( LigatureExtension.create() );
8687
8788
    mRenderer = HtmlRenderer.builder().extensions( extensions ).build();
88
    mParser = Parser.builder().extensions( extensions ).build();
89
    mParser = Parser.builder()
90
                    .extensions( extensions )
91
                    .build();
8992
  }
9093
...
97100
   */
98101
  @Override
99
  public String process( final String markdown ) {
102
  public String apply( final String markdown ) {
100103
    return toHtml( markdown );
101104
  }
A src/main/java/com/scrivenvar/processors/markdown/TeXExtension.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.markdown;
29
30
import com.scrivenvar.processors.markdown.tex.TeXInlineDelimiterProcessor;
31
import com.scrivenvar.processors.markdown.tex.TeXNodeRenderer;
32
import com.vladsch.flexmark.html.HtmlRenderer;
33
import com.vladsch.flexmark.parser.Parser;
34
import com.vladsch.flexmark.util.data.MutableDataHolder;
35
import org.jetbrains.annotations.NotNull;
36
37
import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
38
import static com.vladsch.flexmark.parser.Parser.ParserExtension;
39
40
/**
41
 * Responsible for wrapping delimited TeX code in Markdown into an XML element
42
 * that the HTML renderer can handle. For example, {@code $E=mc^2$} becomes
43
 * {@code <tex>E=mc^2</tex>} when passed to HTML renderer. The HTML renderer
44
 * is responsible for converting the TeX code for display. This avoids inserting
45
 * SVG code into the Markdown document, which the parser would then have to
46
 * iterate---a <em>very</em> wasteful operation that impacts front-end
47
 * performance.
48
 */
49
public class TeXExtension implements ParserExtension, HtmlRendererExtension {
50
  /**
51
   * Creates an extension capable of handling delimited TeX code in Markdown.
52
   *
53
   * @return The new {@link TeXExtension}, never {@code null}.
54
   */
55
  public static TeXExtension create() {
56
    return new TeXExtension();
57
  }
58
59
  /**
60
   * Force using the {@link #create()} method for consistency.
61
   */
62
  private TeXExtension() {
63
  }
64
65
  /**
66
   * Adds the TeX extension for HTML document export types.
67
   *
68
   * @param builder      The document builder.
69
   * @param rendererType Indicates the document type to be built.
70
   */
71
  @Override
72
  public void extend( @NotNull final HtmlRenderer.Builder builder,
73
                      @NotNull final String rendererType ) {
74
    if( "HTML".equalsIgnoreCase( rendererType ) ) {
75
      builder.nodeRendererFactory( new TeXNodeRenderer.Factory() );
76
    }
77
  }
78
79
  @Override
80
  public void extend( final Parser.Builder builder ) {
81
    builder.customDelimiterProcessor( new TeXInlineDelimiterProcessor() );
82
  }
83
84
  @Override
85
  public void rendererOptions( @NotNull final MutableDataHolder options ) {
86
  }
87
88
  @Override
89
  public void parserOptions( final MutableDataHolder options ) {
90
  }
91
}
192
A src/main/java/com/scrivenvar/processors/markdown/tex/TeXInlineDelimiterProcessor.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.markdown.tex;
29
30
import com.vladsch.flexmark.parser.InlineParser;
31
import com.vladsch.flexmark.parser.core.delimiter.Delimiter;
32
import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor;
33
import com.vladsch.flexmark.parser.delimiter.DelimiterRun;
34
import com.vladsch.flexmark.util.ast.Node;
35
36
public class TeXInlineDelimiterProcessor implements DelimiterProcessor {
37
38
  @Override
39
  public void process( final Delimiter opener, final Delimiter closer,
40
                       final int delimitersUsed ) {
41
    final var node = new TeXNode();
42
    opener.moveNodesBetweenDelimitersTo(node, closer);
43
  }
44
45
  @Override
46
  public char getOpeningCharacter() {
47
    return '$';
48
  }
49
50
  @Override
51
  public char getClosingCharacter() {
52
    return '$';
53
  }
54
55
  @Override
56
  public int getMinLength() {
57
    return 1;
58
  }
59
60
  /**
61
   * Allow for $ or $$.
62
   *
63
   * @param opener One or more opening delimiter characters.
64
   * @param closer One or more closing delimiter characters.
65
   * @return The number of delimiters to use to determine whether a valid
66
   * opening delimiter expression is found.
67
   */
68
  @Override
69
  public int getDelimiterUse(
70
      final DelimiterRun opener, final DelimiterRun closer ) {
71
    return 1;
72
  }
73
74
  @Override
75
  public boolean canBeOpener( final String before,
76
                              final String after,
77
                              final boolean leftFlanking,
78
                              final boolean rightFlanking,
79
                              final boolean beforeIsPunctuation,
80
                              final boolean afterIsPunctuation,
81
                              final boolean beforeIsWhitespace,
82
                              final boolean afterIsWhiteSpace ) {
83
    return leftFlanking;
84
  }
85
86
  @Override
87
  public boolean canBeCloser( final String before,
88
                              final String after,
89
                              final boolean leftFlanking,
90
                              final boolean rightFlanking,
91
                              final boolean beforeIsPunctuation,
92
                              final boolean afterIsPunctuation,
93
                              final boolean beforeIsWhitespace,
94
                              final boolean afterIsWhiteSpace ) {
95
    return rightFlanking;
96
  }
97
98
  @Override
99
  public Node unmatchedDelimiterNode(
100
      final InlineParser inlineParser, final DelimiterRun delimiter ) {
101
    return null;
102
  }
103
104
  @Override
105
  public boolean skipNonOpenerCloser() {
106
    return false;
107
  }
108
}
1109
A src/main/java/com/scrivenvar/processors/markdown/tex/TeXNode.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.markdown.tex;
29
30
import com.vladsch.flexmark.ast.DelimitedNodeImpl;
31
32
public class TeXNode extends DelimitedNodeImpl {
33
34
  public TeXNode() {
35
  }
36
37
}
138
A src/main/java/com/scrivenvar/processors/markdown/tex/TeXNodeRenderer.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.markdown.tex;
29
30
import com.vladsch.flexmark.html.HtmlWriter;
31
import com.vladsch.flexmark.html.renderer.NodeRenderer;
32
import com.vladsch.flexmark.html.renderer.NodeRendererContext;
33
import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
34
import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
35
import com.vladsch.flexmark.util.data.DataHolder;
36
import org.jetbrains.annotations.NotNull;
37
import org.jetbrains.annotations.Nullable;
38
39
import java.util.HashSet;
40
import java.util.Set;
41
42
public class TeXNodeRenderer implements NodeRenderer {
43
44
  public static class Factory implements NodeRendererFactory {
45
    @NotNull
46
    @Override
47
    public NodeRenderer apply( @NotNull DataHolder options ) {
48
      return new TeXNodeRenderer();
49
    }
50
  }
51
52
  @Override
53
  public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
54
    final Set<NodeRenderingHandler<?>> set = new HashSet<>();
55
    set.add( new NodeRenderingHandler<>(
56
        TeXNode.class, this::render ) );
57
58
    return set;
59
  }
60
61
  private void render( final TeXNode node,
62
                       final NodeRendererContext context,
63
                       final HtmlWriter html ) {
64
    html.tag( "tex" );
65
    html.raw( node.getText() );
66
    html.closeTag( "tex" );
67
  }
68
}
169
M src/main/java/com/scrivenvar/processors/text/TextReplacementFactory.java
3636
public final class TextReplacementFactory {
3737
38
  private final static TextReplacer APACHE = new StringUtilsReplacer();
39
  private final static TextReplacer AHO_CORASICK = new AhoCorasickReplacer();
38
  private static final TextReplacer APACHE = new StringUtilsReplacer();
39
  private static final TextReplacer AHO_CORASICK = new AhoCorasickReplacer();
4040
4141
  /**
M src/main/java/com/scrivenvar/service/events/Notifier.java
3232
import javafx.stage.Window;
3333
34
import java.io.File;
35
import java.io.FileWriter;
36
import java.io.IOException;
37
import java.io.PrintWriter;
38
import java.util.Observer;
39
4034
/**
4135
 * Provides the application with a uniform way to notify the user of events.
4236
 */
4337
public interface Notifier {
4438
4539
  ButtonType YES = ButtonType.YES;
4640
  ButtonType NO = ButtonType.NO;
4741
  ButtonType CANCEL = ButtonType.CANCEL;
48
49
  /**
50
   * Notifies the user of a problem.
51
   *
52
   * @param message The problem description.
53
   */
54
  void notify( final String message );
55
56
  /**
57
   * Notifies the user about the exception.
58
   *
59
   * @param ex The exception containing a message to show to the user.
60
   */
61
  default void notify( final Exception ex ) {
62
    assert ex != null;
63
64
    log( ex );
65
    notify( ex.getMessage() );
66
  }
67
68
  /**
69
   * Writes the exception to a log file. The log file should be written
70
   * in the System's temporary directory.
71
   *
72
   * @param ex The exception to show in the status bar and log to a file.
73
   */
74
  default void log( final Exception ex ) {
75
    try(
76
        final FileWriter fw = new FileWriter( getLogPath(), true );
77
        final PrintWriter pw = new PrintWriter( fw )
78
    ) {
79
      ex.printStackTrace( pw );
80
    } catch( final IOException ioe ) {
81
      // The notify method will display the message on the status
82
      // bar.
83
    }
84
  }
85
86
  /**
87
   * Returns the fully qualified path to the log file to write to when
88
   * an exception occurs.
89
   *
90
   * @return Location of the log file for writing unexpected exceptions.
91
   */
92
  File getLogPath();
93
94
  /**
95
   * Causes any displayed notifications to disappear.
96
   */
97
  void clear();
9842
9943
  /**
...
12872
   */
12973
  Alert createConfirmation( Window parent, Notification message );
130
131
  /**
132
   * Adds an observer to the list of objects that receive notifications about
133
   * error messages to be presented to the user.
134
   *
135
   * @param observer The observer instance to notify.
136
   */
137
  void addObserver( Observer observer );
13874
}
13975
M src/main/java/com/scrivenvar/service/events/impl/DefaultNotifier.java
3434
import javafx.stage.Window;
3535
36
import java.io.File;
37
import java.nio.file.Paths;
38
import java.util.Observable;
39
40
import static com.scrivenvar.Constants.APP_TITLE;
41
import static com.scrivenvar.Constants.STATUS_BAR_OK;
42
import static com.scrivenvar.Messages.get;
4336
import static javafx.scene.control.Alert.AlertType.CONFIRMATION;
4437
import static javafx.scene.control.Alert.AlertType.ERROR;
4538
4639
/**
47
 * Provides the ability to notify the user of problems.
40
 * Provides the ability to notify the user of events that need attention,
41
 * such as prompting the user to confirm closing when there are unsaved changes.
4842
 */
49
public final class DefaultNotifier extends Observable implements Notifier {
50
51
  private final static String OK = get( STATUS_BAR_OK, "OK" );
52
53
  /**
54
   * Notifies all observer instances of the given message.
55
   *
56
   * @param message The text to display to the user.
57
   */
58
  @Override
59
  public void notify( final String message ) {
60
    if( message != null && !message.isBlank() ) {
61
      setChanged();
62
      notifyObservers( message );
63
    }
64
  }
65
66
  @Override
67
  public void clear() {
68
    notify( OK );
69
  }
43
public final class DefaultNotifier implements Notifier {
7044
7145
  /**
...
11488
  public Alert createError( final Window parent, final Notification message ) {
11589
    return createAlertDialog( parent, ERROR, message );
116
  }
117
118
  @Override
119
  public File getLogPath() {
120
    return Paths.get(
121
        System.getProperty( "java.io.tmpdir" ), APP_TITLE + ".log" ).toFile();
12290
  }
12391
}
A src/main/java/com/scrivenvar/sigils/RSigilOperator.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.sigils;
29
30
import static com.scrivenvar.sigils.YamlSigilOperator.KEY_SEPARATOR_DEF;
31
32
/**
33
 * Brackets variable names between {@link #PREFIX} and {@link #SUFFIX} sigils.
34
 */
35
public class RSigilOperator extends SigilOperator {
36
  public static final char KEY_SEPARATOR_R = '$';
37
38
  public static final String PREFIX = "`r#";
39
  public static final char SUFFIX = '`';
40
41
  private final String mDelimiterBegan =
42
      getUserPreferences().getRDelimiterBegan();
43
  private final String mDelimiterEnded =
44
      getUserPreferences().getRDelimiterEnded();
45
46
  /**
47
   * Returns the given string R-escaping backticks prepended and appended. This
48
   * is not null safe. Do not pass null into this method.
49
   *
50
   * @param key The string to adorn with R token delimiters.
51
   * @return "`r#" + delimiterBegan + variableName+ delimiterEnded + "`".
52
   */
53
  @Override
54
  public String apply( final String key ) {
55
    assert key != null;
56
57
    return PREFIX
58
        + mDelimiterBegan
59
        + entoken( key )
60
        + mDelimiterEnded
61
        + SUFFIX;
62
  }
63
64
  /**
65
   * Transforms a definition key (bracketed by token delimiters) into the
66
   * expected format for an R variable key name.
67
   *
68
   * @param key The variable name to transform, can be empty but not null.
69
   * @return The transformed variable name.
70
   */
71
  public static String entoken( final String key ) {
72
    return "v$" +
73
        YamlSigilOperator.detoken( key )
74
                         .replace( KEY_SEPARATOR_DEF, KEY_SEPARATOR_R );
75
  }
76
}
177
A src/main/java/com/scrivenvar/sigils/SigilOperator.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.sigils;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.preferences.UserPreferences;
32
import com.scrivenvar.service.Options;
33
34
import java.util.function.UnaryOperator;
35
36
/**
37
 * Responsible for updating definition keys to use a machine-readable format
38
 * corresponding to the type of file being edited. This changes a definition
39
 * key name based on some criteria determined by the factory that creates
40
 * implementations of this interface.
41
 */
42
public abstract class SigilOperator implements UnaryOperator<String> {
43
  private static final Options sOptions = Services.load( Options.class );
44
45
  protected static UserPreferences getUserPreferences() {
46
    return sOptions.getUserPreferences();
47
  }
48
}
149
A src/main/java/com/scrivenvar/sigils/YamlSigilOperator.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.sigils;
29
30
import java.util.regex.Pattern;
31
32
import static java.lang.String.format;
33
import static java.util.regex.Pattern.compile;
34
import static java.util.regex.Pattern.quote;
35
36
/**
37
 * Brackets definition keys with token delimiters.
38
 */
39
public class YamlSigilOperator extends SigilOperator {
40
  public static final char KEY_SEPARATOR_DEF = '.';
41
42
  private static final String mDelimiterBegan =
43
      getUserPreferences().getDefDelimiterBegan();
44
  private static final String mDelimiterEnded =
45
      getUserPreferences().getDefDelimiterEnded();
46
47
  /**
48
   * Non-greedy match of key names delimited by definition tokens.
49
   */
50
  private static final String REGEX =
51
      format( "(%s.*?%s)", quote( mDelimiterBegan ), quote( mDelimiterEnded ) );
52
53
  /**
54
   * Compiled regular expression for matching delimited references.
55
   */
56
  public static final Pattern REGEX_PATTERN = compile( REGEX );
57
58
  /**
59
   * Returns the given {@link String} verbatim because variables in YAML
60
   * documents and plain Markdown documents already have the appropriate
61
   * tokenizable syntax wrapped around the text.
62
   *
63
   * @param key Returned verbatim.
64
   */
65
  @Override
66
  public String apply( final String key ) {
67
    return key;
68
  }
69
70
  /**
71
   * Adds delimiters to the given key.
72
   *
73
   * @param key The key to adorn with start and stop definition tokens.
74
   * @return The given key bracketed by definition token symbols.
75
   */
76
  public static String entoken( final String key ) {
77
    assert key != null;
78
    return mDelimiterBegan + key + mDelimiterEnded;
79
  }
80
81
  /**
82
   * Removes start and stop definition key delimiters from the given key. This
83
   * method does not check for delimiters, only that there are sufficient
84
   * characters to remove from either end of the given key.
85
   *
86
   * @param key The key adorned with start and stop definition tokens.
87
   * @return The given key with the delimiters removed.
88
   */
89
  public static String detoken( final String key ) {
90
    final int beganLen = mDelimiterBegan.length();
91
    final int endedLen = mDelimiterEnded.length();
92
93
    return key.length() > beganLen + endedLen
94
        ? key.substring( beganLen, key.length() - endedLen )
95
        : key;
96
  }
97
}
198
A src/main/java/com/scrivenvar/util/BoundedCache.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.util;
29
30
import java.util.LinkedHashMap;
31
import java.util.Map;
32
33
/**
34
 * A map that removes the oldest entry once its capacity (cache size) has
35
 * been reached.
36
 *
37
 * @param <K> The type of key mapped to a value.
38
 * @param <V> The type of value mapped to a key.
39
 */
40
public class BoundedCache<K, V> extends LinkedHashMap<K, V> {
41
  private final int mCacheSize;
42
43
  /**
44
   * Constructs a new instance having a finite size.
45
   *
46
   * @param cacheSize The maximum number of entries.
47
   */
48
  public BoundedCache( final int cacheSize ) {
49
    mCacheSize = cacheSize;
50
  }
51
52
  @Override
53
  protected boolean removeEldestEntry(
54
      final Map.Entry<K, V> eldest ) {
55
    return size() > mCacheSize;
56
  }
57
}
158
M src/main/java/com/scrivenvar/util/ProtocolResolver.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
128
package com.scrivenvar.util;
229
330
import java.io.File;
31
import java.net.MalformedURLException;
432
import java.net.URI;
533
import java.net.URL;
634
7
import static com.scrivenvar.Constants.DEFINITION_PROTOCOL_UNKNOWN;
35
import static com.scrivenvar.util.ProtocolScheme.UNKNOWN;
836
937
/**
1038
 * Responsible for determining the protocol of a resource.
1139
 */
1240
public class ProtocolResolver {
1341
  /**
1442
   * Returns the protocol for a given URI or filename.
1543
   *
1644
   * @param resource Determine the protocol for this URI or filename.
17
   * @return The protocol for the given source.
45
   * @return The protocol for the given resource.
1846
   */
19
  public static String getProtocol( final String resource ) {
47
  public static ProtocolScheme getProtocol( final String resource ) {
2048
    String protocol;
2149
...
4169
    }
4270
43
    return protocol;
71
    return ProtocolScheme.valueFrom( protocol );
4472
  }
4573
4674
  /**
4775
   * Returns the protocol for a given file.
4876
   *
4977
   * @param file Determine the protocol for this file.
5078
   * @return The protocol for the given file.
5179
   */
52
  public static String getProtocol( final File file ) {
80
  private static String getProtocol( final File file ) {
5381
    String result;
5482
5583
    try {
5684
      result = file.toURI().toURL().getProtocol();
57
    } catch( final Exception e ) {
58
      result = DEFINITION_PROTOCOL_UNKNOWN;
85
    } catch( final MalformedURLException ex ) {
86
      // Value guaranteed to avoid identification as a standard protocol.
87
      result = UNKNOWN.toString();
5988
    }
6089
A src/main/java/com/scrivenvar/util/ProtocolScheme.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.util;
29
30
/**
31
 * Represents the type of data encoding scheme used for a universal resource
32
 * indicator.
33
 */
34
public enum ProtocolScheme {
35
  /**
36
   * Denotes either HTTP or HTTPS.
37
   */
38
  HTTP,
39
  /**
40
   * Denotes a local file.
41
   */
42
  FILE,
43
  /**
44
   * Could not determine schema (or is not supported by the application).
45
   */
46
  UNKNOWN;
47
48
  /**
49
   * Answers {@code true} if the given protocol is either HTTP or HTTPS.
50
   *
51
   * @return {@code true} the protocol is either HTTP or HTTPS.
52
   */
53
  public boolean isHttp() {
54
    return this == HTTP;
55
  }
56
57
  /**
58
   * Answers {@code true} if the given protocol is for a local file.
59
   *
60
   * @return {@code true} the protocol is for a local file reference.
61
   */
62
  public boolean isFile() {
63
    return this == FILE;
64
  }
65
66
  /**
67
   * Determines the protocol scheme for a given string.
68
   *
69
   * @param protocol A string representing data encoding protocol scheme.
70
   * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a
71
   * valid value from this enumeration.
72
   */
73
  public static ProtocolScheme valueFrom( String protocol ) {
74
    ProtocolScheme result = UNKNOWN;
75
    protocol = sanitize( protocol );
76
77
    for( final var scheme : values() ) {
78
      // This will match HTTP/HTTPS as well as FILE*, which may be inaccurate.
79
      if( scheme.name().startsWith( protocol ) ) {
80
        result = scheme;
81
        break;
82
      }
83
    }
84
85
    return result;
86
  }
87
88
  /**
89
   * Returns an empty string if the given string to sanitize is {@code null},
90
   * otherwise the given string in uppercase. Uppercase is used to align with
91
   * the enum name.
92
   *
93
   * @param s The string to sanitize, may be {@code null}.
94
   * @return A non-{@code null} string.
95
   */
96
  private static String sanitize( final String s ) {
97
    return s == null ? "" : s.toUpperCase();
98
  }
99
}
1100
M src/main/resources/com/scrivenvar/messages.properties
3333
3434
Main.menu.insert=_Insert
35
Main.menu.insert.bold=Bold
36
Main.menu.insert.italic=Italic
37
Main.menu.insert.superscript=Superscript
38
Main.menu.insert.subscript=Subscript
39
Main.menu.insert.strikethrough=Strikethrough
40
Main.menu.insert.blockquote=Blockquote
41
Main.menu.insert.code=Inline Code
42
Main.menu.insert.fenced_code_block=Fenced Code Block
35
Main.menu.insert.blockquote=_Blockquote
36
Main.menu.insert.code=Inline _Code
37
Main.menu.insert.fenced_code_block=_Fenced Code Block
4338
Main.menu.insert.fenced_code_block.prompt=Enter code here
44
Main.menu.insert.link=Link...
45
Main.menu.insert.image=Image...
46
Main.menu.insert.heading.1=Heading 1
39
Main.menu.insert.link=_Link...
40
Main.menu.insert.image=_Image...
41
Main.menu.insert.heading.1=Heading _1
4742
Main.menu.insert.heading.1.prompt=heading 1
48
Main.menu.insert.heading.2=Heading 2
43
Main.menu.insert.heading.2=Heading _2
4944
Main.menu.insert.heading.2.prompt=heading 2
50
Main.menu.insert.heading.3=Heading 3
45
Main.menu.insert.heading.3=Heading _3
5146
Main.menu.insert.heading.3.prompt=heading 3
52
Main.menu.insert.unordered_list=Unordered List
53
Main.menu.insert.ordered_list=Ordered List
54
Main.menu.insert.horizontal_rule=Horizontal Rule
47
Main.menu.insert.unordered_list=_Unordered List
48
Main.menu.insert.ordered_list=_Ordered List
49
Main.menu.insert.horizontal_rule=_Horizontal Rule
5550
56
Main.menu.view=_View
57
Main.menu.view.refresh=Refresh
51
Main.menu.format=Forma_t
52
Main.menu.format.bold=_Bold
53
Main.menu.format.italic=_Italic
54
Main.menu.format.superscript=Su_perscript
55
Main.menu.format.subscript=Su_bscript
56
Main.menu.format.strikethrough=Stri_kethrough
57
58
Main.menu.definition=_Definition
59
Main.menu.definition.create=_Create
60
Main.menu.definition.insert=_Insert
5861
5962
Main.menu.help=_Help
6063
Main.menu.help.about=About ${Main.title}
6164
6265
# ########################################################################
6366
# Status Bar
6467
# ########################################################################
6568
66
Main.statusbar.text.offset=offset
67
Main.statusbar.line=Line {0} of {1}, ${Main.statusbar.text.offset} {2}
68
Main.statusbar.state.default=OK
69
Main.statusbar.parse.error={0} (near ${Main.statusbar.text.offset} {1})
69
Main.status.text.offset=offset
70
Main.status.line=Line {0} of {1}, ${Main.status.text.offset} {2}
71
Main.status.state.default=OK
72
Main.status.error.parse={0} (near ${Main.status.text.offset} {1})
73
Main.status.error.def.blank=Move the caret to a word before inserting a definition.
74
Main.status.error.def.empty=Create a definition before inserting a definition.
75
Main.status.error.def.missing=No definition value found for ''{0}''.
76
Main.status.error.r=Error with [{0}...]: {1}
7077
7178
# ########################################################################
...
9299
Preferences.definitions.path=File name
93100
Preferences.definitions.path.desc=Absolute path to interpolated string definitions.
101
Preferences.definitions.delimiter.began=Delimiter Prefix
102
Preferences.definitions.delimiter.began.desc=Indicates when a definition key is starting.
103
Preferences.definitions.delimiter.ended=Delimiter Suffix
104
Preferences.definitions.delimiter.ended.desc=Indicates when a definition key is ending.
94105
95106
Preferences.fonts=Editor
M src/main/resources/com/scrivenvar/preview/webview.css
2727
p, blockquote, ul, ol, dl, table, pre {
2828
  margin: 1em 0;
29
  vertical-align: middle;
2930
}
3031
...
212213
img {
213214
  max-width: 100%;
215
}
216
217
/* Required for FlyingSaucer to detect the node.
218
 * See SVGReplacedElementFactory for details.
219
 */
220
svg, tex {
221
  /* Ensure the formulas can be inlined with text. */
222
  display: inline-block;
223
  vertical-align: middle;
214224
}
215225
D src/test/java/com/scrivenvar/definition/TreeItemInterpolatorTest.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import javafx.scene.control.TreeItem;
31
import org.junit.jupiter.api.Test;
32
33
import static java.lang.String.format;
34
import static org.junit.jupiter.api.Assertions.assertEquals;
35
36
public class TreeItemInterpolatorTest {
37
38
  private final static String AUTHOR_FIRST = "FirstName";
39
  private final static String AUTHOR_LAST = "LastName";
40
  private final static String AUTHOR_ALL = "$root.name.first$ $root.name.last$";
41
42
  /**
43
   * Test that a hierarchical relationship of {@link TreeItem} instances can
44
   * create a flat map with all string values containing key names interpolated.
45
   */
46
  @Test
47
  public void test_Resolve_ReferencesInTree_InterpolatedMap() {
48
    final var root = new TreeItem<>( "root" );
49
    final var name = new TreeItem<>( "name" );
50
    final var first = new TreeItem<>( "first" );
51
    final var authorFirst = new TreeItem<>( AUTHOR_FIRST );
52
    final var last = new TreeItem<>( "last" );
53
    final var authorLast = new TreeItem<>( AUTHOR_LAST );
54
    final var full = new TreeItem<>( "full" );
55
    final var expr = new TreeItem<>( AUTHOR_ALL );
56
57
    root.getChildren().add( name );
58
    name.getChildren().add( first );
59
    name.getChildren().add( last );
60
    name.getChildren().add( full );
61
62
    first.getChildren().add( authorFirst );
63
    last.getChildren().add( authorLast );
64
    full.getChildren().add( expr );
65
66
    final var map = TreeItemAdapter.toMap( root );
67
68
    var actualAuthor = map.get( "$root.name.full$" );
69
    var expectedAuthor = AUTHOR_ALL;
70
    assertEquals( expectedAuthor, actualAuthor );
71
72
    MapInterpolator.interpolate( map );
73
    actualAuthor = map.get( "$root.name.full$" );
74
75
    expectedAuthor = format( "%s %s", AUTHOR_FIRST, AUTHOR_LAST );
76
    assertEquals( expectedAuthor, actualAuthor );
77
  }
78
}
791
A src/test/java/com/scrivenvar/tex/TeXRasterization.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.tex;
29
30
import com.whitemagicsoftware.tex.DefaultTeXFont;
31
import com.whitemagicsoftware.tex.TeXEnvironment;
32
import com.whitemagicsoftware.tex.TeXFormula;
33
import com.whitemagicsoftware.tex.TeXLayout;
34
import com.whitemagicsoftware.tex.graphics.AbstractGraphics2D;
35
import com.whitemagicsoftware.tex.graphics.SvgDomGraphics2D;
36
import com.whitemagicsoftware.tex.graphics.SvgGraphics2D;
37
import org.junit.jupiter.api.Test;
38
import org.xml.sax.SAXException;
39
40
import javax.imageio.ImageIO;
41
import javax.xml.parsers.DocumentBuilderFactory;
42
import javax.xml.parsers.ParserConfigurationException;
43
import java.awt.image.BufferedImage;
44
import java.io.ByteArrayInputStream;
45
import java.io.File;
46
import java.io.IOException;
47
import java.nio.file.Path;
48
49
import static com.scrivenvar.preview.SvgRasterizer.*;
50
import static java.lang.System.getProperty;
51
import static org.junit.jupiter.api.Assertions.assertEquals;
52
53
/**
54
 * Test that TeX rasterization produces a readable image.
55
 */
56
public class TeXRasterization {
57
  private static final String LOAD_EXTERNAL_DTD =
58
      "http://apache.org/xml/features/nonvalidating/load-external-dtd";
59
60
  private static final String EQUATION =
61
      "G_{\\mu \\nu} = \\frac{8 \\pi G}{c^4} T_{{\\mu \\nu}}";
62
63
  private static final String DIR_TEMP = getProperty( "java.io.tmpdir" );
64
65
  private static final long FILESIZE = 12547;
66
67
  /**
68
   * Test that an equation can be converted to a raster image and the
69
   * final raster image size corresponds to the input equation. This is
70
   * a simple way to verify that the rasterization process is correct,
71
   * albeit if any aspect of the SVG algorithm changes (such as padding
72
   * around the equation), it will cause this test to fail, which is a bit
73
   * misleading.
74
   */
75
  @Test
76
  public void test_Rasterize_SimpleFormula_CorrectImageSize()
77
      throws IOException {
78
    final var g = new SvgGraphics2D();
79
    drawGraphics( g );
80
    verifyImage( rasterizeString( g.toString() ) );
81
  }
82
83
  /**
84
   * Test that an SVG document object model can be parsed and rasterized into
85
   * an image.
86
   */
87
  @Test
88
  public void getTest_SvgDomGraphics2D_InputElement_OutputRasterizedImage()
89
      throws ParserConfigurationException, IOException, SAXException {
90
    final var g = new SvgGraphics2D();
91
    drawGraphics( g );
92
93
    final var expectedSvg = g.toString();
94
    final var bytes = expectedSvg.getBytes();
95
96
    final var dbf = DocumentBuilderFactory.newInstance();
97
    dbf.setFeature( LOAD_EXTERNAL_DTD, false );
98
    dbf.setNamespaceAware( false );
99
    final var builder = dbf.newDocumentBuilder();
100
101
    final var doc = builder.parse( new ByteArrayInputStream( bytes ) );
102
    final var actualSvg = toSvg( doc.getDocumentElement() );
103
104
    verifyImage( rasterizeString( actualSvg ) );
105
  }
106
107
  /**
108
   * Test that an SVG image from a DOM element can be rasterized.
109
   *
110
   * @throws IOException Could not write the image.
111
   */
112
  @Test
113
  public void test_SvgDomGraphics2D_InputDom_OutputRasterizedImage()
114
      throws IOException {
115
    final var g = new SvgDomGraphics2D();
116
    drawGraphics( g );
117
118
    final var dom = g.toDom();
119
120
    verifyImage( rasterize( dom ) );
121
  }
122
123
  /**
124
   * Asserts that the given image matches an expected file size.
125
   *
126
   * @param image The image to check against the file size.
127
   * @throws IOException Could not write the image.
128
   */
129
  private void verifyImage( final BufferedImage image ) throws IOException {
130
    final var file = export( image, "dom.png" );
131
    assertEquals( FILESIZE, file.length() );
132
  }
133
134
  /**
135
   * Creates an SVG string for the default equation and font size.
136
   */
137
  private void drawGraphics( final AbstractGraphics2D g ) {
138
    final var size = 100f;
139
    final var texFont = new DefaultTeXFont( size );
140
    final var env = new TeXEnvironment( texFont );
141
    g.scale( size, size );
142
143
    final var formula = new TeXFormula( EQUATION );
144
    final var box = formula.createBox( env );
145
    final var layout = new TeXLayout( box, size );
146
147
    g.initialize( layout.getWidth(), layout.getHeight() );
148
    box.draw( g, layout.getX(), layout.getY() );
149
  }
150
151
  @SuppressWarnings("SameParameterValue")
152
  private File export( final BufferedImage image, final String filename )
153
      throws IOException {
154
    final var path = Path.of( DIR_TEMP, filename );
155
    final var file = path.toFile();
156
    ImageIO.write( image, "png", file );
157
    file.deleteOnExit();
158
    return file;
159
  }
160
}
1161
A testing/.gitignore
1
*.class
12
A testing/demo.sikuli/1594187265140.png
Binary file
A testing/demo.sikuli/1594592396134.png
Binary file
A testing/demo.sikuli/1594593710440.png
Binary file
A testing/demo.sikuli/1594593794335.png
Binary file
A testing/demo.sikuli/1594594984108.png
Binary file
A testing/demo.sikuli/1594689573764.png
Binary file
A testing/demo.sikuli/demo.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# Runs all scripts
26
# -----------------------------------------------------------------------------
27
28
import s01
29
import s02
30
import s03
31
import s04
132
A testing/demo.sikuli/s01.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script introduces the editor and its purpose.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
# ---------------------------------------------------------------
36
# Fresh start
37
# ---------------------------------------------------------------
38
rm( app_home + "/variables.yaml" )
39
rm( app_home + "/untitled.md" )
40
rm( dir_home + "/.scrivenvar" )
41
42
# ---------------------------------------------------------------
43
# Wait for application to launch
44
# ---------------------------------------------------------------
45
openApp( "java -jar " + app_bin )
46
47
wait("1594187265140.png", 30)
48
49
# Breathing room for video recording.
50
wait( 4 )
51
52
# ---------------------------------------------------------------
53
# Introduction
54
# ---------------------------------------------------------------
55
set_typing_speed( 240 )
56
57
heading( "What is this application?" )
58
typer( "Well, this application is a text editor that supports interpolated definitions, ")
59
typer( "a few different text formats, real-time preview, spell check ") 
60
typer( "as you tipe" ) 
61
wait( 0.5 )
62
recur( 3, backspace )
63
typer( "ype, and R statements." )
64
paragraph()
65
wait( 1 )
66
67
# ---------------------------------------------------------------
68
# Definition demo
69
# ---------------------------------------------------------------
70
heading( "What are definitions?" )
71
typer( "Watch. " )
72
wait( .5 )
73
74
# Focus the definition editor.
75
click_create()
76
recur( 4, tab )
77
78
wait( .5 )
79
rename_definition( "application" )
80
81
insert()
82
rename_definition( "title" )
83
84
insert()
85
rename_definition( "Scrivenvar" )
86
87
# Set focus to the text editor.
88
tab()
89
90
typer( "The left-hand pane contains a nested, folder-like structure of names " )
91
typer( "and values that are called *definitions*. " )
92
wait( .5 )
93
typer( "Such definitions can simplify updating documents. " )
94
wait( 1 )
95
96
edit_find( "this application" )
97
typer( "$application.title$" )
98
99
edit_find_next()
100
typer( "$application.title$" )
101
102
type( Key.END, Key.CTRL )
103
104
typer( "The right-hand pane shows the result after having substituted definition " )
105
typer( "values into the document." ) 
106
107
paragraph()
108
typer( "Now nobody wants to type definition names all the time. Instead, type any " )
109
typer( "partial definition value followed by `Ctrl+Space`, such as: scr" )
110
wait( 0.5 )
111
autoinsert()
112
wait( 1 )
113
typer( ". *Much* better!" )
114
paragraph()
115
116
heading( "What is interpolation?" )
117
typer( "Definition values can reference definition names. " )
118
wait( .5 )
119
typer( "The definition names act as placeholders. Substituting placeholders with " )
120
typer( "their definition value is called *interpolation*. Let's see how it works." )
121
wait( 2 )
1122
A testing/demo.sikuli/s02.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script demonstrates how to use interpolated strings.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
# -----------------------------------------------------------------------------
36
# Open sample chapter.
37
# -----------------------------------------------------------------------------
38
file_open()
39
type( Key.UP, Key.ALT )
40
wait( 1 )
41
typer( Key.END )
42
wait( 1 )
43
enter()
44
wait( 0.5 )
45
enter()
46
wait( 1 )
47
48
# -----------------------------------------------------------------------------
49
# Open the corresponding definition file.
50
# -----------------------------------------------------------------------------
51
file_open()
52
recur( 2, down )
53
wait( 1 )
54
enter()
55
wait( 1 )
56
57
# -----------------------------------------------------------------------------
58
# Edit the sample document.
59
# -----------------------------------------------------------------------------
60
set_typing_speed( 80 )
61
62
type( Key.HOME, Key.CTRL )
63
recur( 2, down )
64
65
# Grey
66
recur( 3, skip_right )
67
autoinsert()
68
69
# 34
70
recur( 4, skip_right )
71
autoinsert()
72
73
# Central
74
recur( 10, skip_right )
75
autoinsert()
76
77
# London
78
skip_right()
79
autoinsert()
80
81
# Hatchery
82
skip_right()
83
autoinsert()
84
85
# and Conditioning
86
recur( 2, select_word_right )
87
delete()
88
89
# Centre
90
skip_right()
91
autoinsert()
92
93
set_typing_speed( 220 )
94
95
typer( " Let's interpolate those four definitions instead!" )
96
wait( 4 )
97
recur( 13, type, Key.BACKSPACE, Key.CTRL )
98
recur( 9, backspace )
99
100
set_typing_speed( 60 )
101
102
typer( "name$" )
103
wait( 2 )
104
105
# Collapse all definitions
106
tab()
107
recur( 8, typer, Key.LEFT )
108
109
# Expand to city
110
recur( 4, typer, Key.RIGHT )
111
112
# Jump to name
113
recur( 2, down )
114
recur( 2, typer, Key.RIGHT )
115
116
# Open the text field to show the full value
117
typer( Key.F2 )
118
119
# Traverse the text field
120
home()
121
recur( 16, type, Key.RIGHT, Key.CTRL )
122
esc()
123
124
restore_typing_speed()
125
126
tab()
127
type( Key.HOME, Key.CTRL )
128
edit_find( "Director" )
129
autoinsert()
130
131
edit_find_next()
132
autoinsert()
133
134
edit_find_next()
135
typer( Key.RIGHT )
136
recur( 2, delete )
137
autoinsert()
138
typer( "'s" )
139
140
edit_find( "Hatcheries" )
141
autoinsert()
142
143
# and Conditioning
144
recur( 2, select_word_right )
145
delete()
146
147
edit_find( "Central" )
148
autoinsert()
149
150
skip_right()
151
autoinsert()
152
153
typer( " How about a different city?" )
154
wait( 2 )
155
recur( 5, type, Key.BACKSPACE, Key.CTRL )
156
wait( 1 )
157
tab()
158
typer( Key.F2 )
159
typer( "Seattle" )
160
enter()
161
tab()
162
wait( 2 )
163
164
type( Key.END, Key.CTRL )
165
paragraph()
166
typer( "No?" )
167
paragraph()
168
169
tab()
170
typer( Key.F2 )
171
typer( "London" )
172
enter()
173
174
tab()
175
typer( "Organizing definitions is left to your ")
176
typer( "doub" )
177
autoinsert()
178
typer( " Good imagination." )
179
tab()
180
181
# Jump to "char" definition
182
home()
183
184
# Jump to "char.a.primary.name" definition
185
recur( 6, typer, Key.RIGHT )
186
187
# Jump to "char.a.primary.caste" definition
188
down()
189
typer( Key.RIGHT )
190
191
# Jump to root-level "caste" definition
192
recur( 7, down )
193
194
# Reselect "super"
195
recur( 5, typer, Key.RIGHT )
196
wait( 2 )
197
198
# Close the window, no save
199
type( "w", Key.CTRL )
200
wait( 0.5 )
201
tab()
202
wait( 0.5 )
203
typer( Key.SPACE )
204
wait( 1 )
1205
A testing/demo.sikuli/s03.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script introduces images and R.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
set_typing_speed( 80 )
36
37
file_open()
38
type( Key.UP, Key.ALT )
39
wait( 0.5 )
40
home()
41
wait( 0.25 )
42
enter()
43
wait( 1 )
44
end()
45
wait( 0.25 )
46
enter()
47
wait( 1 )
48
49
set_typing_speed( 200 )
50
51
paragraph()
52
heading( "What text formats are supported?" )
53
54
typer( "Scr" )
55
autoinsert()
56
typer( " supports Markdown, R Markdown, XML, and R XML; however, the software " )
57
typer( "architecture enables it to easily add new formats. The following figure " )
58
typer( "depicts the overall architecture: " )
59
paragraph()
60
typer( "![](../writing/images/architecture)" )
61
paragraph()
62
typer( "Many text editors can only open one type of plain text markup format that is " )
63
typer( "only output as HTML. With a little more effort, text editors could support " )
64
typer( "multiple input and output formats. Scr" )
65
autoinsert()
66
typer( " does so and goes one step further by introducing interpolated definitions." )
67
paragraph()
68
typer( "Kitten interlude:" )
69
paragraph()
70
typer( "![](https://i.imgur.com/jboueQH.jpg)" )
71
paragraph()
72
73
heading( "What is R?" )
74
typer( "R is a programming language. You might have noticed a few potential grammar " )
75
typer( "problems with direct substitution. Rules for possessive forms, numbers, and " )
76
typer( "other quirks can be tackled using R." )
77
78
# -----------------------------------------------------------------------------
79
# Demo bootstrapping
80
# -----------------------------------------------------------------------------
81
82
# Jump to the end
83
type( Key.END, Key.CTRL )
84
paragraph()
85
86
set_typing_speed( 300 )
87
heading( "How is R used?" )
88
typer( "R must be instructed where to find script files and what ones to load. The " )
89
typer( "*working directory* is the full path to those R files; the *startup script* " )
90
typer( "defines what R files to load. Both preferences must be changed before prose " )
91
typer( "may be processed. Preferences can be opened using either the " )
92
typeln( "**Edit > Preferences** menu or by pressing `Ctrl+Alt+s`. Here goes!" ) 
93
wait( 2 )
94
95
# -----------------------------------------------------------------------------
96
# Select the R script directory
97
# -----------------------------------------------------------------------------
98
99
# Change the working directory by clicking "Browse"
100
type( "s", Key.CTRL + Key.ALT )
101
wait("1594592396134.png", 1)
102
click("1594592396134.png")
103
wait( 0.5 )
104
105
# Navigate to and select the "r" directory
106
type( Key.UP, Key.ALT )
107
wait( 0.5 )
108
end()
109
wait( 0.5 )
110
enter()
111
wait( 0.5 )
112
end()
113
wait( 0.5 )
114
type( Key.UP )
115
wait( 0.5 )
116
recur( 2, tab )
117
wait( 0.5 )
118
enter()
119
wait( 1 )
120
121
# -----------------------------------------------------------------------------
122
# Set the R startup script instructions
123
# -----------------------------------------------------------------------------
124
125
wait("1594593710440.png", 5)
126
click("1594593710440.png")
127
128
set_typing_speed( 440 )
129
130
typeln( "setwd( '$application.r.working.directory$' )" )
131
typeln( "assign( 'anchor', '$date.anchor$', envir = .GlobalEnv )" )
132
typeln( "source( 'pluralize.R' )" )
133
typeln( "source( 'possessive.R' )" )
134
typeln( "source( 'conversion.R' )" )
135
typeln( "source( 'csv.R' )" )
136
137
wait("1594593794335.png", 3)
138
click("1594593794335.png")
139
140
paragraph()
141
set_typing_speed( 220 )
142
143
typer( "R is now configured. The startup script and other R " )
144
typer( "files can be found in the " )
145
typer( "[repository](https://github.com/DaveJarvis/scrivenvar/tree/master/R). " )
146
wait( 1.5 )
147
148
# Wait for the browser to appear.
149
wait("1594594984108.png", 5)
150
click("1594594984108.png")
151
152
wait( 5 )
153
click("1594689573764.png")
154
155
paragraph()
156
typer( "Next, we'll see how definitions and R can work together." )
157
wait( 2 )
1158
A testing/demo.sikuli/s04.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script demonstrates using R.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
set_typing_speed( 220 )
36
37
# -----------------------------------------------------------------------------
38
# Open the demo text.
39
# -----------------------------------------------------------------------------
40
file_open()
41
type( Key.UP, Key.ALT )
42
wait( 0.5 )
43
end()
44
wait( 0.5 )
45
enter()
46
wait( 0.5 )
47
down()
48
wait( 0.5 )
49
enter()
50
wait( 2 )
51
52
# -----------------------------------------------------------------------------
53
# Re-open the corresponding definition file.
54
# -----------------------------------------------------------------------------
55
file_open()
56
recur( 2, down )
57
wait( 1 )
58
enter()
59
wait( 2 )
60
61
# -----------------------------------------------------------------------------
62
# Brief introduction to R
63
# -----------------------------------------------------------------------------
64
type( Key.HOME, Key.CTRL )
65
end()
66
paragraph()
67
68
typer( "## Using R" )
69
paragraph()
70
typer( "Insert R code into documents as follows: `r# 1+1`. " )
71
wait( 1.5 )
72
typer( "Notice how the right-hand pane shows the computed result. I'll wait. " )
73
wait( 3 )
74
typer( "The syntax is: open backtick, r#, *computable expression*, close " )
75
typer( "backtick. That expression can be any valid R statement. The status bar " ) 
76
typer( "will provide clues when an R expression cannot be computed by the " )
77
typer( "editor. `r# glitch`" )
78
wait( 4 )
79
recur( 11, backspace )
80
typer( "Let's swap 34 storeys for a definition value and replace the number " )
81
typer( "according to the Chicago Manual of Style (cms) rules." )
82
83
# -----------------------------------------------------------------------------
84
# Demo pluralization
85
# -----------------------------------------------------------------------------
86
set_typing_speed( 80 )
87
88
edit_find( "34" )
89
autoinsert()
90
91
edit_find( "x(" )
92
typer( "cms(" )
93
94
edit_find( "storeys." )
95
typer( "34." )
96
autoinsert()
97
edit_find( "x(" )
98
typer( "pl( 'storey'," )
99
wait( 4 )
100
101
tab()
102
rename_definition( "1" )
103
wait( 4 )
104
rename_definition( "142" )
105
wait( 4 )
106
rename_definition( "34" )
107
wait( 4 )
108
tab()
109
110
# -----------------------------------------------------------------------------
111
# Demo possessives (it, her, his, Director)
112
# -----------------------------------------------------------------------------
113
type( Key.HOME, Key.CTRL )
114
edit_find( "Director" )
115
autoinsert()
116
edit_find_next()
117
autoinsert()
118
edit_find_next()
119
autoinsert()
120
type( Key.RIGHT )
121
recur( 2, delete )
122
autoinsert()
123
home()
124
edit_find( "x(" )
125
typer( "pos(" )
126
wait( 2 )
127
128
tab()
129
rename_definition( "Headmistress" )
130
wait( 4 )
131
rename_definition( "Director" )
132
wait( 2 )
133
tab()
134
135
type( Key.END, Key.CTRL )
136
paragraph()
137
typer( "Other possessives: `r# pos( 'it' )`, `r# pos( 'her' )`, `r# pos( 'his' )`, " )
138
typer( "and `r# pos( 'my' )`." )
139
140
# -----------------------------------------------------------------------------
141
# Demo conversion, including ordinal numbers
142
# -----------------------------------------------------------------------------
143
set_typing_speed( 160 )
144
145
paragraph()
146
heading( "Date Conversions" )
147
typer( "Mixing R code with definitions invites endless possibilities. " )
148
typer( "Imagine someone racing to the " ) 
149
typer( "`r#cms( v$location$breeder$storeys, ordinal=TRUE )` floor, whereby that " )
150
typer( "ordinal stems from the Hatchery's storeys' definition. Or how about " )
151
typer( "a complex timeline where dates are expressed in days relative to one " )
152
typer( "point in time. Let's call this the *anchor date* and define it." )
153
154
tab()
155
home()
156
typer( Key.SPACE )
157
insert()
158
rename_definition( "date" )
159
insert()
160
rename_definition( "anchor" )
161
insert()
162
rename_definition( "1969-10-29" )
163
tab()
164
165
paragraph()
166
typer( "Next, set an R variable named `now` to the current date" )
167
typer( "`r# now = format( Sys.time(), '%Y-%m-%d' ); ''`--- the empty single quotes " )
168
typer( "prevent the date from appearing in the output document. " )
169
170
paragraph()
171
typer( "We set the anchor date to `r# annal()`, which was " )
172
typer( "`r# elapsed( 0, days( v$date$anchor, format( Sys.time(), '%Y-%m-%d' ) ) )` " )
173
typer( "ago from `r# format( as.Date( now ), '%B %d, %Y' )`. " )
174
175
# -----------------------------------------------------------------------------
176
# Demo CSV file import
177
# -----------------------------------------------------------------------------
178
paragraph()
179
heading( "Tabular Data" )
180
typer( "The following table shows average Canadian lifespans by birth " )
181
typer( "year and sex:" )
182
paragraph()
183
typer( "`r# csv2md( '../data.csv', total=FALSE )`" )
184
paragraph()
185
typer( "Calling `csv2md` converts the comma-separated values in the spreadsheet " )
186
typer( "to a table formatted using Markdown. The HTML preview pane changes the " )
187
typer( "appearance of the resulting table. Using `../data.csv` instructs R to " )
188
typer( "open `data.csv` from one directory above the *working directory*." )
189
190
# -----------------------------------------------------------------------------
191
# Demo HTML export
192
# -----------------------------------------------------------------------------
193
paragraph()
194
heading( "Export" )
195
typer( "Retrieve the output HTML by using the **Edit > Copy HTML** menu. Let's " )
196
typer( "peek at the output." )
197
wait( 2 )
198
199
type( "e", Key.ALT )
200
wait( 0.5 )
201
down()
202
wait( 0.25 )
203
enter()
204
wait( 0.25 )
205
206
type( "a", Key.CTRL )
207
wait( 0.25 )
208
type( "v", Key.CTRL )
209
wait( 5 )
210
211
set_typing_speed( 40 )
212
213
# Jump to page bottom (should already be there, but just in case)
214
type( Key.END, Key.CTRL )
215
recur( 3, typer, Key.PAGE_UP )
216
type( Key.HOME, Key.CTRL )
217
wait( 3 )
218
219
set_typing_speed( 220 )
220
type( "z", Key.CTRL )
221
type( Key.END, Key.CTRL )
222
223
paragraph()
224
typer( "That's all for now, thank you!" )
225
wait( 5 )
226
227
# Delete the anchor date.
228
tab()
229
end()
230
recur( 2, type, Key.UP )
231
delete()
232
tab()
1233
A testing/demo.sikuli/test.py
1
from sikuli import *
2
3
import sys
4
import os
5
6
def set_class_path():
7
    path_script = getBundlePath()
8
    dir_script = os.path.dirname( path_script )
9
    path_lib = dir_script + "/keycast/build/libs/keycast.jar"
10
    
11
    sys.path.append( path_lib )
12
13
def launch():
14
    from com.whitemagicsoftware.keycast import KeyCast
15
    kc = KeyCast()
16
    kc.show()
17
18
def main():
19
    set_class_path()
20
    launch()
21
   
22
23
if __name__ == "__main__":
24
    main()
125
A testing/editor.sikuli/1594187923258.png
Binary file
A testing/editor.sikuli/editor.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script contains helper functions used by the other scripts.
26
#
27
# Do not run this script.
28
# -----------------------------------------------------------------------------
29
30
from sikuli import *
31
import sys
32
import os
33
from os.path import expanduser
34
35
dir_home = expanduser( "~" )
36
app_home = dir_home + "/bin"
37
app_bin = app_home + "/scrivenvar.jar"
38
39
wpm_typing_speed = 80
40
41
# -----------------------------------------------------------------------------
42
# Try to delete the file pointed to by the path variable. If there is no such
43
# file, this will silently ignore the exception.
44
# -----------------------------------------------------------------------------
45
def rm( path ):
46
    try:
47
        os.remove( path )
48
    except:
49
        print "Ignored"
50
51
# -----------------------------------------------------------------------------
52
# Changes the current typing speed, where speed is given in words per minute.
53
# -----------------------------------------------------------------------------
54
def set_typing_speed( wpm ):
55
    global wpm_typing_speed
56
    wpm_typing_speed = wpm
57
58
# -----------------------------------------------------------------------------
59
# Creates a delay between keystrokes to emulate typing at a particular speed.
60
# -----------------------------------------------------------------------------
61
def random_wait():
62
    from time import sleep
63
    from random import uniform
64
    cpm = wpm_typing_speed * 5.1
65
    cps = cpm / 60.0
66
    ms_per_char = 1000.0 / cps
67
    ms_per_stroke = ms_per_char / 2.0
68
69
    noise = uniform( 0, ms_per_stroke / 2 )
70
    duration = (ms_per_stroke + noise ) / 1000
71
    
72
    sleep( duration )
73
74
# -----------------------------------------------------------------------------
75
# Repeats a function call, f, n times.
76
# -----------------------------------------------------------------------------
77
def recur( n, f, *args ):
78
    for i in range( n ):
79
        f( *args )
80
        random_wait()
81
82
# -----------------------------------------------------------------------------
83
# Emulate a typist who is typing in the given text.
84
# -----------------------------------------------------------------------------
85
def typer( text ):
86
    for c in text:
87
        type( c )
88
        random_wait()
89
90
# -----------------------------------------------------------------------------
91
# Type a line of text followed by typing the ENTER key.
92
# -----------------------------------------------------------------------------
93
def typeln( text ):
94
    typer( text )
95
    enter()
96
97
# -----------------------------------------------------------------------------
98
# Injects a definition.
99
# -----------------------------------------------------------------------------
100
def autoinsert():
101
    type( Key.SPACE, Key.CTRL )
102
    random_wait()
103
104
# -----------------------------------------------------------------------------
105
# Types the TAB key.
106
# -----------------------------------------------------------------------------
107
def tab():
108
    typer( Key.TAB )
109
110
# -----------------------------------------------------------------------------
111
# Types the ENTER key.
112
# -----------------------------------------------------------------------------
113
def enter():
114
    typer( Key.ENTER )
115
116
# -----------------------------------------------------------------------------
117
# Types the ESC key.
118
# -----------------------------------------------------------------------------
119
def esc():
120
    typer( Key.ESC )
121
122
# -----------------------------------------------------------------------------
123
# Types the DOWN arrow key.
124
# -----------------------------------------------------------------------------
125
def down():
126
    typer( Key.DOWN )
127
128
# -----------------------------------------------------------------------------
129
# Types the HOME key.
130
# -----------------------------------------------------------------------------
131
def home():
132
    typer( Key.HOME )
133
134
# -----------------------------------------------------------------------------
135
# Types the END key.
136
# -----------------------------------------------------------------------------
137
def end():
138
    typer( Key.END )
139
140
# -----------------------------------------------------------------------------
141
# Types the BACKSPACE key.
142
# -----------------------------------------------------------------------------
143
def backspace():
144
    typer( Key.BACKSPACE )
145
146
# -----------------------------------------------------------------------------
147
# Types the INSERT key, often to insert a new definition.
148
# -----------------------------------------------------------------------------
149
def insert():
150
    typer( Key.INSERT )
151
152
# -----------------------------------------------------------------------------
153
# Types the DELETE key, often to remove selected text.
154
# -----------------------------------------------------------------------------
155
def delete():
156
    typer( Key.DELETE )
157
158
# -----------------------------------------------------------------------------
159
# Moves the cursor one word to the right.
160
# -----------------------------------------------------------------------------
161
def skip_right():
162
    type( Key.RIGHT, Key.CTRL )
163
    random_wait()
164
165
def select_word_right():
166
    type( Key.RIGHT, Key.CTRL + Key.SHIFT )
167
    random_wait()
168
169
# -----------------------------------------------------------------------------
170
# Types ENTER twice to begin a new paragraph.
171
# -----------------------------------------------------------------------------
172
def paragraph():
173
    recur( 2, enter )
174
    wait( 1.5 )
175
176
# -----------------------------------------------------------------------------
177
# Writes a heading to the document using the given text value as the content.
178
# -----------------------------------------------------------------------------
179
def heading( text ):
180
    typer( "# " + text )
181
    paragraph()
182
183
# -----------------------------------------------------------------------------
184
# Clicks the "Create" button to add a new definition.
185
# -----------------------------------------------------------------------------
186
def click_create():
187
    click("1594187923258.png")
188
    wait( .5 )
189
190
# -----------------------------------------------------------------------------
191
# Changes the text for the actively selected definition.
192
# -----------------------------------------------------------------------------
193
def rename_definition( text ):
194
    typer( Key.F2 )
195
    typer( text )
196
    enter()
197
    wait( .5 )
198
199
# -----------------------------------------------------------------------------
200
# Searches for the given text within the document.
201
# -----------------------------------------------------------------------------
202
def edit_find( text ):
203
    type( "f", Key.CTRL )
204
    typer( text )
205
    enter()
206
    wait( .25 )
207
    esc()
208
    wait( .5 )
209
210
# -----------------------------------------------------------------------------
211
# Searches for the next occurrence of the previous search term.
212
# -----------------------------------------------------------------------------
213
def edit_find_next():
214
    typer( Key.F3 )
215
    wait( .5 )
216
217
# -----------------------------------------------------------------------------
218
# Opens a dialog for selecting a file.
219
# -----------------------------------------------------------------------------
220
def file_open():
221
    type( "o", Key.CTRL )
222
    wait( 1 )
1223
D tex/texmml.xml
1
<?xml version="1.0" encoding="utf-8"?>
2
3
<pat:tex2mmlmap xmlns:pat="http://www.orcca.on.ca/mathml/tex2mml.xml" xmlns="http://www.w3.org/1998/Math/MathML" version="0.13">
4
5
6
7
<!-- ========================================================================== -->
8
9
<!-- NOTE: the precedences only have effect when translating from TeX to MathML -->
10
11
<!-- NOTE: direct use of <, & and " or ' is not legal in XML; use entities instead -->
12
13
<!-- NOTE: <pat:mml> has implied <mrow>, so <mrow> does not have to be added explicitly -->
14
15
16
17
<!-- =================== Approximated characters ==================== -->
18
19
<!-- \shortmid \shortparallel \smallsetminus \jmath \longrightarrow \scshape
20
     \lgroup \rgroup \arrowvert \Arrowvert \bracevert \leftarrowfill \rightarrowfill \mspace
21
-->
22
23
24
25
<!-- =========================== TO DO ============================== -->
26
27
<!-- \above \abovewithdelims \penalty -->
28
29
30
31
<!-- ************************************************************************* -->
32
<!-- *** The following are mappings of TEX macros/symbols to MathML markup *** -->
33
<!-- ************************************************************************* -->
34
35
36
<!-- Matrices & Arrays -->
37
38
<pat:template>
39
  <pat:tex op="\begin" params="{matrix} \patREP+{\patVAR+{firstCol}\patREP*{&amp;\patVAR+{rest}}\\} \end{matrix}"/>
40
  <pat:mml op="mtable">
41
    <mtable>
42
      <pat:rep>
43
        <mtr>
44
          <mtd> <pat:variable name="firstCol"/> </mtd>
45
          <pat:rep>
46
            <mtd> <pat:variable name="rest"/> </mtd>
47
          </pat:rep>
48
        </mtr>
49
      </pat:rep>
50
    </mtable>
51
  </pat:mml>
52
</pat:template>
53
54
<pat:template>
55
  <pat:tex op="\begin" params="{smallmatrix} \patREP+{\patVAR+{firstCol}\patREP*{&amp;\patVAR+{rest}}\\} \end{smallmatrix}"/>
56
  <pat:mml op="mtable">
57
    <mtable>
58
      <pat:rep>
59
        <mtr>
60
          <mtd> <pat:variable name="firstCol"/> </mtd>
61
          <pat:rep>
62
            <mtd> <pat:variable name="rest"/> </mtd>
63
          </pat:rep>
64
        </mtr>
65
      </pat:rep>
66
    </mtable>
67
  </pat:mml>
68
</pat:template>
69
70
<pat:template>
71
  <pat:tex op="\matrix" params="{\patREP+{\patVAR+{firstCol}\patREP*{&amp;\patVAR+{rest}}\cr}}"/>
72
  <pat:mml op="mtable">
73
    <mtable>
74
      <pat:rep>
75
        <mtr>
76
          <mtd> <pat:variable name="firstCol"/> </mtd>
77
          <pat:rep>
78
            <mtd> <pat:variable name="rest"/> </mtd>
79
          </pat:rep>
80
        </mtr>
81
      </pat:rep>
82
    </mtable>
83
  </pat:mml>
84
</pat:template>
85
86
87
<pat:template>
88
  <pat:tex op="\begin" params="{pmatrix} \patREP+{\patVAR+{firstCol}\patREP*{&amp;\patVAR+{rest}}\\} \end{pmatrix}"/>
89
  <pat:mml op="mtable">
90
    <mfenced separators="">
91
      <mtable>
92
        <pat:rep>
93
          <mtr>
94
            <mtd> <pat:variable name="firstCol"/> </mtd>
95
            <pat:rep>
96
              <mtd> <pat:variable name="rest"/> </mtd>
97
            </pat:rep>
98
          </mtr>
99
        </pat:rep>
100
      </mtable>
101
    </mfenced>
102
  </pat:mml>
103
</pat:template>
104
105
<pat:template>
106
  <pat:tex op="\pmatrix" params="{\patREP+{\patVAR+{firstCol}\patREP*{&amp;\patVAR+{rest}}\cr}}"/>
107
  <pat:mml op="mtable">
108
    <mfenced separators="">
109
      <mtable>
110
        <pat:rep>
111
          <mtr>
112
            <mtd> <pat:variable name="firstCol"/> </mtd>
113
            <pat:rep>
114
              <mtd> <pat:variable name="rest"/> </mtd>
115
            </pat:rep>
116
          </mtr>
117
        </pat:rep>
118
      </mtable>
119
    </mfenced>
120
  </pat:mml>
121
</pat:template>
122
123
124
<pat:template>
125
  <pat:tex op="\begin" params="{bmatrix} \patREP+{\patVAR+{firstCol}\patREP*{&amp;\patVAR+{rest}}\\} \end{bmatrix}"/>
126
  <pat:mml op="mtable">
127
    <mfenced open="[" close="]" separators="">
128
      <mtable>
129
        <pat:rep>
130
          <mtr>
131
            <mtd> <pat:variable name="firstCol"/> </mtd>
132
            <pat:rep>
133
              <mtd> <pat:variable name="rest"/> </mtd>
134
            </pat:rep>
135
          </mtr>
136
        </pat:rep>
137
      </mtable>
138
    </mfenced>
139
  </pat:mml>
140
</pat:template>
141
142
<pat:template>
143
  <pat:tex op="\begin" params="{Bmatrix} \patREP+{\patVAR+{firstCol}\patREP*{&amp;\patVAR+{rest}}\\} \end{Bmatrix}"/>
144
  <pat:mml op="mtable">
145
    <mfenced open="{" close="}" separators="">
146
      <mtable>
147
        <pat:rep>
148
          <mtr>
149
            <mtd> <pat:variable name="firstCol"/> </mtd>
150
            <pat:rep>
151
              <mtd> <pat:variable name="rest"/> </mtd>
152
            </pat:rep>
153
          </mtr>
154
        </pat:rep>
155
      </mtable>
156
    </mfenced>
157
  </pat:mml>
158
</pat:template>
159
160
<pat:template>
161
  <pat:tex op="\begin" params="{vmatrix} \patREP+{\patVAR+{firstCol}\patREP*{&amp;\patVAR+{rest}}\\} \end{vmatrix}"/>
162
  <pat:mml op="mtable">
163
    <mfenced open="&#x2223;" close="&#x2223;" separators="">
164
      <mtable>
165
        <pat:rep>
166
          <mtr>
167
            <mtd> <pat:variable name="firstCol"/> </mtd>
168
            <pat:rep>
169
              <mtd> <pat:variable name="rest"/> </mtd>
170
            </pat:rep>
171
          </mtr>
172
        </pat:rep>
173
      </mtable>
174
    </mfenced>
175
  </pat:mml>
176
</pat:template>
177
178
<pat:template>
179
  <pat:tex op="\begin" params="{Vmatrix} \patREP+{\patVAR+{firstCol}\patREP*{&amp;\patVAR+{rest}}\\} \end{Vmatrix}"/>
180
  <pat:mml op="mtable">
181
    <mfenced open="&#x2225;" close="&#x2225;" separators="">
182
      <mtable>
183
        <pat:rep>
184
          <mtr>
185
            <mtd> <pat:variable name="firstCol"/> </mtd>
186
            <pat:rep>
187
              <mtd> <pat:variable name="rest"/> </mtd>
188
            </pat:rep>
189
          </mtr>
190
        </pat:rep>
191
      </mtable>
192
    </mfenced>
193
  </pat:mml>
194
</pat:template>
195
196
197
198
<pat:template>
199
  <pat:tex op="\begin" params="{array} [\patREP*{\patVAR!{valign}}] {\patREP*{\patVAR!{hjust}}} \patREP*{\patVAR*{firstCol}\patREP*{&amp;\patVAR*{rest}}\\} \end{array}"/>
200
  <pat:mml op="">
201
    <mtable>
202
	  <pat:rep>
203
		<pat:variable name="valign" attribute="align" map="t=top b=bottom"/>
204
	  </pat:rep>
205
	  <pat:rep>
206
		<pat:variable name="hjust" attribute="columnalign" map="l=left c=center r=right"/>
207
	  </pat:rep>
208
      <pat:rep>
209
        <mtr>
210
          <mtd> <pat:variable name="firstCol"/> </mtd>
211
          <pat:rep>
212
            <mtd> <pat:variable name="rest"/> </mtd>
213
          </pat:rep>
214
        </mtr>
215
      </pat:rep>
216
    </mtable>
217
  </pat:mml>
218
</pat:template>
219
220
<pat:template>
221
  <pat:tex op="\begin" params="{array} {\patREP*{\patVAR!{hjust}}} \patREP*{\patVAR*{firstCol}\patREP*{&amp;\patVAR*{rest}}\\} \end{array}"/>
222
  <pat:mml op="">
223
    <mtable>
224
      <pat:rep>
225
		<pat:variable name="hjust" attribute="columnalign" map="l=left c=center r=right"/>
226
	  </pat:rep>
227
      <pat:rep>
228
        <mtr>
229
          <mtd> <pat:variable name="firstCol"/> </mtd>
230
          <pat:rep>
231
            <mtd> <pat:variable name="rest"/> </mtd>
232
          </pat:rep>
233
        </mtr>
234
      </pat:rep>
235
    </mtable>
236
  </pat:mml>
237
</pat:template>
238
239
240
241
<!-- Equations -->
242
243
<pat:template>
244
  <pat:tex op="\begin" params="{equation} \patVAR*{eqn} \end{equation}"/>
245
  <pat:mml op="">
246
    <pat:variable name="eqn"/>
247
  </pat:mml>
248
</pat:template>
249
250
<pat:template>
251
  <pat:tex op="\begin" params="{equation*} \patVAR*{eqn} \end{equation*}"/>
252
  <pat:mml op="">
253
    <pat:variable name="eqn"/>
254
  </pat:mml>
255
</pat:template>
256
257
258
<pat:template>
259
  <pat:tex op="\begin" params="{multline} \patVAR*{first} \\ \patREP*{\patVAR*{mid} \\} \patVAR*{last} \end{multline}"/>
260
  <pat:mml op="">
261
    <mtable>
262
      <mtr columnalign="left">
263
        <mtd> <pat:variable name="first"/> </mtd>
264
      </mtr>
265
      <pat:rep>
266
        <mtr columnalign="center">
267
          <mtd> <pat:variable name="mid"/> </mtd>
268
        </mtr>
269
      </pat:rep>
270
      <mtr columnalign="right">
271
        <mtd> <pat:variable name="last"/> </mtd>
272
      </mtr>
273
    </mtable>
274
  </pat:mml>
275
</pat:template>
276
277
<pat:template>
278
  <pat:tex op="\begin" params="{multline} \patVAR*{first} \\ \end{multline}"/>
279
  <pat:mml op="">
280
    <mtable>
281
      <mtr columnalign="left">
282
        <mtd> <pat:variable name="first"/> </mtd>
283
      </mtr>
284
    </mtable>
285
  </pat:mml>
286
</pat:template>
287
288
<pat:template>
289
  <pat:tex op="\begin" params="{multline*} \patVAR*{first} \\ \patREP*{\patVAR*{mid} \\} \patVAR*{last} \end{multline*}"/>
290
  <pat:mml op="">
291
    <mtable>
292
      <mtr columnalign="left">
293
        <mtd> <pat:variable name="first"/> </mtd>
294
      </mtr>
295
      <pat:rep>
296
        <mtr columnalign="center">
297
          <mtd> <pat:variable name="mid"/> </mtd>
298
        </mtr>
299
      </pat:rep>
300
      <mtr columnalign="right">
301
        <mtd> <pat:variable name="last"/> </mtd>
302
      </mtr>
303
    </mtable>
304
  </pat:mml>
305
</pat:template>
306
307
<pat:template>
308
  <pat:tex op="\begin" params="{multline*} \patVAR*{first} \\ \end{multline*}"/>
309
  <pat:mml op="">
310
    <mtable>
311
      <mtr columnalign="left">
312
        <mtd> <pat:variable name="first"/> </mtd>
313
      </mtr>
314
    </mtable>
315
  </pat:mml>
316
</pat:template>
317
318
319
<pat:template>
320
  <pat:tex op="\begin" params="{split} \patREP+{\patVAR*{firstCol}\patREP*{&amp;\patVAR*{rest}}\\} \end{split}"/>
321
  <pat:mml op="">
322
    <mtable>
323
      <pat:rep>
324
        <mtr columnalign="right">
325
          <mtd> <pat:variable name="firstCol"/> </mtd>
326
          <pat:rep>
327
            <mtd columnalign="left"> <pat:variable name="rest"/> </mtd>
328
          </pat:rep>
329
        </mtr>
330
      </pat:rep>
331
    </mtable>
332
  </pat:mml>
333
</pat:template>
334
335
336
<pat:template>
337
  <pat:tex op="\begin" params="{eqnarray*} \patREP+{\patVAR*{left} &amp; \patVAR*{mid} &amp; \patVAR*{right} \\} \end{eqnarray*}"/>
338
  <pat:mml op="">
339
    <mtable columnalign="right center left">
340
      <pat:rep>
341
        <mtr>
342
          <mtd> <pat:variable name="left"/> </mtd>
343
          <mtd> <pat:variable name="mid"/> </mtd>
344
          <mtd> <pat:variable name="right"/> </mtd>
345
        </mtr>
346
      </pat:rep>
347
    </mtable>
348
  </pat:mml>
349
</pat:template>
350
351
<pat:template>
352
  <pat:tex op="\begin" params="{eqnarray*} \patVAR*{first} \end{eqnarray*}"/>
353
  <pat:mml op="">
354
    <mtable columnalign="left">
355
      <mtr>
356
        <mtd> <pat:variable name="first"/> </mtd>
357
      </mtr>
358
    </mtable>
359
  </pat:mml>
360
</pat:template>
361
362
363
<pat:template>
364
  <pat:tex op="\begin" params="{eqnarray} \patREP+{\patVAR*{left} &amp; \patVAR*{mid} &amp; \patVAR*{right} \\} \end{eqnarray}"/>
365
  <pat:mml op="">
366
    <mtable columnalign="right center left">
367
      <pat:rep>
368
        <mtr>
369
          <mtd> <pat:variable name="left"/> </mtd>
370
          <mtd> <pat:variable name="mid"/> </mtd>
371
          <mtd> <pat:variable name="right"/> </mtd>
372
        </mtr>
373
      </pat:rep>
374
    </mtable>
375
  </pat:mml>
376
</pat:template>
377
378
<pat:template>
379
  <pat:tex op="\begin" params="{eqnarray} \patVAR*{first} \end{eqnarray}"/>
380
  <pat:mml op="">
381
    <mtable columnalign="left">
382
      <mtr>
383
        <mtd> <pat:variable name="first"/> </mtd>
384
      </mtr>
385
    </mtable>
386
  </pat:mml>
387
</pat:template>
388
389
390
<pat:template>
391
  <pat:tex op="\eqalign" params="{\patREP+{\patVAR*{left}&amp;\patVAR*{right}\cr}}"/>
392
  <pat:mml op="">
393
    <mtable columnspacing="2em">
394
      <pat:rep>
395
        <mtr>
396
          <mtd columnalign="right"> <pat:variable name="left"/> </mtd>
397
          <mtd columnalign="left"> <pat:variable name="right"/> </mtd>
398
        </mtr>
399
      </pat:rep>
400
    </mtable>
401
  </pat:mml>
402
</pat:template>
403
404
405
<pat:template>
406
  <pat:tex op="\eqno" params="\patVAR+{tag}"/>
407
  <pat:mml op="">
408
    <pat:variable name="tag"/>
409
    <mspace linebreak="newline"/>
410
  </pat:mml>
411
</pat:template>
412
413
414
415
<!-- Tables and other table-like environments -->
416
417
<pat:template>
418
  <pat:tex op="\begin" params="{tabular} [\patREP+{\patVAR!{valign}}] {\patREP*{\patVAR!{hjust}}} \patREP*{\patVAR*{firstCol}\patREP*{&amp;\patVAR*{rest}}\\} \end{tabular}"/>
419
  <pat:mml op="">
420
    <mtable>
421
	  <pat:rep>
422
		<pat:variable name="valign" attribute="align" map="t=top b=bottom"/>
423
	  </pat:rep>
424
	  <pat:rep>
425
		<pat:variable name="hjust" attribute="columnalign" map="l=left c=center r=right"/>
426
	  </pat:rep>
427
      <pat:rep>
428
        <mtr>
429
          <mtd> <pat:variable name="firstCol"/> </mtd>
430
          <pat:rep>
431
            <mtd> <pat:variable name="rest"/> </mtd>
432
          </pat:rep>
433
        </mtr>
434
      </pat:rep>
435
    </mtable>
436
  </pat:mml>
437
</pat:template>
438
439
<pat:template>
440
  <pat:tex op="\begin" params="{tabular} {\patREP*{\patVAR!{hjust}}} \patREP*{\patVAR*{firstCol}\patREP*{&amp;\patVAR*{rest}}\\} \end{tabular}"/>
441
  <pat:mml op="">
442
    <mtable>
443
	  <pat:rep>
444
		<pat:variable name="hjust" attribute="columnalign" map="l=left c=center r=right"/>
445
	  </pat:rep>
446
      <pat:rep>
447
        <mtr>
448
          <mtd> <pat:variable name="firstCol"/> </mtd>
449
          <pat:rep>
450
            <mtd> <pat:variable name="rest"/> </mtd>
451
          </pat:rep>
452
        </mtr>
453
      </pat:rep>
454
    </mtable>
455
  </pat:mml>
456
</pat:template>
457
458
459
<pat:template>
460
  <pat:tex op="\begin" params="{align} \patREP*{\patVAR*{beg}&amp;\patVAR*{end}\\} \end{align}"/>
461
  <pat:mml op="">
462
    <mtable>
463
      <pat:rep>
464
        <mtr>
465
          <mtd columnalign="right"> <pat:variable name="beg"/> </mtd>
466
          <mtd columnalign="left"> <pat:variable name="end"/> </mtd>
467
        </mtr>
468
      </pat:rep>
469
    </mtable>
470
  </pat:mml>
471
</pat:template>
472
473
<pat:template>
474
  <pat:tex op="\begin" params="{align*} \patREP*{\patVAR*{beg}&amp;\patVAR*{end}\\} \end{align*}"/>
475
  <pat:mml op="">
476
    <mtable>
477
      <pat:rep>
478
        <mtr>
479
          <mtd columnalign="right"> <pat:variable name="beg"/> </mtd>
480
          <mtd columnalign="left"> <pat:variable name="end"/> </mtd>
481
        </mtr>
482
      </pat:rep>
483
    </mtable>
484
  </pat:mml>
485
</pat:template>
486
487
488
<pat:template>
489
  <pat:tex op="\begin" params="{gather} \patREP+{\patVAR*{line}\\} \end{gather}"/>
490
  <pat:mml op="">
491
    <mtable>
492
      <pat:rep>
493
        <mtr>
494
          <mtd> <pat:variable name="line"/> </mtd>
495
        </mtr>
496
      </pat:rep>
497
    </mtable>
498
  </pat:mml>
499
</pat:template>
500
501
<pat:template>
502
  <pat:tex op="\begin" params="{gather*} \patREP+{\patVAR*{line}\\} \end{gather*}"/>
503
  <pat:mml op="">
504
    <mtable>
505
      <pat:rep>
506
        <mtr>
507
          <mtd> <pat:variable name="line"/> </mtd>
508
        </mtr>
509
      </pat:rep>
510
    </mtable>
511
  </pat:mml>
512
</pat:template>
513
514
<pat:template>
515
  <pat:tex op="\begin" params="{aligned} [\patREP+{\patVAR!{valign}}] \patREP*{\patVAR*{beg}&amp;\patVAR*{end}\\} \end{aligned}"/>
516
  <pat:mml op="">
517
    <mtable>
518
	  <pat:rep>
519
		<pat:variable name="valign" attribute="align" map="t=top b=bottom"/>
520
	  </pat:rep>
521
      <pat:rep>
522
        <mtr>
523
          <mtd columnalign="right"> <pat:variable name="beg"/> </mtd>
524
          <mtd columnalign="left"> <pat:variable name="end"/> </mtd>
525
        </mtr>
526
      </pat:rep>
527
    </mtable>
528
  </pat:mml>
529
</pat:template>
530
531
<pat:template>
532
  <pat:tex op="\begin" params="{aligned} \patREP*{\patVAR*{beg}&amp;\patVAR*{end}\\} \end{aligned}"/>
533
  <pat:mml op="">
534
    <mtable>
535
      <pat:rep>
536
        <mtr>
537
          <mtd columnalign="right"> <pat:variable name="beg"/> </mtd>
538
          <mtd columnalign="left"> <pat:variable name="end"/> </mtd>
539
        </mtr>
540
      </pat:rep>
541
    </mtable>
542
  </pat:mml>
543
</pat:template>
544
545
<pat:template>
546
  <pat:tex op="\begin" params="{gathered} [\patREP+{\patVAR!{valign}}] \patREP*{\patVAR*{line}\\} \end{gathered}"/>
547
  <pat:mml op="">
548
    <mtable>
549
	  <pat:rep>
550
		<pat:variable name="valign" attribute="align" map="t=top b=bottom"/>
551
	  </pat:rep>
552
      <pat:rep>
553
        <mtr>
554
          <mtd> <pat:variable name="line"/> </mtd>
555
        </mtr>
556
      </pat:rep>
557
    </mtable>
558
  </pat:mml>
559
</pat:template>
560
561
<pat:template>
562
  <pat:tex op="\begin" params="{gathered} \patREP*{\patVAR*{line}\\} \end{gathered}"/>
563
  <pat:mml op="">
564
    <mtable>
565
      <pat:rep>
566
        <mtr>
567
          <mtd> <pat:variable name="line"/> </mtd>
568
        </mtr>
569
      </pat:rep>
570
    </mtable>
571
  </pat:mml>
572
</pat:template>
573
574
575
<pat:template>
576
  <pat:tex op="\begin" params="{alignat} {\patVAR+{num}} \patREP*{\patVAR*{beg}&amp;\patVAR*{end}\\} \end{alignat}"/>
577
  <pat:mml op="">
578
    <mtable>
579
      <pat:rep>
580
        <mtr>
581
          <mtd columnalign="right"> <pat:variable name="beg"/> </mtd>
582
          <mtd columnalign="left"> <pat:variable name="end"/> </mtd>
583
        </mtr>
584
      </pat:rep>
585
    </mtable>
586
  </pat:mml>
587
</pat:template>
588
589
<pat:template>
590
  <pat:tex op="\begin" params="{alignat*} {\patVAR+{num}} \patREP*{\patVAR*{beg}&amp;\patVAR*{end}\\} \end{alignat*}"/>
591
  <pat:mml op="">
592
    <mtable>
593
      <pat:rep>
594
        <mtr>
595
          <mtd columnalign="right"> <pat:variable name="beg"/> </mtd>
596
          <mtd columnalign="left"> <pat:variable name="end"/> </mtd>
597
        </mtr>
598
      </pat:rep>
599
    </mtable>
600
  </pat:mml>
601
</pat:template>
602
603
<pat:template>
604
  <pat:tex op="\begin" params="{falign} \patREP*{\patVAR*{beg}&amp;\patVAR*{end}\\} \end{falign}"/>
605
  <pat:mml op="">
606
    <mtable>
607
      <pat:rep>
608
        <mtr>
609
          <mtd columnalign="right"> <pat:variable name="beg"/> </mtd>
610
          <mtd columnalign="left"> <pat:variable name="end"/> </mtd>
611
        </mtr>
612
      </pat:rep>
613
    </mtable>
614
  </pat:mml>
615
</pat:template>
616
617
<pat:template>
618
  <pat:tex op="\begin" params="{falign*} \patREP*{\patVAR*{beg}&amp;\patVAR*{end}\\} \end{falign*}"/>
619
  <pat:mml op="">
620
    <mtable>
621
      <pat:rep>
622
        <mtr>
623
          <mtd columnalign="right"> <pat:variable name="beg"/> </mtd>
624
          <mtd columnalign="left"> <pat:variable name="end"/> </mtd>
625
        </mtr>
626
      </pat:rep>
627
    </mtable>
628
  </pat:mml>
629
</pat:template>
630
631
<pat:template>
632
  <pat:tex op="\begin" params="{center} \patREP*{\patVAR*{line}\\} \end{center}"/>
633
  <pat:mml op="">
634
    <mtable>
635
      <pat:rep>
636
        <mtr>
637
          <mtd> <pat:variable name="line"/> </mtd>
638
        </mtr>
639
      </pat:rep>
640
    </mtable>
641
  </pat:mml>
642
</pat:template>
643
644
645
<pat:template>
646
  <pat:tex op="\begin" params="{cases} \patREP+{\patVAR+{firstCol} &amp; \patVAR+{rest} \\} \end{cases}"/>
647
  <pat:mml op="mtable">
648
    <mrow>
649
      <mo stretchy="true"> { </mo>
650
      <mtable columnalign="left" columnspacing="2em">
651
        <pat:rep>
652
          <mtr>
653
            <mtd> <pat:variable name="firstCol"/> </mtd>
654
            <mtd> <pat:variable name="rest"/> </mtd>
655
          </mtr>
656
        </pat:rep>
657
      </mtable>
658
    </mrow>
659
  </pat:mml>
660
</pat:template>
661
662
<pat:template>
663
  <pat:tex op="\cases" params="{\patREP+{\patVAR+{firstCol}&amp;\patVAR+{rest}\cr}}"/>
664
  <pat:mml op="mtable">
665
    <mrow>
666
      <mo stretchy="true"> { </mo>
667
      <mtable columnalign="left" columnspacing="2em">
668
        <pat:rep>
669
          <mtr>
670
            <mtd> <pat:variable name="firstCol"/> </mtd>
671
            <mtd> <pat:variable name="rest"/> </mtd>
672
          </mtr>
673
        </pat:rep>
674
      </mtable>
675
    </mrow>
676
  </pat:mml>
677
</pat:template>
678
679
<pat:template>
680
  <pat:tex op="\cases" params="{\patREP*{\patVAR*{firstCase}&amp;\patVAR*{firstCond}\cr} \patVAR*{lastCase}&amp;\patVAR*{lastCond}}"/>
681
  <pat:mml op="mtable">
682
    <mrow>
683
      <mo stretchy="true"> { </mo>
684
      <mtable columnalign="left" columnspacing="2em">
685
        <pat:rep>
686
          <mtr>
687
            <mtd> <pat:variable name="firstCase"/> </mtd>
688
            <mtd> <pat:variable name="firstCond"/> </mtd>
689
          </mtr>
690
        </pat:rep>
691
        <mtr>
692
          <mtd> <pat:variable name="lastCase"/> </mtd>
693
          <mtd> <pat:variable name="lastCond"/> </mtd>
694
        </mtr>
695
      </mtable>
696
    </mrow>
697
  </pat:mml>
698
</pat:template>
699
700
701
<pat:template>
702
  <pat:tex op="\begin" params="{description} \patREP*{\item[\patVAR*{item}] \patVAR*{desc}} \end{description}"/>
703
  <pat:mml op="">
704
    <mtable columnalign="left">
705
      <pat:rep>
706
        <mtr>
707
          <mtd>
708
            <mstyle mathvariant="bold">
709
              <pat:variable name="item"/>
710
            </mstyle>
711
          </mtd>
712
          <mtd> <pat:variable name="desc"/> </mtd>
713
        </mtr>
714
      </pat:rep>
715
    </mtable>
716
  </pat:mml>
717
</pat:template>
718
719
720
721
<pat:template>
722
  <pat:tex op="" params="\patVAR+{a}"/>
723
  <pat:mml op="mtd">
724
    <mtd> <pat:variable name="a"/> </mtd>
725
  </pat:mml>
726
</pat:template>
727
728
729
730
<!-- Quotes -->
731
732
<pat:template>
733
  <pat:tex op="'" params="'"/>
734
  <pat:mml op="">
735
    <mo> &quot; </mo>
736
  </pat:mml>
737
</pat:template>
738
739
<pat:template>
740
  <pat:tex op="`" params="`"/>
741
  <pat:mml op="">
742
    <mo> &quot; </mo>
743
  </pat:mml>
744
</pat:template>
745
746
<pat:template>
747
  <pat:tex op="&quot;"/>
748
  <pat:mml op="">
749
    <mo> &quot; </mo>
750
  </pat:mml>
751
</pat:template>
752
753
754
755
<!-- Brackets -->
756
757
<pat:template>
758
  <pat:tex op="\|"/>
759
  <pat:mml op="&#x2225;">
760
    <mo> &#x2225; </mo>
761
  </pat:mml>
762
</pat:template>
763
764
<pat:template>
765
  <pat:tex op="\Vert"/>
766
  <pat:mml op="&#x2225;">
767
    <mo> &#x2225; </mo>
768
  </pat:mml>
769
</pat:template>
770
771
<pat:template>
772
  <pat:tex op="|"/>
773
  <pat:mml op="&#x2223;">
774
    <mo> &#x2223; </mo>
775
  </pat:mml>
776
</pat:template>
777
778
<pat:template>
779
  <pat:tex op="\vert"/>
780
  <pat:mml op="&#x2223;">
781
    <mo> &#x2223; </mo>
782
  </pat:mml>
783
</pat:template>
784
785
786
<pat:template>
787
  <pat:tex op="("/>
788
  <pat:mml op="(">
789
    <mo> ( </mo>
790
  </pat:mml>
791
</pat:template>
792
793
<pat:template>
794
  <pat:tex op="["/>
795
  <pat:mml op="[">
796
    <mo> [ </mo>
797
  </pat:mml>
798
</pat:template>
799
800
<pat:template>
801
  <pat:tex op="\lbrack"/>
802
  <pat:mml op="[">
803
    <mo> [ </mo>
804
  </pat:mml>
805
</pat:template>
806
807
<pat:template>
808
  <pat:tex op="\{"/>
809
  <pat:mml op="{">
810
    <mo> { </mo>
811
  </pat:mml>
812
</pat:template>
813
814
<pat:template>
815
  <pat:tex op="\lbrace"/>
816
  <pat:mml op="{">
817
    <mo> { </mo>
818
  </pat:mml>
819
</pat:template>
820
821
<pat:template>
822
  <pat:tex op="&lt;"/>
823
  <pat:mml op="&lt;">
824
    <mo> &lt; </mo>
825
  </pat:mml>
826
</pat:template>
827
828
<pat:template>
829
  <pat:tex op="/"/>
830
  <pat:mml op="/">
831
    <mo> / </mo>
832
  </pat:mml>
833
</pat:template>
834
835
<pat:template>
836
  <pat:tex op="\lfloor"/>
837
  <pat:mml op="&#x230A;">
838
    <mo> &#x230A; </mo>
839
  </pat:mml>
840
</pat:template>
841
842
<pat:template>
843
  <pat:tex op="\lceil"/>
844
  <pat:mml op="&#x2308;">
845
    <mo> &#x2308; </mo>
846
  </pat:mml>
847
</pat:template>
848
849
<pat:template>
850
  <pat:tex op="\langle"/>		<!-- 27E8 U3.2 -->
851
  <pat:mml op="&#x2329;">
852
    <mo> &#x2329; </mo>
853
  </pat:mml>
854
</pat:template>
855
856
<pat:template>
857
  <pat:tex op="\lgroup"/>
858
  <pat:mml op="">
859
    <mo> ( </mo>
860
  </pat:mml>
861
</pat:template>
862
863
864
865
<pat:template>
866
  <pat:tex op=")"/>
867
  <pat:mml op=")">
868
    <mo> ) </mo>
869
  </pat:mml>
870
</pat:template>
871
872
<pat:template>
873
  <pat:tex op="]"/>
874
  <pat:mml op="]">
875
    <mo> ] </mo>
876
  </pat:mml>
877
</pat:template>
878
879
<pat:template>
880
  <pat:tex op="\rbrack"/>
881
  <pat:mml op="]">
882
    <mo> ] </mo>
883
  </pat:mml>
884
</pat:template>
885
886
<pat:template>
887
  <pat:tex op="\}"/>
888
  <pat:mml op="}">
889
    <mo> } </mo>
890
  </pat:mml>
891
</pat:template>
892
893
<pat:template>
894
  <pat:tex op="\rbrace"/>
895
  <pat:mml op="}">
896
    <mo> } </mo>
897
  </pat:mml>
898
</pat:template>
899
900
<pat:template>
901
  <pat:tex op="&gt;"/>
902
  <pat:mml op="&gt;">
903
    <mo> &gt; </mo>
904
  </pat:mml>
905
</pat:template>
906
907
<pat:template>
908
  <pat:tex op="\backslash"/>
909
  <pat:mml op="\">
910
    <mo> \ </mo>
911
  </pat:mml>
912
</pat:template>
913
914
<pat:template>
915
  <pat:tex op="\rfloor"/>
916
  <pat:mml op="&#x230B;">
917
    <mo> &#x230B; </mo>
918
  </pat:mml>
919
</pat:template>
920
921
<pat:template>
922
  <pat:tex op="\rceil"/>
923
  <pat:mml op="&#x2309;">
924
    <mo> &#x2309; </mo>
925
  </pat:mml>
926
</pat:template>
927
928
<pat:template>
929
  <pat:tex op="\rangle"/>		<!-- 27E9 U3.2 -->
930
  <pat:mml op="&#x232A;">
931
    <mo> &#x232A; </mo>
932
  </pat:mml>
933
</pat:template>
934
935
<pat:template>
936
  <pat:tex op="\rgroup"/>
937
  <pat:mml op="">
938
    <mo> ) </mo>
939
  </pat:mml>
940
</pat:template>
941
942
943
<pat:template>
944
  <pat:tex op="\uparrow"/>
945
  <pat:mml op="&#x2191;">
946
    <mo> &#x2191; </mo>
947
  </pat:mml>
948
</pat:template>
949
950
<pat:template>
951
  <pat:tex op="\Uparrow"/>
952
  <pat:mml op="&#x21D1;">
953
    <mo> &#x21D1; </mo>
954
  </pat:mml>
955
</pat:template>
956
957
<pat:template>
958
  <pat:tex op="\downarrow"/>
959
  <pat:mml op="&#x2193;">
960
    <mo> &#x2193; </mo>
961
  </pat:mml>
962
</pat:template>
963
964
<pat:template>
965
  <pat:tex op="\Downarrow"/>
966
  <pat:mml op="&#x21D3;">
967
    <mo> &#x21D3; </mo>
968
  </pat:mml>
969
</pat:template>
970
971
<pat:template>
972
  <pat:tex op="\updownarrow"/>
973
  <pat:mml op="&#x21C5;">
974
    <mo> &#x21C5; </mo>
975
  </pat:mml>
976
</pat:template>
977
978
<pat:template>
979
  <pat:tex op="\Updownarrow"/>
980
  <pat:mml op="&#x21D5;">
981
    <mo> &#x21D5; </mo>
982
  </pat:mml>
983
</pat:template>
984
985
986
987
<!-- Arrows -->
988
989
<pat:template>
990
  <pat:tex op="\leftarrow"/>
991
  <pat:mml op="&#x2190;">
992
    <mo> &#x2190; </mo>
993
  </pat:mml>
994
</pat:template>
995
996
<pat:template>
997
  <pat:tex op="\gets"/>
998
  <pat:mml op="&#x2190;">
999
    <mo> &#x2190; </mo>
1000
  </pat:mml>
1001
</pat:template>
1002
1003
<pat:template>
1004
  <pat:tex op="\Leftarrow"/>
1005
  <pat:mml op="&#x21D0;">
1006
    <mo> &#x21D0; </mo>
1007
  </pat:mml>
1008
</pat:template>
1009
1010
<pat:template>
1011
  <pat:tex op="\rightarrow"/>
1012
  <pat:mml op="&#x2192;">
1013
    <mo> &#x2192; </mo>
1014
  </pat:mml>
1015
</pat:template>
1016
1017
<pat:template>
1018
  <pat:tex op="\to"/>
1019
  <pat:mml op="&#x2192;">
1020
    <mo> &#x2192; </mo>
1021
  </pat:mml>
1022
</pat:template>
1023
1024
<pat:template>
1025
  <pat:tex op="\Rightarrow"/>
1026
  <pat:mml op="&#x21D2;">
1027
    <mo> &#x21D2; </mo>
1028
  </pat:mml>
1029
</pat:template>
1030
1031
<pat:template>
1032
  <pat:tex op="\leftrightarrow"/>
1033
  <pat:mml op="&#x21C6;">
1034
    <mo> &#x21C6; </mo>
1035
  </pat:mml>
1036
</pat:template>
1037
1038
<pat:template>
1039
  <pat:tex op="\Leftrightarrow"/>
1040
  <pat:mml op="&#x21D4;">
1041
    <mo> &#x21D4; </mo>
1042
  </pat:mml>
1043
</pat:template>
1044
1045
<pat:template>
1046
  <pat:tex op="\mapsto"/>
1047
  <pat:mml op="&#x21A6;">
1048
    <mo> &#x21A6; </mo>
1049
  </pat:mml>
1050
</pat:template>
1051
1052
<pat:template>
1053
  <pat:tex op="\hookleftarrow"/>
1054
  <pat:mml op="&#x21A9;">
1055
    <mo> &#x21A9; </mo>
1056
  </pat:mml>
1057
</pat:template>
1058
1059
<pat:template>
1060
  <pat:tex op="\leftharpoonup"/>
1061
  <pat:mml op="&#x21BC;">
1062
    <mo> &#x21BC; </mo>
1063
  </pat:mml>
1064
</pat:template>
1065
1066
<pat:template>
1067
  <pat:tex op="\leftharpoondown"/>
1068
  <pat:mml op="&#x21BD;">
1069
    <mo> &#x21BD; </mo>
1070
  </pat:mml>
1071
</pat:template>
1072
1073
<pat:template>
1074
  <pat:tex op="\rightleftharpoons"/>
1075
  <pat:mml op="&#x21CC;">
1076
    <mo> &#x21CC; </mo>
1077
  </pat:mml>
1078
</pat:template>
1079
1080
<pat:template>
1081
  <pat:tex op="\longleftarrow"/>
1082
  <pat:mml op="&#x2190;">
1083
    <mo> &#x2190; </mo>				<!-- Should be 27F5 -->
1084
  </pat:mml>
1085
</pat:template>
1086
1087
<pat:template>
1088
  <pat:tex op="\Longleftarrow"/>
1089
  <pat:mml op="&#x21D0;">
1090
    <mo> &#x21D0; </mo>				<!-- Should be 27F8 -->
1091
  </pat:mml>
1092
</pat:template>
1093
1094
<pat:template>
1095
  <pat:tex op="\longrightarrow"/>
1096
  <pat:mml op="&#x2192;">
1097
    <mo> &#x2192; </mo>				<!-- Should be 27F6 -->
1098
  </pat:mml>
1099
</pat:template>
1100
1101
<pat:template>
1102
  <pat:tex op="\Longrightarrow"/>
1103
  <pat:mml op="&#x21D2;">
1104
    <mo> &#x21D2; </mo>				<!-- Should be 27F9 -->
1105
  </pat:mml>
1106
</pat:template>
1107
1108
<pat:template>
1109
  <pat:tex op="\longleftrightarrow"/>
1110
  <pat:mml op="&#x2194;">
1111
    <mo> &#x2194; </mo>				<!-- Should be 27F7 -->
1112
  </pat:mml>
1113
</pat:template>
1114
1115
<pat:template>
1116
  <pat:tex op="\Longleftrightarrow"/>
1117
  <pat:mml op="&#x21D4;">
1118
    <mo> &#x21D4; </mo>				<!-- Should be 27FA -->
1119
  </pat:mml>
1120
</pat:template>
1121
1122
<pat:template>
1123
  <pat:tex op="\longmapsto"/>
1124
  <pat:mml op="&#x21A6;">
1125
    <mo> &#x21A6; </mo>				<!-- Should be 27FC -->
1126
  </pat:mml>
1127
</pat:template>
1128
1129
<pat:template>
1130
  <pat:tex op="\leadsto"/>
1131
  <pat:mml op="&#x21DD;">
1132
    <mo> &#x21DD; </mo>				<!-- Should be 27FF -->
1133
  </pat:mml>
1134
</pat:template>
1135
1136
<pat:template>
1137
  <pat:tex op="\hookrightarrow"/>
1138
  <pat:mml op="&#x21AA;">
1139
    <mo> &#x21AA; </mo>
1140
  </pat:mml>
1141
</pat:template>
1142
1143
<pat:template>
1144
  <pat:tex op="\rightharpoonup"/>
1145
  <pat:mml op="&#x21C0;">
1146
    <mo> &#x21C0; </mo>
1147
  </pat:mml>
1148
</pat:template>
1149
1150
<pat:template>
1151
  <pat:tex op="\rightharpoondown"/>
1152
  <pat:mml op="&#x21C1;">
1153
    <mo> &#x21C1; </mo>
1154
  </pat:mml>
1155
</pat:template>
1156
1157
<pat:template>
1158
  <pat:tex op="\nearrow"/>
1159
  <pat:mml op="&#x2197;">
1160
    <mo> &#x2197; </mo>
1161
  </pat:mml>
1162
</pat:template>
1163
1164
<pat:template>
1165
  <pat:tex op="\searrow"/>
1166
  <pat:mml op="&#x2198;">
1167
    <mo> &#x2198; </mo>
1168
  </pat:mml>
1169
</pat:template>
1170
1171
<pat:template>
1172
  <pat:tex op="\swarrow"/>
1173
  <pat:mml op="&#x2199;">
1174
    <mo> &#x2199; </mo>
1175
  </pat:mml>
1176
</pat:template>
1177
1178
<pat:template>
1179
  <pat:tex op="\nwarrow"/>
1180
  <pat:mml op="&#x2196;">
1181
    <mo> &#x2196; </mo>
1182
  </pat:mml>
1183
</pat:template>
1184
1185
1186
<pat:template>
1187
  <pat:tex op="\arrowvert"/>
1188
  <pat:mml op="">
1189
    <mo> &#x2223; </mo>
1190
  </pat:mml>
1191
</pat:template>
1192
1193
<pat:template>
1194
  <pat:tex op="\Arrowvert"/>
1195
  <pat:mml op="">
1196
    <mo> &#x2225; </mo>
1197
  </pat:mml>
1198
</pat:template>
1199
1200
<pat:template>
1201
  <pat:tex op="\bracevert"/>
1202
  <pat:mml op="">
1203
    <mo mathvariant="bold"> &#x2223; </mo>
1204
  </pat:mml>
1205
</pat:template>
1206
1207
<pat:template>
1208
  <pat:tex op="\lmoustache"/>
1209
  <pat:mml op="">
1210
    <mo> &#x23B0; </mo>
1211
  </pat:mml>
1212
</pat:template>
1213
1214
<pat:template>
1215
  <pat:tex op="\rmoustache"/>
1216
  <pat:mml op="">
1217
    <mo> &#x23B1; </mo>
1218
  </pat:mml>
1219
</pat:template>
1220
1221
<pat:template>
1222
  <pat:tex op="\leftarrowfill"/>
1223
  <pat:mml op="">
1224
    <mo stretchy="true"> &#x2190; </mo>
1225
  </pat:mml>
1226
</pat:template>
1227
1228
<pat:template>
1229
  <pat:tex op="\rightarrowfill"/>
1230
  <pat:mml op="">
1231
    <mo stretchy="true"> &#x2192; </mo>
1232
  </pat:mml>
1233
</pat:template>
1234
1235
<pat:template>
1236
  <pat:tex op="\iff"/>
1237
  <pat:mml op="">
1238
    <mo> &#x21D4; </mo>
1239
  </pat:mml>
1240
</pat:template>
1241
1242
1243
1244
<!-- AMS arrows -->
1245
1246
<pat:template>
1247
  <pat:tex op="\dashrightarrow"/>
1248
  <pat:mml op="&#x21E2;">
1249
    <mo> &#x21E2; </mo>
1250
  </pat:mml>
1251
</pat:template>
1252
1253
<pat:template>
1254
  <pat:tex op="\dashleftarrow"/>
1255
  <pat:mml op="&#x21E0;">
1256
    <mo> &#x21E0; </mo>
1257
  </pat:mml>
1258
</pat:template>
1259
1260
<pat:template>
1261
  <pat:tex op="\leftleftarrows"/>
1262
  <pat:mml op="&#x21C7;">
1263
    <mo> &#x21C7; </mo>
1264
  </pat:mml>
1265
</pat:template>
1266
1267
<pat:template>
1268
  <pat:tex op="\leftrightarrows"/>
1269
  <pat:mml op="&#x21C6;">
1270
    <mo> &#x21C6; </mo>
1271
  </pat:mml>
1272
</pat:template>
1273
1274
<pat:template>
1275
  <pat:tex op="\Lleftarrow"/>
1276
  <pat:mml op="&#x21DA;">
1277
    <mo> &#x21DA; </mo>
1278
  </pat:mml>
1279
</pat:template>
1280
1281
<pat:template>
1282
  <pat:tex op="\twoheadleftarrow"/>
1283
  <pat:mml op="&#x219E;">
1284
    <mo> &#x219E; </mo>
1285
  </pat:mml>
1286
</pat:template>
1287
1288
<pat:template>
1289
  <pat:tex op="\leftarrowtail"/>
1290
  <pat:mml op="&#x21A2;">
1291
    <mo> &#x21A2; </mo>
1292
  </pat:mml>
1293
</pat:template>
1294
1295
<pat:template>
1296
  <pat:tex op="\looparrowleft"/>
1297
  <pat:mml op="&#x21AB;">
1298
    <mo> &#x21AB; </mo>
1299
  </pat:mml>
1300
</pat:template>
1301
1302
<pat:template>
1303
  <pat:tex op="\leftrightharpoons"/>
1304
  <pat:mml op="&#x21CB;">
1305
    <mo> &#x21CB; </mo>
1306
  </pat:mml>
1307
</pat:template>
1308
1309
<pat:template>
1310
  <pat:tex op="\curvearrowleft"/>
1311
  <pat:mml op="&#x21B6;">
1312
    <mo> &#x21B6; </mo>
1313
  </pat:mml>
1314
</pat:template>
1315
1316
<pat:template>
1317
  <pat:tex op="\circlearrowleft"/>
1318
  <pat:mml op="&#x21BA;">
1319
    <mo> &#x21BA; </mo>
1320
  </pat:mml>
1321
</pat:template>
1322
1323
<pat:template>
1324
  <pat:tex op="\Lsh"/>
1325
  <pat:mml op="&#x21B0;">
1326
    <mo> &#x21B0; </mo>
1327
  </pat:mml>
1328
</pat:template>
1329
1330
<pat:template>
1331
  <pat:tex op="\upuparrows"/>
1332
  <pat:mml op="&#x21C8;">
1333
    <mo> &#x21C8; </mo>
1334
  </pat:mml>
1335
</pat:template>
1336
1337
<pat:template>
1338
  <pat:tex op="\upharpoonleft"/>
1339
  <pat:mml op="&#x21BF;">
1340
    <mo> &#x21BF; </mo>
1341
  </pat:mml>
1342
</pat:template>
1343
1344
<pat:template>
1345
  <pat:tex op="\downharpoonleft"/>
1346
  <pat:mml op="&#x21C3;">
1347
    <mo> &#x21C3; </mo>
1348
  </pat:mml>
1349
</pat:template>
1350
1351
<pat:template>
1352
  <pat:tex op="\multimap"/>
1353
  <pat:mml op="&#x22B8;">
1354
    <mo> &#x22B8; </mo>
1355
  </pat:mml>
1356
</pat:template>
1357
1358
<pat:template>
1359
  <pat:tex op="\leftrightsquigarrow"/>
1360
  <pat:mml op="&#x21AD;">
1361
    <mo> &#x21AD; </mo>
1362
  </pat:mml>
1363
</pat:template>
1364
1365
<pat:template>
1366
  <pat:tex op="\rightrightarrows"/>
1367
  <pat:mml op="&#x21C9;">
1368
    <mo> &#x21C9; </mo>
1369
  </pat:mml>
1370
</pat:template>
1371
1372
<pat:template>
1373
  <pat:tex op="\rightleftarrows"/>
1374
  <pat:mml op="&#x21C4;">
1375
    <mo> &#x21C4; </mo>
1376
  </pat:mml>
1377
</pat:template>
1378
1379
<pat:template>
1380
  <pat:tex op="\twoheadrightarrow"/>
1381
  <pat:mml op="&#x21A0;">
1382
    <mo> &#x21A0; </mo>
1383
  </pat:mml>
1384
</pat:template>
1385
1386
<pat:template>
1387
  <pat:tex op="\rightarrowtail"/>
1388
  <pat:mml op="&#x21A3;">
1389
    <mo> &#x21A3; </mo>
1390
  </pat:mml>
1391
</pat:template>
1392
1393
<pat:template>
1394
  <pat:tex op="\looparrowright"/>
1395
  <pat:mml op="&#x21AC;">
1396
    <mo> &#x21AC; </mo>
1397
  </pat:mml>
1398
</pat:template>
1399
1400
<pat:template>
1401
  <pat:tex op="\rightleftharpoons"/>
1402
  <pat:mml op="&#x21CC;">
1403
    <mo> &#x21CC; </mo>
1404
  </pat:mml>
1405
</pat:template>
1406
1407
<pat:template>
1408
  <pat:tex op="\curvearrowright"/>
1409
  <pat:mml op="&#x21B7;">
1410
    <mo> &#x21B7; </mo>
1411
  </pat:mml>
1412
</pat:template>
1413
1414
<pat:template>
1415
  <pat:tex op="\circlearrowright"/>
1416
  <pat:mml op="&#x21BB;">
1417
    <mo> &#x21BB; </mo>
1418
  </pat:mml>
1419
</pat:template>
1420
1421
<pat:template>
1422
  <pat:tex op="\Rsh"/>
1423
  <pat:mml op="&#x21B1;">
1424
    <mo> &#x21B1; </mo>
1425
  </pat:mml>
1426
</pat:template>
1427
1428
<pat:template>
1429
  <pat:tex op="\downdownarrows"/>
1430
  <pat:mml op="&#x21CA;">
1431
    <mo> &#x21CA; </mo>
1432
  </pat:mml>
1433
</pat:template>
1434
1435
<pat:template>
1436
  <pat:tex op="\upharpoonright"/>
1437
  <pat:mml op="&#x21BE;">
1438
    <mo> &#x21BE; </mo>
1439
  </pat:mml>
1440
</pat:template>
1441
1442
<pat:template>
1443
  <pat:tex op="\rightsquigarrow"/>
1444
  <pat:mml op="&#x21DD;">
1445
    <mo> &#x21DD; </mo>
1446
  </pat:mml>
1447
</pat:template>
1448
1449
1450
<pat:template>
1451
  <pat:tex op="\nleftarrow"/>
1452
  <pat:mml op="&#x219A;">
1453
    <mo> &#x219A; </mo>
1454
  </pat:mml>
1455
</pat:template>
1456
1457
<pat:template>
1458
  <pat:tex op="\nrightarrow"/>
1459
  <pat:mml op="&#x219B;">
1460
    <mo> &#x219B; </mo>
1461
  </pat:mml>
1462
</pat:template>
1463
1464
<pat:template>
1465
  <pat:tex op="\nLeftarrow"/>
1466
  <pat:mml op="&#x21CD;">
1467
    <mo> &#x21CD; </mo>
1468
  </pat:mml>
1469
</pat:template>
1470
1471
<pat:template>
1472
  <pat:tex op="\nRightarrow"/>
1473
  <pat:mml op="&#x21CF;">
1474
    <mo> &#x21CF; </mo>
1475
  </pat:mml>
1476
</pat:template>
1477
1478
<pat:template>
1479
  <pat:tex op="\nleftrightarrow"/>
1480
  <pat:mml op="&#x21AE;">
1481
    <mo> &#x21AE; </mo>
1482
  </pat:mml>
1483
</pat:template>
1484
1485
<pat:template>
1486
  <pat:tex op="\nLeftrightarrow"/>
1487
  <pat:mml op="&#x21CE;">
1488
    <mo> &#x21CE; </mo>
1489
  </pat:mml>
1490
</pat:template>
1491
1492
1493
<pat:template>
1494
  <pat:tex op="\xleftarrow" params="[\patVAR*{below}]\patVAR!{above}"/>
1495
  <pat:mml op="">
1496
    <munderover>
1497
      <mo> &#x2190; </mo>				<!-- Should be 27F5 -->
1498
      <pat:variable name="below"/>
1499
      <pat:variable name="above"/>
1500
    </munderover>
1501
  </pat:mml>
1502
</pat:template>
1503
1504
<pat:template>
1505
  <pat:tex op="\xleftarrow" params="\patVAR!{above}"/>
1506
  <pat:mml op="">
1507
    <munderover>
1508
      <mo> &#x2190; </mo>				<!-- Should be 27F5 -->
1509
      <mrow/>
1510
      <pat:variable name="above"/>
1511
    </munderover>
1512
  </pat:mml>
1513
</pat:template>
1514
1515
<pat:template>
1516
  <pat:tex op="\xrightarrow" params="[\patVAR*{below}]\patVAR!{above}"/>
1517
  <pat:mml op="">
1518
    <munderover>
1519
    <mo> &#x2192; </mo>				<!-- Should be 27F6 -->
1520
      <pat:variable name="below"/>
1521
      <pat:variable name="above"/>
1522
    </munderover>
1523
  </pat:mml>
1524
</pat:template>
1525
1526
<pat:template>
1527
  <pat:tex op="\xrightarrow" params="\patVAR!{above}"/>
1528
  <pat:mml op="">
1529
    <munderover>
1530
    <mo> &#x2192; </mo>				<!-- Should be 27F6 -->
1531
      <mrow/>
1532
      <pat:variable name="above"/>
1533
    </munderover>
1534
  </pat:mml>
1535
</pat:template>
1536
1537
<pat:template>
1538
  <pat:tex op="\overleftarrow" params="\patVAR!{expr}"/>
1539
  <pat:mml op="">
1540
    <mover>
1541
      <pat:variable name="expr"/>
1542
      <mo> &#x2190; </mo>				<!-- Should be 27F5 -->
1543
    </mover>
1544
  </pat:mml>
1545
</pat:template>
1546
1547
<pat:template>
1548
  <pat:tex op="\overrightarrow" params="\patVAR!{expr}"/>
1549
  <pat:mml op="">
1550
    <mover>
1551
      <pat:variable name="expr"/>
1552
      <mo> &#x2192; </mo>				<!-- Should be 27F6 -->
1553
    </mover>
1554
  </pat:mml>
1555
</pat:template>
1556
1557
<pat:template>
1558
  <pat:tex op="\overleftrightarrow" params="\patVAR!{expr}"/>
1559
  <pat:mml op="">
1560
    <mover>
1561
      <pat:variable name="expr"/>
1562
      <mo> &#x2194; </mo>				<!-- Should be 27F7 -->
1563
    </mover>
1564
  </pat:mml>
1565
</pat:template>
1566
1567
1568
1569
<!-- AMS binary operation symbols -->
1570
1571
<pat:template>
1572
  <pat:tex op="\dotplus"/>
1573
  <pat:mml op="&#x2214;">
1574
    <mo> &#x2214; </mo>
1575
  </pat:mml>
1576
</pat:template>
1577
1578
<pat:template>
1579
  <pat:tex op="\smallsetminus"/>
1580
  <pat:mml op="">
1581
    <mo> &#x2216; </mo>
1582
  </pat:mml>
1583
</pat:template>
1584
1585
<pat:template>
1586
  <pat:tex op="\Cap"/>
1587
  <pat:mml op="&#x22D2;">
1588
    <mo> &#x22D2; </mo>
1589
  </pat:mml>
1590
</pat:template>
1591
1592
<pat:template>
1593
  <pat:tex op="\Cup"/>
1594
  <pat:mml op="&#x22D3;">
1595
    <mo> &#x22D3; </mo>
1596
  </pat:mml>
1597
</pat:template>
1598
1599
<pat:template>
1600
  <pat:tex op="\barwedge"/>
1601
  <pat:mml op="&#x22BC;">
1602
    <mo> &#x22BC; </mo>
1603
  </pat:mml>
1604
</pat:template>
1605
1606
<pat:template>
1607
  <pat:tex op="\doublebarwedge"/>
1608
  <pat:mml op="&#x2306;">
1609
    <mo> &#x2306; </mo>
1610
  </pat:mml>
1611
</pat:template>
1612
1613
<pat:template>
1614
  <pat:tex op="\veebar"/>
1615
  <pat:mml op="&#x22BB;">
1616
    <mo> &#x22BB; </mo>
1617
  </pat:mml>
1618
</pat:template>
1619
1620
<pat:template>
1621
  <pat:tex op="\boxminus"/>
1622
  <pat:mml op="&#x229F;">
1623
    <mo> &#x229F; </mo>
1624
  </pat:mml>
1625
</pat:template>
1626
1627
<pat:template>
1628
  <pat:tex op="\boxtimes"/>
1629
  <pat:mml op="&#x22A0;">
1630
    <mo> &#x22A0; </mo>
1631
  </pat:mml>
1632
</pat:template>
1633
1634
<pat:template>
1635
  <pat:tex op="\boxdot"/>
1636
  <pat:mml op="&#x22A1;">
1637
    <mo> &#x22A1; </mo>
1638
  </pat:mml>
1639
</pat:template>
1640
1641
<pat:template>
1642
  <pat:tex op="\boxplus"/>
1643
  <pat:mml op="&#x229E;">
1644
    <mo> &#x229E; </mo>
1645
  </pat:mml>
1646
</pat:template>
1647
1648
<pat:template>
1649
  <pat:tex op="\divideontimes"/>
1650
  <pat:mml op="&#x22C7;">
1651
    <mo> &#x22C7; </mo>
1652
  </pat:mml>
1653
</pat:template>
1654
1655
<pat:template>
1656
  <pat:tex op="\ltimes"/>
1657
  <pat:mml op="&#x22C9;">
1658
    <mo> &#x22C9; </mo>
1659
  </pat:mml>
1660
</pat:template>
1661
1662
<pat:template>
1663
  <pat:tex op="\rtimes"/>
1664
  <pat:mml op="&#x22CA;">
1665
    <mo> &#x22CA; </mo>
1666
  </pat:mml>
1667
</pat:template>
1668
1669
<pat:template>
1670
  <pat:tex op="\leftthreetimes"/>
1671
  <pat:mml op="&#x22CB;">
1672
    <mo> &#x22CB; </mo>
1673
  </pat:mml>
1674
</pat:template>
1675
1676
<pat:template>
1677
  <pat:tex op="\rightthreetimes"/>
1678
  <pat:mml op="&#x22CC;">
1679
    <mo> &#x22CC; </mo>
1680
  </pat:mml>
1681
</pat:template>
1682
1683
<pat:template>
1684
  <pat:tex op="\curlywedge"/>
1685
  <pat:mml op="&#x22CF;">
1686
    <mo> &#x22CF; </mo>
1687
  </pat:mml>
1688
</pat:template>
1689
1690
<pat:template>
1691
  <pat:tex op="\curlyvee"/>
1692
  <pat:mml op="&#x22CF;">
1693
    <mo> &#x22CF; </mo>
1694
  </pat:mml>
1695
</pat:template>
1696
1697
<pat:template>
1698
  <pat:tex op="\circleddash"/>
1699
  <pat:mml op="&#x229D;">
1700
    <mo> &#x229D; </mo>
1701
  </pat:mml>
1702
</pat:template>
1703
1704
<pat:template>
1705
  <pat:tex op="\circledast"/>
1706
  <pat:mml op="&#x229B;">
1707
    <mo> &#x229B; </mo>
1708
  </pat:mml>
1709
</pat:template>
1710
1711
<pat:template>
1712
  <pat:tex op="\circledcirc"/>
1713
  <pat:mml op="&#x229A;">
1714
    <mo> &#x229A; </mo>
1715
  </pat:mml>
1716
</pat:template>
1717
1718
<pat:template>
1719
  <pat:tex op="\centerdot"/>
1720
  <pat:mml op="&#x22C5;">
1721
    <mo> &#x22C5; </mo>
1722
  </pat:mml>
1723
</pat:template>
1724
1725
<pat:template>
1726
  <pat:tex op="\intercal"/>
1727
  <pat:mml op="&#x22BA;">
1728
    <mo> &#x22BA; </mo>
1729
  </pat:mml>
1730
</pat:template>
1731
1732
1733
1734
<!-- AMS Greek and Hebrew letters -->
1735
1736
<pat:template>
1737
  <pat:tex op="\digamma"/>
1738
  <pat:mml op="&#x03DC;">
1739
    <mo> &#x03DC; </mo>
1740
  </pat:mml>
1741
</pat:template>
1742
1743
<pat:template>
1744
  <pat:tex op="\varkappa"/>
1745
  <pat:mml op="&#x03F0;">
1746
    <mo> &#x03F0; </mo>
1747
  </pat:mml>
1748
</pat:template>
1749
1750
<pat:template>
1751
  <pat:tex op="\beth"/>
1752
  <pat:mml op="&#x2136;">
1753
    <mo> &#x2136; </mo>
1754
  </pat:mml>
1755
</pat:template>
1756
1757
<pat:template>
1758
  <pat:tex op="\daleth"/>
1759
  <pat:mml op="&#x2138;">
1760
    <mo> &#x2138; </mo>
1761
  </pat:mml>
1762
</pat:template>
1763
1764
<pat:template>
1765
  <pat:tex op="\gimel"/>
1766
  <pat:mml op="&#x2137;">
1767
    <mo> &#x2137; </mo>
1768
  </pat:mml>
1769
</pat:template>
1770
1771
1772
1773
<!-- AMS delimiters -->
1774
1775
<pat:template>
1776
  <pat:tex op="\ulcorner"/>
1777
  <pat:mml op="&#x231C;">
1778
    <mo> &#x231C; </mo>
1779
  </pat:mml>
1780
</pat:template>
1781
1782
<pat:template>
1783
  <pat:tex op="\urcorner"/>
1784
  <pat:mml op="&#x231D;">
1785
    <mo> &#x231D; </mo>
1786
  </pat:mml>
1787
</pat:template>
1788
1789
<pat:template>
1790
  <pat:tex op="\llcorner"/>
1791
  <pat:mml op="&#x231E;">
1792
    <mo> &#x231E; </mo>
1793
  </pat:mml>
1794
</pat:template>
1795
1796
<pat:template>
1797
  <pat:tex op="\lrcorner"/>
1798
  <pat:mml op="&#x231F;">
1799
    <mo> &#x231F; </mo>
1800
  </pat:mml>
1801
</pat:template>
1802
1803
1804
1805
<!-- AMS relational symbols -->
1806
1807
<pat:template>
1808
  <pat:tex op="\leqq"/>
1809
  <pat:mml op="&#x2266;">
1810
    <mo> &#x2266; </mo>
1811
  </pat:mml>
1812
</pat:template>
1813
1814
<pat:template>
1815
  <pat:tex op="\leqslant"/>
1816
  <pat:mml op="">
1817
    <mo> &#x2A7D; </mo>
1818
  </pat:mml>
1819
</pat:template>
1820
1821
<pat:template>
1822
  <pat:tex op="\eqslantless"/>
1823
  <pat:mml op="">
1824
    <mo> &#x2A95; </mo>
1825
  </pat:mml>
1826
</pat:template>
1827
1828
<pat:template>
1829
  <pat:tex op="\lessapprox"/>
1830
  <pat:mml op="">
1831
    <mo> &#x2A85; </mo>
1832
  </pat:mml>
1833
</pat:template>
1834
1835
<pat:template>
1836
  <pat:tex op="\lesssim"/>
1837
  <pat:mml op="&#x2272;">
1838
    <mo> &#x2272; </mo>
1839
  </pat:mml>
1840
</pat:template>
1841
1842
<pat:template>
1843
  <pat:tex op="\approxeq"/>
1844
  <pat:mml op="&#x224A;">
1845
    <mo> &#x224A; </mo>
1846
  </pat:mml>
1847
</pat:template>
1848
1849
<pat:template>
1850
  <pat:tex op="\lessdot"/>
1851
  <pat:mml op="&#x22D6;">
1852
    <mo> &#x22D6; </mo>
1853
  </pat:mml>
1854
</pat:template>
1855
1856
<pat:template>
1857
  <pat:tex op="\lll"/>
1858
  <pat:mml op="&#x22D8;">
1859
    <mo> &#x22D8; </mo>
1860
  </pat:mml>
1861
</pat:template>
1862
1863
<pat:template>
1864
  <pat:tex op="\lessgtr"/>
1865
  <pat:mml op="&#x2276;">
1866
    <mo> &#x2276; </mo>
1867
  </pat:mml>
1868
</pat:template>
1869
1870
<pat:template>
1871
  <pat:tex op="\lesseqgtr"/>
1872
  <pat:mml op="&#x22DA;">
1873
    <mo> &#x22DA; </mo>
1874
  </pat:mml>
1875
</pat:template>
1876
1877
<pat:template>
1878
  <pat:tex op="\lesseqqgtr"/>
1879
  <pat:mml op="">
1880
    <mo> &#x2A8B; </mo>
1881
  </pat:mml>
1882
</pat:template>
1883
1884
<pat:template>
1885
  <pat:tex op="\doteqdot"/>
1886
  <pat:mml op="&#x2251;">
1887
    <mo> &#x2251; </mo>
1888
  </pat:mml>
1889
</pat:template>
1890
1891
<pat:template>
1892
  <pat:tex op="\risingdotseq"/>
1893
  <pat:mml op="&#x2253;">
1894
    <mo> &#x2253; </mo>
1895
  </pat:mml>
1896
</pat:template>
1897
1898
<pat:template>
1899
  <pat:tex op="\fallingdotseq"/>
1900
  <pat:mml op="&#x2252;">
1901
    <mo> &#x2252; </mo>
1902
  </pat:mml>
1903
</pat:template>
1904
1905
<pat:template>
1906
  <pat:tex op="\backsim"/>
1907
  <pat:mml op="&#x223D;">
1908
    <mo> &#x223D; </mo>
1909
  </pat:mml>
1910
</pat:template>
1911
1912
<pat:template>
1913
  <pat:tex op="\backsimeq"/>
1914
  <pat:mml op="&#x22CD;">
1915
    <mo> &#x22CD; </mo>
1916
  </pat:mml>
1917
</pat:template>
1918
1919
<pat:template>
1920
  <pat:tex op="\subseteqq"/>
1921
  <pat:mml op="">
1922
    <mo> &#x2AC5; </mo>
1923
  </pat:mml>
1924
</pat:template>
1925
1926
<pat:template>
1927
  <pat:tex op="\Subset"/>
1928
  <pat:mml op="&#x22D0;">
1929
    <mo> &#x22D0; </mo>
1930
  </pat:mml>
1931
</pat:template>
1932
1933
<pat:template>
1934
  <pat:tex op="\sqsubset"/>
1935
  <pat:mml op="&#x228F;">
1936
    <mo> &#x228F; </mo>
1937
  </pat:mml>
1938
</pat:template>
1939
1940
<pat:template>
1941
  <pat:tex op="\preccurlyeq"/>
1942
  <pat:mml op="&#x227C;">
1943
    <mo> &#x227C; </mo>
1944
  </pat:mml>
1945
</pat:template>
1946
1947
<pat:template>
1948
  <pat:tex op="\curlyeqprec"/>
1949
  <pat:mml op="&#x22DE;">
1950
    <mo> &#x22DE; </mo>
1951
  </pat:mml>
1952
</pat:template>
1953
1954
<pat:template>
1955
  <pat:tex op="\precsim"/>
1956
  <pat:mml op="&#x227E;">
1957
    <mo> &#x227E; </mo>
1958
  </pat:mml>
1959
</pat:template>
1960
1961
<pat:template>
1962
  <pat:tex op="\precapprox"/>
1963
  <pat:mml op="">
1964
    <mo> &#x2AB7; </mo>
1965
  </pat:mml>
1966
</pat:template>
1967
1968
<pat:template>
1969
  <pat:tex op="\vartriangleleft"/>
1970
  <pat:mml op="&#x22B2;">
1971
    <mo> &#x22B2; </mo>
1972
  </pat:mml>
1973
</pat:template>
1974
1975
<pat:template>
1976
  <pat:tex op="\trianglelefteq"/>
1977
  <pat:mml op="&#x22B4;">
1978
    <mo> &#x22B4; </mo>
1979
  </pat:mml>
1980
</pat:template>
1981
1982
<pat:template>
1983
  <pat:tex op="\Vvdash"/>
1984
  <pat:mml op="&#x22AA;">
1985
    <mo> &#x22AA; </mo>
1986
  </pat:mml>
1987
</pat:template>
1988
1989
<pat:template>
1990
  <pat:tex op="\smallsmile"/>
1991
  <pat:mml op="&#x2323;">
1992
    <mo> &#x2323; </mo>
1993
  </pat:mml>
1994
</pat:template>
1995
1996
<pat:template>
1997
  <pat:tex op="\smallfrown"/>
1998
  <pat:mml op="&#x2322;">
1999
    <mo> &#x2322; </mo>
2000
  </pat:mml>
2001
</pat:template>
2002
2003
<pat:template>
2004
  <pat:tex op="\bumpeq"/>
2005
  <pat:mml op="&#x224F;">
2006
    <mo> &#x224F; </mo>
2007
  </pat:mml>
2008
</pat:template>
2009
2010
<pat:template>
2011
  <pat:tex op="\Bumpeq"/>
2012
  <pat:mml op="&#x224E;">
2013
    <mo> &#x224E; </mo>
2014
  </pat:mml>
2015
</pat:template>
2016
2017
<pat:template>
2018
  <pat:tex op="\geqq"/>
2019
  <pat:mml op="&#x2267;">
2020
    <mo> &#x2267; </mo>
2021
  </pat:mml>
2022
</pat:template>
2023
2024
<pat:template>
2025
  <pat:tex op="\geqslant"/>
2026
  <pat:mml op="">
2027
    <mo> &#x2A7E; </mo>
2028
  </pat:mml>
2029
</pat:template>
2030
2031
<pat:template>
2032
  <pat:tex op="\eqslantgtr"/>
2033
  <pat:mml op="">
2034
    <mo> &#x2A96; </mo>
2035
  </pat:mml>
2036
</pat:template>
2037
2038
<pat:template>
2039
  <pat:tex op="\eqslantgtr"/>
2040
  <pat:mml op="">
2041
    <mo> &#x22DD; </mo>
2042
  </pat:mml>
2043
</pat:template>
2044
2045
<pat:template>
2046
  <pat:tex op="\gtrsim"/>
2047
  <pat:mml op="&#x2273;">
2048
    <mo> &#x2273; </mo>
2049
  </pat:mml>
2050
</pat:template>
2051
2052
<pat:template>
2053
  <pat:tex op="\gtrapprox"/>
2054
  <pat:mml op="">
2055
    <mo> &#x2A86; </mo>
2056
  </pat:mml>
2057
</pat:template>
2058
2059
<pat:template>
2060
  <pat:tex op="\gtrdot"/>
2061
  <pat:mml op="&#x22D7;">
2062
    <mo> &#x22D7; </mo>
2063
  </pat:mml>
2064
</pat:template>
2065
2066
<pat:template>
2067
  <pat:tex op="\ggg"/>
2068
  <pat:mml op="&#x22D9;">
2069
    <mo> &#x22D9; </mo>
2070
  </pat:mml>
2071
</pat:template>
2072
2073
<pat:template>
2074
  <pat:tex op="\gtrless"/>
2075
  <pat:mml op="&#x2277;">
2076
    <mo> &#x2277; </mo>
2077
  </pat:mml>
2078
</pat:template>
2079
2080
<pat:template>
2081
  <pat:tex op="\gtreqless"/>
2082
  <pat:mml op="&#x22DB;">
2083
    <mo> &#x22DB; </mo>
2084
  </pat:mml>
2085
</pat:template>
2086
2087
<pat:template>
2088
  <pat:tex op="\gtreqqless"/>
2089
  <pat:mml op="">
2090
    <mo> &#x2A8C; </mo>
2091
  </pat:mml>
2092
</pat:template>
2093
2094
<pat:template>
2095
  <pat:tex op="\eqcirc"/>
2096
  <pat:mml op="&#x2256;">
2097
    <mo> &#x2256; </mo>
2098
  </pat:mml>
2099
</pat:template>
2100
2101
<pat:template>
2102
  <pat:tex op="\circeq"/>
2103
  <pat:mml op="&#x2257;">
2104
    <mo> &#x2257; </mo>
2105
  </pat:mml>
2106
</pat:template>
2107
2108
<pat:template>
2109
  <pat:tex op="\triangleq"/>
2110
  <pat:mml op="&#x225C;">
2111
    <mo> &#x225C; </mo>
2112
  </pat:mml>
2113
</pat:template>
2114
2115
<pat:template>
2116
  <pat:tex op="\thicksim"/>
2117
  <pat:mml op="&#x223C;">
2118
    <mo> &#x223C; </mo>
2119
  </pat:mml>
2120
</pat:template>
2121
2122
<pat:template>
2123
  <pat:tex op="\supseteqq"/>
2124
  <pat:mml op="">
2125
    <mo> &#x2AC6; </mo>
2126
  </pat:mml>
2127
</pat:template>
2128
2129
<pat:template>
2130
  <pat:tex op="\thickapprox"/>
2131
  <pat:mml op="&#x2248;">
2132
    <mo> &#x2248; </mo>
2133
  </pat:mml>
2134
</pat:template>
2135
2136
<pat:template>
2137
  <pat:tex op="\Supset"/>
2138
  <pat:mml op="&#x22D1;">
2139
    <mo> &#x22D1; </mo>
2140
  </pat:mml>
2141
</pat:template>
2142
2143
<pat:template>
2144
  <pat:tex op="\sqsupset"/>
2145
  <pat:mml op="&#x2290;">
2146
    <mo> &#x2290; </mo>
2147
  </pat:mml>
2148
</pat:template>
2149
2150
<pat:template>
2151
  <pat:tex op="\succcurlyeq"/>
2152
  <pat:mml op="&#x227D;">
2153
    <mo> &#x227D; </mo>
2154
  </pat:mml>
2155
</pat:template>
2156
2157
<pat:template>
2158
  <pat:tex op="\curlyeqsucc"/>
2159
  <pat:mml op="&#x22DF;">
2160
    <mo> &#x22DF; </mo>
2161
  </pat:mml>
2162
</pat:template>
2163
2164
<pat:template>
2165
  <pat:tex op="\succsim"/>
2166
  <pat:mml op="&#x227F;">
2167
    <mo> &#x227F; </mo>
2168
  </pat:mml>
2169
</pat:template>
2170
2171
<pat:template>
2172
  <pat:tex op="\succapprox"/>
2173
  <pat:mml op="">
2174
    <mo> &#x2AB8; </mo>
2175
  </pat:mml>
2176
</pat:template>
2177
2178
<pat:template>
2179
  <pat:tex op="\vartriangleright"/>
2180
  <pat:mml op="&#x22B3;">
2181
    <mo> &#x22B3; </mo>
2182
  </pat:mml>
2183
</pat:template>
2184
2185
<pat:template>
2186
  <pat:tex op="\trianglerighteq"/>
2187
  <pat:mml op="&#x22B5;">
2188
    <mo> &#x22B5; </mo>
2189
  </pat:mml>
2190
</pat:template>
2191
2192
<pat:template>
2193
  <pat:tex op="\Vdash"/>
2194
  <pat:mml op="&#x22A9;">
2195
    <mo> &#x22A9; </mo>
2196
  </pat:mml>
2197
</pat:template>
2198
2199
<pat:template>
2200
  <pat:tex op="\shortmid"/>
2201
  <pat:mml op="">
2202
    <mo> &#x2223; </mo>
2203
  </pat:mml>
2204
</pat:template>
2205
2206
<pat:template>
2207
  <pat:tex op="\shortparallel"/>
2208
  <pat:mml op="">
2209
    <mo> &#x2225; </mo>
2210
  </pat:mml>
2211
</pat:template>
2212
2213
<pat:template>
2214
  <pat:tex op="\between"/>
2215
  <pat:mml op="&#x226C;">
2216
    <mo> &#x226C; </mo>
2217
  </pat:mml>
2218
</pat:template>
2219
2220
<pat:template>
2221
  <pat:tex op="\pitchfork"/>
2222
  <pat:mml op="&#x23D4;">
2223
    <mo> &#x23D4; </mo>
2224
  </pat:mml>
2225
</pat:template>
2226
2227
<pat:template>
2228
  <pat:tex op="\varpropto"/>
2229
  <pat:mml op="&#x221D;">
2230
    <mo> &#x221D; </mo>
2231
  </pat:mml>
2232
</pat:template>
2233
2234
<pat:template>
2235
  <pat:tex op="\blacktriangleleft"/>
2236
  <pat:mml op="&#x25C0;">
2237
    <mo> &#x25C0; </mo>
2238
  </pat:mml>
2239
</pat:template>
2240
2241
<pat:template>
2242
  <pat:tex op="\therefore"/>
2243
  <pat:mml op="&#x2234;">
2244
    <mo> &#x2234; </mo>
2245
  </pat:mml>
2246
</pat:template>
2247
2248
<pat:template>
2249
  <pat:tex op="\backepsilon"/>
2250
  <pat:mml op="&#x220B;">
2251
    <mo> &#x220B; </mo>
2252
  </pat:mml>
2253
</pat:template>
2254
2255
<pat:template>
2256
  <pat:tex op="\blacktriangleright"/>
2257
  <pat:mml op="&#x25B6;">
2258
    <mo> &#x25B6; </mo>
2259
  </pat:mml>
2260
</pat:template>
2261
2262
<pat:template>
2263
  <pat:tex op="\because"/>
2264
  <pat:mml op="&#x2235;">
2265
    <mo> &#x2235; </mo>
2266
  </pat:mml>
2267
</pat:template>
2268
2269
2270
2271
<!-- AMS negated relational symbols -->
2272
2273
<pat:template>
2274
  <pat:tex op="\nless"/>
2275
  <pat:mml op="&#x226E;">
2276
    <mo> &#x226E; </mo>
2277
  </pat:mml>
2278
</pat:template>
2279
2280
<pat:template>
2281
  <pat:tex op="\nleq"/>
2282
  <pat:mml op="&#x2270;">
2283
    <mo> &#x2270; </mo>
2284
  </pat:mml>
2285
</pat:template>
2286
2287
<pat:template>
2288
  <pat:tex op="\lneq"/>
2289
  <pat:mml op="">
2290
    <mo> &#x2A87; </mo>
2291
  </pat:mml>
2292
</pat:template>
2293
2294
<pat:template>
2295
  <pat:tex op="\lneqq"/>
2296
  <pat:mml op="&#x2268;">
2297
    <mo> &#x2268; </mo>
2298
  </pat:mml>
2299
</pat:template>
2300
2301
<pat:template>
2302
  <pat:tex op="\lnsim"/>
2303
  <pat:mml op="&#x22E6;">
2304
    <mo> &#x22E6; </mo>
2305
  </pat:mml>
2306
</pat:template>
2307
2308
<pat:template>
2309
  <pat:tex op="\lnapprox"/>
2310
  <pat:mml op="">
2311
    <mo> &#x2A89; </mo>
2312
  </pat:mml>
2313
</pat:template>
2314
2315
<pat:template>
2316
  <pat:tex op="\precnsim"/>
2317
  <pat:mml op="&#x22E8;">
2318
    <mo> &#x22E8; </mo>
2319
  </pat:mml>
2320
</pat:template>
2321
2322
<pat:template>
2323
  <pat:tex op="\precnapprox"/>
2324
  <pat:mml op="">
2325
    <mo> &#x2AB9; </mo>
2326
  </pat:mml>
2327
</pat:template>
2328
2329
<pat:template>
2330
  <pat:tex op="\nsim"/>
2331
  <pat:mml op="&#x2241;">
2332
    <mo> &#x2241; </mo>
2333
  </pat:mml>
2334
</pat:template>
2335
2336
<pat:template>
2337
  <pat:tex op="\nmid"/>
2338
  <pat:mml op="&#x2224;">
2339
    <mo> &#x2224; </mo>
2340
  </pat:mml>
2341
</pat:template>
2342
2343
<pat:template>
2344
  <pat:tex op="\nvdash"/>
2345
  <pat:mml op="&#x22AC;">
2346
    <mo> &#x22AC; </mo>
2347
  </pat:mml>
2348
</pat:template>
2349
2350
<pat:template>
2351
  <pat:tex op="\nvDash"/>
2352
  <pat:mml op="&#x22AD;">
2353
    <mo> &#x22AD; </mo>
2354
  </pat:mml>
2355
</pat:template>
2356
2357
<pat:template>
2358
  <pat:tex op="\ntriangleleft"/>
2359
  <pat:mml op="&#x22EA;">
2360
    <mo> &#x22EA; </mo>
2361
  </pat:mml>
2362
</pat:template>
2363
2364
<pat:template>
2365
  <pat:tex op="\ntrianglelefteq"/>
2366
  <pat:mml op="&#x22EC;">
2367
    <mo> &#x22EC; </mo>
2368
  </pat:mml>
2369
</pat:template>
2370
2371
<pat:template>
2372
  <pat:tex op="\nsubseteq"/>
2373
  <pat:mml op="&#x2288;">
2374
    <mo> &#x2288; </mo>
2375
  </pat:mml>
2376
</pat:template>
2377
2378
<pat:template>
2379
  <pat:tex op="\subsetneq"/>
2380
  <pat:mml op="&#x228A;">
2381
    <mo> &#x228A; </mo>
2382
  </pat:mml>
2383
</pat:template>
2384
2385
<pat:template>
2386
  <pat:tex op="\subsetneqq"/>
2387
  <pat:mml op="">
2388
    <mo> &#x2ACB; </mo>
2389
  </pat:mml>
2390
</pat:template>
2391
2392
<pat:template>
2393
  <pat:tex op="\ntrianglelefteq"/>
2394
  <pat:mml op="&#x22EC;">
2395
    <mo> &#x22EC; </mo>
2396
  </pat:mml>
2397
</pat:template>
2398
2399
<pat:template>
2400
  <pat:tex op="\ngtr"/>
2401
  <pat:mml op="&#x226F;">
2402
    <mo> &#x226F; </mo>
2403
  </pat:mml>
2404
</pat:template>
2405
2406
<pat:template>
2407
  <pat:tex op="\ngeq"/>
2408
  <pat:mml op="&#x2271;">
2409
    <mo> &#x2271; </mo>
2410
  </pat:mml>
2411
</pat:template>
2412
2413
<pat:template>
2414
  <pat:tex op="\gneq"/>
2415
  <pat:mml op="">
2416
    <mo> &#x2A88; </mo>
2417
  </pat:mml>
2418
</pat:template>
2419
2420
<pat:template>
2421
  <pat:tex op="\gneqq"/>
2422
  <pat:mml op="&#x2269;">
2423
    <mo> &#x2269; </mo>
2424
  </pat:mml>
2425
</pat:template>
2426
2427
<pat:template>
2428
  <pat:tex op="\gnsim"/>
2429
  <pat:mml op="&#x22E7;">
2430
    <mo> &#x22E7; </mo>
2431
  </pat:mml>
2432
</pat:template>
2433
2434
<pat:template>
2435
  <pat:tex op="\gnapprox"/>
2436
  <pat:mml op="">
2437
    <mo> &#x2A8A; </mo>
2438
  </pat:mml>
2439
</pat:template>
2440
2441
<pat:template>
2442
  <pat:tex op="\succnsim"/>
2443
  <pat:mml op="&#x22E9;">
2444
    <mo> &#x22E9; </mo>
2445
  </pat:mml>
2446
</pat:template>
2447
2448
<pat:template>
2449
  <pat:tex op="\succnapprox"/>
2450
  <pat:mml op="">
2451
    <mo> &#x2ABA; </mo>
2452
  </pat:mml>
2453
</pat:template>
2454
2455
<pat:template>
2456
  <pat:tex op="\ncong"/>
2457
  <pat:mml op="&#x2247;">
2458
    <mo> &#x2247; </mo>
2459
  </pat:mml>
2460
</pat:template>
2461
2462
<pat:template>
2463
  <pat:tex op="\nshortparallel"/>
2464
  <pat:mml op="&#x2226;">
2465
    <mo> &#x2226; </mo>
2466
  </pat:mml>
2467
</pat:template>
2468
2469
<pat:template>
2470
  <pat:tex op="\nparallel"/>
2471
  <pat:mml op="&#x2226;">
2472
    <mo> &#x2226; </mo>
2473
  </pat:mml>
2474
</pat:template>
2475
2476
<pat:template>
2477
  <pat:tex op="\nVDash"/>
2478
  <pat:mml op="&#x22AF;">
2479
    <mo> &#x22AF; </mo>
2480
  </pat:mml>
2481
</pat:template>
2482
2483
<pat:template>
2484
  <pat:tex op="\ntriangleright"/>
2485
  <pat:mml op="&#x22EB;">
2486
    <mo> &#x22EB; </mo>
2487
  </pat:mml>
2488
</pat:template>
2489
2490
<pat:template>
2491
  <pat:tex op="\ntrianglerighteq"/>
2492
  <pat:mml op="&#x22ED;">
2493
    <mo> &#x22ED; </mo>
2494
  </pat:mml>
2495
</pat:template>
2496
2497
<pat:template>
2498
  <pat:tex op="\nsupseteq"/>
2499
  <pat:mml op="&#x2289;">
2500
    <mo> &#x2289; </mo>
2501
  </pat:mml>
2502
</pat:template>
2503
2504
<pat:template>
2505
  <pat:tex op="\nsupseteq"/>
2506
  <pat:mml op="&#x2289;">
2507
    <mo> &#x2289; </mo>
2508
  </pat:mml>
2509
</pat:template>
2510
2511
<pat:template>
2512
  <pat:tex op="\supsetneq"/>
2513
  <pat:mml op="&#x228B;">
2514
    <mo> &#x228B; </mo>
2515
  </pat:mml>
2516
</pat:template>
2517
2518
<pat:template>
2519
  <pat:tex op="\supsetneqq"/>
2520
  <pat:mml op="">
2521
    <mo> &#x2ACC; </mo>
2522
  </pat:mml>
2523
</pat:template>
2524
2525
2526
2527
<!-- Miscellaneous AMS symbols -->
2528
2529
<pat:template>
2530
  <pat:tex op="\hbar"/>
2531
  <pat:mml op="&#x0127;">
2532
    <mo> &#x0127; </mo>
2533
  </pat:mml>
2534
</pat:template>
2535
2536
<pat:template>
2537
  <pat:tex op="\hslash"/>
2538
  <pat:mml op="&#x210F;">
2539
    <mo> &#x210F; </mo>
2540
  </pat:mml>
2541
</pat:template>
2542
2543
<pat:template>
2544
  <pat:tex op="\vartriangle"/>
2545
  <pat:mml op="&#x25B3;">
2546
    <mo> &#x25B3; </mo>
2547
  </pat:mml>
2548
</pat:template>
2549
2550
<pat:template>
2551
  <pat:tex op="\triangledown"/>
2552
  <pat:mml op="&#x25BD;">
2553
    <mo> &#x25BD; </mo>
2554
  </pat:mml>
2555
</pat:template>
2556
2557
<pat:template>
2558
  <pat:tex op="\square"/>
2559
  <pat:mml op="&#x25A1;">
2560
    <mo> &#x25A1; </mo>
2561
  </pat:mml>
2562
</pat:template>
2563
2564
<pat:template>
2565
  <pat:tex op="\lozenge"/>
2566
  <pat:mml op="&#x25CA;">
2567
    <mo> &#x25CA; </mo>
2568
  </pat:mml>
2569
</pat:template>
2570
2571
<pat:template>
2572
  <pat:tex op="\circledS"/>
2573
  <pat:mml op="&#x24C8;">
2574
    <mo> &#x24C8; </mo>
2575
  </pat:mml>
2576
</pat:template>
2577
2578
<pat:template>
2579
  <pat:tex op="\measuredangle"/>
2580
  <pat:mml op="&#x2221;">
2581
    <mo> &#x2221; </mo>
2582
  </pat:mml>
2583
</pat:template>
2584
2585
<pat:template>
2586
  <pat:tex op="\nexists"/>
2587
  <pat:mml op="&#x2204;">
2588
    <mo> &#x2204; </mo>
2589
  </pat:mml>
2590
</pat:template>
2591
2592
<pat:template>
2593
  <pat:tex op="\mho"/>
2594
  <pat:mml op="&#x2127;">
2595
    <mo> &#x2127; </mo>
2596
  </pat:mml>
2597
</pat:template>
2598
2599
<pat:template>
2600
  <pat:tex op="\Finv"/>
2601
  <pat:mml op="&#x2132;">
2602
    <mo> &#x2132; </mo>
2603
  </pat:mml>
2604
</pat:template>
2605
2606
<pat:template>
2607
  <pat:tex op="\backprime"/>
2608
  <pat:mml op="&#x2035;">
2609
    <mo> &#x2035; </mo>
2610
  </pat:mml>
2611
</pat:template>
2612
2613
<pat:template>
2614
  <pat:tex op="\varnothing"/>
2615
  <pat:mml op="&#x00D8;">
2616
    <mo> &#x00D8; </mo>
2617
  </pat:mml>
2618
</pat:template>
2619
2620
<pat:template>
2621
  <pat:tex op="\blacktriangle"/>
2622
  <pat:mml op="&#x25B2;">
2623
    <mo> &#x25B2; </mo>
2624
  </pat:mml>
2625
</pat:template>
2626
2627
<pat:template>
2628
  <pat:tex op="\blacktriangledown"/>
2629
  <pat:mml op="&#x25BC;">
2630
    <mo> &#x25BC; </mo>
2631
  </pat:mml>
2632
</pat:template>
2633
2634
<pat:template>
2635
  <pat:tex op="\blacksquare"/>
2636
  <pat:mml op="&#x25A0;">
2637
    <mo> &#x25A0; </mo>
2638
  </pat:mml>
2639
</pat:template>
2640
2641
<pat:template>
2642
  <pat:tex op="\blacklozenge"/>
2643
  <pat:mml op="&#x25CA;">
2644
    <mo> &#x25CA; </mo>
2645
  </pat:mml>
2646
</pat:template>
2647
2648
<pat:template>
2649
  <pat:tex op="\bigstar"/>
2650
  <pat:mml op="&#x2605;">
2651
    <mo> &#x2605; </mo>
2652
  </pat:mml>
2653
</pat:template>
2654
2655
<pat:template>
2656
  <pat:tex op="\sphericalangle"/>
2657
  <pat:mml op="&#x2222;">
2658
    <mo> &#x2222; </mo>
2659
  </pat:mml>
2660
</pat:template>
2661
2662
<pat:template>
2663
  <pat:tex op="\complement"/>
2664
  <pat:mml op="&#x2201;">
2665
    <mo> &#x2201; </mo>
2666
  </pat:mml>
2667
</pat:template>
2668
2669
<pat:template>
2670
  <pat:tex op="\eth"/>
2671
  <pat:mml op="&#x00F0;">
2672
    <mo> &#x00F0; </mo>
2673
  </pat:mml>
2674
</pat:template>
2675
2676
<pat:template>
2677
  <pat:tex op="\qed"/>
2678
  <pat:mml op="&#x25A1;">
2679
    <mo> &#x25A1; </mo>
2680
  </pat:mml>
2681
</pat:template>
2682
2683
2684
2685
<!-- Handlers for parenthesized structures -->
2686
2687
<pat:template>
2688
  <pat:tex op="\left" params=". \patVAR*{expr} \right." prec="154"/>
2689
  <pat:mml op="">
2690
    <pat:variable name="expr"/>
2691
  </pat:mml>
2692
</pat:template>
2693
2694
<pat:template>
2695
  <pat:tex op="\left" params="\patVAR!{lDelim} \patVAR*{expr} \right." prec="152"/>
2696
  <pat:mml op="">
2697
    <mfenced separators="" close="">
2698
      <pat:variable name="lDelim" attribute="open"/>
2699
      <pat:variable name="expr"/>
2700
	</mfenced>
2701
  </pat:mml>
2702
</pat:template>
2703
2704
<pat:template>
2705
  <pat:tex op="\left" params=". \patVAR*{expr} \right\patVAR!{rDelim}" prec="152"/>
2706
  <pat:mml op="">
2707
    <mfenced separators="" open="">
2708
      <pat:variable name="expr"/>
2709
      <pat:variable name="rDelim" attribute="close"/>
2710
	</mfenced>
2711
  </pat:mml>
2712
</pat:template>
2713
2714
<pat:template>
2715
  <pat:tex op="\left" params="\patVAR!{lDelim} \patVAR*{expr} \right\patVAR!{rDelim}" prec="150"/>
2716
  <pat:mml op="">
2717
    <mfenced separators="">
2718
      <pat:variable name="lDelim" attribute="open"/>
2719
      <pat:variable name="expr"/>
2720
      <pat:variable name="rDelim" attribute="close"/>
2721
	</mfenced>
2722
  </pat:mml>
2723
</pat:template>
2724
2725
2726
<!-- Used by MathML to TeX only -->
2727
2728
<!-- {B} -->
2729
<pat:template>
2730
  <pat:tex op="" params="\left\{ \patVAR*{c} \right\}"/>
2731
  <pat:mml op="mfenced">
2732
    <mfenced open="{" close="}">
2733
     <pat:variable name="c"/>
2734
    </mfenced>
2735
  </pat:mml>
2736
</pat:template>
2737
2738
<!-- {BsBsT}  -->
2739
<pat:template>
2740
  <pat:tex op="" params="\left\{ \patREP*{\patVAR*{b}\patREP*{\patVAR{~s}}}\patVAR*{t} \right\}"/>
2741
  <pat:mml op="mfenced">
2742
    <mfenced open="{" close="}" separators="rep:variable =~s">
2743
     <pat:rep> <pat:variable name="b"/> </pat:rep>
2744
     <pat:variable name="t"/>
2745
    </mfenced>
2746
  </pat:mml>
2747
</pat:template>
2748
2749
<!-- {B,B,T} -->
2750
<pat:template>
2751
  <pat:tex op="" params="\left\{ \patREP*{\patVAR*{b},}\patVAR*{t} \right\}"/>
2752
  <pat:mml op="mfenced">
2753
    <mfenced open="{" close="}">
2754
     <pat:rep> <pat:variable name="b"/> </pat:rep>
2755
     <pat:variable name="t"/>
2756
    </mfenced>
2757
  </pat:mml>
2758
</pat:template>
2759
2760
<!-- {B \left.C -->
2761
<pat:template>
2762
  <pat:tex op="" params="\left\{ \patVAR*{b} \right\patVAR!{c}"/>
2763
  <pat:mml op="mfenced">
2764
    <mfenced open="{" close="pat:variable =c">
2765
     <pat:variable name="b"/>
2766
    </mfenced>
2767
  </pat:mml>
2768
</pat:template>
2769
2770
<!-- {BsBsT \left.C -->
2771
<pat:template>
2772
  <pat:tex op="" params="\left\{ \patREP*{\patVAR*{b}\patREP*{\patVAR{~s}}}\patVAR*{t} \right\patVAR!{c}"/>
2773
  <pat:mml op="mfenced">
2774
    <mfenced open="{" close="pat:variable =c" separators="rep:variable =~s">
2775
     <pat:rep> <pat:variable name="b"/> </pat:rep>
2776
     <pat:variable name="t"/>
2777
    </mfenced>
2778
  </pat:mml>
2779
</pat:template>
2780
2781
<!-- {B,B,T \left.C -->
2782
<pat:template>
2783
  <pat:tex op="" params="\left\{ \patREP*{\patVAR*{b},}\patVAR*{t} \right\patVAR!{c}"/>
2784
  <pat:mml op="mfenced">
2785
    <mfenced open="{" close="pat:variable =c">
2786
     <pat:rep> <pat:variable name="b"/> </pat:rep>
2787
     <pat:variable name="t"/>
2788
    </mfenced>
2789
  </pat:mml>
2790
</pat:template>
2791
2792
<!-- \right.O B} -->
2793
<pat:template>
2794
  <pat:tex op="" params="\left \patVAR!{o} \patVAR*{c} \right\}"/>
2795
  <pat:mml op="mfenced">
2796
    <mfenced open="pat:variable =o" close="}">
2797
     <pat:variable name="c"/>
2798
    </mfenced>
2799
  </pat:mml>
2800
</pat:template>
2801
2802
<!-- \right.O BsBsT}-->
2803
<pat:template>
2804
  <pat:tex op="" params="\left\patVAR!{o} \patREP*{\patVAR*{b}\patREP*{\patVAR{~s}}}\patVAR{t} \right\}"/>
2805
  <pat:mml op="mfenced">
2806
    <mfenced open="pat:variable =o" close="}" separators="rep:variable =~s">
2807
     <pat:rep> <pat:variable name="b"/></pat:rep>
2808
     <pat:variable name="t"/>
2809
    </mfenced>
2810
  </pat:mml>
2811
</pat:template>
2812
2813
<!-- \right.O B,B,T} -->
2814
<pat:template>
2815
  <pat:tex op="" params="\left\patVAR!{o} \patREP*{\patVAR*{b},}\patVAR{t} \right\}"/>
2816
  <pat:mml op="mfenced">
2817
    <mfenced open="pat:variable =o" close="}">
2818
     <pat:rep> <pat:variable name="b"/></pat:rep>
2819
     <pat:variable name="t"/>
2820
    </mfenced>
2821
  </pat:mml>
2822
</pat:template>
2823
2824
<!-- \right.O B \left.C -->
2825
<pat:template>
2826
 <pat:tex op="" params="\left\patVAR!{o} \patVAR*{b} \right\patVAR!{c}"/>
2827
  <pat:mml op="mfenced">
2828
    <mfenced open="pat:variable=o" close="pat:variable=c">
2829
     <pat:variable name="b"/>
2830
    </mfenced>
2831
  </pat:mml>
2832
</pat:template>
2833
2834
<!-- \right.O B s B s T\left.C -->
2835
<pat:template>
2836
 <pat:tex op="" params="\left\patVAR!{o} \patREP*{\patVAR*{b} \patREP*{\patVAR{~s}}} \patVAR{t} \right\patVAR!{c}"/>
2837
  <pat:mml op="mfenced">
2838
    <mfenced open="pat:variable=o" close="pat:variable=c" separators="rep:variable=~s">
2839
     <pat:rep><pat:variable name="b"/></pat:rep>
2840
     <pat:variable name="t"/>
2841
    </mfenced>
2842
  </pat:mml>
2843
</pat:template>
2844
2845
<!-- \right.O B,B,T \left.C -->
2846
<pat:template>
2847
 <pat:tex op="" params="\left\patVAR!{o} \patREP*{\patVAR*{b},}\patVAR{t} \right\patVAR!{c}"/>
2848
  <pat:mml op="mfenced">
2849
    <mfenced open="pat:variable=o" close="pat:variable=c">
2850
     <pat:rep> <pat:variable name="b"/> </pat:rep>
2851
     <pat:variable name="t"/>
2852
    </mfenced>
2853
  </pat:mml>
2854
</pat:template>
2855
2856
<!-- {B) -->
2857
<pat:template>
2858
  <pat:tex op="" params="\left\{ \patVAR*{b} \right)"/>
2859
  <pat:mml op="mfenced">
2860
    <mfenced open="{">
2861
     <pat:variable name="b"/>
2862
    </mfenced>
2863
  </pat:mml>
2864
</pat:template>
2865
2866
<!-- \left.O B) -->
2867
<pat:template>
2868
  <pat:tex op="" params="\left\patVAR{o} \patVAR*{b} \right)"/>
2869
  <pat:mml op="mfenced">
2870
    <mfenced open="pat:variable =o">
2871
     <pat:variable name="b"/>
2872
    </mfenced>
2873
  </pat:mml>
2874
</pat:template>
2875
2876
<!-- (B} -->
2877
<pat:template>
2878
  <pat:tex op="" params="\left( \patVAR*{b} \right\}"/>
2879
  <pat:mml op="mfenced">
2880
    <mfenced open="{">
2881
     <pat:variable name="b"/>
2882
    </mfenced>
2883
  </pat:mml>
2884
</pat:template>
2885
2886
<!-- (B \.c -->
2887
<pat:template>
2888
  <pat:tex op="" params="\left( \patVAR*{a} \right\patVAR*{c}"/>
2889
  <pat:mml op="mfenced">
2890
    <mfenced close="pat:variable =c">
2891
     <pat:variable name="b"/>
2892
    </mfenced>
2893
  </pat:mml>
2894
</pat:template>
2895
2896
<!-- (B) -->
2897
<pat:template>
2898
  <pat:tex op="" params="\left( \patVAR*{c} \right)"/>
2899
  <pat:mml op="mfenced">
2900
    <mfenced>
2901
     <pat:variable name="c"/>
2902
    </mfenced>
2903
  </pat:mml>
2904
</pat:template>
2905
2906
<!-- (B s B s T) -->
2907
<pat:template>
2908
  <pat:tex op="" params="\left( \patREP*{\patVAR{b} \patREP*{\patVAR!{~s}}} \patVAR{t} \right)"/>
2909
  <pat:mml op="mfenced">
2910
    <mfenced separators="rep:variable name =~s">
2911
      <pat:rep>
2912
	<pat:variable name="b"/>
2913
      </pat:rep>
2914
      <pat:variable name="t"/>
2915
    </mfenced>
2916
  </pat:mml>
2917
</pat:template>
2918
2919
<!-- { B,B,T) -->
2920
<pat:template>
2921
  <pat:tex op="" params="\left\{ \patREP*{\patVAR*{b},} \patVAR{t} \right)"/>
2922
  <pat:mml op="mfenced">
2923
    <mfenced open="{">
2924
      <pat:rep>
2925
	<pat:variable name="b"/>
2926
      </pat:rep>
2927
      <pat:variable name="t"/>
2928
    </mfenced>
2929
  </pat:mml>
2930
</pat:template>
2931
2932
<!-- \open B,B,T) -->
2933
<pat:template>
2934
  <pat:tex op="" params="\left\patVAR!{o} \patREP*{\patVAR*{b},} \patVAR{t} \right)"/>
2935
  <pat:mml op="mfenced">
2936
    <mfenced open="pat:variable name=o">
2937
      <pat:rep>
2938
	<pat:variable name="b"/>
2939
      </pat:rep>
2940
      <pat:variable name="t"/>
2941
    </mfenced>
2942
  </pat:mml>
2943
</pat:template>
2944
2945
<!-- (B,B,T } -->
2946
<pat:template>
2947
  <pat:tex op="" params="\left( \patREP*{\patVAR*{b},} \patVAR{t} \right\}"/>
2948
  <pat:mml op="mfenced">
2949
    <mfenced close="}">
2950
      <pat:rep>
2951
	<pat:variable name="b"/>
2952
      </pat:rep>
2953
      <pat:variable name="t"/>
2954
    </mfenced>
2955
  </pat:mml>
2956
</pat:template>
2957
2958
<!-- \open B,B,T) -->
2959
<pat:template>
2960
  <pat:tex op="" params="\left\patVAR!{o} \patREP*{\patVAR*{b},} \patVAR{t} \right)"/>
2961
  <pat:mml op="mfenced">
2962
    <mfenced open="pat:variable name=o">
2963
      <pat:rep>
2964
	<pat:variable name="b"/>
2965
      </pat:rep>
2966
      <pat:variable name="t"/>
2967
    </mfenced>
2968
  </pat:mml>
2969
</pat:template>
2970
2971
<!-- (B,B,T \close -->
2972
<pat:template>
2973
  <pat:tex op="" params="\left( \patREP*{\patVAR*{b},} \patVAR{t} \right\patVAR!{c}"/>
2974
  <pat:mml op="mfenced">
2975
    <mfenced close="pat:variable name=c">
2976
      <pat:rep>
2977
	<pat:variable name="b"/>
2978
      </pat:rep>
2979
      <pat:variable name="t"/>
2980
    </mfenced>
2981
  </pat:mml>
2982
</pat:template>
2983
2984
<!-- (B,B,T) -->
2985
<pat:template>
2986
  <pat:tex op="" params="\left( \patREP*{\patVAR*{b},} \patVAR{t} \right)"/>
2987
  <pat:mml op="mfenced">
2988
    <mfenced>
2989
      <pat:rep>
2990
	<pat:variable name="b"/>
2991
      </pat:rep>
2992
      <pat:variable name="t"/>
2993
    </mfenced>
2994
  </pat:mml>
2995
</pat:template>
2996
2997
<!-- END: MathML to TeX -->
2998
2999
3000
3001
<!-- Under/over modifiers -->
3002
3003
<pat:template>
3004
  <pat:tex op="\underbrace" params="\patVAR!{expr}_\patVAR!{lims}"/>
3005
  <pat:mml op="">
3006
    <munder>
3007
      <munder>
3008
        <pat:variable name="expr"/>
3009
        <mo stretchy="true"> &#xFE38; </mo>
3010
      </munder>
3011
      <pat:variable name="lims"/>
3012
    </munder>
3013
  </pat:mml>
3014
</pat:template>
3015
3016
<pat:template>
3017
  <pat:tex op="\underbrace" params="\patVAR!{expr}"/>
3018
  <pat:mml op="munder &#xFE38;">
3019
    <munder>
3020
      <pat:variable name="expr"/>
3021
      <mo stretchy="true"> &#xFE38; </mo>
3022
    </munder>
3023
  </pat:mml>
3024
</pat:template>
3025
3026
<pat:template>
3027
  <pat:tex op="\overbrace" params="\patVAR!{expr}^\patVAR!{lims}"/>
3028
  <pat:mml op="">
3029
    <mover>
3030
      <mover>
3031
        <pat:variable name="expr"/>
3032
        <mo stretchy="true"> &#xFE37; </mo>
3033
      </mover>
3034
      <pat:variable name="lims"/>
3035
    </mover>
3036
  </pat:mml>
3037
</pat:template>
3038
3039
<pat:template>
3040
  <pat:tex op="\overbrace" params="\patVAR!{expr}"/>
3041
  <pat:mml op="mover &#xFE37;">
3042
    <mover>
3043
      <pat:variable name="expr"/>
3044
      <mo stretchy="true"> &#xFE37; </mo>
3045
    </mover>
3046
  </pat:mml>
3047
</pat:template>
3048
3049
<pat:template>
3050
  <pat:tex op="\underline" params="\patVAR!{expr}"/>	<!-- 02CD 005F 00AF -->
3051
  <pat:mml op="munder &#x0332;">
3052
    <munder>
3053
      <pat:variable name="expr"/>
3054
      <mo stretchy="true"> &#x00AF; </mo>
3055
    </munder>
3056
  </pat:mml>
3057
</pat:template>
3058
3059
<pat:template>
3060
  <pat:tex op="\overline" params="\patVAR!{expr}"/>		<!-- 02C9 00AF -->
3061
  <pat:mml op="mover &#x00AF;">
3062
    <mover>
3063
      <pat:variable name="expr"/>
3064
      <mo stretchy="true"> &#x00AF; </mo>
3065
    </mover>
3066
  </pat:mml>
3067
</pat:template>
3068
3069
<pat:template>
3070
  <pat:tex op="\widehat" params="\patVAR!{expr}"/>		<!-- 02C6 0060 -->
3071
  <pat:mml op="mover &#x0302;">
3072
    <mover>
3073
      <pat:variable name="expr"/>
3074
      <mo>&#x02C6;</mo>
3075
    </mover>
3076
  </pat:mml>
3077
</pat:template>
3078
3079
<pat:template>
3080
  <pat:tex op="\widetilde" params="\patVAR!{expr}"/>	<!-- 02DC 007E -->
3081
  <pat:mml op="mover &#x0303;">
3082
    <mover>
3083
      <pat:variable name="expr"/>
3084
      <mo>&#x02DC;</mo>
3085
    </mover>
3086
  </pat:mml>
3087
</pat:template>
3088
3089
3090
3091
<!-- Math accents -->
3092
3093
<pat:template>
3094
  <pat:tex op="\hat" params="\patVAR!{symbol}"/>	<!-- 02C6 005E -->
3095
  <pat:mml op="mover &#x0302;">
3096
    <mover>
3097
      <pat:variable name="symbol"/>
3098
      <mo>&#x02C6;</mo>
3099
    </mover>
3100
  </pat:mml>
3101
</pat:template>
3102
3103
<pat:template>
3104
  <pat:tex op="\Hat" params="\patVAR!{symbol}"/>
3105
  <pat:mml op="mover &#x0302;">
3106
    <mover>
3107
      <pat:variable name="symbol"/>
3108
      <mo>&#x02C6;</mo>
3109
    </mover>
3110
  </pat:mml>
3111
</pat:template>
3112
3113
<pat:template>
3114
  <pat:tex op="\breve" params="\patVAR!{symbol}"/>	<!-- 02D8 -->
3115
  <pat:mml op="mover &#x0306;">
3116
    <mover>
3117
      <pat:variable name="symbol"/>
3118
      <mo>&#x02D8;</mo>
3119
    </mover>
3120
  </pat:mml>
3121
</pat:template>
3122
3123
<pat:template>
3124
  <pat:tex op="\Breve" params="\patVAR!{symbol}"/>
3125
  <pat:mml op="mover &#x0306;">
3126
    <mover>
3127
      <pat:variable name="symbol"/>
3128
      <mo>&#x02D8;</mo>
3129
    </mover>
3130
  </pat:mml>
3131
</pat:template>
3132
3133
<pat:template>
3134
  <pat:tex op="\grave" params="\patVAR!{symbol}"/>	<!-- 02CB 0060 -->
3135
  <pat:mml op="mover &#x0300;">
3136
    <mover>
3137
      <pat:variable name="symbol"/>
3138
      <mo>&#x02CB;</mo>
3139
    </mover>
3140
  </pat:mml>
3141
</pat:template>
3142
3143
<pat:template>
3144
  <pat:tex op="\Grave" params="\patVAR!{symbol}"/>
3145
  <pat:mml op="mover &#x0300;">
3146
    <mover>
3147
      <pat:variable name="symbol"/>
3148
      <mo>&#x02CB;</mo>
3149
    </mover>
3150
  </pat:mml>
3151
</pat:template>
3152
3153
<pat:template>
3154
  <pat:tex op="\bar" params="\patVAR!{symbol}"/>	<!-- 02C9 00AF -->
3155
  <pat:mml op="mover &#x0304;">
3156
    <mover>
3157
      <pat:variable name="symbol"/>
3158
      <mo>&#x02C9;</mo>
3159
    </mover>
3160
  </pat:mml>
3161
</pat:template>
3162
3163
<pat:template>
3164
  <pat:tex op="\Bar" params="\patVAR!{symbol}"/>
3165
  <pat:mml op="mover &#x0304;">
3166
    <mover>
3167
      <pat:variable name="symbol"/>
3168
      <mo>&#x02C9;</mo>
3169
    </mover>
3170
  </pat:mml>
3171
</pat:template>
3172
3173
<pat:template>
3174
  <pat:tex op="\check" params="\patVAR!{symbol}"/>	<!-- 02C7 -->
3175
  <pat:mml op="mover &#x030C;">
3176
    <mover>
3177
      <pat:variable name="symbol"/>
3178
      <mo>&#x02C7;</mo>
3179
    </mover>
3180
  </pat:mml>
3181
</pat:template>
3182
3183
<pat:template>
3184
  <pat:tex op="\Check" params="\patVAR!{symbol}"/>
3185
  <pat:mml op="mover &#x030C;">
3186
    <mover>
3187
      <pat:variable name="symbol"/>
3188
      <mo>&#x02C7;</mo>
3189
    </mover>
3190
  </pat:mml>
3191
</pat:template>
3192
3193
<pat:template>
3194
  <pat:tex op="\acute" params="\patVAR!{symbol}"/>	<!-- 02B9 00B4 -->
3195
  <pat:mml op="mover &#x0301;">
3196
    <mover>
3197
      <pat:variable name="symbol"/>
3198
      <mo>&#x02B9;</mo>
3199
    </mover>
3200
  </pat:mml>
3201
</pat:template>
3202
3203
<pat:template>
3204
  <pat:tex op="\Acute" params="\patVAR!{symbol}"/>
3205
  <pat:mml op="mover &#x0301;">
3206
    <mover>
3207
      <pat:variable name="symbol"/>
3208
      <mo>&#x02B9;</mo>
3209
    </mover>
3210
  </pat:mml>
3211
</pat:template>
3212
3213
<pat:template>
3214
  <pat:tex op="\tilde" params="\patVAR!{symbol}"/>	<!-- 02DC 007E -->
3215
  <pat:mml op="mover &#x0303;">
3216
    <mover>
3217
      <pat:variable name="symbol"/>
3218
      <mo>&#x02DC;</mo>
3219
    </mover>
3220
  </pat:mml>
3221
</pat:template>
3222
3223
<pat:template>
3224
  <pat:tex op="\Tilde" params="\patVAR!{symbol}"/>
3225
  <pat:mml op="mover &#x0303;">
3226
    <mover>
3227
      <pat:variable name="symbol"/>
3228
      <mo>&#x02DC;</mo>
3229
    </mover>
3230
  </pat:mml>
3231
</pat:template>
3232
3233
<pat:template>
3234
  <pat:tex op="\vec" params="\patVAR!{symbol}"/>
3235
  <pat:mml op="mover &#x0304;">
3236
    <mover>
3237
      <pat:variable name="symbol"/>
3238
      <mo>&#x20D7;</mo>
3239
    </mover>
3240
  </pat:mml>
3241
</pat:template>
3242
3243
<pat:template>
3244
  <pat:tex op="\Vec" params="\patVAR!{symbol}"/>
3245
  <pat:mml op="mover &#x0304;">
3246
    <mover>
3247
      <pat:variable name="symbol"/>
3248
      <mo>&#x20D7;</mo>
3249
    </mover>
3250
  </pat:mml>
3251
</pat:template>
3252
3253
3254
<pat:template>
3255
  <pat:tex op="\dot" params="\patVAR!{symbol}"/>		<!-- 02D9 -->
3256
  <pat:mml op="">
3257
    <mover>
3258
      <pat:variable name="symbol"/>
3259
      <mo> . </mo>
3260
    </mover>
3261
  </pat:mml>
3262
</pat:template>
3263
3264
<pat:template>
3265
  <pat:tex op="\Dot" params="\patVAR!{symbol}"/>
3266
  <pat:mml op="">
3267
    <mover>
3268
      <pat:variable name="symbol"/>
3269
      <mo> . </mo>
3270
    </mover>
3271
  </pat:mml>
3272
</pat:template>
3273
3274
<pat:template>
3275
  <pat:tex op="\ddot" params="\patVAR!{symbol}"/>		<!-- 00A8 -->
3276
  <pat:mml op="">
3277
    <mover>
3278
      <pat:variable name="symbol"/>
3279
      <mo> .. </mo>
3280
    </mover>
3281
  </pat:mml>
3282
</pat:template>
3283
3284
<pat:template>
3285
  <pat:tex op="\Ddot" params="\patVAR!{symbol}"/>
3286
  <pat:mml op="">
3287
    <mover>
3288
      <pat:variable name="symbol"/>
3289
      <mo> .. </mo>
3290
    </mover>
3291
  </pat:mml>
3292
</pat:template>
3293
3294
<pat:template>
3295
  <pat:tex op="\dddot" params="\patVAR!{symbol}"/>		<!-- 20DB -->
3296
  <pat:mml op="">
3297
    <mover>
3298
      <pat:variable name="symbol"/>
3299
      <mo> ... </mo>
3300
    </mover>
3301
  </pat:mml>
3302
</pat:template>
3303
3304
<pat:template>
3305
  <pat:tex op="\ddddot" params="\patVAR!{symbol}"/>		<!-- 20DC -->
3306
  <pat:mml op="">
3307
    <mover>
3308
      <pat:variable name="symbol"/>
3309
      <mo> .... </mo>
3310
    </mover>
3311
  </pat:mml>
3312
</pat:template>
3313
3314
<pat:template>
3315
  <pat:tex op="\mathring" params="\patVAR!{symbol}"/>	<!-- 20DA -->
3316
  <pat:mml op="mover &#x030A;">
3317
    <mover>
3318
      <pat:variable name="symbol"/>
3319
      <mo>&#x02DA;</mo>
3320
    </mover>
3321
  </pat:mml>
3322
</pat:template>
3323
3324
3325
3326
<!-- Accents -->
3327
3328
<pat:template>
3329
  <pat:tex op="\`" params="\patVAR!{symbol}"/>
3330
  <pat:mml op="">
3331
    <pat:variable name="symbol"/>&#x0300;
3332
  </pat:mml>
3333
</pat:template>
3334
3335
<pat:template>
3336
  <pat:tex op="\'" params="\patVAR!{symbol}"/>
3337
  <pat:mml op="">
3338
    <pat:variable name="symbol"/>&#x0301;
3339
  </pat:mml>
3340
</pat:template>
3341
3342
<pat:template>
3343
  <pat:tex op="\^" params="\patVAR!{symbol}"/>
3344
  <pat:mml op="">
3345
    <pat:variable name="symbol"/>&#x0302;
3346
  </pat:mml>
3347
</pat:template>
3348
3349
<pat:template>
3350
  <pat:tex op="\&quot;" params="\patVAR!{symbol}"/>
3351
  <pat:mml op="">
3352
    <pat:variable name="symbol"/>&#x0308;
3353
  </pat:mml>
3354
</pat:template>
3355
3356
<pat:template>
3357
  <pat:tex op="\~" params="\patVAR!{symbol}"/>
3358
  <pat:mml op="">
3359
    <pat:variable name="symbol"/>&#x0303;
3360
  </pat:mml>
3361
</pat:template>
3362
3363
<pat:template>
3364
  <pat:tex op="\=" params="\patVAR!{symbol}"/>
3365
  <pat:mml op="">
3366
    <pat:variable name="symbol"/>&#x0304;
3367
  </pat:mml>
3368
</pat:template>
3369
3370
<pat:template>
3371
  <pat:tex op="\." params="\patVAR!{symbol}"/>
3372
  <pat:mml op="">
3373
    <pat:variable name="symbol"/>&#x0307;
3374
  </pat:mml>
3375
</pat:template>
3376
3377
<pat:template>
3378
  <pat:tex op="\u" params="{\patVAR+{symbol}}"/>
3379
  <pat:mml op="">
3380
    <pat:variable name="symbol"/>&#x0306;
3381
  </pat:mml>
3382
</pat:template>
3383
3384
<pat:template>
3385
  <pat:tex op="\v" params="{\patVAR+{symbol}}"/>
3386
  <pat:mml op="">
3387
    <pat:variable name="symbol"/>&#x030C;
3388
  </pat:mml>
3389
</pat:template>
3390
3391
<pat:template>
3392
  <pat:tex op="\H" params="{\patVAR+{symbol}}"/>
3393
  <pat:mml op="">
3394
    <pat:variable name="symbol"/>&#x030B;
3395
  </pat:mml>
3396
</pat:template>
3397
3398
<pat:template>
3399
  <pat:tex op="\t" params="{\patVAR+{symbol}}"/>
3400
  <pat:mml op="">
3401
    <pat:variable name="symbol"/>&#x0361;
3402
  </pat:mml>
3403
</pat:template>
3404
3405
<pat:template>
3406
  <pat:tex op="\c" params="{\patVAR+{symbol}}"/>
3407
  <pat:mml op="">
3408
    <pat:variable name="symbol"/>&#x0327;
3409
  </pat:mml>
3410
</pat:template>
3411
3412
<pat:template>
3413
  <pat:tex op="\d" params="{\patVAR+{symbol}}"/>
3414
  <pat:mml op="">
3415
    <pat:variable name="symbol"/>&#x0323;
3416
  </pat:mml>
3417
</pat:template>
3418
3419
<pat:template>
3420
  <pat:tex op="\b" params="{\patVAR+{symbol}}"/>
3421
  <pat:mml op="">
3422
    <pat:variable name="symbol"/>&#x0320;
3423
  </pat:mml>
3424
</pat:template>
3425
3426
<pat:template>
3427
  <pat:tex op="\r" params="{\patVAR+{symbol}}"/>
3428
  <pat:mml op="">
3429
    <pat:variable name="symbol"/>&#x030A;
3430
  </pat:mml>
3431
</pat:template>
3432
3433
<pat:template>
3434
  <pat:tex op="\i"/>
3435
  <pat:mml op="">
3436
    <mo> &#x0131; </mo>
3437
  </pat:mml>
3438
</pat:template>
3439
3440
<pat:template>
3441
  <pat:tex op="\j"/>		<!-- nothing even close -->
3442
  <pat:mml op="">
3443
    <mo> &#x006A; </mo>
3444
  </pat:mml>
3445
</pat:template>
3446
3447
3448
3449
<!-- Greek alphabet -->
3450
3451
<pat:template>
3452
  <pat:tex op="\alpha"/>
3453
  <pat:mml op="&#x03B1;">
3454
    <mi> &#x03B1; </mi>
3455
  </pat:mml>
3456
</pat:template>
3457
3458
<pat:template>
3459
  <pat:tex op="\beta"/>
3460
  <pat:mml op="&#x03B2;">
3461
    <mi> &#x03B2; </mi>
3462
  </pat:mml>
3463
</pat:template>
3464
3465
<pat:template>
3466
  <pat:tex op="\gamma"/>
3467
  <pat:mml op="&#x03B3;">
3468
    <mi> &#x03B3; </mi>
3469
  </pat:mml>
3470
</pat:template>
3471
3472
<pat:template>
3473
  <pat:tex op="\delta"/>
3474
  <pat:mml op="&#x03B4;">
3475
    <mi> &#x03B4; </mi>
3476
  </pat:mml>
3477
</pat:template>
3478
3479
<pat:template>
3480
  <pat:tex op="\epsilon"/>
3481
  <pat:mml op="&#x03B5;">
3482
    <mi> &#x03B5; </mi>
3483
  </pat:mml>
3484
</pat:template>
3485
3486
<pat:template>
3487
  <pat:tex op="\varepsilon"/>
3488
  <pat:mml op="&#x03B5;">
3489
    <mi> &#x03B5; </mi>
3490
  </pat:mml>
3491
</pat:template>
3492
3493
<pat:template>
3494
  <pat:tex op="\zeta"/>
3495
  <pat:mml op="&#x03B6;">
3496
    <mi> &#x03B6; </mi>
3497
  </pat:mml>
3498
</pat:template>
3499
3500
<pat:template>
3501
  <pat:tex op="\eta"/>
3502
  <pat:mml op="&#x03B7;">
3503
    <mi> &#x03B7; </mi>
3504
  </pat:mml>
3505
</pat:template>
3506
3507
<pat:template>
3508
  <pat:tex op="\theta"/>
3509
  <pat:mml op="&#x03B8;">
3510
    <mi> &#x03B8; </mi>
3511
  </pat:mml>
3512
</pat:template>
3513
3514
<pat:template>
3515
  <pat:tex op="\vartheta"/>
3516
  <pat:mml op="&#x03D1;">
3517
    <mi> &#x03D1; </mi>
3518
  </pat:mml>
3519
</pat:template>
3520
3521
<pat:template>
3522
  <pat:tex op="\iota"/>
3523
  <pat:mml op="&#x03B9;">
3524
    <mi> &#x03B9; </mi>
3525
  </pat:mml>
3526
</pat:template>
3527
3528
<pat:template>
3529
  <pat:tex op="\kappa"/>
3530
  <pat:mml op="&#x03BA;">
3531
    <mi> &#x03BA; </mi>
3532
  </pat:mml>
3533
</pat:template>
3534
3535
<pat:template>
3536
  <pat:tex op="\lambda"/>
3537
  <pat:mml op="&#x03BB;">
3538
    <mi> &#x03BB; </mi>
3539
  </pat:mml>
3540
</pat:template>
3541
3542
<pat:template>
3543
  <pat:tex op="\mu"/>
3544
  <pat:mml op="&#x03BC;">
3545
    <mi> &#x03BC; </mi>
3546
  </pat:mml>
3547
</pat:template>
3548
3549
<pat:template>
3550
  <pat:tex op="\nu"/>
3551
  <pat:mml op="&#x03BD;">
3552
    <mi> &#x03BD; </mi>
3553
  </pat:mml>
3554
</pat:template>
3555
3556
<pat:template>
3557
  <pat:tex op="\xi"/>
3558
  <pat:mml op="&#x03BE;">
3559
    <mi> &#x03BE; </mi>
3560
  </pat:mml>
3561
</pat:template>
3562
3563
<pat:template>
3564
  <pat:tex op="\pi"/>
3565
  <pat:mml op="&#x03C0;">
3566
    <mi> &#x03C0; </mi>
3567
  </pat:mml>
3568
</pat:template>
3569
3570
<pat:template>
3571
  <pat:tex op="\varpi"/>
3572
  <pat:mml op="&#x03D6;">
3573
    <mi> &#x03D6; </mi>
3574
  </pat:mml>
3575
</pat:template>
3576
3577
<pat:template>
3578
  <pat:tex op="\rho"/>
3579
  <pat:mml op="&#x03C1;">
3580
    <mi> &#x03C1; </mi>
3581
  </pat:mml>
3582
</pat:template>
3583
3584
<pat:template>
3585
  <pat:tex op="\varrho"/>
3586
  <pat:mml op="&#x03F1;">
3587
    <mi> &#x03F1; </mi>
3588
  </pat:mml>
3589
</pat:template>
3590
3591
<pat:template>
3592
  <pat:tex op="\varsigma"/>
3593
  <pat:mml op="&#x03C2;">
3594
    <mi> &#x03C2; </mi>
3595
  </pat:mml>
3596
</pat:template>
3597
3598
<pat:template>
3599
  <pat:tex op="\sigma"/>
3600
  <pat:mml op="&#x03C3;">
3601
    <mi> &#x03C3; </mi>
3602
  </pat:mml>
3603
</pat:template>
3604
3605
<pat:template>
3606
  <pat:tex op="\tau"/>
3607
  <pat:mml op="&#x03C4;">
3608
    <mi> &#x03C4; </mi>
3609
  </pat:mml>
3610
</pat:template>
3611
3612
<pat:template>
3613
  <pat:tex op="\upsilon"/>
3614
  <pat:mml op="&#x03C5;">
3615
    <mi> &#x03C5; </mi>
3616
  </pat:mml>
3617
</pat:template>
3618
3619
<pat:template>
3620
  <pat:tex op="\phi"/>
3621
  <pat:mml op="&#x03C6;">
3622
    <mi> &#x03C6; </mi>
3623
  </pat:mml>
3624
</pat:template>
3625
3626
<pat:template>
3627
  <pat:tex op="\varphi"/>
3628
  <pat:mml op="&#x03D5;">
3629
    <mi> &#x03D5; </mi>
3630
  </pat:mml>
3631
</pat:template>
3632
3633
<pat:template>
3634
  <pat:tex op="\chi"/>
3635
  <pat:mml op="&#x03C7;">
3636
    <mi> &#x03C7; </mi>
3637
  </pat:mml>
3638
</pat:template>
3639
3640
<pat:template>
3641
  <pat:tex op="\psi"/>
3642
  <pat:mml op="&#x03C8;">
3643
    <mi> &#x03C8; </mi>
3644
  </pat:mml>
3645
</pat:template>
3646
3647
<pat:template>
3648
  <pat:tex op="\omega"/>
3649
  <pat:mml op="&#x03C9;">
3650
    <mi> &#x03C9; </mi>
3651
  </pat:mml>
3652
</pat:template>
3653
3654
3655
3656
<pat:template>
3657
  <pat:tex op="\Gamma"/>
3658
  <pat:mml op="&#x0393;">
3659
    <mi> &#x0393; </mi>
3660
  </pat:mml>
3661
</pat:template>
3662
3663
<pat:template>
3664
  <pat:tex op="\Delta"/>
3665
  <pat:mml op="&#x0394;">
3666
    <mi> &#x0394; </mi>
3667
  </pat:mml>
3668
</pat:template>
3669
3670
<pat:template>
3671
  <pat:tex op="\Theta"/>
3672
  <pat:mml op="&#x0398;">
3673
    <mi> &#x0398; </mi>
3674
  </pat:mml>
3675
</pat:template>
3676
3677
<pat:template>
3678
  <pat:tex op="\Lambda"/>
3679
  <pat:mml op="&#x039B;">
3680
    <mi> &#x039B; </mi>
3681
  </pat:mml>
3682
</pat:template>
3683
3684
<pat:template>
3685
  <pat:tex op="\Xi"/>
3686
  <pat:mml op="&#x039E;">
3687
    <mi> &#x039E; </mi>
3688
  </pat:mml>
3689
</pat:template>
3690
3691
<pat:template>
3692
  <pat:tex op="\Pi"/>
3693
  <pat:mml op="&#x03A0;">
3694
    <mi> &#x03A0; </mi>
3695
  </pat:mml>
3696
</pat:template>
3697
3698
<pat:template>
3699
  <pat:tex op="\Sigma"/>
3700
  <pat:mml op="&#x03A3;">
3701
    <mi> &#x03A3; </mi>
3702
  </pat:mml>
3703
</pat:template>
3704
3705
<pat:template>
3706
  <pat:tex op="\Upsilon"/>
3707
  <pat:mml op="&#x03A5;">
3708
    <mi> &#x03A5; </mi>
3709
  </pat:mml>
3710
</pat:template>
3711
3712
<pat:template>
3713
  <pat:tex op="\Phi"/>
3714
  <pat:mml op="&#x03A6;">
3715
    <mi> &#x03A6; </mi>
3716
  </pat:mml>
3717
</pat:template>
3718
3719
<pat:template>
3720
  <pat:tex op="\Psi"/>
3721
  <pat:mml op="&#x03A8;">
3722
    <mi> &#x03A8; </mi>
3723
  </pat:mml>
3724
</pat:template>
3725
3726
<pat:template>
3727
  <pat:tex op="\Omega"/>
3728
  <pat:mml op="&#x03A9;">
3729
    <mi> &#x03A9; </mi>
3730
  </pat:mml>
3731
</pat:template>
3732
3733
3734
<pat:template>
3735
  <pat:tex op="\varGamma"/>
3736
  <pat:mml op="&#x1D6E4;">
3737
    <mi> &#x0393; </mi>
3738
  </pat:mml>
3739
</pat:template>
3740
3741
<pat:template>
3742
  <pat:tex op="\varDelta"/>
3743
  <pat:mml op="&#x1D6E5;">
3744
    <mi> &#x0394; </mi>
3745
  </pat:mml>
3746
</pat:template>
3747
3748
<pat:template>
3749
  <pat:tex op="\varTheta"/>
3750
  <pat:mml op="&#x1D6E9;">
3751
    <mi> &#x0398; </mi>
3752
  </pat:mml>
3753
</pat:template>
3754
3755
<pat:template>
3756
  <pat:tex op="\varLambda"/>
3757
  <pat:mml op="&#x1D6EC;">
3758
    <mi> &#x039B; </mi>
3759
  </pat:mml>
3760
</pat:template>
3761
3762
<pat:template>
3763
  <pat:tex op="\varXi"/>
3764
  <pat:mml op="&#x1D6EF;">
3765
    <mi> &#x039E; </mi>
3766
  </pat:mml>
3767
</pat:template>
3768
3769
<pat:template>
3770
  <pat:tex op="\varPi"/>
3771
  <pat:mml op="&#x1D6F1;">
3772
    <mi> &#x03A0; </mi>
3773
  </pat:mml>
3774
</pat:template>
3775
3776
<pat:template>
3777
  <pat:tex op="\varSigma"/>
3778
  <pat:mml op="&#x1D6F4;">
3779
    <mi> &#x03A3; </mi>
3780
  </pat:mml>
3781
</pat:template>
3782
3783
<pat:template>
3784
  <pat:tex op="\varUpsilon"/>
3785
  <pat:mml op="&#x1D6F6;">
3786
    <mi> &#x03A5; </mi>
3787
  </pat:mml>
3788
</pat:template>
3789
3790
<pat:template>
3791
  <pat:tex op="\varPhi"/>
3792
  <pat:mml op="&#x1D6F7;">
3793
    <mi> &#x03A6; </mi>
3794
  </pat:mml>
3795
</pat:template>
3796
3797
<pat:template>
3798
  <pat:tex op="\varPsi"/>
3799
  <pat:mml op="&#x1D6F9;">
3800
    <mi> &#x03A8; </mi>
3801
  </pat:mml>
3802
</pat:template>
3803
3804
<pat:template>
3805
  <pat:tex op="\varOmega"/>
3806
  <pat:mml op="&#x1D6FA;">
3807
    <mi> &#x03A9; </mi>
3808
  </pat:mml>
3809
</pat:template>
3810
3811
3812
3813
<!-- Miscellaneous common characters -->
3814
3815
<pat:template>
3816
  <pat:tex op="\colon"/>
3817
  <pat:mml op=":">
3818
    <mo> : </mo>
3819
  </pat:mml>
3820
</pat:template>
3821
3822
<pat:template>
3823
  <pat:tex op="*"/>
3824
  <pat:mml op="*">
3825
    <mo> * </mo>
3826
  </pat:mml>
3827
</pat:template>
3828
3829
<pat:template>
3830
  <pat:tex op="\#"/>
3831
  <pat:mml op="#">
3832
    <mo> # </mo>
3833
  </pat:mml>
3834
</pat:template>
3835
3836
<pat:template>
3837
  <pat:tex op="\$"/>
3838
  <pat:mml op="$">
3839
    <mo> $ </mo>
3840
  </pat:mml>
3841
</pat:template>
3842
3843
<pat:template>
3844
  <pat:tex op="\%"/>
3845
  <pat:mml op="%">
3846
    <mo> % </mo>
3847
  </pat:mml>
3848
</pat:template>
3849
3850
<pat:template>
3851
  <pat:tex op="\&amp;"/>
3852
  <pat:mml op="&amp;">
3853
    <mo> &amp; </mo>
3854
  </pat:mml>
3855
</pat:template>
3856
3857
<pat:template>
3858
  <pat:tex op="\_"/>
3859
  <pat:mml op="_">
3860
    <mo> _ </mo>
3861
  </pat:mml>
3862
</pat:template>
3863
3864
<pat:template>
3865
  <pat:tex op="!"/>
3866
  <pat:mml op="!">
3867
    <mo> ! </mo>
3868
  </pat:mml>
3869
</pat:template>
3870
3871
3872
3873
<!-- Miscellaneous symbols -->
3874
3875
<pat:template>
3876
  <pat:tex op="\aleph"/>
3877
  <pat:mml op="&#x2135;">
3878
    <mo> &#x2135; </mo>
3879
  </pat:mml>
3880
</pat:template>
3881
3882
<pat:template>
3883
  <pat:tex op="\imath"/>		<!-- 0269 -->
3884
  <pat:mml op="">
3885
    <mo> &#x2373; </mo>
3886
  </pat:mml>
3887
</pat:template>
3888
3889
<pat:template>
3890
  <pat:tex op="\jmath"/>		<!-- nothing even close -->
3891
  <pat:mml op="">
3892
    <mo> &#x006A; </mo>
3893
  </pat:mml>
3894
</pat:template>
3895
3896
<pat:template>
3897
  <pat:tex op="\ell"/>
3898
  <pat:mml op="&#x2113;">
3899
    <mo> &#x2113; </mo>
3900
  </pat:mml>
3901
</pat:template>
3902
3903
<pat:template>
3904
  <pat:tex op="\wp"/>
3905
  <pat:mml op="&#x2118;">
3906
    <mo> &#x2118; </mo>
3907
  </pat:mml>
3908
</pat:template>
3909
3910
<pat:template>
3911
  <pat:tex op="\Re"/>
3912
  <pat:mml op="&#x211C;">
3913
    <mo> &#x211C; </mo>
3914
  </pat:mml>
3915
</pat:template>
3916
3917
<pat:template>
3918
  <pat:tex op="\Im"/>
3919
  <pat:mml op="&#x2111;">
3920
    <mo> &#x2111; </mo>
3921
  </pat:mml>
3922
</pat:template>
3923
3924
<pat:template>
3925
  <pat:tex op="\prime"/>
3926
  <pat:mml op="&#x2032;">
3927
    <mo> &#x2032; </mo>
3928
  </pat:mml>
3929
</pat:template>
3930
3931
<pat:template>
3932
  <pat:tex op="\emptyset"/>
3933
  <pat:mml op="&#x2205;">
3934
    <mo> &#x2205; </mo>
3935
  </pat:mml>
3936
</pat:template>
3937
3938
<pat:template>
3939
  <pat:tex op="\nabla"/>
3940
  <pat:mml op="&#x2207;">
3941
    <mo> &#x2207; </mo>
3942
  </pat:mml>
3943
</pat:template>
3944
3945
<pat:template>
3946
  <pat:tex op="\surd"/>
3947
  <pat:mml op="&#x221A;">
3948
    <mo> &#x221A; </mo>
3949
  </pat:mml>
3950
</pat:template>
3951
3952
<pat:template>
3953
  <pat:tex op="\partial"/>
3954
  <pat:mml op="&#x2202;">
3955
    <mo> &#x2202; </mo>
3956
  </pat:mml>
3957
</pat:template>
3958
3959
<pat:template>
3960
  <pat:tex op="\top"/>
3961
  <pat:mml op="&#x03A4;">
3962
    <mo> &#x03A4; </mo>
3963
  </pat:mml>
3964
</pat:template>
3965
3966
<pat:template>
3967
  <pat:tex op="\bot"/>
3968
  <pat:mml op="&#x03A5;">
3969
    <mo> &#x03A5; </mo>
3970
  </pat:mml>
3971
</pat:template>
3972
3973
<pat:template>
3974
  <pat:tex op="\vdash"/>
3975
  <pat:mml op="&#x22A2;">
3976
    <mo> &#x22A2; </mo>
3977
  </pat:mml>
3978
</pat:template>
3979
3980
<pat:template>
3981
  <pat:tex op="\dashv"/>
3982
  <pat:mml op="&#x22A3;">
3983
    <mo> &#x22A3; </mo>
3984
  </pat:mml>
3985
</pat:template>
3986
3987
<pat:template>
3988
  <pat:tex op="\forall"/>
3989
  <pat:mml op="&#x2200;">
3990
    <mo> &#x2200; </mo>
3991
  </pat:mml>
3992
</pat:template>
3993
3994
<pat:template>
3995
  <pat:tex op="\exists"/>
3996
  <pat:mml op="&#x2203;">
3997
    <mo> &#x2203; </mo>
3998
  </pat:mml>
3999
</pat:template>
4000
4001
<pat:template>
4002
  <pat:tex op="\neg"/>
4003
  <pat:mml op="&#x00AC;">
4004
    <mo> &#x00AC; </mo>
4005
  </pat:mml>
4006
</pat:template>
4007
4008
<pat:template>
4009
  <pat:tex op="\flat"/>
4010
  <pat:mml op="&#x266D;">
4011
    <mo> &#x266D; </mo>
4012
  </pat:mml>
4013
</pat:template>
4014
4015
<pat:template>
4016
  <pat:tex op="\natural"/>
4017
  <pat:mml op="&#x266E;">
4018
    <mo> &#x266E; </mo>
4019
  </pat:mml>
4020
</pat:template>
4021
4022
<pat:template>
4023
  <pat:tex op="\sharp"/>
4024
  <pat:mml op="&#x266F;">
4025
    <mo> &#x266F; </mo>
4026
  </pat:mml>
4027
</pat:template>
4028
4029
<pat:template>
4030
  <pat:tex op="\angle"/>
4031
  <pat:mml op="&#x2220;">
4032
    <mo> &#x2220; </mo>
4033
  </pat:mml>
4034
</pat:template>
4035
4036
<pat:template>
4037
  <pat:tex op="\Box"/>
4038
  <pat:mml op="&#x25AB;">
4039
    <mo> &#x25AB; </mo>
4040
  </pat:mml>
4041
</pat:template>
4042
4043
<pat:template>
4044
  <pat:tex op="\Diamond"/>
4045
  <pat:mml op="&#x25CA;">
4046
    <mo> &#x25CA; </mo>
4047
  </pat:mml>
4048
</pat:template>
4049
4050
<pat:template>
4051
  <pat:tex op="\triangle"/>
4052
  <pat:mml op="&#x25B3;">
4053
    <mo> &#x25B3; </mo>
4054
  </pat:mml>
4055
</pat:template>
4056
4057
<pat:template>
4058
  <pat:tex op="\clubsuit"/>
4059
  <pat:mml op="&#x2663;">
4060
    <mo> &#x2663; </mo>
4061
  </pat:mml>
4062
</pat:template>
4063
4064
<pat:template>
4065
  <pat:tex op="\diamondsuit"/>
4066
  <pat:mml op="&#x2666;">
4067
    <mo> &#x2666; </mo>
4068
  </pat:mml>
4069
</pat:template>
4070
4071
<pat:template>
4072
  <pat:tex op="\heartsuit"/>
4073
  <pat:mml op="&#x2665;">
4074
    <mo> &#x2665; </mo>
4075
  </pat:mml>
4076
</pat:template>
4077
4078
<pat:template>
4079
  <pat:tex op="\spadesuit"/>
4080
  <pat:mml op=" &#x2660;">
4081
    <mo> &#x2660; </mo>
4082
  </pat:mml>
4083
</pat:template>
4084
4085
<pat:template>
4086
  <pat:tex op="\Join"/>
4087
  <pat:mml op="&#x22C8;">
4088
    <mo> &#x22C8; </mo>
4089
  </pat:mml>
4090
</pat:template>
4091
4092
<pat:template>
4093
  <pat:tex op="\infty"/>
4094
  <pat:mml op=" &#x221E;">
4095
    <mo> &#x221E; </mo>
4096
  </pat:mml>
4097
</pat:template>
4098
4099
4100
<pat:template>
4101
  <pat:tex op="\lnot"/>
4102
  <pat:mml op="&#x2310;">
4103
    <mo> &#x2310; </mo>
4104
  </pat:mml>
4105
</pat:template>
4106
4107
<pat:template>
4108
  <pat:tex op="\bull"/>
4109
  <pat:mml op="">
4110
    <mo> &#x25AA; </mo>
4111
  </pat:mml>
4112
</pat:template>
4113
4114
<pat:template>
4115
  <pat:tex op="\cents"/>
4116
  <pat:mml op="">
4117
    <mo> &#x00A2; </mo>
4118
  </pat:mml>
4119
</pat:template>
4120
4121
4122
4123
<!-- Math symbols in two sizes -->
4124
4125
<pat:template>
4126
  <pat:tex op="\sum" params="\nolimits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4127
  <pat:mml op="&#x02211;">
4128
    <msubsup>
4129
      <mo> &#x02211; </mo>
4130
      <pat:variable name="a"/>
4131
      <pat:variable name="b"/>
4132
    </msubsup>
4133
  </pat:mml>
4134
</pat:template>
4135
4136
<pat:template>
4137
  <pat:tex op="\sum" params="\limits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4138
  <pat:mml op="&#x02211;">
4139
    <munderover>
4140
      <mo> &#x02211; </mo>
4141
      <pat:variable name="a"/>
4142
      <pat:variable name="b"/>
4143
    </munderover>
4144
  </pat:mml>
4145
</pat:template>
4146
4147
<pat:template>
4148
  <pat:tex op="\sum" params="\nolimits_\patVAR!{a}"/>
4149
  <pat:mml op="&#x02211;">
4150
    <msub>
4151
      <mo> &#x02211; </mo>
4152
      <pat:variable name="a"/>
4153
    </msub>
4154
  </pat:mml>
4155
</pat:template>
4156
4157
<pat:template>
4158
  <pat:tex op="\sum" params="_\patVAR!{a}&#x005E;\patVAR!{b}" prec="350"/>
4159
  <pat:mml op="&#x02211;">
4160
    <munderover>
4161
      <mo> &#x02211; </mo>
4162
      <pat:variable name="a"/>
4163
      <pat:variable name="b"/>
4164
    </munderover>
4165
  </pat:mml>
4166
</pat:template>
4167
4168
<pat:template>
4169
  <pat:tex op="\sum" params="_\patVAR!{a}" prec="350"/>
4170
  <pat:mml op="&#x02211;">
4171
    <munder>
4172
      <mo> &#x02211; </mo>
4173
      <pat:variable name="a"/>
4174
    </munder>
4175
  </pat:mml>
4176
</pat:template>
4177
4178
<pat:template>
4179
  <pat:tex op="\sum"/>
4180
  <pat:mml op="&#x02211;">
4181
      <mo> &#x02211; </mo>
4182
  </pat:mml>
4183
</pat:template>
4184
4185
<pat:template>
4186
  <pat:tex op="\prod" params="\nolimits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4187
  <pat:mml op="&#x220F;">
4188
    <msubsup>
4189
      <mo> &#x220F; </mo>
4190
      <pat:variable name="a"/>
4191
      <pat:variable name="b"/>
4192
    </msubsup>
4193
  </pat:mml>
4194
</pat:template>
4195
4196
<pat:template>
4197
  <pat:tex op="\prod" params="\limits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4198
  <pat:mml op="&#x220F;">
4199
    <munderover>
4200
      <mo> &#x220F; </mo>
4201
      <pat:variable name="a"/>
4202
      <pat:variable name="b"/>
4203
    </munderover>
4204
  </pat:mml>
4205
</pat:template>
4206
4207
<pat:template>
4208
  <pat:tex op="\prod" params="\nolimits_\patVAR!{a}"/>
4209
  <pat:mml op="&#x220F;">
4210
    <msub>
4211
      <mo> &#x220F; </mo>
4212
      <pat:variable name="a"/>
4213
    </msub>
4214
  </pat:mml>
4215
</pat:template>
4216
4217
<pat:template>
4218
  <pat:tex op="\prod" params="_\patVAR!{a}&#x005E;\patVAR!{b}" prec="350"/>
4219
  <pat:mml op="&#x220F;">
4220
    <munderover>
4221
      <mo> &#x220F; </mo>
4222
      <pat:variable name="a"/>
4223
      <pat:variable name="b"/>
4224
    </munderover>
4225
  </pat:mml>
4226
</pat:template>
4227
4228
<pat:template>
4229
  <pat:tex op="\prod" params="_\patVAR!{a}" prec="350"/>
4230
  <pat:mml op="&#x220F;">
4231
    <munder>
4232
      <mo> &#x220F; </mo>
4233
      <pat:variable name="a"/>
4234
    </munder>
4235
  </pat:mml>
4236
</pat:template>
4237
4238
<pat:template>
4239
  <pat:tex op="\prod"/>
4240
  <pat:mml op="&#x220F;">
4241
    <mo> &#x220F; </mo>
4242
  </pat:mml>
4243
</pat:template>
4244
4245
<pat:template>
4246
  <pat:tex op="\coprod" params="\nolimits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4247
  <pat:mml op="&#x2210;">
4248
    <msubsup>
4249
      <mo> &#x2210; </mo>
4250
      <pat:variable name="a"/>
4251
      <pat:variable name="b"/>
4252
    </msubsup>
4253
  </pat:mml>
4254
</pat:template>
4255
4256
<pat:template>
4257
  <pat:tex op="\coprod" params="\nolimits_\patVAR!{a}"/>
4258
  <pat:mml op="&#x2210;">
4259
    <msub>
4260
      <mo> &#x2210; </mo>
4261
      <pat:variable name="a"/>
4262
    </msub>
4263
  </pat:mml>
4264
</pat:template>
4265
4266
<pat:template>
4267
  <pat:tex op="\coprod" params="_\patVAR!{a}&#x005E;\patVAR!{b}" prec="350"/>
4268
  <pat:mml op="&#x2210;">
4269
    <munderover>
4270
      <mo> &#x2210; </mo>
4271
      <pat:variable name="a"/>
4272
      <pat:variable name="b"/>
4273
    </munderover>
4274
  </pat:mml>
4275
</pat:template>
4276
4277
<pat:template>
4278
  <pat:tex op="\coprod" params="_\patVAR!{a}" prec="350"/>
4279
  <pat:mml op="&#x2210;">
4280
    <munder>
4281
      <mo> &#x2210; </mo>
4282
      <pat:variable name="a"/>
4283
    </munder>
4284
  </pat:mml>
4285
</pat:template>
4286
4287
<pat:template>
4288
  <pat:tex op="\coprod"/>
4289
  <pat:mml op="&#x2210;">
4290
    <mo> &#x2210; </mo>
4291
  </pat:mml>
4292
</pat:template>
4293
4294
4295
<pat:template>
4296
  <pat:tex op="\int" params="\limits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4297
  <pat:mml op="&#x222B;">
4298
    <munderover>
4299
      <mo> &#x222B; </mo>
4300
      <pat:variable name="a"/>
4301
      <pat:variable name="b"/>
4302
    </munderover>
4303
  </pat:mml>
4304
</pat:template>
4305
4306
<pat:template>
4307
  <pat:tex op="\int" params="\limits_\patVAR!{a}"/>
4308
  <pat:mml op="&#x222B;">
4309
    <munder>
4310
      <mo> &#x222B; </mo>
4311
      <pat:variable name="a"/>
4312
    </munder>
4313
  </pat:mml>
4314
</pat:template>
4315
4316
<pat:template>
4317
  <pat:tex op="\int" params="\nolimits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4318
  <pat:mml op="&#x222B;">
4319
    <msubsup>
4320
      <mo> &#x222B; </mo>
4321
      <pat:variable name="a"/>
4322
      <pat:variable name="b"/>
4323
    </msubsup>
4324
  </pat:mml>
4325
</pat:template>
4326
4327
<pat:template>
4328
  <pat:tex op="\int" params="\nolimits_\patVAR!{a}"/>
4329
  <pat:mml op="&#x222B;">
4330
    <msub>
4331
      <mo> &#x222B; </mo>
4332
      <pat:variable name="a"/>
4333
    </msub>
4334
  </pat:mml>
4335
</pat:template>
4336
4337
<pat:template>
4338
  <pat:tex op="\int" params="_\patVAR!{a}&#x005E;\patVAR!{b}" prec="350"/>
4339
  <pat:mml op="&#x222B;">
4340
    <munderover>
4341
      <mo> &#x222B; </mo>
4342
      <pat:variable name="a"/>
4343
      <pat:variable name="b"/>
4344
    </munderover>
4345
  </pat:mml>
4346
</pat:template>
4347
4348
<pat:template>
4349
  <pat:tex op="\int" params="_\patVAR!{a}" prec="350"/>
4350
  <pat:mml op="&#x222B;">
4351
    <munder>
4352
      <mo> &#x222B; </mo>
4353
      <pat:variable name="a"/>
4354
    </munder>
4355
  </pat:mml>
4356
</pat:template>
4357
4358
<pat:template>
4359
  <pat:tex op="\int"/>
4360
  <pat:mml op="&#x222B;">
4361
    <mo> &#x222B; </mo>
4362
  </pat:mml>
4363
</pat:template>
4364
4365
4366
<pat:template>
4367
  <pat:tex op="\iint" params="\limits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4368
  <pat:mml op="&#x222C;">
4369
    <munderover>
4370
      <mo> &#x222C; </mo>
4371
      <pat:variable name="a"/>
4372
      <pat:variable name="b"/>
4373
    </munderover>
4374
  </pat:mml>
4375
</pat:template>
4376
4377
<pat:template>
4378
  <pat:tex op="\iint" params="\limits_\patVAR!{a}"/>
4379
  <pat:mml op="&#x222C;">
4380
    <munder>
4381
      <mo> &#x222C; </mo>
4382
      <pat:variable name="a"/>
4383
    </munder>
4384
  </pat:mml>
4385
</pat:template>
4386
4387
<pat:template>
4388
  <pat:tex op="\iint" params="\nolimits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4389
  <pat:mml op="&#x222C;">
4390
    <msubsup>
4391
      <mo> &#x222C; </mo>
4392
      <pat:variable name="a"/>
4393
      <pat:variable name="b"/>
4394
    </msubsup>
4395
  </pat:mml>
4396
</pat:template>
4397
4398
<pat:template>
4399
  <pat:tex op="\iint" params="\nolimits_\patVAR!{a}"/>
4400
  <pat:mml op="&#x222C;">
4401
    <msub>
4402
      <mo> &#x222C; </mo>
4403
      <pat:variable name="a"/>
4404
    </msub>
4405
  </pat:mml>
4406
</pat:template>
4407
4408
<pat:template>
4409
  <pat:tex op="\iint" params="_\patVAR!{a}&#x005E;\patVAR!{b}" prec="350"/>
4410
  <pat:mml op="&#x222C;">
4411
    <munderover>
4412
      <mo> &#x222C; </mo>
4413
      <pat:variable name="a"/>
4414
      <pat:variable name="b"/>
4415
    </munderover>
4416
  </pat:mml>
4417
</pat:template>
4418
4419
<pat:template>
4420
  <pat:tex op="\iint" params="_\patVAR!{a}" prec="350"/>
4421
  <pat:mml op="&#x222C;">
4422
    <munder>
4423
      <mo> &#x222C; </mo>
4424
      <pat:variable name="a"/>
4425
    </munder>
4426
  </pat:mml>
4427
</pat:template>
4428
4429
<pat:template>
4430
  <pat:tex op="\iint"/>
4431
  <pat:mml op="&#x222C;">
4432
    <mo> &#x222C; </mo>
4433
  </pat:mml>
4434
</pat:template>
4435
4436
4437
<pat:template>
4438
  <pat:tex op="\iiint" params="\limits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4439
  <pat:mml op="&#x222D;">
4440
    <munderover>
4441
      <mo> &#x222D; </mo>
4442
      <pat:variable name="a"/>
4443
      <pat:variable name="b"/>
4444
    </munderover>
4445
  </pat:mml>
4446
</pat:template>
4447
4448
<pat:template>
4449
  <pat:tex op="\iiint" params="\limits_\patVAR!{a}"/>
4450
  <pat:mml op="&#x222D;">
4451
    <munder>
4452
      <mo> &#x222D; </mo>
4453
      <pat:variable name="a"/>
4454
    </munder>
4455
  </pat:mml>
4456
</pat:template>
4457
4458
<pat:template>
4459
  <pat:tex op="\iiint" params="\nolimits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4460
  <pat:mml op="&#x222D;">
4461
    <msubsup>
4462
      <mo> &#x222D; </mo>
4463
      <pat:variable name="a"/>
4464
      <pat:variable name="b"/>
4465
    </msubsup>
4466
  </pat:mml>
4467
</pat:template>
4468
4469
<pat:template>
4470
  <pat:tex op="\iiint" params="\nolimits_\patVAR!{a}"/>
4471
  <pat:mml op="&#x222D;">
4472
    <msub>
4473
      <mo> &#x222D; </mo>
4474
      <pat:variable name="a"/>
4475
    </msub>
4476
  </pat:mml>
4477
</pat:template>
4478
4479
<pat:template>
4480
  <pat:tex op="\iiint" params="_\patVAR!{a}&#x005E;\patVAR!{b}" prec="350"/>
4481
  <pat:mml op="&#x222D;">
4482
    <munderover>
4483
      <mo> &#x222D; </mo>
4484
      <pat:variable name="a"/>
4485
      <pat:variable name="b"/>
4486
    </munderover>
4487
  </pat:mml>
4488
</pat:template>
4489
4490
<pat:template>
4491
  <pat:tex op="\iiint" params="_\patVAR!{a}" prec="350"/>
4492
  <pat:mml op="&#x222D;">
4493
    <munder>
4494
      <mo> &#x222D; </mo>
4495
      <pat:variable name="a"/>
4496
    </munder>
4497
  </pat:mml>
4498
</pat:template>
4499
4500
<pat:template>
4501
  <pat:tex op="\iiint"/>
4502
  <pat:mml op="&#x222D;">
4503
    <mo> &#x222D; </mo>
4504
  </pat:mml>
4505
</pat:template>
4506
4507
4508
<pat:template>
4509
  <pat:tex op="\iiiint" params="\limits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4510
  <pat:mml op="&#x2A0C;">
4511
    <munderover>
4512
      <mo> &#x2A0C; </mo>
4513
      <pat:variable name="a"/>
4514
      <pat:variable name="b"/>
4515
    </munderover>
4516
  </pat:mml>
4517
</pat:template>
4518
4519
<pat:template>
4520
  <pat:tex op="\iiiint" params="\limits_\patVAR!{a}"/>
4521
  <pat:mml op="&#x2A0C;">
4522
    <munder>
4523
      <mo> &#x2A0C; </mo>
4524
      <pat:variable name="a"/>
4525
    </munder>
4526
  </pat:mml>
4527
</pat:template>
4528
4529
<pat:template>
4530
  <pat:tex op="\iiiint" params="\nolimits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4531
  <pat:mml op="&#x2A0C;">
4532
    <msubsup>
4533
      <mo> &#x2A0C; </mo>
4534
      <pat:variable name="a"/>
4535
      <pat:variable name="b"/>
4536
    </msubsup>
4537
  </pat:mml>
4538
</pat:template>
4539
4540
<pat:template>
4541
  <pat:tex op="\iiiint" params="\nolimits_\patVAR!{a}"/>
4542
  <pat:mml op="&#x2A0C;">
4543
    <msub>
4544
      <mo> &#x2A0C; </mo>
4545
      <pat:variable name="a"/>
4546
    </msub>
4547
  </pat:mml>
4548
</pat:template>
4549
4550
<pat:template>
4551
  <pat:tex op="\iiiint" params="_\patVAR!{a}&#x005E;\patVAR!{b}" prec="350"/>
4552
  <pat:mml op="&#x2A0C;">
4553
    <munderover>
4554
      <mo> &#x2A0C; </mo>
4555
      <pat:variable name="a"/>
4556
      <pat:variable name="b"/>
4557
    </munderover>
4558
  </pat:mml>
4559
</pat:template>
4560
4561
<pat:template>
4562
  <pat:tex op="\iiiint" params="_\patVAR!{a}" prec="350"/>
4563
  <pat:mml op="&#x2A0C;">
4564
    <munder>
4565
      <mo> &#x2A0C; </mo>
4566
      <pat:variable name="a"/>
4567
    </munder>
4568
  </pat:mml>
4569
</pat:template>
4570
4571
<pat:template>
4572
  <pat:tex op="\iiiint"/>
4573
  <pat:mml op="&#x2A0C;">
4574
    <mo> &#x2A0C; </mo>
4575
  </pat:mml>
4576
</pat:template>
4577
4578
4579
<pat:template>
4580
  <pat:tex op="\idotsint" params="\limits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4581
  <pat:mml op="&#x222B;">
4582
    <munderover>
4583
      <mrow>
4584
        <mo> &#x222B; </mo>
4585
        <mo> &#x22EF; </mo>
4586
        <mo> &#x222B; </mo>
4587
      </mrow>
4588
      <pat:variable name="a"/>
4589
      <pat:variable name="b"/>
4590
    </munderover>
4591
  </pat:mml>
4592
</pat:template>
4593
4594
<pat:template>
4595
  <pat:tex op="\idotsint" params="\limits_\patVAR!{a}"/>
4596
  <pat:mml op="&#x222B;">
4597
    <munder>
4598
      <mrow>
4599
        <mo> &#x222B; </mo>
4600
        <mo> &#x22EF; </mo>
4601
        <mo> &#x222B; </mo>
4602
      </mrow>
4603
      <pat:variable name="a"/>
4604
    </munder>
4605
  </pat:mml>
4606
</pat:template>
4607
4608
<pat:template>
4609
  <pat:tex op="\idotsint" params="\nolimits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4610
  <pat:mml op="&#x222B;">
4611
    <msubsup>
4612
      <mrow>
4613
        <mo> &#x222B; </mo>
4614
        <mo> &#x22EF; </mo>
4615
        <mo> &#x222B; </mo>
4616
      </mrow>
4617
      <pat:variable name="a"/>
4618
      <pat:variable name="b"/>
4619
    </msubsup>
4620
  </pat:mml>
4621
</pat:template>
4622
4623
<pat:template>
4624
  <pat:tex op="\idotsint" params="\nolimits_\patVAR!{a}"/>
4625
  <pat:mml op="&#x222B;">
4626
    <msub>
4627
      <mrow>
4628
        <mo> &#x222B; </mo>
4629
        <mo> &#x22EF; </mo>
4630
        <mo> &#x222B; </mo>
4631
      </mrow>
4632
      <pat:variable name="a"/>
4633
    </msub>
4634
  </pat:mml>
4635
</pat:template>
4636
4637
<pat:template>
4638
  <pat:tex op="\idotsint" params="_\patVAR!{a}&#x005E;\patVAR!{b}" prec="350"/>
4639
  <pat:mml op="&#x222B;">
4640
    <munderover>
4641
      <mrow>
4642
        <mo> &#x222B; </mo>
4643
        <mo> &#x22EF; </mo>
4644
        <mo> &#x222B; </mo>
4645
      </mrow>
4646
      <pat:variable name="a"/>
4647
      <pat:variable name="b"/>
4648
    </munderover>
4649
  </pat:mml>
4650
</pat:template>
4651
4652
<pat:template>
4653
  <pat:tex op="\idotsint" params="_\patVAR!{a}" prec="350"/>
4654
  <pat:mml op="&#x222B;">
4655
    <munder>
4656
      <mrow>
4657
        <mo> &#x222B; </mo>
4658
        <mo> &#x22EF; </mo>
4659
        <mo> &#x222B; </mo>
4660
      </mrow>
4661
      <pat:variable name="a"/>
4662
    </munder>
4663
  </pat:mml>
4664
</pat:template>
4665
4666
<pat:template>
4667
  <pat:tex op="\idotsint"/>
4668
  <pat:mml op="&#x222B;">
4669
    <mo> &#x222B; </mo>
4670
    <mo> &#x22EF; </mo>
4671
    <mo> &#x222B; </mo>
4672
  </pat:mml>
4673
</pat:template>
4674
4675
4676
<pat:template>
4677
  <pat:tex op="\oint" params="\limits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4678
  <pat:mml op="&#x222E;">
4679
    <munderover>
4680
      <mo> &#x222E; </mo>
4681
      <pat:variable name="a"/>
4682
      <pat:variable name="b"/>
4683
    </munderover>
4684
  </pat:mml>
4685
</pat:template>
4686
4687
<pat:template>
4688
  <pat:tex op="\oint" params="\limits_\patVAR!{a}"/>
4689
  <pat:mml op="&#x222E;">
4690
    <munder>
4691
      <mo> &#x222E; </mo>
4692
      <pat:variable name="a"/>
4693
    </munder>
4694
  </pat:mml>
4695
</pat:template>
4696
4697
<pat:template>
4698
  <pat:tex op="\oint" params="\nolimits_\patVAR!{a}&#x005E;\patVAR!{b}"/>
4699
  <pat:mml op="&#x222E;">
4700
    <msubsup>
4701
      <mo> &#x222E; </mo>
4702
      <pat:variable name="a"/>
4703
      <pat:variable name="b"/>
4704
    </msubsup>
4705
  </pat:mml>
4706
</pat:template>
4707
4708
<pat:template>
4709
  <pat:tex op="\oint" params="\nolimits_\patVAR!{a}"/>
4710
  <pat:mml op="&#x222E;">
4711
    <msub>
4712
      <mo> &#x222E; </mo>
4713
      <pat:variable name="a"/>
4714
    </msub>
4715
  </pat:mml>
4716
</pat:template>
4717
4718
<pat:template>
4719
  <pat:tex op="\oint" params="_\patVAR!{a}&#x005E;\patVAR!{b}" prec="350"/>
4720
  <pat:mml op="&#x222E;">
4721
    <munderover>
4722
      <mo> &#x222E; </mo>
4723
      <pat:variable name="a"/>
4724
      <pat:variable name="b"/>
4725
    </munderover>
4726
  </pat:mml>
4727
</pat:template>
4728
4729
<pat:template>
4730
  <pat:tex op="\oint" params="_\patVAR!{a}" prec="350"/>
4731
  <pat:mml op="&#x222E;">
4732
    <munder>
4733
      <mo> &#x222E; </mo>
4734
      <pat:variable name="a"/>
4735
    </munder>
4736
  </pat:mml>
4737
</pat:template>
4738
4739
<pat:template>
4740
  <pat:tex op="\oint"/>
4741
  <pat:mml op="&#x222E;">
4742
    <mo> &#x222E; </mo>
4743
  </pat:mml>
4744
</pat:template>
4745
4746
4747
4748
<pat:template>
4749
  <pat:tex op="\bigcap"/>
4750
  <pat:mml op="&#x2229;">
4751
    <mo> &#x2229; </mo>
4752
  </pat:mml>
4753
</pat:template>
4754
4755
<pat:template>
4756
  <pat:tex op="\bigcup"/>
4757
  <pat:mml op="&#x222A;">
4758
    <mo> &#x222A; </mo>
4759
  </pat:mml>
4760
</pat:template>
4761
4762
<pat:template>
4763
  <pat:tex op="\bigsqcup"/>
4764
  <pat:mml op="&#x2294;">
4765
    <mo> &#x2294; </mo>
4766
  </pat:mml>
4767
</pat:template>
4768
4769
<pat:template>
4770
  <pat:tex op="\bigvee"/>
4771
  <pat:mml op="&#x22C1;">
4772
    <mo> &#x22C1; </mo>
4773
  </pat:mml>
4774
</pat:template>
4775
4776
<pat:template>
4777
  <pat:tex op="\bigwedge"/>
4778
  <pat:mml op="&#x22C0;">
4779
    <mo> &#x22C0; </mo>
4780
  </pat:mml>
4781
</pat:template>
4782
4783
<pat:template>
4784
  <pat:tex op="\bigodot"/>
4785
  <pat:mml op="&#x2299;">
4786
    <mo> &#x2299; </mo>
4787
  </pat:mml>
4788
</pat:template>
4789
4790
<pat:template>
4791
  <pat:tex op="\bigotimes"/>
4792
  <pat:mml op="&#x2297;">
4793
    <mo> &#x2297; </mo>
4794
  </pat:mml>
4795
</pat:template>
4796
4797
<pat:template>
4798
  <pat:tex op="\bigoplus"/>
4799
  <pat:mml op="&#x2295;">
4800
    <mo> &#x2295; </mo>
4801
  </pat:mml>
4802
</pat:template>
4803
4804
<pat:template>
4805
  <pat:tex op="\biguplus"/>
4806
  <pat:mml op="&#x228E;">
4807
    <mo> &#x228E; </mo>
4808
  </pat:mml>
4809
</pat:template>
4810
4811
4812
4813
<!-- Special letters from other languages -->
4814
4815
<pat:template>
4816
  <pat:tex op="\oe"/>
4817
  <pat:mml op="&#x0153;">
4818
    <mo> &#x0153; </mo>
4819
  </pat:mml>
4820
</pat:template>
4821
4822
<pat:template>
4823
  <pat:tex op="\OE"/>
4824
  <pat:mml op="&#x0152;">
4825
    <mo> &#x0152; </mo>
4826
  </pat:mml>
4827
</pat:template>
4828
4829
<pat:template>
4830
  <pat:tex op="\ae"/>
4831
  <pat:mml op="&#x00E6;">
4832
    <mo> &#x00E6; </mo>
4833
  </pat:mml>
4834
</pat:template>
4835
4836
<pat:template>
4837
  <pat:tex op="\AE"/>
4838
  <pat:mml op="&#x00C6;">
4839
    <mo> &#x00C6; </mo>
4840
  </pat:mml>
4841
</pat:template>
4842
4843
<pat:template>
4844
  <pat:tex op="\aa"/>
4845
  <pat:mml op="&#x00E5;">
4846
    <mo> &#x00E5; </mo>
4847
  </pat:mml>
4848
</pat:template>
4849
4850
<pat:template>
4851
  <pat:tex op="\AA"/>
4852
  <pat:mml op="&#x00C5;">
4853
    <mo> &#x00C5; </mo>
4854
  </pat:mml>
4855
</pat:template>
4856
4857
<pat:template>
4858
  <pat:tex op="\o"/>
4859
  <pat:mml op="&#x00F8;">
4860
    <mo> &#x00F8; </mo>
4861
  </pat:mml>
4862
</pat:template>
4863
4864
<pat:template>
4865
  <pat:tex op="\O"/>
4866
  <pat:mml op="&#x00D8;">
4867
    <mo> &#x00D8; </mo>
4868
  </pat:mml>
4869
</pat:template>
4870
4871
<pat:template>
4872
  <pat:tex op="\l"/>
4873
  <pat:mml op="&#x0142;">
4874
    <mo> &#x0142; </mo>
4875
  </pat:mml>
4876
</pat:template>
4877
4878
<pat:template>
4879
  <pat:tex op="\L"/>
4880
  <pat:mml op="&#x0141;">
4881
    <mo> &#x0141; </mo>
4882
  </pat:mml>
4883
</pat:template>
4884
4885
<pat:template>
4886
  <pat:tex op="\SS"/>
4887
  <pat:mml op="&#x00DF;">
4888
    <mo> &#x00DF; </mo>
4889
  </pat:mml>
4890
</pat:template>
4891
4892
<pat:template>
4893
  <pat:tex op="!" params="`"/>
4894
  <pat:mml op="&#x00A1;">
4895
    <mo> &#x00A1; </mo>
4896
  </pat:mml>
4897
</pat:template>
4898
4899
<pat:template>
4900
  <pat:tex op="?" params="`"/>
4901
  <pat:mml op="&#x00BF;">
4902
    <mo> &#x00BF; </mo>
4903
  </pat:mml>
4904
</pat:template>
4905
4906
4907
4908
<!-- Special symbols -->
4909
4910
<pat:template>
4911
  <pat:tex op="\S"/>
4912
  <pat:mml op="&#x00A7;">
4913
    <mo> &#x00A7; </mo>
4914
  </pat:mml>
4915
</pat:template>
4916
4917
<pat:template>
4918
  <pat:tex op="\copyright"/>
4919
  <pat:mml op="&#x00A9;">
4920
    <mo> &#x00A9; </mo>
4921
  </pat:mml>
4922
</pat:template>
4923
4924
<pat:template>
4925
  <pat:tex op="\P"/>
4926
  <pat:mml op="&#x00B6;">
4927
    <mo> &#x00B6; </mo>
4928
  </pat:mml>
4929
</pat:template>
4930
4931
<pat:template>
4932
  <pat:tex op="\pounds"/>
4933
  <pat:mml op="&#x00A3;">
4934
    <mo> &#x00A3; </mo>
4935
  </pat:mml>
4936
</pat:template>
4937
4938
4939
4940
<!-- Binary operation symbols -->
4941
4942
<pat:template>
4943
  <pat:tex op="+"/>
4944
  <pat:mml op="+">
4945
    <mo> + </mo>
4946
  </pat:mml>
4947
</pat:template>
4948
4949
<pat:template>
4950
  <pat:tex op="-"/>
4951
  <pat:mml op="-">
4952
    <mo> - </mo>
4953
  </pat:mml>
4954
</pat:template>
4955
4956
<pat:template>
4957
  <pat:tex op="\pm"/>
4958
  <pat:mml op="&#x00B1;">
4959
    <mo> &#x00B1; </mo>
4960
  </pat:mml>
4961
</pat:template>
4962
4963
<pat:template>
4964
  <pat:tex op="\mp"/>
4965
  <pat:mml op="&#x2213;">
4966
    <mo> &#x00B1; </mo>
4967
  </pat:mml>
4968
</pat:template>
4969
4970
<pat:template>
4971
  <pat:tex op="\times"/>
4972
  <pat:mml op="&#x00D7;">
4973
    <mo> &#x00D7; </mo>
4974
  </pat:mml>
4975
</pat:template>
4976
4977
<pat:template>
4978
  <pat:tex op="\div"/>
4979
  <pat:mml op="&#x00F7;">
4980
    <mo> &#x00F7; </mo>
4981
  </pat:mml>
4982
</pat:template>
4983
4984
<pat:template>
4985
  <pat:tex op="\cdot"/>
4986
  <pat:mml op="&#x22C5;">
4987
    <mo> &#x22C5; </mo>
4988
  </pat:mml>
4989
</pat:template>
4990
4991
<pat:template>
4992
  <pat:tex op="\ast"/>
4993
  <pat:mml op="*">
4994
    <mo> * </mo>
4995
  </pat:mml>
4996
</pat:template>
4997
4998
<pat:template>
4999
  <pat:tex op="\star"/>
5000
  <pat:mml op="&#x22C6;">
5001
    <mo> &#x22C6; </mo>
5002
  </pat:mml>
5003
</pat:template>
5004
5005
<pat:template>
5006
  <pat:tex op="\dagger"/>
5007
  <pat:mml op="&#x2020;">
5008
    <mo> &#x2020; </mo>
5009
  </pat:mml>
5010
</pat:template>
5011
5012
<pat:template>
5013
  <pat:tex op="\dag"/>
5014
  <pat:mml op="">
5015
    <mo> &#x2020; </mo>
5016
  </pat:mml>
5017
</pat:template>
5018
5019
<pat:template>
5020
  <pat:tex op="\ddagger"/>
5021
  <pat:mml op="&#x2021;">
5022
    <mo> &#x2021; </mo>
5023
  </pat:mml>
5024
</pat:template>
5025
5026
<pat:template>
5027
  <pat:tex op="\ddag"/>
5028
  <pat:mml op="">
5029
    <mo> &#x2021; </mo>
5030
  </pat:mml>
5031
</pat:template>
5032
5033
<pat:template>
5034
  <pat:tex op="\amalg"/>
5035
  <pat:mml op="">
5036
    <mo> &#x2210; </mo>
5037
  </pat:mml>
5038
</pat:template>
5039
5040
<pat:template>
5041
  <pat:tex op="\cap"/>
5042
  <pat:mml op="&#x2229;">
5043
    <mo> &#x2229; </mo>
5044
  </pat:mml>
5045
</pat:template>
5046
5047
<pat:template>
5048
  <pat:tex op="\cup"/>
5049
  <pat:mml op="&#x222A;">
5050
    <mo> &#x222A; </mo>
5051
  </pat:mml>
5052
</pat:template>
5053
5054
<pat:template>
5055
  <pat:tex op="\uplus"/>
5056
  <pat:mml op="&#x228E;">
5057
    <mo> &#x228E; </mo>
5058
  </pat:mml>
5059
</pat:template>
5060
5061
<pat:template>
5062
  <pat:tex op="\sqcap"/>
5063
  <pat:mml op="&#x2293;">
5064
    <mo> &#x2293; </mo>
5065
  </pat:mml>
5066
</pat:template>
5067
5068
<pat:template>
5069
  <pat:tex op="\sqcup"/>
5070
  <pat:mml op="&#x2294;">
5071
    <mo> &#x2294; </mo>
5072
  </pat:mml>
5073
</pat:template>
5074
5075
<pat:template>
5076
  <pat:tex op="\vee"/>
5077
  <pat:mml op="&#x2228;">
5078
    <mo> &#x2228; </mo>
5079
  </pat:mml>
5080
</pat:template>
5081
5082
<pat:template>
5083
  <pat:tex op="\wedge"/>
5084
  <pat:mml op="&#x2227;">
5085
    <mo> &#x2227; </mo>
5086
  </pat:mml>
5087
</pat:template>
5088
5089
<pat:template>
5090
  <pat:tex op="\oplus"/>
5091
  <pat:mml op="&#x2295;">
5092
    <mo> &#x2295; </mo>
5093
  </pat:mml>
5094
</pat:template>
5095
5096
<pat:template>
5097
  <pat:tex op="\ominus"/>
5098
  <pat:mml op="&#x2296;">
5099
    <mo> &#x2296; </mo>
5100
  </pat:mml>
5101
</pat:template>
5102
5103
<pat:template>
5104
  <pat:tex op="\otimes"/>
5105
  <pat:mml op="&#x2297;">
5106
    <mo> &#x2297; </mo>
5107
  </pat:mml>
5108
</pat:template>
5109
5110
<pat:template>
5111
  <pat:tex op="\oslash"/>
5112
  <pat:mml op="&#x2298;">
5113
    <mo> &#x2298; </mo>
5114
  </pat:mml>
5115
</pat:template>
5116
5117
<pat:template>
5118
  <pat:tex op="\odot"/>
5119
  <pat:mml op="&#x2299;">
5120
    <mo> &#x2299; </mo>
5121
  </pat:mml>
5122
</pat:template>
5123
5124
<pat:template>
5125
  <pat:tex op="\circ"/>
5126
  <pat:mml op="&#x2218;">
5127
    <mo> &#x2218; </mo>
5128
  </pat:mml>
5129
</pat:template>
5130
5131
<pat:template>
5132
  <pat:tex op="\bullet"/>
5133
  <pat:mml op="&#x2219;">
5134
    <mo> &#x2219; </mo>
5135
  </pat:mml>
5136
</pat:template>
5137
5138
<pat:template>
5139
  <pat:tex op="\diamond"/>
5140
  <pat:mml op="&#x22C4;">
5141
    <mo> &#x22C4; </mo>
5142
  </pat:mml>
5143
</pat:template>
5144
5145
<pat:template>
5146
  <pat:tex op="\lhd"/>
5147
  <pat:mml op="&#x22B2;">
5148
    <mo> &#x22B2; </mo>
5149
  </pat:mml>
5150
</pat:template>
5151
5152
<pat:template>
5153
  <pat:tex op="\rhd"/>
5154
  <pat:mml op="&#x22B3;">
5155
    <mo> &#x22B3; </mo>
5156
  </pat:mml>
5157
</pat:template>
5158
5159
<pat:template>
5160
  <pat:tex op="\unlhd"/>
5161
  <pat:mml op="&#x22B4;">
5162
    <mo> &#x22B4; </mo>
5163
  </pat:mml>
5164
</pat:template>
5165
5166
<pat:template>
5167
  <pat:tex op="\unrhd"/>
5168
  <pat:mml op="&#x22B5;">
5169
    <mo> &#x22B5; </mo>
5170
  </pat:mml>
5171
</pat:template>
5172
5173
<pat:template>
5174
  <pat:tex op="\bigcirc"/>
5175
  <pat:mml op="&#x25EF;">
5176
    <mo> &#x25EF; </mo>
5177
  </pat:mml>
5178
</pat:template>
5179
5180
<pat:template>
5181
  <pat:tex op="\bigtriangleup"/>
5182
  <pat:mml op="&#x25B3;">
5183
    <mo> &#x25B3; </mo>
5184
  </pat:mml>
5185
</pat:template>
5186
5187
<pat:template>
5188
  <pat:tex op="\bigtriangledown"/>
5189
  <pat:mml op="&#x25BD;">
5190
    <mo> &#x25BD; </mo>
5191
  </pat:mml>
5192
</pat:template>
5193
5194
<pat:template>
5195
  <pat:tex op="\triangleleft"/>
5196
  <pat:mml op="&#x25C5;">
5197
    <mo> &#x25C5; </mo>
5198
  </pat:mml>
5199
</pat:template>
5200
5201
<pat:template>
5202
  <pat:tex op="\triangleright"/>
5203
  <pat:mml op="&#x25BB;">
5204
    <mo> &#x25BB; </mo>
5205
  </pat:mml>
5206
</pat:template>
5207
5208
<pat:template>
5209
  <pat:tex op="\setminus"/>
5210
  <pat:mml op="&#x2216;">
5211
    <mo> &#x2216; </mo>
5212
  </pat:mml>
5213
</pat:template>
5214
5215
<pat:template>
5216
  <pat:tex op="\wr"/>
5217
  <pat:mml op="&#x2240;">
5218
    <mo> &#x2240; </mo>
5219
  </pat:mml>
5220
</pat:template>
5221
5222
5223
<pat:template>
5224
  <pat:tex op="\lor"/>
5225
  <pat:mml op="&#x2228;">
5226
    <mo> &#x2228; </mo>
5227
  </pat:mml>
5228
</pat:template>
5229
5230
<pat:template>
5231
  <pat:tex op="\land"/>
5232
  <pat:mml op="&#x2227;">
5233
    <mo> &#x2227; </mo>
5234
  </pat:mml>
5235
</pat:template>
5236
5237
5238
5239
<!-- Dots -->
5240
5241
<pat:template>
5242
  <pat:tex op="\cdots"/>
5243
  <pat:mml op="&#x22EF;">
5244
    <mo> &#x22EF; </mo>
5245
  </pat:mml>
5246
</pat:template>
5247
5248
<pat:template>
5249
  <pat:tex op="\ddots"/>
5250
  <pat:mml op="&#x22F1;">
5251
    <mo> &#x22F1; </mo>
5252
  </pat:mml>
5253
</pat:template>
5254
5255
<pat:template>
5256
  <pat:tex op="\vdots"/>
5257
  <pat:mml op="&#x22EE;">
5258
    <mo> &#x22EE; </mo>
5259
  </pat:mml>
5260
</pat:template>
5261
5262
<pat:template>
5263
  <pat:tex op="\ldots"/>
5264
  <pat:mml op="&#x2026;">
5265
    <mo> &#x2026; </mo>
5266
  </pat:mml>
5267
</pat:template>
5268
5269
<pat:template>
5270
  <pat:tex op="\dots"/>
5271
  <pat:mml op="&#x22EF;">
5272
    <mo> &#x22EF; </mo>
5273
  </pat:mml>
5274
</pat:template>
5275
5276
<pat:template>
5277
  <pat:tex op="\dots" params=","/>
5278
  <pat:mml op="&#x2026;">
5279
    <mo> &#x2026; </mo>
5280
  </pat:mml>
5281
</pat:template>
5282
5283
<pat:template>
5284
  <pat:tex op="\dotsb"/>
5285
  <pat:mml op="&#x22EF;">
5286
    <mo> &#x22EF; </mo>
5287
  </pat:mml>
5288
</pat:template>
5289
5290
<pat:template>
5291
  <pat:tex op="\dotsc"/>
5292
  <pat:mml op="&#x2026;">
5293
    <mo> &#x2026; </mo>
5294
  </pat:mml>
5295
</pat:template>
5296
5297
<pat:template>
5298
  <pat:tex op="\dotsi"/>
5299
  <pat:mml op="&#x22EF;">
5300
    <mo> &#x22EF; </mo>
5301
  </pat:mml>
5302
</pat:template>
5303
5304
<pat:template>
5305
  <pat:tex op="\dotsm"/>
5306
  <pat:mml op="&#x22EF;">
5307
    <mo> &#x22EF; </mo>
5308
  </pat:mml>
5309
</pat:template>
5310
5311
<pat:template>
5312
  <pat:tex op="\dotso"/>
5313
  <pat:mml op="&#x22EF;">
5314
    <mo> &#x22EF; </mo>
5315
  </pat:mml>
5316
</pat:template>
5317
5318
5319
5320
<!-- Relational symbols -->
5321
5322
<pat:template>
5323
  <pat:tex op="="/>
5324
  <pat:mml op="=">
5325
    <mo> = </mo>
5326
  </pat:mml>
5327
</pat:template>
5328
5329
<pat:template>
5330
  <pat:tex op="\leq"/>
5331
  <pat:mml op="&#x2264;">
5332
    <mo> &#x2264; </mo>
5333
  </pat:mml>
5334
</pat:template>
5335
5336
<pat:template>
5337
  <pat:tex op="\le"/>
5338
  <pat:mml op="&#x2264;">
5339
    <mo> &#x2264; </mo>
5340
  </pat:mml>
5341
</pat:template>
5342
5343
<pat:template>
5344
  <pat:tex op="\ll"/>
5345
  <pat:mml op="&#x226A;">
5346
    <mo> &#x226A; </mo>
5347
  </pat:mml>
5348
</pat:template>
5349
5350
<pat:template>
5351
  <pat:tex op="\geq"/>
5352
  <pat:mml op="&#x2265;">
5353
    <mo> &#x2265; </mo>
5354
  </pat:mml>
5355
</pat:template>
5356
5357
<pat:template>
5358
  <pat:tex op="\ge"/>
5359
  <pat:mml op="&#x2265;">
5360
    <mo> &#x2265; </mo>
5361
  </pat:mml>
5362
</pat:template>
5363
5364
<pat:template>
5365
  <pat:tex op="\gg"/>
5366
  <pat:mml op="&#x226B;">
5367
    <mo> &#x226B; </mo>
5368
  </pat:mml>
5369
</pat:template>
5370
5371
<pat:template>
5372
  <pat:tex op="\ne"/>
5373
  <pat:mml op="&#x2260;">
5374
    <mo> &#x2260; </mo>
5375
  </pat:mml>
5376
</pat:template>
5377
5378
<pat:template>
5379
  <pat:tex op="\neq"/>
5380
  <pat:mml op="&#x2260;">
5381
    <mo> &#x2260; </mo>
5382
  </pat:mml>
5383
</pat:template>
5384
5385
<pat:template>
5386
  <pat:tex op="\doteq"/>
5387
  <pat:mml op="&#x2250;">
5388
    <mo> &#x2250; </mo>
5389
  </pat:mml>
5390
</pat:template>
5391
5392
<pat:template>
5393
  <pat:tex op="\subset"/>
5394
  <pat:mml op="&#x2282;">
5395
    <mo> &#x2282; </mo>
5396
  </pat:mml>
5397
</pat:template>
5398
5399
<pat:template>
5400
  <pat:tex op="\subseteq"/>
5401
  <pat:mml op="&#x2286;">
5402
    <mo> &#x2286; </mo>
5403
  </pat:mml>
5404
</pat:template>
5405
5406
<pat:template>
5407
  <pat:tex op="\sqsubseteq"/>
5408
  <pat:mml op="&#x2291;">
5409
    <mo> &#x2291; </mo>
5410
  </pat:mml>
5411
</pat:template>
5412
5413
<pat:template>
5414
  <pat:tex op="\supset"/>
5415
  <pat:mml op="&#x2283;">
5416
    <mo> &#x2283; </mo>
5417
  </pat:mml>
5418
</pat:template>
5419
5420
<pat:template>
5421
  <pat:tex op="\supseteq"/>
5422
  <pat:mml op="&#x2287;">
5423
    <mo> &#x2287; </mo>
5424
  </pat:mml>
5425
</pat:template>
5426
5427
<pat:template>
5428
  <pat:tex op="\sqsupseteq"/>
5429
  <pat:mml op="&#x2292;">
5430
    <mo> &#x2292; </mo>
5431
  </pat:mml>
5432
</pat:template>
5433
5434
<pat:template>
5435
  <pat:tex op="\in"/>
5436
  <pat:mml op="&#x2208;">
5437
    <mo> &#x2208; </mo>
5438
  </pat:mml>
5439
</pat:template>
5440
5441
<pat:template>
5442
  <pat:tex op="\ni"/>
5443
  <pat:mml op="&#x220B;">
5444
    <mo> &#x220B; </mo>
5445
  </pat:mml>
5446
</pat:template>
5447
5448
<pat:template>
5449
  <pat:tex op="\models"/>
5450
  <pat:mml op="&#x22A7;">
5451
    <mo> &#x22A7; </mo>
5452
  </pat:mml>
5453
</pat:template>
5454
5455
<pat:template>
5456
  <pat:tex op="\perp"/>
5457
  <pat:mml op="&#x22A5;">
5458
    <mo> &#x22A5; </mo>
5459
  </pat:mml>
5460
</pat:template>
5461
5462
<pat:template>
5463
  <pat:tex op="\approx"/>
5464
  <pat:mml op="&#x2248;">
5465
    <mo> &#x2248; </mo>
5466
  </pat:mml>
5467
</pat:template>
5468
5469
<pat:template>
5470
  <pat:tex op="\cong"/>
5471
  <pat:mml op="&#x2245;">
5472
    <mo> &#x2245; </mo>
5473
  </pat:mml>
5474
</pat:template>
5475
5476
<pat:template>
5477
  <pat:tex op="\equiv"/>
5478
  <pat:mml op="&#x2261;">
5479
    <mo> &#x224D; </mo>
5480
  </pat:mml>
5481
</pat:template>
5482
5483
<pat:template>
5484
  <pat:tex op="\propto"/>
5485
  <pat:mml op="&#x221D;">
5486
    <mo> &#x221D; </mo>
5487
  </pat:mml>
5488
</pat:template>
5489
5490
<pat:template>
5491
  <pat:tex op="\prec"/>
5492
  <pat:mml op="&#x227A;">
5493
    <mo> &#x227A; </mo>
5494
  </pat:mml>
5495
</pat:template>
5496
5497
<pat:template>
5498
  <pat:tex op="\preceq"/>
5499
  <pat:mml op="&#x227C;">
5500
    <mo> &#x227C; </mo>
5501
  </pat:mml>
5502
</pat:template>
5503
5504
<pat:template>
5505
  <pat:tex op="\parallel"/>
5506
  <pat:mml op="&#x2225;">
5507
    <mo> &#x2225; </mo>
5508
  </pat:mml>
5509
</pat:template>
5510
5511
<pat:template>
5512
  <pat:tex op="\sim"/>
5513
  <pat:mml op="&#x223C;">
5514
    <mo> &#x223C; </mo>
5515
  </pat:mml>
5516
</pat:template>
5517
5518
<pat:template>
5519
  <pat:tex op="\simeq"/>
5520
  <pat:mml op="&#x2243;">
5521
    <mo> &#x2243; </mo>
5522
  </pat:mml>
5523
</pat:template>
5524
5525
<pat:template>
5526
  <pat:tex op="\asymp"/>
5527
  <pat:mml op="&#x224D;">
5528
    <mo> &#x224D; </mo>
5529
  </pat:mml>
5530
</pat:template>
5531
5532
<pat:template>
5533
  <pat:tex op="\smile"/>
5534
  <pat:mml op="&#x2323;">
5535
    <mo> &#x2323; </mo>
5536
  </pat:mml>
5537
</pat:template>
5538
5539
<pat:template>
5540
  <pat:tex op="\frown"/>
5541
  <pat:mml op="&#x2322;">
5542
    <mo> &#x2322; </mo>
5543
  </pat:mml>
5544
</pat:template>
5545
5546
<pat:template>
5547
  <pat:tex op="\bowtie"/>
5548
  <pat:mml op="&#x22C8;">
5549
    <mo> &#x22C8; </mo>
5550
  </pat:mml>
5551
</pat:template>
5552
5553
<pat:template>
5554
  <pat:tex op="\succ"/>
5555
  <pat:mml op="&#x227B;">
5556
    <mo> &#x227B; </mo>
5557
  </pat:mml>
5558
</pat:template>
5559
5560
<pat:template>
5561
  <pat:tex op="\succeq"/>
5562
  <pat:mml op="&#x227D;">
5563
    <mo> &#x227D; </mo>
5564
  </pat:mml>
5565
</pat:template>
5566
5567
<pat:template>
5568
  <pat:tex op="\mid"/>
5569
  <pat:mml op="&#x2223;">
5570
    <mo> &#x2223; </mo>
5571
  </pat:mml>
5572
</pat:template>
5573
5574
<pat:template>
5575
  <pat:tex op="\owns"/>
5576
  <pat:mml op="&#x220B;">
5577
    <mo> &#x220B; </mo>
5578
  </pat:mml>
5579
</pat:template>
5580
5581
5582
5583
<!-- NOTs -->
5584
5585
<pat:template>
5586
  <pat:tex op="\not" params="&lt;"/>
5587
  <pat:mml op="&#x226E;">
5588
    <mo> &#x226E; </mo>
5589
  </pat:mml>
5590
</pat:template>
5591
5592
<pat:template>
5593
  <pat:tex op="\not" params="&gt;"/>
5594
  <pat:mml op="&#x226F;">
5595
    <mo> &#x226F; </mo>
5596
  </pat:mml>
5597
</pat:template>
5598
5599
<pat:template>
5600
  <pat:tex op="\not" params="="/>
5601
  <pat:mml op="&#x2260;">
5602
    <mo> &#x2260; </mo>
5603
  </pat:mml>
5604
</pat:template>
5605
5606
<pat:template>
5607
  <pat:tex op="\not" params="\equiv"/>
5608
  <pat:mml op="&#x2262;">
5609
    <mo> &#x2262; </mo>
5610
  </pat:mml>
5611
</pat:template>
5612
5613
<pat:template>
5614
  <pat:tex op="\not" params="\le"/>
5615
  <pat:mml op="&#x2270;">
5616
    <mo> &#x2270; </mo>
5617
  </pat:mml>
5618
</pat:template>
5619
5620
<pat:template>
5621
  <pat:tex op="\not" params="\leq"/>
5622
  <pat:mml op="&#x2270;">
5623
    <mo> &#x2270; </mo>
5624
  </pat:mml>
5625
</pat:template>
5626
5627
<pat:template>
5628
  <pat:tex op="\not" params="\ge"/>
5629
  <pat:mml op="&#x2271;">
5630
    <mo> &#x2271; </mo>
5631
  </pat:mml>
5632
</pat:template>
5633
5634
<pat:template>
5635
  <pat:tex op="\not" params="\geq"/>
5636
  <pat:mml op="&#x2271;">
5637
    <mo> &#x2271; </mo>
5638
  </pat:mml>
5639
</pat:template>
5640
5641
<pat:template>
5642
  <pat:tex op="\not" params="\prec"/>
5643
  <pat:mml op="&#x2280;">
5644
    <mo> &#x2280; </mo>
5645
  </pat:mml>
5646
</pat:template>
5647
5648
<pat:template>
5649
  <pat:tex op="\not" params="\preceq"/>
5650
  <pat:mml op="&#x22E0;">
5651
    <mo> &#x22E0; </mo>
5652
  </pat:mml>
5653
</pat:template>
5654
5655
<pat:template>
5656
  <pat:tex op="\not" params="\subset"/>
5657
  <pat:mml op="&#x2284;">
5658
    <mo> &#x2284; </mo>
5659
  </pat:mml>
5660
</pat:template>
5661
5662
<pat:template>
5663
  <pat:tex op="\not" params="\subseteq"/>
5664
  <pat:mml op="&#x2288;">
5665
    <mo> &#x2288; </mo>
5666
  </pat:mml>
5667
</pat:template>
5668
5669
<pat:template>
5670
  <pat:tex op="\not" params="\sqsubseteq"/>
5671
  <pat:mml op="&#x22E2;">
5672
    <mo> &#x22E2; </mo>
5673
  </pat:mml>
5674
</pat:template>
5675
5676
<pat:template>
5677
  <pat:tex op="\not" params="\in"/>
5678
  <pat:mml op="&#x2209;">
5679
    <mo> &#x2209; </mo>
5680
  </pat:mml>
5681
</pat:template>
5682
5683
<pat:template>
5684
  <pat:tex op="\notin"/>
5685
  <pat:mml op="&#x2209;">
5686
    <mo> &#x2209; </mo>
5687
  </pat:mml>
5688
</pat:template>
5689
5690
<pat:template>
5691
  <pat:tex op="\not" params="\succ"/>
5692
  <pat:mml op="&#x2281;">
5693
    <mo> &#x2281; </mo>
5694
  </pat:mml>
5695
</pat:template>
5696
5697
<pat:template>
5698
  <pat:tex op="\not" params="\succeq"/>
5699
  <pat:mml op="&#x22E1;">
5700
    <mo> &#x22E1; </mo>
5701
  </pat:mml>
5702
</pat:template>
5703
5704
<pat:template>
5705
  <pat:tex op="\not" params="\supset"/>
5706
  <pat:mml op="&#x2285;">
5707
    <mo> &#x2285; </mo>
5708
  </pat:mml>
5709
</pat:template>
5710
5711
<pat:template>
5712
  <pat:tex op="\not" params="\supseteq"/>
5713
  <pat:mml op="&#x2289;">
5714
    <mo> &#x2289; </mo>
5715
  </pat:mml>
5716
</pat:template>
5717
5718
<pat:template>
5719
  <pat:tex op="\not" params="\sqsupseteq"/>
5720
  <pat:mml op="&#x22E3;">
5721
    <mo> &#x22E3; </mo>
5722
  </pat:mml>
5723
</pat:template>
5724
5725
<pat:template>
5726
  <pat:tex op="\not" params="\sim"/>
5727
  <pat:mml op="&#x2241;">
5728
    <mo> &#x2241; </mo>
5729
  </pat:mml>
5730
</pat:template>
5731
5732
<pat:template>
5733
  <pat:tex op="\not" params="\simeq"/>
5734
  <pat:mml op="&#x2244;">
5735
    <mo> &#x2244; </mo>
5736
  </pat:mml>
5737
</pat:template>
5738
5739
<pat:template>
5740
  <pat:tex op="\not" params="\approx"/>
5741
  <pat:mml op="&#x2249;">
5742
    <mo> &#x2249; </mo>
5743
  </pat:mml>
5744
</pat:template>
5745
5746
<pat:template>
5747
  <pat:tex op="\not" params="\cong"/>
5748
  <pat:mml op="&#x2247;">
5749
    <mo> &#x2247; </mo>
5750
  </pat:mml>
5751
</pat:template>
5752
5753
<pat:template>
5754
  <pat:tex op="\not" params="\asymp"/>
5755
  <pat:mml op="&#x226D;">
5756
    <mo> &#x226D; </mo>
5757
  </pat:mml>
5758
</pat:template>
5759
5760
<pat:template>
5761
  <pat:tex op="\not" params="\patVAR!{symbol}"/>
5762
  <pat:mml op="">
5763
    <pat:variable name="symbol"/>
5764
    <mpadded width="0em" lspace="-1em"><mo>/</mo></mpadded>
5765
  </pat:mml>
5766
</pat:template>
5767
5768
5769
5770
<pat:template>
5771
  <pat:tex op="\frac" params="\patVAR!{num}\patVAR!{den}"/>
5772
  <pat:mml op="mfrac">
5773
    <mfrac>
5774
      <pat:variable name="num"/>
5775
      <pat:variable name="den"/>
5776
    </mfrac>
5777
  </pat:mml>
5778
</pat:template>
5779
5780
<pat:template>
5781
  <pat:tex op="\cfrac" params="[\patVAR!{pos}]\patVAR!{num}\patVAR!{den}"/>
5782
  <pat:mml op="mfrac">
5783
    <mfrac>
5784
      <pat:variable name="num"/>
5785
      <pat:variable name="den"/>
5786
    </mfrac>
5787
  </pat:mml>
5788
</pat:template>
5789
5790
<pat:template>
5791
  <pat:tex op="\cfrac" params="\patVAR!{num}\patVAR!{den}"/>
5792
  <pat:mml op="mfrac">
5793
    <mfrac>
5794
      <pat:variable name="num"/>
5795
      <pat:variable name="den"/>
5796
    </mfrac>
5797
  </pat:mml>
5798
</pat:template>
5799
5800
<pat:template>
5801
  <pat:tex op="\dfrac" params="{\patVAR*{num}}{\patVAR*{den}}"/>
5802
  <pat:mml op="">
5803
    <mstyle displaystyle="true">
5804
      <mfrac>
5805
        <pat:variable name="num"/>
5806
        <pat:variable name="den"/>
5807
      </mfrac>
5808
    </mstyle>
5809
  </pat:mml>
5810
</pat:template>
5811
5812
<pat:template>
5813
  <pat:tex op="\tfrac" params="{\patVAR*{num}}{\patVAR*{den}}"/>
5814
  <pat:mml op="">
5815
    <mstyle displaystyle="false" scriptlevel="0">
5816
      <mfrac>
5817
        <pat:variable name="num"/>
5818
        <pat:variable name="den"/>
5819
      </mfrac>
5820
    </mstyle>
5821
  </pat:mml>
5822
</pat:template>
5823
5824
<pat:template>
5825
  <pat:tex op="\genfrac" params="{\patVAR*{l}}{\patVAR*{r}}{}{}{\patVAR*{num}}{\patVAR*{den}}"/>
5826
  <pat:mml op="">
5827
    <pat:variable name="l"/>
5828
    <mfrac>
5829
      <pat:variable name="num"/>
5830
      <pat:variable name="den"/>
5831
    </mfrac>
5832
    <pat:variable name="r"/>
5833
  </pat:mml>
5834
</pat:template>
5835
5836
<pat:template>
5837
  <pat:tex op="\genfrac" params="{\patVAR*{l}}{\patVAR*{r}}{\patVAR*{thickness}}{0}{\patVAR*{num}}{\patVAR*{den}}"/>
5838
  <pat:mml op="">
5839
    <mstyle displaystyle="true">
5840
      <pat:variable name="l"/>
5841
      <mfrac>
5842
        <pat:variable name="num"/>
5843
        <pat:variable name="den"/>
5844
      </mfrac>
5845
      <pat:variable name="r"/>
5846
    </mstyle>
5847
  </pat:mml>
5848
</pat:template>
5849
5850
<pat:template>
5851
  <pat:tex op="\genfrac" params="{\patVAR*{l}}{\patVAR*{r}}{\patVAR*{thickness}}{\patVAR*{style}}{\patVAR*{num}}{\patVAR*{den}}"/>
5852
  <pat:mml op="">
5853
    <mstyle displaystyle="false">
5854
      <pat:variable name="style" attribute="scriptlevel" map="0=0 1=0 2=1 3=2"/>
5855
      <pat:variable name="l"/>
5856
	  <mfrac>
5857
        <pat:variable name="num"/>
5858
        <pat:variable name="den"/>
5859
      </mfrac>
5860
      <pat:variable name="r"/>
5861
    </mstyle>
5862
  </pat:mml>
5863
</pat:template>
5864
5865
5866
5867
<!-- Elementary functions -->
5868
5869
<pat:template>
5870
  <pat:tex op="\sqrt" params="[\patVAR+{deg}]\patVAR!{expr}"/>
5871
  <pat:mml op="mroot">
5872
    <mroot>
5873
      <pat:variable name="expr"/>
5874
      <pat:variable name="deg"/>
5875
    </mroot>
5876
  </pat:mml>
5877
</pat:template>
5878
5879
<pat:template>
5880
  <pat:tex op="\sqrt" params="\patVAR!{expr}"/>
5881
  <pat:mml op="msqrt">
5882
    <msqrt>
5883
      <pat:variable name="expr"/>
5884
    </msqrt>
5885
  </pat:mml>
5886
</pat:template>
5887
5888
<pat:template>
5889
  <pat:tex op="\root" params="\patVAR+{deg} \of \patVAR!{expr}"/>
5890
  <pat:mml op="mroot">
5891
    <mroot>
5892
      <pat:variable name="expr"/>
5893
      <pat:variable name="deg"/>
5894
    </mroot>
5895
  </pat:mml>
5896
</pat:template>
5897
5898
5899
5900
<pat:template>
5901
  <pat:tex op="\arccos"/>
5902
  <pat:mml op="arccos">
5903
    <mi> arccos </mi>
5904
  </pat:mml>
5905
</pat:template>
5906
5907
<pat:template>
5908
  <pat:tex op="\arcsin"/>
5909
  <pat:mml op="arcsin">
5910
    <mi> arcsin </mi>
5911
  </pat:mml>
5912
</pat:template>
5913
5914
<pat:template>
5915
  <pat:tex op="\arctan"/>
5916
  <pat:mml op="arctan">
5917
    <mi> arctan </mi>
5918
  </pat:mml>
5919
</pat:template>
5920
5921
<pat:template>
5922
  <pat:tex op="\arg"/>
5923
  <pat:mml op="arg">
5924
    <mi> arg </mi>
5925
  </pat:mml>
5926
</pat:template>
5927
5928
<pat:template>
5929
  <pat:tex op="\cos"/>
5930
  <pat:mml op="cos">
5931
    <mi> cos </mi>
5932
  </pat:mml>
5933
</pat:template>
5934
5935
<pat:template>
5936
  <pat:tex op="\cosh"/>
5937
  <pat:mml op="cosh">
5938
    <mi> cosh </mi>
5939
  </pat:mml>
5940
</pat:template>
5941
5942
<pat:template>
5943
  <pat:tex op="\cot"/>
5944
  <pat:mml op="cot">
5945
    <mi> cot </mi>
5946
  </pat:mml>
5947
</pat:template>
5948
5949
<pat:template>
5950
  <pat:tex op="\coth"/>
5951
  <pat:mml op="coth">
5952
    <mi> coth </mi>
5953
  </pat:mml>
5954
</pat:template>
5955
5956
<pat:template>
5957
  <pat:tex op="\csc"/>
5958
  <pat:mml op="csc">
5959
    <mi> csc </mi>
5960
  </pat:mml>
5961
</pat:template>
5962
5963
<pat:template>
5964
  <pat:tex op="\deg"/>
5965
  <pat:mml op="deg">
5966
    <mi> deg </mi>
5967
  </pat:mml>
5968
</pat:template>
5969
5970
<pat:template>
5971
  <pat:tex op="\det" params="_\patVAR!{limit}" prec="500"/>
5972
  <pat:mml op="">
5973
    <munder>
5974
      <mi> det </mi>
5975
      <pat:variable name="limit"/>
5976
    </munder>
5977
  </pat:mml>
5978
</pat:template>
5979
5980
<pat:template>
5981
  <pat:tex op="\det"/>
5982
  <pat:mml op="det">
5983
    <mi> det </mi>
5984
  </pat:mml>
5985
</pat:template>
5986
5987
<pat:template>
5988
  <pat:tex op="\dim"/>
5989
  <pat:mml op="dim">
5990
    <mi> dim </mi>
5991
  </pat:mml>
5992
</pat:template>
5993
5994
<pat:template>
5995
  <pat:tex op="\exp"/>
5996
  <pat:mml op="exp">
5997
    <mi> exp </mi>
5998
  </pat:mml>
5999
</pat:template>
6000
6001
<pat:template>
6002
  <pat:tex op="\gcd" params="_\patVAR!{limit}" prec="500"/>
6003
  <pat:mml op="">
6004
    <munder>
6005
      <mi> gcd </mi>
6006
      <pat:variable name="limit"/>
6007
    </munder>
6008
  </pat:mml>
6009
</pat:template>
6010
6011
<pat:template>
6012
  <pat:tex op="\gcd"/>
6013
  <pat:mml op="gcd">
6014
    <mi> gcd </mi>
6015
  </pat:mml>
6016
</pat:template>
6017
6018
<pat:template>
6019
  <pat:tex op="\hom"/>
6020
  <pat:mml op="hom">
6021
    <mi> hom </mi>
6022
  </pat:mml>
6023
</pat:template>
6024
6025
<pat:template>
6026
  <pat:tex op="\inf" params="_\patVAR!{limit}" prec="500"/>
6027
  <pat:mml op="">
6028
    <munder>
6029
      <mi> inf </mi>
6030
      <pat:variable name="limit"/>
6031
    </munder>
6032
  </pat:mml>
6033
</pat:template>
6034
6035
<pat:template>
6036
  <pat:tex op="\inf"/>
6037
  <pat:mml op="inf">
6038
    <mi> inf </mi>
6039
  </pat:mml>
6040
</pat:template>
6041
6042
<pat:template>
6043
  <pat:tex op="\ker"/>
6044
  <pat:mml op="ker">
6045
    <mi> ker </mi>
6046
  </pat:mml>
6047
</pat:template>
6048
6049
<pat:template>
6050
  <pat:tex op="\lg"/>
6051
  <pat:mml op="lg">
6052
    <mi> lg </mi>
6053
  </pat:mml>
6054
</pat:template>
6055
6056
<pat:template>
6057
  <pat:tex op="\lim" params="_\patVAR!{limit}" prec="500"/>
6058
  <pat:mml op="">
6059
    <munder>
6060
      <mi> lim </mi>
6061
      <pat:variable name="limit"/>
6062
    </munder>
6063
  </pat:mml>
6064
</pat:template>
6065
6066
<pat:template>
6067
  <pat:tex op="\lim"/>
6068
  <pat:mml op="lim">
6069
    <mi> lim </mi>
6070
  </pat:mml>
6071
</pat:template>
6072
6073
<pat:template>
6074
  <pat:tex op="\liminf" params="_\patVAR!{limit}" prec="500"/>
6075
  <pat:mml op="">
6076
    <munder>
6077
      <mi> lim inf </mi>
6078
      <pat:variable name="limit"/>
6079
    </munder>
6080
  </pat:mml>
6081
</pat:template>
6082
6083
<pat:template>
6084
  <pat:tex op="\liminf"/>
6085
  <pat:mml op="lim inf">
6086
    <mi> lim inf </mi>
6087
  </pat:mml>
6088
</pat:template>
6089
6090
<pat:template>
6091
  <pat:tex op="\limsup" params="_\patVAR!{limit}" prec="500"/>
6092
  <pat:mml op="">
6093
    <munder>
6094
      <mi> lim sup </mi>
6095
      <pat:variable name="limit"/>
6096
    </munder>
6097
  </pat:mml>
6098
</pat:template>
6099
6100
<pat:template>
6101
  <pat:tex op="\limsup"/>
6102
  <pat:mml op="lim sup">
6103
    <mi> lim sum </mi>
6104
  </pat:mml>
6105
</pat:template>
6106
6107
<pat:template>
6108
  <pat:tex op="\ln"/>
6109
  <pat:mml op="ln">
6110
    <mi> ln </mi>
6111
  </pat:mml>
6112
</pat:template>
6113
6114
<pat:template>
6115
  <pat:tex op="\log"/>
6116
  <pat:mml op="log">
6117
    <mi> log </mi>
6118
  </pat:mml>
6119
</pat:template>
6120
6121
<pat:template>
6122
  <pat:tex op="\bmod"/>
6123
  <pat:mml op="mod">
6124
    <mi> mod </mi>
6125
  </pat:mml>
6126
</pat:template>
6127
6128
<pat:template>
6129
  <pat:tex op="\mod"/>
6130
  <pat:mml op="">
6131
    <mi lspace="1em"> mod </mi>
6132
  </pat:mml>
6133
</pat:template>
6134
6135
<pat:template>
6136
  <pat:tex op="\pmod" params="\patVAR!{arg}"/>
6137
  <pat:mml op="">
6138
    <mspace width="1em"/>
6139
    <mfenced separators="">
6140
      <mi> mod </mi>
6141
      <pat:variable name="arg"/>
6142
    </mfenced>
6143
  </pat:mml>
6144
</pat:template>
6145
6146
<pat:template>
6147
  <pat:tex op="\pod" params="\patVAR!{arg}"/>
6148
  <pat:mml op="">
6149
    <mspace width="1em"/>
6150
    <mfenced separators="">
6151
      <pat:variable name="arg"/>
6152
    </mfenced>
6153
  </pat:mml>
6154
</pat:template>
6155
6156
<pat:template>
6157
  <pat:tex op="\max" params="_\patVAR!{limit}" prec="500"/>
6158
  <pat:mml op="">
6159
    <munder>
6160
      <mi> max </mi>
6161
      <pat:variable name="limit"/>
6162
    </munder>
6163
  </pat:mml>
6164
</pat:template>
6165
6166
<pat:template>
6167
  <pat:tex op="\max"/>
6168
  <pat:mml op="max">
6169
    <mi> max </mi>
6170
  </pat:mml>
6171
</pat:template>
6172
6173
<pat:template>
6174
  <pat:tex op="\min" params="_\patVAR!{limit}" prec="500"/>
6175
  <pat:mml op="">
6176
    <munder>
6177
      <mi> min </mi>
6178
      <pat:variable name="limit"/>
6179
    </munder>
6180
  </pat:mml>
6181
</pat:template>
6182
6183
<pat:template>
6184
  <pat:tex op="\min"/>
6185
  <pat:mml op="min">
6186
    <mi> min </mi>
6187
  </pat:mml>
6188
</pat:template>
6189
6190
<pat:template>
6191
  <pat:tex op="\Pr" params="_\patVAR!{limit}" prec="500"/>
6192
  <pat:mml op="">
6193
    <munder>
6194
      <mi> Pr </mi>
6195
      <pat:variable name="limit"/>
6196
    </munder>
6197
  </pat:mml>
6198
</pat:template>
6199
6200
<pat:template>
6201
  <pat:tex op="\Pr"/>
6202
  <pat:mml op="Pr">
6203
    <mi> Pr </mi>
6204
  </pat:mml>
6205
</pat:template>
6206
6207
<pat:template>
6208
  <pat:tex op="\sec"/>
6209
  <pat:mml op="sec">
6210
    <mi> sec </mi>
6211
  </pat:mml>
6212
</pat:template>
6213
6214
<pat:template>
6215
  <pat:tex op="\sin"/>
6216
  <pat:mml op="sin">
6217
    <mi> sin </mi>
6218
  </pat:mml>
6219
</pat:template>
6220
6221
<pat:template>
6222
  <pat:tex op="\sinh"/>
6223
  <pat:mml op="sinh">
6224
    <mi> sin </mi>
6225
  </pat:mml>
6226
</pat:template>
6227
6228
<pat:template>
6229
  <pat:tex op="\sup" params="_\patVAR!{limit}" prec="500"/>
6230
  <pat:mml op="">
6231
    <munder>
6232
      <mi> sup </mi>
6233
      <pat:variable name="limit"/>
6234
    </munder>
6235
  </pat:mml>
6236
</pat:template>
6237
6238
<pat:template>
6239
  <pat:tex op="\sup"/>
6240
  <pat:mml op="sup">
6241
    <mi> sup </mi>
6242
  </pat:mml>
6243
</pat:template>
6244
6245
<pat:template>
6246
  <pat:tex op="\tan"/>
6247
  <pat:mml op="tan">
6248
    <mi> tan </mi>
6249
  </pat:mml>
6250
</pat:template>
6251
6252
<pat:template>
6253
  <pat:tex op="\tanh"/>
6254
  <pat:mml op="tanh">
6255
    <mi> tanh </mi>
6256
  </pat:mml>
6257
</pat:template>
6258
6259
6260
6261
<!-- Under, over, sup & sub - scripts for specitial cases:
6262
     sum, proc, lim, inf,  max etc. MathML to TeX -->
6263
6264
<pat:template>
6265
  <pat:tex op="" params="\lim_\patVAR!{a}" prec="350"/>
6266
  <pat:mml op="lim">
6267
    <munder>
6268
      <mi> lim </mi>
6269
      <pat:variable name="a"/>
6270
    </munder>
6271
  </pat:mml>
6272
</pat:template>
6273
6274
<pat:template>
6275
  <pat:tex op="" params="\liminf_\patVAR!{a}" prec="350"/>
6276
  <pat:mml op="lim inf">
6277
    <munder>
6278
      <mi> lim inf </mi>
6279
      <pat:variable name="a"/>
6280
    </munder>
6281
  </pat:mml>
6282
</pat:template>
6283
6284
<pat:template>
6285
  <pat:tex op="" params="\limsup_\patVAR!{a}" prec="350"/>
6286
  <pat:mml op="lim sup">
6287
    <munder>
6288
      <mi> lim sup </mi>
6289
      <pat:variable name="a"/>
6290
    </munder>
6291
  </pat:mml>
6292
</pat:template>
6293
6294
<pat:template>
6295
  <pat:tex op="" params="\inf_\patVAR!{a}" prec="350"/>
6296
  <pat:mml op="inf">
6297
    <munder>
6298
      <mo> inf </mo>
6299
      <pat:variable name="a"/>
6300
    </munder>
6301
  </pat:mml>
6302
</pat:template>
6303
6304
<pat:template>
6305
  <pat:tex op="" params="\sup_\patVAR!{a}" prec="350"/>
6306
  <pat:mml op="sup">
6307
    <munder>
6308
      <mo> sup </mo>
6309
      <pat:variable name="a"/>
6310
    </munder>
6311
  </pat:mml>
6312
</pat:template>
6313
6314
<pat:template>
6315
  <pat:tex op="" params="\min_\patVAR!{a}" prec="350"/>
6316
  <pat:mml op="min">
6317
    <munder>
6318
      <mo> min </mo>
6319
      <pat:variable name="a"/>
6320
    </munder>
6321
  </pat:mml>
6322
</pat:template>
6323
6324
<pat:template>
6325
  <pat:tex op="" params="\max_\patVAR!{a}" prec="350"/>
6326
  <pat:mml op="max">
6327
    <munder>
6328
      <mo> max </mo>
6329
      <pat:variable name="a"/>
6330
    </munder>
6331
  </pat:mml>
6332
</pat:template>
6333
6334
6335
6336
<!-- Mathematical operators -->
6337
6338
<pat:template>
6339
  <pat:tex op="\mathop" params="\patVAR+{a}\limits_\patVAR!{b}&#x005E;\patVAR!{c}"/>
6340
  <pat:mml op="munderover">
6341
    <munderover>
6342
      <pat:variable name="a"/>
6343
      <pat:variable name="b"/>
6344
      <pat:variable name="c"/>
6345
    </munderover>
6346
  </pat:mml>
6347
</pat:template>
6348
6349
<pat:template>
6350
  <pat:tex op="\mathop" params="\patVAR+{a}\limits&#x005E;\patVAR!{c}_\patVAR!{b}"/>
6351
  <pat:mml op="munderover">
6352
    <munderover>
6353
      <pat:variable name="a"/>
6354
      <pat:variable name="b"/>
6355
      <pat:variable name="c"/>
6356
    </munderover>
6357
  </pat:mml>
6358
</pat:template>
6359
6360
<pat:template>
6361
  <pat:tex op="\mathop" params="\patVAR+{a}\limits_\patVAR!{b}"/>
6362
  <pat:mml op="munder">
6363
     <munder>
6364
      <pat:variable name="a"/>
6365
      <pat:variable name="b"/>
6366
    </munder>
6367
  </pat:mml>
6368
</pat:template>
6369
6370
<pat:template>
6371
  <pat:tex op="\mathop" params="\patVAR+{a}\limits&#x005E;\patVAR!{b}"/>
6372
  <pat:mml op="mover">
6373
    <mover>
6374
      <pat:variable name="a"/>
6375
      <pat:variable name="b"/>
6376
    </mover>
6377
  </pat:mml>
6378
</pat:template>
6379
6380
<pat:template>
6381
  <pat:tex op="\mathop"/>
6382
  <pat:mml op=""/>
6383
</pat:template>
6384
6385
<pat:template>
6386
  <pat:tex op="\mathord"/>
6387
  <pat:mml op=""/>
6388
</pat:template>
6389
6390
<pat:template>
6391
  <pat:tex op="\mathbin"/>
6392
  <pat:mml op=""/>
6393
</pat:template>
6394
6395
<pat:template>
6396
  <pat:tex op="\mathrel"/>
6397
  <pat:mml op=""/>
6398
</pat:template>
6399
6400
<pat:template>
6401
  <pat:tex op="\mathopen"/>
6402
  <pat:mml op=""/>
6403
</pat:template>
6404
6405
<pat:template>
6406
  <pat:tex op="\mathclose"/>
6407
  <pat:mml op=""/>
6408
</pat:template>
6409
6410
<pat:template>
6411
  <pat:tex op="\mathpunct"/>
6412
  <pat:mml op=""/>
6413
</pat:template>
6414
6415
<pat:template>
6416
  <pat:tex op="\mathinner"/>
6417
  <pat:mml op=""/>
6418
</pat:template>
6419
6420
6421
<pat:template>
6422
  <pat:tex op="\stackrel" params="\patVAR!{upper}\patVAR!{lower}"/>
6423
  <pat:mml op="">
6424
    <mover>
6425
      <pat:variable name="lower"/>
6426
      <pat:variable name="upper"/>
6427
    </mover>
6428
  </pat:mml>
6429
</pat:template>
6430
6431
6432
6433
<!-- LaTeX 2e math alphabet commands -->
6434
6435
<pat:template>
6436
  <pat:tex op="\mathrm" params="\patVAR!{text}"/>
6437
  <pat:mml op="mstyle">
6438
    <mstyle mathvariant="normal">
6439
      <pat:variable name="text"/>
6440
    </mstyle>
6441
  </pat:mml>
6442
</pat:template>
6443
6444
<pat:template>
6445
  <pat:tex op="\mathsf" params="\patVAR!{text}"/>
6446
  <pat:mml op="mstyle">
6447
    <mstyle mathvariant="sans-serif">
6448
      <pat:variable name="text"/>
6449
    </mstyle>
6450
  </pat:mml>
6451
</pat:template>
6452
6453
<pat:template>
6454
  <pat:tex op="\mathnormal" params="\patVAR!{text}"/>
6455
  <pat:mml op="mstyle">
6456
    <mstyle mathvariant="normal">
6457
      <pat:variable name="text"/>
6458
    </mstyle>
6459
  </pat:mml>
6460
</pat:template>
6461
6462
<pat:template>
6463
  <pat:tex op="\mathtt" params="\patVAR!{text}"/>
6464
  <pat:mml op="mstyle">
6465
    <mstyle mathvariant="sans-serif">
6466
      <pat:variable name="text"/>
6467
    </mstyle>
6468
  </pat:mml>
6469
</pat:template>
6470
6471
<pat:template>
6472
  <pat:tex op="\mathit" params="\patVAR!{text}"/>
6473
  <pat:mml op="mstyle">
6474
    <mstyle mathvariant="italic">
6475
      <pat:variable name="text"/>
6476
    </mstyle>
6477
  </pat:mml>
6478
</pat:template>
6479
6480
<pat:template>
6481
  <pat:tex op="\mathbf" params="\patVAR!{text}"/>
6482
  <pat:mml op="mstyle">
6483
    <mstyle mathvariant="bold">
6484
      <pat:variable name="text"/>
6485
    </mstyle>
6486
  </pat:mml>
6487
</pat:template>
6488
6489
<pat:template>
6490
  <pat:tex op="\mathcal" params="{B}"/>
6491
  <pat:mml op="">
6492
    <mi> &#x212C; </mi>
6493
  </pat:mml>
6494
</pat:template>
6495
6496
<pat:template>
6497
  <pat:tex op="\mathcal" params="{E}"/>
6498
  <pat:mml op="">
6499
    <mi> &#x2130; </mi>
6500
  </pat:mml>
6501
</pat:template>
6502
6503
<pat:template>
6504
  <pat:tex op="\mathcal" params="{F}"/>
6505
  <pat:mml op="">
6506
    <mi> &#x2131; </mi>
6507
  </pat:mml>
6508
</pat:template>
6509
6510
<pat:template>
6511
  <pat:tex op="\mathcal" params="{H}"/>
6512
  <pat:mml op="">
6513
    <mi> &#x210B; </mi>
6514
  </pat:mml>
6515
</pat:template>
6516
6517
<pat:template>
6518
  <pat:tex op="\mathcal" params="{I}"/>
6519
  <pat:mml op="">
6520
    <mi> &#x2110; </mi>
6521
  </pat:mml>
6522
</pat:template>
6523
6524
<pat:template>
6525
  <pat:tex op="\mathcal" params="{L}"/>
6526
  <pat:mml op="">
6527
    <mi> &#x2112; </mi>
6528
  </pat:mml>
6529
</pat:template>
6530
6531
<pat:template>
6532
  <pat:tex op="\mathcal" params="{M}"/>
6533
  <pat:mml op="">
6534
    <mi> &#x2133; </mi>
6535
  </pat:mml>
6536
</pat:template>
6537
6538
<pat:template>
6539
  <pat:tex op="\mathcal" params="{R}"/>
6540
  <pat:mml op="">
6541
    <mi> &#x211B; </mi>
6542
  </pat:mml>
6543
</pat:template>
6544
6545
<pat:template>
6546
  <pat:tex op="\mathcal" params="\patVAR!{text}"/>		<!-- Others in plane 1 - not yet widely supported -->
6547
  <pat:mml op="mstyle">
6548
    <mstyle mathvariant="script">
6549
      <pat:variable name="text"/>
6550
    </mstyle>
6551
  </pat:mml>
6552
</pat:template>
6553
6554
<pat:template>
6555
  <pat:tex op="\mathscr" params="\patVAR!{text}"/>
6556
  <pat:mml op="mstyle">
6557
    <mstyle mathvariant="script">
6558
      <pat:variable name="text"/>
6559
    </mstyle>
6560
  </pat:mml>
6561
</pat:template>
6562
6563
<pat:template>
6564
  <pat:tex op="\mathbb" params="{C}"/>
6565
  <pat:mml op="">
6566
    <mi> &#x2102; </mi>
6567
  </pat:mml>
6568
</pat:template>
6569
6570
<pat:template>
6571
  <pat:tex op="\mathbb" params="{H}"/>
6572
  <pat:mml op="">
6573
    <mi> &#x210D; </mi>
6574
  </pat:mml>
6575
</pat:template>
6576
6577
<pat:template>
6578
  <pat:tex op="\mathbb" params="{N}"/>
6579
  <pat:mml op="">
6580
    <mi> &#x2115; </mi>
6581
  </pat:mml>
6582
</pat:template>
6583
6584
<pat:template>
6585
  <pat:tex op="\mathbb" params="{P}"/>
6586
  <pat:mml op="">
6587
    <mi> &#x2119; </mi>
6588
  </pat:mml>
6589
</pat:template>
6590
6591
<pat:template>
6592
  <pat:tex op="\mathbb" params="{Q}"/>
6593
  <pat:mml op="">
6594
    <mi> &#x211A; </mi>
6595
  </pat:mml>
6596
</pat:template>
6597
6598
<pat:template>
6599
  <pat:tex op="\mathbb" params="{R}"/>
6600
  <pat:mml op="">
6601
    <mi> &#x211D; </mi>
6602
  </pat:mml>
6603
</pat:template>
6604
6605
<pat:template>
6606
  <pat:tex op="\mathbb" params="{Z}"/>
6607
  <pat:mml op="">
6608
    <mi> &#x2124; </mi>
6609
  </pat:mml>
6610
</pat:template>
6611
6612
<pat:template>
6613
  <pat:tex op="\mathbb" params="\patVAR!{text}"/>		<!-- Others in plane 1 - not yet widely supported -->
6614
  <pat:mml op="mstyle">
6615
    <mstyle mathvariant="double-struck">
6616
      <pat:variable name="text"/>
6617
    </mstyle>
6618
  </pat:mml>
6619
</pat:template>
6620
6621
<pat:template>
6622
  <pat:tex op="\bmit" params="\patVAR*{text}"/>
6623
  <pat:mml op="mstyle">
6624
    <mstyle mathvariant="bold-italic">
6625
      <pat:variable name="text"/>
6626
    </mstyle>
6627
  </pat:mml>
6628
</pat:template>
6629
6630
6631
6632
<!-- LaTeX 2.09 font declarations -->
6633
6634
<pat:template>
6635
  <pat:tex op="\rm" params="\patVAR*{text}"/>
6636
  <pat:mml op="mstyle">
6637
    <mstyle mathvariant="normal">
6638
      <pat:variable name="text"/>
6639
    </mstyle>
6640
  </pat:mml>
6641
</pat:template>
6642
6643
<pat:template>
6644
  <pat:tex op="\bf" params="\patVAR*{text}"/>
6645
  <pat:mml op="mstyle">
6646
    <mstyle mathvariant="bold">
6647
      <pat:variable name="text"/>
6648
    </mstyle>
6649
  </pat:mml>
6650
</pat:template>
6651
6652
<pat:template>
6653
  <pat:tex op="\tt" params="\patVAR*{text}"/>
6654
  <pat:mml op="mstyle">
6655
    <mstyle mathvariant="sans-serif">
6656
      <pat:variable name="text"/>
6657
    </mstyle>
6658
  </pat:mml>
6659
</pat:template>
6660
6661
<pat:template>
6662
  <pat:tex op="\it" params="\patVAR*{text}"/>
6663
  <pat:mml op="mstyle">
6664
    <mstyle mathvariant="italic">
6665
      <pat:variable name="text"/>
6666
    </mstyle>
6667
  </pat:mml>
6668
</pat:template>
6669
6670
<pat:template>
6671
  <pat:tex op="\sl" params="\patVAR*{text}"/>
6672
  <pat:mml op="mstyle">
6673
    <mstyle mathvariant="italic">
6674
      <pat:variable name="text"/>
6675
    </mstyle>
6676
  </pat:mml>
6677
</pat:template>
6678
6679
<pat:template>
6680
  <pat:tex op="\mit" params="\patVAR*{text}"/>
6681
  <pat:mml op="mstyle">
6682
    <mstyle mathvariant="italic">
6683
      <pat:variable name="text"/>
6684
    </mstyle>
6685
  </pat:mml>
6686
</pat:template>
6687
6688
<pat:template>
6689
  <pat:tex op="\sc" params="\patVAR*{text}"/>
6690
  <pat:mml op="">
6691
    <pat:variable name="text"/>
6692
  </pat:mml>
6693
</pat:template>
6694
6695
<pat:template>
6696
  <pat:tex op="\sf" params="\patVAR*{text}"/>
6697
  <pat:mml op="mstyle">
6698
    <mstyle mathvariant="sans-serif">
6699
      <pat:variable name="text"/>
6700
    </mstyle>
6701
  </pat:mml>
6702
</pat:template>
6703
6704
<pat:template>
6705
  <pat:tex op="\cal" params="\patVAR*{text}"/>
6706
  <pat:mml op="mstyle">
6707
    <mstyle mathvariant="script">
6708
      <pat:variable name="text"/>
6709
    </mstyle>
6710
  </pat:mml>
6711
</pat:template>
6712
6713
6714
6715
<!-- Text font type control commands -->
6716
6717
<pat:template>
6718
  <pat:tex op="" params="\textrm \patVAR!{text}"/>
6719
  <pat:mml op="mtext">
6720
    <mtext><pat:variable name="text"/></mtext>
6721
  </pat:mml>
6722
</pat:template>
6723
6724
6725
<pat:template>
6726
  <pat:tex op="\textrm" params="{\patVAR*{text}}"/>
6727
  <pat:mml op="">
6728
    <mstyle mathvariant="normal">
6729
      <pat:variable name="text"/>
6730
    </mstyle>
6731
  </pat:mml>
6732
</pat:template>
6733
6734
<pat:template>
6735
  <pat:tex op="\texttt" params="{\patVAR*{text}}"/>
6736
  <pat:mml op="">
6737
    <mstyle mathvariant="sans-serif">
6738
      <pat:variable name="text"/>
6739
    </mstyle>
6740
  </pat:mml>
6741
</pat:template>
6742
6743
<pat:template>
6744
  <pat:tex op="\textsf" params="{\patVAR*{text}}"/>
6745
  <pat:mml op="">
6746
    <mstyle mathvariant="sans-serif">
6747
      <pat:variable name="text"/>
6748
    </mstyle>
6749
  </pat:mml>
6750
</pat:template>
6751
6752
<pat:template>
6753
  <pat:tex op="\textup" params="{\patVAR*{text}}"/>
6754
  <pat:mml op="">
6755
    <pat:variable name="text"/>
6756
  </pat:mml>
6757
</pat:template>
6758
6759
<pat:template>
6760
  <pat:tex op="\textit" params="{\patVAR*{text}}"/>
6761
  <pat:mml op="">
6762
    <mstyle mathvariant="italic">
6763
      <pat:variable name="text"/>
6764
    </mstyle>
6765
  </pat:mml>
6766
</pat:template>
6767
6768
<pat:template>
6769
  <pat:tex op="\textsl" params="{\patVAR*{text}}"/>
6770
  <pat:mml op="">
6771
    <mstyle mathvariant="italic">
6772
      <pat:variable name="text"/>
6773
    </mstyle>
6774
  </pat:mml>
6775
</pat:template>
6776
6777
<pat:template>
6778
  <pat:tex op="\textsc" params="{\patVAR*{text}}"/>
6779
  <pat:mml op="">
6780
    <pat:variable name="text"/>
6781
  </pat:mml>
6782
</pat:template>
6783
6784
<pat:template>
6785
  <pat:tex op="\textmd" params="{\patVAR*{text}}"/>
6786
  <pat:mml op="">
6787
    <pat:variable name="text"/>
6788
  </pat:mml>
6789
</pat:template>
6790
6791
<pat:template>
6792
  <pat:tex op="\textbf" params="{\patVAR*{text}}"/>
6793
  <pat:mml op="">
6794
    <mstyle mathvariant="italic">
6795
      <pat:variable name="text"/>
6796
    </mstyle>
6797
  </pat:mml>
6798
</pat:template>
6799
6800
<pat:template>
6801
  <pat:tex op="\textnormal" params="{\patVAR*{text}}"/>
6802
  <pat:mml op="">
6803
    <mstyle mathvariant="normal">
6804
      <pat:variable name="text"/>
6805
    </mstyle>
6806
  </pat:mml>
6807
</pat:template>
6808
6809
<pat:template>
6810
  <pat:tex op="\text" params="{\patVAR*{text}}"/>
6811
  <pat:mml op="">
6812
    <mstyle mathvariant="normal">
6813
      <pat:variable name="text"/>
6814
    </mstyle>
6815
  </pat:mml>
6816
</pat:template>
6817
6818
<pat:template>
6819
  <pat:tex op="\emph" params="{\patVAR*{text}}"/>
6820
  <pat:mml op="">
6821
    <mstyle mathvariant="italic">
6822
      <pat:variable name="text"/>
6823
    </mstyle>
6824
  </pat:mml>
6825
</pat:template>
6826
6827
<pat:template>
6828
  <pat:tex op="\em" params="\patVAR*{text}"/>
6829
  <pat:mml op="">
6830
    <mstyle mathvariant="italic">
6831
      <pat:variable name="text"/>
6832
    </mstyle>
6833
  </pat:mml>
6834
</pat:template>
6835
6836
<pat:template>
6837
  <pat:tex op="\upshape" params="\patVAR*{text}"/>
6838
  <pat:mml op="">
6839
    <mstyle mathvariant="normal">
6840
      <pat:variable name="text"/>
6841
    </mstyle>
6842
  </pat:mml>
6843
</pat:template>
6844
6845
<pat:template>
6846
  <pat:tex op="\itshape" params="\patVAR*{text}"/>
6847
  <pat:mml op="">
6848
    <mstyle mathvariant="italic">
6849
      <pat:variable name="text"/>
6850
    </mstyle>
6851
  </pat:mml>
6852
</pat:template>
6853
6854
<pat:template>
6855
  <pat:tex op="\slshape" params="\patVAR*{text}"/>
6856
  <pat:mml op="">
6857
    <mstyle mathvariant="italic">
6858
      <pat:variable name="text"/>
6859
    </mstyle>
6860
  </pat:mml>
6861
</pat:template>
6862
6863
<pat:template>
6864
  <pat:tex op="\scshape" params="\patVAR*{text}"/>
6865
  <pat:mml op="">
6866
    <mstyle mathvariant="normal">
6867
      <pat:variable name="text"/>
6868
    </mstyle>
6869
  </pat:mml>
6870
</pat:template>
6871
6872
6873
6874
<!-- Other font type control commands -->
6875
6876
<pat:template>
6877
  <pat:tex op="\Bbb" params="C"/>
6878
  <pat:mml op="mstyle">
6879
    <mo> &#x2102; </mo>
6880
  </pat:mml>
6881
</pat:template>
6882
6883
<pat:template>
6884
  <pat:tex op="\Bbb" params="H"/>
6885
  <pat:mml op="mstyle">
6886
    <mo> &#x210D; </mo>
6887
  </pat:mml>
6888
</pat:template>
6889
6890
<pat:template>
6891
  <pat:tex op="\Bbb" params="N"/>
6892
  <pat:mml op="mstyle">
6893
    <mo> &#x2115; </mo>
6894
  </pat:mml>
6895
</pat:template>
6896
6897
<pat:template>
6898
  <pat:tex op="\Bbb" params="P"/>
6899
  <pat:mml op="mstyle">
6900
    <mo> &#x2119; </mo>
6901
  </pat:mml>
6902
</pat:template>
6903
6904
<pat:template>
6905
  <pat:tex op="\Bbb" params="Q"/>
6906
  <pat:mml op="mstyle">
6907
    <mo> &#x211A; </mo>
6908
  </pat:mml>
6909
</pat:template>
6910
6911
<pat:template>
6912
  <pat:tex op="\Bbb" params="R"/>
6913
  <pat:mml op="mstyle">
6914
    <mo> &#x211D; </mo>
6915
  </pat:mml>
6916
</pat:template>
6917
6918
<pat:template>
6919
  <pat:tex op="\Bbb" params="Z"/>
6920
  <pat:mml op="mstyle">
6921
    <mo> &#x2124; </mo>
6922
  </pat:mml>
6923
</pat:template>
6924
6925
<pat:template>
6926
  <pat:tex op="\Bbb" params="\patVAR!{cap}"/>		<!-- Others in plane 1 - not yet widely supported -->
6927
  <pat:mml op="mstyle">
6928
    <mstyle mathvariant="double-struck">
6929
      <pat:variable name="cap"/>
6930
    </mstyle>
6931
  </pat:mml>
6932
</pat:template>
6933
6934
<pat:template>
6935
  <pat:tex op="\roman" params="\patVAR!{text}"/>
6936
  <pat:mml op="mstyle">
6937
    <mstyle mathvariant="normal">
6938
      <pat:variable name="text"/>
6939
    </mstyle>
6940
  </pat:mml>
6941
</pat:template>
6942
6943
<pat:template>
6944
  <pat:tex op="\rom" params="\patVAR!{text}"/>
6945
  <pat:mml op="mstyle">
6946
    <mstyle mathvariant="normal">
6947
      <pat:variable name="text"/>
6948
    </mstyle>
6949
  </pat:mml>
6950
</pat:template>
6951
6952
<pat:template>
6953
  <pat:tex op="\Cal" params="\patVAR!{text}"/>
6954
  <pat:mml op="mstyle">
6955
    <mstyle mathvariant="script">
6956
      <pat:variable name="text"/>
6957
    </mstyle>
6958
  </pat:mml>
6959
</pat:template>
6960
6961
<pat:template>
6962
  <pat:tex op="\bold" params="\patVAR!{symbol}"/>
6963
  <pat:mml op="">
6964
    <mstyle mathvariant="bold">
6965
      <pat:variable name="symbol"/>
6966
    </mstyle>
6967
  </pat:mml>
6968
</pat:template>
6969
6970
<pat:template>
6971
  <pat:tex op="\boldkey" params="\patVAR!{symbol}"/>
6972
  <pat:mml op="">
6973
    <mstyle mathvariant="bold">
6974
      <pat:variable name="symbol"/>
6975
    </mstyle>
6976
  </pat:mml>
6977
</pat:template>
6978
6979
<pat:template>
6980
  <pat:tex op="\boldsymbol" params="\patVAR!{symbol}"/>
6981
  <pat:mml op="">
6982
    <mstyle mathvariant="bold">
6983
      <pat:variable name="symbol"/>
6984
    </mstyle>
6985
  </pat:mml>
6986
</pat:template>
6987
6988
<pat:template>
6989
  <pat:tex op="\pmb" params="\patVAR!{symbol}"/>
6990
  <pat:mml op="">
6991
    <mstyle mathvariant="bold">
6992
      <pat:variable name="symbol"/>
6993
    </mstyle>
6994
  </pat:mml>
6995
</pat:template>
6996
6997
<pat:template>
6998
  <pat:tex op="\mathfrak" params="\patVAR!{symbol}"/>
6999
  <pat:mml op="">
7000
    <mstyle mathvariant="fraktur">
7001
      <pat:variable name="symbol"/>
7002
    </mstyle>
7003
  </pat:mml>
7004
</pat:template>
7005
7006
<pat:template>
7007
  <pat:tex op="\intertext" params="{\patVAR*{text}}"/>
7008
  <pat:mml op="">
7009
    <mspace linebreak="newline"/>
7010
    <pat:variable name="text"/>
7011
  </pat:mml>
7012
</pat:template>
7013
7014
7015
7016
<!-- Font size control -->
7017
7018
<pat:template>
7019
  <pat:tex op="\tiny" params="\patVAR+{text}"/>
7020
  <pat:mml op="">
7021
    <mstyle scriptlevel="+4">
7022
      <pat:variable name="text"/>
7023
    </mstyle>
7024
  </pat:mml>
7025
</pat:template>
7026
7027
<pat:template>
7028
  <pat:tex op="\scriptsize" params="\patVAR+{text}"/>
7029
  <pat:mml op="">
7030
    <mstyle scriptlevel="+3">
7031
      <pat:variable name="text"/>
7032
    </mstyle>
7033
  </pat:mml>
7034
</pat:template>
7035
7036
<pat:template>
7037
  <pat:tex op="\footnotesize" params="\patVAR+{text}"/>
7038
  <pat:mml op="">
7039
    <mstyle scriptlevel="+2">
7040
      <pat:variable name="text"/>
7041
    </mstyle>
7042
  </pat:mml>
7043
</pat:template>
7044
7045
<pat:template>
7046
  <pat:tex op="\small" params="\patVAR+{text}"/>
7047
  <pat:mml op="">
7048
    <mstyle scriptlevel="+1">
7049
      <pat:variable name="text"/>
7050
    </mstyle>
7051
  </pat:mml>
7052
</pat:template>
7053
7054
<pat:template>
7055
  <pat:tex op="\normalsize" params="\patVAR+{text}"/>
7056
  <pat:mml op="">
7057
    <mstyle scriptlevel="0">
7058
      <pat:variable name="text"/>
7059
    </mstyle>
7060
  </pat:mml>
7061
</pat:template>
7062
7063
<pat:template>
7064
  <pat:tex op="\large" params="\patVAR+{text}"/>
7065
  <pat:mml op="">
7066
    <mstyle scriptlevel="-1">
7067
      <pat:variable name="text"/>
7068
    </mstyle>
7069
  </pat:mml>
7070
</pat:template>
7071
7072
<pat:template>
7073
  <pat:tex op="\Large" params="\patVAR+{text}"/>
7074
  <pat:mml op="">
7075
    <mstyle scriptlevel="-2">
7076
      <pat:variable name="text"/>
7077
    </mstyle>
7078
  </pat:mml>
7079
</pat:template>
7080
7081
<pat:template>
7082
  <pat:tex op="\LARGE" params="\patVAR+{text}"/>
7083
  <pat:mml op="">
7084
    <mstyle scriptlevel="-3">
7085
      <pat:variable name="text"/>
7086
    </mstyle>
7087
  </pat:mml>
7088
</pat:template>
7089
7090
<pat:template>
7091
  <pat:tex op="\huge" params="\patVAR+{text}"/>
7092
  <pat:mml op="">
7093
    <mstyle scriptlevel="-4">
7094
      <pat:variable name="text"/>
7095
    </mstyle>
7096
  </pat:mml>
7097
</pat:template>
7098
7099
<pat:template>
7100
  <pat:tex op="\HUGE" params="\patVAR+{text}"/>
7101
  <pat:mml op="">
7102
    <mstyle scriptlevel="-5">
7103
      <pat:variable name="text"/>
7104
    </mstyle>
7105
  </pat:mml>
7106
</pat:template>
7107
7108
7109
<pat:template>
7110
  <pat:tex op="\displaystyle" params="\patVAR*{text}"/>
7111
  <pat:mml op="">
7112
    <mstyle displaystyle="true" scriptlevel="0">
7113
      <pat:variable name="text"/>
7114
    </mstyle>
7115
  </pat:mml>
7116
</pat:template>
7117
7118
<pat:template>
7119
  <pat:tex op="\textstyle" params="\patVAR*{text}"/>
7120
  <pat:mml op="">
7121
    <mstyle displaystyle="false" scriptlevel="0">
7122
      <pat:variable name="text"/>
7123
    </mstyle>
7124
  </pat:mml>
7125
</pat:template>
7126
7127
<pat:template>
7128
  <pat:tex op="\scriptstyle" params="\patVAR*{text}"/>
7129
  <pat:mml op="">
7130
    <mstyle displaystyle="false" scriptlevel="1">
7131
      <pat:variable name="text"/>
7132
    </mstyle>
7133
  </pat:mml>
7134
</pat:template>
7135
7136
<pat:template>
7137
  <pat:tex op="\scriptscriptstyle" params="\patVAR*{text}"/>
7138
  <pat:mml op="">
7139
    <mstyle displaystyle="false" scriptlevel="2">
7140
      <pat:variable name="text"/>
7141
    </mstyle>
7142
  </pat:mml>
7143
</pat:template>
7144
7145
7146
7147
<!-- Big delimiter modifiers -->
7148
7149
<pat:template>
7150
  <pat:tex op="\bigl" params="\patVAR!{a}" prec="350"/>
7151
  <pat:mml op="">
7152
    <mstyle form="prefix" scriptlevel="-1">
7153
      <pat:variable name="a"/>
7154
    </mstyle>
7155
  </pat:mml>
7156
</pat:template>
7157
7158
<pat:template>
7159
  <pat:tex op="\bigr" params="\patVAR!{a}" prec="350"/>
7160
  <pat:mml op="">
7161
    <mstyle form="postfix" scriptlevel="-1">
7162
      <pat:variable name="a"/>
7163
    </mstyle>
7164
  </pat:mml>
7165
</pat:template>
7166
7167
7168
<pat:template>
7169
  <pat:tex op="\Bigl" params="\patVAR!{a}" prec="350"/>
7170
  <pat:mml op="">
7171
    <mstyle form="prefix" scriptlevel="-2">
7172
      <pat:variable name="a"/>
7173
    </mstyle>
7174
  </pat:mml>
7175
</pat:template>
7176
7177
<pat:template>
7178
  <pat:tex op="\Bigr" params="\patVAR!{a}" prec="350"/>
7179
  <pat:mml op="">
7180
    <mstyle form="postfix" scriptlevel="-2">
7181
      <pat:variable name="a"/>
7182
    </mstyle>
7183
  </pat:mml>
7184
</pat:template>
7185
7186
7187
<pat:template>
7188
  <pat:tex op="\biggl" params="\patVAR!{a}" prec="350"/>
7189
  <pat:mml op="">
7190
    <mstyle form="prefix" scriptlevel="-3">
7191
      <pat:variable name="a"/>
7192
    </mstyle>
7193
  </pat:mml>
7194
</pat:template>
7195
7196
<pat:template>
7197
  <pat:tex op="\biggr" params="\patVAR!{a}" prec="350"/>
7198
  <pat:mml op="">
7199
    <mstyle form="postfix" scriptlevel="-3">
7200
      <pat:variable name="a"/>
7201
    </mstyle>
7202
  </pat:mml>
7203
</pat:template>
7204
7205
7206
<pat:template>
7207
  <pat:tex op="\Biggl" params="\patVAR!{a}" prec="350"/>
7208
  <pat:mml op="">
7209
    <mstyle form="prefix" scriptlevel="-4">
7210
      <pat:variable name="a"/>
7211
    </mstyle>
7212
  </pat:mml>
7213
</pat:template>
7214
7215
<pat:template>
7216
  <pat:tex op="\Biggr" params="\patVAR!{a}" prec="350"/>
7217
  <pat:mml op="">
7218
    <mstyle form="postfix" scriptlevel="-1">
7219
      <pat:variable name="a"/>
7220
    </mstyle>
7221
  </pat:mml>
7222
</pat:template>
7223
7224
7225
<pat:template>
7226
  <pat:tex op="\bigm" params="\patVAR!{a}" prec="350"/>
7227
  <pat:mml op="">
7228
    <mstyle form="infix" scriptlevel="-1">
7229
      <pat:variable name="a"/>
7230
    </mstyle>
7231
  </pat:mml>
7232
</pat:template>
7233
7234
<pat:template>
7235
  <pat:tex op="\Bigm" params="\patVAR!{a}" prec="350"/>
7236
  <pat:mml op="">
7237
    <mstyle form="infix" scriptlevel="-2">
7238
      <pat:variable name="a"/>
7239
    </mstyle>
7240
  </pat:mml>
7241
</pat:template>
7242
7243
7244
<pat:template>
7245
  <pat:tex op="\biggm" params="\patVAR!{a}" prec="350"/>
7246
  <pat:mml op="">
7247
    <mstyle form="infix" scriptlevel="-3">
7248
      <pat:variable name="a"/>
7249
    </mstyle>
7250
  </pat:mml>
7251
</pat:template>
7252
7253
<pat:template>
7254
  <pat:tex op="\Biggm" params="\patVAR!{a}" prec="350"/>
7255
  <pat:mml op="">
7256
    <mstyle form="infix" scriptlevel="-4">
7257
      <pat:variable name="a"/>
7258
    </mstyle>
7259
  </pat:mml>
7260
</pat:template>
7261
7262
<pat:template>
7263
  <pat:tex op="\big" params="\patVAR!{a}" prec="350"/>
7264
  <pat:mml op="">
7265
    <mstyle scriptlevel="-1">
7266
      <pat:variable name="a"/>
7267
    </mstyle>
7268
  </pat:mml>
7269
</pat:template>
7270
7271
<pat:template>
7272
  <pat:tex op="\Big" params="\patVAR!{a}" prec="350"/>
7273
  <pat:mml op="">
7274
    <mstyle scriptlevel="-2">
7275
      <pat:variable name="a"/>
7276
    </mstyle>
7277
  </pat:mml>
7278
</pat:template>
7279
7280
7281
<pat:template>
7282
  <pat:tex op="\bigg" params="\patVAR!{a}" prec="350"/>
7283
  <pat:mml op="">
7284
    <mstyle scriptlevel="-3">
7285
      <pat:variable name="a"/>
7286
    </mstyle>
7287
  </pat:mml>
7288
</pat:template>
7289
7290
<pat:template>
7291
  <pat:tex op="\Bigg" params="\patVAR!{a}" prec="350"/>
7292
  <pat:mml op="">
7293
    <mstyle scriptlevel="-4">
7294
      <pat:variable name="a"/>
7295
    </mstyle>
7296
  </pat:mml>
7297
</pat:template>
7298
7299
7300
7301
<!-- Layout related TeX macros -->
7302
7303
<pat:template>
7304
  <pat:tex op="\buildrel" params="\patVAR*{cond} \over \patVAR!{base}" prec="777"/>
7305
  <pat:mml op="">
7306
    <mover>
7307
      <pat:variable name="base"/>
7308
      <pat:variable name="cond"/>
7309
    </mover>
7310
  </pat:mml>
7311
</pat:template>
7312
7313
<pat:template>
7314
  <pat:tex op="\lefteqn" params="\patVAR!{eqn}"/>
7315
  <pat:mml op="">
7316
    <pat:variable name="eqn"/>
7317
  </pat:mml>
7318
</pat:template>
7319
7320
<pat:template>
7321
  <pat:tex op="\hbox" params="to \patVAR+{size} {\patVAR*{sequence}}"/>
7322
  <pat:mml op="">
7323
    <mpadded>
7324
      <pat:variable name="size" attribute="width"/>
7325
      <pat:variable name="sequence"/>
7326
    </mpadded>
7327
  </pat:mml>
7328
</pat:template>
7329
7330
<pat:template>
7331
  <pat:tex op="\hbox" params="spread \patVAR+{size} {\patVAR*{sequence}}"/>
7332
  <pat:mml op="">
7333
    <mpadded>
7334
      <pat:variable name="sequence"/>
7335
    </mpadded>
7336
  </pat:mml>
7337
</pat:template>
7338
7339
<pat:template>
7340
  <pat:tex op="\hbox" params="{\patVAR*{sequence}}"/>
7341
  <pat:mml op="">
7342
    <mpadded>
7343
      <pat:variable name="sequence"/>
7344
    </mpadded>
7345
  </pat:mml>
7346
</pat:template>
7347
7348
<pat:template>
7349
  <pat:tex op="\hspace" params="{\patVAR+{width}}"/>
7350
  <pat:mml op="">
7351
    <mspace width="1em"/>
7352
  </pat:mml>
7353
</pat:template>
7354
7355
<pat:template>
7356
  <pat:tex op="\hspace" params="*{\patVAR+{width}}"/>
7357
  <pat:mml op="">
7358
    <mspace width="1em"/>
7359
  </pat:mml>
7360
</pat:template>
7361
7362
<pat:template>
7363
  <pat:tex op="\vspace" params="{\patVAR+{width}}"/>
7364
  <pat:mml op="">
7365
    <mspace width="1ex"/>
7366
  </pat:mml>
7367
</pat:template>
7368
7369
<pat:template>
7370
  <pat:tex op="\vspace" params="*{\patVAR+{width}}"/>
7371
  <pat:mml op="">
7372
    <mspace width="1ex"/>
7373
  </pat:mml>
7374
</pat:template>
7375
7376
<pat:template>
7377
  <pat:tex op="\strut"/>
7378
  <pat:mml op="">
7379
    <mspace width="0pt" height="8.5pt" depth="3.5pt"/>
7380
  </pat:mml>
7381
</pat:template>
7382
7383
<pat:template>
7384
  <pat:tex op="\phantom" params="{\patVAR+{expr}}"/>
7385
  <pat:mml op="mphantom">
7386
    <mphantom>
7387
      <pat:variable name="expr"/>
7388
    </mphantom>
7389
  </pat:mml>
7390
</pat:template>
7391
7392
<pat:template>
7393
  <pat:tex op="\vphantom" params="{\patVAR+{expr}}"/>
7394
  <pat:mml op="mphantom">
7395
    <mphantom>
7396
      <mpadded width="0">
7397
        <pat:variable name="expr"/>
7398
      </mpadded>
7399
    </mphantom>
7400
  </pat:mml>
7401
</pat:template>
7402
7403
<pat:template>
7404
  <pat:tex op="\smashed" params="{\patVAR+{expr}}"/>
7405
  <pat:mml op="">
7406
    <mphantom>
7407
      <mpadded height="0" depth="0">
7408
        <pat:variable name="expr"/>
7409
      </mpadded>
7410
    </mphantom>
7411
  </pat:mml>
7412
</pat:template>
7413
7414
<pat:template>
7415
  <pat:tex op="\llap" params="\patVAR!{expr}"/>
7416
  <pat:mml op="">
7417
    <mpadded width="-1 width">
7418
      <pat:variable name="expr"/>
7419
    </mpadded>
7420
  </pat:mml>
7421
</pat:template>
7422
7423
<pat:template>
7424
  <pat:tex op="\rlap" params="\patVAR!{expr}"/>
7425
  <pat:mml op="">
7426
    <mpadded width="0">
7427
      <pat:variable name="expr"/>
7428
    </mpadded>
7429
  </pat:mml>
7430
</pat:template>
7431
7432
7433
7434
<!-- Spaces / new line characters -->
7435
7436
<pat:template>
7437
  <pat:tex op="\enskip"/>
7438
  <pat:mml op="">
7439
    <mspace width="0.5em"/>
7440
  </pat:mml>
7441
</pat:template>
7442
7443
<pat:template>
7444
  <pat:tex op="\enspace"/>
7445
  <pat:mml op="">
7446
    <mspace width="0.5em"/>
7447
  </pat:mml>
7448
</pat:template>
7449
7450
<pat:template>
7451
  <pat:tex op="\ "/>
7452
  <pat:mml op="">
7453
    <mspace width="1em"/>
7454
  </pat:mml>
7455
</pat:template>
7456
7457
<pat:template>
7458
  <pat:tex op="~"/>
7459
  <pat:mml op="">
7460
    <mspace width="1em" linebreak="nobreak"/>
7461
  </pat:mml>
7462
</pat:template>
7463
7464
<pat:template>
7465
  <pat:tex op="\,"/>
7466
  <pat:mml op="&#x2009;">
7467
    <mo> &#x2009; </mo>
7468
  </pat:mml>
7469
</pat:template>
7470
7471
<pat:template>
7472
  <pat:tex op="\thinspace"/>
7473
  <pat:mml op="&#x2009;">
7474
    <mo> &#x2009; </mo>
7475
  </pat:mml>
7476
</pat:template>
7477
7478
<pat:template>
7479
  <pat:tex op="\:"/>
7480
  <pat:mml op="">
7481
    <mspace width="0.22222em"/>		<!-- 4/18 em -->
7482
  </pat:mml>
7483
</pat:template>
7484
7485
<pat:template>
7486
  <pat:tex op="\>"/>
7487
  <pat:mml op="">
7488
    <mspace width="0.22222em"/>		<!-- 4/18 em -->
7489
  </pat:mml>
7490
</pat:template>
7491
7492
<pat:template>
7493
  <pat:tex op="\medspace"/>
7494
  <pat:mml op="">
7495
    <mspace width="0.22222em"/>		<!-- 4/18 em -->
7496
  </pat:mml>
7497
</pat:template>
7498
7499
<pat:template>
7500
  <pat:tex op="\;"/>
7501
  <pat:mml op="">
7502
    <mspace width="0.27778em"/>		<!-- 5/18 em -->
7503
  </pat:mml>
7504
</pat:template>
7505
7506
<pat:template>
7507
  <pat:tex op="\thickspace"/>
7508
  <pat:mml op="">
7509
    <mspace width="0.27778em"/>		<!-- 5/18 em -->
7510
  </pat:mml>
7511
</pat:template>
7512
7513
<pat:template>
7514
  <pat:tex op="\!"/>
7515
  <pat:mml op="">
7516
    <mspace width="-0.16667em"/>	<!-- -3/18 em -->
7517
  </pat:mml>
7518
</pat:template>
7519
7520
<pat:template>
7521
  <pat:tex op="\negthinspace"/>
7522
  <pat:mml op="">
7523
    <mspace width="-0.16667em"/>	<!-- -3/18 em -->
7524
  </pat:mml>
7525
</pat:template>
7526
7527
<pat:template>
7528
  <pat:tex op="\negmedspace"/>
7529
  <pat:mml op="">
7530
    <mspace width="-0.22222em"/>	<!-- -4/18 em -->
7531
  </pat:mml>
7532
</pat:template>
7533
7534
<pat:template>
7535
  <pat:tex op="\negthickspace"/>
7536
  <pat:mml op="">
7537
    <mspace width="-0.27778em"/>	<!-- -5/18 em -->
7538
  </pat:mml>
7539
</pat:template>
7540
7541
<pat:template>
7542
  <pat:tex op="\quad"/>
7543
  <pat:mml op="">
7544
    <mspace width="1em"/>
7545
  </pat:mml>
7546
</pat:template>
7547
7548
<pat:template>
7549
  <pat:tex op="\qquad"/>
7550
  <pat:mml op="">
7551
    <mspace width="2em"/>
7552
  </pat:mml>
7553
</pat:template>
7554
7555
<pat:template>
7556
  <pat:tex op="\mspace" params="{\patVAR+{space}}"/>
7557
  <pat:mml op="">
7558
    <mspace width="0.16667em"/>	<!-- default -->
7559
  </pat:mml>
7560
</pat:template>
7561
7562
7563
<pat:template>
7564
  <pat:tex op="\cr" prec="-1"/>
7565
  <pat:mml op="">
7566
    <mspace linebreak="newline"/>
7567
  </pat:mml>
7568
</pat:template>
7569
7570
<pat:template>
7571
  <pat:tex op="\\" params="[\patVAR+{space}]"/>
7572
  <pat:mml op="">
7573
    <mspace linebreak="newline"/>
7574
  </pat:mml>
7575
</pat:template>
7576
7577
<pat:template>
7578
  <pat:tex op="\\" params="*[\patVAR+{space}]"/>
7579
  <pat:mml op="">
7580
    <mspace linebreak="newline"/>
7581
  </pat:mml>
7582
</pat:template>
7583
7584
<pat:template>
7585
  <pat:tex op="\\" prec="-1"/>
7586
  <pat:mml op="\\">
7587
    <mspace linebreak="newline"/>
7588
  </pat:mml>
7589
</pat:template>
7590
7591
<pat:template>
7592
  <pat:tex op="\linebreak" params="[4]"/>
7593
  <pat:mml op="">
7594
    <mspace linebreak="newline"/>
7595
  </pat:mml>
7596
</pat:template>
7597
7598
<pat:template>
7599
  <pat:tex op="\linebreak" params="[\patVAR!{level}]"/>
7600
  <pat:mml op="">
7601
    <mspace linebreak="goodbreak"/>
7602
  </pat:mml>
7603
</pat:template>
7604
7605
<pat:template>
7606
  <pat:tex op="\linebreak"/>
7607
  <pat:mml op="">
7608
    <mspace linebreak="newline"/>
7609
  </pat:mml>
7610
</pat:template>
7611
7612
<pat:template>
7613
  <pat:tex op="\nolinebreak" params="[4]"/>
7614
  <pat:mml op="">
7615
    <mspace linebreak="nobreak"/>
7616
  </pat:mml>
7617
</pat:template>
7618
7619
<pat:template>
7620
  <pat:tex op="\nolinebreak" params="[\patVAR!{level}]"/>
7621
  <pat:mml op="">
7622
    <mspace linebreak="badbreak"/>
7623
  </pat:mml>
7624
</pat:template>
7625
7626
<pat:template>
7627
  <pat:tex op="\nolinebreak"/>
7628
  <pat:mml op="">
7629
    <mspace linebreak="nobreak"/>
7630
  </pat:mml>
7631
</pat:template>
7632
7633
<pat:template>
7634
  <pat:tex op="\allowbreak"/>
7635
  <pat:mml op="">
7636
    <mspace linebreak="goodbreak"/>
7637
  </pat:mml>
7638
</pat:template>
7639
7640
<pat:template>
7641
  <pat:tex op="\nobreak"/>
7642
  <pat:mml op="">
7643
    <mspace linebreak="nobreak"/>
7644
  </pat:mml>
7645
</pat:template>
7646
7647
<pat:template>
7648
  <pat:tex op="\break"/>
7649
  <pat:mml op="">
7650
    <mspace linebreak="newline"/>
7651
  </pat:mml>
7652
</pat:template>
7653
7654
<pat:template>
7655
  <pat:tex op="\newpage"/>
7656
  <pat:mml op=""/>
7657
</pat:template>
7658
7659
<pat:template>
7660
  <pat:tex op="\displaybreak" params="[\patVAR*{level}]"/>
7661
  <pat:mml op=""/>
7662
</pat:template>
7663
7664
<pat:template>
7665
  <pat:tex op="\displaybreak"/>
7666
  <pat:mml op=""/>
7667
</pat:template>
7668
7669
7670
7671
<!-- Pseudo macros (for infix operators) - DO NOT RENAME/REMOVE! -->
7672
7673
<pat:template>
7674
  <pat:tex op="\patPSEUDO" params="\patVAR+{num}\over\patVAR+{den}" prec="666"/>
7675
  <pat:mml op="mfrac">
7676
    <mfrac>
7677
      <pat:variable name="num"/>
7678
      <pat:variable name="den"/>
7679
    </mfrac>
7680
  </pat:mml>
7681
</pat:template>
7682
7683
<pat:template>
7684
  <pat:tex op="\patPSEUDO" params="\patVAR+{num}\choose\patVAR+{den}" prec="666"/>
7685
  <pat:mml op="mfrac">
7686
    <mfrac linethickness="0">
7687
      <pat:variable name="num"/>
7688
      <pat:variable name="den"/>
7689
    </mfrac>
7690
  </pat:mml>
7691
</pat:template>
7692
7693
<pat:template>
7694
  <pat:tex op="\patPSEUDO" params="\patVAR+{num}\atop\patVAR+{den}" prec="666"/>
7695
  <pat:mml op="mfrac">
7696
    <mfrac linethickness="0">
7697
      <pat:variable name="num"/>
7698
      <pat:variable name="den"/>
7699
    </mfrac>
7700
  </pat:mml>
7701
</pat:template>
7702
7703
<pat:template>
7704
  <pat:tex op="\patPSEUDO" params="\patVAR!{base}_\patVAR!{sub}&#x005E;\patVAR!{sup}" prec="333"/>
7705
  <pat:mml op="msubsup">
7706
    <msubsup>
7707
      <pat:variable name="base"/>
7708
      <pat:variable name="sub"/>
7709
      <pat:variable name="sup"/>
7710
    </msubsup>
7711
  </pat:mml>
7712
</pat:template>
7713
7714
<pat:template>
7715
  <pat:tex op="\patPSEUDO" params="\patVAR!{base}&#x005E;\patVAR!{sup}_\patVAR!{sub}" prec="333"/>
7716
  <pat:mml op="msupsub">
7717
    <msubsup>
7718
      <pat:variable name="base"/>
7719
      <pat:variable name="sub"/>
7720
      <pat:variable name="sup"/>
7721
    </msubsup>
7722
  </pat:mml>
7723
</pat:template>
7724
7725
<pat:template>
7726
  <pat:tex op="\patPSEUDO" params="\patVAR!{base}_\patVAR!{sub}" prec="330"/>
7727
  <pat:mml op="msub">
7728
    <msub>
7729
      <pat:variable name="base"/>
7730
      <pat:variable name="sub"/>
7731
    </msub>
7732
  </pat:mml>
7733
</pat:template>
7734
7735
<pat:template>
7736
  <pat:tex op="\patPSEUDO" params="\patVAR!{base}&#x005E;\patVAR!{sup}" prec="330"/>
7737
  <pat:mml op="msup">
7738
    <msup>
7739
      <pat:variable name="base"/>
7740
      <pat:variable name="sup"/>
7741
    </msup>
7742
  </pat:mml>
7743
</pat:template>
7744
7745
7746
<!-- The following four are variations without base explicitly given -->
7747
7748
<pat:template>
7749
  <pat:tex op="_" params="\patVAR!{sub}&#x005E;\patVAR!{sup}" prec="333"/>
7750
  <pat:mml op="msubsup">
7751
    <msubsup>
7752
      <mrow/>
7753
      <pat:variable name="sub"/>
7754
      <pat:variable name="sup"/>
7755
    </msubsup>
7756
  </pat:mml>
7757
</pat:template>
7758
7759
<pat:template>
7760
  <pat:tex op="&#x005E;" params="\patVAR!{sup}_\patVAR!{sub}" prec="333"/>
7761
  <pat:mml op="msupsub">
7762
    <msubsup>
7763
      <mrow/>
7764
      <pat:variable name="sub"/>
7765
      <pat:variable name="sup"/>
7766
    </msubsup>
7767
  </pat:mml>
7768
</pat:template>
7769
7770
<pat:template>
7771
  <pat:tex op="_" params="\patVAR!{sub}" prec="330"/>
7772
  <pat:mml op="msub">
7773
    <msub>
7774
      <mrow/>
7775
      <pat:variable name="sub"/>
7776
    </msub>
7777
  </pat:mml>
7778
</pat:template>
7779
7780
<pat:template>
7781
  <pat:tex op="&#x005E;" params="\patVAR!{sup}" prec="330"/>
7782
  <pat:mml op="msup">
7783
    <msup>
7784
      <mrow/>
7785
      <pat:variable name="sup"/>
7786
    </msup>
7787
  </pat:mml>
7788
</pat:template>
7789
7790
7791
7792
<!-- \def handling - DO NOT RENAME/REMOVE! -->
7793
7794
<pat:template>
7795
  <pat:tex op="\def" params="\patVAR!{name}\patVAR!{def}"/>
7796
  <pat:mml op=""/>
7797
</pat:template>
7798
7799
<pat:template>
7800
  <pat:tex op="\newcommand" params="\patVAR!{name}\patVAR!{def}"/>
7801
  <pat:mml op=""/>
7802
</pat:template>
7803
7804
<pat:template>
7805
  <pat:tex op="\renewcommand" params="\patVAR!{name}\patVAR!{def}"/>
7806
  <pat:mml op=""/>
7807
</pat:template>
7808
7809
7810
7811
<!-- Math/Text mode switchers -->
7812
7813
<!--pat:template>
7814
  <pat:tex op="\mbox" params="{$\patVAR*{math}$}"/>
7815
  <pat:mml op="">
7816
    <pat:variable name="math"/>
7817
  </pat:mml>
7818
</pat:template-->
7819
7820
<pat:template>
7821
  <pat:tex op="\mbox" params="{\patVAR*{text}}"/>
7822
  <pat:mml op="">
7823
    <pat:variable name="text"/>
7824
  </pat:mml>
7825
</pat:template>
7826
7827
<pat:template>
7828
  <pat:tex op="\fbox" params="{\patVAR*{text}}"/>
7829
  <pat:mml op="">
7830
    <mo> <pat:variable name="text"/> </mo>
7831
  </pat:mml>
7832
</pat:template>
7833
7834
<pat:template>
7835
  <pat:tex op="\ensuremath" params="{\patVAR*{math}}"/>
7836
  <pat:mml op="">
7837
    <pat:variable name="math"/>
7838
  </pat:mml>
7839
</pat:template>
7840
7841
<pat:template>
7842
  <pat:tex op="$" params="\patVAR+{math} $"/>
7843
  <pat:mml op="">
7844
    <pat:variable name="math"/>
7845
  </pat:mml>
7846
</pat:template>
7847
7848
7849
7850
<!-- Other miscellaneuos TeX macros -->
7851
7852
<pat:template>
7853
  <pat:tex op="\label" params="\patVAR!{~label}"/>
7854
  <pat:mml op=""/>
7855
</pat:template>
7856
7857
<pat:template>
7858
  <pat:tex op="\cite" params="\patVAR!{key}"/>
7859
  <pat:mml op=""/>
7860
</pat:template>
7861
7862
<pat:template>
7863
  <pat:tex op="\nonumber"/>
7864
  <pat:mml op=""/>
7865
</pat:template>
7866
7867
<pat:template>
7868
  <pat:tex op="\limits"/>
7869
  <pat:mml op=""/>
7870
</pat:template>
7871
7872
<pat:template>
7873
  <pat:tex op="\nolimits"/>
7874
  <pat:mml op=""/>
7875
</pat:template>
7876
7877
<pat:template>
7878
  <pat:tex op="\hline"/>
7879
  <pat:mml op=""/>
7880
</pat:template>
7881
7882
<pat:template>
7883
  <pat:tex op="\vline"/>
7884
  <pat:mml op=""/>
7885
</pat:template>
7886
7887
<pat:template>
7888
  <pat:tex op="\relax"/>
7889
  <pat:mml op=""/>
7890
</pat:template>
7891
7892
7893
7894
<!-- Other other stuff (used by MathML to TeX only) -->
7895
7896
<pat:template>
7897
  <pat:tex op=""/>
7898
  <pat:mml op="&#x02062;">
7899
    <mo> &#x02062; </mo>	<!-- invisible times -->
7900
  </pat:mml>
7901
</pat:template>
7902
7903
<pat:template>
7904
  <pat:tex op=""/>
7905
  <pat:mml op="&#x02061;">
7906
    <mo> &#x02061; </mo>	  <!-- apply function -->
7907
  </pat:mml>
7908
</pat:template>
7909
7910
<pat:template>
7911
  <pat:tex op=""/>
7912
  <pat:mml op="&#x0200B;">
7913
    <mo> &#x0200B; </mo>	  <!-- invisible comma -->
7914
  </pat:mml>
7915
</pat:template>
7916
7917
<pat:template>
7918
  <pat:tex op="" params="{\patVAR+{text}}"/>
7919
  <pat:mml op="mo">
7920
    <mo> <pat:variable name="text"/> </mo>
7921
  </pat:mml>
7922
</pat:template>
7923
7924
<pat:template>
7925
  <pat:tex op="" params="\hbox{\patVAR{a}}"/>
7926
  <pat:mml op="mspace">
7927
    <mspace width="pat:variable =a"/>
7928
  </pat:mml>
7929
</pat:template>
7930
7931
<pat:template>
7932
  <pat:tex op="" params="\patREP*{\patVAR!{a}}"/>
7933
  <pat:mml op="mrow">
7934
    <mrow>
7935
      <pat:rep> <pat:variable name="a"/> </pat:rep>
7936
    </mrow>
7937
  </pat:mml>
7938
</pat:template>
7939
7940
<pat:template>
7941
  <pat:tex op="" params="\patVAR!{a}"/>
7942
  <pat:mml op="mi">
7943
    <mi> <pat:variable name="a"/> </mi>
7944
  </pat:mml>
7945
</pat:template>
7946
7947
<pat:template>
7948
  <pat:tex op="" params="\patVAR!{a}"/>
7949
  <pat:mml op="mn">
7950
    <mn> <pat:variable name="a"/> </mn>
7951
  </pat:mml>
7952
</pat:template>
7953
7954
<pat:template>
7955
  <pat:tex op="" params="\patREP*{\patVAR!{a}}"/>
7956
  <pat:mml op="math">
7957
    <math>
7958
      <pat:rep> <pat:variable name="a"/> </pat:rep>
7959
    </math>
7960
  </pat:mml>
7961
</pat:template>
7962
7963
<pat:template>
7964
  <pat:tex op="" params=""/>
7965
  <pat:mml op="none">
7966
    <none/>
7967
  </pat:mml>
7968
</pat:template>
7969
7970
<pat:template>
7971
  <pat:tex op="" params="\patREP*{\patVAR{a}}"/>
7972
  <pat:mml op="mstyle">
7973
    <mstyle>
7974
     <pat:rep> <pat:variable name="a"/> </pat:rep>
7975
    </mstyle>
7976
  </pat:mml>
7977
</pat:template>
7978
7979
<pat:template>
7980
  <pat:tex op="" params="\patREP*{\patVAR{a}}"/>
7981
  <pat:mml op="merror">
7982
    <merror>
7983
      <pat:rep> <pat:variable name="a"/> </pat:rep>
7984
    </merror>
7985
  </pat:mml>
7986
</pat:template>
7987
7988
<pat:template>
7989
  <pat:tex op="" params="\patREP*{\patVAR{a}}"/>
7990
  <pat:mml op="menclose">
7991
    <menclose>
7992
      <pat:rep> <pat:variable name="a"/> </pat:rep>
7993
    </menclose>
7994
  </pat:mml>
7995
</pat:template>
7996
7997
<pat:template>
7998
  <pat:tex op="" params="\patREP*{\patVAR{a}}"/>
7999
  <pat:mml op="mpadded">
8000
    <mpadded>
8001
      <pat:rep> <pat:variable name="a"/> </pat:rep>
8002
    </mpadded>
8003
  </pat:mml>
8004
</pat:template>
8005
8006
<pat:template>
8007
  <pat:tex op="" params=""/>
8008
  <pat:mml op="~none">
8009
    <pat:empty/>
8010
  </pat:mml>
8011
</pat:template>
8012
8013
8014
8015
<!-- ========================= NEW STUFF ============================ -->
8016
8017
<pat:template>
8018
  <pat:tex op="\*"/>
8019
  <pat:mml op=""/>
8020
</pat:template>
8021
8022
<pat:template>
8023
  <pat:tex op="\-"/>
8024
  <pat:mml op=""/>
8025
</pat:template>
8026
8027
<pat:template>
8028
  <pat:tex op="\patPSEUDO" params="\patVAR*{num} \overwithdelims \patVAR!{delim1} \patVAR!{delim2} \patVAR*{den}" prec="666"/>
8029
  <pat:mml op="">
8030
    <pat:variable name="delim1"/>
8031
    <mfrac>
8032
      <pat:variable name="num"/>
8033
      <pat:variable name="den"/>
8034
    </mfrac>
8035
    <pat:variable name="delim2"/>
8036
  </pat:mml>
8037
</pat:template>
8038
8039
<pat:template>
8040
  <pat:tex op="\patPSEUDO" params="\patVAR*{num} \atopwithdelims \patVAR!{delim1} \patVAR!{delim2} \patVAR*{den}" prec="666"/>
8041
  <pat:mml op="">
8042
    <pat:variable name="delim1"/>
8043
    <mfrac linethickness="0">
8044
      <pat:variable name="num"/>
8045
      <pat:variable name="den"/>
8046
    </mfrac>
8047
    <pat:variable name="delim2"/>
8048
  </pat:mml>
8049
</pat:template>
8050
8051
<pat:template>
8052
  <pat:tex op="\patPSEUDO" params="\patVAR*{num} \brack \patVAR*{den}" prec="666"/>
8053
  <pat:mml op="">
8054
    <mfenced open="[" close="]" separators="">
8055
      <mfrac linethickness="0">
8056
        <pat:variable name="num"/>
8057
        <pat:variable name="den"/>
8058
      </mfrac>
8059
    </mfenced>
8060
  </pat:mml>
8061
</pat:template>
8062
8063
<pat:template>
8064
  <pat:tex op="\patPSEUDO" params="\patVAR*{num} \brace \patVAR*{den}" prec="666"/>
8065
  <pat:mml op="">
8066
    <mfenced open="{" close="}" separators="">
8067
      <mfrac linethickness="0">
8068
        <pat:variable name="num"/>
8069
        <pat:variable name="den"/>
8070
      </mfrac>
8071
    </mfenced>
8072
  </pat:mml>
8073
</pat:template>
8074
8075
8076
<pat:template>
8077
  <pat:tex op="\implies"/>
8078
  <pat:mml op="&#x21D2;">
8079
    <mo> &#x21D2; </mo>				<!-- Should be 27F9 -->
8080
  </pat:mml>
8081
</pat:template>
8082
8083
<pat:template>
8084
  <pat:tex op="\begin" params="{picture} \patVAR*{whatever} \end{picture}"/>
8085
  <pat:mml op=""/>
8086
</pat:template>
8087
8088
<pat:template>
8089
  <pat:tex op="\setlength" params="{\patVAR*{cmd}}{\patVAR*{spec}}"/>
8090
  <pat:mml op=""/>
8091
</pat:template>
8092
8093
<pat:template>
8094
  <pat:tex op="\if" params="\patVAR*{stuff}\fi"/>
8095
  <pat:mml op="">
8096
    <pat:variable name="stuff"/>
8097
  </pat:mml>
8098
</pat:template>
8099
8100
<pat:template>
8101
  <pat:tex op="\ifx" params="\patVAR!{token1}\patVAR!{token2}\patVAR*{stuff}\fi"/>
8102
  <pat:mml op="">
8103
    <pat:variable name="stuff"/>
8104
  </pat:mml>
8105
</pat:template>
8106
8107
<pat:template>
8108
  <pat:tex op="\fontencoding" params="{\patVAR*{whatever}}"/>
8109
  <pat:mml op=""/>
8110
</pat:template>
8111
8112
<pat:template>
8113
  <pat:tex op="\fontfamily" params="{\patVAR*{whatever}}"/>
8114
  <pat:mml op=""/>
8115
</pat:template>
8116
8117
<pat:template>
8118
  <pat:tex op="\fontseries" params="{\patVAR*{whatever}}"/>
8119
  <pat:mml op=""/>
8120
</pat:template>
8121
8122
<pat:template>
8123
  <pat:tex op="\fontshape" params="{\patVAR*{whatever}}"/>
8124
  <pat:mml op=""/>
8125
</pat:template>
8126
8127
<pat:template>
8128
  <pat:tex op="\fontsize" params="{\patVAR*{size}}{\patVAR*{lspacing}}"/>
8129
  <pat:mml op=""/>
8130
</pat:template>
8131
8132
<pat:template>
8133
  <pat:tex op="\selectfont"/>
8134
  <pat:mml op=""/>
8135
</pat:template>
8136
8137
<pat:template>
8138
  <pat:tex op="\begin" params="{minipage} {\patVAR*{width}} \patVAR*{text} \end{minipage}"/>
8139
  <pat:mml op="">
8140
    <pat:variable name="text"/>
8141
  </pat:mml>
8142
</pat:template>
8143
8144
<pat:template>
8145
  <pat:tex op="\noindent"/>
8146
  <pat:mml op=""/>
8147
</pat:template>
8148
8149
<pat:template>
8150
  <pat:tex op="\substack" params="{\patREP*{\patVAR+{line}\\}\patVAR*{last}}"/>
8151
  <pat:mml op="">
8152
    <mtable>
8153
      <pat:rep>
8154
        <mtr> <pat:variable name="line"/> </mtr>
8155
      </pat:rep>
8156
        <mtr> <pat:variable name="last"/> </mtr>
8157
    </mtable>
8158
  </pat:mml>
8159
</pat:template>
8160
8161
<pat:template>
8162
  <pat:tex op="\binom" params="{\patVAR*{top}}{\patVAR*{bot}}"/>
8163
  <pat:mml op="">
8164
    <mfenced separators="">
8165
      <mfrac linethickness="0">
8166
        <pat:variable name="top"/>
8167
        <pat:variable name="bot"/>
8168
      </mfrac>
8169
    </mfenced>
8170
  </pat:mml>
8171
</pat:template>
8172
8173
<pat:template>
8174
  <pat:tex op="\dbinom" params="{\patVAR*{top}}{\patVAR*{bot}}"/>
8175
  <pat:mml op="">
8176
    <mstyle displaystyle="true">
8177
      <mfenced separators="">
8178
        <mfrac linethickness="0">
8179
          <pat:variable name="top"/>
8180
          <pat:variable name="bot"/>
8181
        </mfrac>
8182
      </mfenced>
8183
    </mstyle>
8184
  </pat:mml>
8185
</pat:template>
8186
8187
<pat:template>
8188
  <pat:tex op="\tbinom" params="{\patVAR*{top}}{\patVAR*{bot}}"/>
8189
  <pat:mml op="">
8190
    <mstyle displaystyle="false" scriptlevel="0">
8191
      <mfenced separators="">
8192
        <mfrac linethickness="0">
8193
          <pat:variable name="top"/>
8194
          <pat:variable name="bot"/>
8195
        </mfrac>
8196
      </mfenced>
8197
    </mstyle>
8198
  </pat:mml>
8199
</pat:template>
8200
8201
<pat:template>
8202
  <pat:tex op="\notag"/>
8203
  <pat:mml op=""/>
8204
</pat:template>
8205
8206
<pat:template>
8207
  <pat:tex op="\protect"/>
8208
  <pat:mml op=""/>
8209
</pat:template>
8210
8211
<pat:template>
8212
  <pat:tex op="\rule" params="[\patVAR*{lift}]{\patVAR*{w}}{\patVAR*{h}}"/>
8213
  <pat:mml op="">
8214
    <mstyle color="black">
8215
      <mspace>
8216
        <pat:variable name="w" attribute="width"/>
8217
        <pat:variable name="h" attribute="height"/>
8218
        <pat:variable name="lift" attribute="depth"/>
8219
      </mspace>
8220
    </mstyle>
8221
  </pat:mml>
8222
</pat:template>
8223
8224
<pat:template>
8225
  <pat:tex op="\rule" params="{\patVAR*{w}}{\patVAR*{h}}"/>
8226
  <pat:mml op="">
8227
    <mstyle color="black">
8228
      <mspace>
8229
        <pat:variable name="w" attribute="width"/>
8230
        <pat:variable name="h" attribute="height"/>
8231
      </mspace>
8232
    </mstyle>
8233
  </pat:mml>
8234
</pat:template>
8235
8236
<pat:template>
8237
  <pat:tex op="\shorthandoff" params="{\patVAR*{arg}}"/>
8238
  <pat:mml op=""/>
8239
</pat:template>
8240
8241
<pat:template>
8242
  <pat:tex op="\shorthandon" params="{\patVAR*{arg}}"/>
8243
  <pat:mml op=""/>
8244
</pat:template>
8245
8246
<pat:template>
8247
  <pat:tex op="\xymatrix" params="{\patREP+{\patVAR*{firstCol}\patREP*{&amp;\patVAR*{rest}}\\}}"/>
8248
  <pat:mml op="mtable">
8249
    <mtable>
8250
      <pat:rep>
8251
        <mtr>
8252
          <mtd> <pat:variable name="firstCol"/> </mtd>
8253
          <pat:rep>
8254
            <mtd> <pat:variable name="rest"/> </mtd>
8255
          </pat:rep>
8256
        </mtr>
8257
      </pat:rep>
8258
    </mtable>
8259
  </pat:mml>
8260
</pat:template>
8261
8262
<pat:template>
8263
  <pat:tex op="\ar" params="@\patVAR!{style}[\patVAR*{dir}]"/>
8264
  <pat:mml op=""/>
8265
</pat:template>
8266
8267
<pat:template>
8268
  <pat:tex op="\ar" params="[\patVAR*{dir}]"/>
8269
  <pat:mml op=""/>
8270
</pat:template>
8271
8272
<pat:template>
8273
  <pat:tex op="\injlim"/>
8274
  <pat:mml op="">
8275
    <mo> inj lim </mo>
8276
  </pat:mml>
8277
</pat:template>
8278
8279
<pat:template>
8280
  <pat:tex op="\projlim"/>
8281
  <pat:mml op="">
8282
    <mo> proj lim </mo>
8283
  </pat:mml>
8284
</pat:template>
8285
8286
<pat:template>
8287
  <pat:tex op="\varlimsup"/>
8288
  <pat:mml op="">
8289
    <mover>
8290
      <mo> lim </mo>
8291
      <mo stretchy="true"> &#x00AF; </mo>
8292
	</mover>
8293
  </pat:mml>
8294
</pat:template>
8295
8296
<pat:template>
8297
  <pat:tex op="\varliminf"/>
8298
  <pat:mml op="">
8299
    <munder>
8300
      <mo> lim </mo>
8301
      <mo stretchy="true"> &#x00AF; </mo>
8302
	</munder>
8303
  </pat:mml>
8304
</pat:template>
8305
8306
<pat:template>
8307
  <pat:tex op="\varinjlim"/>
8308
  <pat:mml op="">
8309
    <mover>
8310
      <mo> lim </mo>
8311
      <mo stretchy="true"> &#x2192; </mo>
8312
	</mover>
8313
  </pat:mml>
8314
</pat:template>
8315
8316
<pat:template>
8317
  <pat:tex op="\varprojlim"/>
8318
  <pat:mml op="">
8319
    <munder>
8320
      <mo> lim </mo>
8321
      <mo stretchy="true"> &#x2190; </mo>
8322
	</munder>
8323
  </pat:mml>
8324
</pat:template>
8325
8326
<pat:template>
8327
  <pat:tex op="\operatorname" params="{\patVAR*{op}}"/>
8328
  <pat:mml op="">
8329
    <pat:variable name="op"/>
8330
  </pat:mml>
8331
</pat:template>
8332
8333
<pat:template>
8334
  <pat:tex op="\operatorname" params="*{\patVAR*{op}}"/>
8335
  <pat:mml op="">
8336
    <pat:variable name="op"/>
8337
  </pat:mml>
8338
</pat:template>
8339
8340
<pat:template>
8341
  <pat:tex op="\shoveleft" params="{\patVAR*{formula}}"/>
8342
  <pat:mml op="">
8343
    <pat:variable name="formula"/>
8344
  </pat:mml>
8345
</pat:template>
8346
8347
<pat:template>
8348
  <pat:tex op="\shoveright" params="{\patVAR*{formula}}"/>
8349
  <pat:mml op="">
8350
    <pat:variable name="formula"/>
8351
  </pat:mml>
8352
</pat:template>
8353
8354
<pat:template>
8355
  <pat:tex op="\ref"/>
8356
  <pat:mml op=""/>
8357
</pat:template>
8358
8359
<pat:template>
8360
  <pat:tex op="\eqref"/>
8361
  <pat:mml op=""/>
8362
</pat:template>
8363
8364
<pat:template>
8365
  <pat:tex op="\leftroot" params="\patVAR!{shift}"/>
8366
  <pat:mml op=""/>
8367
</pat:template>
8368
8369
<pat:template>
8370
  <pat:tex op="\uproot" params="\patVAR!{shift}"/>
8371
  <pat:mml op=""/>
8372
</pat:template>
8373
8374
<pat:template>
8375
  <pat:tex op="\nobreakdash"/>
8376
  <pat:mml op="">
8377
    <mo> &#x2011; </mo>
8378
  </pat:mml>
8379
</pat:template>
8380
8381
<pat:template>
8382
  <pat:tex op="\boxed" params="\patVAR!{formula}"/>
8383
  <pat:mml op="">
8384
    <mtable frame="solid">
8385
      <mtr>
8386
        <mtd>
8387
          <pat:variable name="formula"/>
8388
        </mtd>
8389
      </mtr>
8390
    </mtable>
8391
  </pat:mml>
8392
</pat:template>
8393
8394
<pat:template>
8395
  <pat:tex op="\overset" params="\patVAR!{over}\patVAR!{base}"/>
8396
  <pat:mml op="">
8397
    <mover>
8398
     <pat:variable name="base"/>
8399
     <pat:variable name="over"/>
8400
    </mover>
8401
  </pat:mml>
8402
</pat:template>
8403
8404
<pat:template>
8405
  <pat:tex op="\underset" params="\patVAR!{under}\patVAR!{base}"/>
8406
  <pat:mml op="">
8407
    <munder>
8408
     <pat:variable name="base"/>
8409
     <pat:variable name="under"/>
8410
    </munder>
8411
  </pat:mml>
8412
</pat:template>
8413
8414
<pat:template>
8415
  <pat:tex op="\lvert"/>
8416
  <pat:mml op="">
8417
    <mo form="prefix"> | </mo>
8418
  </pat:mml>
8419
</pat:template>
8420
8421
<pat:template>
8422
  <pat:tex op="\rvert"/>
8423
  <pat:mml op="">
8424
    <mo form="postfix"> | </mo>
8425
  </pat:mml>
8426
</pat:template>
8427
8428
<pat:template>
8429
  <pat:tex op="\lVert"/>
8430
  <pat:mml op="">
8431
    <mo form="prefix"> &#x2016; </mo>
8432
  </pat:mml>
8433
</pat:template>
8434
8435
<pat:template>
8436
  <pat:tex op="\rVert"/>
8437
  <pat:mml op="">
8438
    <mo form="postfix"> &#x2016; </mo>
8439
  </pat:mml>
8440
</pat:template>
8441
8442
<pat:template>
8443
  <pat:tex op="\abs" params="\patVAR!{formula}"/>
8444
  <pat:mml op="">
8445
    <mfenced open="|" close="|" separators="">
8446
      <pat:variable name="formula"/>
8447
    </mfenced>
8448
  </pat:mml>
8449
</pat:template>
8450
8451
<pat:template>
8452
  <pat:tex op="\norm" params="\patVAR!{formula}"/>
8453
  <pat:mml op="">
8454
    <mfenced open="&#x2016;" close="&#x2016;" separators="">
8455
      <pat:variable name="formula"/>
8456
    </mfenced>
8457
  </pat:mml>
8458
</pat:template>
8459
8460
<pat:template>
8461
  <pat:tex op="\DeclareMathOperator" params="\patVAR!{name}\patVAR!{def}"/>
8462
  <pat:mml op=""/>
8463
</pat:template>
8464
8465
<pat:template>
8466
  <pat:tex op="\DeclareMathOperator" params="*\patVAR!{name}\patVAR!{def}"/>
8467
  <pat:mml op=""/>
8468
</pat:template>
8469
8470
<pat:template>
8471
  <pat:tex op="\sideset" params="{\patVAR*{sub}}{\patVAR*{sup}}\patVAR!{symbol}"/>
8472
  <pat:mml op="">
8473
    <msubsup>
8474
      <pat:variable name="symbol"/>
8475
      <pat:variable name="sub"/>
8476
      <pat:variable name="sup"/>
8477
    </msubsup>
8478
  </pat:mml>
8479
</pat:template>
8480
8481
8482
</pat:tex2mmlmap>
84831