Added commands, styles, fixes

- added [!newpage] to start a new page (for pdf)
- added pdf exports supported by puppeteer
- added styles for math and code highlighting
- fixed circular imports
- added CHANGELOG
- switched to argparse for argument parsing
- switched to gulp for building
develop
Trivernis 5 years ago
parent beb437fad0
commit 6f39c36f56

@ -0,0 +1,12 @@
## [Unreleased]
### Added
- Changelog
- markdown parsing with markdown it
- command to include plugins `[!use]: plugin`
- command to include markdown files `[!include]: file`
- command to start a new page `[!newpage]`
- option to export to pdf `--pdf`
- option to watch (and export to html) `--watch`
- stylesheets supporting math and code highlighting (with highlightjs)

@ -2,6 +2,22 @@
Markdown-Super is a markkdown-parser using markdown it that allows including other markdown-documents and manage markdown-it plugins inside the document itself.
## Commandline
```
usage: index.js [-h] [-w] [--pdf] file
Positional arguments:
file The file to render
Optional arguments:
-h, --help Show this help message and exit.
-w, --watch Watch files for changes
--pdf Output as pdf
```
## Including other markdown documents
A document can be included by using
@ -10,6 +26,9 @@ A document can be included by using
[!include]: path/to/file
```
Included documents can also use include.
If there is a circular include, the include resulting in an endless loop is ignored.
## Managing markdown-it plugins
The usage of a markdown-it plugin inside a document can be decleared by using
@ -31,15 +50,34 @@ The plugin names are listed in the following table. Basically it is just the pac
| markdown-it-checkbox | checkbox
| markdown-it-imsize | imsize
| markdown-it-highlightjs | highlightjs
| markdown-it-prism | prism
| markdown-it-toc-done-right | toc-done-right
| markdown-it-smartarrows | smartarrows
| markdown-it-plantuml | plantuml
| markdown-it-mathjax | mathjax
| markdown-it-math | math
| markdown-it-div | div
For example you can declare the use of `markdown-it-emoji` the following:
```markdown
[!use]: emoji
```
## Pages
You can manage the pages that are exported to the pdf. A new page can be started with:
```markdown
[!newpage]
```
Note that this automatically includes `markdown-it-div` in your project.
If you want to declare one page specifically, you need to declare the use of `markdown-it-div` (`[!use]: div`). Then you pages can be created like this:
```markdown
::: page
Your page's content.
Warning: be careful not to put too much content in this environment
because it will be rendered as only ONE page in the pdf output.
:::
```

@ -0,0 +1,42 @@
const {src, dest, watch, series, task} = require('gulp');
const sass = require('gulp-sass');
const ts = require('gulp-typescript');
const del = require('delete');
const cleanCss = require('gulp-clean-css');
const renderer = require('./dist/Renderer');
const fsx = require("fs-extra");
const path = require("path");
function clearDist(cb) {
del('dist/*', cb);
}
function compileTypescript() {
let tsProject = ts.createProject('tsconfig.json');
let tsResult = tsProject.src().pipe(tsProject());
return tsResult
.pipe(dest('dist'));
}
function compileSass() {
return src('src/styles/*.sass')
.pipe(sass())
.pipe(cleanCss())
.pipe(dest('dist/styles'));
}
async function renderMarkdown() {
let filename = 'test.md';
let rend = new renderer.Renderer();
let html = await rend.render(filename);
await fsx.writeFile(filename.replace(path.extname(filename), '') + '.html', html);
}
task('cleanBuild', series(clearDist, compileTypescript, compileSass));
task('default', series(compileTypescript, compileSass));
task('watch', () => {
series(compileSass, compileTypescript, renderMarkdown);
watch('src/**/*.sass', series(compileSass, renderMarkdown));
watch('**/*.ts', compileTypescript);
watch('**/*.md', renderMarkdown);
});

5707
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -10,22 +10,34 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@types/argparse": "^1.0.36",
"@types/chokidar": "^2.1.3",
"@types/fs-extra": "^8.0.0",
"@types/jsdom": "^12.2.4",
"@types/markdown-it": "0.0.8",
"@types/node": "^12.6.8",
"@types/winston": "^2.4.4",
"delete": "^1.1.0",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.2.0",
"gulp-sass": "^4.0.2",
"gulp-typescript": "^5.0.1",
"tsc": "^1.20150623.0",
"typescript": "^3.5.3"
},
"dependencies": {
"@types/chokidar": "^2.1.3",
"@types/puppeteer": "^1.19.0",
"argparse": "^1.0.10",
"chokidar": "^3.0.2",
"fs-extra": "^8.1.0",
"jsdom": "^15.1.1",
"line-by-line": "^0.1.6",
"markdown-it": "^9.0.1",
"markdown-it-abbr": "^1.0.4",
"markdown-it-anchor": "^5.2.4",
"markdown-it-attrs": "^2.4.1",
"markdown-it-checkbox": "^1.1.0",
"markdown-it-div": "^1.1.0",
"markdown-it-emoji": "^1.4.0",
"markdown-it-footnote": "^3.0.2",
"markdown-it-highlightjs": "^3.0.0",
@ -35,10 +47,11 @@
"markdown-it-math": "^4.1.1",
"markdown-it-mathjax": "^2.0.0",
"markdown-it-plantuml": "^1.4.1",
"markdown-it-prism": "^2.0.2",
"markdown-it-smartarrows": "^1.0.1",
"markdown-it-sub": "^1.0.0",
"markdown-it-toc-done-right": "^4.0.2",
"uninstall": "0.0.0"
"puppeteer": "^1.19.0",
"uninstall": "0.0.0",
"winston": "^3.2.1"
}
}

@ -2,16 +2,24 @@ import * as path from "path";
import * as fsx from "fs-extra";
import {Renderer} from "./Renderer";
import {markdownPlugins} from './plugins';
import {pageFormats} from "./formats";
import {PDFFormat} from "puppeteer";
namespace Matchers {
export const commandMatcher: RegExp = /\[ *!(\w+) *]: *(.*)/g;
export const commandMatcher: RegExp = /\[ *!(\w+) *]:? *(.*)/g;
}
export class CommandParser {
public projectFiles: string[];
public pageFormat: PDFFormat;
public loadedPlugins: string[];
private resolvePath: {path: string, lines: number}[];
constructor() {
this.projectFiles = [];
this.loadedPlugins = [];
this.resolvePath = [];
}
async processCommands(doc: string, docpath: string, renderer: Renderer) {
@ -20,28 +28,56 @@ export class CommandParser {
let mainDir: string = path.dirname(docpath);
this.projectFiles = [];
this.projectFiles.push(docpath);
this.resolvePath.push({path: docpath, lines: inputLines.length});
while (inputLines.length > 0) {
let inputLine = inputLines.shift();
let inputLine = inputLines.shift().replace(/\r/, '');
let currentFile = this.resolvePath[this.resolvePath.length - 1]; // keeping track of the current file
if (currentFile.lines > 0) {
currentFile.lines--;
} else {
while (currentFile.lines === 0 && this.resolvePath.length > 0) {
this.resolvePath.pop();
currentFile = this.resolvePath[this.resolvePath.length - 1];
}
}
let match: RegExpExecArray = Matchers.commandMatcher.exec(inputLine);
if (match != null && match[0]) {
if (match && match[0]) { // TODO: stylesheets
switch(match[1]) {
case 'use':
let plugins = match[2].split(',');
console.log(`Using plugins: ${match[2]}`);
for (let mdPlugin of plugins) {
this.addMarkdownPlugin(mdPlugin.replace(/^ *| *$/g, ''), renderer);
}
break;
case 'include':
try {
let included = await this.getInclude(match[2], mainDir);
inputLines.unshift(...included);
if (!this.resolvePath.find(x => x.path === match[2])) { // if the include is in the path, it is a circular reference
let included = await this.getInclude(match[2], mainDir);
inputLines.unshift(...included);
this.resolvePath.push({path: match[2], lines: included.length});
} else {
console.error(`Circular reference detected. Skipping include ${match[2]}`);
}
} catch (err) {
console.error(err.message);
outputLines.push(inputLine);
}
break;
case 'format':
if (!this.pageFormat && Object.values(pageFormats).includes(match[2]))
// @ts-ignore
this.pageFormat = match[2];
else
console.log('Invalid page format or format already set: ' + match[2]);
break;
case 'newpage':
if (!this.loadedPlugins.includes(markdownPlugins.div))
this.addMarkdownPlugin('div', renderer);
outputLines.push('::: .newpage \n:::');
break;
default:
outputLines.push(inputLine);
}
@ -64,8 +100,9 @@ export class CommandParser {
let moduleName = markdownPlugins[pluginName];
if (moduleName) {
let plugin: any = require(moduleName);
if (plugin) {
if (plugin && !this.loadedPlugins.includes(plugin)) {
renderer.addPlugin(plugin);
this.loadedPlugins.push(plugin);
}
} else {
console.error(`Plugin "${pluginName}" not found.`);

@ -2,8 +2,11 @@ import * as MarkdownIt from 'markdown-it';
import * as fsx from 'fs-extra';
import * as path from 'path';
import * as chokidar from 'chokidar';
import * as puppeteer from 'puppeteer';
import {JSDOM} from 'jsdom';
import {CommandParser} from "./CommandParser";
import {EventEmitter} from "events";
import {PDFFormat} from "puppeteer";
export class Renderer extends EventEmitter {
private md: MarkdownIt;
@ -64,6 +67,25 @@ export class Renderer extends EventEmitter {
return result;
}
/**
* Renders the file to pdf by rendering the html and printing it to pdf with puppeteer
* @param filename
* @param output
* @param format
*/
public async renderPdf(filename: string, output: string, format: PDFFormat = 'A4') {
let html = await this.render(filename);
if (this.commandParser.pageFormat)
format = this.commandParser.pageFormat;
console.log('Launching puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(html);
console.log(`Starting PDF export (format: ${format}) to ${output}`);
await page.pdf({path: output, format: format, margin: {top: '1.5cm', bottom: '1.5cm'}});
await browser.close();
}
/**
* Watches for changes.
* @param filename
@ -89,5 +111,12 @@ export class Renderer extends EventEmitter {
*/
private configure() {
this.useBefore((a: string, b: string, c: Renderer) => this.commandParser.processCommands(a, b, c));
this.useAfter(async (doc: string) => {
let dom: JSDOM = new JSDOM(doc);
let styleTag = dom.window.document.createElement('style');
styleTag.innerHTML = await fsx.readFile(path.join(__dirname, 'styles/default.css'), 'utf-8');
dom.window.document.head.appendChild(styleTag);
return dom.serialize();
});
}
}

@ -0,0 +1,12 @@
export const pageFormats = {
LETTER: "Letter",
LEGAL: "Legal",
TABLOID: "Tabloid",
LEDGER: "Ledger",
A0: "A0",
A1: "A1",
A2: "A2",
A3: "A3",
A4: "A4",
A5: "A5"
};

@ -1,20 +1,59 @@
import {Renderer} from "./Renderer";
import {writeFile} from 'fs-extra';
import {extname} from 'path';
import {ArgumentParser} from "argparse";
/**
* Returns the filename without the extension.
* @param file
*/
function noExt(file: string): string {
return file.replace(extname(file), '');
}
async function main() {
const args = process.argv.slice(2);
const parser = new ArgumentParser({
addHelp: true
});
parser.addArgument('file',{
help: 'The file to render'
});
parser.addArgument(
['-w', '--watch'],
{
help: 'Watch files for changes',
required: false,
action: 'storeTrue'
}
);
parser.addArgument(
['--pdf'],
{
help: 'Output as pdf',
required: false,
action: 'storeTrue'
}
);
let args = parser.parseArgs();
let renderer = new Renderer();
if (args[1] && args[1] === '--watch') {
if (args.watch) {
let outputFile = noExt(args.file) + '.html';
renderer.on('rendered', async (html) => {
await writeFile(args[0].replace(extname(args[0]), '') + '.html', html);
await writeFile(outputFile, html);
console.log(`File stored as ${outputFile}`);
});
renderer.watch(args[0]);
renderer.watch(args.file);
} else if (args.pdf) {
let outputFile = noExt(args.file) + '.pdf';
await renderer.renderPdf(args.file, outputFile);
console.log(`File stored as ${outputFile}`);
} else {
let html = await renderer.render(args[0]);
await writeFile(args[0].replace(extname(args[0]), '') + '.html', html);
let outputFile = noExt(args.file) + '.html';
let html = await renderer.render(args.file);
await writeFile(outputFile, html);
console.log(`File stored as ${outputFile}`);
}
}
main()
main();

@ -1,4 +1,4 @@
export const markdownPlugins: object = {
export const markdownPlugins: any = {
emoji: 'markdown-it-emoji',
footnote: 'markdown-it-footnote',
anchor: 'markdown-it-anchor',
@ -9,10 +9,10 @@ export const markdownPlugins: object = {
checkbox: 'markdown-it-checkbox',
imsize: 'markdown-it-imsize',
highlightjs: 'markdown-it-highlightjs',
prism: 'markdown-it-prism',
smartarrows: 'markdown-it-smartarrows',
plantuml: 'markdown-it-plantuml',
mathjax: 'markdown-it-mathjax',
math: 'markdown-it-math',
div: 'markdown-it-div',
'toc-done-right': 'markdown-it-toc-done-right'
};

@ -0,0 +1,31 @@
@import includes/vars
@import includes/w3-math
@import includes/highlighjs
body
font-family: Arial, sans-serif
width: 80%
margin: auto
pre
background-color: $backgroundSecondary
padding: 2px
border-radius: 2px
p
code
background-color: $backgroundSecondary
padding: 1px
border-radius: 2px
nav
border: 2px solid $backgroundSecondary
page-break-after: always
.page
page-break-before: always
page-break-after: always
page-break-inside: avoid
.newpage
page-break-after: always

@ -0,0 +1,57 @@
/*
*
*Original highlight.js style (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
.hljs
display: block
overflow-x: auto
padding: 0.5em
background: #F0F0F0
color: #444
/* Base color: saturation 0;
.hljs-subst
color: #444
.hljs-comment
color: #888888
.hljs-keyword, .hljs-attribute, .hljs-selector-tag, .hljs-meta-keyword, .hljs-doctag, .hljs-name
font-weight: bold
/* User color: hue: 0
.hljs-type, .hljs-string, .hljs-number, .hljs-selector-id, .hljs-selector-class, .hljs-quote, .hljs-template-tag, .hljs-deletion
color: #880000
.hljs-title, .hljs-section
color: #880000
font-weight: bold
.hljs-regexp, .hljs-symbol, .hljs-variable, .hljs-template-variable, .hljs-link, .hljs-selector-attr, .hljs-selector-pseudo
color: #BC6060
/* Language color: hue: 90;
.hljs-literal
color: #78A960
.hljs-built_in, .hljs-bullet, .hljs-code, .hljs-addition
color: #397300
/* Meta color: hue: 200
.hljs-meta
color: #1f7199
.hljs-meta-string
color: #4d99bf
/* Misc effects
.hljs-emphasis
font-style: italic
.hljs-strong
font-weight: bold

@ -0,0 +1 @@
$backgroundSecondary: #eee

@ -0,0 +1,380 @@
math
{line-height:1.3em;
text-indent:0;}
math[display="block"]
{display:block;
text-align:center;
page-break-inside:avoid;}
mfrac
{display:inline-table;
white-space:nowrap;
border-collapse:collapse;
text-align:center;
vertical-align:0.9em;
margin:0 2px;
font-size:1em;}
mfrac > *
{line-height:1.3em;
font-size:0.9em;}
mfrac > *:first-child
{display:inline-table;
vertical-align:text-bottom;}
mfrac > * + *
{border-top:solid thin;
display:table-row;}
mfrac[linethickness="0"] > * + *
{border-top:none;}
mfrac[linethickness="2"] > * + *, mfrac[linethickness="thick"] > * + *
{border-top:solid medium;}
mfrac[numalign="left"] > *:first-child, mfrac[denalign="left"] > * + *
{text-align:left;}
mfrac[numalign="right"] > *:first-child, mfrac[denalign="right"] > * + *
{text-align:right;}
msub, msup, msubsup, mmultiscripts
{display:inline-table;
line-height:0.4em;}
msubsup, msup, mmultiscripts
{margin-top:0.4ex;
table-baseline:2;}
msubsup, msub, mmultiscripts
{margin-bottom:0.4ex;}
msubsup, msup
{direction:rtl;}
msub > *
{display:table-row;}
none
{content:"\A0";}
msubsup > *, msup > *
{display:table-row;
direction:ltr;
text-align:left;}
mmultiscripts > *
{display:none;}
mmultiscripts > *:first-child, mmultiscripts > mprescripts + *
{display:table-row;}
mmultiscripts > mprescripts + * + *
{display:table-header-group;}
msub > *:first-child:after, msub > * + *:before,
msubsup > *:first-child:before, msup > *:first-child:before,
mmultiscripts > *:first-child:before
{display:table-cell;
content:"\A0";}
msubsup > * + * + *, msup > * + *
{display:table-header-group;}
msub > * + *, msup > * + *, msubsup > * + *, munder > * + *, mover > * + *, munderover > * + *,
mmultiscripts > * + *
{font-size:0.7em;}
munder, munderover, mover
{display:inline-table;
margin:1px;
text-align:center;}
munder > *, munderover > *, mover > *
{display:table-row;}
mover > * + *, munderover > * + * + *
{display:table-header-group;}
mover, munderover
{table-baseline:2;}
msqrt
{display:inline-block;
margin:1px 0 1px 22px;
border-top:solid 1px;
border-left:groove 2px;
padding:2px 5px 0 0;}
msqrt:before
{display:inline-block;
vertical-align:bottom;
content:'';
width:22px;
height:14px;
background-repeat:no-repeat;
margin:0 3px 0 -22px;}
msqrt:before, mroot > * + *:after
{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAOCAYAAAArMezNAAAArklEQVR42mNgIAzkgJiRgYpAGIgnAfFvIF4JxKyUGsgOxKVA/ASI5wCxHRBvA+K9QMxHrqGRQLweiDOBmBdJnAWI5wPxeSAWJ8VAPSBOAWJTAuo6gPguEKsQY6gQEPOQ4Ih8IH4JxCYMNABhQPwOiN1pYbgzEL8C4hhaGG4AxM+BuJgWhisB8W0g7qOF4SJAfBqIl1EjI6EDbiDeAcQ7SUxlRAGQa5cA8RkGGoFeACG1GGdqegJfAAAAAElFTkSuQmCC");}
mroot
{display:inline-table;
direction:rtl;}
mroot > *
{display:table-cell;
direction:ltr;
text-align:left;}
mroot > *:first-child
{border-top:solid 1px;
border-left:groove 2px;
padding:2px 5px 0 3px;}
mroot > * + *
{vertical-align:bottom;
text-align:right;
font-size:0.7em;
line-height:1em;}
mroot > * + *:after
{display:block;
content:"";
width:22px;
height:14px;
margin-right:-1px;
margin-left:auto;}
mfenced
{display:inline-table;
border-collapse:separate;
border-spacing:0.2ex 0;
white-space:nowrap;
margin:1px;}
mfenced > *:first-child
{display:table-row;}
mfenced > * + *
{display:none;}
mfenced > *:before, mfenced > *:after
{display:table-cell;
content:"\A0";
background-repeat:no-repeat;
background-size:100% 100%;}
mfenced > *:before
{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAABQCAYAAAAZQFV3AAABtElEQVR42s2ZK0hEURCGr8UiPhYVRJAFoxssFtsafDXRKLogNpNNDDazTSxiMYhoMdtWsAibtKhssCj4ALWIoM4s58Awdb7gD3+45eO8z5z/FgWnfnEfAWoTz4tHCJhCdsQDBGxKXBd3EbBl8a14kIBtiF+pMdsS/4pnCNic+Ed8SMAq4g/xM7HWetIEaFdrROtOEuySgE0kmHqB2FKNBLtP3yGtmtatRWGd4scEexF3RIHrpnV7xGRcG+BsFDZuYO/i9ihw3wCPicn4NMDFKHDJwNS9UeCBgTWJ2W0a4GkUVnbd3YwCaw44TY6fOnyQ3hjYE3FUfRngVRQ45Lp7FgVWHXA3Clyhl8y2A4ZvtyMHnIwCzx1wLAq8cMDRKLDhgMNR4J0DlqPABwcsRYFvDtgdBX47YPHvgHiX8UnBlw2+sPGthx8O+PGFH7D4FVAt4EsKv0bxi16FliIqvFjCyzm84FShJbEfR6Rox58V+MNHhT7NVPjjUYU+b1X4AxyPCFRoiKHCYxYVGgRloVGVCg/TVGjcl4UGklloZJqFhrpZaOychQbjWWh0b7co9nPBqvX74w/Kju1j59IjnwAAAABJRU5ErkJggg==");}
mfenced > *:after
{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAABQCAYAAAAZQFV3AAABl0lEQVR42s3ZPyhFYRjH8ddiEa5cJaVbd2SwWGwM/m1yR3FKNpNNBpvZJossBonFbKMs6k4s6A4Wyp/CIoXfqXd4etbnOzj1Wz+97znv3+eklFJV6UvwM6TMK20k2q9sZxx7upRzZYpEB5RbZYl+p6/KOonOKL/KJokeKD/KHAWWY/RZ+VCGKbTIXS8/VIVCLzN6TIGNDJaZIMBySt5nsElN0VXTyhUC7FBeMviodBLormnlGgHOGvCaANuVd4OOEeiRAfcIcMGAn8TH6TVgmUWilS0D7hPgiQFbBLjhul2LgtMOLKJg1YHIe3wy4A0BXhnwKwFL2qnr9mAU3HHgOD10lqNg4cCtKDjpwMMoOOrAsyg44sCLKFh3YDMK1hx4FwV7HPgQBbsd+EbMZwt+/zsQ7zL+UfBhgw9sfOrhiwO+fOELLL4F4JsUvo3iGz16FMEPS/hxDj9w4kdi9NCOXyvwiw96NcMvj/j1Fr2A4yUCtIiBl1nwQhBaqkKLaXi5Dy1IoiVTtKiLlp3Rwjhaukd/LqC/P/4AwHbtY0GeJ20AAAAASUVORK5CYII=");}
mfenced[open="["] > *:before
{border-style:solid;
border-width:1px 0 1px 1px;
background-image:none;}
mfenced[close="]"] > *:after
{border-style:solid;
border-width:1px 1px 1px 0;
background-image:none;}
mfenced[open="\2016"] > *:before
{border-style:double;
border-width:0 0 0 3px;
background-image:none;}
mfenced[close="\2016"] > *:after
{border-style:double;
border-width:0 3px 0 0;
background-image:none;}
mfenced[open="\27e6"] > *:before
{border-style:double;
border-width:2px 0 2px 3px;
background-image:none;}
mfenced[close="\27e7"] > *:after
{border-style:double;
border-width:2px 3px 2px 0;
background-image:none;}
mfenced[open="|"] > *:before
{border-style:solid;
border-width:0 0 0 1px;
background-image:none;}
mfenced[close="|"] > *:after
{border-style:solid;
border-width:0 1px 0 0;
background-image:none;}
mfenced[open="\230a"] > *:before
{border-style:solid;
border-width:0 0 1px 1px;
background-image:none;}
mfenced[close="\230b"] > *:after
{border-style:solid;
border-width:0 1px 1px 0;
background-image:none;}
mfenced[open="\2308"] > *:before
{border-style:solid;
border-width:1px 0 0 1px;
background-image:none;}
mfenced[close="\2309"] > *:after
{border-style:solid;
border-width:1px 1px 0 0;
background-image:none;}
mfenced[open="{"] > *:before
{content:"\A0\A0";
background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAABQCAYAAAAZQFV3AAABuklEQVR42rWZuy9EURCHj0c8Qta7pRMh0WgUqBQqG4leNGRL2VZpO6GiIYobHf+DhIiSf8AjFBIkmwiNRNZvsmdjtM53J/naL+fec+7MnLkhpMeoWBZDoi1F1C12REW0pK6qQ1yIY+AJQ5M4ES+inxCWRU2UCFmXeBWfokAIN+PqMkJmG1GNwnlCWIyyL9FJCI+i8IqQNcfNMOE2IZyNMmOJPHvGGCHMouw7NQE04iYKHwlZazwqJjwjhJPu/R0SwkUn3CKEa05YJhOCsUoI95ywSAhPnXCGEF464QQhvHbCEUJ4SwvvnXCQED47YQ8hrNLCdydEUlfNkRwFJ3sjhONOeE4IF5zwgBD61LVBCPfDb3GaSpUNiI8o3CVWV4myu1Bv45J3176QBzGdKlsRT6FeP9r/I7DPqU8MizmxLnpTVoQL8UfOfVNyOTa5HGz808slOeDpC0+weAkIAS5SFngZxQs93orgzRLezuENJ94S4007fq3ALz741Qy/POLXW/wCboGOCCzwIQY+ZsEHQfioygIdpoWQw7gPH0j6RJFRQnyo689kiRLig/HGBmGj+0agPxd8/Pn98QMoXcSZw6bNQwAAAABJRU5ErkJggg==");}
mfenced[close="}"] > *:after
{content:"\A0\A0";
background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAABQCAYAAAAZQFV3AAABoklEQVR42sWZvUpDQRBGxx+iosS/2GoXJIKNjYVapbAyCPbBRrEMaS1NJ1ppo1gEO30HQZGU+gIxooWgQkC0ESR+iwOZBzjghdMe9t7dnZ39rplZRkyJDZE38OkTNXEgRkjxubgRg5RwQryKC9FDSXdER1QpYVZ8iTcxTEnrPspdSlh0YZuaoCHx7dISNcqGC88o4b4L0+T0EsJ1FyaWCeFsECJrMhWOHxfWqe/45MJ7SnjlwrSE+gnhafiO84RwLwjXCGE1CLcI4WYQIoWiFIRHhHApCC8J4VwQ3hLCmSC8o4VNQpgLwhYhHA3CF1rYJoSZIPygKk4ngDzvQZglhNdBWCCEJ0G4SggrBpewBeseVsfUxBy68FNMEsLU1j24tEaNclE8+o4pUNIB+ztnnkXZwGdMbIsVMS3GfZv+vxB9ZXRS8GWDLmx86+HFAS9feIHFjwD0kMKPUfygx1sRvFnC2zm84cRbYrxpx68V+MUHv5rhl0f8eotfwNGIAA8x8JgFD4LwqAoN0/C4Dw8k0cgUD3XR2BkPxrHoHvu5gP7++AUnfMSZodLInwAAAABJRU5ErkJggg==");}
mfenced[open="\27e8"] > *:before, mfenced[open="\2329"] > *:before
{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAABZCAYAAAA+TwS/AAAB50lEQVR42s2ZPyiFURiHj7+Lrkhks0goEqtFWSilW8oqg5GULExWk5K6g26ZrFI3i24Go8kkkWQwSPkTA673lzOcvu7kfZSnfuvTe77vnO+c834hsORgX+gjZR2WIVI4T8oaLGukcNYyTgoPLfWUbNiyR1ZXtOQpWbvl0dJECfVmDyhZneXWMkcJZywfljZKeGIpUzKt2YplkRLuRmEXIdMze7OcUdWtxurWqalyE4UDhDAfZZfUcMtRuEnIBqNMGSWEhSi7t9R4Za2W1ygsENWtJMOd9MpqLddR9mRp9Aqnk+r2ieEeJ8JZr6zf8hVl75Zmr3Anqa7klbVYXhLhgle4nMg+LZ0emVbCVSI89VY3lcgqcWK7OMoIuz2y3mSqKOfe6rYz1W14ZJq4zxnhiEe4lJHdeKfKRUa45RFOZGTKmEdYysgews+2+St6MlNFKXqq26oy3GmP8K6KsPVfVYg/Q/wt/8k8xFcKvpb/5GuDfw8F/sUW6J4i8F0P35cFenIQ+NlGoKcvgZ8PBXqCFfgZG78FhADfUwR+kwoBvusJ/DYqygG8Lwv8Ro/3HATaFRF430agnSWB974E2p0TeP8Q73AKtAcr8C6xKAawjy3wTrtA/wUI/G8F/j9FzNNC/J+U6KOFuW87Lt0vtp6NAwAAAABJRU5ErkJggg==");}
mfenced[close="\27e9"] > *:after, mfenced[close="\232A"] > *:after
{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAABZCAYAAAA+TwS/AAAByElEQVR42s2ZPSjFURiH/z4XkZvIZpFQJFaLslDqdkvdVQYjqZuFyWq6JXUH3TJZpW4WyWA0mSS6yWCQ8hGDr9+pdzid7vY+g1PP+vSe//89H+97sizLOjN4jNDCCdFHS5dp4aZoI4WzokgKW8UxPe0DMUkKC6JKCjvEs+glpUf2x7GxJO5FCyXsEV9ikYzyTJyTwlXxa2scGQMm3CejvBQf9k2RsWVRblDCMRPWyRS6MWmBEu6Y8IwSTpswME4Im8SjCStUlBUTvoscIZyPpl0ihO3ixYR3opmQHkZR5glhMRKeEsIu8WnCHzFKSGtRlHuEcCUSvolur7BffEfSdSLKi0h4ayvJNUqRMLDgFQ4mwhNi2leRMKTQsFe4nUS56xVOJcJXS3zXqCfSNa+wnAivvSk0kwgDcx5hOFafEmHNO+1qIgwpNOQR5htMu+wR5hoIH/5VhOg3xP8ynof4SkHXMr7b4PshumPjZwp+6qHnMn5zwO826O0Lvx/iN1j0jo1XAXidglZSeK2HV6N4vYxW9HjPAe+KoH0bvLOE975CmmDdObx/iHc40R4s3iXG+9hopx1/C8BfK/D3FPTFB3+Twl/N0He9P0bV3S+iamc2AAAAAElFTkSuQmCC");}
mfenced[open=""] > *:before, mfenced[close=""] > *:after
{content:normal;}
mover > * + mo[fence="true"], munder > * + mo[fence="true"]
{content:"\A0";
line-height:1ex;
background-size:100% 100%;
background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAUCAYAAAAa2LrXAAABxElEQVR42u2YsUsCURzHnxlncmJlttYWkeDi4pBNDU6J0C4tRmPc2phb2KSL0iBt9T8EhTjWP2CFDoInCFKLINe3+gaH1ekdZ6S8L3yWd+/d7/v7+t5xpxD/WwqRcqhlIuVQa0TKoXaIlANlwCHJyDjsaQs0wRJpckxqhFZADnSBZhrXOJbjHCkoyF2VBFlQBC/AAM/AZ5rr45jBOUWuSfIewWkNYR5EwR4bOgEFcA2q4B7UwRNocRf1GMRvPIL4D7XivGa1tscaLdas00OVngr0mKXnKHv4E82BBI9TBTyA/oiG7DAA50C18KByzsDFun32UmFvCfbqihZAClwA3UXT73TALSiBYxCz4SvGNSXeo+OyN509p5iBbanc6l2L3dIAN6AMTvnrHbDoNoiAdRIGi2SSn2aKqU7YVD9CTyl61Oi5zB4aFru6yyzUcQx4eHN9aIvXwBlIg00xm9+nCntLs9fa0CNKZzYeq+N6xcmvfCbsAv8MhjWu/MygwkwMZvTtWAfAHWiDIzHFrwUTVJDZtJlVwHwxDy5BSOY0UiFmlf8a2BCfb/hS9pRjdmIfeGUetuVldmJVZuFYH9nJv8ydS3kDn8bEmYaXZFsAAAAASUVORK5CYII=");}
munder > * + mo[fence="true"]
{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAUCAYAAAAa2LrXAAABzElEQVR42u2Xv0tCURzFrxlPRbEyW2uLSHBxccimBqdEaJcWozFcG3MLm3RRGqSt/oegEEf7B6zQQVBBkFoEsfPsCA8zX+9m0Y974LNc773f7znvPrlPCCE0oSSrYXYrKgdpDbPbA3aVhWXZmZ1YB2mVh2Wlmd1QGXABfCoXU/mYVcY46AG3oAkOgVfl9EZeZtNkVp7xCU5wCQbgGRTBDnD949BczKDITAbMyPneAhtIgRYn6/RAGZyCONgQf/Pqo9FbnF7L9D7KocVsbB/ZzA2OQcewgZE+qIFrUAAn3HwfxMAWCIA14gcLRPviEEZ1/Ib6AfYUY48p9lyghxo9TfLaYRZumYacLHo+dipnQRvcgDw4AiELfYW4Js892jPurUXPsWmvq1XNgQifnv6fcDd2xD+LfgLOTJ60m3P6M6zbo5civUXo9Vs0D4JgFyR51LPgCpRABVTBA2jwdeiaGLoH4Qm1wvxt2touazRYs8oeSuwpyx6T7DlID79S+rVgE0RpKAeeGMQjcBjmOjg24Jwc10S5h7p2Ucvi9Ybf4es0Uopjac5RMpF+qupgkdQ5pmRBCXBAEioOOW0TJUmtEiVJLRElSWnih393vwCZOsSZn592GQAAAABJRU5ErkJggg==");}
menclose
{display:inline-table;
border-collapse:separate;
border-spacing:0.4ex 0;}
menclose[notation="top"]
{border-top:solid thin;}
menclose[notation="bottom"]
{border-bottom:solid thin;}
menclose[notation="right"]
{border-right:solid thin;}
menclose[notation="left"]
{border-left:solid thin;}
menclose[notation="box"]
{border:solid thin;}
menclose[notation="horizontalstrike"]
{text-decoration:line-through;}
mtable
{display:inline-table;
line-height:1.5em;
text-align:center;
vertical-align:middle;}
mtr
{display:table-row;}
mtd
{display:table-cell;
padding:0 0.5ex;}
mtable[columnalign="left"], mtr[columnalign="left"], mtd[columnalign="left"]
{text-align:left;}
mtable[columnalign="right"], mtr[columnalign="right"], mtd[columnalign="right"]
{text-align:right;}
mtable[rowalign="top"] mtd, mtable mtr[rowalign="top"] mtd,
math mtable mtr mtd[rowalign="top"]
{vertical-align:top}
mtable[rowalign="bottom"] mtd, mtable mtr[rowalign="bottom"] mtd,
math mtable mtr mtd[rowalign="bottom"]
{vertical-align:bottom}
mtable[rowalign="center"] mtd, mtable mtr[rowalign="center"] mtd,
math mtable mtr mtd[rowalign="center"]
{vertical-align:middle}
mtable[frame="solid"]
{border:solid thin;}
mtable[frame="dashed"]
{border:dashed thin;}
mtable[rowlines="solid"], mtable[rowlines="dashed"],
mtable[columnlines="solid"], mtable[columnlines="dashed"]
{border-collapse:collapse;}
mtable[rowlines="solid"] > mtr + mtr
{border-top:solid thin;}
mtable[rowlines="dashed"] > mtr + mtr
{border-top:dashed thin;}
mtable[columnlines="solid"] > mtr > mtd + mtd
{border-left:solid thin;}
mtable[columnlines="dashed"] > mtr > mtd + mtd
{border-left:dashed thin;}
mspace[linebreak="goodbreak"]:before
{content:"\200B";
white-space:normal;}
mspace[linebreak="newline"]:before, mspace[linebreak="indentingnewline"]:before
{content:"\000A";
white-space:pre;}
mspace[width]:before
{content:normal;}
mspace[width="verythinmathspace"]
{padding:0 0.05em;}
mspace[width="thinmathspace"]
{padding:0 0.08em;}
mspace[width="mediummathspace"]
{padding:0 0.11em;}
mspace[width="thickmathspace"]
{padding:0 0.14em;}
mspace[width="verythickmathspace"]
{padding:0 0.17em;}
mo[largeop="true"]
{font-size:1.3em;
vertical-align:-0.1ex;}
mo[form="infix"], * + mo
{padding:0 0.3ex;}
mo[form="prefix"]
{padding:0 0 0 0.5ex;}
mo[form="postfix"]
{padding:0 0.5ex 0 0;}
mo[fence="true"], mo[separator="true"]
{padding:0;}
mi[mathvariant="bold"], mi[mathvariant="bold-italic"], mi[mathvariant="bold-sans-serif"],
mi[mathvariant="sans-serif-bold-italic"],mn[mathvariant="bold"], mn[mathvariant="bold-italic"],
mn[mathvariant="bold-sans-serif"], mn[mathvariant="sans-serif-bold-italic"],mo[mathvariant="bold"],
mo[mathvariant="bold-italic"], mo[mathvariant="bold-sans-serif"], mo[mathvariant="sans-serif-bold-italic"],
ms[mathvariant="bold"], ms[mathvariant="bold-italic"], ms[mathvariant="bold-sans-serif"],
ms[mathvariant="sans-serif-bold-italic"],mtext[mathvariant="bold"], mtext[mathvariant="bold-italic"],
mtext[mathvariant="bold-sans-serif"], mtext[mathvariant="sans-serif-bold-italic"]
{font-weight:bold;
font-style:normal;}
mi[mathvariant="monospace"], mn[mathvariant="monospace"],mo[mathvariant="monospace"],
ms[mathvariant="monospace"],mtext[mathvariant="monospace"]
{font-family:monospace;
font-style:normal;}
mi[mathvariant="sans-serif"], mi[mathvariant="bold-sans-serif"], mi[mathvariant="bold-sans-serif"],
mi[mathvariant="sans-serif-italic"], mi[mathvariant="sans-serif-bold-italic"],mn[mathvariant="bold-sans-serif"],
mn[mathvariant="sans-serif"], mn[mathvariant="bold-sans-serif"], mn[mathvariant="sans-serif-italic"],
mn[mathvariant="sans-serif-bold-italic"], mo[mathvariant="sans-serif"], mo[mathvariant="bold-sans-serif"],
mo[mathvariant="bold-sans-serif"], mo[mathvariant="sans-serif-italic"], mo[mathvariant="sans-serif-bold-italic"],
ms[mathvariant="sans-serif"], ms[mathvariant="bold-sans-serif"], ms[mathvariant="bold-sans-serif"],
ms[mathvariant="sans-serif-italic"], ms[mathvariant="sans-serif-bold-italic"], mtext[mathvariant="sans-serif"],
mtext[mathvariant="bold-sans-serif"], mtext[mathvariant="bold-sans-serif"], mtext[mathvariant="sans-serif-italic"],
mtext[mathvariant="sans-serif-bold-italic"]
{font-family:sans-serif;
font-style:normal;}
mi, mi[mathvariant="italic"], mi[mathvariant="bold-italic"],mi[mathvariant="sans-serif-italic"],
mi[mathvariant="sans-serif-bold-italic"],mn[mathvariant="italic"], mn[mathvariant="bold-italic"],
mn[mathvariant="sans-serif-italic"], mn[mathvariant="sans-serif-bold-italic"],mo[mathvariant="italic"],
mo[mathvariant="bold-italic"],mo[mathvariant="sans-serif-italic"], mo[mathvariant="sans-serif-bold-italic"],
ms[mathvariant="italic"], ms[mathvariant="bold-italic"],ms[mathvariant="sans-serif-italic"],
ms[mathvariant="sans-serif-bold-italic"],mtext[mathvariant="italic"], mtext[mathvariant="bold-italic"],
mtext[mathvariant="sans-serif-italic"], mtext[mathvariant="sans-serif-bold-italic"]
{font-style:italic;}
mi[mathvariant="normal"], mn[mathvariant="normal"], mo[mathvariant="normal"],
ms[mathvariant="normal"], mtext[mathvariant="normal"]
{font-style:normal;}
ms:before, ms:after
{content:"\0022"}
ms[lquote]:before
{content:attr(lquote)}
ms[rquote]:after
{content:attr(rquote)}
mphantom
{visibility:hidden}
merror
{outline:solid thin red}
merror:before
{content:"Error: "}
mrow
{white-space:nowrap;}
math[display='block']
{display:block;
margin:1em 0 1em 3em;}
mstack, mlongdiv
{display:inline-table;
font-family:monospace;}
mstack
{text-align:right;
border-collapse:collapse;}
mstack[align='top'], mlongdiv[align='top']
{vertical-align:top;}
mstack[align='bottom'], mlongdiv[align='bottom']
{vertical-align:bottom;}
mstack[align='center'], mlongdiv[align='center']
{vertical-align:middle;}
msline
{display:block;
border-bottom:solid thin;}
mstack > *, mlongdiv > mn:first-child
{display:table-row;}
mlongdiv > *:first-child + *
{display:table-cell;}
mlongdiv > *:first-child + * + *
{border-top:solid thin;}
mlongdiv > *:first-child:before
{display:table-cell;
content:'\a0';}
mlongdiv > *:first-child + *:after
{content:')';}
mlongdiv > *
{display:block;}
mscarry
{display:none;}
maction > * + *
{display:none;}
maction[actiontype="tooltip"]:focus > * + *,
maction[actiontype="tooltip"]:hover > * + *
{position:fixed;
display:block;
top:0;
left:0;
background-color:InfoBackground;
color:InfoText;
padding:0.5ex;
border:solid 1px;}
annotation, annotation-xml
{display:none;}
Loading…
Cancel
Save