@abi-software/flatmap-viewer 2.4.2-b.5 → 2.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.rst +1 -1
- package/package.json +6 -6
- package/src/controls/controls.js +0 -67
- package/src/flatmap-viewer.js +0 -37
- package/src/interactions.js +40 -132
- package/src/main.js +0 -1
- package/src/mathjax.js +100 -0
- package/src/styling.js +35 -48
- package/src/annotation.js +0 -665
package/README.rst
CHANGED
|
@@ -38,7 +38,7 @@ The map server endpoint is specified as ``MAP_ENDPOINT`` in ``src/main.js``. It
|
|
|
38
38
|
Package Installation
|
|
39
39
|
====================
|
|
40
40
|
|
|
41
|
-
* ``npm install @abi-software/flatmap-viewer@2.4.
|
|
41
|
+
* ``npm install @abi-software/flatmap-viewer@2.4.4``
|
|
42
42
|
|
|
43
43
|
Documentation
|
|
44
44
|
-------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abi-software/flatmap-viewer",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.4",
|
|
4
4
|
"description": "Flatmap viewer using Maplibre GL",
|
|
5
5
|
"repository": "https://github.com/AnatomicMaps/flatmap-viewer.git",
|
|
6
6
|
"main": "src/main.js",
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@babel/runtime": "^7.10.4",
|
|
21
21
|
"@fortawesome/fontawesome-free": "^6.4.0",
|
|
22
|
-
"@turf/area": "^6.0
|
|
23
|
-
"@turf/bbox": "^6.0
|
|
24
|
-
"@turf/helpers": "^6.
|
|
22
|
+
"@turf/area": "^6.5.0",
|
|
23
|
+
"@turf/bbox": "^6.5.0",
|
|
24
|
+
"@turf/helpers": "^6.5.0",
|
|
25
25
|
"@turf/projection": "^6.5.0",
|
|
26
26
|
"bezier-js": "^6.1.0",
|
|
27
27
|
"html-es6cape": "^2.0.2",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
28
|
+
"maplibre-gl": ">=3.6.0",
|
|
29
|
+
"mathjax-full": "^3.2.2",
|
|
30
30
|
"minisearch": "^2.2.1",
|
|
31
31
|
"polylabel": "^1.1.0"
|
|
32
32
|
},
|
package/src/controls/controls.js
CHANGED
|
@@ -526,73 +526,6 @@ export class NerveControl
|
|
|
526
526
|
|
|
527
527
|
//==============================================================================
|
|
528
528
|
|
|
529
|
-
export class AnnotatedControl
|
|
530
|
-
{
|
|
531
|
-
constructor(ui, options={excludeAnnotated: false})
|
|
532
|
-
{
|
|
533
|
-
this.__ui = ui;
|
|
534
|
-
this.__map = undefined;
|
|
535
|
-
this.__exclude = options.excludeAnnotated || false;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
getDefaultPosition()
|
|
539
|
-
//==================
|
|
540
|
-
{
|
|
541
|
-
return 'top-right';
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
onAdd(map)
|
|
545
|
-
//========
|
|
546
|
-
{
|
|
547
|
-
this.__map = map;
|
|
548
|
-
this.__container = document.createElement('div');
|
|
549
|
-
this.__container.className = 'maplibregl-ctrl';
|
|
550
|
-
|
|
551
|
-
this.__button = document.createElement('button');
|
|
552
|
-
this.__button.id = 'map-annotated-button';
|
|
553
|
-
this.__button.className = 'control-button text-button';
|
|
554
|
-
this.__button.setAttribute('type', 'button');
|
|
555
|
-
this.__button.setAttribute('aria-label', 'Show/hide annotated paths');
|
|
556
|
-
this.__button.textContent = 'UNANN';
|
|
557
|
-
this.__button.title = 'Show/hide annotated paths';
|
|
558
|
-
this.__container.appendChild(this.__button);
|
|
559
|
-
|
|
560
|
-
this.__container.addEventListener('click', this.onClick_.bind(this));
|
|
561
|
-
this.__setBackground();
|
|
562
|
-
return this.__container;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
__setBackground()
|
|
566
|
-
//===============
|
|
567
|
-
{
|
|
568
|
-
if (this.__exclude) {
|
|
569
|
-
this.__button.setAttribute('style', 'background: red');
|
|
570
|
-
} else {
|
|
571
|
-
this.__button.removeAttribute('style');
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
onRemove()
|
|
576
|
-
//========
|
|
577
|
-
{
|
|
578
|
-
this.__container.parentNode.removeChild(this.__container);
|
|
579
|
-
this.__map = undefined;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
onClick_(event)
|
|
583
|
-
//=============
|
|
584
|
-
{
|
|
585
|
-
if (event.target.id === 'map-annotated-button') {
|
|
586
|
-
this.__exclude = !this.__exclude;
|
|
587
|
-
this.__setBackground();
|
|
588
|
-
this.__ui.excludeAnnotated(this.__exclude);
|
|
589
|
-
}
|
|
590
|
-
event.stopPropagation();
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
//==============================================================================
|
|
595
|
-
|
|
596
529
|
export class BackgroundControl
|
|
597
530
|
{
|
|
598
531
|
constructor(flatmap)
|
package/src/flatmap-viewer.js
CHANGED
|
@@ -95,9 +95,6 @@ class FlatMap
|
|
|
95
95
|
this.__addAnnotation(featureId, annotation);
|
|
96
96
|
this.__searchIndex.indexMetadata(featureId, annotation);
|
|
97
97
|
}
|
|
98
|
-
if (this.options.annotator) {
|
|
99
|
-
this.__addAnnotatedComments();
|
|
100
|
-
}
|
|
101
98
|
|
|
102
99
|
// Set base of source URLs in map's style
|
|
103
100
|
|
|
@@ -223,25 +220,6 @@ class FlatMap
|
|
|
223
220
|
});
|
|
224
221
|
}
|
|
225
222
|
|
|
226
|
-
async __addAnnotatedComments()
|
|
227
|
-
//============================
|
|
228
|
-
{
|
|
229
|
-
const url = this.makeServerUrl('', 'annotator/')
|
|
230
|
-
const annotatedFeatures = await loadJSON(url);
|
|
231
|
-
for (const annotatedId of annotatedFeatures) {
|
|
232
|
-
const featureId = this.__annIdToFeatureId.get(annotatedId);
|
|
233
|
-
if (featureId) {
|
|
234
|
-
const url = this.makeServerUrl(annotatedId, 'annotator/')
|
|
235
|
-
const annotations = await loadJSON(url);
|
|
236
|
-
for (const annotation of annotations) { // In order of most recent to oldest
|
|
237
|
-
if ('rdfs:comment' in annotation) {
|
|
238
|
-
this.__searchIndex.indexText(featureId, annotation['rdfs:comment']);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
223
|
async setupUserInteractions_()
|
|
246
224
|
//============================
|
|
247
225
|
{
|
|
@@ -1237,20 +1215,6 @@ class FlatMap
|
|
|
1237
1215
|
|
|
1238
1216
|
//==========================================================================
|
|
1239
1217
|
|
|
1240
|
-
/**
|
|
1241
|
-
* Highlight features on the map.
|
|
1242
|
-
*
|
|
1243
|
-
* @param {Array.<string>} externalIds An array of anaotomical terms identifing features to highlight
|
|
1244
|
-
*/
|
|
1245
|
-
highlightFeatures(externalIds)
|
|
1246
|
-
//============================
|
|
1247
|
-
{
|
|
1248
|
-
if (this._userInteractions !== null) {
|
|
1249
|
-
const featureIds = this.modelFeatureIdList(externalIds);
|
|
1250
|
-
this._userInteractions.highlightFeatures(featureIds);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
1218
|
/**
|
|
1255
1219
|
* Select features on the map.
|
|
1256
1220
|
*
|
|
@@ -1435,7 +1399,6 @@ export class MapManager
|
|
|
1435
1399
|
* @arg options.showPosition {boolean} Show ``position`` of tooltip.
|
|
1436
1400
|
* @arg options.standalone {boolean} Viewer is running ``standalone``, as opposed to integrated into
|
|
1437
1401
|
* another application so show a number of controls. Defaults to ``false``.
|
|
1438
|
-
* @arg options.annotator {boolean} Allow interactive annotation of features and paths.
|
|
1439
1402
|
* @example
|
|
1440
1403
|
* const humanMap1 = mapManager.loadMap('humanV1', 'div-1');
|
|
1441
1404
|
*
|
package/src/interactions.js
CHANGED
|
@@ -33,19 +33,19 @@ import polylabel from 'polylabel';
|
|
|
33
33
|
|
|
34
34
|
//==============================================================================
|
|
35
35
|
|
|
36
|
-
import {Annotator} from './annotation';
|
|
37
36
|
import {LayerManager} from './layers';
|
|
38
37
|
import {PATHWAYS_LAYER, PathManager} from './pathways';
|
|
39
38
|
import {VECTOR_TILES_SOURCE} from './styling';
|
|
40
39
|
import {SystemsManager} from './systems';
|
|
41
40
|
|
|
42
41
|
import {displayedProperties, InfoControl} from './controls/info';
|
|
43
|
-
import {
|
|
42
|
+
import {BackgroundControl, LayerControl, NerveControl,
|
|
44
43
|
SCKANControl} from './controls/controls';
|
|
45
44
|
import {PathControl} from './controls/paths';
|
|
46
45
|
import {SearchControl} from './controls/search';
|
|
47
46
|
import {SystemsControl} from './controls/systems';
|
|
48
47
|
import {TaxonsControl} from './controls/taxons';
|
|
48
|
+
import {latex2Svg} from './mathjax';
|
|
49
49
|
|
|
50
50
|
import * as utils from './utils';
|
|
51
51
|
|
|
@@ -93,6 +93,31 @@ function expandBounds(bbox1, bbox2, padding)
|
|
|
93
93
|
|
|
94
94
|
//==============================================================================
|
|
95
95
|
|
|
96
|
+
function labelPosition(feature)
|
|
97
|
+
{
|
|
98
|
+
const polygon = feature.geometry.coordinates;
|
|
99
|
+
// Rough heuristic. Area is in km^2; below appears to be good enough.
|
|
100
|
+
const precision = ('area' in feature.properties)
|
|
101
|
+
? Math.sqrt(feature.properties.area)/500000
|
|
102
|
+
: 0.1;
|
|
103
|
+
return polylabel(polygon, precision);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//==============================================================================
|
|
107
|
+
|
|
108
|
+
function getRenderedLabel(properties)
|
|
109
|
+
{
|
|
110
|
+
if (!('renderedLabel' in properties)) {
|
|
111
|
+
const label = ('label' in properties) ? (properties.label.substr(0, 1).toUpperCase()
|
|
112
|
+
+ properties.label.substr(1)).replaceAll("\n", "<br/>")
|
|
113
|
+
: '';
|
|
114
|
+
properties.renderedLabel = label.replaceAll(/`\$([^\$]*)\$`/g, math => latex2Svg(math.slice(2, -2)));
|
|
115
|
+
}
|
|
116
|
+
return properties.renderedLabel;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//==============================================================================
|
|
120
|
+
|
|
96
121
|
export class UserInteractions
|
|
97
122
|
{
|
|
98
123
|
constructor(flatmap)
|
|
@@ -152,13 +177,6 @@ export class UserInteractions
|
|
|
152
177
|
this.enableCentrelines(this.__pathManager.enabledCentrelines, true);
|
|
153
178
|
}
|
|
154
179
|
|
|
155
|
-
// Add annotation capability
|
|
156
|
-
if (flatmap.options.annotator) {
|
|
157
|
-
this.__setupAnnotation();
|
|
158
|
-
} else {
|
|
159
|
-
this.__annotator = null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
180
|
// Note features that are FC systems
|
|
163
181
|
this.__systemsManager = new SystemsManager(this._flatmap, this, featuresEnabled);
|
|
164
182
|
|
|
@@ -196,11 +214,6 @@ export class UserInteractions
|
|
|
196
214
|
// Connectivity taxon control for AC maps
|
|
197
215
|
this._map.addControl(new TaxonsControl(flatmap));
|
|
198
216
|
}
|
|
199
|
-
|
|
200
|
-
if (flatmap.options.annotator) {
|
|
201
|
-
// Show/hide annotated paths
|
|
202
|
-
this._map.addControl(new AnnotatedControl(this, flatmap.options.layerOptions));
|
|
203
|
-
}
|
|
204
217
|
}
|
|
205
218
|
|
|
206
219
|
// Handle mouse events
|
|
@@ -256,41 +269,6 @@ export class UserInteractions
|
|
|
256
269
|
}
|
|
257
270
|
}
|
|
258
271
|
|
|
259
|
-
async __setupAnnotation()
|
|
260
|
-
//=======================
|
|
261
|
-
{
|
|
262
|
-
// Add annotation capability
|
|
263
|
-
|
|
264
|
-
this.__annotator = new Annotator(this._flatmap, this);
|
|
265
|
-
const annotated_features = await this.__annotator.annotated_features();
|
|
266
|
-
|
|
267
|
-
// Flag features that have annotations
|
|
268
|
-
this.__featureIdToMapId = new Map();
|
|
269
|
-
for (const [mapId, ann] of this._flatmap.annotations) {
|
|
270
|
-
this.__featureIdToMapId.set(ann.id, mapId);
|
|
271
|
-
const feature = this.mapFeature(mapId);
|
|
272
|
-
if (feature !== undefined) {
|
|
273
|
-
this._map.setFeatureState(feature, { 'map-annotation': true });
|
|
274
|
-
if (annotated_features.includes(ann.id)) {
|
|
275
|
-
this._map.setFeatureState(feature, { 'annotated': true });
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
setFeatureAnnotated(featureId)
|
|
282
|
-
//============================
|
|
283
|
-
{
|
|
284
|
-
if (this.__annotator) {
|
|
285
|
-
// featureId v's geoJSON id
|
|
286
|
-
const mapId = this.__featureIdToMapId.get(featureId);
|
|
287
|
-
const feature = this.mapFeature(mapId);
|
|
288
|
-
if (feature !== undefined) {
|
|
289
|
-
this._map.setFeatureState(feature, { 'annotated': true });
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
272
|
setPaint(options)
|
|
295
273
|
//===============
|
|
296
274
|
{
|
|
@@ -398,9 +376,12 @@ export class UserInteractions
|
|
|
398
376
|
__featureEnabled(feature)
|
|
399
377
|
//=======================
|
|
400
378
|
{
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
379
|
+
if (feature.id) {
|
|
380
|
+
const state = this._map.getFeatureState(feature);
|
|
381
|
+
return (state !== undefined
|
|
382
|
+
&& (!('hidden' in state) || !state.hidden));
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
404
385
|
}
|
|
405
386
|
|
|
406
387
|
featureSelected_(featureId)
|
|
@@ -495,19 +476,6 @@ export class UserInteractions
|
|
|
495
476
|
}
|
|
496
477
|
}
|
|
497
478
|
|
|
498
|
-
highlightFeature_(featureId)
|
|
499
|
-
//==========================
|
|
500
|
-
{
|
|
501
|
-
featureId = +featureId; // Ensure numeric
|
|
502
|
-
this.__activateFeature(this.mapFeature(featureId));
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
unhighlightFeatures_()
|
|
506
|
-
//====================
|
|
507
|
-
{
|
|
508
|
-
this.resetActiveFeatures_();
|
|
509
|
-
}
|
|
510
|
-
|
|
511
479
|
smallestAnnotatedPolygonFeature_(features)
|
|
512
480
|
//========================================
|
|
513
481
|
{
|
|
@@ -556,30 +524,6 @@ export class UserInteractions
|
|
|
556
524
|
this.unselectFeatures();
|
|
557
525
|
}
|
|
558
526
|
|
|
559
|
-
/**
|
|
560
|
-
* Highlight features on the map.
|
|
561
|
-
*
|
|
562
|
-
* @param {Array.<string>} featureIds An array of feature identifiers to highlight
|
|
563
|
-
*/
|
|
564
|
-
highlightFeatures(featureIds)
|
|
565
|
-
//===========================
|
|
566
|
-
{
|
|
567
|
-
if (featureIds.length) {
|
|
568
|
-
this.unhighlightFeatures_();
|
|
569
|
-
for (const featureId of featureIds) {
|
|
570
|
-
const annotation = this._flatmap.annotation(featureId);
|
|
571
|
-
if (annotation) {
|
|
572
|
-
this.highlightFeature_(featureId);
|
|
573
|
-
if ('type' in annotation && annotation.type.startsWith('line')) {
|
|
574
|
-
for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
|
|
575
|
-
this.highlightFeature_(pathFeatureId);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
527
|
/**
|
|
584
528
|
* Select features on the map.
|
|
585
529
|
*
|
|
@@ -753,7 +697,6 @@ export class UserInteractions
|
|
|
753
697
|
tooltips.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
|
|
754
698
|
}
|
|
755
699
|
if ('label' in properties && (!('tooltip' in properties) || properties.tooltip)) {
|
|
756
|
-
let tooltip = '';
|
|
757
700
|
const label = properties.label;
|
|
758
701
|
const cleanLabel = (label.substr(0, 1).toUpperCase() + label.substr(1)).replaceAll("\n", "<br/>");
|
|
759
702
|
if (!tooltips.includes(cleanLabel)) {
|
|
@@ -775,19 +718,18 @@ export class UserInteractions
|
|
|
775
718
|
if ('warning' in properties) {
|
|
776
719
|
tooltip.push(`<div class="feature-error">Warning: ${properties.warning}</div>`)
|
|
777
720
|
}
|
|
721
|
+
let renderedLabel;
|
|
778
722
|
if (('label' in properties || 'hyperlink' in properties)
|
|
779
723
|
&& (forceLabel || !('tooltip' in properties) || properties.tooltip)) {
|
|
780
|
-
const
|
|
781
|
-
+ properties.label.substr(1)).replaceAll("\n", "<br/>")
|
|
782
|
-
: '';
|
|
724
|
+
const renderedLabel = getRenderedLabel(properties);
|
|
783
725
|
if ('hyperlink' in properties) {
|
|
784
|
-
if (
|
|
726
|
+
if (renderedLabel === '') {
|
|
785
727
|
tooltip.push(`<a href='${properties.hyperlink}'>${properties.hyperlink}</a>`);
|
|
786
728
|
} else {
|
|
787
|
-
tooltip.push(`<a href='${properties.hyperlink}'>${
|
|
729
|
+
tooltip.push(`<a href='${properties.hyperlink}'>${renderedLabel}</a></div>`);
|
|
788
730
|
}
|
|
789
731
|
} else {
|
|
790
|
-
tooltip.push(
|
|
732
|
+
tooltip.push(renderedLabel);
|
|
791
733
|
}
|
|
792
734
|
}
|
|
793
735
|
return (tooltip.length === 0) ? ''
|
|
@@ -1018,28 +960,6 @@ export class UserInteractions
|
|
|
1018
960
|
}
|
|
1019
961
|
}
|
|
1020
962
|
|
|
1021
|
-
__annotationEvent(features)
|
|
1022
|
-
//=========================
|
|
1023
|
-
{
|
|
1024
|
-
if (!this.__annotator) {
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
event.preventDefault();
|
|
1029
|
-
|
|
1030
|
-
// Remove any tooltip
|
|
1031
|
-
this.removeTooltip_();
|
|
1032
|
-
|
|
1033
|
-
// Don't respond to mouse events while the dialog is open
|
|
1034
|
-
this.setModal_();
|
|
1035
|
-
|
|
1036
|
-
// The annotation dialog...
|
|
1037
|
-
this.__annotator.annotate(features, () => {
|
|
1038
|
-
this.unselectFeatures();
|
|
1039
|
-
this.__clearModal();
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
963
|
clickEvent_(event)
|
|
1044
964
|
//================
|
|
1045
965
|
{
|
|
@@ -1054,14 +974,8 @@ export class UserInteractions
|
|
|
1054
974
|
this.unselectFeatures();
|
|
1055
975
|
return;
|
|
1056
976
|
}
|
|
1057
|
-
const originalEvent = event.originalEvent;
|
|
1058
|
-
if (originalEvent.altKey) {
|
|
1059
|
-
this.__annotationEvent(clickedFeatures);
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
977
|
const clickedFeature = clickedFeatures[0];
|
|
1064
|
-
this.selectionEvent_(originalEvent, clickedFeature);
|
|
978
|
+
this.selectionEvent_(event.originalEvent, clickedFeature);
|
|
1065
979
|
if (this._modal) {
|
|
1066
980
|
// Remove tooltip, reset active features, etc
|
|
1067
981
|
this.__resetFeatureDisplay();
|
|
@@ -1191,13 +1105,7 @@ export class UserInteractions
|
|
|
1191
1105
|
]
|
|
1192
1106
|
});
|
|
1193
1107
|
if (features.length > 0) {
|
|
1194
|
-
|
|
1195
|
-
const polygon = feature.geometry.coordinates;
|
|
1196
|
-
// Rough heuristic. Area is in km^2; below appears to be good enough.
|
|
1197
|
-
const precision = ('area' in feature.properties)
|
|
1198
|
-
? Math.sqrt(feature.properties.area)/500000
|
|
1199
|
-
: 0.1;
|
|
1200
|
-
position = polylabel(polygon, precision);
|
|
1108
|
+
position = labelPosition(features[0]);
|
|
1201
1109
|
}
|
|
1202
1110
|
}
|
|
1203
1111
|
this.__markerPositions.set(featureId, position);
|
package/src/main.js
CHANGED
package/src/mathjax.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
|
|
2
|
+
/******************************************************************************
|
|
3
|
+
|
|
4
|
+
Flatmap viewer and annotation tool
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2019-2023 David Brooks
|
|
7
|
+
|
|
8
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
you may not use this file except in compliance with the License.
|
|
10
|
+
You may obtain a copy of the License at
|
|
11
|
+
|
|
12
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
|
|
14
|
+
Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
See the License for the specific language governing permissions and
|
|
18
|
+
limitations under the License.
|
|
19
|
+
|
|
20
|
+
******************************************************************************/
|
|
21
|
+
|
|
22
|
+
/*************************************************************************
|
|
23
|
+
*
|
|
24
|
+
* direct/tex2svg
|
|
25
|
+
*
|
|
26
|
+
* Uses MathJax v3 to convert a TeX string to an SVG string.
|
|
27
|
+
*
|
|
28
|
+
* ----------------------------------------------------------------------
|
|
29
|
+
*
|
|
30
|
+
* Copyright (c) 2018 The MathJax Consortium
|
|
31
|
+
*
|
|
32
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
33
|
+
* you may not use this file except in compliance with the License.
|
|
34
|
+
* You may obtain a copy of the License at
|
|
35
|
+
*
|
|
36
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
37
|
+
*
|
|
38
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
39
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
40
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
41
|
+
* See the License for the specific language governing permissions and
|
|
42
|
+
* limitations under the License.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
//==============================================================================
|
|
46
|
+
|
|
47
|
+
const EM_SIZE = 16;
|
|
48
|
+
const EX_SIZE = 8;
|
|
49
|
+
|
|
50
|
+
//==============================================================================
|
|
51
|
+
|
|
52
|
+
// Load the packages needed for MathJax
|
|
53
|
+
import {mathjax} from 'mathjax-full/js/mathjax';
|
|
54
|
+
import {TeX} from 'mathjax-full/js/input/tex';
|
|
55
|
+
import {SVG} from 'mathjax-full/js/output/svg';
|
|
56
|
+
import {browserAdaptor} from 'mathjax-full/js/adaptors/browserAdaptor';
|
|
57
|
+
import {RegisterHTMLHandler} from 'mathjax-full/js/handlers/html';
|
|
58
|
+
import {AllPackages} from 'mathjax-full/js/input/tex/AllPackages'; // required to load `textmacros`
|
|
59
|
+
|
|
60
|
+
//==============================================================================
|
|
61
|
+
|
|
62
|
+
// Minimal CSS needed for stand-alone image
|
|
63
|
+
export const LatexStyleRules = [
|
|
64
|
+
'svg a{fill:blue;stroke:blue}',
|
|
65
|
+
// Round the corners of filled background rectangles
|
|
66
|
+
'[data-mml-node="mstyle"]>rect[data-bgcolor="true"]{rx: 8%; ry: 12%}',
|
|
67
|
+
'[data-mml-node="merror"]>g{fill:red;stroke:red}',
|
|
68
|
+
'[data-mml-node="merror"]>rect[data-background]{fill:yellow;stroke:none}',
|
|
69
|
+
'[data-frame],[data-line]{stroke-width:70px;fill:none}',
|
|
70
|
+
'.mjx-dashed{stroke-dasharray:140}',
|
|
71
|
+
'.mjx-dotted{stroke-linecap:round;stroke-dasharray:0,140}',
|
|
72
|
+
'use[data-c]{stroke-width:3px}'
|
|
73
|
+
].join('');
|
|
74
|
+
|
|
75
|
+
//==============================================================================
|
|
76
|
+
|
|
77
|
+
// Create DOM adaptor and register it for HTML documents
|
|
78
|
+
const adaptor = browserAdaptor();
|
|
79
|
+
RegisterHTMLHandler(adaptor);
|
|
80
|
+
|
|
81
|
+
//==============================================================================
|
|
82
|
+
|
|
83
|
+
const tex = new TeX({packages: AllPackages}); // ['base', 'textmacros']});
|
|
84
|
+
const svg = new SVG({fontCache: 'local'});
|
|
85
|
+
const html = mathjax.document('', {InputJax: tex, OutputJax: svg});
|
|
86
|
+
|
|
87
|
+
//==============================================================================
|
|
88
|
+
|
|
89
|
+
export function latex2Svg(latex)
|
|
90
|
+
{
|
|
91
|
+
const node = html.convert(latex, {
|
|
92
|
+
display: false, // process as inline math
|
|
93
|
+
em: 2*EM_SIZE,
|
|
94
|
+
ex: 2*EX_SIZE,
|
|
95
|
+
});
|
|
96
|
+
let result = adaptor.innerHTML(node);
|
|
97
|
+
return result.replace(/<defs>/, `<defs><style>${LatexStyleRules}</style>`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//==============================================================================
|