Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
D .classpath
1
<?xml version="1.0" encoding="UTF-8"?>
2
<classpath>
3
	<classpathentry kind="src" path="src/main/java">
4
		<attributes>
5
			<attribute name="FROM_GRADLE_MODEL" value="true"/>
6
		</attributes>
7
	</classpathentry>
8
	<classpathentry kind="src" path="src/main/resources">
9
		<attributes>
10
			<attribute name="FROM_GRADLE_MODEL" value="true"/>
11
		</attributes>
12
	</classpathentry>
13
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
14
		<accessrules>
15
			<accessrule kind="accessible" pattern="javafx/**"/>
16
		</accessrules>
17
	</classpathentry>
18
	<classpathentry exported="true" kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
19
	<classpathentry kind="output" path="bin"/>
20
</classpath>
211
M .gitattributes
1616
*.zip binary
1717
*.ttf binary
18
*.otf binary
19
*.blend binary
1820
1921
M .gitignore
44
build
55
.gradle
6
contacted.csv
7
video
8
.settings
9
.classpath
610
M .idea/compiler.xml
44
    <bytecodeTargetLevel target="14" />
55
  </component>
6
  <component name="JavacSettings">
7
    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
8
      <module name="scrivenvar.main" options="--add-exports java.desktop/sun.swing=ALL-UNNAMED" />
9
    </option>
10
  </component>
611
</project>
D .settings/org.eclipse.buildship.core.prefs
1
GRADLE_BUILD_COMMANDS=org.eclipse.jdt.core.javabuilder
2
GRADLE_NATURES=org.eclipse.jdt.core.javanature
3
build.commands=org.eclipse.jdt.core.javabuilder
4
connection.arguments=
5
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
6
connection.gradle.user.home=null
7
connection.java.home=null
8
connection.jvm.arguments=
9
connection.project.dir=
10
derived.resources=.gradle,build
11
eclipse.preferences.version=1
12
natures=org.eclipse.jdt.core.javanature
13
project.path=\:
141
D .settings/org.eclipse.core.runtime.prefs
1
eclipse.preferences.version=1
2
line.separator=\n
31
D .settings/org.eclipse.jdt.core.prefs
1
eclipse.preferences.version=1
2
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
4
org.eclipse.jdt.core.compiler.compliance=1.8
5
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
6
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
7
org.eclipse.jdt.core.compiler.source=1.8
81
D .settings/org.eclipse.jdt.ui.prefs
1
eclipse.preferences.version=1
2
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
3
org.eclipse.jdt.ui.ignorelowercasenames=true
4
org.eclipse.jdt.ui.importorder=java;javafx;javax;org;com;
5
org.eclipse.jdt.ui.ondemandthreshold=99
6
org.eclipse.jdt.ui.staticondemandthreshold=99
7
sp_cleanup.add_default_serial_version_id=true
8
sp_cleanup.add_generated_serial_version_id=false
9
sp_cleanup.add_missing_annotations=true
10
sp_cleanup.add_missing_deprecated_annotations=true
11
sp_cleanup.add_missing_methods=false
12
sp_cleanup.add_missing_nls_tags=false
13
sp_cleanup.add_missing_override_annotations=true
14
sp_cleanup.add_missing_override_annotations_interface_methods=true
15
sp_cleanup.add_serial_version_id=false
16
sp_cleanup.always_use_blocks=true
17
sp_cleanup.always_use_parentheses_in_expressions=false
18
sp_cleanup.always_use_this_for_non_static_field_access=false
19
sp_cleanup.always_use_this_for_non_static_method_access=false
20
sp_cleanup.convert_functional_interfaces=false
21
sp_cleanup.convert_to_enhanced_for_loop=false
22
sp_cleanup.correct_indentation=false
23
sp_cleanup.format_source_code=false
24
sp_cleanup.format_source_code_changes_only=false
25
sp_cleanup.insert_inferred_type_arguments=false
26
sp_cleanup.make_local_variable_final=false
27
sp_cleanup.make_parameters_final=false
28
sp_cleanup.make_private_fields_final=true
29
sp_cleanup.make_type_abstract_if_missing_method=false
30
sp_cleanup.make_variable_declarations_final=true
31
sp_cleanup.never_use_blocks=false
32
sp_cleanup.never_use_parentheses_in_expressions=true
33
sp_cleanup.on_save_use_additional_actions=true
34
sp_cleanup.organize_imports=false
35
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
36
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
37
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
38
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
39
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
40
sp_cleanup.remove_private_constructors=true
41
sp_cleanup.remove_redundant_type_arguments=true
42
sp_cleanup.remove_trailing_whitespaces=true
43
sp_cleanup.remove_trailing_whitespaces_all=true
44
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
45
sp_cleanup.remove_unnecessary_casts=true
46
sp_cleanup.remove_unnecessary_nls_tags=false
47
sp_cleanup.remove_unused_imports=true
48
sp_cleanup.remove_unused_local_variables=false
49
sp_cleanup.remove_unused_private_fields=true
50
sp_cleanup.remove_unused_private_members=false
51
sp_cleanup.remove_unused_private_methods=true
52
sp_cleanup.remove_unused_private_types=true
53
sp_cleanup.sort_members=false
54
sp_cleanup.sort_members_all=false
55
sp_cleanup.use_anonymous_class_creation=false
56
sp_cleanup.use_blocks=false
57
sp_cleanup.use_blocks_only_for_return_and_throw=false
58
sp_cleanup.use_lambda=true
59
sp_cleanup.use_parentheses_in_expressions=false
60
sp_cleanup.use_this_for_non_static_field_access=false
61
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
62
sp_cleanup.use_this_for_non_static_method_access=false
63
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
64
sp_cleanup.use_type_arguments=false
651
M README.md
5959
## License
6060
61
This software is licensed under the [BSD 2-Clause License](LICENSE.md).
61
This software is licensed under the [BSD 2-Clause License](LICENSE.md) and
62
derived heavily from [Markdown-Writer-FX](licenses/MARKDOWN-WRITER-FX.md).
63
6264
M build.gradle
1919
}
2020
21
// Assume an überjar unless targetOs is set.
21
// Assume a cross-platform überjar unless targetOs is set.
2222
String[] os = ["win", "mac", "linux"]
2323
...
5656
5757
  // YAML
58
  implementation 'com.fasterxml.jackson.core:jackson-core:2.11.0'
59
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0'
60
  implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.0'
61
  implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.0'
58
  implementation 'com.fasterxml.jackson.core:jackson-core:2.11.2'
59
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.2'
60
  implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.2'
61
  implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.2'
6262
  implementation 'org.yaml:snakeyaml:1.26'
6363
M installer
136136
137137
readonly SCRIPT_SRC="\$(dirname "\${BASH_SOURCE[\${#BASH_SOURCE[@]} - 1]}")"
138
readonly SCRIPT_DIR="\$(cd "\${SCRIPT_SRC}" >/dev/null 2>&1 && pwd)"
139138
140
"\${SCRIPT_DIR}/${ARG_JAVA_DIR}/bin/java.exe" -jar "\${SCRIPT_DIR}/${FILE_APP_JAR}" "\$@"
139
"\${SCRIPT_SRC}/${ARG_JAVA_DIR}/bin/java" -jar "\${SCRIPT_SRC}/${FILE_APP_JAR}" "\$@" 2>&1 >/dev/null &
141140
__EOT
142141
M src/main/java/com/scrivenvar/AbstractFileFactory.java
2828
package com.scrivenvar;
2929
30
import com.scrivenvar.predicates.files.FileTypePredicate;
3130
import com.scrivenvar.service.Settings;
3231
3332
import java.nio.file.Path;
34
import java.util.Iterator;
35
import java.util.List;
3633
3734
import static com.scrivenvar.Constants.GLOB_PREFIX_FILE;
35
import static com.scrivenvar.Constants.SETTINGS;
3836
import static com.scrivenvar.FileType.UNKNOWN;
37
import static com.scrivenvar.predicates.PredicateFactory.createFileTypePredicate;
3938
import static java.lang.String.format;
4039
...
4746
  private static final String MSG_UNKNOWN_FILE_TYPE =
4847
      "Unknown type '%s' for file '%s'.";
49
50
  private final Settings mSettings = Services.load( Settings.class );
5148
5249
  /**
...
7471
    assert prefix != null;
7572
76
    final Settings properties = getSettings();
77
    final Iterator<String> keys = properties.getKeys( prefix );
73
    final var settings = getSettings();
74
    final var keys = settings.getKeys( prefix );
7875
79
    boolean found = false;
80
    FileType fileType = UNKNOWN;
76
    var found = false;
77
    var fileType = UNKNOWN;
8178
8279
    while( keys.hasNext() && !found ) {
83
      final String key = keys.next();
84
      final List<String> patterns = properties.getStringSettingList( key );
85
      final FileTypePredicate predicate = new FileTypePredicate( patterns );
80
      final var key = keys.next();
81
      final var patterns = settings.getStringSettingList( key );
82
      final var predicate = createFileTypePredicate( patterns );
8683
8784
      if( found = predicate.test( path.toFile() ) ) {
...
114111
   */
115112
  private Settings getSettings() {
116
    return this.mSettings;
113
    return SETTINGS;
117114
  }
118115
}
M src/main/java/com/scrivenvar/Constants.java
3838
public class Constants {
3939
40
  private static final Settings SETTINGS = Services.load( Settings.class );
40
  public final static Settings SETTINGS = Services.load( Settings.class );
4141
4242
  /**
...
5858
  public static final String SETTINGS_NAME =
5959
      "/com/scrivenvar/settings.properties";
60
61
  public static final String DEFINITION_NAME = "variables.yaml";
6062
6163
  public static final String APP_TITLE = get( "application.title" );
...
140142
  public static final String PARAGRAPH_ID_PREFIX = "p-";
141143
144
  /**
145
   * Absolute location of true type font files within the Java archive file.
146
   */
142147
  public static final String FONT_DIRECTORY = "/fonts";
148
149
  /**
150
   * Default text editor font size, in points.
151
   */
152
  public static final int FONT_SIZE_EDITOR = 12;
143153
}
144154
M src/main/java/com/scrivenvar/FileEditorTabPane.java
2828
package com.scrivenvar;
2929
30
import com.scrivenvar.predicates.files.FileTypePredicate;
31
import com.scrivenvar.service.Options;
32
import com.scrivenvar.service.Settings;
33
import com.scrivenvar.service.events.Notification;
34
import com.scrivenvar.service.events.Notifier;
35
import com.scrivenvar.util.Utils;
36
import javafx.beans.property.ReadOnlyBooleanProperty;
37
import javafx.beans.property.ReadOnlyBooleanWrapper;
38
import javafx.beans.property.ReadOnlyObjectProperty;
39
import javafx.beans.property.ReadOnlyObjectWrapper;
40
import javafx.beans.value.ChangeListener;
41
import javafx.collections.ListChangeListener;
42
import javafx.collections.ObservableList;
43
import javafx.event.Event;
44
import javafx.scene.Node;
45
import javafx.scene.control.Alert;
46
import javafx.scene.control.ButtonType;
47
import javafx.scene.control.Tab;
48
import javafx.scene.control.TabPane;
49
import javafx.stage.FileChooser;
50
import javafx.stage.FileChooser.ExtensionFilter;
51
import javafx.stage.Window;
52
53
import java.io.File;
54
import java.nio.file.Path;
55
import java.util.ArrayList;
56
import java.util.List;
57
import java.util.Optional;
58
import java.util.concurrent.atomic.AtomicReference;
59
import java.util.prefs.Preferences;
60
import java.util.stream.Collectors;
61
62
import static com.scrivenvar.Constants.GLOB_PREFIX_FILE;
63
import static com.scrivenvar.FileType.*;
64
import static com.scrivenvar.Messages.get;
65
import static com.scrivenvar.service.events.Notifier.YES;
66
67
/**
68
 * Tab pane for file editors.
69
 */
70
public final class FileEditorTabPane extends TabPane {
71
72
  private final static String FILTER_EXTENSION_TITLES =
73
      "Dialog.file.choose.filter";
74
75
  private final static Options sOptions = Services.load( Options.class );
76
  private final static Settings sSettings = Services.load( Settings.class );
77
  private final static Notifier sNotifier = Services.load( Notifier.class );
78
79
  private final ReadOnlyObjectWrapper<Path> mOpenDefinition =
80
      new ReadOnlyObjectWrapper<>();
81
  private final ReadOnlyObjectWrapper<FileEditorTab> mActiveFileEditor =
82
      new ReadOnlyObjectWrapper<>();
83
  private final ReadOnlyBooleanWrapper mAnyFileEditorModified =
84
      new ReadOnlyBooleanWrapper();
85
  private final ChangeListener<Integer> mCaretPositionListener;
86
  private final ChangeListener<Integer> mCaretParagraphListener;
87
88
  /**
89
   * Constructs a new file editor tab pane.
90
   *
91
   * @param caretPositionListener  Listens for changes to caret position so
92
   *                               that the status bar can update.
93
   * @param caretParagraphListener Listens for changes to the caret's paragraph
94
   *                               so that scrolling may occur.
95
   */
96
  public FileEditorTabPane(
97
      final ChangeListener<Integer> caretPositionListener,
98
      final ChangeListener<Integer> caretParagraphListener ) {
99
    final ObservableList<Tab> tabs = getTabs();
100
101
    setFocusTraversable( false );
102
    setTabClosingPolicy( TabClosingPolicy.ALL_TABS );
103
104
    addTabSelectionListener(
105
        ( tabPane, oldTab, newTab ) -> {
106
          if( newTab != null ) {
107
            mActiveFileEditor.set( (FileEditorTab) newTab );
108
          }
109
        }
110
    );
111
112
    final ChangeListener<Boolean> modifiedListener =
113
        ( observable, oldValue, newValue ) -> {
114
          for( final Tab tab : tabs ) {
115
            if( ((FileEditorTab) tab).isModified() ) {
116
              mAnyFileEditorModified.set( true );
117
              break;
118
            }
119
          }
120
        };
121
122
    tabs.addListener(
123
        (ListChangeListener<Tab>) change -> {
124
          while( change.next() ) {
125
            if( change.wasAdded() ) {
126
              change.getAddedSubList().forEach(
127
                  ( tab ) -> {
128
                    final var fet = (FileEditorTab) tab;
129
                    fet.modifiedProperty()
130
                       .addListener( modifiedListener );
131
                  } );
132
            }
133
            else if( change.wasRemoved() ) {
134
              change.getRemoved().forEach(
135
                  ( tab ) ->
136
                      ((FileEditorTab) tab).modifiedProperty()
137
                                           .removeListener( modifiedListener ) );
138
            }
139
          }
140
141
          // Changes in the tabs may also change anyFileEditorModified property
142
          // (e.g. closed modified file)
143
          modifiedListener.changed( null, null, null );
144
        }
145
    );
146
147
    mCaretPositionListener = caretPositionListener;
148
    mCaretParagraphListener = caretParagraphListener;
149
  }
150
151
  /**
152
   * Allows observers to be notified when the current file editor tab changes.
153
   *
154
   * @param listener The listener to notify of tab change events.
155
   */
156
  public void addTabSelectionListener( final ChangeListener<Tab> listener ) {
157
    // Observe the tab so that when a new tab is opened or selected,
158
    // a notification is kicked off.
159
    getSelectionModel().selectedItemProperty().addListener( listener );
160
  }
161
162
  /**
163
   * Returns the tab that has keyboard focus.
164
   *
165
   * @return A non-null instance.
166
   */
167
  public FileEditorTab getActiveFileEditor() {
168
    return mActiveFileEditor.get();
169
  }
170
171
  /**
172
   * Returns the property corresponding to the tab that has focus.
173
   *
174
   * @return A non-null instance.
175
   */
176
  public ReadOnlyObjectProperty<FileEditorTab> activeFileEditorProperty() {
177
    return mActiveFileEditor.getReadOnlyProperty();
178
  }
179
180
  /**
181
   * Property that can answer whether the text has been modified.
182
   *
183
   * @return A non-null instance, true meaning the content has not been saved.
184
   */
185
  ReadOnlyBooleanProperty anyFileEditorModifiedProperty() {
186
    return mAnyFileEditorModified.getReadOnlyProperty();
187
  }
188
189
  /**
190
   * Creates a new editor instance from the given path.
191
   *
192
   * @param path The file to open.
193
   * @return A non-null instance.
194
   */
195
  private FileEditorTab createFileEditor( final Path path ) {
196
    assert path != null;
197
198
    final FileEditorTab tab = new FileEditorTab( path );
199
200
    tab.setOnCloseRequest( e -> {
201
      if( !canCloseEditor( tab ) ) {
202
        e.consume();
203
      }
204
      else if( isActiveFileEditor( tab ) ) {
205
        // Prevent prompting the user to save when there are no file editor
206
        // tabs open.
207
        mActiveFileEditor.set( null );
208
      }
209
    } );
210
211
    tab.addCaretPositionListener( mCaretPositionListener );
212
    tab.addCaretParagraphListener( mCaretParagraphListener );
213
214
    return tab;
215
  }
216
217
  private boolean isActiveFileEditor( final FileEditorTab tab ) {
218
    return getActiveFileEditor() == tab;
219
  }
220
221
  private Path getDefaultPath() {
222
    final String filename = getDefaultFilename();
223
    return (new File( filename )).toPath();
224
  }
225
226
  private String getDefaultFilename() {
227
    return getSettings().getSetting( "file.default", "untitled.md" );
228
  }
229
230
  /**
231
   * Called to add a new {@link FileEditorTab} to the tab pane.
232
   */
233
  void newEditor() {
234
    final FileEditorTab tab = createFileEditor( getDefaultPath() );
235
236
    getTabs().add( tab );
237
    getSelectionModel().select( tab );
238
  }
239
240
  void openFileDialog() {
241
    final String title = get( "Dialog.file.choose.open.title" );
242
    final FileChooser dialog = createFileChooser( title );
243
    final List<File> files = dialog.showOpenMultipleDialog( getWindow() );
244
245
    if( files != null ) {
246
      openFiles( files );
247
    }
248
  }
249
250
  /**
251
   * Opens the files into new editors, unless one of those files was a
252
   * definition file. The definition file is loaded into the definition pane,
253
   * but only the first one selected (multiple definition files will result in a
254
   * warning).
255
   *
256
   * @param files The list of non-definition files that the were requested to
257
   *              open.
258
   */
259
  private void openFiles( final List<File> files ) {
260
    final List<String> extensions =
261
        createExtensionFilter( DEFINITION ).getExtensions();
262
    final FileTypePredicate predicate =
263
        new FileTypePredicate( extensions );
264
265
    // The user might have opened multiple definitions files. These will
266
    // be discarded from the text editable files.
267
    final List<File> definitions
268
        = files.stream().filter( predicate ).collect( Collectors.toList() );
269
270
    // Create a modifiable list to remove any definition files that were
271
    // opened.
272
    final List<File> editors = new ArrayList<>( files );
273
274
    if( !editors.isEmpty() ) {
275
      saveLastDirectory( editors.get( 0 ) );
276
    }
277
278
    editors.removeAll( definitions );
279
280
    // Open editor-friendly files (e.g,. Markdown, XML) in new tabs.
281
    if( !editors.isEmpty() ) {
282
      openEditors( editors, 0 );
283
    }
284
285
    if( !definitions.isEmpty() ) {
286
      openDefinition( definitions.get( 0 ) );
287
    }
288
  }
289
290
  private void openEditors( final List<File> files, final int activeIndex ) {
291
    final int fileTally = files.size();
292
    final List<Tab> tabs = getTabs();
293
294
    // Close single unmodified "Untitled" tab.
295
    if( tabs.size() == 1 ) {
296
      final FileEditorTab fileEditor = (FileEditorTab) (tabs.get( 0 ));
297
298
      if( fileEditor.getPath() == null && !fileEditor.isModified() ) {
299
        closeEditor( fileEditor, false );
300
      }
301
    }
302
303
    for( int i = 0; i < fileTally; i++ ) {
304
      final Path path = files.get( i ).toPath();
305
306
      FileEditorTab fileEditorTab = findEditor( path );
307
308
      // Only open new files.
309
      if( fileEditorTab == null ) {
310
        fileEditorTab = createFileEditor( path );
311
        getTabs().add( fileEditorTab );
312
      }
313
314
      // Select the first file in the list.
315
      if( i == activeIndex ) {
316
        getSelectionModel().select( fileEditorTab );
317
      }
318
    }
319
  }
320
321
  /**
322
   * Returns a property that changes when a new definition file is opened.
323
   *
324
   * @return The path to a definition file that was opened.
325
   */
326
  public ReadOnlyObjectProperty<Path> onOpenDefinitionFileProperty() {
327
    return getOnOpenDefinitionFile().getReadOnlyProperty();
328
  }
329
330
  private ReadOnlyObjectWrapper<Path> getOnOpenDefinitionFile() {
331
    return mOpenDefinition;
332
  }
333
334
  /**
335
   * Called when the user has opened a definition file (using the file open
336
   * dialog box). This will replace the current set of definitions for the
337
   * active tab.
338
   *
339
   * @param definition The file to open.
340
   */
341
  private void openDefinition( final File definition ) {
342
    // TODO: Prevent reading this file twice when a new text document is opened.
343
    // (might be a matter of checking the value first).
344
    getOnOpenDefinitionFile().set( definition.toPath() );
345
  }
346
347
  /**
348
   * Called when the contents of the editor are to be saved.
349
   *
350
   * @param tab The tab containing content to save.
351
   * @return true The contents were saved (or needn't be saved).
352
   */
353
  public boolean saveEditor( final FileEditorTab tab ) {
354
    if( tab == null || !tab.isModified() ) {
355
      return true;
356
    }
357
358
    return tab.getPath() == null ? saveEditorAs( tab ) : tab.save();
359
  }
360
361
  /**
362
   * Opens the Save As dialog for the user to save the content under a new
363
   * path.
364
   *
365
   * @param tab The tab with contents to save.
366
   * @return true The contents were saved, or the tab was null.
367
   */
368
  public boolean saveEditorAs( final FileEditorTab tab ) {
369
    if( tab == null ) {
370
      return true;
371
    }
372
373
    getSelectionModel().select( tab );
374
375
    final FileChooser fileChooser = createFileChooser( get(
376
        "Dialog.file.choose.save.title" ) );
377
    final File file = fileChooser.showSaveDialog( getWindow() );
378
    if( file == null ) {
379
      return false;
380
    }
381
382
    saveLastDirectory( file );
383
    tab.setPath( file.toPath() );
384
385
    return tab.save();
386
  }
387
388
  void saveAllEditors() {
389
    for( final FileEditorTab fileEditor : getAllEditors() ) {
390
      saveEditor( fileEditor );
391
    }
392
  }
393
394
  /**
395
   * Answers whether the file has had modifications. '
396
   *
397
   * @param tab THe tab to check for modifications.
398
   * @return false The file is unmodified.
399
   */
400
  @SuppressWarnings("BooleanMethodIsAlwaysInverted")
401
  boolean canCloseEditor( final FileEditorTab tab ) {
402
    final AtomicReference<Boolean> canClose = new AtomicReference<>();
403
    canClose.set( true );
404
405
    if( tab.isModified() ) {
406
      final Notification message = getNotifyService().createNotification(
407
          Messages.get( "Alert.file.close.title" ),
408
          Messages.get( "Alert.file.close.text" ),
409
          tab.getText()
410
      );
411
412
      final Alert confirmSave = getNotifyService().createConfirmation(
413
          getWindow(), message );
414
415
      final Optional<ButtonType> buttonType = confirmSave.showAndWait();
416
417
      buttonType.ifPresent(
418
          save -> canClose.set(
419
              save == YES ? saveEditor( tab ) : save == ButtonType.NO
420
          )
421
      );
422
    }
423
424
    return canClose.get();
425
  }
426
427
  boolean closeEditor( final FileEditorTab tab, final boolean save ) {
428
    if( tab == null ) {
429
      return true;
430
    }
431
432
    if( save ) {
433
      Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT );
434
      Event.fireEvent( tab, event );
435
436
      if( event.isConsumed() ) {
437
        return false;
438
      }
439
    }
440
441
    getTabs().remove( tab );
442
443
    if( tab.getOnClosed() != null ) {
444
      Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) );
445
    }
446
447
    return true;
448
  }
449
450
  boolean closeAllEditors() {
451
    final FileEditorTab[] allEditors = getAllEditors();
452
    final FileEditorTab activeEditor = getActiveFileEditor();
453
454
    // try to save active tab first because in case the user decides to cancel,
455
    // then it stays active
456
    if( activeEditor != null && !canCloseEditor( activeEditor ) ) {
457
      return false;
458
    }
459
460
    // This should be called any time a tab changes.
461
    persistPreferences();
462
463
    // save modified tabs
464
    for( int i = 0; i < allEditors.length; i++ ) {
465
      final FileEditorTab fileEditor = allEditors[ i ];
466
467
      if( fileEditor == activeEditor ) {
468
        continue;
469
      }
470
471
      if( fileEditor.isModified() ) {
472
        // activate the modified tab to make its modified content visible to
473
        // the user
474
        getSelectionModel().select( i );
475
476
        if( !canCloseEditor( fileEditor ) ) {
477
          return false;
478
        }
479
      }
480
    }
481
482
    // Close all tabs.
483
    for( final FileEditorTab fileEditor : allEditors ) {
484
      if( !closeEditor( fileEditor, false ) ) {
485
        return false;
486
      }
487
    }
488
489
    return getTabs().isEmpty();
490
  }
491
492
  private FileEditorTab[] getAllEditors() {
493
    final ObservableList<Tab> tabs = getTabs();
494
    final int length = tabs.size();
495
    final FileEditorTab[] allEditors = new FileEditorTab[ length ];
496
497
    for( int i = 0; i < length; i++ ) {
498
      allEditors[ i ] = (FileEditorTab) tabs.get( i );
499
    }
500
501
    return allEditors;
502
  }
503
504
  /**
505
   * Returns the file editor tab that has the given path.
506
   *
507
   * @return null No file editor tab for the given path was found.
508
   */
509
  private FileEditorTab findEditor( final Path path ) {
510
    for( final Tab tab : getTabs() ) {
511
      final FileEditorTab fileEditor = (FileEditorTab) tab;
512
513
      if( fileEditor.isPath( path ) ) {
514
        return fileEditor;
515
      }
516
    }
517
518
    return null;
519
  }
520
521
  private FileChooser createFileChooser( String title ) {
522
    final FileChooser fileChooser = new FileChooser();
523
524
    fileChooser.setTitle( title );
525
    fileChooser.getExtensionFilters().addAll(
526
        createExtensionFilters() );
527
528
    final String lastDirectory = getPreferences().get( "lastDirectory", null );
529
    File file = new File( (lastDirectory != null) ? lastDirectory : "." );
530
531
    if( !file.isDirectory() ) {
532
      file = new File( "." );
533
    }
534
535
    fileChooser.setInitialDirectory( file );
536
    return fileChooser;
537
  }
538
539
  private List<ExtensionFilter> createExtensionFilters() {
540
    final List<ExtensionFilter> list = new ArrayList<>();
541
542
    // TODO: Return a list of all properties that match the filter prefix.
543
    // This will allow dynamic filters to be added and removed just by
544
    // updating the properties file.
545
    list.add( createExtensionFilter( ALL ) );
546
    list.add( createExtensionFilter( SOURCE ) );
547
    list.add( createExtensionFilter( DEFINITION ) );
548
    list.add( createExtensionFilter( XML ) );
549
    return list;
550
  }
551
552
  /**
553
   * Returns a filter for file name extensions recognized by the application
554
   * that can be opened by the user.
555
   *
556
   * @param filetype Used to find the globbing pattern for extensions.
557
   * @return A filename filter suitable for use by a FileDialog instance.
558
   */
559
  private ExtensionFilter createExtensionFilter( final FileType filetype ) {
560
    final String tKey = String.format( "%s.title.%s",
561
                                       FILTER_EXTENSION_TITLES,
562
                                       filetype );
563
    final String eKey = String.format( "%s.%s", GLOB_PREFIX_FILE, filetype );
564
565
    return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) );
566
  }
567
568
  private void saveLastDirectory( final File file ) {
569
    getPreferences().put( "lastDirectory", file.getParent() );
570
  }
571
572
  public void initPreferences() {
573
    int activeIndex = 0;
574
575
    final Preferences preferences = getPreferences();
576
    final String[] fileNames = Utils.getPrefsStrings( preferences, "file" );
577
    final String activeFileName = preferences.get( "activeFile", null );
578
579
    final List<File> files = new ArrayList<>( fileNames.length );
580
581
    for( final String fileName : fileNames ) {
582
      final File file = new File( fileName );
583
584
      if( file.exists() ) {
585
        files.add( file );
586
587
        if( fileName.equals( activeFileName ) ) {
588
          activeIndex = files.size() - 1;
589
        }
590
      }
591
    }
592
593
    if( files.isEmpty() ) {
594
      newEditor();
595
    }
596
    else {
597
      openEditors( files, activeIndex );
598
    }
599
  }
600
601
  public void persistPreferences() {
602
    final ObservableList<Tab> allEditors = getTabs();
603
    final List<String> fileNames = new ArrayList<>( allEditors.size() );
604
605
    for( final Tab tab : allEditors ) {
606
      final FileEditorTab fileEditor = (FileEditorTab) tab;
607
      final Path filePath = fileEditor.getPath();
608
609
      if( filePath != null ) {
610
        fileNames.add( filePath.toString() );
611
      }
612
    }
613
614
    final Preferences preferences = getPreferences();
615
    Utils.putPrefsStrings( preferences,
616
                           "file",
617
                           fileNames.toArray( new String[ 0 ] ) );
618
619
    final FileEditorTab activeEditor = getActiveFileEditor();
620
    final Path filePath = activeEditor == null ? null : activeEditor.getPath();
621
622
    if( filePath == null ) {
623
      preferences.remove( "activeFile" );
624
    }
625
    else {
626
      preferences.put( "activeFile", filePath.toString() );
627
    }
628
  }
629
630
  private List<String> getExtensions( final String key ) {
631
    return getSettings().getStringSettingList( key );
632
  }
633
634
  private Notifier getNotifyService() {
635
    return sNotifier;
636
  }
637
638
  private Settings getSettings() {
639
    return sSettings;
30
import com.scrivenvar.service.Options;
31
import com.scrivenvar.service.Settings;
32
import com.scrivenvar.service.events.Notification;
33
import com.scrivenvar.service.events.Notifier;
34
import com.scrivenvar.util.Utils;
35
import javafx.beans.property.ReadOnlyBooleanProperty;
36
import javafx.beans.property.ReadOnlyBooleanWrapper;
37
import javafx.beans.property.ReadOnlyObjectProperty;
38
import javafx.beans.property.ReadOnlyObjectWrapper;
39
import javafx.beans.value.ChangeListener;
40
import javafx.collections.ListChangeListener;
41
import javafx.collections.ObservableList;
42
import javafx.event.Event;
43
import javafx.scene.Node;
44
import javafx.scene.control.Alert;
45
import javafx.scene.control.ButtonType;
46
import javafx.scene.control.Tab;
47
import javafx.scene.control.TabPane;
48
import javafx.stage.FileChooser;
49
import javafx.stage.FileChooser.ExtensionFilter;
50
import javafx.stage.Window;
51
52
import java.io.File;
53
import java.nio.file.Path;
54
import java.util.ArrayList;
55
import java.util.List;
56
import java.util.Optional;
57
import java.util.concurrent.atomic.AtomicReference;
58
import java.util.prefs.Preferences;
59
import java.util.stream.Collectors;
60
61
import static com.scrivenvar.Constants.GLOB_PREFIX_FILE;
62
import static com.scrivenvar.Constants.SETTINGS;
63
import static com.scrivenvar.FileType.*;
64
import static com.scrivenvar.Messages.get;
65
import static com.scrivenvar.predicates.PredicateFactory.createFileTypePredicate;
66
import static com.scrivenvar.service.events.Notifier.YES;
67
68
/**
69
 * Tab pane for file editors.
70
 */
71
public final class FileEditorTabPane extends TabPane {
72
73
  private final static String FILTER_EXTENSION_TITLES =
74
      "Dialog.file.choose.filter";
75
76
  private final static Options sOptions = Services.load( Options.class );
77
  private final static Notifier sNotifier = Services.load( Notifier.class );
78
79
  private final ReadOnlyObjectWrapper<Path> mOpenDefinition =
80
      new ReadOnlyObjectWrapper<>();
81
  private final ReadOnlyObjectWrapper<FileEditorTab> mActiveFileEditor =
82
      new ReadOnlyObjectWrapper<>();
83
  private final ReadOnlyBooleanWrapper mAnyFileEditorModified =
84
      new ReadOnlyBooleanWrapper();
85
  private final ChangeListener<Integer> mCaretPositionListener;
86
  private final ChangeListener<Integer> mCaretParagraphListener;
87
88
  /**
89
   * Constructs a new file editor tab pane.
90
   *
91
   * @param caretPositionListener  Listens for changes to caret position so
92
   *                               that the status bar can update.
93
   * @param caretParagraphListener Listens for changes to the caret's paragraph
94
   *                               so that scrolling may occur.
95
   */
96
  public FileEditorTabPane(
97
      final ChangeListener<Integer> caretPositionListener,
98
      final ChangeListener<Integer> caretParagraphListener ) {
99
    final ObservableList<Tab> tabs = getTabs();
100
101
    setFocusTraversable( false );
102
    setTabClosingPolicy( TabClosingPolicy.ALL_TABS );
103
104
    addTabSelectionListener(
105
        ( tabPane, oldTab, newTab ) -> {
106
          if( newTab != null ) {
107
            mActiveFileEditor.set( (FileEditorTab) newTab );
108
          }
109
        }
110
    );
111
112
    final ChangeListener<Boolean> modifiedListener =
113
        ( observable, oldValue, newValue ) -> {
114
          for( final Tab tab : tabs ) {
115
            if( ((FileEditorTab) tab).isModified() ) {
116
              mAnyFileEditorModified.set( true );
117
              break;
118
            }
119
          }
120
        };
121
122
    tabs.addListener(
123
        (ListChangeListener<Tab>) change -> {
124
          while( change.next() ) {
125
            if( change.wasAdded() ) {
126
              change.getAddedSubList().forEach(
127
                  ( tab ) -> {
128
                    final var fet = (FileEditorTab) tab;
129
                    fet.modifiedProperty()
130
                       .addListener( modifiedListener );
131
                  } );
132
            }
133
            else if( change.wasRemoved() ) {
134
              change.getRemoved().forEach(
135
                  ( tab ) ->
136
                      ((FileEditorTab) tab).modifiedProperty()
137
                                           .removeListener( modifiedListener ) );
138
            }
139
          }
140
141
          // Changes in the tabs may also change anyFileEditorModified property
142
          // (e.g. closed modified file)
143
          modifiedListener.changed( null, null, null );
144
        }
145
    );
146
147
    mCaretPositionListener = caretPositionListener;
148
    mCaretParagraphListener = caretParagraphListener;
149
  }
150
151
  /**
152
   * Allows observers to be notified when the current file editor tab changes.
153
   *
154
   * @param listener The listener to notify of tab change events.
155
   */
156
  public void addTabSelectionListener( final ChangeListener<Tab> listener ) {
157
    // Observe the tab so that when a new tab is opened or selected,
158
    // a notification is kicked off.
159
    getSelectionModel().selectedItemProperty().addListener( listener );
160
  }
161
162
  /**
163
   * Returns the tab that has keyboard focus.
164
   *
165
   * @return A non-null instance.
166
   */
167
  public FileEditorTab getActiveFileEditor() {
168
    return mActiveFileEditor.get();
169
  }
170
171
  /**
172
   * Returns the property corresponding to the tab that has focus.
173
   *
174
   * @return A non-null instance.
175
   */
176
  public ReadOnlyObjectProperty<FileEditorTab> activeFileEditorProperty() {
177
    return mActiveFileEditor.getReadOnlyProperty();
178
  }
179
180
  /**
181
   * Property that can answer whether the text has been modified.
182
   *
183
   * @return A non-null instance, true meaning the content has not been saved.
184
   */
185
  ReadOnlyBooleanProperty anyFileEditorModifiedProperty() {
186
    return mAnyFileEditorModified.getReadOnlyProperty();
187
  }
188
189
  /**
190
   * Creates a new editor instance from the given path.
191
   *
192
   * @param path The file to open.
193
   * @return A non-null instance.
194
   */
195
  private FileEditorTab createFileEditor( final Path path ) {
196
    assert path != null;
197
198
    final FileEditorTab tab = new FileEditorTab( path );
199
200
    tab.setOnCloseRequest( e -> {
201
      if( !canCloseEditor( tab ) ) {
202
        e.consume();
203
      }
204
      else if( isActiveFileEditor( tab ) ) {
205
        // Prevent prompting the user to save when there are no file editor
206
        // tabs open.
207
        mActiveFileEditor.set( null );
208
      }
209
    } );
210
211
    tab.addCaretPositionListener( mCaretPositionListener );
212
    tab.addCaretParagraphListener( mCaretParagraphListener );
213
214
    return tab;
215
  }
216
217
  private boolean isActiveFileEditor( final FileEditorTab tab ) {
218
    return getActiveFileEditor() == tab;
219
  }
220
221
  private Path getDefaultPath() {
222
    final String filename = getDefaultFilename();
223
    return (new File( filename )).toPath();
224
  }
225
226
  private String getDefaultFilename() {
227
    return getSettings().getSetting( "file.default", "untitled.md" );
228
  }
229
230
  /**
231
   * Called to add a new {@link FileEditorTab} to the tab pane.
232
   */
233
  void newEditor() {
234
    final FileEditorTab tab = createFileEditor( getDefaultPath() );
235
236
    getTabs().add( tab );
237
    getSelectionModel().select( tab );
238
  }
239
240
  void openFileDialog() {
241
    final String title = get( "Dialog.file.choose.open.title" );
242
    final FileChooser dialog = createFileChooser( title );
243
    final List<File> files = dialog.showOpenMultipleDialog( getWindow() );
244
245
    if( files != null ) {
246
      openFiles( files );
247
    }
248
  }
249
250
  /**
251
   * Opens the files into new editors, unless one of those files was a
252
   * definition file. The definition file is loaded into the definition pane,
253
   * but only the first one selected (multiple definition files will result in a
254
   * warning).
255
   *
256
   * @param files The list of non-definition files that the were requested to
257
   *              open.
258
   */
259
  private void openFiles( final List<File> files ) {
260
    final List<String> extensions =
261
        createExtensionFilter( DEFINITION ).getExtensions();
262
    final var predicate = createFileTypePredicate( extensions );
263
264
    // The user might have opened multiple definitions files. These will
265
    // be discarded from the text editable files.
266
    final var definitions
267
        = files.stream().filter( predicate ).collect( Collectors.toList() );
268
269
    // Create a modifiable list to remove any definition files that were
270
    // opened.
271
    final var editors = new ArrayList<>( files );
272
273
    if( !editors.isEmpty() ) {
274
      saveLastDirectory( editors.get( 0 ) );
275
    }
276
277
    editors.removeAll( definitions );
278
279
    // Open editor-friendly files (e.g,. Markdown, XML) in new tabs.
280
    if( !editors.isEmpty() ) {
281
      openEditors( editors, 0 );
282
    }
283
284
    if( !definitions.isEmpty() ) {
285
      openDefinition( definitions.get( 0 ) );
286
    }
287
  }
288
289
  private void openEditors( final List<File> files, final int activeIndex ) {
290
    final int fileTally = files.size();
291
    final List<Tab> tabs = getTabs();
292
293
    // Close single unmodified "Untitled" tab.
294
    if( tabs.size() == 1 ) {
295
      final FileEditorTab fileEditor = (FileEditorTab) (tabs.get( 0 ));
296
297
      if( fileEditor.getPath() == null && !fileEditor.isModified() ) {
298
        closeEditor( fileEditor, false );
299
      }
300
    }
301
302
    for( int i = 0; i < fileTally; i++ ) {
303
      final Path path = files.get( i ).toPath();
304
305
      FileEditorTab fileEditorTab = findEditor( path );
306
307
      // Only open new files.
308
      if( fileEditorTab == null ) {
309
        fileEditorTab = createFileEditor( path );
310
        getTabs().add( fileEditorTab );
311
      }
312
313
      // Select the first file in the list.
314
      if( i == activeIndex ) {
315
        getSelectionModel().select( fileEditorTab );
316
      }
317
    }
318
  }
319
320
  /**
321
   * Returns a property that changes when a new definition file is opened.
322
   *
323
   * @return The path to a definition file that was opened.
324
   */
325
  public ReadOnlyObjectProperty<Path> onOpenDefinitionFileProperty() {
326
    return getOnOpenDefinitionFile().getReadOnlyProperty();
327
  }
328
329
  private ReadOnlyObjectWrapper<Path> getOnOpenDefinitionFile() {
330
    return mOpenDefinition;
331
  }
332
333
  /**
334
   * Called when the user has opened a definition file (using the file open
335
   * dialog box). This will replace the current set of definitions for the
336
   * active tab.
337
   *
338
   * @param definition The file to open.
339
   */
340
  private void openDefinition( final File definition ) {
341
    // TODO: Prevent reading this file twice when a new text document is opened.
342
    // (might be a matter of checking the value first).
343
    getOnOpenDefinitionFile().set( definition.toPath() );
344
  }
345
346
  /**
347
   * Called when the contents of the editor are to be saved.
348
   *
349
   * @param tab The tab containing content to save.
350
   * @return true The contents were saved (or needn't be saved).
351
   */
352
  public boolean saveEditor( final FileEditorTab tab ) {
353
    if( tab == null || !tab.isModified() ) {
354
      return true;
355
    }
356
357
    return tab.getPath() == null ? saveEditorAs( tab ) : tab.save();
358
  }
359
360
  /**
361
   * Opens the Save As dialog for the user to save the content under a new
362
   * path.
363
   *
364
   * @param tab The tab with contents to save.
365
   * @return true The contents were saved, or the tab was null.
366
   */
367
  public boolean saveEditorAs( final FileEditorTab tab ) {
368
    if( tab == null ) {
369
      return true;
370
    }
371
372
    getSelectionModel().select( tab );
373
374
    final FileChooser fileChooser = createFileChooser( get(
375
        "Dialog.file.choose.save.title" ) );
376
    final File file = fileChooser.showSaveDialog( getWindow() );
377
    if( file == null ) {
378
      return false;
379
    }
380
381
    saveLastDirectory( file );
382
    tab.setPath( file.toPath() );
383
384
    return tab.save();
385
  }
386
387
  void saveAllEditors() {
388
    for( final FileEditorTab fileEditor : getAllEditors() ) {
389
      saveEditor( fileEditor );
390
    }
391
  }
392
393
  /**
394
   * Answers whether the file has had modifications. '
395
   *
396
   * @param tab THe tab to check for modifications.
397
   * @return false The file is unmodified.
398
   */
399
  @SuppressWarnings("BooleanMethodIsAlwaysInverted")
400
  boolean canCloseEditor( final FileEditorTab tab ) {
401
    final AtomicReference<Boolean> canClose = new AtomicReference<>();
402
    canClose.set( true );
403
404
    if( tab.isModified() ) {
405
      final Notification message = getNotifyService().createNotification(
406
          Messages.get( "Alert.file.close.title" ),
407
          Messages.get( "Alert.file.close.text" ),
408
          tab.getText()
409
      );
410
411
      final Alert confirmSave = getNotifyService().createConfirmation(
412
          getWindow(), message );
413
414
      final Optional<ButtonType> buttonType = confirmSave.showAndWait();
415
416
      buttonType.ifPresent(
417
          save -> canClose.set(
418
              save == YES ? saveEditor( tab ) : save == ButtonType.NO
419
          )
420
      );
421
    }
422
423
    return canClose.get();
424
  }
425
426
  boolean closeEditor( final FileEditorTab tab, final boolean save ) {
427
    if( tab == null ) {
428
      return true;
429
    }
430
431
    if( save ) {
432
      Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT );
433
      Event.fireEvent( tab, event );
434
435
      if( event.isConsumed() ) {
436
        return false;
437
      }
438
    }
439
440
    getTabs().remove( tab );
441
442
    if( tab.getOnClosed() != null ) {
443
      Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) );
444
    }
445
446
    return true;
447
  }
448
449
  boolean closeAllEditors() {
450
    final FileEditorTab[] allEditors = getAllEditors();
451
    final FileEditorTab activeEditor = getActiveFileEditor();
452
453
    // try to save active tab first because in case the user decides to cancel,
454
    // then it stays active
455
    if( activeEditor != null && !canCloseEditor( activeEditor ) ) {
456
      return false;
457
    }
458
459
    // This should be called any time a tab changes.
460
    persistPreferences();
461
462
    // save modified tabs
463
    for( int i = 0; i < allEditors.length; i++ ) {
464
      final FileEditorTab fileEditor = allEditors[ i ];
465
466
      if( fileEditor == activeEditor ) {
467
        continue;
468
      }
469
470
      if( fileEditor.isModified() ) {
471
        // activate the modified tab to make its modified content visible to
472
        // the user
473
        getSelectionModel().select( i );
474
475
        if( !canCloseEditor( fileEditor ) ) {
476
          return false;
477
        }
478
      }
479
    }
480
481
    // Close all tabs.
482
    for( final FileEditorTab fileEditor : allEditors ) {
483
      if( !closeEditor( fileEditor, false ) ) {
484
        return false;
485
      }
486
    }
487
488
    return getTabs().isEmpty();
489
  }
490
491
  private FileEditorTab[] getAllEditors() {
492
    final ObservableList<Tab> tabs = getTabs();
493
    final int length = tabs.size();
494
    final FileEditorTab[] allEditors = new FileEditorTab[ length ];
495
496
    for( int i = 0; i < length; i++ ) {
497
      allEditors[ i ] = (FileEditorTab) tabs.get( i );
498
    }
499
500
    return allEditors;
501
  }
502
503
  /**
504
   * Returns the file editor tab that has the given path.
505
   *
506
   * @return null No file editor tab for the given path was found.
507
   */
508
  private FileEditorTab findEditor( final Path path ) {
509
    for( final Tab tab : getTabs() ) {
510
      final FileEditorTab fileEditor = (FileEditorTab) tab;
511
512
      if( fileEditor.isPath( path ) ) {
513
        return fileEditor;
514
      }
515
    }
516
517
    return null;
518
  }
519
520
  private FileChooser createFileChooser( String title ) {
521
    final FileChooser fileChooser = new FileChooser();
522
523
    fileChooser.setTitle( title );
524
    fileChooser.getExtensionFilters().addAll(
525
        createExtensionFilters() );
526
527
    final String lastDirectory = getPreferences().get( "lastDirectory", null );
528
    File file = new File( (lastDirectory != null) ? lastDirectory : "." );
529
530
    if( !file.isDirectory() ) {
531
      file = new File( "." );
532
    }
533
534
    fileChooser.setInitialDirectory( file );
535
    return fileChooser;
536
  }
537
538
  private List<ExtensionFilter> createExtensionFilters() {
539
    final List<ExtensionFilter> list = new ArrayList<>();
540
541
    // TODO: Return a list of all properties that match the filter prefix.
542
    // This will allow dynamic filters to be added and removed just by
543
    // updating the properties file.
544
    list.add( createExtensionFilter( ALL ) );
545
    list.add( createExtensionFilter( SOURCE ) );
546
    list.add( createExtensionFilter( DEFINITION ) );
547
    list.add( createExtensionFilter( XML ) );
548
    return list;
549
  }
550
551
  /**
552
   * Returns a filter for file name extensions recognized by the application
553
   * that can be opened by the user.
554
   *
555
   * @param filetype Used to find the globbing pattern for extensions.
556
   * @return A filename filter suitable for use by a FileDialog instance.
557
   */
558
  private ExtensionFilter createExtensionFilter( final FileType filetype ) {
559
    final String tKey = String.format( "%s.title.%s",
560
                                       FILTER_EXTENSION_TITLES,
561
                                       filetype );
562
    final String eKey = String.format( "%s.%s", GLOB_PREFIX_FILE, filetype );
563
564
    return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) );
565
  }
566
567
  private void saveLastDirectory( final File file ) {
568
    getPreferences().put( "lastDirectory", file.getParent() );
569
  }
570
571
  public void initPreferences() {
572
    int activeIndex = 0;
573
574
    final Preferences preferences = getPreferences();
575
    final String[] fileNames = Utils.getPrefsStrings( preferences, "file" );
576
    final String activeFileName = preferences.get( "activeFile", null );
577
578
    final List<File> files = new ArrayList<>( fileNames.length );
579
580
    for( final String fileName : fileNames ) {
581
      final File file = new File( fileName );
582
583
      if( file.exists() ) {
584
        files.add( file );
585
586
        if( fileName.equals( activeFileName ) ) {
587
          activeIndex = files.size() - 1;
588
        }
589
      }
590
    }
591
592
    if( files.isEmpty() ) {
593
      newEditor();
594
    }
595
    else {
596
      openEditors( files, activeIndex );
597
    }
598
  }
599
600
  public void persistPreferences() {
601
    final ObservableList<Tab> allEditors = getTabs();
602
    final List<String> fileNames = new ArrayList<>( allEditors.size() );
603
604
    for( final Tab tab : allEditors ) {
605
      final FileEditorTab fileEditor = (FileEditorTab) tab;
606
      final Path filePath = fileEditor.getPath();
607
608
      if( filePath != null ) {
609
        fileNames.add( filePath.toString() );
610
      }
611
    }
612
613
    final Preferences preferences = getPreferences();
614
    Utils.putPrefsStrings( preferences,
615
                           "file",
616
                           fileNames.toArray( new String[ 0 ] ) );
617
618
    final FileEditorTab activeEditor = getActiveFileEditor();
619
    final Path filePath = activeEditor == null ? null : activeEditor.getPath();
620
621
    if( filePath == null ) {
622
      preferences.remove( "activeFile" );
623
    }
624
    else {
625
      preferences.put( "activeFile", filePath.toString() );
626
    }
627
  }
628
629
  private List<String> getExtensions( final String key ) {
630
    return getSettings().getStringSettingList( key );
631
  }
632
633
  private Notifier getNotifyService() {
634
    return sNotifier;
635
  }
636
637
  private Settings getSettings() {
638
    return SETTINGS;
640639
  }
641640
M src/main/java/com/scrivenvar/Main.java
4949
import static com.scrivenvar.Constants.*;
5050
import static com.scrivenvar.Messages.get;
51
import static java.awt.font.TextAttribute.LIGATURES;
52
import static java.awt.font.TextAttribute.LIGATURES_ON;
51
import static java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment;
52
import static java.awt.font.TextAttribute.*;
5353
import static javafx.scene.input.KeyCode.F11;
5454
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
...
105105
    stage.show();
106106
107
    // After the stage is visible, the panel dimensions are known, which
108
    // allows scaling images to fit the preview panel.
107
    // After the stage is visible, the panel dimensions are
108
    // known, which allows scaling images to fit the preview panel.
109109
    getMainWindow().init();
110110
  }
111111
112112
  /**
113113
   * This needs to run before the windowing system kicks in, otherwise the
114114
   * fonts will not be found.
115115
   */
116116
  @SuppressWarnings({"rawtypes", "unchecked"})
117
  private static void initFonts() {
118
    final var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
117
  public static void initFonts() {
118
    final var ge = getLocalGraphicsEnvironment();
119119
120120
    try {
...
129129
130130
              attributes.put( LIGATURES, LIGATURES_ON );
131
              attributes.put( KERNING, KERNING_ON );
131132
              ge.registerFont( font.deriveFont( attributes ) );
132133
            } catch( final Exception e ) {
M src/main/java/com/scrivenvar/MainWindow.java
144144
145145
  private final EventHandler<PreferencesFxEvent> mRPreferencesListener =
146
      event -> {
147
        rerender();
148
      };
149
150
  /**
151
   * Called when the definition data is changed.
152
   */
153
  private final EventHandler<TreeItem.TreeModificationEvent<Event>>
154
      mTreeHandler = event -> {
155
    exportDefinitions( getDefinitionPath() );
156
    interpolateResolvedMap();
157
    rerender();
158
  };
159
160
  /**
161
   * Called to switch to the definition pane when the user presses the TAB key.
162
   */
163
  private final EventHandler<? super KeyEvent> mTabKeyHandler =
164
      (EventHandler<KeyEvent>) event -> {
165
        if( event.getCode() == TAB ) {
166
          getDefinitionPane().requestFocus();
167
          event.consume();
168
        }
169
      };
170
171
  /**
172
   * Called to inject the selected item when the user presses ENTER in the
173
   * definition pane.
174
   */
175
  private final EventHandler<? super KeyEvent> mDefinitionKeyHandler =
176
      event -> {
177
        if( event.getCode() == ENTER ) {
178
          getVariableNameInjector().injectSelectedItem();
179
        }
180
      };
181
182
  private final ChangeListener<Integer> mCaretPositionListener =
183
      ( observable, oldPosition, newPosition ) -> {
184
        final FileEditorTab tab = getActiveFileEditorTab();
185
        final EditorPane pane = tab.getEditorPane();
186
        final StyleClassedTextArea editor = pane.getEditor();
187
188
        getLineNumberText().setText(
189
            get( STATUS_BAR_LINE,
190
                 editor.getCurrentParagraph() + 1,
191
                 editor.getParagraphs().size(),
192
                 editor.getCaretPosition()
193
            )
194
        );
195
      };
196
197
  private final ChangeListener<Integer> mCaretParagraphListener =
198
      ( observable, oldIndex, newIndex ) ->
199
          scrollToParagraph( newIndex, true );
200
201
  private DefinitionSource mDefinitionSource = createDefaultDefinitionSource();
202
  private final DefinitionPane mDefinitionPane = new DefinitionPane();
203
  private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane();
204
  private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane(
205
      mCaretPositionListener,
206
      mCaretParagraphListener );
207
208
  /**
209
   * Listens on the definition pane for double-click events.
210
   */
211
  private final VariableNameInjector mVariableNameInjector
212
      = new VariableNameInjector( mDefinitionPane );
213
214
  public MainWindow() {
215
    sNotifier.addObserver( this );
216
217
    mStatusBar = createStatusBar();
218
    mLineNumberText = createLineNumberText();
219
    mFindTextField = createFindTextField();
220
    mScene = createScene();
221
    mSpellChecker = createSpellChecker();
222
223
    // Add the close request listener before the window is shown.
224
    initLayout();
225
  }
226
227
  /**
228
   * Called after the stage is shown.
229
   */
230
  public void init() {
231
    initFindInput();
232
    initSnitch();
233
    initDefinitionListener();
234
    initTabAddedListener();
235
    initTabChangedListener();
236
    initPreferences();
237
    initVariableNameInjector();
238
  }
239
240
  private void initLayout() {
241
    final var appScene = getScene();
242
243
    appScene.getStylesheets().add( STYLESHEET_SCENE );
244
    appScene.windowProperty().addListener(
245
        ( unused, oldWindow, newWindow ) ->
246
            newWindow.setOnCloseRequest(
247
                e -> {
248
                  if( !getFileEditorPane().closeAllEditors() ) {
249
                    e.consume();
250
                  }
251
                }
252
            )
253
    );
254
  }
255
256
  /**
257
   * Initialize the find input text field to listen on F3, ENTER, and
258
   * ESCAPE key presses.
259
   */
260
  private void initFindInput() {
261
    final TextField input = getFindTextField();
262
263
    input.setOnKeyPressed( ( KeyEvent event ) -> {
264
      switch( event.getCode() ) {
265
        case F3:
266
        case ENTER:
267
          editFindNext();
268
          break;
269
        case F:
270
          if( !event.isControlDown() ) {
271
            break;
272
          }
273
        case ESCAPE:
274
          getStatusBar().setGraphic( null );
275
          getActiveFileEditorTab().getEditorPane().requestFocus();
276
          break;
277
      }
278
    } );
279
280
    // Remove when the input field loses focus.
281
    input.focusedProperty().addListener(
282
        ( focused, oldFocus, newFocus ) -> {
283
          if( !newFocus ) {
284
            getStatusBar().setGraphic( null );
285
          }
286
        }
287
    );
288
  }
289
290
  /**
291
   * Watch for changes to external files. In particular, this awaits
292
   * modifications to any XSL files associated with XML files being edited.
293
   * When
294
   * an XSL file is modified (external to the application), the snitch's ears
295
   * perk up and the file is reloaded. This keeps the XSL transformation up to
296
   * date with what's on the file system.
297
   */
298
  private void initSnitch() {
299
    SNITCH.addObserver( this );
300
  }
301
302
  /**
303
   * Listen for {@link FileEditorTabPane} to receive open definition file
304
   * event.
305
   */
306
  private void initDefinitionListener() {
307
    getFileEditorPane().onOpenDefinitionFileProperty().addListener(
308
        ( final ObservableValue<? extends Path> file,
309
          final Path oldPath, final Path newPath ) -> {
310
          openDefinitions( newPath );
311
          rerender();
312
        }
313
    );
314
  }
315
316
  /**
317
   * Re-instantiates all processors then re-renders the active tab. This
318
   * will refresh the resolved map, force R to re-initialize, and brute-force
319
   * XSLT file reloads.
320
   */
321
  private void rerender() {
322
    runLater(
323
        () -> {
324
          resetProcessors();
325
          renderActiveTab();
326
        }
327
    );
328
  }
329
330
  /**
331
   * When tabs are added, hook the various change listeners onto the new
332
   * tab sothat the preview pane refreshes as necessary.
333
   */
334
  private void initTabAddedListener() {
335
    final FileEditorTabPane editorPane = getFileEditorPane();
336
337
    // Make sure the text processor kicks off when new files are opened.
338
    final ObservableList<Tab> tabs = editorPane.getTabs();
339
340
    // Update the preview pane on tab changes.
341
    tabs.addListener(
342
        ( final Change<? extends Tab> change ) -> {
343
          while( change.next() ) {
344
            if( change.wasAdded() ) {
345
              // Multiple tabs can be added simultaneously.
346
              for( final Tab newTab : change.getAddedSubList() ) {
347
                final FileEditorTab tab = (FileEditorTab) newTab;
348
349
                initTextChangeListener( tab );
350
                initTabKeyEventListener( tab );
351
                initScrollEventListener( tab );
352
                initSpellCheckListener( tab );
353
//              initSyntaxListener( tab );
354
              }
355
            }
356
          }
357
        }
358
    );
359
  }
360
361
  private void initTextChangeListener( final FileEditorTab tab ) {
362
    tab.addTextChangeListener(
363
        ( editor, oldValue, newValue ) -> {
364
          process( tab );
365
          scrollToParagraph( getCurrentParagraphIndex() );
366
        }
367
    );
368
  }
369
370
  /**
371
   * Ensure that the keyboard events are received when a new tab is added
372
   * to the user interface.
373
   *
374
   * @param tab The tab editor that can trigger keyboard events.
375
   */
376
  private void initTabKeyEventListener( final FileEditorTab tab ) {
377
    tab.addEventFilter( KeyEvent.KEY_PRESSED, mTabKeyHandler );
378
  }
379
380
  private void initScrollEventListener( final FileEditorTab tab ) {
381
    final var scrollPane = tab.getScrollPane();
382
    final var scrollBar = getPreviewPane().getVerticalScrollBar();
383
384
    addShowListener( scrollPane, ( __ ) -> {
385
      final var handler = new ScrollEventHandler( scrollPane, scrollBar );
386
      handler.enabledProperty().bind( tab.selectedProperty() );
387
    } );
388
  }
389
390
  /**
391
   * Listen for changes to the any particular paragraph and perform a quick
392
   * spell check upon it. The style classes in the editor will be changed to
393
   * mark any spelling mistakes in the paragraph. The user may then interact
394
   * with any misspelled word (i.e., any piece of text that is marked) to
395
   * revise the spelling.
396
   *
397
   * @param tab The tab to spellcheck.
398
   */
399
  private void initSpellCheckListener( final FileEditorTab tab ) {
400
    final var editor = tab.getEditorPane().getEditor();
401
402
    // When the editor first appears, run a full spell check. This allows
403
    // spell checking while typing to be restricted to the active paragraph,
404
    // which is usually substantially smaller than the whole document.
405
    addShowListener(
406
        editor, ( __ ) -> spellcheck( editor, editor.getText() )
407
    );
408
409
    // Use the plain text changes so that notifications of style changes
410
    // are suppressed. Checking against the identity ensures that only
411
    // new text additions or deletions trigger proofreading.
412
    editor.plainTextChanges()
413
          .filter( p -> !p.isIdentity() ).subscribe( change -> {
414
415
      // Only perform a spell check on the current paragraph. The
416
      // entire document is processed once, when opened.
417
      final var offset = change.getPosition();
418
      final var position = editor.offsetToPosition( offset, Forward );
419
      final var paraId = position.getMajor();
420
      final var paragraph = editor.getParagraph( paraId );
421
      final var text = paragraph.getText();
422
423
      // Ensure that styles aren't doubled-up.
424
      editor.clearStyle( paraId );
425
426
      spellcheck( editor, text, paraId );
427
    } );
428
  }
429
430
  /**
431
   * Listen for new tab selection events.
432
   */
433
  private void initTabChangedListener() {
434
    final FileEditorTabPane editorPane = getFileEditorPane();
435
436
    // Update the preview pane changing tabs.
437
    editorPane.addTabSelectionListener(
438
        ( tabPane, oldTab, newTab ) -> {
439
          if( newTab == null ) {
440
            // Clear the preview pane when closing an editor. When the last
441
            // tab is closed, this ensures that the preview pane is empty.
442
            getPreviewPane().clear();
443
          }
444
          else {
445
            final var tab = (FileEditorTab) newTab;
446
            updateVariableNameInjector( tab );
447
            process( tab );
448
          }
449
        }
450
    );
451
  }
452
453
  /**
454
   * Reloads the preferences from the previous session.
455
   */
456
  private void initPreferences() {
457
    initDefinitionPane();
458
    getFileEditorPane().initPreferences();
459
    getUserPreferences().addSaveEventHandler( mRPreferencesListener );
460
  }
461
462
  private void initVariableNameInjector() {
463
    updateVariableNameInjector( getActiveFileEditorTab() );
464
  }
465
466
  /**
467
   * Calls the listener when the given node is shown for the first time. The
468
   * visible property is not the same as the initial showing event; visibility
469
   * can be triggered numerous times (such as going off screen).
470
   * <p>
471
   * This is called, for example, before the drag handler can be attached,
472
   * because the scrollbar for the text editor pane must be visible.
473
   * </p>
474
   *
475
   * @param node     The node to watch for showing.
476
   * @param consumer The consumer to invoke when the event fires.
477
   */
478
  private void addShowListener(
479
      final Node node, final Consumer<Void> consumer ) {
480
    final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) ->
481
        runLater( () -> {
482
          if( newShow ) {
483
            try {
484
              consumer.accept( null );
485
            } catch( final Exception ex ) {
486
              error( ex );
487
            }
488
          }
489
        } );
490
491
    Val.flatMap( node.sceneProperty(), Scene::windowProperty )
492
       .flatMap( Window::showingProperty )
493
       .addListener( listener );
494
  }
495
496
  private void scrollToParagraph( final int id ) {
497
    scrollToParagraph( id, false );
498
  }
499
500
  /**
501
   * @param id    The paragraph to scroll to, will be approximated if it doesn't
502
   *              exist.
503
   * @param force {@code true} means to force scrolling immediately, which
504
   *              should only be attempted when it is known that the document
505
   *              has been fully rendered. Otherwise the internal map of ID
506
   *              attributes will be incomplete and scrolling will flounder.
507
   */
508
  private void scrollToParagraph( final int id, final boolean force ) {
509
    synchronized( mMutex ) {
510
      final var previewPane = getPreviewPane();
511
      final var scrollPane = previewPane.getScrollPane();
512
      final int approxId = getActiveEditorPane().approximateParagraphId( id );
513
514
      if( force ) {
515
        previewPane.scrollTo( approxId );
516
      }
517
      else {
518
        previewPane.tryScrollTo( approxId );
519
      }
520
521
      scrollPane.repaint();
522
    }
523
  }
524
525
  private void updateVariableNameInjector( final FileEditorTab tab ) {
526
    getVariableNameInjector().addListener( tab );
527
  }
528
529
  /**
530
   * Called whenever the preview pane becomes out of sync with the file editor
531
   * tab. This can be called when the text changes, the caret paragraph
532
   * changes, or the file tab changes.
533
   *
534
   * @param tab The file editor tab that has been changed in some fashion.
535
   */
536
  private void process( final FileEditorTab tab ) {
537
    if( tab != null ) {
538
      getPreviewPane().setPath( tab.getPath() );
539
540
      final Processor<String> processor = getProcessors().computeIfAbsent(
541
          tab, p -> createProcessors( tab )
542
      );
543
544
      try {
545
        processChain( processor, tab.getEditorText() );
546
      } catch( final Exception ex ) {
547
        error( ex );
548
      }
549
    }
550
  }
551
552
  /**
553
   * Executes the processing chain, operating on the given string.
554
   *
555
   * @param handler The first processor in the chain to call.
556
   * @param text    The initial value of the text to process.
557
   * @return The final value of the text that was processed by the chain.
558
   */
559
  private String processChain( Processor<String> handler, String text ) {
560
    while( handler != null && text != null ) {
561
      text = handler.process( text );
562
      handler = handler.next();
563
    }
564
565
    return text;
566
  }
567
568
  private void renderActiveTab() {
569
    process( getActiveFileEditorTab() );
570
  }
571
572
  /**
573
   * Called when a definition source is opened.
574
   *
575
   * @param path Path to the definition source that was opened.
576
   */
577
  private void openDefinitions( final Path path ) {
578
    try {
579
      final var ds = createDefinitionSource( path );
580
      setDefinitionSource( ds );
581
582
      final var prefs = getUserPreferences();
583
      prefs.definitionPathProperty().setValue( path.toFile() );
584
      prefs.save();
585
586
      final var tooltipPath = new Tooltip( path.toString() );
587
      tooltipPath.setShowDelay( Duration.millis( 200 ) );
588
589
      final var pane = getDefinitionPane();
590
      pane.update( ds );
591
      pane.addTreeChangeHandler( mTreeHandler );
592
      pane.addKeyEventHandler( mDefinitionKeyHandler );
593
      pane.filenameProperty().setValue( path.getFileName().toString() );
594
      pane.setTooltip( tooltipPath );
595
596
      interpolateResolvedMap();
597
    } catch( final Exception ex ) {
598
      error( ex );
599
    }
600
  }
601
602
  private void exportDefinitions( final Path path ) {
603
    try {
604
      final DefinitionPane pane = getDefinitionPane();
605
      final TreeItem<String> root = pane.getTreeView().getRoot();
606
      final TreeItem<String> problemChild = pane.isTreeWellFormed();
607
608
      if( problemChild == null ) {
609
        getDefinitionSource().getTreeAdapter().export( root, path );
610
        getNotifier().clear();
611
      }
612
      else {
613
        final String msg = get(
614
            "yaml.error.tree.form", problemChild.getValue() );
615
        error( msg );
616
      }
617
    } catch( final Exception ex ) {
618
      error( ex );
619
    }
620
  }
621
622
  private void interpolateResolvedMap() {
623
    final Map<String, String> treeMap = getDefinitionPane().toMap();
624
    final Map<String, String> map = new HashMap<>( treeMap );
625
    MapInterpolator.interpolate( map );
626
627
    getResolvedMap().clear();
628
    getResolvedMap().putAll( map );
629
  }
630
631
  private void initDefinitionPane() {
632
    openDefinitions( getDefinitionPath() );
633
  }
634
635
  /**
636
   * Called when an exception occurs that warrants the user's attention.
637
   *
638
   * @param ex The exception with a message that the user should know about.
639
   */
640
  private void error( final Exception ex ) {
641
    getNotifier().notify( ex );
642
  }
643
644
  private void error( final String msg ) {
645
    getNotifier().notify( msg );
646
  }
647
648
  //---- File actions -------------------------------------------------------
649
650
  /**
651
   * Called when an {@link Observable} instance has changed. This is called
652
   * by both the {@link Snitch} service and the notify service. The @link
653
   * Snitch} service can be called for different file types, including
654
   * {@link DefinitionSource} instances.
655
   *
656
   * @param observable The observed instance.
657
   * @param value      The noteworthy item.
658
   */
659
  @Override
660
  public void update( final Observable observable, final Object value ) {
661
    if( value != null ) {
662
      if( observable instanceof Snitch && value instanceof Path ) {
663
        updateSelectedTab();
664
      }
665
      else if( observable instanceof Notifier && value instanceof String ) {
666
        updateStatusBar( (String) value );
667
      }
668
    }
669
  }
670
671
  /**
672
   * Updates the status bar to show the given message.
673
   *
674
   * @param s The message to show in the status bar.
675
   */
676
  private void updateStatusBar( final String s ) {
677
    runLater(
678
        () -> {
679
          final int index = s.indexOf( '\n' );
680
          final String message = s.substring(
681
              0, index > 0 ? index : s.length() );
682
683
          getStatusBar().setText( message );
684
        }
685
    );
686
  }
687
688
  /**
689
   * Called when a file has been modified.
690
   */
691
  private void updateSelectedTab() {
692
    rerender();
693
  }
694
695
  /**
696
   * After resetting the processors, they will refresh anew to be up-to-date
697
   * with the files (text and definition) currently loaded into the editor.
698
   */
699
  private void resetProcessors() {
700
    getProcessors().clear();
701
  }
702
703
  //---- File actions -------------------------------------------------------
704
705
  private void fileNew() {
706
    getFileEditorPane().newEditor();
707
  }
708
709
  private void fileOpen() {
710
    getFileEditorPane().openFileDialog();
711
  }
712
713
  private void fileClose() {
714
    getFileEditorPane().closeEditor( getActiveFileEditorTab(), true );
715
  }
716
717
  /**
718
   * TODO: Upon closing, first remove the tab change listeners. (There's no
719
   * need to re-render each tab when all are being closed.)
720
   */
721
  private void fileCloseAll() {
722
    getFileEditorPane().closeAllEditors();
723
  }
724
725
  private void fileSave() {
726
    getFileEditorPane().saveEditor( getActiveFileEditorTab() );
727
  }
728
729
  private void fileSaveAs() {
730
    final FileEditorTab editor = getActiveFileEditorTab();
731
    getFileEditorPane().saveEditorAs( editor );
732
    getProcessors().remove( editor );
733
734
    try {
735
      process( editor );
736
    } catch( final Exception ex ) {
737
      error( ex );
738
    }
739
  }
740
741
  private void fileSaveAll() {
742
    getFileEditorPane().saveAllEditors();
743
  }
744
745
  private void fileExit() {
746
    final Window window = getWindow();
747
    fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
748
  }
749
750
  //---- Edit actions -------------------------------------------------------
751
752
  /**
753
   * Transform the Markdown into HTML then copy that HTML into the copy
754
   * buffer.
755
   */
756
  private void copyHtml() {
757
    final var markdown = getActiveEditorPane().getText();
758
    final var processors = createProcessorFactory().createProcessors(
759
        getActiveFileEditorTab()
760
    );
761
762
    final var chain = processors.remove( HtmlPreviewProcessor.class );
763
764
    final String html = processChain( chain, markdown );
765
766
    final Clipboard clipboard = Clipboard.getSystemClipboard();
767
    final ClipboardContent content = new ClipboardContent();
768
    content.putString( html );
769
    clipboard.setContent( content );
770
  }
771
772
  /**
773
   * Used to find text in the active file editor window.
774
   */
775
  private void editFind() {
776
    final TextField input = getFindTextField();
777
    getStatusBar().setGraphic( input );
778
    input.requestFocus();
779
  }
780
781
  public void editFindNext() {
782
    getActiveFileEditorTab().searchNext( getFindTextField().getText() );
783
  }
784
785
  public void editPreferences() {
786
    getUserPreferences().show();
787
  }
788
789
  //---- Insert actions -----------------------------------------------------
790
791
  /**
792
   * Delegates to the active editor to handle wrapping the current text
793
   * selection with leading and trailing strings.
794
   *
795
   * @param leading  The string to put before the selection.
796
   * @param trailing The string to put after the selection.
797
   */
798
  private void insertMarkdown(
799
      final String leading, final String trailing ) {
800
    getActiveEditorPane().surroundSelection( leading, trailing );
801
  }
802
803
  private void insertMarkdown(
804
      final String leading, final String trailing, final String hint ) {
805
    getActiveEditorPane().surroundSelection( leading, trailing, hint );
806
  }
807
808
  //---- Help actions -------------------------------------------------------
809
810
  private void helpAbout() {
811
    final Alert alert = new Alert( AlertType.INFORMATION );
812
    alert.setTitle( get( "Dialog.about.title" ) );
813
    alert.setHeaderText( get( "Dialog.about.header" ) );
814
    alert.setContentText( get( "Dialog.about.content" ) );
815
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
816
    alert.initOwner( getWindow() );
817
818
    alert.showAndWait();
819
  }
820
821
  //---- Member creators ----------------------------------------------------
822
823
  private SpellChecker createSpellChecker() {
824
    try {
825
      final Collection<String> lexicon = readLexicon( "en.txt" );
826
      return SymSpellSpeller.forLexicon( lexicon );
827
    } catch( final Exception ex ) {
828
      error( ex );
829
      return new PermissiveSpeller();
830
    }
831
  }
832
833
  /**
834
   * Factory to create processors that are suited to different file types.
835
   *
836
   * @param tab The tab that is subjected to processing.
837
   * @return A processor suited to the file type specified by the tab's path.
838
   */
839
  private Processor<String> createProcessors( final FileEditorTab tab ) {
840
    return createProcessorFactory().createProcessors( tab );
841
  }
842
843
  private ProcessorFactory createProcessorFactory() {
844
    return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
845
  }
846
847
  private HTMLPreviewPane createHTMLPreviewPane() {
848
    return new HTMLPreviewPane();
849
  }
850
851
  private DefinitionSource createDefaultDefinitionSource() {
852
    return new YamlDefinitionSource( getDefinitionPath() );
853
  }
854
855
  private DefinitionSource createDefinitionSource( final Path path ) {
856
    try {
857
      return createDefinitionFactory().createDefinitionSource( path );
858
    } catch( final Exception ex ) {
859
      error( ex );
860
      return createDefaultDefinitionSource();
861
    }
862
  }
863
864
  private TextField createFindTextField() {
865
    return new TextField();
866
  }
867
868
  private DefinitionFactory createDefinitionFactory() {
869
    return new DefinitionFactory();
870
  }
871
872
  private StatusBar createStatusBar() {
873
    return new StatusBar();
874
  }
875
876
  private Scene createScene() {
877
    final SplitPane splitPane = new SplitPane(
878
        getDefinitionPane().getNode(),
879
        getFileEditorPane().getNode(),
880
        getPreviewPane().getNode() );
881
882
    splitPane.setDividerPositions(
883
        getFloat( K_PANE_SPLIT_DEFINITION, .22f ),
884
        getFloat( K_PANE_SPLIT_EDITOR, .60f ),
885
        getFloat( K_PANE_SPLIT_PREVIEW, .18f ) );
886
887
    getDefinitionPane().prefHeightProperty()
888
                       .bind( splitPane.heightProperty() );
889
890
    final BorderPane borderPane = new BorderPane();
891
    borderPane.setPrefSize( 1280, 800 );
892
    borderPane.setTop( createMenuBar() );
893
    borderPane.setBottom( getStatusBar() );
894
    borderPane.setCenter( splitPane );
895
896
    final VBox statusBar = new VBox();
897
    statusBar.setAlignment( Pos.BASELINE_CENTER );
898
    statusBar.getChildren().add( getLineNumberText() );
899
    getStatusBar().getRightItems().add( statusBar );
900
901
    // Force preview pane refresh on Windows.
902
    if( SystemUtils.IS_OS_WINDOWS ) {
903
      splitPane.getDividers().get( 1 ).positionProperty().addListener(
904
          ( l, oValue, nValue ) -> runLater(
905
              () -> getPreviewPane().getScrollPane().repaint()
906
          )
907
      );
908
    }
909
910
    return new Scene( borderPane );
911
  }
912
913
  private Text createLineNumberText() {
914
    return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) );
915
  }
916
917
  private Node createMenuBar() {
918
    final BooleanBinding activeFileEditorIsNull =
919
        getFileEditorPane().activeFileEditorProperty().isNull();
920
921
    // File actions
922
    final Action fileNewAction = new ActionBuilder()
923
        .setText( "Main.menu.file.new" )
924
        .setAccelerator( "Shortcut+N" )
925
        .setIcon( FILE_ALT )
926
        .setAction( e -> fileNew() )
927
        .build();
928
    final Action fileOpenAction = new ActionBuilder()
929
        .setText( "Main.menu.file.open" )
930
        .setAccelerator( "Shortcut+O" )
931
        .setIcon( FOLDER_OPEN_ALT )
932
        .setAction( e -> fileOpen() )
933
        .build();
934
    final Action fileCloseAction = new ActionBuilder()
935
        .setText( "Main.menu.file.close" )
936
        .setAccelerator( "Shortcut+W" )
937
        .setAction( e -> fileClose() )
938
        .setDisable( activeFileEditorIsNull )
939
        .build();
940
    final Action fileCloseAllAction = new ActionBuilder()
941
        .setText( "Main.menu.file.close_all" )
942
        .setAction( e -> fileCloseAll() )
943
        .setDisable( activeFileEditorIsNull )
944
        .build();
945
    final Action fileSaveAction = new ActionBuilder()
946
        .setText( "Main.menu.file.save" )
947
        .setAccelerator( "Shortcut+S" )
948
        .setIcon( FLOPPY_ALT )
949
        .setAction( e -> fileSave() )
950
        .setDisable( createActiveBooleanProperty(
951
            FileEditorTab::modifiedProperty ).not() )
952
        .build();
953
    final Action fileSaveAsAction = new ActionBuilder()
954
        .setText( "Main.menu.file.save_as" )
955
        .setAction( e -> fileSaveAs() )
956
        .setDisable( activeFileEditorIsNull )
957
        .build();
958
    final Action fileSaveAllAction = new ActionBuilder()
959
        .setText( "Main.menu.file.save_all" )
960
        .setAccelerator( "Shortcut+Shift+S" )
961
        .setAction( e -> fileSaveAll() )
962
        .setDisable( Bindings.not(
963
            getFileEditorPane().anyFileEditorModifiedProperty() ) )
964
        .build();
965
    final Action fileExitAction = new ActionBuilder()
966
        .setText( "Main.menu.file.exit" )
967
        .setAction( e -> fileExit() )
968
        .build();
969
970
    // Edit actions
971
    final Action editCopyHtmlAction = new ActionBuilder()
972
        .setText( Messages.get( "Main.menu.edit.copy.html" ) )
973
        .setIcon( HTML5 )
974
        .setAction( e -> copyHtml() )
975
        .setDisable( activeFileEditorIsNull )
976
        .build();
977
978
    final Action editUndoAction = new ActionBuilder()
979
        .setText( "Main.menu.edit.undo" )
980
        .setAccelerator( "Shortcut+Z" )
981
        .setIcon( UNDO )
982
        .setAction( e -> getActiveEditorPane().undo() )
983
        .setDisable( createActiveBooleanProperty(
984
            FileEditorTab::canUndoProperty ).not() )
985
        .build();
986
    final Action editRedoAction = new ActionBuilder()
987
        .setText( "Main.menu.edit.redo" )
988
        .setAccelerator( "Shortcut+Y" )
989
        .setIcon( REPEAT )
990
        .setAction( e -> getActiveEditorPane().redo() )
991
        .setDisable( createActiveBooleanProperty(
992
            FileEditorTab::canRedoProperty ).not() )
993
        .build();
994
995
    final Action editCutAction = new ActionBuilder()
996
        .setText( Messages.get( "Main.menu.edit.cut" ) )
997
        .setAccelerator( "Shortcut+X" )
998
        .setIcon( CUT )
999
        .setAction( e -> getActiveEditorPane().cut() )
1000
        .setDisable( activeFileEditorIsNull )
1001
        .build();
1002
    final Action editCopyAction = new ActionBuilder()
1003
        .setText( Messages.get( "Main.menu.edit.copy" ) )
1004
        .setAccelerator( "Shortcut+C" )
1005
        .setIcon( COPY )
1006
        .setAction( e -> getActiveEditorPane().copy() )
1007
        .setDisable( activeFileEditorIsNull )
1008
        .build();
1009
    final Action editPasteAction = new ActionBuilder()
1010
        .setText( Messages.get( "Main.menu.edit.paste" ) )
1011
        .setAccelerator( "Shortcut+V" )
1012
        .setIcon( PASTE )
1013
        .setAction( e -> getActiveEditorPane().paste() )
1014
        .setDisable( activeFileEditorIsNull )
1015
        .build();
1016
    final Action editSelectAllAction = new ActionBuilder()
1017
        .setText( Messages.get( "Main.menu.edit.selectAll" ) )
1018
        .setAccelerator( "Shortcut+A" )
1019
        .setAction( e -> getActiveEditorPane().selectAll() )
1020
        .setDisable( activeFileEditorIsNull )
1021
        .build();
1022
1023
    final Action editFindAction = new ActionBuilder()
1024
        .setText( "Main.menu.edit.find" )
1025
        .setAccelerator( "Ctrl+F" )
1026
        .setIcon( SEARCH )
1027
        .setAction( e -> editFind() )
1028
        .setDisable( activeFileEditorIsNull )
1029
        .build();
1030
    final Action editFindNextAction = new ActionBuilder()
1031
        .setText( "Main.menu.edit.find.next" )
1032
        .setAccelerator( "F3" )
1033
        .setIcon( null )
1034
        .setAction( e -> editFindNext() )
1035
        .setDisable( activeFileEditorIsNull )
1036
        .build();
1037
    final Action editPreferencesAction = new ActionBuilder()
1038
        .setText( "Main.menu.edit.preferences" )
1039
        .setAccelerator( "Ctrl+Alt+S" )
1040
        .setAction( e -> editPreferences() )
1041
        .build();
1042
1043
    // Insert actions
1044
    final Action insertBoldAction = new ActionBuilder()
1045
        .setText( "Main.menu.insert.bold" )
1046
        .setAccelerator( "Shortcut+B" )
1047
        .setIcon( BOLD )
1048
        .setAction( e -> insertMarkdown( "**", "**" ) )
1049
        .setDisable( activeFileEditorIsNull )
1050
        .build();
1051
    final Action insertItalicAction = new ActionBuilder()
1052
        .setText( "Main.menu.insert.italic" )
1053
        .setAccelerator( "Shortcut+I" )
1054
        .setIcon( ITALIC )
1055
        .setAction( e -> insertMarkdown( "*", "*" ) )
1056
        .setDisable( activeFileEditorIsNull )
1057
        .build();
1058
    final Action insertSuperscriptAction = new ActionBuilder()
1059
        .setText( "Main.menu.insert.superscript" )
1060
        .setAccelerator( "Shortcut+[" )
1061
        .setIcon( SUPERSCRIPT )
1062
        .setAction( e -> insertMarkdown( "^", "^" ) )
1063
        .setDisable( activeFileEditorIsNull )
1064
        .build();
1065
    final Action insertSubscriptAction = new ActionBuilder()
1066
        .setText( "Main.menu.insert.subscript" )
1067
        .setAccelerator( "Shortcut+]" )
1068
        .setIcon( SUBSCRIPT )
1069
        .setAction( e -> insertMarkdown( "~", "~" ) )
1070
        .setDisable( activeFileEditorIsNull )
1071
        .build();
1072
    final Action insertStrikethroughAction = new ActionBuilder()
1073
        .setText( "Main.menu.insert.strikethrough" )
1074
        .setAccelerator( "Shortcut+T" )
1075
        .setIcon( STRIKETHROUGH )
1076
        .setAction( e -> insertMarkdown( "~~", "~~" ) )
1077
        .setDisable( activeFileEditorIsNull )
1078
        .build();
1079
    final Action insertBlockquoteAction = new ActionBuilder()
1080
        .setText( "Main.menu.insert.blockquote" )
1081
        .setAccelerator( "Ctrl+Q" )
1082
        .setIcon( QUOTE_LEFT )
1083
        .setAction( e -> insertMarkdown( "\n\n> ", "" ) )
1084
        .setDisable( activeFileEditorIsNull )
1085
        .build();
1086
    final Action insertCodeAction = new ActionBuilder()
1087
        .setText( "Main.menu.insert.code" )
1088
        .setAccelerator( "Shortcut+K" )
1089
        .setIcon( CODE )
1090
        .setAction( e -> insertMarkdown( "`", "`" ) )
1091
        .setDisable( activeFileEditorIsNull )
1092
        .build();
1093
    final Action insertFencedCodeBlockAction = new ActionBuilder()
1094
        .setText( "Main.menu.insert.fenced_code_block" )
1095
        .setAccelerator( "Shortcut+Shift+K" )
1096
        .setIcon( FILE_CODE_ALT )
1097
        .setAction( e -> insertMarkdown(
1098
            "\n\n```\n",
1099
            "\n```\n\n",
1100
            get( "Main.menu.insert.fenced_code_block.prompt" ) ) )
1101
        .setDisable( activeFileEditorIsNull )
1102
        .build();
1103
    final Action insertLinkAction = new ActionBuilder()
1104
        .setText( "Main.menu.insert.link" )
1105
        .setAccelerator( "Shortcut+L" )
1106
        .setIcon( LINK )
1107
        .setAction( e -> getActiveEditorPane().insertLink() )
1108
        .setDisable( activeFileEditorIsNull )
1109
        .build();
1110
    final Action insertImageAction = new ActionBuilder()
1111
        .setText( "Main.menu.insert.image" )
1112
        .setAccelerator( "Shortcut+G" )
1113
        .setIcon( PICTURE_ALT )
1114
        .setAction( e -> getActiveEditorPane().insertImage() )
1115
        .setDisable( activeFileEditorIsNull )
1116
        .build();
1117
1118
    // Number of heading actions (H1 ... H3)
1119
    final int HEADINGS = 3;
1120
    final Action[] headings = new Action[ HEADINGS ];
1121
1122
    for( int i = 1; i <= HEADINGS; i++ ) {
1123
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
1124
      final String markup = String.format( "%n%n%s ", hashes );
1125
      final String text = "Main.menu.insert.heading." + i;
1126
      final String accelerator = "Shortcut+" + i;
1127
      final String prompt = text + ".prompt";
1128
1129
      headings[ i - 1 ] = new ActionBuilder()
1130
          .setText( text )
1131
          .setAccelerator( accelerator )
1132
          .setIcon( HEADER )
1133
          .setAction( e -> insertMarkdown( markup, "", get( prompt ) ) )
1134
          .setDisable( activeFileEditorIsNull )
1135
          .build();
1136
    }
1137
1138
    final Action insertUnorderedListAction = new ActionBuilder()
1139
        .setText( "Main.menu.insert.unordered_list" )
1140
        .setAccelerator( "Shortcut+U" )
1141
        .setIcon( LIST_UL )
1142
        .setAction( e -> insertMarkdown( "\n\n* ", "" ) )
1143
        .setDisable( activeFileEditorIsNull )
1144
        .build();
1145
    final Action insertOrderedListAction = new ActionBuilder()
1146
        .setText( "Main.menu.insert.ordered_list" )
1147
        .setAccelerator( "Shortcut+Shift+O" )
1148
        .setIcon( LIST_OL )
1149
        .setAction( e -> insertMarkdown(
1150
            "\n\n1. ", "" ) )
1151
        .setDisable( activeFileEditorIsNull )
1152
        .build();
1153
    final Action insertHorizontalRuleAction = new ActionBuilder()
1154
        .setText( "Main.menu.insert.horizontal_rule" )
1155
        .setAccelerator( "Shortcut+H" )
1156
        .setAction( e -> insertMarkdown(
1157
            "\n\n---\n\n", "" ) )
1158
        .setDisable( activeFileEditorIsNull )
1159
        .build();
1160
1161
    // Help actions
1162
    final Action helpAboutAction = new ActionBuilder()
1163
        .setText( "Main.menu.help.about" )
1164
        .setAction( e -> helpAbout() )
1165
        .build();
1166
1167
    //---- MenuBar ----
1168
    final Menu fileMenu = ActionUtils.createMenu(
1169
        get( "Main.menu.file" ),
1170
        fileNewAction,
1171
        fileOpenAction,
1172
        null,
1173
        fileCloseAction,
1174
        fileCloseAllAction,
1175
        null,
1176
        fileSaveAction,
1177
        fileSaveAsAction,
1178
        fileSaveAllAction,
1179
        null,
1180
        fileExitAction );
1181
1182
    final Menu editMenu = ActionUtils.createMenu(
1183
        get( "Main.menu.edit" ),
1184
        editCopyHtmlAction,
1185
        null,
1186
        editUndoAction,
1187
        editRedoAction,
1188
        null,
1189
        editCutAction,
1190
        editCopyAction,
1191
        editPasteAction,
1192
        editSelectAllAction,
1193
        null,
1194
        editFindAction,
1195
        editFindNextAction,
1196
        null,
1197
        editPreferencesAction );
1198
1199
    final Menu insertMenu = ActionUtils.createMenu(
1200
        get( "Main.menu.insert" ),
1201
        insertBoldAction,
1202
        insertItalicAction,
1203
        insertSuperscriptAction,
1204
        insertSubscriptAction,
1205
        insertStrikethroughAction,
1206
        insertBlockquoteAction,
1207
        insertCodeAction,
1208
        insertFencedCodeBlockAction,
1209
        null,
1210
        insertLinkAction,
1211
        insertImageAction,
1212
        null,
1213
        headings[ 0 ],
1214
        headings[ 1 ],
1215
        headings[ 2 ],
1216
        null,
1217
        insertUnorderedListAction,
1218
        insertOrderedListAction,
1219
        insertHorizontalRuleAction
1220
    );
1221
1222
    final Menu helpMenu = ActionUtils.createMenu(
1223
        get( "Main.menu.help" ),
1224
        helpAboutAction );
1225
1226
    final MenuBar menuBar = new MenuBar(
1227
        fileMenu,
1228
        editMenu,
1229
        insertMenu,
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,
12301246
        helpMenu );
12311247
A src/main/java/com/scrivenvar/adapters/DocumentAdapter.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.adapters;
29
30
import org.xhtmlrenderer.event.DocumentListener;
31
32
/**
33
 * Allows subclasses to implement specific events.
34
 */
35
public class DocumentAdapter implements DocumentListener {
36
  @Override
37
  public void documentStarted() {
38
  }
39
40
  @Override
41
  public void documentLoaded() {
42
  }
43
44
  @Override
45
  public void onLayoutException( final Throwable t ) {
46
  }
47
48
  @Override
49
  public void onRenderException( final Throwable t ) {
50
  }
51
}
152
A src/main/java/com/scrivenvar/adapters/ReplacedElementAdapter.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.adapters;
29
30
import org.w3c.dom.Element;
31
import org.xhtmlrenderer.extend.ReplacedElementFactory;
32
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;
33
34
public abstract class ReplacedElementAdapter implements ReplacedElementFactory {
35
  @Override
36
  public void reset() {
37
  }
38
39
  @Override
40
  public void remove( final Element e ) {
41
  }
42
43
  @Override
44
  public void setFormSubmissionListener(
45
      final FormSubmissionListener listener ) {
46
  }
47
}
148
M src/main/java/com/scrivenvar/definition/DefinitionPane.java
4040
import javafx.scene.Node;
4141
import javafx.scene.control.*;
42
import javafx.scene.control.cell.TextFieldTreeCell;
43
import javafx.scene.input.KeyEvent;
44
import javafx.scene.layout.BorderPane;
45
import javafx.scene.layout.HBox;
46
import javafx.util.StringConverter;
47
48
import java.util.*;
49
50
import static com.scrivenvar.Messages.get;
51
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
52
import static javafx.geometry.Pos.CENTER;
53
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
54
55
/**
56
 * Provides the user interface that holds a {@link TreeView}, which
57
 * allows users to interact with key/value pairs loaded from the
58
 * {@link DocumentParser} and adapted using a {@link TreeAdapter}.
59
 */
60
public final class DefinitionPane extends BorderPane {
61
62
  /**
63
   * Contains a view of the definitions.
64
   */
65
  private final TreeView<String> mTreeView = new TreeView<>();
66
67
  /**
68
   * Handlers for key press events.
69
   */
70
  private final Set<EventHandler<? super KeyEvent>> mKeyEventHandlers
71
      = new HashSet<>();
72
73
  /**
74
   * Definition file name shown in the title of the pane.
75
   */
76
  private final StringProperty mFilename = new SimpleStringProperty();
77
78
  private final TitledPane mTitledPane = new TitledPane();
79
80
  /**
81
   * Constructs a definition pane with a given tree view root.
82
   */
83
  public DefinitionPane() {
84
    final var treeView = getTreeView();
85
    treeView.setEditable( true );
86
    treeView.setCellFactory( cell -> createTreeCell() );
87
    treeView.setContextMenu( createContextMenu() );
88
    treeView.addEventFilter( KEY_PRESSED, this::keyEventFilter );
89
    treeView.setShowRoot( false );
90
    getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE );
91
92
    final var bCreate = createButton(
93
        "create", TREE, e -> addItem() );
94
    final var bRename = createButton(
95
        "rename", EDIT, e -> editSelectedItem() );
96
    final var bDelete = createButton(
97
        "delete", TRASH, e -> deleteSelectedItems() );
98
99
    final var buttonBar = new HBox();
100
    buttonBar.getChildren().addAll( bCreate, bRename, bDelete );
101
    buttonBar.setAlignment( CENTER );
102
    buttonBar.setSpacing( 10 );
103
104
    final var titledPane = getTitledPane();
105
    titledPane.textProperty().bind( mFilename );
106
    titledPane.setContent( treeView );
107
    titledPane.setCollapsible( false );
108
    titledPane.setPadding( new Insets( 0, 0, 0, 0 ) );
109
110
    setTop( buttonBar );
111
    setCenter( titledPane );
112
    setAlignment( buttonBar, Pos.TOP_CENTER );
113
    setAlignment( titledPane, Pos.TOP_CENTER );
114
115
    titledPane.prefHeightProperty().bind( this.heightProperty() );
116
  }
117
118
  public void setTooltip( final Tooltip tooltip ) {
119
    getTitledPane().setTooltip( tooltip );
120
  }
121
122
  private TitledPane getTitledPane() {
123
    return mTitledPane;
124
  }
125
126
  private Button createButton(
127
      final String msgKey,
128
      final FontAwesomeIcon icon,
129
      final EventHandler<ActionEvent> eventHandler ) {
130
    final var keyPrefix = "Pane.definition.button." + msgKey;
131
    final var button = new Button( get( keyPrefix + ".label" ) );
132
    button.setOnAction( eventHandler );
133
134
    button.setGraphic(
135
        FontAwesomeIconFactory.get().createIcon( icon )
136
    );
137
    button.setTooltip( new Tooltip( get( keyPrefix + ".tooltip" ) ) );
138
139
    return button;
140
  }
141
142
  /**
143
   * Changes the root of the {@link TreeView} to the root of the
144
   * {@link TreeView} from the {@link DefinitionSource}.
145
   *
146
   * @param definitionSource Container for the hierarchy of key/value pairs
147
   *                         to replace the existing hierarchy.
148
   */
149
  public void update( final DefinitionSource definitionSource ) {
150
    assert definitionSource != null;
151
152
    final TreeAdapter treeAdapter = definitionSource.getTreeAdapter();
153
    final TreeItem<String> root = treeAdapter.adapt(
154
        get( "Pane.definition.node.root.title" )
155
    );
156
157
    getTreeView().setRoot( root );
158
  }
159
160
  public Map<String, String> toMap() {
161
    return TreeItemAdapter.toMap( getTreeView().getRoot() );
162
  }
163
164
  /**
165
   * Informs the caller of whenever any {@link TreeItem} in the {@link TreeView}
166
   * is modified. The modifications include: item value changes, item additions,
167
   * and item removals.
168
   * <p>
169
   * Safe to call multiple times; if a handler is already registered, the
170
   * old handler is used.
171
   * </p>
172
   *
173
   * @param handler The handler to call whenever any {@link TreeItem} changes.
174
   */
175
  public void addTreeChangeHandler(
176
      final EventHandler<TreeItem.TreeModificationEvent<Event>> handler ) {
177
    final TreeItem<String> root = getTreeView().getRoot();
178
    root.addEventHandler( TreeItem.valueChangedEvent(), handler );
179
    root.addEventHandler( TreeItem.childrenModificationEvent(), handler );
180
  }
181
182
  public void addKeyEventHandler(
183
      final EventHandler<? super KeyEvent> handler ) {
184
    getKeyEventHandlers().add( handler );
185
  }
186
187
  /**
188
   * Answers whether the {@link TreeItem}s in the {@link TreeView} are suitably
189
   * well-formed for export. A tree is considered well-formed if the following
190
   * conditions are met:
191
   *
192
   * <ul>
193
   *   <li>The root node contains at least one child node having a leaf.</li>
194
   *   <li>There are no leaf nodes with sibling leaf nodes.</li>
195
   * </ul>
196
   *
197
   * @return {@code null} if the document is well-formed, otherwise the
198
   * problematic child {@link TreeItem}.
199
   */
200
  public TreeItem<String> isTreeWellFormed() {
201
    final var root = getTreeView().getRoot();
202
203
    for( final var child : root.getChildren() ) {
204
      final var problemChild = isWellFormed( child );
205
206
      if( child.isLeaf() || problemChild != null ) {
207
        return problemChild;
208
      }
209
    }
210
211
    return null;
212
  }
213
214
  /**
215
   * Determines whether the document is well-formed by ensuring that
216
   * child branches do not contain multiple leaves.
217
   *
218
   * @param item The sub-tree to check for well-formedness.
219
   * @return {@code null} when the tree is well-formed, otherwise the
220
   * problematic {@link TreeItem}.
221
   */
222
  private TreeItem<String> isWellFormed( final TreeItem<String> item ) {
223
    int childLeafs = 0;
224
    int childBranches = 0;
225
226
    for( final TreeItem<String> child : item.getChildren() ) {
227
      if( child.isLeaf() ) {
228
        childLeafs++;
229
      }
230
      else {
231
        childBranches++;
232
      }
233
234
      final var problemChild = isWellFormed( child );
235
236
      if( problemChild != null ) {
237
        return problemChild;
238
      }
239
    }
240
241
    return ((childBranches > 0 && childLeafs == 0) ||
242
        (childBranches == 0 && childLeafs <= 1)) ? null : item;
243
  }
244
245
  /**
246
   * Delegates to {@link VariableTreeItem#findLeafExact(String)}.
247
   *
248
   * @param text The value to find, never {@code null}.
249
   * @return The leaf that contains the given value, or {@code null} if
250
   * not found.
251
   */
252
  public VariableTreeItem<String> findLeafExact( final String text ) {
253
    return getTreeRoot().findLeafExact( text );
254
  }
255
256
  /**
257
   * Delegates to {@link VariableTreeItem#findLeafContains(String)}.
258
   *
259
   * @param text The value to find, never {@code null}.
260
   * @return The leaf that contains the given value, or {@code null} if
261
   * not found.
262
   */
263
  public VariableTreeItem<String> findLeafContains( final String text ) {
264
    return getTreeRoot().findLeafContains( text );
265
  }
266
267
  /**
268
   * Delegates to {@link VariableTreeItem#findLeafContains(String)}.
269
   *
270
   * @param text The value to find, never {@code null}.
271
   * @return The leaf that contains the given value, or {@code null} if
272
   * not found.
273
   */
274
  public VariableTreeItem<String> findLeafContainsNoCase( final String text ) {
275
    return getTreeRoot().findLeafContainsNoCase( text );
276
  }
277
278
  /**
279
   * Delegates to {@link VariableTreeItem#findLeafStartsWith(String)}.
280
   *
281
   * @param text The value to find, never {@code null}.
282
   * @return The leaf that contains the given value, or {@code null} if
283
   * not found.
284
   */
285
  public VariableTreeItem<String> findLeafStartsWith( final String text ) {
286
    return getTreeRoot().findLeafStartsWith( text );
287
  }
288
289
  /**
290
   * Expands the node to the root, recursively.
291
   *
292
   * @param <T>  The type of tree item to expand (usually String).
293
   * @param node The node to expand.
294
   */
295
  public <T> void expand( final TreeItem<T> node ) {
296
    if( node != null ) {
297
      expand( node.getParent() );
298
299
      if( !node.isLeaf() ) {
300
        node.setExpanded( true );
301
      }
302
    }
303
  }
304
305
  public void select( final TreeItem<String> item ) {
306
    getSelectionModel().clearSelection();
307
    getSelectionModel().select( getTreeView().getRow( item ) );
308
  }
309
310
  /**
311
   * Collapses the tree, recursively.
312
   */
313
  public void collapse() {
314
    collapse( getTreeRoot().getChildren() );
315
  }
316
317
  /**
318
   * Collapses the tree, recursively.
319
   *
320
   * @param <T>   The type of tree item to expand (usually String).
321
   * @param nodes The nodes to collapse.
322
   */
323
  private <T> void collapse( final ObservableList<TreeItem<T>> nodes ) {
324
    for( final var node : nodes ) {
325
      node.setExpanded( false );
326
      collapse( node.getChildren() );
327
    }
328
  }
329
330
  /**
331
   * @return {@code true} when the user is editing a {@link TreeItem}.
332
   */
333
  private boolean isEditingTreeItem() {
334
    return getTreeView().editingItemProperty().getValue() != null;
335
  }
336
337
  /**
338
   * Changes to edit mode for the selected item.
339
   */
340
  private void editSelectedItem() {
341
    getTreeView().edit( getSelectedItem() );
342
  }
343
344
  /**
345
   * Removes all selected items from the {@link TreeView}.
346
   */
347
  private void deleteSelectedItems() {
348
    for( final var item : getSelectedItems() ) {
349
      final var parent = item.getParent();
350
351
      if( parent != null ) {
352
        parent.getChildren().remove( item );
353
      }
354
    }
355
  }
356
357
  /**
358
   * Deletes the selected item.
359
   */
360
  private void deleteSelectedItem() {
361
    final var c = getSelectedItem();
362
    getSiblings( c ).remove( c );
363
  }
364
365
  /**
366
   * Adds a new item under the selected item (or root if nothing is selected).
367
   * There are a few conditions to consider: when adding to the root,
368
   * when adding to a leaf, and when adding to a non-leaf. Items added to the
369
   * root must contain two items: a key and a value.
370
   */
371
  private void addItem() {
372
    final var value = createTreeItem();
373
    getSelectedItem().getChildren().add( value );
374
    expand( value );
375
    select( value );
376
  }
377
378
  private ContextMenu createContextMenu() {
379
    final ContextMenu menu = new ContextMenu();
380
    final ObservableList<MenuItem> items = menu.getItems();
381
382
    addMenuItem( items, "Definition.menu.create" )
383
        .setOnAction( e -> addItem() );
384
385
    addMenuItem( items, "Definition.menu.rename" )
386
        .setOnAction( e -> editSelectedItem() );
387
388
    addMenuItem( items, "Definition.menu.remove" )
389
        .setOnAction( e -> deleteSelectedItem() );
390
391
    return menu;
392
  }
393
394
  /**
395
   * Executes hot-keys for edits to the definition tree.
396
   *
397
   * @param event Contains the key code of the key that was pressed.
398
   */
399
  private void keyEventFilter( final KeyEvent event ) {
400
    if( !isEditingTreeItem() ) {
401
      switch( event.getCode() ) {
402
        case ENTER:
403
          expand( getSelectedItem() );
404
          event.consume();
405
          break;
406
407
        case DELETE:
408
          deleteSelectedItems();
409
          break;
410
411
        case INSERT:
412
          addItem();
413
          break;
414
415
        case R:
416
          if( event.isControlDown() ) {
417
            editSelectedItem();
418
          }
419
420
          break;
421
      }
422
423
      for( final var handler : getKeyEventHandlers() ) {
424
        handler.handle( event );
425
      }
426
    }
427
  }
428
429
  /**
430
   * Adds a menu item to a list of menu items.
431
   *
432
   * @param items    The list of menu items to append to.
433
   * @param labelKey The resource bundle key name for the menu item's label.
434
   * @return The menu item added to the list of menu items.
435
   */
436
  private MenuItem addMenuItem(
437
      final List<MenuItem> items, final String labelKey ) {
438
    final MenuItem menuItem = createMenuItem( labelKey );
439
    items.add( menuItem );
440
    return menuItem;
441
  }
442
443
  private MenuItem createMenuItem( final String labelKey ) {
444
    return new MenuItem( get( labelKey ) );
445
  }
446
447
  private VariableTreeItem<String> createTreeItem() {
448
    return new VariableTreeItem<>( get( "Definition.menu.add.default" ) );
449
  }
450
451
  private TreeCell<String> createTreeCell() {
452
    return new TextFieldTreeCell<>( createStringConverter() ) {
42
import javafx.scene.input.KeyEvent;
43
import javafx.scene.layout.BorderPane;
44
import javafx.scene.layout.HBox;
45
import javafx.util.StringConverter;
46
47
import java.util.*;
48
49
import static com.scrivenvar.Messages.get;
50
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
51
import static javafx.geometry.Pos.CENTER;
52
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
53
54
/**
55
 * Provides the user interface that holds a {@link TreeView}, which
56
 * allows users to interact with key/value pairs loaded from the
57
 * {@link DocumentParser} and adapted using a {@link TreeAdapter}.
58
 */
59
public final class DefinitionPane extends BorderPane {
60
61
  /**
62
   * Contains a view of the definitions.
63
   */
64
  private final TreeView<String> mTreeView = new TreeView<>();
65
66
  /**
67
   * Handlers for key press events.
68
   */
69
  private final Set<EventHandler<? super KeyEvent>> mKeyEventHandlers
70
      = new HashSet<>();
71
72
  /**
73
   * Definition file name shown in the title of the pane.
74
   */
75
  private final StringProperty mFilename = new SimpleStringProperty();
76
77
  private final TitledPane mTitledPane = new TitledPane();
78
79
  /**
80
   * Constructs a definition pane with a given tree view root.
81
   */
82
  public DefinitionPane() {
83
    final var treeView = getTreeView();
84
    treeView.setEditable( true );
85
    treeView.setCellFactory( cell -> createTreeCell() );
86
    treeView.setContextMenu( createContextMenu() );
87
    treeView.addEventFilter( KEY_PRESSED, this::keyEventFilter );
88
    treeView.setShowRoot( false );
89
    getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE );
90
91
    final var bCreate = createButton(
92
        "create", TREE, e -> addItem() );
93
    final var bRename = createButton(
94
        "rename", EDIT, e -> editSelectedItem() );
95
    final var bDelete = createButton(
96
        "delete", TRASH, e -> deleteSelectedItems() );
97
98
    final var buttonBar = new HBox();
99
    buttonBar.getChildren().addAll( bCreate, bRename, bDelete );
100
    buttonBar.setAlignment( CENTER );
101
    buttonBar.setSpacing( 10 );
102
103
    final var titledPane = getTitledPane();
104
    titledPane.textProperty().bind( mFilename );
105
    titledPane.setContent( treeView );
106
    titledPane.setCollapsible( false );
107
    titledPane.setPadding( new Insets( 0, 0, 0, 0 ) );
108
109
    setTop( buttonBar );
110
    setCenter( titledPane );
111
    setAlignment( buttonBar, Pos.TOP_CENTER );
112
    setAlignment( titledPane, Pos.TOP_CENTER );
113
114
    titledPane.prefHeightProperty().bind( this.heightProperty() );
115
  }
116
117
  public void setTooltip( final Tooltip tooltip ) {
118
    getTitledPane().setTooltip( tooltip );
119
  }
120
121
  private TitledPane getTitledPane() {
122
    return mTitledPane;
123
  }
124
125
  private Button createButton(
126
      final String msgKey,
127
      final FontAwesomeIcon icon,
128
      final EventHandler<ActionEvent> eventHandler ) {
129
    final var keyPrefix = "Pane.definition.button." + msgKey;
130
    final var button = new Button( get( keyPrefix + ".label" ) );
131
    button.setOnAction( eventHandler );
132
133
    button.setGraphic(
134
        FontAwesomeIconFactory.get().createIcon( icon )
135
    );
136
    button.setTooltip( new Tooltip( get( keyPrefix + ".tooltip" ) ) );
137
138
    return button;
139
  }
140
141
  /**
142
   * Changes the root of the {@link TreeView} to the root of the
143
   * {@link TreeView} from the {@link DefinitionSource}.
144
   *
145
   * @param definitionSource Container for the hierarchy of key/value pairs
146
   *                         to replace the existing hierarchy.
147
   */
148
  public void update( final DefinitionSource definitionSource ) {
149
    assert definitionSource != null;
150
151
    final TreeAdapter treeAdapter = definitionSource.getTreeAdapter();
152
    final TreeItem<String> root = treeAdapter.adapt(
153
        get( "Pane.definition.node.root.title" )
154
    );
155
156
    getTreeView().setRoot( root );
157
  }
158
159
  public Map<String, String> toMap() {
160
    return TreeItemAdapter.toMap( getTreeView().getRoot() );
161
  }
162
163
  /**
164
   * Informs the caller of whenever any {@link TreeItem} in the {@link TreeView}
165
   * is modified. The modifications include: item value changes, item additions,
166
   * and item removals.
167
   * <p>
168
   * Safe to call multiple times; if a handler is already registered, the
169
   * old handler is used.
170
   * </p>
171
   *
172
   * @param handler The handler to call whenever any {@link TreeItem} changes.
173
   */
174
  public void addTreeChangeHandler(
175
      final EventHandler<TreeItem.TreeModificationEvent<Event>> handler ) {
176
    final TreeItem<String> root = getTreeView().getRoot();
177
    root.addEventHandler( TreeItem.valueChangedEvent(), handler );
178
    root.addEventHandler( TreeItem.childrenModificationEvent(), handler );
179
  }
180
181
  public void addKeyEventHandler(
182
      final EventHandler<? super KeyEvent> handler ) {
183
    getKeyEventHandlers().add( handler );
184
  }
185
186
  /**
187
   * Answers whether the {@link TreeItem}s in the {@link TreeView} are suitably
188
   * well-formed for export. A tree is considered well-formed if the following
189
   * conditions are met:
190
   *
191
   * <ul>
192
   *   <li>The root node contains at least one child node having a leaf.</li>
193
   *   <li>There are no leaf nodes with sibling leaf nodes.</li>
194
   * </ul>
195
   *
196
   * @return {@code null} if the document is well-formed, otherwise the
197
   * problematic child {@link TreeItem}.
198
   */
199
  public TreeItem<String> isTreeWellFormed() {
200
    final var root = getTreeView().getRoot();
201
202
    for( final var child : root.getChildren() ) {
203
      final var problemChild = isWellFormed( child );
204
205
      if( child.isLeaf() || problemChild != null ) {
206
        return problemChild;
207
      }
208
    }
209
210
    return null;
211
  }
212
213
  /**
214
   * Determines whether the document is well-formed by ensuring that
215
   * child branches do not contain multiple leaves.
216
   *
217
   * @param item The sub-tree to check for well-formedness.
218
   * @return {@code null} when the tree is well-formed, otherwise the
219
   * problematic {@link TreeItem}.
220
   */
221
  private TreeItem<String> isWellFormed( final TreeItem<String> item ) {
222
    int childLeafs = 0;
223
    int childBranches = 0;
224
225
    for( final TreeItem<String> child : item.getChildren() ) {
226
      if( child.isLeaf() ) {
227
        childLeafs++;
228
      }
229
      else {
230
        childBranches++;
231
      }
232
233
      final var problemChild = isWellFormed( child );
234
235
      if( problemChild != null ) {
236
        return problemChild;
237
      }
238
    }
239
240
    return ((childBranches > 0 && childLeafs == 0) ||
241
        (childBranches == 0 && childLeafs <= 1)) ? null : item;
242
  }
243
244
  /**
245
   * Delegates to {@link VariableTreeItem#findLeafExact(String)}.
246
   *
247
   * @param text The value to find, never {@code null}.
248
   * @return The leaf that contains the given value, or {@code null} if
249
   * not found.
250
   */
251
  public VariableTreeItem<String> findLeafExact( final String text ) {
252
    return getTreeRoot().findLeafExact( text );
253
  }
254
255
  /**
256
   * Delegates to {@link VariableTreeItem#findLeafContains(String)}.
257
   *
258
   * @param text The value to find, never {@code null}.
259
   * @return The leaf that contains the given value, or {@code null} if
260
   * not found.
261
   */
262
  public VariableTreeItem<String> findLeafContains( final String text ) {
263
    return getTreeRoot().findLeafContains( text );
264
  }
265
266
  /**
267
   * Delegates to {@link VariableTreeItem#findLeafContains(String)}.
268
   *
269
   * @param text The value to find, never {@code null}.
270
   * @return The leaf that contains the given value, or {@code null} if
271
   * not found.
272
   */
273
  public VariableTreeItem<String> findLeafContainsNoCase( final String text ) {
274
    return getTreeRoot().findLeafContainsNoCase( text );
275
  }
276
277
  /**
278
   * Delegates to {@link VariableTreeItem#findLeafStartsWith(String)}.
279
   *
280
   * @param text The value to find, never {@code null}.
281
   * @return The leaf that contains the given value, or {@code null} if
282
   * not found.
283
   */
284
  public VariableTreeItem<String> findLeafStartsWith( final String text ) {
285
    return getTreeRoot().findLeafStartsWith( text );
286
  }
287
288
  /**
289
   * Expands the node to the root, recursively.
290
   *
291
   * @param <T>  The type of tree item to expand (usually String).
292
   * @param node The node to expand.
293
   */
294
  public <T> void expand( final TreeItem<T> node ) {
295
    if( node != null ) {
296
      expand( node.getParent() );
297
298
      if( !node.isLeaf() ) {
299
        node.setExpanded( true );
300
      }
301
    }
302
  }
303
304
  public void select( final TreeItem<String> item ) {
305
    getSelectionModel().clearSelection();
306
    getSelectionModel().select( getTreeView().getRow( item ) );
307
  }
308
309
  /**
310
   * Collapses the tree, recursively.
311
   */
312
  public void collapse() {
313
    collapse( getTreeRoot().getChildren() );
314
  }
315
316
  /**
317
   * Collapses the tree, recursively.
318
   *
319
   * @param <T>   The type of tree item to expand (usually String).
320
   * @param nodes The nodes to collapse.
321
   */
322
  private <T> void collapse( final ObservableList<TreeItem<T>> nodes ) {
323
    for( final var node : nodes ) {
324
      node.setExpanded( false );
325
      collapse( node.getChildren() );
326
    }
327
  }
328
329
  /**
330
   * @return {@code true} when the user is editing a {@link TreeItem}.
331
   */
332
  private boolean isEditingTreeItem() {
333
    return getTreeView().editingItemProperty().getValue() != null;
334
  }
335
336
  /**
337
   * Changes to edit mode for the selected item.
338
   */
339
  private void editSelectedItem() {
340
    getTreeView().edit( getSelectedItem() );
341
  }
342
343
  /**
344
   * Removes all selected items from the {@link TreeView}.
345
   */
346
  private void deleteSelectedItems() {
347
    for( final var item : getSelectedItems() ) {
348
      final var parent = item.getParent();
349
350
      if( parent != null ) {
351
        parent.getChildren().remove( item );
352
      }
353
    }
354
  }
355
356
  /**
357
   * Deletes the selected item.
358
   */
359
  private void deleteSelectedItem() {
360
    final var c = getSelectedItem();
361
    getSiblings( c ).remove( c );
362
  }
363
364
  /**
365
   * Adds a new item under the selected item (or root if nothing is selected).
366
   * There are a few conditions to consider: when adding to the root,
367
   * when adding to a leaf, and when adding to a non-leaf. Items added to the
368
   * root must contain two items: a key and a value.
369
   */
370
  private void addItem() {
371
    final var value = createTreeItem();
372
    getSelectedItem().getChildren().add( value );
373
    expand( value );
374
    select( value );
375
  }
376
377
  private ContextMenu createContextMenu() {
378
    final ContextMenu menu = new ContextMenu();
379
    final ObservableList<MenuItem> items = menu.getItems();
380
381
    addMenuItem( items, "Definition.menu.create" )
382
        .setOnAction( e -> addItem() );
383
384
    addMenuItem( items, "Definition.menu.rename" )
385
        .setOnAction( e -> editSelectedItem() );
386
387
    addMenuItem( items, "Definition.menu.remove" )
388
        .setOnAction( e -> deleteSelectedItem() );
389
390
    return menu;
391
  }
392
393
  /**
394
   * Executes hot-keys for edits to the definition tree.
395
   *
396
   * @param event Contains the key code of the key that was pressed.
397
   */
398
  private void keyEventFilter( final KeyEvent event ) {
399
    if( !isEditingTreeItem() ) {
400
      switch( event.getCode() ) {
401
        case ENTER:
402
          expand( getSelectedItem() );
403
          event.consume();
404
          break;
405
406
        case DELETE:
407
          deleteSelectedItems();
408
          break;
409
410
        case INSERT:
411
          addItem();
412
          break;
413
414
        case R:
415
          if( event.isControlDown() ) {
416
            editSelectedItem();
417
          }
418
419
          break;
420
      }
421
422
      for( final var handler : getKeyEventHandlers() ) {
423
        handler.handle( event );
424
      }
425
    }
426
  }
427
428
  /**
429
   * Adds a menu item to a list of menu items.
430
   *
431
   * @param items    The list of menu items to append to.
432
   * @param labelKey The resource bundle key name for the menu item's label.
433
   * @return The menu item added to the list of menu items.
434
   */
435
  private MenuItem addMenuItem(
436
      final List<MenuItem> items, final String labelKey ) {
437
    final MenuItem menuItem = createMenuItem( labelKey );
438
    items.add( menuItem );
439
    return menuItem;
440
  }
441
442
  private MenuItem createMenuItem( final String labelKey ) {
443
    return new MenuItem( get( labelKey ) );
444
  }
445
446
  private VariableTreeItem<String> createTreeItem() {
447
    return new VariableTreeItem<>( get( "Definition.menu.add.default" ) );
448
  }
449
450
  private TreeCell<String> createTreeCell() {
451
    return new FocusAwareTextFieldTreeCell( createStringConverter() ) {
453452
      @Override
454453
      public void commitEdit( final String newValue ) {
A src/main/java/com/scrivenvar/definition/FocusAwareTextFieldTreeCell.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.Node;
31
import javafx.scene.control.TextField;
32
import javafx.scene.control.cell.TextFieldTreeCell;
33
import javafx.util.StringConverter;
34
35
/**
36
 * Responsible for fixing a focus lost bug in the JavaFX implementation.
37
 * See https://bugs.openjdk.java.net/browse/JDK-8089514 for details.
38
 * This implementation borrows from the official documentation on creating
39
 * tree views: https://docs.oracle.com/javafx/2/ui_controls/tree-view.htm
40
 */
41
public class FocusAwareTextFieldTreeCell extends TextFieldTreeCell<String> {
42
  private TextField mTextField;
43
44
  public FocusAwareTextFieldTreeCell(
45
      final StringConverter<String> converter ) {
46
    super( converter );
47
  }
48
49
  @Override
50
  public void startEdit() {
51
    super.startEdit();
52
    var textField = mTextField;
53
54
    if( textField == null ) {
55
      textField = createTextField();
56
    }
57
    else {
58
      textField.setText( getItem() );
59
    }
60
61
    setText( null );
62
    setGraphic( textField );
63
    textField.selectAll();
64
    textField.requestFocus();
65
66
    // When the focus is lost, commit the edit then close the input field.
67
    // This fixes the unexpected behaviour when user clicks away.
68
    textField.focusedProperty().addListener( ( l, o, n ) -> {
69
      if( !n ) {
70
        commitEdit( mTextField.getText() );
71
      }
72
    } );
73
74
    mTextField = textField;
75
  }
76
77
  @Override
78
  public void cancelEdit() {
79
    super.cancelEdit();
80
    setText( getItem() );
81
    setGraphic( getTreeItem().getGraphic() );
82
  }
83
84
  @Override
85
  public void updateItem( String item, boolean empty ) {
86
    super.updateItem( item, empty );
87
88
    String text = null;
89
    Node graphic = null;
90
91
    if( !empty ) {
92
      if( isEditing() ) {
93
        final var textField = mTextField;
94
95
        if( textField != null ) {
96
          textField.setText( getString() );
97
        }
98
99
        graphic = textField;
100
      }
101
      else {
102
        text = getString();
103
        graphic = getTreeItem().getGraphic();
104
      }
105
    }
106
107
    setText( text );
108
    setGraphic( graphic );
109
  }
110
111
  private TextField createTextField() {
112
    final var textField = new TextField( getString() );
113
114
    textField.setOnKeyReleased( t -> {
115
      switch( t.getCode() ) {
116
        case ENTER -> commitEdit( textField.getText() );
117
        case ESCAPE -> cancelEdit();
118
      }
119
    } );
120
121
    return textField;
122
  }
123
124
  private String getString() {
125
    return getConverter().toString( getItem() );
126
  }
127
}
1128
M src/main/java/com/scrivenvar/editors/EditorPane.java
2828
package com.scrivenvar.editors;
2929
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.Options;
32
import javafx.beans.property.IntegerProperty;
3033
import javafx.beans.property.ObjectProperty;
3134
import javafx.beans.property.SimpleObjectProperty;
...
4346
import java.util.function.Consumer;
4447
48
import static java.lang.String.format;
4549
import static javafx.application.Platform.runLater;
4650
import static org.fxmisc.wellbehaved.event.InputMap.consume;
4751
4852
/**
4953
 * Represents common editing features for various types of text editors.
5054
 */
5155
public class EditorPane extends Pane {
56
57
  private final static Options sOptions = Services.load( Options.class );
58
59
  /**
60
   * Used when changing the text area font size.
61
   */
62
  private static final String FMT_CSS_FONT_SIZE = "-fx-font-size: %dpt;";
5263
5364
  private final StyleClassedTextArea mEditor =
5465
      new StyleClassedTextArea( false );
5566
  private final VirtualizedScrollPane<StyleClassedTextArea> mScrollPane =
5667
      new VirtualizedScrollPane<>( mEditor );
5768
  private final ObjectProperty<Path> mPath = new SimpleObjectProperty<>();
5869
5970
  public EditorPane() {
6071
    getScrollPane().setVbarPolicy( ScrollPane.ScrollBarPolicy.ALWAYS );
72
    fontsSizeProperty().addListener(
73
        ( l, o, n ) -> setFontSize( n.intValue() )
74
    );
6175
  }
6276
...
209223
  public void setPath( final Path path ) {
210224
    mPath.set( path );
225
  }
226
227
  /**
228
   * Sets the font size in points.
229
   *
230
   * @param size The new font size to use for the text editor.
231
   */
232
  private void setFontSize( final int size ) {
233
    mEditor.setStyle( format( FMT_CSS_FONT_SIZE, size ) );
234
  }
235
236
  /**
237
   * Returns the text editor font size property for handling font size change
238
   * events.
239
   */
240
  private IntegerProperty fontsSizeProperty() {
241
    return sOptions.getUserPreferences().fontsSizeEditorProperty();
211242
  }
212243
}
A 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
}
194
A 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
}
1204
A 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
}
1135
A src/main/java/com/scrivenvar/predicates/PredicateFactory.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.predicates;
29
30
import java.io.File;
31
import java.util.Collection;
32
import java.util.function.Predicate;
33
34
import static java.lang.String.join;
35
import static java.nio.file.FileSystems.getDefault;
36
37
/**
38
 * Provides a number of simple {@link Predicate} instances for various types
39
 * of string comparisons, including basic strings and file name strings.
40
 */
41
public class PredicateFactory {
42
  /**
43
   * Creates an instance of {@link Predicate} that matches a globbed file
44
   * name pattern.
45
   *
46
   * @param pattern The file name pattern to match.
47
   * @return A {@link Predicate} that can answer whether a given file name
48
   * matches the given glob pattern.
49
   */
50
  public static Predicate<File> createFileTypePredicate(
51
      final String pattern ) {
52
    final var matcher = getDefault().getPathMatcher(
53
        "glob:**{" + pattern + "}"
54
    );
55
56
    return file -> matcher.matches( file.toPath() );
57
  }
58
59
  /**
60
   * Creates an instance of {@link Predicate} that matches any file name from
61
   * a {@link Collection} of file name patterns. The given patterns are joined
62
   * with commas into a single comma-separated list.
63
   *
64
   * @param patterns The file name patterns to be matched.
65
   * @return A {@link Predicate} that can answer whether a given file name
66
   * matches the given glob patterns.
67
   */
68
  public static Predicate<File> createFileTypePredicate(
69
      final Collection<String> patterns ) {
70
    return createFileTypePredicate( join( ",", patterns ) );
71
  }
72
73
  /**
74
   * Creates an instance of {@link Predicate} that compares whether the given
75
   * {@code reference} string is contained by the comparator. Comparison is
76
   * case-insensitive. The test will also pass if the comparate is empty.
77
   *
78
   * @param comparator The string to check as being contained.
79
   * @return A {@link Predicate} that can answer whether the given string
80
   * is contained within the comparator, or the comparate is empty.
81
   */
82
  public static Predicate<String> createStringContainsPredicate(
83
      final String comparator ) {
84
    return comparate -> comparate.isEmpty() ||
85
        comparate.toLowerCase().contains( comparator.toLowerCase() );
86
  }
187
88
  /**
89
   * Creates an instance of {@link Predicate} that compares whether the given
90
   * {@code reference} string is starts with the comparator. Comparison is
91
   * case-insensitive.
92
   *
93
   * @param comparator The string to check as being contained.
94
   * @return A {@link Predicate} that can answer whether the given string
95
   * is contained within the comparator.
96
   */
97
  public static Predicate<String> createStringStartsPredicate(
98
      final String comparator ) {
99
    return comparate ->
100
        comparate.toLowerCase().startsWith( comparator.toLowerCase() );
101
  }
102
}
D src/main/java/com/scrivenvar/predicates/files/FileTypePredicate.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.predicates.files;
29
30
import java.io.File;
31
import java.nio.file.FileSystems;
32
import java.nio.file.PathMatcher;
33
import java.util.Collection;
34
import java.util.function.Predicate;
35
36
/**
37
 * Responsible for testing whether a given path (to a file) matches one of the
38
 * filename extension patterns provided during construction.
39
 */
40
public class FileTypePredicate implements Predicate<File> {
41
42
  private final PathMatcher mMatcher;
43
44
  /**
45
   * Constructs a new instance given a set of file extension globs.
46
   *
47
   * @param patterns Comma-separated list of globbed extensions including the
48
   * Kleene star (e.g., <code>*.md,*.markdown,*.txt</code>).
49
   */
50
  public FileTypePredicate( final String patterns ) {
51
    mMatcher = FileSystems.getDefault().getPathMatcher(
52
      "glob:**{" + patterns + "}"
53
    );
54
  }
55
56
  /**
57
   * Constructs a new instance given a list of file extension globs, each must
58
   * include the Kleene star (a.k.a. asterisk).
59
   *
60
   * @param patterns Collection of globbed extensions.
61
   */
62
  public FileTypePredicate( final Collection<String> patterns ) {
63
    this( String.join( ",", patterns ) );
64
  }
65
66
  /**
67
   * Returns true if the file matches the patterns defined during construction.
68
   *
69
   * @param file The filename to match against the given glob patterns.
70
   *
71
   * @return false The filename does not match the glob patterns.
72
   */
73
  @Override
74
  public boolean test( final File file ) {
75
    return getMatcher().matches( file.toPath() );
76
  }
77
78
  private PathMatcher getMatcher() {
79
    return mMatcher;
80
  }
81
}
821
D src/main/java/com/scrivenvar/predicates/strings/ContainsPredicate.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.predicates.strings;
29
30
/**
31
 * Determines if one string contains another.
32
 */
33
public class ContainsPredicate extends StringPredicate {
34
35
  /**
36
   * Calls the superclass to construct the instance.
37
   *
38
   * @param comparate Not null.
39
   */
40
  public ContainsPredicate( final String comparate ) {
41
    super( comparate );
42
  }
43
44
  /**
45
   * Answers whether the given strings match each other. What match means will
46
   * depend on user preferences. The empty condition is required to return the
47
   * first node in a list of child nodes when the user has not yet selected a
48
   * node.
49
   *
50
   * @param comparator The string to compare against the comparate.
51
   *
52
   * @return true if s1 and s2 are a match according to some criteria,or s2 is
53
   * empty.
54
   */
55
  @Override
56
  public boolean test( final String comparator ) {
57
    final String comparate = getComparate().toLowerCase();
58
    return comparator.contains( comparate.toLowerCase() )
59
      || comparate.isEmpty();
60
  }
61
}
621
D src/main/java/com/scrivenvar/predicates/strings/StartsPredicate.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.predicates.strings;
29
30
/**
31
 * Determines if a string starts with another.
32
 */
33
public class StartsPredicate extends StringPredicate {
34
35
  /**
36
   * Constructs a new instance using a comparate that will be compared with
37
   * the comparator during the test.
38
   *
39
   * @param comparate The string to compare against the comparator.
40
   */
41
  public StartsPredicate( final String comparate ) {
42
    super( comparate );
43
  }
44
45
  /**
46
   * Compares two strings.
47
   *
48
   * @param comparator A non-null string, possibly empty.
49
   *
50
   * @return true The comparator starts with the comparate, ignoring case.
51
   */
52
  @Override
53
  public boolean test( final String comparator ) {
54
    final String comparate = getComparate().toLowerCase();
55
    return comparator.startsWith( comparate.toLowerCase() );
56
  }
57
}
581
D src/main/java/com/scrivenvar/predicates/strings/StringPredicate.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.predicates.strings;
29
30
import java.util.function.Predicate;
31
32
/**
33
 * General predicate for different types of string comparisons.
34
 */
35
public abstract class StringPredicate implements Predicate<String> {
36
37
  private final String comparate;
38
39
  public StringPredicate( final String comparate ) {
40
    this.comparate = comparate;
41
  }
42
43
  protected String getComparate() {
44
    return this.comparate;
45
  }
46
}
471
M src/main/java/com/scrivenvar/preferences/UserPreferences.java
3434
import com.dlsc.preferencesfx.model.Group;
3535
import com.dlsc.preferencesfx.model.Setting;
36
import com.scrivenvar.Services;
37
import com.scrivenvar.service.Settings;
38
import javafx.beans.property.ObjectProperty;
39
import javafx.beans.property.SimpleObjectProperty;
40
import javafx.beans.property.SimpleStringProperty;
41
import javafx.beans.property.StringProperty;
36
import javafx.beans.property.*;
4237
import javafx.event.EventHandler;
4338
import javafx.scene.Node;
...
5550
 */
5651
public class UserPreferences {
57
  private final Settings SETTINGS = Services.load( Settings.class );
52
  private final PreferencesFx mPreferencesFx;
5853
5954
  private final ObjectProperty<File> mPropRDirectory;
6055
  private final StringProperty mPropRScript;
6156
  private final ObjectProperty<File> mPropImagesDirectory;
6257
  private final StringProperty mPropImagesOrder;
6358
  private final ObjectProperty<File> mPropDefinitionPath;
6459
  private final StringProperty mRDelimiterBegan;
6560
  private final StringProperty mRDelimiterEnded;
66
67
  private final PreferencesFx mPreferencesFx;
61
  private final IntegerProperty mPropFontsSizeEditor;
6862
6963
  public UserPreferences() {
7064
    mPropRDirectory = simpleFile( USER_DIRECTORY );
7165
    mPropRScript = new SimpleStringProperty( "" );
7266
7367
    mPropImagesDirectory = simpleFile( USER_DIRECTORY );
7468
    mPropImagesOrder = new SimpleStringProperty( PERSIST_IMAGES_DEFAULT );
7569
76
    mPropDefinitionPath = simpleFile( getSetting(
77
        "file.definition.default", "variables.yaml" )
70
    mPropDefinitionPath = simpleFile(
71
        getSetting( "file.definition.default", DEFINITION_NAME )
7872
    );
7973
8074
    mRDelimiterBegan = new SimpleStringProperty( R_DELIMITER_BEGAN_DEFAULT );
8175
    mRDelimiterEnded = new SimpleStringProperty( R_DELIMITER_ENDED_DEFAULT );
76
77
    mPropFontsSizeEditor = new SimpleIntegerProperty( FONT_SIZE_EDITOR );
8278
79
    // All properties must be initialized before creating the dialog.
8380
    mPreferencesFx = createPreferencesFx();
8481
  }
...
159156
                Setting.of( label( "Preferences.definitions.path.desc" ) ),
160157
                Setting.of( "Path", mPropDefinitionPath, false )
158
            )
159
        ),
160
        Category.of(
161
            get( "Preferences.fonts" ),
162
            Group.of(
163
                get( "Preferences.fonts.size_editor" ),
164
                Setting.of( label( "Preferences.fonts.size_editor.desc" ) ),
165
                Setting.of( "Points", mPropFontsSizeEditor )
161166
            )
162167
        )
163
    );
168
    ).instantPersistent( false );
164169
  }
165170
...
277282
  public String getImagesOrder() {
278283
    return imagesOrderProperty().getValue();
284
  }
285
286
  public IntegerProperty fontsSizeEditorProperty() {
287
    return mPropFontsSizeEditor;
288
  }
289
290
  /**
291
   * Returns the preferred font size of the text editor.
292
   *
293
   * @return A non-negative integer, in points.
294
   */
295
  public int getFontsSizeEditor() {
296
    return mPropFontsSizeEditor.intValue();
279297
  }
280298
}
M src/main/java/com/scrivenvar/preview/ChainedReplacedElementFactory.java
11
/*
2
 * {{{ header & license
32
 * Copyright 2006 Patrick Wright
43
 * Copyright 2007 Wisconsin Court System
4
 * Copyright 2020 White Magic Software, Ltd.
55
 *
66
 * This program is free software; you can redistribute it and/or
...
1717
 * along with this program; if not, write to the Free Software
1818
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
 * }}}
2019
 */
2120
package com.scrivenvar.preview;
2221
22
import com.scrivenvar.adapters.ReplacedElementAdapter;
2323
import org.w3c.dom.Element;
2424
import org.xhtmlrenderer.extend.ReplacedElement;
2525
import org.xhtmlrenderer.extend.ReplacedElementFactory;
2626
import org.xhtmlrenderer.extend.UserAgentCallback;
2727
import org.xhtmlrenderer.layout.LayoutContext;
2828
import org.xhtmlrenderer.render.BlockBox;
29
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;
3029
3130
import java.util.ArrayList;
3231
import java.util.List;
3332
34
public class ChainedReplacedElementFactory implements ReplacedElementFactory {
33
public class ChainedReplacedElementFactory extends ReplacedElementAdapter {
3534
  private final List<ReplacedElementFactory> mFactoryList = new ArrayList<>();
36
37
  public ChainedReplacedElementFactory() {
38
  }
3935
36
  @Override
4037
  public ReplacedElement createReplacedElement(
4138
      final LayoutContext c,
4239
      final BlockBox box,
4340
      final UserAgentCallback uac,
4441
      final int cssWidth,
4542
      final int cssHeight ) {
4643
    for( final var f : mFactoryList ) {
47
      final var r = f.createReplacedElement( c, box, uac, cssWidth, cssHeight );
44
      final var r = f.createReplacedElement(
45
          c, box, uac, cssWidth, cssHeight );
4846
4947
      if( r != null ) {
5048
        return r;
5149
      }
5250
    }
5351
5452
    return null;
55
  }
56
57
  public void addFactory( final ReplacedElementFactory factory ) {
58
    mFactoryList.add( factory );
5953
  }
6054
55
  @Override
6156
  public void reset() {
6257
    for( final var factory : mFactoryList ) {
6358
      factory.reset();
6459
    }
6560
  }
6661
62
  @Override
6763
  public void remove( final Element element ) {
6864
    for( final var factory : mFactoryList ) {
6965
      factory.remove( element );
7066
    }
7167
  }
7268
73
  public void setFormSubmissionListener( FormSubmissionListener listener ) {
69
  public void addFactory( final ReplacedElementFactory factory ) {
70
    mFactoryList.add( factory );
7471
  }
7572
}
M src/main/java/com/scrivenvar/preview/CustomImageLoader.java
3939
import java.nio.file.Paths;
4040
41
import static com.scrivenvar.preview.SVGRasterizer.BROKEN_IMAGE_PLACEHOLDER;
41
import static com.scrivenvar.graphics.SVGRasterizer.BROKEN_IMAGE_PLACEHOLDER;
4242
import static org.xhtmlrenderer.swing.AWTFSImage.createImage;
4343
...
5454
5555
  private final IntegerProperty mWidthProperty = new SimpleIntegerProperty();
56
57
  public CustomImageLoader() {
58
  }
5956
6057
  /**
M src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
2929
3030
import com.scrivenvar.Services;
31
import com.scrivenvar.service.events.Notifier;
32
import javafx.beans.property.BooleanProperty;
33
import javafx.beans.property.SimpleBooleanProperty;
34
import javafx.beans.value.ChangeListener;
35
import javafx.beans.value.ObservableValue;
36
import javafx.embed.swing.SwingNode;
37
import javafx.scene.Node;
38
import javafx.scene.layout.Pane;
39
import org.jsoup.Jsoup;
40
import org.jsoup.helper.W3CDom;
41
import org.jsoup.nodes.Document;
42
import org.xhtmlrenderer.event.DocumentListener;
43
import org.xhtmlrenderer.layout.SharedContext;
44
import org.xhtmlrenderer.render.Box;
45
import org.xhtmlrenderer.simple.XHTMLPanel;
46
import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler;
47
import org.xhtmlrenderer.swing.*;
48
49
import javax.swing.*;
50
import java.awt.*;
51
import java.awt.event.ComponentEvent;
52
import java.awt.event.ComponentListener;
53
import java.net.URI;
54
import java.nio.file.Path;
55
56
import static com.scrivenvar.Constants.*;
57
import static java.awt.Desktop.Action.BROWSE;
58
import static java.awt.Desktop.getDesktop;
59
import static org.xhtmlrenderer.swing.ImageResourceLoader.NO_OP_REPAINT_LISTENER;
60
61
/**
62
 * HTML preview pane is responsible for rendering an HTML document.
63
 */
64
public final class HTMLPreviewPane extends Pane {
65
  private final static Notifier NOTIFIER = Services.load( Notifier.class );
66
67
  /**
68
   * Suppresses scrolling to the top on every key press.
69
   */
70
  private static class HTMLPanel extends XHTMLPanel {
71
    @Override
72
    public void resetScrollPosition() {
73
    }
74
  }
75
76
  /**
77
   * Suppresses scroll attempts until after the document has loaded.
78
   */
79
  private static final class DocumentEventHandler implements DocumentListener {
80
    private final BooleanProperty mReadyProperty = new SimpleBooleanProperty();
81
82
    public BooleanProperty readyProperty() {
83
      return mReadyProperty;
84
    }
85
86
    @Override
87
    public void documentStarted() {
88
      mReadyProperty.setValue( Boolean.FALSE );
89
    }
90
91
    @Override
92
    public void documentLoaded() {
93
      mReadyProperty.setValue( Boolean.TRUE );
94
    }
95
96
    @Override
97
    public void onLayoutException( final Throwable t ) {
98
    }
99
100
    @Override
101
    public void onRenderException( final Throwable t ) {
102
    }
103
  }
104
105
  /**
106
   * Responsible for ensuring that images are constrained to the panel width
107
   * upon resizing.
108
   */
109
  private final class ResizeListener implements ComponentListener {
110
    @Override
111
    public void componentResized( final ComponentEvent e ) {
112
      setWidth( e );
113
    }
114
115
    @Override
116
    public void componentShown( final ComponentEvent e ) {
117
      setWidth( e );
118
    }
119
120
    @Override
121
    public void componentMoved( final ComponentEvent e ) {
122
    }
123
124
    @Override
125
    public void componentHidden( final ComponentEvent e ) {
126
    }
127
128
    /**
129
     * Sets the width of the {@link HTMLPreviewPane} so that images can be
130
     * scaled to fit. The scale factor is adjusted a bit below the full width
131
     * to prevent the horizontal scrollbar from appearing.
132
     *
133
     * @param e The component that defines the image scaling width.
134
     */
135
    private void setWidth( final ComponentEvent e ) {
136
      final int width = (int) (e.getComponent().getWidth() * .95);
137
      HTMLPreviewPane.this.mImageLoader.widthProperty().set( width );
138
    }
139
  }
140
141
  /**
142
   * Responsible for launching hyperlinks in the system's default browser.
143
   */
144
  private static class HyperlinkListener extends LinkListener {
145
    @Override
146
    public void linkClicked( final BasicPanel panel, final String uri ) {
147
      try {
148
        final var desktop = getDesktop();
149
150
        if( desktop.isSupported( BROWSE ) ) {
151
          desktop.browse( new URI( uri ) );
152
        }
153
      } catch( final Exception e ) {
154
        NOTIFIER.notify( e );
155
      }
156
    }
157
  }
158
159
  /**
160
   * The CSS must be rendered in points (pt) not pixels (px) to avoid blurry
161
   * rendering on some platforms.
162
   */
163
  private final static String HTML_PREFIX = "<!DOCTYPE html>"
164
      + "<html>"
165
      + "<head>"
166
      + "<link rel='stylesheet' href='" +
167
      HTMLPreviewPane.class.getResource( STYLESHEET_PREVIEW ) + "'/>"
168
      + "</head>"
169
      + "<body>";
170
171
  // Provide some extra space at the end for scrolling past the last line.
172
  private final static String HTML_SUFFIX =
173
      "<p style='height=2em'>&nbsp;</p></body></html>";
174
175
  private final static W3CDom W3C_DOM = new W3CDom();
176
  private final static XhtmlNamespaceHandler NS_HANDLER =
177
      new XhtmlNamespaceHandler();
178
179
  private final StringBuilder mHtmlDocument = new StringBuilder( 65536 );
180
  private final int mHtmlPrefixLength;
181
182
  private final HTMLPanel mHtmlRenderer = new HTMLPanel();
183
  private final SwingNode mSwingNode = new SwingNode();
184
  private final JScrollPane mScrollPane = new JScrollPane( mHtmlRenderer );
185
  private final DocumentEventHandler mDocHandler = new DocumentEventHandler();
186
  private final CustomImageLoader mImageLoader = new CustomImageLoader();
187
188
  private Path mPath = DEFAULT_DIRECTORY;
189
190
  /**
191
   * Creates a new preview pane that can scroll to the caret position within the
192
   * document.
193
   */
194
  public HTMLPreviewPane() {
195
    setStyle( "-fx-background-color: white;" );
196
197
    // No need to append same prefix each time the HTML content is updated.
198
    mHtmlDocument.append( HTML_PREFIX );
199
    mHtmlPrefixLength = mHtmlDocument.length();
200
201
    // Inject an SVG renderer that produces high-quality SVG buffered images.
202
    final var factory = new ChainedReplacedElementFactory();
203
    factory.addFactory( new SVGReplacedElementFactory() );
204
    factory.addFactory( new SwingReplacedElementFactory(
205
        NO_OP_REPAINT_LISTENER, mImageLoader ) );
206
207
    final var context = getSharedContext();
208
    context.setReplacedElementFactory( factory );
209
    context.getTextRenderer().setSmoothingThreshold( 0 );
210
211
    mSwingNode.setContent( mScrollPane );
212
    mSwingNode.setCache( true );
213
214
    mHtmlRenderer.addDocumentListener( mDocHandler );
215
    mHtmlRenderer.addComponentListener( new ResizeListener() );
216
217
    // The default mouse click listener attempts navigation within the
218
    // preview panel. We want to usurp that behaviour to open the link in
219
    // a platform-specific browser.
220
    for( final var listener : mHtmlRenderer.getMouseTrackingListeners() ) {
221
      if( !(listener instanceof HoverListener) ) {
222
        mHtmlRenderer.removeMouseTrackingListener( (FSMouseListener) listener );
223
      }
224
    }
225
226
    mHtmlRenderer.addMouseTrackingListener( new HyperlinkListener() );
227
  }
228
229
  /**
230
   * Updates the internal HTML source, loads it into the preview pane, then
231
   * scrolls to the caret position.
232
   *
233
   * @param html The new HTML document to display.
234
   */
235
  public void process( final String html ) {
236
    final Document jsoupDoc = Jsoup.parse( decorate( html ) );
237
    final org.w3c.dom.Document w3cDoc = W3C_DOM.fromJsoup( jsoupDoc );
238
239
    mHtmlRenderer.setDocument( w3cDoc, getBaseUrl(), NS_HANDLER );
240
  }
241
242
  public void clear() {
243
    process( "" );
244
  }
245
246
  /**
247
   * Scrolls to an anchor link. The anchor links are injected when the
248
   * HTML document is created.
249
   *
250
   * @param id The unique anchor link identifier.
251
   */
252
  public void tryScrollTo( final int id ) {
253
    final ChangeListener<Boolean> listener = new ChangeListener<>() {
254
      @Override
255
      public void changed(
256
          final ObservableValue<? extends Boolean> observable,
257
          final Boolean oldValue,
258
          final Boolean newValue ) {
259
        if( newValue ) {
260
          scrollTo( id );
261
262
          mDocHandler.readyProperty().removeListener( this );
263
        }
264
      }
265
    };
266
267
    mDocHandler.readyProperty().addListener( listener );
268
  }
269
270
  /**
271
   * Scrolls to the closest element matching the given identifier without
272
   * waiting for the document to be ready. Be sure the document is ready
273
   * before calling this method.
274
   *
275
   * @param id Paragraph index.
276
   */
277
  public void scrollTo( final int id ) {
278
    if( id < 2 ) {
279
      scrollToTop();
280
    }
281
    else {
282
      Box box = findPrevBox( id );
283
      box = box == null ? findNextBox( id + 1 ) : box;
284
285
      if( box == null ) {
286
        scrollToBottom();
287
      }
288
      else {
289
        scrollTo( box );
290
      }
291
    }
292
  }
293
294
  private Box findPrevBox( final int id ) {
295
    int prevId = id;
296
    Box box = null;
297
298
    while( prevId > 0 && (box = getBoxById( PARAGRAPH_ID_PREFIX + prevId )) == null ) {
299
      prevId--;
300
    }
301
302
    return box;
303
  }
304
305
  private Box findNextBox( final int id ) {
306
    int nextId = id;
307
    Box box = null;
308
309
    while( nextId - id < 5 &&
310
        (box = getBoxById( PARAGRAPH_ID_PREFIX + nextId )) == null ) {
311
      nextId++;
312
    }
313
314
    return box;
315
  }
316
317
  private void scrollTo( final Point point ) {
318
    mHtmlRenderer.scrollTo( point );
319
  }
320
321
  private void scrollTo( final Box box ) {
322
    scrollTo( createPoint( box ) );
323
  }
324
325
  private void scrollToY( final int y ) {
326
    scrollTo( new Point( 0, y ) );
327
  }
328
329
  private void scrollToTop() {
330
    scrollToY( 0 );
331
  }
332
333
  private void scrollToBottom() {
334
    scrollToY( mHtmlRenderer.getHeight() );
335
  }
336
337
  private Box getBoxById( final String id ) {
338
    return getSharedContext().getBoxById( id );
339
  }
340
341
  private String decorate( final String html ) {
342
    // Trim the HTML back to only the prefix.
343
    mHtmlDocument.setLength( mHtmlPrefixLength );
344
345
    // Write the HTML body element followed by closing tags.
346
    return mHtmlDocument.append( html )
347
                        .append( HTML_SUFFIX )
348
                        .toString();
349
  }
350
351
  public Path getPath() {
352
    return mPath;
353
  }
354
355
  public void setPath( final Path path ) {
356
    assert path != null;
357
    mPath = path;
358
  }
359
360
  /**
361
   * Content to embed in a panel.
362
   *
363
   * @return The content to display to the user.
364
   */
365
  public Node getNode() {
366
    return mSwingNode;
367
  }
368
369
  public JScrollPane getScrollPane() {
370
    return mScrollPane;
371
  }
372
373
  public JScrollBar getVerticalScrollBar() {
374
    return getScrollPane().getVerticalScrollBar();
375
  }
376
377
  /**
378
   * Creates a {@link Point} to use as a reference for scrolling to the area
379
   * described by the given {@link Box}. The {@link Box} coordinates are used
380
   * to populate the {@link Point}'s location, with minor adjustments for
381
   * vertical centering.
382
   *
383
   * @param box The {@link Box} that represents a scrolling anchor reference.
384
   * @return A coordinate suitable for scrolling to.
385
   */
386
  private Point createPoint( final Box box ) {
387
    assert box != null;
388
389
    int x = box.getAbsX();
390
391
    // Scroll back up by half the height of the scroll bar to keep the typing
392
    // area within the view port. Otherwise the view port will have jumped too
393
    // high up and the whatever gets typed won't be visible.
394
    int y = Math.max(
31
import com.scrivenvar.adapters.DocumentAdapter;
32
import com.scrivenvar.graphics.SVGReplacedElementFactory;
33
import com.scrivenvar.service.events.Notifier;
34
import javafx.beans.property.BooleanProperty;
35
import javafx.beans.property.SimpleBooleanProperty;
36
import javafx.beans.value.ChangeListener;
37
import javafx.beans.value.ObservableValue;
38
import javafx.embed.swing.SwingNode;
39
import javafx.scene.Node;
40
import org.jsoup.Jsoup;
41
import org.jsoup.helper.W3CDom;
42
import org.jsoup.nodes.Document;
43
import org.xhtmlrenderer.layout.SharedContext;
44
import org.xhtmlrenderer.render.Box;
45
import org.xhtmlrenderer.simple.XHTMLPanel;
46
import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler;
47
import org.xhtmlrenderer.swing.*;
48
49
import javax.swing.*;
50
import java.awt.*;
51
import java.awt.event.ComponentAdapter;
52
import java.awt.event.ComponentEvent;
53
import java.net.URI;
54
import java.nio.file.Path;
55
56
import static com.scrivenvar.Constants.*;
57
import static java.awt.Desktop.Action.BROWSE;
58
import static java.awt.Desktop.getDesktop;
59
import static java.lang.Math.max;
60
import static javax.swing.SwingUtilities.invokeLater;
61
import static org.xhtmlrenderer.swing.ImageResourceLoader.NO_OP_REPAINT_LISTENER;
62
63
/**
64
 * HTML preview pane is responsible for rendering an HTML document.
65
 */
66
public final class HTMLPreviewPane extends SwingNode {
67
  private final static Notifier sNotifier = Services.load( Notifier.class );
68
69
  private static class HTMLPanel extends XHTMLPanel {
70
    /**
71
     * Suppresses scrolling to the top on every key press.
72
     */
73
    @Override
74
    public void resetScrollPosition() {
75
    }
76
  }
77
78
  /**
79
   * Suppresses scroll attempts until after the document has loaded.
80
   */
81
  private static final class DocumentEventHandler extends DocumentAdapter {
82
    private final BooleanProperty mReadyProperty = new SimpleBooleanProperty();
83
84
    public BooleanProperty readyProperty() {
85
      return mReadyProperty;
86
    }
87
88
    @Override
89
    public void documentStarted() {
90
      mReadyProperty.setValue( Boolean.FALSE );
91
    }
92
93
    @Override
94
    public void documentLoaded() {
95
      mReadyProperty.setValue( Boolean.TRUE );
96
    }
97
  }
98
99
  /**
100
   * Responsible for ensuring that images are constrained to the panel width
101
   * upon resizing.
102
   */
103
  private final class ResizeListener extends ComponentAdapter {
104
    @Override
105
    public void componentResized( final ComponentEvent e ) {
106
      setWidth( e );
107
    }
108
109
    @Override
110
    public void componentShown( final ComponentEvent e ) {
111
      setWidth( e );
112
    }
113
114
    /**
115
     * Sets the width of the {@link HTMLPreviewPane} so that images can be
116
     * scaled to fit. The scale factor is adjusted a bit below the full width
117
     * to prevent the horizontal scrollbar from appearing.
118
     *
119
     * @param e The component that defines the image scaling width.
120
     */
121
    private void setWidth( final ComponentEvent e ) {
122
      final int width = (int) (e.getComponent().getWidth() * .95);
123
      HTMLPreviewPane.this.mImageLoader.widthProperty().set( width );
124
    }
125
  }
126
127
  /**
128
   * Responsible for launching hyperlinks in the system's default browser.
129
   */
130
  private static class HyperlinkListener extends LinkListener {
131
    @Override
132
    public void linkClicked( final BasicPanel panel, final String uri ) {
133
      try {
134
        final var desktop = getDesktop();
135
136
        if( desktop.isSupported( BROWSE ) ) {
137
          desktop.browse( new URI( uri ) );
138
        }
139
      } catch( final Exception e ) {
140
        sNotifier.notify( e );
141
      }
142
    }
143
  }
144
145
  /**
146
   * The CSS must be rendered in points (pt) not pixels (px) to avoid blurry
147
   * rendering on some platforms.
148
   */
149
  private final static String HTML_PREFIX = "<!DOCTYPE html>"
150
      + "<html>"
151
      + "<head>"
152
      + "<link rel='stylesheet' href='" +
153
      HTMLPreviewPane.class.getResource( STYLESHEET_PREVIEW ) + "'/>"
154
      + "</head>"
155
      + "<body>";
156
157
  // Provide some extra space at the end for scrolling past the last line.
158
  private final static String HTML_SUFFIX =
159
      "<p style='height=2em'>&nbsp;</p></body></html>";
160
161
  private final static W3CDom W3C_DOM = new W3CDom();
162
  private final static XhtmlNamespaceHandler NS_HANDLER =
163
      new XhtmlNamespaceHandler();
164
165
  private final StringBuilder mHtmlDocument = new StringBuilder( 65536 );
166
  private final int mHtmlPrefixLength;
167
168
  private final HTMLPanel mHtmlRenderer = new HTMLPanel();
169
  private final JScrollPane mScrollPane = new JScrollPane( mHtmlRenderer );
170
  private final DocumentEventHandler mDocHandler = new DocumentEventHandler();
171
  private final CustomImageLoader mImageLoader = new CustomImageLoader();
172
173
  private Path mPath = DEFAULT_DIRECTORY;
174
175
  /**
176
   * Creates a new preview pane that can scroll to the caret position within the
177
   * document.
178
   */
179
  public HTMLPreviewPane() {
180
    setStyle( "-fx-background-color: white;" );
181
182
    // No need to append same prefix each time the HTML content is updated.
183
    mHtmlDocument.append( HTML_PREFIX );
184
    mHtmlPrefixLength = mHtmlDocument.length();
185
186
    // Inject an SVG renderer that produces high-quality SVG buffered images.
187
    final var factory = new ChainedReplacedElementFactory();
188
    factory.addFactory( new SVGReplacedElementFactory() );
189
    factory.addFactory( new SwingReplacedElementFactory(
190
        NO_OP_REPAINT_LISTENER, mImageLoader ) );
191
192
    final var context = getSharedContext();
193
    final var textRenderer = context.getTextRenderer();
194
    context.setReplacedElementFactory( factory );
195
    textRenderer.setSmoothingThreshold( 0 );
196
197
    setContent( mScrollPane );
198
    mHtmlRenderer.addDocumentListener( mDocHandler );
199
    mHtmlRenderer.addComponentListener( new ResizeListener() );
200
201
    // The default mouse click listener attempts navigation within the
202
    // preview panel. We want to usurp that behaviour to open the link in
203
    // a platform-specific browser.
204
    for( final var listener : mHtmlRenderer.getMouseTrackingListeners() ) {
205
      if( !(listener instanceof HoverListener) ) {
206
        mHtmlRenderer.removeMouseTrackingListener( (FSMouseListener) listener );
207
      }
208
    }
209
210
    mHtmlRenderer.addMouseTrackingListener( new HyperlinkListener() );
211
  }
212
213
  /**
214
   * Updates the internal HTML source, loads it into the preview pane, then
215
   * scrolls to the caret position.
216
   *
217
   * @param html The new HTML document to display.
218
   */
219
  public void process( final String html ) {
220
    final Document jsoupDoc = Jsoup.parse( decorate( html ) );
221
    final org.w3c.dom.Document w3cDoc = W3C_DOM.fromJsoup( jsoupDoc );
222
223
    // Access to a Swing component must occur from the Event Dispatch
224
    // thread according to Swing threading restrictions.
225
    invokeLater(
226
        () -> mHtmlRenderer.setDocument( w3cDoc, getBaseUrl(), NS_HANDLER )
227
    );
228
  }
229
230
  public void clear() {
231
    process( "" );
232
  }
233
234
  /**
235
   * Scrolls to an anchor link. The anchor links are injected when the
236
   * HTML document is created.
237
   *
238
   * @param id The unique anchor link identifier.
239
   */
240
  public void tryScrollTo( final int id ) {
241
    final ChangeListener<Boolean> listener = new ChangeListener<>() {
242
      @Override
243
      public void changed(
244
          final ObservableValue<? extends Boolean> observable,
245
          final Boolean oldValue,
246
          final Boolean newValue ) {
247
        if( newValue ) {
248
          scrollTo( id );
249
250
          mDocHandler.readyProperty().removeListener( this );
251
        }
252
      }
253
    };
254
255
    mDocHandler.readyProperty().addListener( listener );
256
  }
257
258
  /**
259
   * Scrolls to the closest element matching the given identifier without
260
   * waiting for the document to be ready. Be sure the document is ready
261
   * before calling this method.
262
   *
263
   * @param id Paragraph index.
264
   */
265
  public void scrollTo( final int id ) {
266
    if( id < 2 ) {
267
      scrollToTop();
268
    }
269
    else {
270
      Box box = findPrevBox( id );
271
      box = box == null ? findNextBox( id + 1 ) : box;
272
273
      if( box == null ) {
274
        scrollToBottom();
275
      }
276
      else {
277
        scrollTo( box );
278
      }
279
    }
280
  }
281
282
  private Box findPrevBox( final int id ) {
283
    int prevId = id;
284
    Box box = null;
285
286
    while( prevId > 0 && (box = getBoxById( PARAGRAPH_ID_PREFIX + prevId )) == null ) {
287
      prevId--;
288
    }
289
290
    return box;
291
  }
292
293
  private Box findNextBox( final int id ) {
294
    int nextId = id;
295
    Box box = null;
296
297
    while( nextId - id < 5 &&
298
        (box = getBoxById( PARAGRAPH_ID_PREFIX + nextId )) == null ) {
299
      nextId++;
300
    }
301
302
    return box;
303
  }
304
305
  private void scrollTo( final Point point ) {
306
    invokeLater( () -> mHtmlRenderer.scrollTo( point ) );
307
  }
308
309
  private void scrollTo( final Box box ) {
310
    scrollTo( createPoint( box ) );
311
  }
312
313
  private void scrollToY( final int y ) {
314
    scrollTo( new Point( 0, y ) );
315
  }
316
317
  private void scrollToTop() {
318
    scrollToY( 0 );
319
  }
320
321
  private void scrollToBottom() {
322
    scrollToY( mHtmlRenderer.getHeight() );
323
  }
324
325
  private Box getBoxById( final String id ) {
326
    return getSharedContext().getBoxById( id );
327
  }
328
329
  private String decorate( final String html ) {
330
    // Trim the HTML back to only the prefix.
331
    mHtmlDocument.setLength( mHtmlPrefixLength );
332
333
    // Write the HTML body element followed by closing tags.
334
    return mHtmlDocument.append( html ).append( HTML_SUFFIX ).toString();
335
  }
336
337
  public Path getPath() {
338
    return mPath;
339
  }
340
341
  public void setPath( final Path path ) {
342
    assert path != null;
343
    mPath = path;
344
  }
345
346
  /**
347
   * Content to embed in a panel.
348
   *
349
   * @return The content to display to the user.
350
   */
351
  public Node getNode() {
352
    return this;
353
  }
354
355
  public JScrollPane getScrollPane() {
356
    return mScrollPane;
357
  }
358
359
  public JScrollBar getVerticalScrollBar() {
360
    return getScrollPane().getVerticalScrollBar();
361
  }
362
363
  /**
364
   * Creates a {@link Point} to use as a reference for scrolling to the area
365
   * described by the given {@link Box}. The {@link Box} coordinates are used
366
   * to populate the {@link Point}'s location, with minor adjustments for
367
   * vertical centering.
368
   *
369
   * @param box The {@link Box} that represents a scrolling anchor reference.
370
   * @return A coordinate suitable for scrolling to.
371
   */
372
  private Point createPoint( final Box box ) {
373
    assert box != null;
374
375
    int x = box.getAbsX();
376
377
    // Scroll back up by half the height of the scroll bar to keep the typing
378
    // area within the view port. Otherwise the view port will have jumped too
379
    // high up and the whatever gets typed won't be visible.
380
    int y = max(
395381
        box.getAbsY() - (mScrollPane.getVerticalScrollBar().getHeight() / 2),
396382
        0 );
D 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 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
import java.util.Map;
47
48
import static java.awt.Color.WHITE;
49
import static java.awt.RenderingHints.*;
50
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
51
import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH;
52
import static org.apache.batik.transcoder.image.ImageTranscoder.KEY_BACKGROUND_COLOR;
53
import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName;
54
55
/**
56
 * Responsible for converting SVG images into rasterized PNG images.
57
 */
58
public class SVGRasterizer {
59
  private final static Notifier NOTIFIER = Services.load( Notifier.class );
60
61
  private final static SAXSVGDocumentFactory mFactory =
62
      new SAXSVGDocumentFactory( getXMLParserClassName() );
63
64
  public final static Map<Object, Object> RENDERING_HINTS = Map.of(
65
      KEY_ANTIALIASING,
66
      VALUE_ANTIALIAS_ON,
67
      KEY_ALPHA_INTERPOLATION,
68
      VALUE_ALPHA_INTERPOLATION_QUALITY,
69
      KEY_COLOR_RENDERING,
70
      VALUE_COLOR_RENDER_QUALITY,
71
      KEY_DITHERING,
72
      VALUE_DITHER_DISABLE,
73
      KEY_FRACTIONALMETRICS,
74
      VALUE_FRACTIONALMETRICS_ON,
75
      KEY_INTERPOLATION,
76
      VALUE_INTERPOLATION_BICUBIC,
77
      KEY_RENDERING,
78
      VALUE_RENDER_QUALITY,
79
      KEY_STROKE_CONTROL,
80
      VALUE_STROKE_PURE,
81
      KEY_TEXT_ANTIALIASING,
82
      VALUE_TEXT_ANTIALIAS_ON
83
  );
84
85
  public final static Image BROKEN_IMAGE_PLACEHOLDER;
86
87
  static {
88
    // A FontAwesome camera icon, cleft asunder.
89
    final String BROKEN_IMAGE_SVG = "<svg height='19pt' viewBox='0 0 25 19' " +
90
        "width='25pt' xmlns='http://www.w3.org/2000/svg'><g " +
91
        "fill='#454545'><path d='m8.042969 11.085938c.332031 1.445312 1" +
92
        ".660156 2.503906 3.214843 2.558593zm0 0'/><path d='m6.792969 9" +
93
        ".621094-.300781.226562.242187.195313c.015625-.144531.03125-.28125" +
94
        ".058594-.421875zm0 0'/><path d='m10.597656.949219-2.511718.207031c-" +
95
        ".777344.066406-1.429688.582031-1.636719 1.292969l-.367188 1.253906-3" +
96
        ".414062.28125c-1.027344.085937-1.792969.949219-1.699219 1.925781l" +
97
        ".976562 10.621094c.089844.976562.996094 1.699219 2.023438 1" +
98
        ".613281l11.710938-.972656-3.117188-2.484375c-.246094.0625-.5.109375-" +
99
        ".765625.132812-2.566406.210938-4.835937-1.597656-5.0625-4.039062-" +
100
        ".023437-.25-.019531-.496094 0-.738281l-.242187-.195313.300781-" +
101
        ".226562c.359375-1.929688 2.039062-3.472656 4.191406-3.652344.207031-" +
102
        ".015625.414063-.015625.617187-.007812l.933594-.707032zm0 0'/><path " +
103
        "d='m10.234375 11.070312 2.964844 2.820313c.144531.015625.285156" +
104
        ".027344.433593.027344 1.890626 0 3.429688-1.460938 3.429688-3.257813" +
105
        " 0-1.792968-1.539062-3.257812-3.429688-3.257812-1.890624 0-3.429687 " +
106
        "1.464844-3.429687 3.257812 0 .140625.011719.277344.03125.410156zm0 " +
107
        "0'/><path d='m14.488281.808594 1.117188 4.554687-1.042969.546875c2" +
108
        ".25.476563 3.84375 2.472656 3.636719 4.714844-.199219 2.191406-2" +
109
        ".050781 3.871094-4.285157 4.039062l2.609376 2.957032 4.4375.371094c1" +
110
        ".03125.085937 1.9375-.640626 2.027343-1.617188l.976563-10.617188c" +
111
        ".089844-.980468-.667969-1.839843-1.699219-1.925781l-3.414063-" +
112
        ".285156-.371093-1.253906c-.207031-.710938-.859375-1.226563-1" +
113
        ".636719-1.289063zm0 0'/></g></svg>";
114
115
    // The width and height cannot be embedded in the SVG above because the
116
    // path element values are relative to the viewBox dimensions.
117
    final int w = 75;
118
    final int h = 75;
119
    Image image;
120
121
    try( final StringReader reader = new StringReader( BROKEN_IMAGE_SVG ) ) {
122
      final String parser = XMLResourceDescriptor.getXMLParserClassName();
123
      final SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory( parser );
124
      final Document document = factory.createDocument( "", reader );
125
126
      image = rasterize( document, w );
127
    } catch( final Exception e ) {
128
      image = new BufferedImage( w, h, TYPE_INT_RGB );
129
      final var graphics = (Graphics2D) image.getGraphics();
130
      graphics.setRenderingHints( RENDERING_HINTS );
131
132
      // Fall back to a (\) symbol.
133
      graphics.setColor( new Color( 204, 204, 204 ) );
134
      graphics.fillRect( 0, 0, w, h );
135
      graphics.setColor( new Color( 255, 204, 204 ) );
136
      graphics.setStroke( new BasicStroke( 4 ) );
137
      graphics.drawOval( w / 4, h / 4, w / 2, h / 2 );
138
      graphics.drawLine( w / 4 + (int) (w / 4 / Math.PI),
139
                         h / 4 + (int) (w / 4 / Math.PI),
140
                         w / 2 + w / 4 - (int) (w / 4 / Math.PI),
141
                         h / 2 + h / 4 - (int) (w / 4 / Math.PI) );
142
    }
143
144
    BROKEN_IMAGE_PLACEHOLDER = image;
145
  }
146
147
  private static class BufferedImageTranscoder extends ImageTranscoder {
148
    private BufferedImage mImage;
149
150
    @Override
151
    public BufferedImage createImage( final int w, final int h ) {
152
      return new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB );
153
    }
154
155
    @Override
156
    public void writeImage(
157
        final BufferedImage image, final TranscoderOutput output ) {
158
      mImage = image;
159
    }
160
161
    public Image getImage() {
162
      return mImage;
163
    }
164
165
    @Override
166
    protected ImageRenderer createRenderer() {
167
      final ImageRenderer renderer = super.createRenderer();
168
      final RenderingHints hints = renderer.getRenderingHints();
169
      hints.putAll( RENDERING_HINTS );
170
171
      renderer.setRenderingHints( hints );
172
173
      return renderer;
174
    }
175
  }
176
177
  /**
178
   * Rasterizes the vector graphic file at the given URL. If any exception
179
   * happens, a red circle is returned instead.
180
   *
181
   * @param url   The URL to a vector graphic file, which must include the
182
   *              protocol scheme (such as file:// or https://).
183
   * @param width The number of pixels wide to render the image. The aspect
184
   *              ratio is maintained.
185
   * @return Either the rasterized image upon success or a red circle.
186
   */
187
  public static Image rasterize( final String url, final int width ) {
188
    try {
189
      return rasterize( new URL( url ), width );
190
    } catch( final Exception e ) {
191
      NOTIFIER.notify( e );
192
      return BROKEN_IMAGE_PLACEHOLDER;
193
    }
194
  }
195
196
  /**
197
   * Converts an SVG drawing into a rasterized image that can be drawn on
198
   * a graphics context.
199
   *
200
   * @param url   The path to the image (can be web address).
201
   * @param width Scale the image width to this size (aspect ratio is
202
   *              maintained).
203
   * @return The vector graphic transcoded into a raster image format.
204
   * @throws IOException         Could not read the vector graphic.
205
   * @throws TranscoderException Could not convert the vector graphic to an
206
   *                             instance of {@link Image}.
207
   */
208
  public static Image rasterize( final URL url, final int width )
209
      throws IOException, TranscoderException {
210
    return rasterize(
211
        mFactory.createDocument( url.toString() ), width );
212
  }
213
214
  public static Image rasterize(
215
      final Document svg, final int width ) throws TranscoderException {
216
    final var transcoder = new BufferedImageTranscoder();
217
    final var input = new TranscoderInput( svg );
218
219
    transcoder.addTranscodingHint( KEY_BACKGROUND_COLOR, WHITE );
220
    transcoder.addTranscodingHint( KEY_WIDTH, (float) width );
221
    transcoder.transcode( input, null );
222
223
    return transcoder.getImage();
224
  }
225
}
2261
D 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.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.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
  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/processors/ProcessorFactory.java
6969
   */
7070
  public Processor<String> createProcessors( final FileEditorTab tab ) {
71
    final Path path = tab.getPath();
72
    final Processor<String> processor;
73
74
    switch( lookup( path ) ) {
75
      case RMARKDOWN:
76
        processor = createRProcessor();
77
        break;
78
79
      case SOURCE:
80
        processor = createMarkdownDefinitionProcessor();
81
        break;
82
83
      case XML:
84
        processor = createXMLProcessor( tab );
85
        break;
86
87
      case RXML:
88
        processor = createRXMLProcessor( tab );
89
        break;
90
91
      default:
92
        processor = createIdentityProcessor();
93
        break;
94
    }
95
96
    return processor;
71
    return switch( lookup( tab.getPath() ) ) {
72
      case RMARKDOWN -> createRProcessor();
73
      case SOURCE -> createMarkdownDefinitionProcessor();
74
      case XML -> createXMLProcessor( tab );
75
      case RXML -> createRXMLProcessor( tab );
76
      default -> createIdentityProcessor();
77
    };
9778
  }
9879
M src/main/java/com/scrivenvar/processors/markdown/BlockExtension.java
7171
7272
  @Override
73
  public void extend( final HtmlRenderer.Builder rendererBuilder,
73
  public void extend( final HtmlRenderer.Builder builder,
7474
                      @NotNull final String rendererType ) {
75
    rendererBuilder.attributeProviderFactory(
76
        IdAttributeProvider.createFactory()
77
    );
75
    builder.attributeProviderFactory( IdAttributeProvider.createFactory() );
7876
  }
7977
A src/main/java/com/scrivenvar/processors/markdown/LigatureExtension.java
1
package com.scrivenvar.processors.markdown;
2
3
import com.vladsch.flexmark.ast.Text;
4
import com.vladsch.flexmark.html.HtmlWriter;
5
import com.vladsch.flexmark.html.renderer.NodeRenderer;
6
import com.vladsch.flexmark.html.renderer.NodeRendererContext;
7
import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
8
import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
9
import com.vladsch.flexmark.util.ast.TextCollectingVisitor;
10
import com.vladsch.flexmark.util.data.DataHolder;
11
import com.vladsch.flexmark.util.data.MutableDataHolder;
12
import org.jetbrains.annotations.NotNull;
13
import org.jetbrains.annotations.Nullable;
14
15
import java.util.LinkedHashMap;
16
import java.util.Map;
17
import java.util.Set;
18
19
import static com.scrivenvar.processors.text.TextReplacementFactory.replace;
20
import static com.vladsch.flexmark.html.HtmlRenderer.Builder;
21
import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
22
23
/**
24
 * Responsible for substituting multi-codepoint glyphs with single codepoint
25
 * glyphs. The text is adorned with ligatures prior to rendering as HTML.
26
 * This requires a font that supports ligatures.
27
 * <p>
28
 * TODO: I18N https://github.com/DaveJarvis/scrivenvar/issues/81
29
 * </p>
30
 */
31
public class LigatureExtension implements HtmlRendererExtension {
32
  /**
33
   * Retain insertion order so that ligature substitution uses longer ligatures
34
   * ahead of shorter ligatures. The word "ruffian" should use the "ffi"
35
   * ligature, not the "ff" ligature.
36
   */
37
  private final static Map<String, String> LIGATURES = new LinkedHashMap<>();
38
39
  static {
40
    LIGATURES.put( "ffi", "\uFB03" );
41
    LIGATURES.put( "ffl", "\uFB04" );
42
    LIGATURES.put( "ff", "\uFB00" );
43
    LIGATURES.put( "fi", "\uFB01" );
44
    LIGATURES.put( "fl", "\uFB02" );
45
    LIGATURES.put( "ft", "\uFB05" );
46
    LIGATURES.put( "AE", "\u00C6" );
47
    LIGATURES.put( "OE", "\u0152" );
48
//      "ae", "\u00E6",
49
//      "oe", "\u0153",
50
  }
51
52
  private static class LigatureRenderer implements NodeRenderer {
53
    private final TextCollectingVisitor mVisitor = new TextCollectingVisitor();
54
55
    @SuppressWarnings("unused")
56
    public LigatureRenderer( final DataHolder options ) {
57
    }
58
59
    @Override
60
    public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
61
      return Set.of( new NodeRenderingHandler<>(
62
          Text.class, LigatureRenderer.this::render ) );
63
    }
64
65
    /**
66
     * This will pick the fastest string replacement algorithm based on the
67
     * text length. The insertion order of the {@link #LIGATURES} is
68
     * important to give precedence to longer ligatures.
69
     *
70
     * @param textNode The text node containing text to replace with ligatures.
71
     * @param context  Not used.
72
     * @param html     Where to write the text adorned with ligatures.
73
     */
74
    private void render(
75
        @NotNull final Text textNode,
76
        @NotNull final NodeRendererContext context,
77
        @NotNull final HtmlWriter html ) {
78
      final var text = mVisitor.collectAndGetText( textNode );
79
      html.text( replace( text, LIGATURES ) );
80
    }
81
  }
82
83
  private static class Factory implements NodeRendererFactory {
84
    @NotNull
85
    @Override
86
    public NodeRenderer apply( @NotNull DataHolder options ) {
87
      return new LigatureRenderer( options );
88
    }
89
  }
90
91
  private LigatureExtension() {
92
  }
93
94
  @Override
95
  public void rendererOptions( @NotNull final MutableDataHolder options ) {
96
  }
97
98
  @Override
99
  public void extend( @NotNull final Builder builder,
100
                      @NotNull final String rendererType ) {
101
    if( "HTML".equalsIgnoreCase( rendererType ) ) {
102
      builder.nodeRendererFactory( new Factory() );
103
    }
104
  }
105
106
  public static LigatureExtension create() {
107
    return new LigatureExtension();
108
  }
109
}
1110
M src/main/java/com/scrivenvar/processors/markdown/MarkdownProcessor.java
8181
    extensions.add( BlockExtension.create() );
8282
83
    // TODO: https://github.com/FAlthausen/Vollkorn-Typeface/issues/38
84
    // TODO: Uncomment when Vollkorn ligatures are fixed.
85
    // extensions.add( LigatureExtension.create() );
86
8387
    mRenderer = HtmlRenderer.builder().extensions( extensions ).build();
8488
    mParser = Parser.builder().extensions( extensions ).build();
M src/main/java/com/scrivenvar/processors/text/TextReplacementFactory.java
4444
   *
4545
   * @param length The length of text that requires some search and replacing.
46
   *
4746
   * @return A class that can search and replace text with utmost expediency.
4847
   */
...
6261
   *
6362
   * @param text The text containing zero or more variables to replace.
64
   * @param map The map of variables to their dereferenced values.
65
   *
63
   * @param map  The map of variables to their dereferenced values.
6664
   * @return The text with all variables replaced.
6765
   */
6866
  public static String replace(
69
    final String text, final Map<String, String> map ) {
67
      final String text, final Map<String, String> map ) {
7068
    return getTextReplacer( text.length() ).replace( text, map );
7169
  }
M src/main/java/com/scrivenvar/service/events/impl/ButtonOrderPane.java
2828
package com.scrivenvar.service.events.impl;
2929
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.Settings;
3230
import javafx.scene.Node;
3331
import javafx.scene.control.ButtonBar;
34
import static javafx.scene.control.ButtonBar.BUTTON_ORDER_WINDOWS;
3532
import javafx.scene.control.DialogPane;
33
34
import static com.scrivenvar.Constants.SETTINGS;
35
import static javafx.scene.control.ButtonBar.BUTTON_ORDER_WINDOWS;
3636
3737
/**
3838
 * Ensures a consistent button order for alert dialogs across platforms (because
3939
 * the default button order on Linux defies all logic).
4040
 */
4141
public class ButtonOrderPane extends DialogPane {
4242
4343
  @Override
4444
  protected Node createButtonBar() {
45
    final ButtonBar node = (ButtonBar)super.createButtonBar();
45
    final var node = (ButtonBar) super.createButtonBar();
4646
    node.setButtonOrder( getButtonOrder() );
4747
    return node;
4848
  }
4949
5050
  private String getButtonOrder() {
51
    return getSetting( "dialog.alert.button.order.windows", BUTTON_ORDER_WINDOWS );
51
    return getSetting( "dialog.alert.button.order.windows",
52
                       BUTTON_ORDER_WINDOWS );
5253
  }
5354
5455
  @SuppressWarnings("SameParameterValue")
5556
  private String getSetting( final String key, final String defaultValue ) {
56
    return getSettings().getSetting( key, defaultValue );
57
  }
58
59
  private Settings getSettings() {
60
    return Services.load( Settings.class );
57
    return SETTINGS.getSetting( key, defaultValue );
6158
  }
6259
}
M src/main/java/com/scrivenvar/util/ResourceWalker.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
...
1441
public class ResourceWalker {
1542
  private static final PathMatcher PATH_MATCHER =
16
      FileSystems.getDefault().getPathMatcher( "glob:**.ttf" );
43
      FileSystems.getDefault().getPathMatcher( "glob:**.{ttf,otf}" );
1744
1845
  /**
M src/main/resources/com/scrivenvar/messages.properties
33
# ########################################################################
44
5
# suppress inspection "UnusedProperty" for whole file
6
57
# The application title should exist only once in the entire code base.
68
# All other references should either refer to this value via the Messages
...
5153
Main.menu.insert.ordered_list=Ordered List
5254
Main.menu.insert.horizontal_rule=Horizontal Rule
55
56
Main.menu.view=_View
57
Main.menu.view.refresh=Refresh
5358
5459
Main.menu.help=_Help
...
8792
Preferences.definitions.path=File name
8893
Preferences.definitions.path.desc=Absolute path to interpolated string definitions.
94
95
Preferences.fonts=Editor
96
Preferences.fonts.size_editor=Font Size
97
Preferences.fonts.size_editor.desc=Font size to use for the text editor.
8998
9099
# ########################################################################
M src/main/resources/com/scrivenvar/preview/webview.css
1212
  color: #454545;
1313
  padding: 0 1em;
14
  font-feature-settings: "liga" 1;
15
  font-variant-ligatures: normal;
1416
}
1517
M src/main/resources/fonts/vollkorn/Vollkorn-Bold.ttf
Binary file
M src/main/resources/fonts/vollkorn/Vollkorn-BoldItalic.ttf
Binary file
M src/main/resources/fonts/vollkorn/Vollkorn-Italic.ttf
Binary file
M src/main/resources/fonts/vollkorn/Vollkorn-Regular.ttf
Binary file
D src/main/resources/fonts/vollkorn/VollkornSC-Bold.ttf
Binary file
D src/main/resources/fonts/vollkorn/VollkornSC-Regular.ttf
Binary file
A 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>
18483
A video/.gitignore
1
*.avi
2
*.wav
3
*.png
4
*.mp4
5
*.mp3
6
17
A video/title.blend
Binary file
A video/traced-text.svg
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<svg
3
   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
   xmlns:cc="http://creativecommons.org/ns#"
5
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
   xmlns:svg="http://www.w3.org/2000/svg"
7
   xmlns="http://www.w3.org/2000/svg"
8
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
   width="211.87125mm"
11
   height="56.576mm"
12
   viewBox="0 0 211.87125 56.576"
13
   version="1.1"
14
   id="svg8"
15
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
16
   sodipodi:docname="traced-text.svg">
17
  <defs
18
     id="defs2" />
19
  <sodipodi:namedview
20
     id="base"
21
     pagecolor="#ffffff"
22
     bordercolor="#666666"
23
     borderopacity="1.0"
24
     inkscape:pageopacity="0.0"
25
     inkscape:pageshadow="2"
26
     inkscape:zoom="1.4142136"
27
     inkscape:cx="367.6429"
28
     inkscape:cy="129.23348"
29
     inkscape:document-units="mm"
30
     inkscape:current-layer="layer1"
31
     inkscape:document-rotation="0"
32
     showgrid="false"
33
     fit-margin-top="10"
34
     fit-margin-left="10"
35
     fit-margin-right="10"
36
     fit-margin-bottom="10" />
37
  <metadata
38
     id="metadata5">
39
    <rdf:RDF>
40
      <cc:Work
41
         rdf:about="">
42
        <dc:format>image/svg+xml</dc:format>
43
        <dc:type
44
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
45
        <dc:title></dc:title>
46
      </cc:Work>
47
    </rdf:RDF>
48
  </metadata>
49
  <g
50
     inkscape:label="Layer 1"
51
     inkscape:groupmode="layer"
52
     id="layer1"
53
     transform="translate(-1.4263456,-106.05539)">
54
    <text
55
       xml:space="preserve"
56
       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.8px;line-height:1.25;font-family:'Alex Brush';-inkscape-font-specification:'Alex Brush, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
57
       x="12.289946"
58
       y="147.80539"
59
       id="text835"><tspan
60
         sodipodi:role="line"
61
         id="tspan833"
62
         x="12.289946"
63
         y="147.80539"
64
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.8px;font-family:'Alex Brush';-inkscape-font-specification:'Alex Brush, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583">Scrivenvar</tspan></text>
65
    <path
66
       sodipodi:nodetypes="cssssc"
67
       id="path859"
68
       d="m 47.37594,126.25759 c 5.878995,0.58684 8.108819,-2.8906 6.991897,-5.39049 -4.163299,-9.31827 -26.104298,-1.57165 -26.47428,4.67958 -0.290066,4.90098 4.329286,5.69691 9.138161,6.81221 4.75698,1.10326 9.980125,1.72503 10.138085,4.5281 0.511551,9.07772 -11.28247,13.50974 -21.577969,13.14767"
69
       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#4eb059;stroke-width:0.132292;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
70
    <path
71
       sodipodi:nodetypes="cssc"
72
       id="path861"
73
       d="m 61.538159,137.91416 c 8.229745,-12.05206 -9.227635,-1.22793 -10.272792,5.40306 -0.929347,5.89623 4.566953,5.63307 9.024721,2.11036 5.095939,-4.02702 8.706628,-8.11599 12.031905,-13.9409"
74
       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#4eb059;stroke-width:0.132292;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
75
    <path
76
       sodipodi:nodetypes="ccssc"
77
       id="path863"
78
       d="m 72.321991,131.48668 c 3.834665,-5.91801 -1.131419,0.83402 0.75311,2.48796 2.189872,1.94816 6.580549,-2.11016 5.400159,-0.72958 -0.854851,0.99983 -9.857527,10.41157 -5.126492,13.80621 2.461609,1.76627 8.936925,-2.58857 11.751532,-5.5313"
79
       style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
80
    <path
81
       sodipodi:nodetypes="csssc"
82
       id="path963"
83
       d="m 85.1003,141.51997 c 0,0 6.754775,-9.24626 6.743495,-8.01563 -0.01328,1.44899 -5.040946,6.68411 -6.63123,10.08427 -0.90584,1.93677 -0.626402,4.68995 2.447111,4.25184 1.468017,-0.20926 5.212094,-2.44913 10.029682,-7.66684"
84
       style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
85
    <path
86
       sodipodi:nodetypes="csccc"
87
       id="path965"
88
       d="m 97.689357,140.17361 c 0,0 3.797813,-8.42805 4.594353,-7.95573 0.58723,0.34822 -6.526154,13.32545 -5.477472,14.50806 2.435753,1.7862 19.064212,-11.51107 15.563042,-16.73913 -0.73409,-1.34256 -3.18033,-1.99148 -3.18033,-1.99148"
89
       style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
90
    <path
91
       sodipodi:nodetypes="csssc"
92
       d="m 113.37707,141.34636 c 4.23091,0.29831 11.94363,-4.90618 10.94354,-7.7799 -1.29105,-3.70978 -8.05529,1.78774 -9.69006,3.68511 -4.97668,5.77609 -4.11733,10.31478 -0.92228,10.61275 3.436,0.32045 8.83724,-3.13085 13.69698,-9.62574"
93
       style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
94
       id="path967" />
95
    <path
96
       style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
97
       d="m 146.49943,140.17361 c 0,0 3.79781,-8.42805 4.59435,-7.95573 0.58723,0.34822 -6.52616,13.32545 -5.47747,14.50806 2.43575,1.7862 19.06421,-11.51107 15.56304,-16.73913 -0.73409,-1.34256 -3.10123,-1.96263 -3.10123,-1.96263"
98
       id="path970"
99
       sodipodi:nodetypes="csccc" />
100
    <path
101
       style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
102
       d="m 188.80833,131.36316 c 3.83466,-5.91801 -1.13142,0.83402 0.75311,2.48796 2.18987,1.94816 6.58055,-2.11016 5.40016,-0.72958 -0.85485,0.99983 -9.98962,10.60367 -5.12649,13.80621 2.8329,1.86556 9.63808,-2.25455 13.61435,-8.05051"
103
       id="path987"
104
       sodipodi:nodetypes="ccssc" />
105
    <path
106
       sodipodi:nodetypes="ccsssccc"
107
       d="m 127.40525,138.23858 c 1.53961,-1.23511 5.06979,-6.4876 5.94375,-5.82833 -1.7832,2.5949 -8.95273,13.68991 -7.1105,13.94503 1.19011,0.16482 7.25976,-8.00422 10.87675,-10.901 1.83151,-1.46682 4.35069,-3.49971 5.94917,-3.73267 1.66376,-0.24247 -1.93803,2.90472 -3.80099,5.77097 -1.36327,2.14988 -4.92421,8.02816 -2.69839,9.35481 3.0826,1.21137 7.35116,-4.27566 9.93439,-6.67382"
108
       style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
109
       id="path989" />
110
    <path
111
       sodipodi:nodetypes="csscsc"
112
       id="path992"
113
       d="m 176.85645,132.78853 c -3.26879,-6.24001 -16.43513,7.99373 -16.14879,12.14556 0.1378,1.99804 2.16776,3.14653 3.8818,2.44798 4.44909,-1.8132 11.93103,-13.58278 13.4413,-14.18515 -6.97685,9.84354 -7.04537,13.29844 -4.02229,13.83262 2.49715,0.44125 8.94275,-6.11484 14.79986,-15.66638"
114
       style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
115
  </g>
116
</svg>
1117