├── src
├── core
│ ├── pkg
│ │ └── .npmignore
│ ├── src
│ │ ├── katex
│ │ │ ├── source.rs
│ │ │ ├── mod.rs
│ │ │ ├── types.rs
│ │ │ └── constructor.rs
│ │ ├── ext.rs
│ │ ├── lib.rs
│ │ ├── symbol.rs
│ │ ├── node.rs
│ │ ├── utils.rs
│ │ └── main.rs
│ └── Cargo.toml
└── utils.js
├── CONTRIBUTING.md
├── .gitignore
├── test
├── index.css
├── package.json
├── webpack.config.js
├── index.html
├── utils.js
└── index.js
├── lib
├── README.md
└── katex
│ ├── src
│ ├── environments.js
│ ├── functions
│ │ ├── relax.js
│ │ ├── ordgroup.js
│ │ ├── htmlmathml.js
│ │ ├── math.js
│ │ ├── hbox.js
│ │ ├── symbolsOp.js
│ │ ├── tag.js
│ │ ├── raisebox.js
│ │ ├── vcenter.js
│ │ ├── pmb.js
│ │ ├── mathchoice.js
│ │ ├── char.js
│ │ ├── overline.js
│ │ ├── underline.js
│ │ ├── cr.js
│ │ ├── kern.js
│ │ ├── symbolsOrd.js
│ │ ├── accentunder.js
│ │ ├── verb.js
│ │ ├── text.js
│ │ ├── environment.js
│ │ ├── styling.js
│ │ ├── href.js
│ │ ├── color.js
│ │ ├── lap.js
│ │ ├── symbolsSpacing.js
│ │ ├── rule.js
│ │ ├── sizing.js
│ │ ├── font.js
│ │ ├── html.js
│ │ ├── smash.js
│ │ ├── phantom.js
│ │ ├── sqrt.js
│ │ ├── utils
│ │ │ └── assembleSupSub.js
│ │ ├── horizBrace.js
│ │ └── includegraphics.js
│ ├── unicodeAccents.js
│ ├── unicodeSymbols.js
│ ├── types.js
│ ├── SourceLocation.js
│ ├── Token.js
│ ├── parseTree.js
│ ├── functions.js
│ ├── buildTree.js
│ ├── tree.js
│ ├── unicodeSupOrSub.js
│ ├── spacingData.js
│ ├── ParseError.js
│ ├── Style.js
│ ├── defineEnvironment.js
│ ├── defineMacro.js
│ ├── utils.js
│ ├── unicodeScripts.js
│ ├── units.js
│ ├── Namespace.js
│ ├── wide-character.js
│ └── Lexer.js
│ ├── contrib
│ ├── mathtex-script-type
│ │ └── mathtex-script-type.js
│ ├── copy-tex
│ │ ├── copy-tex.js
│ │ └── katex2tex.js
│ └── auto-render
│ │ ├── splitAtDelimiters.js
│ │ └── auto-render.js
│ └── cli.js
├── Makefile
├── esbuild.config.mjs
├── LICENSE
├── .github
└── workflows
│ └── static.yml
├── scripts.config.js
├── package.json
├── wypst.js
├── README.md
└── scripts
└── symbol_gen.js
/src/core/pkg/.npmignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Setup
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | node_modules
3 | dist
4 | *.tgz
5 | .envrc
6 |
--------------------------------------------------------------------------------
/test/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | display: flex;
3 | }
4 |
5 | #katex, #typst, #wypst {
6 | flex: 1;
7 | }
8 |
--------------------------------------------------------------------------------
/src/core/src/katex/source.rs:
--------------------------------------------------------------------------------
1 | // Reference: SourceLocation.js
2 |
3 | use serde::Serialize;
4 |
5 | #[derive(Clone, Serialize)]
6 | pub struct SourceLocation {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/lib/README.md:
--------------------------------------------------------------------------------
1 | # lib
2 | Libraries that may be difficult to work with when installing directly with npm. Currently katex has flow types which I chose not to deal when building and thus I made a stripped version of it.
3 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: wasm
2 | wasm:
3 | cd src/core && wasm-pack build --target web
4 |
5 | .PHONY: wasm-dev
6 | wasm-dev:
7 | cd src/core && wasm-pack build --target web --dev
8 |
9 | .PHONY: build
10 | build:
11 | node esbuild.config.mjs
12 |
--------------------------------------------------------------------------------
/src/core/src/katex/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod symbol;
2 | pub mod node;
3 | pub mod source;
4 | pub mod types;
5 | pub mod constructor;
6 |
7 | pub use symbol::*;
8 | pub use node::*;
9 | pub use source::*;
10 | pub use types::*;
11 | pub use constructor::*;
12 |
--------------------------------------------------------------------------------
/lib/katex/src/environments.js:
--------------------------------------------------------------------------------
1 | //
2 | import {_environments} from "./defineEnvironment";
3 |
4 | const environments = _environments;
5 |
6 | export default environments;
7 |
8 | // All environment definitions should be imported below
9 | import "./environments/array";
10 |
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wypst": "file:.."
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/relax.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 |
4 | defineFunction({
5 | type: "internal",
6 | names: ["\\relax"],
7 | props: {
8 | numArgs: 0,
9 | allowedInText: true,
10 | },
11 | handler({parser}) {
12 | return {
13 | type: "internal",
14 | mode: parser.mode,
15 | };
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/ordgroup.js:
--------------------------------------------------------------------------------
1 | //
2 | import {defineFunctionBuilders} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 |
5 | import * as html from "../buildHTML";
6 | import * as mml from "../buildMathML";
7 |
8 | defineFunctionBuilders({
9 | type: "ordgroup",
10 | htmlBuilder(group, options) {
11 | if (group.semisimple) {
12 | return buildCommon.makeFragment(
13 | html.buildExpression(group.body, options, false));
14 | }
15 | return buildCommon.makeSpan(
16 | ["mord"], html.buildExpression(group.body, options, true), options);
17 | },
18 | mathmlBuilder(group, options) {
19 | return mml.buildExpressionRow(group.body, options, true);
20 | },
21 | });
22 |
23 |
--------------------------------------------------------------------------------
/lib/katex/src/unicodeAccents.js:
--------------------------------------------------------------------------------
1 | // Mapping of Unicode accent characters to their LaTeX equivalent in text and
2 | // math mode (when they exist).
3 | // This exports a CommonJS module, allowing to be required in unicodeSymbols
4 | // without transpiling.
5 | module.exports = {
6 | '\u0301': {text: "\\'", math: '\\acute'},
7 | '\u0300': {text: '\\`', math: '\\grave'},
8 | '\u0308': {text: '\\"', math: '\\ddot'},
9 | '\u0303': {text: '\\~', math: '\\tilde'},
10 | '\u0304': {text: '\\=', math: '\\bar'},
11 | '\u0306': {text: '\\u', math: '\\breve'},
12 | '\u030c': {text: '\\v', math: '\\check'},
13 | '\u0302': {text: '\\^', math: '\\hat'},
14 | '\u0307': {text: '\\.', math: '\\dot'},
15 | '\u030a': {text: '\\r', math: '\\mathring'},
16 | '\u030b': {text: '\\H'},
17 | '\u0327': {text: '\\c'},
18 | };
19 |
--------------------------------------------------------------------------------
/src/core/src/ext.rs:
--------------------------------------------------------------------------------
1 | use typst;
2 |
3 | pub trait DelimiterOpenClose {
4 | fn open(self) -> char;
5 | fn close(self) -> char;
6 | }
7 |
8 | impl DelimiterOpenClose for typst::math::Delimiter {
9 | /// The delimiter's opening character.
10 | fn open(self) -> char {
11 | match self {
12 | Self::Paren => '(',
13 | Self::Bracket => '[',
14 | Self::Brace => '{',
15 | Self::Bar => '|',
16 | Self::DoubleBar => '‖',
17 | }
18 | }
19 |
20 | /// The delimiter's closing character.
21 | fn close(self) -> char {
22 | match self {
23 | Self::Paren => ')',
24 | Self::Bracket => ']',
25 | Self::Brace => '}',
26 | Self::Bar => '|',
27 | Self::DoubleBar => '‖',
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import ParseError from '../lib/katex/src/ParseError';
2 | import Settings from '../lib/katex/src/Settings';
3 | import parseTree from '../lib/katex/src/parseTree';
4 | import buildTree from '../lib/katex/src/buildTree';
5 | import buildCommon from '../lib/katex/src/buildCommon';
6 | import { SymbolNode } from '../lib/katex/src/domTree';
7 |
8 | function renderError(error, expression, settings) {
9 | if (settings.throwOnError) {
10 | throw error;
11 | }
12 | const node = buildCommon.makeSpan(["katex-error"], [new SymbolNode(expression)]);
13 | node.setAttribute("title", error.toString());
14 | node.setAttribute("style", `color:${settings.errorColor}`);
15 | return node;
16 | }
17 |
18 | export default {
19 | ParseError,
20 | Settings,
21 | parseTree,
22 | buildTree,
23 | renderError,
24 | };
25 |
--------------------------------------------------------------------------------
/src/core/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "core"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["cdylib", "rlib"]
8 |
9 | [features]
10 | default = ["console_error_panic_hook"]
11 |
12 | [dependencies]
13 | typst = { git = "https://github.com/typst/typst.git", tag = "v0.10.0" }
14 | typst-syntax = { git = "https://github.com/typst/typst.git", tag = "v0.10.0" }
15 | wasm-bindgen = "0.2.84"
16 | serde = { version = "1.0", features = ["derive"] }
17 | serde-wasm-bindgen = "0.4"
18 | console_error_panic_hook = { version = "0.1.7", optional = true }
19 | js-sys = "0.3.64"
20 | phf = "0.11.2"
21 | serde_json = "1.0.108"
22 | comemo = "0.3.1"
23 | derive_builder = "0.12.0"
24 | log = "0.4.20"
25 |
26 | [dev-dependencies]
27 | wasm-bindgen-test = "0.3.34"
28 |
29 | [profile.release]
30 | opt-level = "z"
31 | lto = true
32 |
33 | [lib.metadata.wasm-pack.profile.release]
34 | wasm-opt = ["-Oz"]
35 |
--------------------------------------------------------------------------------
/lib/katex/contrib/mathtex-script-type/mathtex-script-type.js:
--------------------------------------------------------------------------------
1 | import katex from "katex";
2 |
3 | let scripts = document.body.getElementsByTagName("script");
4 | scripts = Array.prototype.slice.call(scripts);
5 | scripts.forEach(function(script) {
6 | if (!script.type || !script.type.match(/math\/tex/i)) {
7 | return -1;
8 | }
9 | const display =
10 | (script.type.match(/mode\s*=\s*display(;|\s|\n|$)/) != null);
11 |
12 | const katexElement = document.createElement(display ? "div" : "span");
13 | katexElement.setAttribute("class",
14 | display ? "equation" : "inline-equation");
15 | try {
16 | katex.render(script.text, katexElement, {displayMode: display});
17 | } catch (err) {
18 | //console.error(err); linter doesn't like this
19 | katexElement.textContent = script.text;
20 | }
21 | script.parentNode.replaceChild(katexElement, script);
22 | });
23 |
--------------------------------------------------------------------------------
/test/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | entry: './index.js',
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'dist'),
9 | },
10 | devServer: {
11 | port: 8080,
12 | },
13 | plugins: [
14 | new HtmlWebpackPlugin({
15 | template: 'index.html',
16 | }),
17 | ],
18 | experiments: {
19 | asyncWebAssembly: true,
20 | },
21 | module: {
22 | rules: [
23 | {
24 | test: /\.js$/,
25 | use: {
26 | loader: 'babel-loader',
27 | options: {
28 | presets: ['@babel/preset-flow'],
29 | },
30 | },
31 | },
32 | ]
33 | },
34 | devtool: 'source-map',
35 | mode: 'development',
36 | };
37 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/htmlmathml.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction, {ordargument} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 |
5 | import * as html from "../buildHTML";
6 | import * as mml from "../buildMathML";
7 |
8 | defineFunction({
9 | type: "htmlmathml",
10 | names: ["\\html@mathml"],
11 | props: {
12 | numArgs: 2,
13 | allowedInText: true,
14 | },
15 | handler: ({parser}, args) => {
16 | return {
17 | type: "htmlmathml",
18 | mode: parser.mode,
19 | html: ordargument(args[0]),
20 | mathml: ordargument(args[1]),
21 | };
22 | },
23 | htmlBuilder: (group, options) => {
24 | const elements = html.buildExpression(
25 | group.html,
26 | options,
27 | false
28 | );
29 | return buildCommon.makeFragment(elements);
30 | },
31 | mathmlBuilder: (group, options) => {
32 | return mml.buildExpressionRow(group.mathml, options);
33 | },
34 | });
35 |
--------------------------------------------------------------------------------
/esbuild.config.mjs:
--------------------------------------------------------------------------------
1 | import esbuild from 'esbuild';
2 | import fs from 'fs';
3 |
4 | const entryPoint = './wypst.js';
5 | const isDevelopment = process.env.NODE_ENV === 'development';
6 |
7 | // Inline wasm build
8 | esbuild.build({
9 | entryPoints: [entryPoint],
10 | bundle: true,
11 | minify: !isDevelopment,
12 | sourcemap: isDevelopment,
13 | target: ['es6'],
14 | outfile: './dist/wypst.min.js',
15 | format: 'iife',
16 | globalName: 'wypst',
17 | loader: {
18 | '.wasm': 'binary'
19 | },
20 | });
21 |
22 | // Main build
23 | esbuild.build({
24 | entryPoints: [entryPoint],
25 | bundle: true,
26 | minify: false,
27 | sourcemap: isDevelopment,
28 | target: ['es6'],
29 | outfile: './dist/wypst.js',
30 | format: 'esm',
31 | globalName: 'wypst',
32 | loader: {
33 | '.wasm': 'file'
34 | },
35 | metafile: true,
36 | assetNames: 'wypst',
37 | });
38 |
39 | // Copy CSS files
40 | fs.copyFileSync('./node_modules/katex/dist/katex.css', './dist/wypst.css');
41 | fs.copyFileSync('./node_modules/katex/dist/katex.min.css', './dist/wypst.min.css');
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 0xpapercut
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/math.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import ParseError from "../ParseError";
4 |
5 | // Switching from text mode back to math mode
6 | defineFunction({
7 | type: "styling",
8 | names: ["\\(", "$"],
9 | props: {
10 | numArgs: 0,
11 | allowedInText: true,
12 | allowedInMath: false,
13 | },
14 | handler({funcName, parser}, args) {
15 | const outerMode = parser.mode;
16 | parser.switchMode("math");
17 | const close = (funcName === "\\(" ? "\\)" : "$");
18 | const body = parser.parseExpression(false, close);
19 | parser.expect(close);
20 | parser.switchMode(outerMode);
21 | return {
22 | type: "styling",
23 | mode: parser.mode,
24 | style: "text",
25 | body,
26 | };
27 | },
28 | });
29 |
30 | // Check for extra closing math delimiters
31 | defineFunction({
32 | type: "text", // Doesn't matter what this is.
33 | names: ["\\)", "\\]"],
34 | props: {
35 | numArgs: 0,
36 | allowedInText: true,
37 | allowedInMath: false,
38 | },
39 | handler(context, args) {
40 | throw new ParseError(`Mismatched ${context.funcName}`);
41 | },
42 | });
43 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Debug wypst
7 |
8 |
27 |
28 |
29 |
30 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/core/src/lib.rs:
--------------------------------------------------------------------------------
1 | use wasm_bindgen::prelude::*;
2 | use serde_wasm_bindgen::to_value;
3 | use serde_json;
4 | use typst;
5 |
6 | mod converter;
7 | mod katex;
8 | mod utils;
9 | mod node;
10 | mod ext;
11 | mod content;
12 | mod symbol;
13 |
14 | fn content_tree(expression: &str) -> Result {
15 | let world = utils::FakeWorld::new();
16 | utils::eval(&world, expression)
17 | }
18 |
19 | pub fn convert(content: &typst::foundations::Content) -> serde_json::Value {
20 | let katex_tree = converter::convert(content);
21 | serde_json::to_value(&katex_tree).unwrap()
22 | }
23 |
24 | #[wasm_bindgen(js_name = "parseTree")]
25 | pub fn parse_tree(expression: &str) -> Result {
26 | #[cfg(debug_assertions)]
27 | console_error_panic_hook::set_once();
28 | let content = content_tree(expression);
29 | content.map(|c| {
30 | let katex_tree = converter::convert(&c);
31 | to_value(&katex_tree).unwrap()
32 | })
33 | }
34 |
35 | #[wasm_bindgen(js_name = "typstContentTree")]
36 | pub fn typst_content_tree(expression: &str) -> Result {
37 | let content = content_tree(expression);
38 | match content {
39 | Ok(tree) => Ok(format!("{:#?}", tree).into()),
40 | Err(err) => Err(err),
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/hbox.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction, {ordargument} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 |
6 | import * as html from "../buildHTML";
7 | import * as mml from "../buildMathML";
8 |
9 | // \hbox is provided for compatibility with LaTeX \vcenter.
10 | // In LaTeX, \vcenter can act only on a box, as in
11 | // \vcenter{\hbox{$\frac{a+b}{\dfrac{c}{d}}$}}
12 | // This function by itself doesn't do anything but prevent a soft line break.
13 |
14 | defineFunction({
15 | type: "hbox",
16 | names: ["\\hbox"],
17 | props: {
18 | numArgs: 1,
19 | argTypes: ["text"],
20 | allowedInText: true,
21 | primitive: true,
22 | },
23 | handler({parser}, args) {
24 | return {
25 | type: "hbox",
26 | mode: parser.mode,
27 | body: ordargument(args[0]),
28 | };
29 | },
30 | htmlBuilder(group, options) {
31 | const elements = html.buildExpression(group.body, options, false);
32 | return buildCommon.makeFragment(elements);
33 | },
34 | mathmlBuilder(group, options) {
35 | return new mathMLTree.MathNode(
36 | "mrow", mml.buildExpression(group.body, options)
37 | );
38 | },
39 | });
40 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/symbolsOp.js:
--------------------------------------------------------------------------------
1 | //
2 | import {defineFunctionBuilders} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 |
6 | import * as mml from "../buildMathML";
7 |
8 | // Operator ParseNodes created in Parser.js from symbol Groups in src/symbols.js.
9 |
10 | defineFunctionBuilders({
11 | type: "atom",
12 | htmlBuilder(group, options) {
13 | return buildCommon.mathsym(
14 | group.text, group.mode, options, ["m" + group.family]);
15 | },
16 | mathmlBuilder(group, options) {
17 | const node = new mathMLTree.MathNode(
18 | "mo", [mml.makeText(group.text, group.mode)]);
19 | if (group.family === "bin") {
20 | const variant = mml.getVariant(group, options);
21 | if (variant === "bold-italic") {
22 | node.setAttribute("mathvariant", variant);
23 | }
24 | } else if (group.family === "punct") {
25 | node.setAttribute("separator", "true");
26 | } else if (group.family === "open" || group.family === "close") {
27 | // Delims built here should not stretch vertically.
28 | // See delimsizing.js for stretchy delims.
29 | node.setAttribute("stretchy", "false");
30 | }
31 | return node;
32 | },
33 | });
34 |
35 |
--------------------------------------------------------------------------------
/lib/katex/src/unicodeSymbols.js:
--------------------------------------------------------------------------------
1 | //
2 | // This is an internal module, not part of the KaTeX distribution,
3 | // whose purpose is to generate `unicodeSymbols` in Parser.js
4 | // In this way, only this module, and not the distribution/browser,
5 | // needs String's normalize function. As this file is not transpiled,
6 | // Flow comment types syntax is used.
7 | const accents = require('./unicodeAccents');
8 |
9 | const result /*: {[string]: string}*/ = {};
10 | const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +
11 | "αβγδεϵζηθϑικλμνξοπϖρϱςστυφϕχψωΓΔΘΛΞΠΣΥΦΨΩ";
12 | for (const letter of letters) {
13 | for (const accent of Object.getOwnPropertyNames(accents)) {
14 | const combined = letter + accent;
15 | const normalized = combined.normalize('NFC');
16 | if (normalized.length === 1) {
17 | result[normalized] = combined;
18 | }
19 | for (const accent2 of Object.getOwnPropertyNames(accents)) {
20 | if (accent === accent2) {
21 | continue;
22 | }
23 | const combined2 = combined + accent2;
24 | const normalized2 = combined2.normalize('NFC');
25 | if (normalized2.length === 1) {
26 | result[normalized2] = combined2;
27 | }
28 | }
29 | }
30 | }
31 |
32 | module.exports = result;
33 |
--------------------------------------------------------------------------------
/src/core/src/symbol.rs:
--------------------------------------------------------------------------------
1 | use crate::node::Node;
2 | use crate::katex::{self, LapBuilder, MClassBuilder};
3 |
4 | pub fn not() -> Node {
5 | Node::Node(katex::AtomBuilder::default()
6 | .family(katex::AtomGroup::Rel)
7 | .text("\\@not".to_string())
8 | .build().unwrap().into_node())
9 | }
10 |
11 | pub fn equals() -> Node {
12 | Node::Node(katex::AtomBuilder::default()
13 | .family(katex::AtomGroup::Rel)
14 | .text("=".to_string())
15 | .build().unwrap().into_node())
16 | }
17 |
18 | pub fn neq() -> Node {
19 | let not = MClassBuilder::default()
20 | .mclass("rel".to_string())
21 | .body([
22 | LapBuilder::default()
23 | .alignment("rlap".to_string())
24 | .body(Box::new(not().into_ordgroup(katex::Mode::Math).into_node()))
25 | .build().unwrap().into_node()
26 | ].to_vec())
27 | .is_character_box(false)
28 | .build().unwrap().into_node();
29 | Node::Node(MClassBuilder::default()
30 | .mclass("mrel".to_string())
31 | .is_character_box(false)
32 | .body([not, equals().into_node().unwrap()].to_vec())
33 | .build().unwrap().into_node())
34 | }
35 |
36 | pub fn define() -> Node {
37 | Node::Array([
38 | katex::Symbol::get(katex::Mode::Math, ':').create_node(),
39 | katex::Symbol::get(katex::Mode::Math, '=').create_node(),
40 | ].to_vec())
41 | }
42 |
--------------------------------------------------------------------------------
/.github/workflows/static.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["main"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Single deploy job since we're just deploying
26 | deploy:
27 | environment:
28 | name: github-pages
29 | url: ${{ steps.deployment.outputs.page_url }}
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 | - name: Setup Pages
35 | uses: actions/configure-pages@v4
36 | - name: Upload artifact
37 | uses: actions/upload-pages-artifact@v3
38 | with:
39 | # Upload entire repository
40 | path: './test/dist'
41 | - name: Deploy to GitHub Pages
42 | id: deployment
43 | uses: actions/deploy-pages@v4
44 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/tag.js:
--------------------------------------------------------------------------------
1 | //
2 | import {defineFunctionBuilders} from "../defineFunction";
3 | import mathMLTree from "../mathMLTree";
4 |
5 | import * as mml from "../buildMathML";
6 |
7 | const pad = () => {
8 | const padNode = new mathMLTree.MathNode("mtd", []);
9 | padNode.setAttribute("width", "50%");
10 | return padNode;
11 | };
12 |
13 | defineFunctionBuilders({
14 | type: "tag",
15 | mathmlBuilder(group, options) {
16 | const table = new mathMLTree.MathNode("mtable", [
17 | new mathMLTree.MathNode("mtr", [
18 | pad(),
19 | new mathMLTree.MathNode("mtd", [
20 | mml.buildExpressionRow(group.body, options),
21 | ]),
22 | pad(),
23 | new mathMLTree.MathNode("mtd", [
24 | mml.buildExpressionRow(group.tag, options),
25 | ]),
26 | ]),
27 | ]);
28 | table.setAttribute("width", "100%");
29 | return table;
30 |
31 | // TODO: Left-aligned tags.
32 | // Currently, the group and options passed here do not contain
33 | // enough info to set tag alignment. `leqno` is in Settings but it is
34 | // not passed to Options. On the HTML side, leqno is
35 | // set by a CSS class applied in buildTree.js. That would have worked
36 | // in MathML if browsers supported . Since they don't, we
37 | // need to rewrite the way this function is called.
38 | },
39 | });
40 |
41 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | export function deleteFields(obj, fields) {
2 | if (Array.isArray(obj)) {
3 | for (let i = 0; i < obj.length; i++) {
4 | deleteFields(obj[i], fields);
5 | }
6 | } else if (typeof obj === 'object' && obj !== null) {
7 | for (let field of fields) {
8 | delete obj[field];
9 | }
10 | for (let key in obj) {
11 | if (obj.hasOwnProperty(key)) {
12 | deleteFields(obj[key], fields);
13 | }
14 | }
15 | }
16 | }
17 |
18 | export function deleteIfFieldEquals(obj, value) {
19 | if (typeof obj === 'object' && obj !== null) {
20 | for (let key in obj) {
21 | if (!obj.hasOwnProperty(key))
22 | continue;
23 | if (obj[key] === value) {
24 | delete obj[key];
25 | } else {
26 | deleteIfFieldEquals(obj[key], value);
27 | }
28 | }
29 | }
30 | }
31 |
32 | export function diff(obj1, obj2) {
33 | const result = {};
34 | for (const key in obj1) {
35 | if (!obj2.hasOwnProperty(key)) {
36 | result[key] = obj1[key];
37 | } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {
38 | const value = diff(obj1[key], obj2[key]);
39 | if (Object.keys(value).length !== 0) {
40 | result[key] = value;
41 | }
42 | } else if (obj1[key] !== obj2[key]) {
43 | result[key] = obj1[key];
44 | }
45 | }
46 | return result;
47 | }
48 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/raisebox.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 | import {assertNodeType} from "../parseNode";
6 | import {calculateSize} from "../units";
7 |
8 | import * as html from "../buildHTML";
9 | import * as mml from "../buildMathML";
10 |
11 | // Box manipulation
12 | defineFunction({
13 | type: "raisebox",
14 | names: ["\\raisebox"],
15 | props: {
16 | numArgs: 2,
17 | argTypes: ["size", "hbox"],
18 | allowedInText: true,
19 | },
20 | handler({parser}, args) {
21 | const amount = assertNodeType(args[0], "size").value;
22 | const body = args[1];
23 | return {
24 | type: "raisebox",
25 | mode: parser.mode,
26 | dy: amount,
27 | body,
28 | };
29 | },
30 | htmlBuilder(group, options) {
31 | const body = html.buildGroup(group.body, options);
32 | const dy = calculateSize(group.dy, options);
33 | return buildCommon.makeVList({
34 | positionType: "shift",
35 | positionData: -dy,
36 | children: [{type: "elem", elem: body}],
37 | }, options);
38 | },
39 | mathmlBuilder(group, options) {
40 | const node = new mathMLTree.MathNode(
41 | "mpadded", [mml.buildGroup(group.body, options)]);
42 | const dy = group.dy.number + group.dy.unit;
43 | node.setAttribute("voffset", dy);
44 | return node;
45 | },
46 | });
47 |
--------------------------------------------------------------------------------
/scripts.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const ShellPlugin = require('webpack-shell-plugin-next');
3 |
4 | const scriptsDir = path.resolve(__dirname, 'scripts');
5 | const symbolGenFilename = 'symbol_gen.js';
6 |
7 | const symbolSourceConfig = {
8 | mode: 'development',
9 | target: 'node',
10 | entry: path.resolve(__dirname, 'node_modules/katex/src/symbols.js'),
11 | output: {
12 | path: path.resolve(scriptsDir, 'dist'),
13 | filename: 'symbols.js'
14 | },
15 | devtool: 'inline-source-map',
16 | module: {
17 | rules: [
18 | {
19 | test: /\.js$/,
20 | exclude: /node_modules\/(?!katex)/,
21 | use: {
22 | loader: 'babel-loader',
23 | options: {
24 | presets: ['@babel/preset-flow'],
25 | },
26 | },
27 | }
28 | ],
29 | },
30 | }
31 |
32 | const symbolGenConfig = {
33 | mode: 'development',
34 | target: 'node',
35 | entry: path.resolve(scriptsDir, symbolGenFilename),
36 | output: {
37 | path: path.resolve(scriptsDir, 'dist'),
38 | filename: symbolGenFilename
39 | },
40 | plugins: [
41 | new ShellPlugin({
42 | onBuildEnd: {
43 | scripts: [`node ${path.resolve(scriptsDir, 'dist', symbolGenFilename)}`],
44 | blocking: true,
45 | parallel: false
46 | }
47 | })
48 | ]
49 | }
50 |
51 | module.exports = [symbolSourceConfig, symbolGenConfig]
52 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/vcenter.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 |
6 | import * as html from "../buildHTML";
7 | import * as mml from "../buildMathML";
8 |
9 | // \vcenter: Vertically center the argument group on the math axis.
10 |
11 | defineFunction({
12 | type: "vcenter",
13 | names: ["\\vcenter"],
14 | props: {
15 | numArgs: 1,
16 | argTypes: ["original"], // In LaTeX, \vcenter can act only on a box.
17 | allowedInText: false,
18 | },
19 | handler({parser}, args) {
20 | return {
21 | type: "vcenter",
22 | mode: parser.mode,
23 | body: args[0],
24 | };
25 | },
26 | htmlBuilder(group, options) {
27 | const body = html.buildGroup(group.body, options);
28 | const axisHeight = options.fontMetrics().axisHeight;
29 | const dy = 0.5 * ((body.height - axisHeight) - (body.depth + axisHeight));
30 | return buildCommon.makeVList({
31 | positionType: "shift",
32 | positionData: dy,
33 | children: [{type: "elem", elem: body}],
34 | }, options);
35 | },
36 | mathmlBuilder(group, options) {
37 | // There is no way to do this in MathML.
38 | // Write a class as a breadcrumb in case some post-processor wants
39 | // to perform a vcenter adjustment.
40 | return new mathMLTree.MathNode(
41 | "mpadded", [mml.buildGroup(group.body, options)], ["vcenter"]);
42 | },
43 | });
44 |
45 |
--------------------------------------------------------------------------------
/lib/katex/src/types.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 | /**
4 | * This file consists only of basic flow types used in multiple places.
5 | * For types with javascript, create separate files by themselves.
6 | */
7 |
8 |
9 |
10 | // LaTeX argument type.
11 | // - "size": A size-like thing, such as "1em" or "5ex"
12 | // - "color": An html color, like "#abc" or "blue"
13 | // - "url": An url string, in which "\" will be ignored
14 | // - if it precedes [#$%&~_^\{}]
15 | // - "raw": A string, allowing single character, percent sign,
16 | // and nested braces
17 | // - "original": The same type as the environment that the
18 | // function being parsed is in (e.g. used for the
19 | // bodies of functions like \textcolor where the
20 | // first argument is special and the second
21 | // argument is parsed normally)
22 | // - Mode: Node group parsed in given mode.
23 |
24 |
25 |
26 | // LaTeX display style.
27 |
28 |
29 | // Allowable token text for "break" arguments in parser.
30 |
31 |
32 |
33 | // Math font variants.
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/lib/katex/src/SourceLocation.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 |
4 | /**
5 | * Lexing or parsing positional information for error reporting.
6 | * This object is immutable.
7 | */
8 | export default class SourceLocation {
9 | // The + prefix indicates that these fields aren't writeable
10 | lexer ; // Lexer holding the input string.
11 | start ; // Start offset, zero-based inclusive.
12 | end ; // End offset, zero-based exclusive.
13 |
14 | constructor(lexer , start , end ) {
15 | this.lexer = lexer;
16 | this.start = start;
17 | this.end = end;
18 | }
19 |
20 | /**
21 | * Merges two `SourceLocation`s from location providers, given they are
22 | * provided in order of appearance.
23 | * - Returns the first one's location if only the first is provided.
24 | * - Returns a merged range of the first and the last if both are provided
25 | * and their lexers match.
26 | * - Otherwise, returns null.
27 | */
28 | static range(
29 | first ,
30 | second ,
31 | ) {
32 | if (!second) {
33 | return first && first.loc;
34 | } else if (!first || !first.loc || !second.loc ||
35 | first.loc.lexer !== second.loc.lexer) {
36 | return null;
37 | } else {
38 | return new SourceLocation(
39 | first.loc.lexer, first.loc.start, second.loc.end);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/pmb.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction, {ordargument} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 | import * as html from "../buildHTML";
6 | import * as mml from "../buildMathML";
7 | import {binrelClass} from "./mclass";
8 |
9 |
10 |
11 | // \pmb is a simulation of bold font.
12 | // The version of \pmb in ambsy.sty works by typesetting three copies
13 | // with small offsets. We use CSS text-shadow.
14 | // It's a hack. Not as good as a real bold font. Better than nothing.
15 |
16 | defineFunction({
17 | type: "pmb",
18 | names: ["\\pmb"],
19 | props: {
20 | numArgs: 1,
21 | allowedInText: true,
22 | },
23 | handler({parser}, args) {
24 | return {
25 | type: "pmb",
26 | mode: parser.mode,
27 | mclass: binrelClass(args[0]),
28 | body: ordargument(args[0]),
29 | };
30 | },
31 | htmlBuilder(group , options) {
32 | const elements = html.buildExpression(group.body, options, true);
33 | const node = buildCommon.makeSpan([group.mclass], elements, options);
34 | node.style.textShadow = "0.02em 0.01em 0.04px";
35 | return node;
36 | },
37 | mathmlBuilder(group , style) {
38 | const inner = mml.buildExpression(group.body, style);
39 | // Wrap with an element.
40 | const node = new mathMLTree.MathNode("mstyle", inner);
41 | node.setAttribute("style", "text-shadow: 0.02em 0.01em 0.04px");
42 | return node;
43 | },
44 | });
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wypst",
3 | "version": "0.0.8",
4 | "description": "Typst math typesetting for the web.",
5 | "scripts": {
6 | "scripts": "webpack --config=scripts.config.js"
7 | },
8 | "main": "dist/wypst.js",
9 | "files": [
10 | "wypst.js",
11 | "src/utils.js",
12 | "src/core/pkg/",
13 | "dist/"
14 | ],
15 | "keywords": [
16 | "typst",
17 | "katex",
18 | "transpiler",
19 | "math"
20 | ],
21 | "type": "module",
22 | "author": "0xpapercut",
23 | "license": "MIT",
24 | "devDependencies": {
25 | "@babel/generator": "^7.23.4",
26 | "@babel/parser": "^7.23.4",
27 | "@babel/preset-flow": "^7.23.3",
28 | "@babel/preset-typescript": "^7.23.3",
29 | "@babel/traverse": "^7.23.4",
30 | "@rollup/plugin-babel": "^6.0.4",
31 | "@rollup/plugin-commonjs": "^25.0.7",
32 | "@rollup/plugin-node-resolve": "^15.2.3",
33 | "@rollup/plugin-wasm": "^6.2.2",
34 | "@wasm-tool/wasm-pack-plugin": "^1.7.0",
35 | "babel-loader": "^9.1.3",
36 | "chai": "^4.3.10",
37 | "css-loader": "^6.9.0",
38 | "esbuild": "^0.20.2",
39 | "esprima": "^4.0.1",
40 | "estraverse": "^5.3.0",
41 | "express": "^4.18.2",
42 | "flow-remove-types": "^2.234.0",
43 | "fs": "^0.0.1-security",
44 | "html-webpack-plugin": "^5.5.3",
45 | "isolated-vm": "^4.6.0",
46 | "json-formatter-js": "^2.3.4",
47 | "katex": "^0.16.10",
48 | "puppeteer": "^21.5.1",
49 | "rollup": "^4.14.3",
50 | "style-loader": "^3.3.4",
51 | "webpack": "^5.89.0",
52 | "webpack-cli": "^5.1.4",
53 | "webpack-dev-server": "^4.15.1",
54 | "webpack-shell-plugin-next": "^2.3.1",
55 | "esbuild-plugin-copy": "^2.1.1"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/katex/src/Token.js:
--------------------------------------------------------------------------------
1 | //
2 | import SourceLocation from "./SourceLocation";
3 |
4 | /**
5 | * Interface required to break circular dependency between Token, Lexer, and
6 | * ParseError.
7 | */
8 |
9 |
10 | /**
11 | * The resulting token returned from `lex`.
12 | *
13 | * It consists of the token text plus some position information.
14 | * The position information is essentially a range in an input string,
15 | * but instead of referencing the bare input string, we refer to the lexer.
16 | * That way it is possible to attach extra metadata to the input string,
17 | * like for example a file name or similar.
18 | *
19 | * The position information is optional, so it is OK to construct synthetic
20 | * tokens if appropriate. Not providing available position information may
21 | * lead to degraded error reporting, though.
22 | */
23 | export class Token {
24 | text ;
25 | loc ;
26 | noexpand ; // don't expand the token
27 | treatAsRelax ; // used in \noexpand
28 |
29 | constructor(
30 | text , // the text of this token
31 | loc ,
32 | ) {
33 | this.text = text;
34 | this.loc = loc;
35 | }
36 |
37 | /**
38 | * Given a pair of tokens (this and endToken), compute a `Token` encompassing
39 | * the whole input range enclosed by these two.
40 | */
41 | range(
42 | endToken , // last token of the range, inclusive
43 | text , // the text of the newly constructed token
44 | ) {
45 | return new Token(text, SourceLocation.range(this, endToken));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/mathchoice.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction, {ordargument} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import Style from "../Style";
5 |
6 | import * as html from "../buildHTML";
7 | import * as mml from "../buildMathML";
8 |
9 |
10 |
11 | const chooseMathStyle = (group , options) => {
12 | switch (options.style.size) {
13 | case Style.DISPLAY.size: return group.display;
14 | case Style.TEXT.size: return group.text;
15 | case Style.SCRIPT.size: return group.script;
16 | case Style.SCRIPTSCRIPT.size: return group.scriptscript;
17 | default: return group.text;
18 | }
19 | };
20 |
21 | defineFunction({
22 | type: "mathchoice",
23 | names: ["\\mathchoice"],
24 | props: {
25 | numArgs: 4,
26 | primitive: true,
27 | },
28 | handler: ({parser}, args) => {
29 | return {
30 | type: "mathchoice",
31 | mode: parser.mode,
32 | display: ordargument(args[0]),
33 | text: ordargument(args[1]),
34 | script: ordargument(args[2]),
35 | scriptscript: ordargument(args[3]),
36 | };
37 | },
38 | htmlBuilder: (group, options) => {
39 | const body = chooseMathStyle(group, options);
40 | const elements = html.buildExpression(
41 | body,
42 | options,
43 | false
44 | );
45 | return buildCommon.makeFragment(elements);
46 | },
47 | mathmlBuilder: (group, options) => {
48 | const body = chooseMathStyle(group, options);
49 | return mml.buildExpressionRow(body, options);
50 | },
51 | });
52 |
--------------------------------------------------------------------------------
/lib/katex/src/parseTree.js:
--------------------------------------------------------------------------------
1 | //
2 | /**
3 | * Provides a single function for parsing an expression using a Parser
4 | * TODO(emily): Remove this
5 | */
6 |
7 | import Parser from "./Parser";
8 | import ParseError from "./ParseError";
9 | import {Token} from "./Token";
10 |
11 |
12 |
13 |
14 | /**
15 | * Parses an expression using a Parser, then returns the parsed result.
16 | */
17 | const parseTree = function(toParse , settings ) {
18 | if (!(typeof toParse === 'string' || toParse instanceof String)) {
19 | throw new TypeError('KaTeX can only parse string typed expression');
20 | }
21 | const parser = new Parser(toParse, settings);
22 |
23 | // Blank out any \df@tag to avoid spurious "Duplicate \tag" errors
24 | delete parser.gullet.macros.current["\\df@tag"];
25 |
26 | let tree = parser.parse();
27 |
28 | // Prevent a color definition from persisting between calls to katex.render().
29 | delete parser.gullet.macros.current["\\current@color"];
30 | delete parser.gullet.macros.current["\\color"];
31 |
32 | // If the input used \tag, it will set the \df@tag macro to the tag.
33 | // In this case, we separately parse the tag and wrap the tree.
34 | if (parser.gullet.macros.get("\\df@tag")) {
35 | if (!settings.displayMode) {
36 | throw new ParseError("\\tag works only in display equations");
37 | }
38 | tree = [{
39 | type: "tag",
40 | mode: "text",
41 | body: tree,
42 | tag: parser.subparse([new Token("\\df@tag")]),
43 | }];
44 | }
45 |
46 | return tree;
47 | };
48 |
49 | export default parseTree;
50 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/char.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import ParseError from "../ParseError";
4 | import {assertNodeType} from "../parseNode";
5 |
6 | // \@char is an internal function that takes a grouped decimal argument like
7 | // {123} and converts into symbol with code 123. It is used by the *macro*
8 | // \char defined in macros.js.
9 | defineFunction({
10 | type: "textord",
11 | names: ["\\@char"],
12 | props: {
13 | numArgs: 1,
14 | allowedInText: true,
15 | },
16 | handler({parser}, args) {
17 | const arg = assertNodeType(args[0], "ordgroup");
18 | const group = arg.body;
19 | let number = "";
20 | for (let i = 0; i < group.length; i++) {
21 | const node = assertNodeType(group[i], "textord");
22 | number += node.text;
23 | }
24 | let code = parseInt(number);
25 | let text;
26 | if (isNaN(code)) {
27 | throw new ParseError(`\\@char has non-numeric argument ${number}`);
28 | // If we drop IE support, the following code could be replaced with
29 | // text = String.fromCodePoint(code)
30 | } else if (code < 0 || code >= 0x10ffff) {
31 | throw new ParseError(`\\@char with invalid code point ${number}`);
32 | } else if (code <= 0xffff) {
33 | text = String.fromCharCode(code);
34 | } else { // Astral code point; split into surrogate halves
35 | code -= 0x10000;
36 | text = String.fromCharCode((code >> 10) + 0xd800,
37 | (code & 0x3ff) + 0xdc00);
38 | }
39 | return {
40 | type: "textord",
41 | mode: parser.mode,
42 | text: text,
43 | };
44 | },
45 | });
46 |
--------------------------------------------------------------------------------
/lib/katex/src/functions.js:
--------------------------------------------------------------------------------
1 | //
2 | /** Include this to ensure that all functions are defined. */
3 | import {_functions} from "./defineFunction";
4 |
5 | const functions = _functions;
6 | export default functions;
7 |
8 | // TODO(kevinb): have functions return an object and call defineFunction with
9 | // that object in this file instead of relying on side-effects.
10 | import "./functions/accent";
11 | import "./functions/accentunder";
12 | import "./functions/arrow";
13 | import "./functions/pmb";
14 | import "./environments/cd";
15 | import "./functions/char";
16 | import "./functions/color";
17 | import "./functions/cr";
18 | import "./functions/def";
19 | import "./functions/delimsizing";
20 | import "./functions/enclose";
21 | import "./functions/environment";
22 | import "./functions/font";
23 | import "./functions/genfrac";
24 | import "./functions/horizBrace";
25 | import "./functions/href";
26 | import "./functions/hbox";
27 | import "./functions/html";
28 | import "./functions/htmlmathml";
29 | import "./functions/includegraphics";
30 | import "./functions/kern";
31 | import "./functions/lap";
32 | import "./functions/math";
33 | import "./functions/mathchoice";
34 | import "./functions/mclass";
35 | import "./functions/op";
36 | import "./functions/operatorname";
37 | import "./functions/ordgroup";
38 | import "./functions/overline";
39 | import "./functions/phantom";
40 | import "./functions/raisebox";
41 | import "./functions/relax";
42 | import "./functions/rule";
43 | import "./functions/sizing";
44 | import "./functions/smash";
45 | import "./functions/sqrt";
46 | import "./functions/styling";
47 | import "./functions/supsub";
48 | import "./functions/symbolsOp";
49 | import "./functions/symbolsOrd";
50 | import "./functions/symbolsSpacing";
51 | import "./functions/tag";
52 | import "./functions/text";
53 | import "./functions/underline";
54 | import "./functions/vcenter";
55 | import "./functions/verb";
56 |
--------------------------------------------------------------------------------
/wypst.js:
--------------------------------------------------------------------------------
1 | import init, { parseTree as _parseTree, typstContentTree } from './src/core/pkg';
2 | import utils from './src/utils';
3 |
4 | import wasm from './src/core/pkg/core_bg.wasm';
5 |
6 | function parseTree(expression, settings) {
7 | expression = expression.trim().replace(/\n/g, ' ');
8 | return _parseTree(expression, settings);
9 | }
10 |
11 | function renderToDomTree(expression, options) {
12 | let settings = new utils.Settings(options);
13 | try {
14 | const tree = parseTree(expression, settings);
15 | return utils.buildTree(tree, expression, settings);
16 | } catch (error) {
17 | // Temporary fix so that we actually see errors like "unknown variable: ..."
18 | return utils.renderError(error, error, settings);
19 | }
20 | }
21 |
22 | /**
23 | * Renders a Typst expression into the specified DOM element
24 | * @param expression A Typst expression
25 | * @param element The DOM element to render into
26 | * @param options Render options
27 | */
28 | function render(expression, baseNode, options) {
29 | baseNode.textContent = "";
30 | const node = renderToDomTree(expression, options).toNode();
31 | baseNode.appendChild(node);
32 | };
33 |
34 | /**
35 | * Renders a Typst expression into an HTML string
36 | * @param expression A Typst expression
37 | * @param options Render options
38 | */
39 | function renderToString(expression, options) {
40 | const markup = renderToDomTree(expression, options).toMarkup();
41 | return markup;
42 | }
43 |
44 | async function initialize(path) {
45 | if (path) {
46 | await init(path);
47 | } else {
48 | await init(wasm);
49 | }
50 | }
51 |
52 | export default {
53 | render,
54 | renderToString,
55 | parseTree,
56 | __typstContentTree: typstContentTree,
57 | initialize,
58 | };
59 |
60 | export {
61 | render,
62 | renderToString,
63 | parseTree,
64 | initialize,
65 | };
66 |
--------------------------------------------------------------------------------
/src/core/src/node.rs:
--------------------------------------------------------------------------------
1 | use serde::Serialize;
2 | use crate::katex;
3 |
4 | #[derive(Clone, Serialize)]
5 | #[serde(untagged)]
6 | pub enum Node {
7 | Node(katex::Node),
8 | Array(katex::NodeArray),
9 | }
10 |
11 | impl std::fmt::Debug for Node {
12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13 | match self {
14 | Node::Node(node) => write!(f, "{:?}", node),
15 | Node::Array(array) => write!(f, "{:?}", array),
16 | }
17 | }
18 | }
19 |
20 | impl Node {
21 | pub fn into_node(self) -> Result {
22 | match self {
23 | Node::Node(node) => Ok(node),
24 | Node::Array(array) => {
25 | if array.len() == 1 {
26 | // TODO: Must check if this code makes sense
27 | Ok(array.iter().next().cloned().unwrap())
28 | } else {
29 | Err("Cannot convert an array with more than one element to a single node")
30 | }
31 | },
32 | }
33 | }
34 |
35 | pub fn into_ordgroup(&self, mode: katex::Mode) -> katex::OrdGroup {
36 | katex::OrdGroupBuilder::default()
37 | .mode(mode)
38 | .body(self.clone().into_array())
39 | .build().unwrap()
40 | }
41 |
42 | pub fn into_node_fallback_ordgroup(&self, mode: katex::Mode) -> katex::Node {
43 | match self.clone().into_node() {
44 | Ok(node) => node,
45 | Err(_) => self.into_ordgroup(mode).into_node(),
46 | }
47 | }
48 |
49 | pub fn into_array(self) -> katex::NodeArray {
50 | match self {
51 | Node::Node(node) => vec![node.clone()],
52 | Node::Array(array) => array,
53 | }
54 | }
55 |
56 | // pub fn join(&mut self, node: Node) {
57 | // let mut arr = self.clone().as_array();
58 | // arr.append(&mut node.as_array());
59 | // *self = Node::Array(arr);
60 | // }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/katex/contrib/copy-tex/copy-tex.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 | import katexReplaceWithTex from './katex2tex';
4 |
5 | // Return element containing node, or null if not found.
6 | function closestKatex(node ) {
7 | // If node is a Text Node, for example, go up to containing Element,
8 | // where we can apply the `closest` method.
9 | const element =
10 | (node instanceof Element ? node : node.parentElement);
11 | return element && element.closest('.katex');
12 | }
13 |
14 | // Global copy handler to modify behavior on/within .katex elements.
15 | document.addEventListener('copy', function(event ) {
16 | const selection = window.getSelection();
17 | if (selection.isCollapsed || !event.clipboardData) {
18 | return; // default action OK if selection is empty or unchangeable
19 | }
20 | const clipboardData = event.clipboardData;
21 | const range = selection.getRangeAt(0);
22 |
23 | // When start point is within a formula, expand to entire formula.
24 | const startKatex = closestKatex(range.startContainer);
25 | if (startKatex) {
26 | range.setStartBefore(startKatex);
27 | }
28 |
29 | // Similarly, when end point is within a formula, expand to entire formula.
30 | const endKatex = closestKatex(range.endContainer);
31 | if (endKatex) {
32 | range.setEndAfter(endKatex);
33 | }
34 |
35 | const fragment = range.cloneContents();
36 | if (!fragment.querySelector('.katex-mathml')) {
37 | return; // default action OK if no .katex-mathml elements
38 | }
39 |
40 | const htmlContents = Array.prototype.map.call(fragment.childNodes,
41 | (el) => (el instanceof Text ? el.textContent : el.outerHTML)
42 | ).join('');
43 |
44 | // Preserve usual HTML copy/paste behavior.
45 | clipboardData.setData('text/html', htmlContents);
46 | // Rewrite plain-text version.
47 | clipboardData.setData('text/plain',
48 | katexReplaceWithTex(fragment).textContent);
49 | // Prevent normal copy handling.
50 | event.preventDefault();
51 | });
52 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/overline.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 |
6 | import * as html from "../buildHTML";
7 | import * as mml from "../buildMathML";
8 |
9 | defineFunction({
10 | type: "overline",
11 | names: ["\\overline"],
12 | props: {
13 | numArgs: 1,
14 | },
15 | handler({parser}, args) {
16 | const body = args[0];
17 | return {
18 | type: "overline",
19 | mode: parser.mode,
20 | body,
21 | };
22 | },
23 | htmlBuilder(group, options) {
24 | // Overlines are handled in the TeXbook pg 443, Rule 9.
25 |
26 | // Build the inner group in the cramped style.
27 | const innerGroup = html.buildGroup(group.body,
28 | options.havingCrampedStyle());
29 |
30 | // Create the line above the body
31 | const line = buildCommon.makeLineSpan("overline-line", options);
32 |
33 | // Generate the vlist, with the appropriate kerns
34 | const defaultRuleThickness = options.fontMetrics().defaultRuleThickness;
35 | const vlist = buildCommon.makeVList({
36 | positionType: "firstBaseline",
37 | children: [
38 | {type: "elem", elem: innerGroup},
39 | {type: "kern", size: 3 * defaultRuleThickness},
40 | {type: "elem", elem: line},
41 | {type: "kern", size: defaultRuleThickness},
42 | ],
43 | }, options);
44 |
45 | return buildCommon.makeSpan(["mord", "overline"], [vlist], options);
46 | },
47 | mathmlBuilder(group, options) {
48 | const operator = new mathMLTree.MathNode(
49 | "mo", [new mathMLTree.TextNode("\u203e")]);
50 | operator.setAttribute("stretchy", "true");
51 |
52 | const node = new mathMLTree.MathNode(
53 | "mover",
54 | [mml.buildGroup(group.body, options), operator]);
55 | node.setAttribute("accent", "true");
56 |
57 | return node;
58 | },
59 | });
60 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/underline.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 |
6 | import * as html from "../buildHTML";
7 | import * as mml from "../buildMathML";
8 |
9 | defineFunction({
10 | type: "underline",
11 | names: ["\\underline"],
12 | props: {
13 | numArgs: 1,
14 | allowedInText: true,
15 | },
16 | handler({parser}, args) {
17 | return {
18 | type: "underline",
19 | mode: parser.mode,
20 | body: args[0],
21 | };
22 | },
23 | htmlBuilder(group, options) {
24 | // Underlines are handled in the TeXbook pg 443, Rule 10.
25 | // Build the inner group.
26 | const innerGroup = html.buildGroup(group.body, options);
27 |
28 | // Create the line to go below the body
29 | const line = buildCommon.makeLineSpan("underline-line", options);
30 |
31 | // Generate the vlist, with the appropriate kerns
32 | const defaultRuleThickness = options.fontMetrics().defaultRuleThickness;
33 | const vlist = buildCommon.makeVList({
34 | positionType: "top",
35 | positionData: innerGroup.height,
36 | children: [
37 | {type: "kern", size: defaultRuleThickness},
38 | {type: "elem", elem: line},
39 | {type: "kern", size: 3 * defaultRuleThickness},
40 | {type: "elem", elem: innerGroup},
41 | ],
42 | }, options);
43 |
44 | return buildCommon.makeSpan(["mord", "underline"], [vlist], options);
45 | },
46 | mathmlBuilder(group, options) {
47 | const operator = new mathMLTree.MathNode(
48 | "mo", [new mathMLTree.TextNode("\u203e")]);
49 | operator.setAttribute("stretchy", "true");
50 |
51 | const node = new mathMLTree.MathNode(
52 | "munder",
53 | [mml.buildGroup(group.body, options), operator]);
54 | node.setAttribute("accentunder", "true");
55 |
56 | return node;
57 | },
58 | });
59 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/cr.js:
--------------------------------------------------------------------------------
1 | //
2 | // Row breaks within tabular environments, and line breaks at top level
3 |
4 | import defineFunction from "../defineFunction";
5 | import buildCommon from "../buildCommon";
6 | import mathMLTree from "../mathMLTree";
7 | import {calculateSize, makeEm} from "../units";
8 | import {assertNodeType} from "../parseNode";
9 |
10 | // \DeclareRobustCommand\\{...\@xnewline}
11 | defineFunction({
12 | type: "cr",
13 | names: ["\\\\"],
14 | props: {
15 | numArgs: 0,
16 | numOptionalArgs: 0,
17 | allowedInText: true,
18 | },
19 |
20 | handler({parser}, args, optArgs) {
21 | const size = parser.gullet.future().text === "[" ?
22 | parser.parseSizeGroup(true) : null;
23 | const newLine = !parser.settings.displayMode ||
24 | !parser.settings.useStrictBehavior(
25 | "newLineInDisplayMode", "In LaTeX, \\\\ or \\newline " +
26 | "does nothing in display mode");
27 | return {
28 | type: "cr",
29 | mode: parser.mode,
30 | newLine,
31 | size: size && assertNodeType(size, "size").value,
32 | };
33 | },
34 |
35 | // The following builders are called only at the top level,
36 | // not within tabular/array environments.
37 |
38 | htmlBuilder(group, options) {
39 | const span = buildCommon.makeSpan(["mspace"], [], options);
40 | if (group.newLine) {
41 | span.classes.push("newline");
42 | if (group.size) {
43 | span.style.marginTop =
44 | makeEm(calculateSize(group.size, options));
45 | }
46 | }
47 | return span;
48 | },
49 |
50 | mathmlBuilder(group, options) {
51 | const node = new mathMLTree.MathNode("mspace");
52 | if (group.newLine) {
53 | node.setAttribute("linebreak", "newline");
54 | if (group.size) {
55 | node.setAttribute("height",
56 | makeEm(calculateSize(group.size, options)));
57 | }
58 | }
59 | return node;
60 | },
61 | });
62 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/kern.js:
--------------------------------------------------------------------------------
1 | //
2 | // Horizontal spacing commands
3 |
4 | import defineFunction from "../defineFunction";
5 | import buildCommon from "../buildCommon";
6 | import mathMLTree from "../mathMLTree";
7 | import {calculateSize} from "../units";
8 | import {assertNodeType} from "../parseNode";
9 |
10 | // TODO: \hskip and \mskip should support plus and minus in lengths
11 |
12 | defineFunction({
13 | type: "kern",
14 | names: ["\\kern", "\\mkern", "\\hskip", "\\mskip"],
15 | props: {
16 | numArgs: 1,
17 | argTypes: ["size"],
18 | primitive: true,
19 | allowedInText: true,
20 | },
21 | handler({parser, funcName}, args) {
22 | const size = assertNodeType(args[0], "size");
23 | if (parser.settings.strict) {
24 | const mathFunction = (funcName[1] === 'm'); // \mkern, \mskip
25 | const muUnit = (size.value.unit === 'mu');
26 | if (mathFunction) {
27 | if (!muUnit) {
28 | parser.settings.reportNonstrict("mathVsTextUnits",
29 | `LaTeX's ${funcName} supports only mu units, ` +
30 | `not ${size.value.unit} units`);
31 | }
32 | if (parser.mode !== "math") {
33 | parser.settings.reportNonstrict("mathVsTextUnits",
34 | `LaTeX's ${funcName} works only in math mode`);
35 | }
36 | } else { // !mathFunction
37 | if (muUnit) {
38 | parser.settings.reportNonstrict("mathVsTextUnits",
39 | `LaTeX's ${funcName} doesn't support mu units`);
40 | }
41 | }
42 | }
43 | return {
44 | type: "kern",
45 | mode: parser.mode,
46 | dimension: size.value,
47 | };
48 | },
49 | htmlBuilder(group, options) {
50 | return buildCommon.makeGlue(group.dimension, options);
51 | },
52 | mathmlBuilder(group, options) {
53 | const dimension = calculateSize(group.dimension, options);
54 | return new mathMLTree.SpaceNode(dimension);
55 | },
56 | });
57 |
--------------------------------------------------------------------------------
/src/core/src/katex/types.rs:
--------------------------------------------------------------------------------
1 | use serde::Serialize;
2 | use crate::katex::NodeArray;
3 |
4 | use super::SourceLocation;
5 |
6 | #[derive(Clone, Serialize, PartialEq, Copy)]
7 | #[serde(rename_all = "lowercase")]
8 | pub enum Mode {
9 | Math,
10 | Text,
11 | }
12 |
13 | // Refernece: array.js
14 | #[derive(Clone, Serialize, PartialEq)]
15 | #[serde(rename_all = "lowercase")]
16 | pub enum ColSeparationType { // TODO
17 | Align,
18 | AlignAt,
19 | Gather,
20 | Small,
21 | #[serde(rename = "CD")]
22 | CD,
23 | }
24 |
25 | // Reference: array.js
26 | #[derive(Clone, Serialize)]
27 | #[serde(tag = "type", rename_all = "lowercase")]
28 | pub enum AlignSpec {
29 | Separator(Separator),
30 | Align(Align),
31 | }
32 |
33 | #[derive(Clone, Serialize)]
34 | pub struct Separator {
35 | pub separator: String,
36 | }
37 |
38 | #[derive(Clone, Serialize)]
39 | pub struct Align {
40 | pub align: String,
41 | pub pregap: Option
,
42 | pub postgap: Option,
43 | }
44 |
45 | // Reference: units.js
46 | #[derive(Clone, Serialize)]
47 | pub struct Measurement {
48 | pub number: f32,
49 | pub unit: String,
50 | }
51 |
52 | #[derive(Clone, Serialize)]
53 | pub enum TagType {
54 | Bool(bool),
55 | NodeArray(NodeArray),
56 | }
57 |
58 | // Reference: types.js
59 | #[derive(Clone, Serialize)]
60 | #[serde(rename_all = "lowercase")]
61 | pub enum StyleStr {
62 | Text,
63 | Display,
64 | Script,
65 | ScriptScript,
66 | }
67 |
68 | #[derive(Clone, Serialize)]
69 | pub enum SizeType { // TODO: Check serialization
70 | One = 1,
71 | Two = 2,
72 | Three = 3,
73 | Four = 4,
74 | }
75 |
76 | #[derive(Clone, Serialize)]
77 | pub enum MClassType {
78 | MOpen,
79 | MClose,
80 | MRel,
81 | MOrd,
82 | }
83 |
84 | #[derive(Clone, Serialize)]
85 | #[serde(rename_all = "lowercase")]
86 | pub enum GenFracSizeType {
87 | StyleStr(StyleStr),
88 | Auto,
89 | }
90 |
91 | // Reference: Token.js
92 | #[derive(Clone, Serialize)]
93 | pub struct Token {
94 | pub text: String,
95 | pub loc: Option,
96 | pub noexpand: Option,
97 | pub treat_as_relax: Option,
98 | }
99 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/symbolsOrd.js:
--------------------------------------------------------------------------------
1 | //
2 | import {defineFunctionBuilders} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 |
6 | import * as mml from "../buildMathML";
7 |
8 |
9 |
10 | // "mathord" and "textord" ParseNodes created in Parser.js from symbol Groups in
11 | // src/symbols.js.
12 |
13 | const defaultVariant = {
14 | "mi": "italic",
15 | "mn": "normal",
16 | "mtext": "normal",
17 | };
18 |
19 | defineFunctionBuilders({
20 | type: "mathord",
21 | htmlBuilder(group, options) {
22 | return buildCommon.makeOrd(group, options, "mathord");
23 | },
24 | mathmlBuilder(group , options) {
25 | const node = new mathMLTree.MathNode(
26 | "mi",
27 | [mml.makeText(group.text, group.mode, options)]);
28 |
29 | const variant = mml.getVariant(group, options) || "italic";
30 | if (variant !== defaultVariant[node.type]) {
31 | node.setAttribute("mathvariant", variant);
32 | }
33 | return node;
34 | },
35 | });
36 |
37 | defineFunctionBuilders({
38 | type: "textord",
39 | htmlBuilder(group, options) {
40 | return buildCommon.makeOrd(group, options, "textord");
41 | },
42 | mathmlBuilder(group , options) {
43 | const text = mml.makeText(group.text, group.mode, options);
44 | const variant = mml.getVariant(group, options) || "normal";
45 |
46 | let node;
47 | if (group.mode === 'text') {
48 | node = new mathMLTree.MathNode("mtext", [text]);
49 | } else if (/[0-9]/.test(group.text)) {
50 | node = new mathMLTree.MathNode("mn", [text]);
51 | } else if (group.text === "\\prime") {
52 | node = new mathMLTree.MathNode("mo", [text]);
53 | } else {
54 | node = new mathMLTree.MathNode("mi", [text]);
55 | }
56 | if (variant !== defaultVariant[node.type]) {
57 | node.setAttribute("mathvariant", variant);
58 | }
59 |
60 | return node;
61 | },
62 | });
63 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/accentunder.js:
--------------------------------------------------------------------------------
1 | //
2 | // Horizontal overlap functions
3 | import defineFunction from "../defineFunction";
4 | import buildCommon from "../buildCommon";
5 | import mathMLTree from "../mathMLTree";
6 | import stretchy from "../stretchy";
7 |
8 | import * as html from "../buildHTML";
9 | import * as mml from "../buildMathML";
10 |
11 |
12 |
13 | defineFunction({
14 | type: "accentUnder",
15 | names: [
16 | "\\underleftarrow", "\\underrightarrow", "\\underleftrightarrow",
17 | "\\undergroup", "\\underlinesegment", "\\utilde",
18 | ],
19 | props: {
20 | numArgs: 1,
21 | },
22 | handler: ({parser, funcName}, args) => {
23 | const base = args[0];
24 | return {
25 | type: "accentUnder",
26 | mode: parser.mode,
27 | label: funcName,
28 | base: base,
29 | };
30 | },
31 | htmlBuilder: (group , options) => {
32 | // Treat under accents much like underlines.
33 | const innerGroup = html.buildGroup(group.base, options);
34 |
35 | const accentBody = stretchy.svgSpan(group, options);
36 | const kern = group.label === "\\utilde" ? 0.12 : 0;
37 |
38 | // Generate the vlist, with the appropriate kerns
39 | const vlist = buildCommon.makeVList({
40 | positionType: "top",
41 | positionData: innerGroup.height,
42 | children: [
43 | {type: "elem", elem: accentBody, wrapperClasses: ["svg-align"]},
44 | {type: "kern", size: kern},
45 | {type: "elem", elem: innerGroup},
46 | ],
47 | }, options);
48 |
49 | return buildCommon.makeSpan(["mord", "accentunder"], [vlist], options);
50 | },
51 | mathmlBuilder: (group, options) => {
52 | const accentNode = stretchy.mathMLnode(group.label);
53 | const node = new mathMLTree.MathNode(
54 | "munder",
55 | [mml.buildGroup(group.base, options), accentNode]
56 | );
57 | node.setAttribute("accentunder", "true");
58 | return node;
59 | },
60 | });
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wypst
2 | Typst math typesetting for the web.
3 |
4 | ## Current Project Status
5 | I feel like there's something like 20% of work left to get Wypst in a more stable state, but for some months now I haven't been able to allocate the necessary time to do it.
6 |
7 | There's still no conclusive roadmap for native HTML export in Typst, and because this project gained some traction, I'll make a commitment to complete all unimplemented functionality, fix issues, and add whatever is needed to achieve equivalency of `obsidian-wypst` to `obsidian-typst`.
8 |
9 | ## Usage
10 | You can load this library either by using a script tag, or installing it with npm.
11 |
12 | ### Script tag (simple usage)
13 | ```html
14 |
15 |
16 |
17 |
22 | ```
23 |
24 | Keep in mind that the javascript file is 17M, so if your internet is slow it might take some seconds to load.
25 |
26 | ### npm package (advanced usage)
27 | If having the wasm inlined directly is an incovenience, install the npm package
28 | ```bash
29 | npm install wypst
30 | ```
31 |
32 | You may then load the wasm binary
33 | ```javascript
34 | import wypst from 'wypst';
35 | import wasm from 'wypst/dist/wypst.wasm';
36 |
37 | await wypst.initialize(wasm);
38 | wypst.renderToString("x + y"); // Test it out!
39 | ```
40 |
41 | Keep in mind that you will probably need to tell your bundler how to load a `.wasm` file. If you have difficulties you can open an issue.
42 |
43 | ### Rendering Typst Math
44 | To render a Typst math expression, you can use either `render` or `renderToString`, as the example below shows:
45 | ```javascript
46 | wypst.render('sum_(n >= 1) 1/n^2 = pi^2/6', element); // Renders into the HTML element
47 | wypst.renderToString('sum_(n >= 1) 1/n^2 = pi^2/6'); // Renders into an HTML string
48 | ```
49 |
50 | ## Contributing
51 | All help is welcome. Please see [CONTRIBUTING](CONTRIBUTING.md).
52 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/verb.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 | import ParseError from "../ParseError";
6 |
7 |
8 |
9 | defineFunction({
10 | type: "verb",
11 | names: ["\\verb"],
12 | props: {
13 | numArgs: 0,
14 | allowedInText: true,
15 | },
16 | handler(context, args, optArgs) {
17 | // \verb and \verb* are dealt with directly in Parser.js.
18 | // If we end up here, it's because of a failure to match the two delimiters
19 | // in the regex in Lexer.js. LaTeX raises the following error when \verb is
20 | // terminated by end of line (or file).
21 | throw new ParseError(
22 | "\\verb ended by end of line instead of matching delimiter");
23 | },
24 | htmlBuilder(group, options) {
25 | const text = makeVerb(group);
26 | const body = [];
27 | // \verb enters text mode and therefore is sized like \textstyle
28 | const newOptions = options.havingStyle(options.style.text());
29 | for (let i = 0; i < text.length; i++) {
30 | let c = text[i];
31 | if (c === '~') {
32 | c = '\\textasciitilde';
33 | }
34 | body.push(buildCommon.makeSymbol(c, "Typewriter-Regular",
35 | group.mode, newOptions, ["mord", "texttt"]));
36 | }
37 | return buildCommon.makeSpan(
38 | ["mord", "text"].concat(newOptions.sizingClasses(options)),
39 | buildCommon.tryCombineChars(body),
40 | newOptions,
41 | );
42 | },
43 | mathmlBuilder(group, options) {
44 | const text = new mathMLTree.TextNode(makeVerb(group));
45 | const node = new mathMLTree.MathNode("mtext", [text]);
46 | node.setAttribute("mathvariant", "monospace");
47 | return node;
48 | },
49 | });
50 |
51 | /**
52 | * Converts verb group into body string.
53 | *
54 | * \verb* replaces each space with an open box \u2423
55 | * \verb replaces each space with a no-break space \xA0
56 | */
57 | const makeVerb = (group ) =>
58 | group.body.replace(/ /g, group.star ? '\u2423' : '\xA0');
59 |
--------------------------------------------------------------------------------
/lib/katex/src/buildTree.js:
--------------------------------------------------------------------------------
1 | //
2 | import buildHTML from "./buildHTML";
3 | import buildMathML from "./buildMathML";
4 | import buildCommon from "./buildCommon";
5 | import Options from "./Options";
6 | import Settings from "./Settings";
7 | import Style from "./Style";
8 |
9 |
10 |
11 |
12 | const optionsFromSettings = function(settings ) {
13 | return new Options({
14 | style: (settings.displayMode ? Style.DISPLAY : Style.TEXT),
15 | maxSize: settings.maxSize,
16 | minRuleThickness: settings.minRuleThickness,
17 | });
18 | };
19 |
20 | const displayWrap = function(node , settings ) {
21 | if (settings.displayMode) {
22 | const classes = ["katex-display"];
23 | if (settings.leqno) {
24 | classes.push("leqno");
25 | }
26 | if (settings.fleqn) {
27 | classes.push("fleqn");
28 | }
29 | node = buildCommon.makeSpan(classes, [node]);
30 | }
31 | return node;
32 | };
33 |
34 | export const buildTree = function(
35 | tree ,
36 | expression ,
37 | settings ,
38 | ) {
39 | const options = optionsFromSettings(settings);
40 | let katexNode;
41 | if (settings.output === "mathml") {
42 | return buildMathML(tree, expression, options, settings.displayMode, true);
43 | } else if (settings.output === "html") {
44 | const htmlNode = buildHTML(tree, options);
45 | katexNode = buildCommon.makeSpan(["katex"], [htmlNode]);
46 | } else {
47 | const mathMLNode = buildMathML(tree, expression, options,
48 | settings.displayMode, false);
49 | const htmlNode = buildHTML(tree, options);
50 | katexNode = buildCommon.makeSpan(["katex"], [mathMLNode, htmlNode]);
51 | }
52 |
53 | return displayWrap(katexNode, settings);
54 | };
55 |
56 | export const buildHTMLTree = function(
57 | tree ,
58 | expression ,
59 | settings ,
60 | ) {
61 | const options = optionsFromSettings(settings);
62 | const htmlNode = buildHTML(tree, options);
63 | const katexNode = buildCommon.makeSpan(["katex"], [htmlNode]);
64 | return displayWrap(katexNode, settings);
65 | };
66 |
67 | export default buildTree;
68 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/text.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction, {ordargument} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 |
5 | import * as html from "../buildHTML";
6 | import * as mml from "../buildMathML";
7 |
8 | // Non-mathy text, possibly in a font
9 | const textFontFamilies = {
10 | "\\text": undefined, "\\textrm": "textrm", "\\textsf": "textsf",
11 | "\\texttt": "texttt", "\\textnormal": "textrm",
12 | };
13 |
14 | const textFontWeights = {
15 | "\\textbf": "textbf",
16 | "\\textmd": "textmd",
17 | };
18 |
19 | const textFontShapes = {
20 | "\\textit": "textit",
21 | "\\textup": "textup",
22 | };
23 |
24 | const optionsWithFont = (group, options) => {
25 | const font = group.font;
26 | // Checks if the argument is a font family or a font style.
27 | if (!font) {
28 | return options;
29 | } else if (textFontFamilies[font]) {
30 | return options.withTextFontFamily(textFontFamilies[font]);
31 | } else if (textFontWeights[font]) {
32 | return options.withTextFontWeight(textFontWeights[font]);
33 | } else {
34 | return options.withTextFontShape(textFontShapes[font]);
35 | }
36 | };
37 |
38 | defineFunction({
39 | type: "text",
40 | names: [
41 | // Font families
42 | "\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal",
43 | // Font weights
44 | "\\textbf", "\\textmd",
45 | // Font Shapes
46 | "\\textit", "\\textup",
47 | ],
48 | props: {
49 | numArgs: 1,
50 | argTypes: ["text"],
51 | allowedInArgument: true,
52 | allowedInText: true,
53 | },
54 | handler({parser, funcName}, args) {
55 | const body = args[0];
56 | return {
57 | type: "text",
58 | mode: parser.mode,
59 | body: ordargument(body),
60 | font: funcName,
61 | };
62 | },
63 | htmlBuilder(group, options) {
64 | const newOptions = optionsWithFont(group, options);
65 | const inner = html.buildExpression(group.body, newOptions, true);
66 | return buildCommon.makeSpan(["mord", "text"], inner, newOptions);
67 | },
68 | mathmlBuilder(group, options) {
69 | const newOptions = optionsWithFont(group, options);
70 | return mml.buildExpressionRow(group.body, newOptions);
71 | },
72 | });
73 |
--------------------------------------------------------------------------------
/src/core/src/katex/constructor.rs:
--------------------------------------------------------------------------------
1 | use crate::katex::*;
2 |
3 | pub struct ArrayConstructor {
4 | pub body: NodeArray2D,
5 | pub h_lines_before_row: Vec>,
6 | pub cols: Option>,
7 | }
8 |
9 | impl ArrayConstructor {
10 | pub fn default() -> Self {
11 | Self {
12 | body: vec![],
13 | h_lines_before_row: vec![vec![]],
14 | cols: None
15 | }
16 | }
17 |
18 | pub fn next_row(&mut self) {
19 | self.body.push(Vec::new());
20 | self.h_lines_before_row.push(Vec::new());
21 | }
22 |
23 | pub fn push_node(&mut self, node: Node) {
24 | self.body.last_mut().unwrap().push(node);
25 | }
26 |
27 | pub fn map_body(&mut self, f: &dyn Fn(&mut Node) -> Node) {
28 | for row in self.body.iter_mut() {
29 | for node in row.iter_mut() {
30 | *node = f(node);
31 | }
32 | }
33 | }
34 |
35 | pub fn count_columns(&self) -> usize {
36 | self.body.iter().map(|row| row.len()).max().unwrap_or(0)
37 | }
38 |
39 | pub fn count_rows(&self) -> usize {
40 | self.body.iter().count()
41 | }
42 |
43 | pub fn cols_leftright_align(&mut self) -> &mut Self {
44 | let mut cols: Vec = Vec::new();
45 | for i in 0..self.count_columns() {
46 | let align = Align {
47 | align: if i % 2 == 0 { "r".to_string() } else { "l".to_string() },
48 | pregap: if i > 1 && i % 2 == 0 { Some(1f32) } else { Some(0f32) },
49 | postgap: Some(0f32),
50 | };
51 | cols.push(AlignSpec::Align(align));
52 | }
53 | self.cols = Some(cols);
54 | self
55 | }
56 |
57 | pub fn cols_center_align(&mut self) -> &mut Self {
58 | let mut cols: Vec = Vec::new();
59 | for i in 0..self.count_columns() {
60 | let align = Align {
61 | align: "c".to_string(),
62 | pregap: None,
63 | postgap: None
64 | };
65 | cols.push(AlignSpec::Align(align));
66 | }
67 | self.cols = Some(cols);
68 | self
69 | }
70 |
71 | pub fn builder(&self) -> ArrayBuilder {
72 | ArrayBuilder::default()
73 | .body(self.body.clone())
74 | .h_lines_before_row(self.h_lines_before_row.clone())
75 | .cols(self.cols.clone()).clone()
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/environment.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import ParseError from "../ParseError";
4 | import {assertNodeType} from "../parseNode";
5 | import environments from "../environments";
6 |
7 | // Environment delimiters. HTML/MathML rendering is defined in the corresponding
8 | // defineEnvironment definitions.
9 | defineFunction({
10 | type: "environment",
11 | names: ["\\begin", "\\end"],
12 | props: {
13 | numArgs: 1,
14 | argTypes: ["text"],
15 | },
16 | handler({parser, funcName}, args) {
17 | const nameGroup = args[0];
18 | if (nameGroup.type !== "ordgroup") {
19 | throw new ParseError("Invalid environment name", nameGroup);
20 | }
21 | let envName = "";
22 | for (let i = 0; i < nameGroup.body.length; ++i) {
23 | envName += assertNodeType(nameGroup.body[i], "textord").text;
24 | }
25 |
26 | if (funcName === "\\begin") {
27 | // begin...end is similar to left...right
28 | if (!environments.hasOwnProperty(envName)) {
29 | throw new ParseError(
30 | "No such environment: " + envName, nameGroup);
31 | }
32 | // Build the environment object. Arguments and other information will
33 | // be made available to the begin and end methods using properties.
34 | const env = environments[envName];
35 | const {args, optArgs} =
36 | parser.parseArguments("\\begin{" + envName + "}", env);
37 | const context = {
38 | mode: parser.mode,
39 | envName,
40 | parser,
41 | };
42 | const result = env.handler(context, args, optArgs);
43 | parser.expect("\\end", false);
44 | const endNameToken = parser.nextToken;
45 | const end = assertNodeType(parser.parseFunction(), "environment");
46 | if (end.name !== envName) {
47 | throw new ParseError(
48 | `Mismatch: \\begin{${envName}} matched by \\end{${end.name}}`,
49 | endNameToken);
50 | }
51 | // $FlowFixMe, "environment" handler returns an environment ParseNode
52 | return result;
53 | }
54 |
55 | return {
56 | type: "environment",
57 | mode: parser.mode,
58 | name: envName,
59 | nameGroup,
60 | };
61 | },
62 | });
63 |
--------------------------------------------------------------------------------
/lib/katex/src/tree.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 | import utils from "./utils";
4 |
5 |
6 |
7 |
8 |
9 | // To ensure that all nodes have compatible signatures for these methods.
10 |
11 |
12 |
13 |
14 |
15 |
16 | /**
17 | * This node represents a document fragment, which contains elements, but when
18 | * placed into the DOM doesn't have any representation itself. It only contains
19 | * children and doesn't have any DOM node properties.
20 | */
21 | export class DocumentFragment
22 | {
23 | children ;
24 | // HtmlDomNode
25 | classes ;
26 | height ;
27 | depth ;
28 | maxFontSize ;
29 | style ; // Never used; needed for satisfying interface.
30 |
31 | constructor(children ) {
32 | this.children = children;
33 | this.classes = [];
34 | this.height = 0;
35 | this.depth = 0;
36 | this.maxFontSize = 0;
37 | this.style = {};
38 | }
39 |
40 | hasClass(className ) {
41 | return utils.contains(this.classes, className);
42 | }
43 |
44 | /** Convert the fragment into a node. */
45 | toNode() {
46 | const frag = document.createDocumentFragment();
47 |
48 | for (let i = 0; i < this.children.length; i++) {
49 | frag.appendChild(this.children[i].toNode());
50 | }
51 |
52 | return frag;
53 | }
54 |
55 | /** Convert the fragment into HTML markup. */
56 | toMarkup() {
57 | let markup = "";
58 |
59 | // Simply concatenate the markup for the children together.
60 | for (let i = 0; i < this.children.length; i++) {
61 | markup += this.children[i].toMarkup();
62 | }
63 |
64 | return markup;
65 | }
66 |
67 | /**
68 | * Converts the math node into a string, similar to innerText. Applies to
69 | * MathDomNode's only.
70 | */
71 | toText() {
72 | // To avoid this, we would subclass documentFragment separately for
73 | // MathML, but polyfills for subclassing is expensive per PR 1469.
74 | // $FlowFixMe: Only works for ChildType = MathDomNode.
75 | const toText = (child ) => child.toText();
76 | return this.children.map(toText).join("");
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/katex/src/unicodeSupOrSub.js:
--------------------------------------------------------------------------------
1 | // Helpers for Parser.js handling of Unicode (sub|super)script characters.
2 |
3 | export const unicodeSubRegEx = /^[₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ]/;
4 |
5 | export const uSubsAndSups = Object.freeze({
6 | '₊': '+',
7 | '₋': '-',
8 | '₌': '=',
9 | '₍': '(',
10 | '₎': ')',
11 | '₀': '0',
12 | '₁': '1',
13 | '₂': '2',
14 | '₃': '3',
15 | '₄': '4',
16 | '₅': '5',
17 | '₆': '6',
18 | '₇': '7',
19 | '₈': '8',
20 | '₉': '9',
21 | '\u2090': 'a',
22 | '\u2091': 'e',
23 | '\u2095': 'h',
24 | '\u1D62': 'i',
25 | '\u2C7C': 'j',
26 | '\u2096': 'k',
27 | '\u2097': 'l',
28 | '\u2098': 'm',
29 | '\u2099': 'n',
30 | '\u2092': 'o',
31 | '\u209A': 'p',
32 | '\u1D63': 'r',
33 | '\u209B': 's',
34 | '\u209C': 't',
35 | '\u1D64': 'u',
36 | '\u1D65': 'v',
37 | '\u2093': 'x',
38 | '\u1D66': 'β',
39 | '\u1D67': 'γ',
40 | '\u1D68': 'ρ',
41 | '\u1D69': '\u03d5',
42 | '\u1D6A': 'χ',
43 | '⁺': '+',
44 | '⁻': '-',
45 | '⁼': '=',
46 | '⁽': '(',
47 | '⁾': ')',
48 | '⁰': '0',
49 | '¹': '1',
50 | '²': '2',
51 | '³': '3',
52 | '⁴': '4',
53 | '⁵': '5',
54 | '⁶': '6',
55 | '⁷': '7',
56 | '⁸': '8',
57 | '⁹': '9',
58 | '\u1D2C': 'A',
59 | '\u1D2E': 'B',
60 | '\u1D30': 'D',
61 | '\u1D31': 'E',
62 | '\u1D33': 'G',
63 | '\u1D34': 'H',
64 | '\u1D35': 'I',
65 | '\u1D36': 'J',
66 | '\u1D37': 'K',
67 | '\u1D38': 'L',
68 | '\u1D39': 'M',
69 | '\u1D3A': 'N',
70 | '\u1D3C': 'O',
71 | '\u1D3E': 'P',
72 | '\u1D3F': 'R',
73 | '\u1D40': 'T',
74 | '\u1D41': 'U',
75 | '\u2C7D': 'V',
76 | '\u1D42': 'W',
77 | '\u1D43': 'a',
78 | '\u1D47': 'b',
79 | '\u1D9C': 'c',
80 | '\u1D48': 'd',
81 | '\u1D49': 'e',
82 | '\u1DA0': 'f',
83 | '\u1D4D': 'g',
84 | '\u02B0': 'h',
85 | '\u2071': 'i',
86 | '\u02B2': 'j',
87 | '\u1D4F': 'k',
88 | '\u02E1': 'l',
89 | '\u1D50': 'm',
90 | '\u207F': 'n',
91 | '\u1D52': 'o',
92 | '\u1D56': 'p',
93 | '\u02B3': 'r',
94 | '\u02E2': 's',
95 | '\u1D57': 't',
96 | '\u1D58': 'u',
97 | '\u1D5B': 'v',
98 | '\u02B7': 'w',
99 | '\u02E3': 'x',
100 | '\u02B8': 'y',
101 | '\u1DBB': 'z',
102 | '\u1D5D': 'β',
103 | '\u1D5E': 'γ',
104 | '\u1D5F': 'δ',
105 | '\u1D60': '\u03d5',
106 | '\u1D61': 'χ',
107 | '\u1DBF': 'θ',
108 | });
109 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/styling.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import mathMLTree from "../mathMLTree";
4 | import Style from "../Style";
5 | import {sizingGroup} from "./sizing";
6 |
7 | import * as mml from "../buildMathML";
8 |
9 | const styleMap = {
10 | "display": Style.DISPLAY,
11 | "text": Style.TEXT,
12 | "script": Style.SCRIPT,
13 | "scriptscript": Style.SCRIPTSCRIPT,
14 | };
15 |
16 | defineFunction({
17 | type: "styling",
18 | names: [
19 | "\\displaystyle", "\\textstyle", "\\scriptstyle",
20 | "\\scriptscriptstyle",
21 | ],
22 | props: {
23 | numArgs: 0,
24 | allowedInText: true,
25 | primitive: true,
26 | },
27 | handler({breakOnTokenText, funcName, parser}, args) {
28 | // parse out the implicit body
29 | const body = parser.parseExpression(true, breakOnTokenText);
30 |
31 | // TODO: Refactor to avoid duplicating styleMap in multiple places (e.g.
32 | // here and in buildHTML and de-dupe the enumeration of all the styles).
33 | // $FlowFixMe: The names above exactly match the styles.
34 | const style = funcName.slice(1, funcName.length - 5);
35 | return {
36 | type: "styling",
37 | mode: parser.mode,
38 | // Figure out what style to use by pulling out the style from
39 | // the function name
40 | style,
41 | body,
42 | };
43 | },
44 | htmlBuilder(group, options) {
45 | // Style changes are handled in the TeXbook on pg. 442, Rule 3.
46 | const newStyle = styleMap[group.style];
47 | const newOptions = options.havingStyle(newStyle).withFont('');
48 | return sizingGroup(group.body, newOptions, options);
49 | },
50 | mathmlBuilder(group, options) {
51 | // Figure out what style we're changing to.
52 | const newStyle = styleMap[group.style];
53 | const newOptions = options.havingStyle(newStyle);
54 |
55 | const inner = mml.buildExpression(group.body, newOptions);
56 |
57 | const node = new mathMLTree.MathNode("mstyle", inner);
58 |
59 | const styleAttributes = {
60 | "display": ["0", "true"],
61 | "text": ["0", "false"],
62 | "script": ["1", "false"],
63 | "scriptscript": ["2", "false"],
64 | };
65 |
66 | const attr = styleAttributes[group.style];
67 |
68 | node.setAttribute("scriptlevel", attr[0]);
69 | node.setAttribute("displaystyle", attr[1]);
70 |
71 | return node;
72 | },
73 | });
74 |
--------------------------------------------------------------------------------
/lib/katex/contrib/auto-render/splitAtDelimiters.js:
--------------------------------------------------------------------------------
1 | /* eslint no-constant-condition:0 */
2 | const findEndOfMath = function(delimiter, text, startIndex) {
3 | // Adapted from
4 | // https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
5 | let index = startIndex;
6 | let braceLevel = 0;
7 |
8 | const delimLength = delimiter.length;
9 |
10 | while (index < text.length) {
11 | const character = text[index];
12 |
13 | if (braceLevel <= 0 &&
14 | text.slice(index, index + delimLength) === delimiter) {
15 | return index;
16 | } else if (character === "\\") {
17 | index++;
18 | } else if (character === "{") {
19 | braceLevel++;
20 | } else if (character === "}") {
21 | braceLevel--;
22 | }
23 |
24 | index++;
25 | }
26 |
27 | return -1;
28 | };
29 |
30 | const escapeRegex = function(string) {
31 | return string.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
32 | };
33 |
34 | const amsRegex = /^\\begin{/;
35 |
36 | const splitAtDelimiters = function(text, delimiters) {
37 | let index;
38 | const data = [];
39 |
40 | const regexLeft = new RegExp(
41 | "(" + delimiters.map((x) => escapeRegex(x.left)).join("|") + ")"
42 | );
43 |
44 | while (true) {
45 | index = text.search(regexLeft);
46 | if (index === -1) {
47 | break;
48 | }
49 | if (index > 0) {
50 | data.push({
51 | type: "text",
52 | data: text.slice(0, index),
53 | });
54 | text = text.slice(index); // now text starts with delimiter
55 | }
56 | // ... so this always succeeds:
57 | const i = delimiters.findIndex((delim) => text.startsWith(delim.left));
58 | index = findEndOfMath(delimiters[i].right, text, delimiters[i].left.length);
59 | if (index === -1) {
60 | break;
61 | }
62 | const rawData = text.slice(0, index + delimiters[i].right.length);
63 | const math = amsRegex.test(rawData)
64 | ? rawData
65 | : text.slice(delimiters[i].left.length, index);
66 | data.push({
67 | type: "math",
68 | data: math,
69 | rawData,
70 | display: delimiters[i].display,
71 | });
72 | text = text.slice(index + delimiters[i].right.length);
73 | }
74 |
75 | if (text !== "") {
76 | data.push({
77 | type: "text",
78 | data: text,
79 | });
80 | }
81 |
82 | return data;
83 | };
84 |
85 | export default splitAtDelimiters;
86 |
--------------------------------------------------------------------------------
/lib/katex/contrib/copy-tex/katex2tex.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 |
4 |
5 |
6 |
7 |
8 | // Set these to how you want inline and display math to be delimited.
9 | export const defaultCopyDelimiters = {
10 | inline: ['$', '$'], // alternative: ['\(', '\)']
11 | display: ['$$', '$$'], // alternative: ['\[', '\]']
12 | };
13 |
14 | // Replace .katex elements with their TeX source ( element).
15 | // Modifies fragment in-place. Useful for writing your own 'copy' handler,
16 | // as in copy-tex.js.
17 | export function katexReplaceWithTex(
18 | fragment ,
19 | copyDelimiters = defaultCopyDelimiters
20 | ) {
21 | // Remove .katex-html blocks that are preceded by .katex-mathml blocks
22 | // (which will get replaced below).
23 | const katexHtml = fragment.querySelectorAll('.katex-mathml + .katex-html');
24 | for (let i = 0; i < katexHtml.length; i++) {
25 | const element = katexHtml[i];
26 | if (element.remove) {
27 | element.remove();
28 | } else if (element.parentNode) {
29 | element.parentNode.removeChild(element);
30 | }
31 | }
32 | // Replace .katex-mathml elements with their annotation (TeX source)
33 | // descendant, with inline delimiters.
34 | const katexMathml = fragment.querySelectorAll('.katex-mathml');
35 | for (let i = 0; i < katexMathml.length; i++) {
36 | const element = katexMathml[i];
37 | const texSource = element.querySelector('annotation');
38 | if (texSource) {
39 | if (element.replaceWith) {
40 | element.replaceWith(texSource);
41 | } else if (element.parentNode) {
42 | element.parentNode.replaceChild(texSource, element);
43 | }
44 | texSource.innerHTML = copyDelimiters.inline[0] +
45 | texSource.innerHTML + copyDelimiters.inline[1];
46 | }
47 | }
48 | // Switch display math to display delimiters.
49 | const displays = fragment.querySelectorAll('.katex-display annotation');
50 | for (let i = 0; i < displays.length; i++) {
51 | const element = displays[i];
52 | element.innerHTML = copyDelimiters.display[0] +
53 | element.innerHTML.substr(copyDelimiters.inline[0].length,
54 | element.innerHTML.length - copyDelimiters.inline[0].length
55 | - copyDelimiters.inline[1].length)
56 | + copyDelimiters.display[1];
57 | }
58 | return fragment;
59 | }
60 |
61 | export default katexReplaceWithTex;
62 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import parseTree from 'katex/src/parseTree';
2 | import Settings from 'katex/src/Settings';
3 | import katex from 'katex';
4 | import wypst from 'wypst';
5 | import { deleteFields } from './utils';
6 |
7 | let renderDiv = document.getElementById('render');
8 |
9 | function katexRender() {
10 | let katexDiv = document.getElementById('katex');
11 |
12 | let input = katexDiv.querySelector('#input');
13 | let output = katexDiv.querySelector('#output');
14 |
15 | input.addEventListener('input', function() {
16 | // Print KaTeX parse tree
17 | try {
18 | let tree = parseTree(input.value, new Settings({displayMode: true, strict: "ignore"}));
19 | deleteFields(tree, ['loc']);
20 |
21 | let treeHTML = prettyJsonToHtml(tree);
22 | output.innerHTML = treeHTML;
23 | } catch (error) {
24 | output.innerHTML = error;
25 | }
26 |
27 | // Render the equation
28 | try {
29 | katex.render(input.value, renderDiv, {displayMode: true});
30 | } catch (error) { console.log(error); }
31 | });
32 | }
33 | katexRender();
34 |
35 | async function wypstRender() {
36 | let wypstDiv = document.getElementById('wypst');
37 | let typstDiv = document.getElementById('typst');
38 |
39 | let input = wypstDiv.querySelector('#input');
40 | let wypstOutput = wypstDiv.querySelector('#output');
41 | let typstOutput = typstDiv.querySelector('#output');
42 |
43 | await wypst.init();
44 | input.addEventListener('input', async function() {
45 | // Print Typst parse tree (in KaTeX representation)
46 | try {
47 | let tree = wypst.parseTree(input.value);
48 | let treeHTML = prettyJsonToHtml(tree);
49 |
50 | wypstOutput.innerHTML = treeHTML;
51 | } catch (error) {
52 | wypstOutput.innerHTML = error;
53 | }
54 |
55 | // Print Typst content tree
56 | try {
57 | let tree = wypst.__typstContentTree(input.value);
58 | let treeHTML = prettyStringToHtml(tree);
59 | typstOutput.innerHTML = treeHTML;
60 | } catch (error) {
61 | typstOutput.innerHTML = '';
62 | }
63 |
64 | // Render the equation
65 | try {
66 | wypst.render(input.value, renderDiv, {displayMode: true});
67 | } catch (error) { }
68 | });
69 | }
70 | wypstRender()
71 |
72 | function prettyJsonToHtml(json) {
73 | let s = JSON.stringify(json, null, 4);
74 | return prettyStringToHtml(s);
75 | }
76 |
77 | function prettyStringToHtml(s) {
78 | return s.replace(/\n/g, '
').replace(/ /g, ' ');
79 | }
80 |
--------------------------------------------------------------------------------
/src/core/src/utils.rs:
--------------------------------------------------------------------------------
1 | use comemo::Prehashed;
2 | use comemo::Track;
3 | use typst;
4 | use typst::World;
5 |
6 | pub struct FakeWorld {
7 | library: Prehashed,
8 | }
9 |
10 | impl FakeWorld {
11 | pub fn new() -> Self {
12 | FakeWorld {
13 | library: Prehashed::new(typst::Library::build()),
14 | }
15 | }
16 | }
17 |
18 | impl World for FakeWorld {
19 | fn library(&self) -> &Prehashed {
20 | &self.library
21 | }
22 | fn book(&self) -> &Prehashed {
23 | unimplemented!();
24 | }
25 | fn file(&self,id:typst_syntax::FileId) -> typst::diag::FileResult {
26 | unimplemented!();
27 | }
28 | fn font(&self,index:usize) -> Option {
29 | unimplemented!();
30 | }
31 | fn main(&self) -> typst_syntax::Source {
32 | unimplemented!();
33 | }
34 | fn packages(&self) -> &[(typst_syntax::PackageSpec,Option)] {
35 | unimplemented!();
36 | }
37 | fn source(&self,id:typst_syntax::FileId) -> typst::diag::FileResult {
38 | unimplemented!();
39 | }
40 | fn today(&self,offset:Option) -> Option {
41 | unimplemented!();
42 | }
43 | }
44 |
45 | pub fn eval(world: &dyn typst::World, string: &str) -> Result {
46 | // Make engine
47 | let introspector = typst::introspection::Introspector::default();
48 | let mut locator = typst::introspection::Locator::default();
49 | let mut tracer = typst::eval::Tracer::default();
50 |
51 | let engine = typst::engine::Engine {
52 | world: world.track(),
53 | introspector: introspector.track(),
54 | route: typst::engine::Route::default(),
55 | locator: &mut locator,
56 | tracer: tracer.track_mut(),
57 | };
58 |
59 | let result = typst::eval::eval_string(
60 | world.track(),
61 | string,
62 | typst::syntax::Span::detached(),
63 | typst::eval::EvalMode::Math,
64 | world.library().math.scope().clone()
65 | );
66 |
67 | match result {
68 | Ok(value) => match value {
69 | typst::foundations::Value::Content(content) => Ok(content),
70 | _ => Err("Expected content result.".to_string()),
71 | }
72 | Err(err) => Err(err[0].message.to_string())
73 | }
74 | }
75 |
76 | pub fn insert_separator(list: &[T], separator: T) -> Vec {
77 | list.iter()
78 | .flat_map(|x| vec![x.clone(), separator.clone()])
79 | .take(list.len() * 2 - 1)
80 | .collect()
81 | }
82 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/href.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction, {ordargument} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import {assertNodeType} from "../parseNode";
5 | import {MathNode} from "../mathMLTree";
6 |
7 | import * as html from "../buildHTML";
8 | import * as mml from "../buildMathML";
9 |
10 | defineFunction({
11 | type: "href",
12 | names: ["\\href"],
13 | props: {
14 | numArgs: 2,
15 | argTypes: ["url", "original"],
16 | allowedInText: true,
17 | },
18 | handler: ({parser}, args) => {
19 | const body = args[1];
20 | const href = assertNodeType(args[0], "url").url;
21 |
22 | if (!parser.settings.isTrusted({
23 | command: "\\href",
24 | url: href,
25 | })) {
26 | return parser.formatUnsupportedCmd("\\href");
27 | }
28 |
29 | return {
30 | type: "href",
31 | mode: parser.mode,
32 | href,
33 | body: ordargument(body),
34 | };
35 | },
36 | htmlBuilder: (group, options) => {
37 | const elements = html.buildExpression(group.body, options, false);
38 | return buildCommon.makeAnchor(group.href, [], elements, options);
39 | },
40 | mathmlBuilder: (group, options) => {
41 | let math = mml.buildExpressionRow(group.body, options);
42 | if (!(math instanceof MathNode)) {
43 | math = new MathNode("mrow", [math]);
44 | }
45 | math.setAttribute("href", group.href);
46 | return math;
47 | },
48 | });
49 |
50 | defineFunction({
51 | type: "href",
52 | names: ["\\url"],
53 | props: {
54 | numArgs: 1,
55 | argTypes: ["url"],
56 | allowedInText: true,
57 | },
58 | handler: ({parser}, args) => {
59 | const href = assertNodeType(args[0], "url").url;
60 |
61 | if (!parser.settings.isTrusted({
62 | command: "\\url",
63 | url: href,
64 | })) {
65 | return parser.formatUnsupportedCmd("\\url");
66 | }
67 |
68 | const chars = [];
69 | for (let i = 0; i < href.length; i++) {
70 | let c = href[i];
71 | if (c === "~") {
72 | c = "\\textasciitilde";
73 | }
74 | chars.push({
75 | type: "textord",
76 | mode: "text",
77 | text: c,
78 | });
79 | }
80 | const body = {
81 | type: "text",
82 | mode: parser.mode,
83 | font: "\\texttt",
84 | body: chars,
85 | };
86 | return {
87 | type: "href",
88 | mode: parser.mode,
89 | href,
90 | body: ordargument(body),
91 | };
92 | },
93 | });
94 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/color.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction, {ordargument} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 | import {assertNodeType} from "../parseNode";
6 |
7 |
8 |
9 | import * as html from "../buildHTML";
10 | import * as mml from "../buildMathML";
11 |
12 | const htmlBuilder = (group, options) => {
13 | const elements = html.buildExpression(
14 | group.body,
15 | options.withColor(group.color),
16 | false
17 | );
18 |
19 | // \color isn't supposed to affect the type of the elements it contains.
20 | // To accomplish this, we wrap the results in a fragment, so the inner
21 | // elements will be able to directly interact with their neighbors. For
22 | // example, `\color{red}{2 +} 3` has the same spacing as `2 + 3`
23 | return buildCommon.makeFragment(elements);
24 | };
25 |
26 | const mathmlBuilder = (group, options) => {
27 | const inner = mml.buildExpression(group.body,
28 | options.withColor(group.color));
29 |
30 | const node = new mathMLTree.MathNode("mstyle", inner);
31 |
32 | node.setAttribute("mathcolor", group.color);
33 |
34 | return node;
35 | };
36 |
37 | defineFunction({
38 | type: "color",
39 | names: ["\\textcolor"],
40 | props: {
41 | numArgs: 2,
42 | allowedInText: true,
43 | argTypes: ["color", "original"],
44 | },
45 | handler({parser}, args) {
46 | const color = assertNodeType(args[0], "color-token").color;
47 | const body = args[1];
48 | return {
49 | type: "color",
50 | mode: parser.mode,
51 | color,
52 | body: (ordargument(body) ),
53 | };
54 | },
55 | htmlBuilder,
56 | mathmlBuilder,
57 | });
58 |
59 | defineFunction({
60 | type: "color",
61 | names: ["\\color"],
62 | props: {
63 | numArgs: 1,
64 | allowedInText: true,
65 | argTypes: ["color"],
66 | },
67 | handler({parser, breakOnTokenText}, args) {
68 | const color = assertNodeType(args[0], "color-token").color;
69 |
70 | // Set macro \current@color in current namespace to store the current
71 | // color, mimicking the behavior of color.sty.
72 | // This is currently used just to correctly color a \right
73 | // that follows a \color command.
74 | parser.gullet.macros.set("\\current@color", color);
75 |
76 | // Parse out the implicit body that should be colored.
77 | const body = parser.parseExpression(true, breakOnTokenText);
78 |
79 | return {
80 | type: "color",
81 | mode: parser.mode,
82 | color,
83 | body,
84 | };
85 | },
86 | htmlBuilder,
87 | mathmlBuilder,
88 | });
89 |
--------------------------------------------------------------------------------
/lib/katex/src/spacingData.js:
--------------------------------------------------------------------------------
1 | //
2 | /**
3 | * Describes spaces between different classes of atoms.
4 | */
5 |
6 |
7 | const thinspace = {
8 | number: 3,
9 | unit: "mu",
10 | };
11 | const mediumspace = {
12 | number: 4,
13 | unit: "mu",
14 | };
15 | const thickspace = {
16 | number: 5,
17 | unit: "mu",
18 | };
19 |
20 | // Making the type below exact with all optional fields doesn't work due to
21 | // - https://github.com/facebook/flow/issues/4582
22 | // - https://github.com/facebook/flow/issues/5688
23 | // However, since *all* fields are optional, $Shape<> works as suggested in 5688
24 | // above.
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | // Spacing relationships for display and text styles
37 | export const spacings = {
38 | mord: {
39 | mop: thinspace,
40 | mbin: mediumspace,
41 | mrel: thickspace,
42 | minner: thinspace,
43 | },
44 | mop: {
45 | mord: thinspace,
46 | mop: thinspace,
47 | mrel: thickspace,
48 | minner: thinspace,
49 | },
50 | mbin: {
51 | mord: mediumspace,
52 | mop: mediumspace,
53 | mopen: mediumspace,
54 | minner: mediumspace,
55 | },
56 | mrel: {
57 | mord: thickspace,
58 | mop: thickspace,
59 | mopen: thickspace,
60 | minner: thickspace,
61 | },
62 | mopen: {},
63 | mclose: {
64 | mop: thinspace,
65 | mbin: mediumspace,
66 | mrel: thickspace,
67 | minner: thinspace,
68 | },
69 | mpunct: {
70 | mord: thinspace,
71 | mop: thinspace,
72 | mrel: thickspace,
73 | mopen: thinspace,
74 | mclose: thinspace,
75 | mpunct: thinspace,
76 | minner: thinspace,
77 | },
78 | minner: {
79 | mord: thinspace,
80 | mop: thinspace,
81 | mbin: mediumspace,
82 | mrel: thickspace,
83 | mopen: thinspace,
84 | mpunct: thinspace,
85 | minner: thinspace,
86 | },
87 | };
88 |
89 | // Spacing relationships for script and scriptscript styles
90 | export const tightSpacings = {
91 | mord: {
92 | mop: thinspace,
93 | },
94 | mop: {
95 | mord: thinspace,
96 | mop: thinspace,
97 | },
98 | mbin: {},
99 | mrel: {},
100 | mopen: {},
101 | mclose: {
102 | mop: thinspace,
103 | },
104 | mpunct: {},
105 | minner: {
106 | mop: thinspace,
107 | },
108 | };
109 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/lap.js:
--------------------------------------------------------------------------------
1 | //
2 | // Horizontal overlap functions
3 | import defineFunction from "../defineFunction";
4 | import buildCommon from "../buildCommon";
5 | import mathMLTree from "../mathMLTree";
6 | import {makeEm} from "../units";
7 |
8 | import * as html from "../buildHTML";
9 | import * as mml from "../buildMathML";
10 |
11 | defineFunction({
12 | type: "lap",
13 | names: ["\\mathllap", "\\mathrlap", "\\mathclap"],
14 | props: {
15 | numArgs: 1,
16 | allowedInText: true,
17 | },
18 | handler: ({parser, funcName}, args) => {
19 | const body = args[0];
20 | return {
21 | type: "lap",
22 | mode: parser.mode,
23 | alignment: funcName.slice(5),
24 | body,
25 | };
26 | },
27 | htmlBuilder: (group, options) => {
28 | // mathllap, mathrlap, mathclap
29 | let inner;
30 | if (group.alignment === "clap") {
31 | // ref: https://www.math.lsu.edu/~aperlis/publications/mathclap/
32 | inner = buildCommon.makeSpan(
33 | [], [html.buildGroup(group.body, options)]);
34 | // wrap, since CSS will center a .clap > .inner > span
35 | inner = buildCommon.makeSpan(["inner"], [inner], options);
36 | } else {
37 | inner = buildCommon.makeSpan(
38 | ["inner"], [html.buildGroup(group.body, options)]);
39 | }
40 | const fix = buildCommon.makeSpan(["fix"], []);
41 | let node = buildCommon.makeSpan(
42 | [group.alignment], [inner, fix], options);
43 |
44 | // At this point, we have correctly set horizontal alignment of the
45 | // two items involved in the lap.
46 | // Next, use a strut to set the height of the HTML bounding box.
47 | // Otherwise, a tall argument may be misplaced.
48 | // This code resolved issue #1153
49 | const strut = buildCommon.makeSpan(["strut"]);
50 | strut.style.height = makeEm(node.height + node.depth);
51 | if (node.depth) {
52 | strut.style.verticalAlign = makeEm(-node.depth);
53 | }
54 | node.children.unshift(strut);
55 |
56 | // Next, prevent vertical misplacement when next to something tall.
57 | // This code resolves issue #1234
58 | node = buildCommon.makeSpan(["thinbox"], [node], options);
59 | return buildCommon.makeSpan(["mord", "vbox"], [node], options);
60 | },
61 | mathmlBuilder: (group, options) => {
62 | // mathllap, mathrlap, mathclap
63 | const node = new mathMLTree.MathNode(
64 | "mpadded", [mml.buildGroup(group.body, options)]);
65 |
66 | if (group.alignment !== "rlap") {
67 | const offset = (group.alignment === "llap" ? "-1" : "-0.5");
68 | node.setAttribute("lspace", offset + "width");
69 | }
70 | node.setAttribute("width", "0px");
71 |
72 | return node;
73 | },
74 | });
75 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/symbolsSpacing.js:
--------------------------------------------------------------------------------
1 | //
2 | import {defineFunctionBuilders} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 | import ParseError from "../ParseError";
6 |
7 | // A map of CSS-based spacing functions to their CSS class.
8 | const cssSpace = {
9 | "\\nobreak": "nobreak",
10 | "\\allowbreak": "allowbreak",
11 | };
12 |
13 | // A lookup table to determine whether a spacing function/symbol should be
14 | // treated like a regular space character. If a symbol or command is a key
15 | // in this table, then it should be a regular space character. Furthermore,
16 | // the associated value may have a `className` specifying an extra CSS class
17 | // to add to the created `span`.
18 | const regularSpace = {
19 | " ": {},
20 | "\\ ": {},
21 | "~": {
22 | className: "nobreak",
23 | },
24 | "\\space": {},
25 | "\\nobreakspace": {
26 | className: "nobreak",
27 | },
28 | };
29 |
30 | // ParseNode<"spacing"> created in Parser.js from the "spacing" symbol Groups in
31 | // src/symbols.js.
32 | defineFunctionBuilders({
33 | type: "spacing",
34 | htmlBuilder(group, options) {
35 | if (regularSpace.hasOwnProperty(group.text)) {
36 | const className = regularSpace[group.text].className || "";
37 | // Spaces are generated by adding an actual space. Each of these
38 | // things has an entry in the symbols table, so these will be turned
39 | // into appropriate outputs.
40 | if (group.mode === "text") {
41 | const ord = buildCommon.makeOrd(group, options, "textord");
42 | ord.classes.push(className);
43 | return ord;
44 | } else {
45 | return buildCommon.makeSpan(["mspace", className],
46 | [buildCommon.mathsym(group.text, group.mode, options)],
47 | options);
48 | }
49 | } else if (cssSpace.hasOwnProperty(group.text)) {
50 | // Spaces based on just a CSS class.
51 | return buildCommon.makeSpan(
52 | ["mspace", cssSpace[group.text]],
53 | [], options);
54 | } else {
55 | throw new ParseError(`Unknown type of space "${group.text}"`);
56 | }
57 | },
58 | mathmlBuilder(group, options) {
59 | let node;
60 |
61 | if (regularSpace.hasOwnProperty(group.text)) {
62 | node = new mathMLTree.MathNode(
63 | "mtext", [new mathMLTree.TextNode("\u00a0")]);
64 | } else if (cssSpace.hasOwnProperty(group.text)) {
65 | // CSS-based MathML spaces (\nobreak, \allowbreak) are ignored
66 | return new mathMLTree.MathNode("mspace");
67 | } else {
68 | throw new ParseError(`Unknown type of space "${group.text}"`);
69 | }
70 |
71 | return node;
72 | },
73 | });
74 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/rule.js:
--------------------------------------------------------------------------------
1 | //
2 | import buildCommon from "../buildCommon";
3 | import defineFunction from "../defineFunction";
4 | import mathMLTree from "../mathMLTree";
5 | import {assertNodeType} from "../parseNode";
6 | import {calculateSize, makeEm} from "../units";
7 |
8 | defineFunction({
9 | type: "rule",
10 | names: ["\\rule"],
11 | props: {
12 | numArgs: 2,
13 | numOptionalArgs: 1,
14 | argTypes: ["size", "size", "size"],
15 | },
16 | handler({parser}, args, optArgs) {
17 | const shift = optArgs[0];
18 | const width = assertNodeType(args[0], "size");
19 | const height = assertNodeType(args[1], "size");
20 | return {
21 | type: "rule",
22 | mode: parser.mode,
23 | shift: shift && assertNodeType(shift, "size").value,
24 | width: width.value,
25 | height: height.value,
26 | };
27 | },
28 | htmlBuilder(group, options) {
29 | // Make an empty span for the rule
30 | const rule = buildCommon.makeSpan(["mord", "rule"], [], options);
31 |
32 | // Calculate the shift, width, and height of the rule, and account for units
33 | const width = calculateSize(group.width, options);
34 | const height = calculateSize(group.height, options);
35 | const shift = (group.shift) ? calculateSize(group.shift, options) : 0;
36 |
37 | // Style the rule to the right size
38 | rule.style.borderRightWidth = makeEm(width);
39 | rule.style.borderTopWidth = makeEm(height);
40 | rule.style.bottom = makeEm(shift);
41 |
42 | // Record the height and width
43 | rule.width = width;
44 | rule.height = height + shift;
45 | rule.depth = -shift;
46 | // Font size is the number large enough that the browser will
47 | // reserve at least `absHeight` space above the baseline.
48 | // The 1.125 factor was empirically determined
49 | rule.maxFontSize = height * 1.125 * options.sizeMultiplier;
50 |
51 | return rule;
52 | },
53 | mathmlBuilder(group, options) {
54 | const width = calculateSize(group.width, options);
55 | const height = calculateSize(group.height, options);
56 | const shift = (group.shift) ? calculateSize(group.shift, options) : 0;
57 | const color = options.color && options.getColor() || "black";
58 |
59 | const rule = new mathMLTree.MathNode("mspace");
60 | rule.setAttribute("mathbackground", color);
61 | rule.setAttribute("width", makeEm(width));
62 | rule.setAttribute("height", makeEm(height));
63 |
64 | const wrapper = new mathMLTree.MathNode("mpadded", [rule]);
65 | if (shift >= 0) {
66 | wrapper.setAttribute("height", makeEm(shift));
67 | } else {
68 | wrapper.setAttribute("height", makeEm(shift));
69 | wrapper.setAttribute("depth", makeEm(-shift));
70 | }
71 | wrapper.setAttribute("voffset", makeEm(shift));
72 |
73 | return wrapper;
74 | },
75 | });
76 |
--------------------------------------------------------------------------------
/lib/katex/src/ParseError.js:
--------------------------------------------------------------------------------
1 | //
2 | import {Token} from "./Token";
3 |
4 |
5 |
6 | /**
7 | * This is the ParseError class, which is the main error thrown by KaTeX
8 | * functions when something has gone wrong. This is used to distinguish internal
9 | * errors from errors in the expression that the user provided.
10 | *
11 | * If possible, a caller should provide a Token or ParseNode with information
12 | * about where in the source string the problem occurred.
13 | */
14 | class ParseError {
15 | name ;
16 | position ;
17 | // Error start position based on passed-in Token or ParseNode.
18 | length ;
19 | // Length of affected text based on passed-in Token or ParseNode.
20 | rawMessage ;
21 | // The underlying error message without any context added.
22 |
23 | constructor(
24 | message , // The error message
25 | token , // An object providing position information
26 | ) {
27 | let error = "KaTeX parse error: " + message;
28 | let start;
29 | let end;
30 |
31 | const loc = token && token.loc;
32 | if (loc && loc.start <= loc.end) {
33 | // If we have the input and a position, make the error a bit fancier
34 |
35 | // Get the input
36 | const input = loc.lexer.input;
37 |
38 | // Prepend some information
39 | start = loc.start;
40 | end = loc.end;
41 | if (start === input.length) {
42 | error += " at end of input: ";
43 | } else {
44 | error += " at position " + (start + 1) + ": ";
45 | }
46 |
47 | // Underline token in question using combining underscores
48 | const underlined = input.slice(start, end).replace(/[^]/g, "$&\u0332");
49 |
50 | // Extract some context from the input and add it to the error
51 | let left;
52 | if (start > 15) {
53 | left = "…" + input.slice(start - 15, start);
54 | } else {
55 | left = input.slice(0, start);
56 | }
57 | let right;
58 | if (end + 15 < input.length) {
59 | right = input.slice(end, end + 15) + "…";
60 | } else {
61 | right = input.slice(end);
62 | }
63 | error += left + underlined + right;
64 |
65 | }
66 |
67 | // Some hackery to make ParseError a prototype of Error
68 | // See http://stackoverflow.com/a/8460753
69 | // $FlowFixMe
70 | const self = new Error(error);
71 | self.name = "ParseError";
72 | // $FlowFixMe
73 | self.__proto__ = ParseError.prototype;
74 | self.position = start;
75 | if (start != null && end != null) {
76 | self.length = end - start;
77 | }
78 | self.rawMessage = message;
79 | return self;
80 | }
81 | }
82 |
83 | // $FlowFixMe More hackery
84 | ParseError.prototype.__proto__ = Error.prototype;
85 |
86 | export default ParseError;
87 |
--------------------------------------------------------------------------------
/lib/katex/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | // Simple CLI for KaTeX.
3 | // Reads TeX from stdin, outputs HTML to stdout.
4 | // To run this from the repository, you must first build KaTeX by running
5 | // `yarn` and `yarn build`.
6 |
7 | /* eslint no-console:0 */
8 |
9 | let katex;
10 | try {
11 | katex = require("./");
12 | } catch (e) {
13 | console.error(
14 | "KaTeX could not import, likely because dist/katex.js is missing.");
15 | console.error("Please run 'yarn' and 'yarn build' before running");
16 | console.error("cli.js from the KaTeX repository.");
17 | console.error();
18 | throw e;
19 | }
20 | const {version} = require("./package.json");
21 | const fs = require("fs");
22 |
23 | const program = require("commander").version(version);
24 | for (const prop in katex.SETTINGS_SCHEMA) {
25 | if (katex.SETTINGS_SCHEMA.hasOwnProperty(prop)) {
26 | const opt = katex.SETTINGS_SCHEMA[prop];
27 | if (opt.cli !== false) {
28 | program.option(opt.cli || "--" + prop, opt.cliDescription ||
29 | opt.description, opt.cliProcessor, opt.cliDefault);
30 | }
31 | }
32 | }
33 | program.option("-f, --macro-file ",
34 | "Read macro definitions, one per line, from the given file.")
35 | .option("-i, --input ", "Read LaTeX input from the given file.")
36 | .option("-o, --output ", "Write html output to the given file.");
37 |
38 | let options;
39 |
40 | function readMacros() {
41 | if (options.macroFile) {
42 | fs.readFile(options.macroFile, "utf-8", function(err, data) {
43 | if (err) {throw err;}
44 | splitMacros(data.toString().split('\n'));
45 | });
46 | } else {
47 | splitMacros([]);
48 | }
49 | }
50 |
51 | function splitMacros(macroStrings) {
52 | // Override macros from macro file (if any)
53 | // with macros from command line (if any)
54 | macroStrings = macroStrings.concat(options.macro);
55 |
56 | const macros = {};
57 |
58 | for (const m of macroStrings) {
59 | const i = m.search(":");
60 | if (i !== -1) {
61 | macros[m.substring(0, i).trim()] = m.substring(i + 1).trim();
62 | }
63 | }
64 |
65 | options.macros = macros;
66 | readInput();
67 | }
68 |
69 | function readInput() {
70 | let input = "";
71 |
72 | if (options.input) {
73 | fs.readFile(options.input, "utf-8", function(err, data) {
74 | if (err) {throw err;}
75 | input = data.toString();
76 | writeOutput(input);
77 | });
78 | } else {
79 | process.stdin.on("data", function(chunk) {
80 | input += chunk.toString();
81 | });
82 |
83 | process.stdin.on("end", function() {
84 | writeOutput(input);
85 | });
86 | }
87 | }
88 |
89 | function writeOutput(input) {
90 | // --format specifies the KaTeX output
91 | const outputFile = options.output;
92 | options.output = options.format;
93 |
94 | const output = katex.renderToString(input, options) + "\n";
95 |
96 | if (outputFile) {
97 | fs.writeFile(outputFile, output, function(err) {
98 | if (err) {
99 | return console.log(err);
100 | }
101 | });
102 | } else {
103 | console.log(output);
104 | }
105 | }
106 |
107 | if (require.main !== module) {
108 | module.exports = program;
109 | } else {
110 | options = program.parse(process.argv).opts();
111 | readMacros();
112 | }
113 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/sizing.js:
--------------------------------------------------------------------------------
1 | //
2 | import buildCommon from "../buildCommon";
3 | import defineFunction from "../defineFunction";
4 | import mathMLTree from "../mathMLTree";
5 | import {makeEm} from "../units";
6 |
7 | import * as html from "../buildHTML";
8 | import * as mml from "../buildMathML";
9 |
10 |
11 |
12 |
13 |
14 |
15 | export function sizingGroup(
16 | value ,
17 | options ,
18 | baseOptions ,
19 | ) {
20 | const inner = html.buildExpression(value, options, false);
21 | const multiplier = options.sizeMultiplier / baseOptions.sizeMultiplier;
22 |
23 | // Add size-resetting classes to the inner list and set maxFontSize
24 | // manually. Handle nested size changes.
25 | for (let i = 0; i < inner.length; i++) {
26 | const pos = inner[i].classes.indexOf("sizing");
27 | if (pos < 0) {
28 | Array.prototype.push.apply(inner[i].classes,
29 | options.sizingClasses(baseOptions));
30 | } else if (inner[i].classes[pos + 1] === "reset-size" + options.size) {
31 | // This is a nested size change: e.g., inner[i] is the "b" in
32 | // `\Huge a \small b`. Override the old size (the `reset-` class)
33 | // but not the new size.
34 | inner[i].classes[pos + 1] = "reset-size" + baseOptions.size;
35 | }
36 |
37 | inner[i].height *= multiplier;
38 | inner[i].depth *= multiplier;
39 | }
40 |
41 | return buildCommon.makeFragment(inner);
42 | }
43 |
44 | const sizeFuncs = [
45 | "\\tiny", "\\sixptsize", "\\scriptsize", "\\footnotesize", "\\small",
46 | "\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge",
47 | ];
48 |
49 | export const htmlBuilder = (group, options) => {
50 | // Handle sizing operators like \Huge. Real TeX doesn't actually allow
51 | // these functions inside of math expressions, so we do some special
52 | // handling.
53 | const newOptions = options.havingSize(group.size);
54 | return sizingGroup(group.body, newOptions, options);
55 | };
56 |
57 | defineFunction({
58 | type: "sizing",
59 | names: sizeFuncs,
60 | props: {
61 | numArgs: 0,
62 | allowedInText: true,
63 | },
64 | handler: ({breakOnTokenText, funcName, parser}, args) => {
65 | const body = parser.parseExpression(false, breakOnTokenText);
66 |
67 | return {
68 | type: "sizing",
69 | mode: parser.mode,
70 | // Figure out what size to use based on the list of functions above
71 | size: sizeFuncs.indexOf(funcName) + 1,
72 | body,
73 | };
74 | },
75 | htmlBuilder,
76 | mathmlBuilder: (group, options) => {
77 | const newOptions = options.havingSize(group.size);
78 | const inner = mml.buildExpression(group.body, newOptions);
79 |
80 | const node = new mathMLTree.MathNode("mstyle", inner);
81 |
82 | // TODO(emily): This doesn't produce the correct size for nested size
83 | // changes, because we don't keep state of what style we're currently
84 | // in, so we can't reset the size to normal before changing it. Now
85 | // that we're passing an options parameter we should be able to fix
86 | // this.
87 | node.setAttribute("mathsize", makeEm(newOptions.sizeMultiplier));
88 |
89 | return node;
90 | },
91 | });
92 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/font.js:
--------------------------------------------------------------------------------
1 | //
2 | // TODO(kevinb): implement \\sl and \\sc
3 |
4 | import {binrelClass} from "./mclass";
5 | import defineFunction, {normalizeArgument} from "../defineFunction";
6 | import utils from "../utils";
7 |
8 | import * as html from "../buildHTML";
9 | import * as mml from "../buildMathML";
10 |
11 |
12 |
13 | const htmlBuilder = (group , options) => {
14 | const font = group.font;
15 | const newOptions = options.withFont(font);
16 | return html.buildGroup(group.body, newOptions);
17 | };
18 |
19 | const mathmlBuilder = (group , options) => {
20 | const font = group.font;
21 | const newOptions = options.withFont(font);
22 | return mml.buildGroup(group.body, newOptions);
23 | };
24 |
25 | const fontAliases = {
26 | "\\Bbb": "\\mathbb",
27 | "\\bold": "\\mathbf",
28 | "\\frak": "\\mathfrak",
29 | "\\bm": "\\boldsymbol",
30 | };
31 |
32 | defineFunction({
33 | type: "font",
34 | names: [
35 | // styles, except \boldsymbol defined below
36 | "\\mathrm", "\\mathit", "\\mathbf", "\\mathnormal",
37 |
38 | // families
39 | "\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf",
40 | "\\mathtt",
41 |
42 | // aliases, except \bm defined below
43 | "\\Bbb", "\\bold", "\\frak",
44 | ],
45 | props: {
46 | numArgs: 1,
47 | allowedInArgument: true,
48 | },
49 | handler: ({parser, funcName}, args) => {
50 | const body = normalizeArgument(args[0]);
51 | let func = funcName;
52 | if (func in fontAliases) {
53 | func = fontAliases[func];
54 | }
55 | return {
56 | type: "font",
57 | mode: parser.mode,
58 | font: func.slice(1),
59 | body,
60 | };
61 | },
62 | htmlBuilder,
63 | mathmlBuilder,
64 | });
65 |
66 | defineFunction({
67 | type: "mclass",
68 | names: ["\\boldsymbol", "\\bm"],
69 | props: {
70 | numArgs: 1,
71 | },
72 | handler: ({parser}, args) => {
73 | const body = args[0];
74 | const isCharacterBox = utils.isCharacterBox(body);
75 | // amsbsy.sty's \boldsymbol uses \binrel spacing to inherit the
76 | // argument's bin|rel|ord status
77 | return {
78 | type: "mclass",
79 | mode: parser.mode,
80 | mclass: binrelClass(body),
81 | body: [
82 | {
83 | type: "font",
84 | mode: parser.mode,
85 | font: "boldsymbol",
86 | body,
87 | },
88 | ],
89 | isCharacterBox: isCharacterBox,
90 | };
91 | },
92 | });
93 |
94 | // Old font changing functions
95 | defineFunction({
96 | type: "font",
97 | names: ["\\rm", "\\sf", "\\tt", "\\bf", "\\it", "\\cal"],
98 | props: {
99 | numArgs: 0,
100 | allowedInText: true,
101 | },
102 | handler: ({parser, funcName, breakOnTokenText}, args) => {
103 | const {mode} = parser;
104 | const body = parser.parseExpression(true, breakOnTokenText);
105 | const style = `math${funcName.slice(1)}`;
106 |
107 | return {
108 | type: "font",
109 | mode: mode,
110 | font: style,
111 | body: {
112 | type: "ordgroup",
113 | mode: parser.mode,
114 | body,
115 | },
116 | };
117 | },
118 | htmlBuilder,
119 | mathmlBuilder,
120 | });
121 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/html.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction, {ordargument} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import {assertNodeType} from "../parseNode";
5 | import ParseError from "../ParseError";
6 |
7 | import * as html from "../buildHTML";
8 | import * as mml from "../buildMathML";
9 |
10 | defineFunction({
11 | type: "html",
12 | names: ["\\htmlClass", "\\htmlId", "\\htmlStyle", "\\htmlData"],
13 | props: {
14 | numArgs: 2,
15 | argTypes: ["raw", "original"],
16 | allowedInText: true,
17 | },
18 | handler: ({parser, funcName, token}, args) => {
19 | const value = assertNodeType(args[0], "raw").string;
20 | const body = args[1];
21 |
22 | if (parser.settings.strict) {
23 | parser.settings.reportNonstrict("htmlExtension",
24 | "HTML extension is disabled on strict mode");
25 | }
26 |
27 | let trustContext;
28 | const attributes = {};
29 |
30 | switch (funcName) {
31 | case "\\htmlClass":
32 | attributes.class = value;
33 | trustContext = {
34 | command: "\\htmlClass",
35 | class: value,
36 | };
37 | break;
38 | case "\\htmlId":
39 | attributes.id = value;
40 | trustContext = {
41 | command: "\\htmlId",
42 | id: value,
43 | };
44 | break;
45 | case "\\htmlStyle":
46 | attributes.style = value;
47 | trustContext = {
48 | command: "\\htmlStyle",
49 | style: value,
50 | };
51 | break;
52 | case "\\htmlData": {
53 | const data = value.split(",");
54 | for (let i = 0; i < data.length; i++) {
55 | const keyVal = data[i].split("=");
56 | if (keyVal.length !== 2) {
57 | throw new ParseError(
58 | "Error parsing key-value for \\htmlData");
59 | }
60 | attributes["data-" + keyVal[0].trim()] = keyVal[1].trim();
61 | }
62 |
63 | trustContext = {
64 | command: "\\htmlData",
65 | attributes,
66 | };
67 | break;
68 | }
69 | default:
70 | throw new Error("Unrecognized html command");
71 | }
72 |
73 | if (!parser.settings.isTrusted(trustContext)) {
74 | return parser.formatUnsupportedCmd(funcName);
75 | }
76 | return {
77 | type: "html",
78 | mode: parser.mode,
79 | attributes,
80 | body: ordargument(body),
81 | };
82 | },
83 | htmlBuilder: (group, options) => {
84 | const elements = html.buildExpression(group.body, options, false);
85 |
86 | const classes = ["enclosing"];
87 | if (group.attributes.class) {
88 | classes.push(...group.attributes.class.trim().split(/\s+/));
89 | }
90 |
91 | const span = buildCommon.makeSpan(classes, elements, options);
92 | for (const attr in group.attributes) {
93 | if (attr !== "class" && group.attributes.hasOwnProperty(attr)) {
94 | span.setAttribute(attr, group.attributes[attr]);
95 | }
96 | }
97 | return span;
98 | },
99 | mathmlBuilder: (group, options) => {
100 | return mml.buildExpressionRow(group.body, options);
101 | },
102 | });
103 |
--------------------------------------------------------------------------------
/lib/katex/src/Style.js:
--------------------------------------------------------------------------------
1 | //
2 | /**
3 | * This file contains information and classes for the various kinds of styles
4 | * used in TeX. It provides a generic `Style` class, which holds information
5 | * about a specific style. It then provides instances of all the different kinds
6 | * of styles possible, and provides functions to move between them and get
7 | * information about them.
8 | */
9 |
10 | /**
11 | * The main style class. Contains a unique id for the style, a size (which is
12 | * the same for cramped and uncramped version of a style), and a cramped flag.
13 | */
14 | class Style {
15 | id ;
16 | size ;
17 | cramped ;
18 |
19 | constructor(id , size , cramped ) {
20 | this.id = id;
21 | this.size = size;
22 | this.cramped = cramped;
23 | }
24 |
25 | /**
26 | * Get the style of a superscript given a base in the current style.
27 | */
28 | sup() {
29 | return styles[sup[this.id]];
30 | }
31 |
32 | /**
33 | * Get the style of a subscript given a base in the current style.
34 | */
35 | sub() {
36 | return styles[sub[this.id]];
37 | }
38 |
39 | /**
40 | * Get the style of a fraction numerator given the fraction in the current
41 | * style.
42 | */
43 | fracNum() {
44 | return styles[fracNum[this.id]];
45 | }
46 |
47 | /**
48 | * Get the style of a fraction denominator given the fraction in the current
49 | * style.
50 | */
51 | fracDen() {
52 | return styles[fracDen[this.id]];
53 | }
54 |
55 | /**
56 | * Get the cramped version of a style (in particular, cramping a cramped style
57 | * doesn't change the style).
58 | */
59 | cramp() {
60 | return styles[cramp[this.id]];
61 | }
62 |
63 | /**
64 | * Get a text or display version of this style.
65 | */
66 | text() {
67 | return styles[text[this.id]];
68 | }
69 |
70 | /**
71 | * Return true if this style is tightly spaced (scriptstyle/scriptscriptstyle)
72 | */
73 | isTight() {
74 | return this.size >= 2;
75 | }
76 | }
77 |
78 | // Export an interface for type checking, but don't expose the implementation.
79 | // This way, no more styles can be generated.
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | // IDs of the different styles
95 | const D = 0;
96 | const Dc = 1;
97 | const T = 2;
98 | const Tc = 3;
99 | const S = 4;
100 | const Sc = 5;
101 | const SS = 6;
102 | const SSc = 7;
103 |
104 | // Instances of the different styles
105 | const styles = [
106 | new Style(D, 0, false),
107 | new Style(Dc, 0, true),
108 | new Style(T, 1, false),
109 | new Style(Tc, 1, true),
110 | new Style(S, 2, false),
111 | new Style(Sc, 2, true),
112 | new Style(SS, 3, false),
113 | new Style(SSc, 3, true),
114 | ];
115 |
116 | // Lookup tables for switching from one style to another
117 | const sup = [S, Sc, S, Sc, SS, SSc, SS, SSc];
118 | const sub = [Sc, Sc, Sc, Sc, SSc, SSc, SSc, SSc];
119 | const fracNum = [T, Tc, S, Sc, SS, SSc, SS, SSc];
120 | const fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, SSc, SSc];
121 | const cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc];
122 | const text = [D, Dc, T, Tc, T, Tc, T, Tc];
123 |
124 | // We only export some of the styles.
125 | export default {
126 | DISPLAY: (styles[D] ),
127 | TEXT: (styles[T] ),
128 | SCRIPT: (styles[S] ),
129 | SCRIPTSCRIPT: (styles[SS] ),
130 | };
131 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/smash.js:
--------------------------------------------------------------------------------
1 | //
2 | // smash, with optional [tb], as in AMS
3 | import defineFunction from "../defineFunction";
4 | import buildCommon from "../buildCommon";
5 | import mathMLTree from "../mathMLTree";
6 | import {assertNodeType} from "../parseNode";
7 |
8 | import * as html from "../buildHTML";
9 | import * as mml from "../buildMathML";
10 |
11 | defineFunction({
12 | type: "smash",
13 | names: ["\\smash"],
14 | props: {
15 | numArgs: 1,
16 | numOptionalArgs: 1,
17 | allowedInText: true,
18 | },
19 | handler: ({parser}, args, optArgs) => {
20 | let smashHeight = false;
21 | let smashDepth = false;
22 | const tbArg = optArgs[0] && assertNodeType(optArgs[0], "ordgroup");
23 | if (tbArg) {
24 | // Optional [tb] argument is engaged.
25 | // ref: amsmath: \renewcommand{\smash}[1][tb]{%
26 | // def\mb@t{\ht}\def\mb@b{\dp}\def\mb@tb{\ht\z@\z@\dp}%
27 | let letter = "";
28 | for (let i = 0; i < tbArg.body.length; ++i) {
29 | const node = tbArg.body[i];
30 | // $FlowFixMe: Not every node type has a `text` property.
31 | letter = node.text;
32 | if (letter === "t") {
33 | smashHeight = true;
34 | } else if (letter === "b") {
35 | smashDepth = true;
36 | } else {
37 | smashHeight = false;
38 | smashDepth = false;
39 | break;
40 | }
41 | }
42 | } else {
43 | smashHeight = true;
44 | smashDepth = true;
45 | }
46 |
47 | const body = args[0];
48 | return {
49 | type: "smash",
50 | mode: parser.mode,
51 | body,
52 | smashHeight,
53 | smashDepth,
54 | };
55 | },
56 | htmlBuilder: (group, options) => {
57 | const node = buildCommon.makeSpan(
58 | [], [html.buildGroup(group.body, options)]);
59 |
60 | if (!group.smashHeight && !group.smashDepth) {
61 | return node;
62 | }
63 |
64 | if (group.smashHeight) {
65 | node.height = 0;
66 | // In order to influence makeVList, we have to reset the children.
67 | if (node.children) {
68 | for (let i = 0; i < node.children.length; i++) {
69 | node.children[i].height = 0;
70 | }
71 | }
72 | }
73 |
74 | if (group.smashDepth) {
75 | node.depth = 0;
76 | if (node.children) {
77 | for (let i = 0; i < node.children.length; i++) {
78 | node.children[i].depth = 0;
79 | }
80 | }
81 | }
82 |
83 | // At this point, we've reset the TeX-like height and depth values.
84 | // But the span still has an HTML line height.
85 | // makeVList applies "display: table-cell", which prevents the browser
86 | // from acting on that line height. So we'll call makeVList now.
87 |
88 | const smashedNode = buildCommon.makeVList({
89 | positionType: "firstBaseline",
90 | children: [{type: "elem", elem: node}],
91 | }, options);
92 |
93 | // For spacing, TeX treats \hphantom as a math group (same spacing as ord).
94 | return buildCommon.makeSpan(["mord"], [smashedNode], options);
95 | },
96 | mathmlBuilder: (group, options) => {
97 | const node = new mathMLTree.MathNode(
98 | "mpadded", [mml.buildGroup(group.body, options)]);
99 |
100 | if (group.smashHeight) {
101 | node.setAttribute("height", "0px");
102 | }
103 |
104 | if (group.smashDepth) {
105 | node.setAttribute("depth", "0px");
106 | }
107 |
108 | return node;
109 | },
110 | });
111 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/phantom.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction, {ordargument} from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 |
6 | import * as html from "../buildHTML";
7 | import * as mml from "../buildMathML";
8 |
9 | defineFunction({
10 | type: "phantom",
11 | names: ["\\phantom"],
12 | props: {
13 | numArgs: 1,
14 | allowedInText: true,
15 | },
16 | handler: ({parser}, args) => {
17 | const body = args[0];
18 | return {
19 | type: "phantom",
20 | mode: parser.mode,
21 | body: ordargument(body),
22 | };
23 | },
24 | htmlBuilder: (group, options) => {
25 | const elements = html.buildExpression(
26 | group.body,
27 | options.withPhantom(),
28 | false
29 | );
30 |
31 | // \phantom isn't supposed to affect the elements it contains.
32 | // See "color" for more details.
33 | return buildCommon.makeFragment(elements);
34 | },
35 | mathmlBuilder: (group, options) => {
36 | const inner = mml.buildExpression(group.body, options);
37 | return new mathMLTree.MathNode("mphantom", inner);
38 | },
39 | });
40 |
41 | defineFunction({
42 | type: "hphantom",
43 | names: ["\\hphantom"],
44 | props: {
45 | numArgs: 1,
46 | allowedInText: true,
47 | },
48 | handler: ({parser}, args) => {
49 | const body = args[0];
50 | return {
51 | type: "hphantom",
52 | mode: parser.mode,
53 | body,
54 | };
55 | },
56 | htmlBuilder: (group, options) => {
57 | let node = buildCommon.makeSpan(
58 | [], [html.buildGroup(group.body, options.withPhantom())]);
59 | node.height = 0;
60 | node.depth = 0;
61 | if (node.children) {
62 | for (let i = 0; i < node.children.length; i++) {
63 | node.children[i].height = 0;
64 | node.children[i].depth = 0;
65 | }
66 | }
67 |
68 | // See smash for comment re: use of makeVList
69 | node = buildCommon.makeVList({
70 | positionType: "firstBaseline",
71 | children: [{type: "elem", elem: node}],
72 | }, options);
73 |
74 | // For spacing, TeX treats \smash as a math group (same spacing as ord).
75 | return buildCommon.makeSpan(["mord"], [node], options);
76 | },
77 | mathmlBuilder: (group, options) => {
78 | const inner = mml.buildExpression(ordargument(group.body), options);
79 | const phantom = new mathMLTree.MathNode("mphantom", inner);
80 | const node = new mathMLTree.MathNode("mpadded", [phantom]);
81 | node.setAttribute("height", "0px");
82 | node.setAttribute("depth", "0px");
83 | return node;
84 | },
85 | });
86 |
87 | defineFunction({
88 | type: "vphantom",
89 | names: ["\\vphantom"],
90 | props: {
91 | numArgs: 1,
92 | allowedInText: true,
93 | },
94 | handler: ({parser}, args) => {
95 | const body = args[0];
96 | return {
97 | type: "vphantom",
98 | mode: parser.mode,
99 | body,
100 | };
101 | },
102 | htmlBuilder: (group, options) => {
103 | const inner = buildCommon.makeSpan(
104 | ["inner"],
105 | [html.buildGroup(group.body, options.withPhantom())]);
106 | const fix = buildCommon.makeSpan(["fix"], []);
107 | return buildCommon.makeSpan(
108 | ["mord", "rlap"], [inner, fix], options);
109 | },
110 | mathmlBuilder: (group, options) => {
111 | const inner = mml.buildExpression(ordargument(group.body), options);
112 | const phantom = new mathMLTree.MathNode("mphantom", inner);
113 | const node = new mathMLTree.MathNode("mpadded", [phantom]);
114 | node.setAttribute("width", "0px");
115 | return node;
116 | },
117 | });
118 |
--------------------------------------------------------------------------------
/lib/katex/src/defineEnvironment.js:
--------------------------------------------------------------------------------
1 | //
2 | import {_htmlGroupBuilders, _mathmlGroupBuilders} from "./defineFunction";
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | /**
11 | * The context contains the following properties:
12 | * - mode: current parsing mode.
13 | * - envName: the name of the environment, one of the listed names.
14 | * - parser: the parser object.
15 | */
16 |
17 |
18 |
19 |
20 |
21 |
22 | /**
23 | * - context: information and references provided by the parser
24 | * - args: an array of arguments passed to \begin{name}
25 | * - optArgs: an array of optional arguments passed to \begin{name}
26 | */
27 |
28 |
29 |
30 |
31 |
32 |
33 | /**
34 | * - numArgs: (default 0) The number of arguments after the \begin{name} function.
35 | * - argTypes: (optional) Just like for a function
36 | * - allowedInText: (default false) Whether or not the environment is allowed
37 | * inside text mode (not enforced yet).
38 | * - numOptionalArgs: (default 0) Just like for a function
39 | */
40 |
41 |
42 |
43 |
44 | /**
45 | * Final environment spec for use at parse time.
46 | * This is almost identical to `EnvDefSpec`, except it
47 | * 1. includes the function handler
48 | * 2. requires all arguments except argType
49 | * It is generated by `defineEnvironment()` below.
50 | */
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | /**
61 | * All registered environments.
62 | * `environments.js` exports this same dictionary again and makes it public.
63 | * `Parser.js` requires this dictionary via `environments.js`.
64 | */
65 | export const _environments = {};
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | export default function defineEnvironment ({
90 | type,
91 | names,
92 | props,
93 | handler,
94 | htmlBuilder,
95 | mathmlBuilder,
96 | } ) {
97 | // Set default values of environments.
98 | const data = {
99 | type,
100 | numArgs: props.numArgs || 0,
101 | allowedInText: false,
102 | numOptionalArgs: 0,
103 | handler,
104 | };
105 | for (let i = 0; i < names.length; ++i) {
106 | // TODO: The value type of _environments should be a type union of all
107 | // possible `EnvSpec<>` possibilities instead of `EnvSpec<*>`, which is
108 | // an existential type.
109 | _environments[names[i]] = data;
110 | }
111 | if (htmlBuilder) {
112 | _htmlGroupBuilders[type] = htmlBuilder;
113 | }
114 | if (mathmlBuilder) {
115 | _mathmlGroupBuilders[type] = mathmlBuilder;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/katex/src/defineMacro.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 | import {Token} from "./Token";
4 |
5 |
6 |
7 | /**
8 | * Provides context to macros defined by functions. Implemented by
9 | * MacroExpander.
10 | */
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | /** Macro tokens (in reverse order). */
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | /**
116 | * All registered global/built-in macros.
117 | * `macros.js` exports this same dictionary again and makes it public.
118 | * `Parser.js` requires this dictionary via `macros.js`.
119 | */
120 | export const _macros = {};
121 |
122 | // This function might one day accept an additional argument and do more things.
123 | export default function defineMacro(name , body ) {
124 | _macros[name] = body;
125 | }
126 |
--------------------------------------------------------------------------------
/lib/katex/src/utils.js:
--------------------------------------------------------------------------------
1 | //
2 | /**
3 | * This file contains a list of utility functions which are useful in other
4 | * files.
5 | */
6 |
7 |
8 |
9 | /**
10 | * Return whether an element is contained in a list
11 | */
12 | const contains = function (list , elem ) {
13 | return list.indexOf(elem) !== -1;
14 | };
15 |
16 | /**
17 | * Provide a default value if a setting is undefined
18 | * NOTE: Couldn't use `T` as the output type due to facebook/flow#5022.
19 | */
20 | const deflt = function (setting , defaultIfUndefined ) {
21 | return setting === undefined ? defaultIfUndefined : setting;
22 | };
23 |
24 | // hyphenate and escape adapted from Facebook's React under Apache 2 license
25 |
26 | const uppercase = /([A-Z])/g;
27 | const hyphenate = function(str ) {
28 | return str.replace(uppercase, "-$1").toLowerCase();
29 | };
30 |
31 | const ESCAPE_LOOKUP = {
32 | "&": "&",
33 | ">": ">",
34 | "<": "<",
35 | "\"": """,
36 | "'": "'",
37 | };
38 |
39 | const ESCAPE_REGEX = /[&><"']/g;
40 |
41 | /**
42 | * Escapes text to prevent scripting attacks.
43 | */
44 | function escape(text ) {
45 | return String(text).replace(ESCAPE_REGEX, match => ESCAPE_LOOKUP[match]);
46 | }
47 |
48 | /**
49 | * Sometimes we want to pull out the innermost element of a group. In most
50 | * cases, this will just be the group itself, but when ordgroups and colors have
51 | * a single element, we want to pull that out.
52 | */
53 | const getBaseElem = function(group ) {
54 | if (group.type === "ordgroup") {
55 | if (group.body.length === 1) {
56 | return getBaseElem(group.body[0]);
57 | } else {
58 | return group;
59 | }
60 | } else if (group.type === "color") {
61 | if (group.body.length === 1) {
62 | return getBaseElem(group.body[0]);
63 | } else {
64 | return group;
65 | }
66 | } else if (group.type === "font") {
67 | return getBaseElem(group.body);
68 | } else {
69 | return group;
70 | }
71 | };
72 |
73 | /**
74 | * TeXbook algorithms often reference "character boxes", which are simply groups
75 | * with a single character in them. To decide if something is a character box,
76 | * we find its innermost group, and see if it is a single character.
77 | */
78 | const isCharacterBox = function(group ) {
79 | const baseElem = getBaseElem(group);
80 |
81 | // These are all they types of groups which hold single characters
82 | return baseElem.type === "mathord" ||
83 | baseElem.type === "textord" ||
84 | baseElem.type === "atom";
85 | };
86 |
87 | export const assert = function (value ) {
88 | if (!value) {
89 | throw new Error('Expected non-null, but got ' + String(value));
90 | }
91 | return value;
92 | };
93 |
94 | /**
95 | * Return the protocol of a URL, or "_relative" if the URL does not specify a
96 | * protocol (and thus is relative), or `null` if URL has invalid protocol
97 | * (so should be outright rejected).
98 | */
99 | export const protocolFromUrl = function(url ) {
100 | // Check for possible leading protocol.
101 | // https://url.spec.whatwg.org/#url-parsing strips leading whitespace
102 | // (U+20) or C0 control (U+00-U+1F) characters.
103 | // eslint-disable-next-line no-control-regex
104 | const protocol = /^[\x00-\x20]*([^\\/#?]*?)(:|*58|*3a|&colon)/i
105 | .exec(url);
106 | if (!protocol) {
107 | return "_relative";
108 | }
109 | // Reject weird colons
110 | if (protocol[2] !== ":") {
111 | return null;
112 | }
113 | // Reject invalid characters in scheme according to
114 | // https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
115 | if (!/^[a-zA-Z][a-zA-Z0-9+\-.]*$/.test(protocol[1])) {
116 | return null;
117 | }
118 | // Lowercase the protocol
119 | return protocol[1].toLowerCase();
120 | };
121 |
122 | export default {
123 | contains,
124 | deflt,
125 | escape,
126 | hyphenate,
127 | getBaseElem,
128 | isCharacterBox,
129 | protocolFromUrl,
130 | };
131 |
--------------------------------------------------------------------------------
/lib/katex/src/unicodeScripts.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 | /*
4 | * This file defines the Unicode scripts and script families that we
5 | * support. To add new scripts or families, just add a new entry to the
6 | * scriptData array below. Adding scripts to the scriptData array allows
7 | * characters from that script to appear in \text{} environments.
8 | */
9 |
10 | /**
11 | * Each script or script family has a name and an array of blocks.
12 | * Each block is an array of two numbers which specify the start and
13 | * end points (inclusive) of a block of Unicode codepoints.
14 | */
15 |
16 |
17 |
18 |
19 |
20 | /**
21 | * Unicode block data for the families of scripts we support in \text{}.
22 | * Scripts only need to appear here if they do not have font metrics.
23 | */
24 | const scriptData = [
25 | {
26 | // Latin characters beyond the Latin-1 characters we have metrics for.
27 | // Needed for Czech, Hungarian and Turkish text, for example.
28 | name: 'latin',
29 | blocks: [
30 | [0x0100, 0x024f], // Latin Extended-A and Latin Extended-B
31 | [0x0300, 0x036f], // Combining Diacritical marks
32 | ],
33 | },
34 | {
35 | // The Cyrillic script used by Russian and related languages.
36 | // A Cyrillic subset used to be supported as explicitly defined
37 | // symbols in symbols.js
38 | name: 'cyrillic',
39 | blocks: [[0x0400, 0x04ff]],
40 | },
41 | {
42 | // Armenian
43 | name: 'armenian',
44 | blocks: [[0x0530, 0x058F]],
45 | },
46 | {
47 | // The Brahmic scripts of South and Southeast Asia
48 | // Devanagari (0900–097F)
49 | // Bengali (0980–09FF)
50 | // Gurmukhi (0A00–0A7F)
51 | // Gujarati (0A80–0AFF)
52 | // Oriya (0B00–0B7F)
53 | // Tamil (0B80–0BFF)
54 | // Telugu (0C00–0C7F)
55 | // Kannada (0C80–0CFF)
56 | // Malayalam (0D00–0D7F)
57 | // Sinhala (0D80–0DFF)
58 | // Thai (0E00–0E7F)
59 | // Lao (0E80–0EFF)
60 | // Tibetan (0F00–0FFF)
61 | // Myanmar (1000–109F)
62 | name: 'brahmic',
63 | blocks: [[0x0900, 0x109F]],
64 | },
65 | {
66 | name: 'georgian',
67 | blocks: [[0x10A0, 0x10ff]],
68 | },
69 | {
70 | // Chinese and Japanese.
71 | // The "k" in cjk is for Korean, but we've separated Korean out
72 | name: "cjk",
73 | blocks: [
74 | [0x3000, 0x30FF], // CJK symbols and punctuation, Hiragana, Katakana
75 | [0x4E00, 0x9FAF], // CJK ideograms
76 | [0xFF00, 0xFF60], // Fullwidth punctuation
77 | // TODO: add halfwidth Katakana and Romanji glyphs
78 | ],
79 | },
80 | {
81 | // Korean
82 | name: 'hangul',
83 | blocks: [[0xAC00, 0xD7AF]],
84 | },
85 | ];
86 |
87 | /**
88 | * Given a codepoint, return the name of the script or script family
89 | * it is from, or null if it is not part of a known block
90 | */
91 | export function scriptFromCodepoint(codepoint ) {
92 | for (let i = 0; i < scriptData.length; i++) {
93 | const script = scriptData[i];
94 | for (let i = 0; i < script.blocks.length; i++) {
95 | const block = script.blocks[i];
96 | if (codepoint >= block[0] && codepoint <= block[1]) {
97 | return script.name;
98 | }
99 | }
100 | }
101 | return null;
102 | }
103 |
104 | /**
105 | * A flattened version of all the supported blocks in a single array.
106 | * This is an optimization to make supportedCodepoint() fast.
107 | */
108 | const allBlocks = [];
109 | scriptData.forEach(s => s.blocks.forEach(b => allBlocks.push(...b)));
110 |
111 | /**
112 | * Given a codepoint, return true if it falls within one of the
113 | * scripts or script families defined above and false otherwise.
114 | *
115 | * Micro benchmarks shows that this is faster than
116 | * /[\u3000-\u30FF\u4E00-\u9FAF\uFF00-\uFF60\uAC00-\uD7AF\u0900-\u109F]/.test()
117 | * in Firefox, Chrome and Node.
118 | */
119 | export function supportedCodepoint(codepoint ) {
120 | for (let i = 0; i < allBlocks.length; i += 2) {
121 | if (codepoint >= allBlocks[i] && codepoint <= allBlocks[i + 1]) {
122 | return true;
123 | }
124 | }
125 | return false;
126 | }
127 |
--------------------------------------------------------------------------------
/lib/katex/src/units.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 | /**
4 | * This file does conversion between units. In particular, it provides
5 | * calculateSize to convert other units into ems.
6 | */
7 |
8 | import ParseError from "./ParseError";
9 | import Options from "./Options";
10 |
11 | // This table gives the number of TeX pts in one of each *absolute* TeX unit.
12 | // Thus, multiplying a length by this number converts the length from units
13 | // into pts. Dividing the result by ptPerEm gives the number of ems
14 | // *assuming* a font size of ptPerEm (normal size, normal style).
15 | const ptPerUnit = {
16 | // https://en.wikibooks.org/wiki/LaTeX/Lengths and
17 | // https://tex.stackexchange.com/a/8263
18 | "pt": 1, // TeX point
19 | "mm": 7227 / 2540, // millimeter
20 | "cm": 7227 / 254, // centimeter
21 | "in": 72.27, // inch
22 | "bp": 803 / 800, // big (PostScript) points
23 | "pc": 12, // pica
24 | "dd": 1238 / 1157, // didot
25 | "cc": 14856 / 1157, // cicero (12 didot)
26 | "nd": 685 / 642, // new didot
27 | "nc": 1370 / 107, // new cicero (12 new didot)
28 | "sp": 1 / 65536, // scaled point (TeX's internal smallest unit)
29 | // https://tex.stackexchange.com/a/41371
30 | "px": 803 / 800, // \pdfpxdimen defaults to 1 bp in pdfTeX and LuaTeX
31 | };
32 |
33 | // Dictionary of relative units, for fast validity testing.
34 | const relativeUnit = {
35 | "ex": true,
36 | "em": true,
37 | "mu": true,
38 | };
39 |
40 |
41 |
42 | /**
43 | * Determine whether the specified unit (either a string defining the unit
44 | * or a "size" parse node containing a unit field) is valid.
45 | */
46 | export const validUnit = function(unit ) {
47 | if (typeof unit !== "string") {
48 | unit = unit.unit;
49 | }
50 | return (unit in ptPerUnit || unit in relativeUnit || unit === "ex");
51 | };
52 |
53 | /*
54 | * Convert a "size" parse node (with numeric "number" and string "unit" fields,
55 | * as parsed by functions.js argType "size") into a CSS em value for the
56 | * current style/scale. `options` gives the current options.
57 | */
58 | export const calculateSize = function(
59 | sizeValue , options ) {
60 | let scale;
61 | if (sizeValue.unit in ptPerUnit) {
62 | // Absolute units
63 | scale = ptPerUnit[sizeValue.unit] // Convert unit to pt
64 | / options.fontMetrics().ptPerEm // Convert pt to CSS em
65 | / options.sizeMultiplier; // Unscale to make absolute units
66 | } else if (sizeValue.unit === "mu") {
67 | // `mu` units scale with scriptstyle/scriptscriptstyle.
68 | scale = options.fontMetrics().cssEmPerMu;
69 | } else {
70 | // Other relative units always refer to the *textstyle* font
71 | // in the current size.
72 | let unitOptions;
73 | if (options.style.isTight()) {
74 | // isTight() means current style is script/scriptscript.
75 | unitOptions = options.havingStyle(options.style.text());
76 | } else {
77 | unitOptions = options;
78 | }
79 | // TODO: In TeX these units are relative to the quad of the current
80 | // *text* font, e.g. cmr10. KaTeX instead uses values from the
81 | // comparably-sized *Computer Modern symbol* font. At 10pt, these
82 | // match. At 7pt and 5pt, they differ: cmr7=1.138894, cmsy7=1.170641;
83 | // cmr5=1.361133, cmsy5=1.472241. Consider $\scriptsize a\kern1emb$.
84 | // TeX \showlists shows a kern of 1.13889 * fontsize;
85 | // KaTeX shows a kern of 1.171 * fontsize.
86 | if (sizeValue.unit === "ex") {
87 | scale = unitOptions.fontMetrics().xHeight;
88 | } else if (sizeValue.unit === "em") {
89 | scale = unitOptions.fontMetrics().quad;
90 | } else {
91 | throw new ParseError("Invalid unit: '" + sizeValue.unit + "'");
92 | }
93 | if (unitOptions !== options) {
94 | scale *= unitOptions.sizeMultiplier / options.sizeMultiplier;
95 | }
96 | }
97 | return Math.min(sizeValue.number * scale, options.maxSize);
98 | };
99 |
100 | /**
101 | * Round `n` to 4 decimal places, or to the nearest 1/10,000th em. See
102 | * https://github.com/KaTeX/KaTeX/pull/2460.
103 | */
104 | export const makeEm = function(n ) {
105 | return +n.toFixed(4) + "em";
106 | };
107 |
--------------------------------------------------------------------------------
/src/core/src/main.rs:
--------------------------------------------------------------------------------
1 | // Mainly for debug purposes
2 |
3 | use comemo::Prehashed;
4 | use comemo::Track;
5 | use typst;
6 | use core::convert;
7 | use typst::World;
8 |
9 | struct FakeWorld {
10 | library: Prehashed,
11 | }
12 |
13 | impl FakeWorld {
14 | fn new() -> Self {
15 | FakeWorld {
16 | library: Prehashed::new(typst::Library::build()),
17 | }
18 | }
19 | }
20 |
21 | impl World for FakeWorld {
22 | fn library(&self) -> &Prehashed {
23 | &self.library
24 | }
25 | fn book(&self) -> &Prehashed {
26 | unimplemented!();
27 | }
28 | fn file(&self, id: typst_syntax::FileId) -> typst::diag::FileResult {
29 | unimplemented!();
30 | }
31 | fn font(&self, index: usize) -> Option {
32 | unimplemented!();
33 | }
34 | fn main(&self) -> typst_syntax::Source {
35 | unimplemented!();
36 | }
37 | fn packages(&self) -> &[(typst_syntax::PackageSpec,Option)] {
38 | unimplemented!();
39 | }
40 | fn source(&self, id: typst_syntax::FileId) -> typst::diag::FileResult {
41 | unimplemented!();
42 | }
43 | fn today(&self, offset: Option) -> Option {
44 | unimplemented!();
45 | }
46 | }
47 |
48 | fn eval(world: &dyn World, string: &str) -> typst::foundations::Content {
49 | // Make engine
50 | let introspector = typst::introspection::Introspector::default();
51 | let mut locator = typst::introspection::Locator::default();
52 | let mut tracer = typst::eval::Tracer::default();
53 |
54 | let engine = typst::engine::Engine {
55 | world: world.track(),
56 | introspector: introspector.track(),
57 | route: typst::engine::Route::default(),
58 | locator: &mut locator,
59 | tracer: tracer.track_mut(),
60 | };
61 |
62 | let result = typst::eval::eval_string(
63 | world.track(),
64 | string,
65 | typst::syntax::Span::detached(),
66 | typst::eval::EvalMode::Math,
67 | world.library().math.scope().clone()
68 | ).unwrap();
69 | match result {
70 | typst::foundations::Value::Content(content) => content,
71 | _ => panic!(),
72 | }
73 | }
74 |
75 | // Equations tested:
76 | // A = pi r^2
77 | // "area" = pi dot "radius"^2
78 | // cal(A) := { x in RR | x "is natural" }
79 | // x < y => x gt.eq.not y
80 | // sum_(k=0)^n k &= 1 + ... + n \ &= (n(n+1)) / 2
81 | // frac(a^2, 2)
82 | // vec(1, 2, delim: "[")
83 | // mat(1, 2; 3, 4)
84 | // lim_x = op("lim", limits: #true)_x
85 | // (3x + y) / 7 &= 9 && "given" \ 3x + y &= 63 & "multiply by 7" \ 3x &= 63 - y && "subtract y" \ x &= 21 - y/3 & "divide by 3"
86 | // sum_(i=0)^n a_i = 2^(1+i)
87 | // 1/2 < (x+1)/2
88 | // ((x+1)) / 2 = frac(a, b)
89 | // tan x = (sin x)/(cos x)
90 | // op("custom", limits: #true)_(n->oo) n
91 | // bb(b)
92 | // bb(N) = NN
93 | // f: NN -> RR
94 | // vec(a, b, c) dot vec(1, 2, 3) = a + 2b + 3c
95 |
96 | // Works partially
97 | // attach(Pi, t: alpha, b: beta, tl: 1, tr: 2+3, bl: 4+5, br: 6)
98 | // lr(]sum_(x=1)^n] x, size: #50%)
99 | // mat(1, 2, ..., 10; 2, 2, ..., 10; dots.v, dots.v, dots.down, dots.v; 10, 10, ..., 10)
100 | // upright(A) != A
101 |
102 | // Does not work
103 | // grave(a) = accent(a, `)
104 | // arrow(a) = accent(a, arrow)
105 | // tilde(a) = accent(a, \u{0303})
106 | // scripts(sum)_1^2 != sum_1^2
107 | // limits(A)_1^2 != A_1^2
108 | // (a dot b dot cancel(x)) / cancel(x)
109 | // f(x, y) := cases(1 "if" (x dot y)/2 <= 0, 2 "if" x "is even", 3 "if" x in NN, 4 "else")
110 | // Class: https://typst.app/docs/reference/math/class/
111 | // abs((x + y) / 2)
112 | // { x mid(|) sum_(i=1)^n w_i|f_i (x)| < 1 }
113 | // norm(x/2)
114 | // abs(x/2)
115 | // floor(x/2)
116 | // ceil(x/2)
117 | // round(x/2)
118 | // sqrt(3 - 2 sqrt(2)) = sqrt(2) - 1
119 | // root(3, x)
120 | // sum_i x_i/2 = inline(sum_i x_i/2)
121 | // sum_i x_i/2 = display(sum_i x_i/2)
122 | // sum_i x_i/2 = script(sum_i x_i/2)
123 | // sum_i x_i/2 = sscript(sum_i x_i/2)
124 | // upright(A) != A
125 | // underline(1 + 2 + ... + 5)
126 | // overline(1 + 2 + ... + 5)
127 | // underbrace(1 + 2 + ... + 5, "numbers")
128 | // overbrace(1 + 2 + ... + 5, "numbers")
129 | // underbracket(1 + 2 + ... + 5, "numbers")
130 | // overbracket(1 + 2 + ... + 5, "numbers")
131 | // sans(A B C)
132 | // frak(P)
133 | // mono(x + y = z)
134 |
135 | pub fn main() {
136 | // Try to construct a MathContext object.
137 | let mut world = FakeWorld::new();
138 | let content = eval(&world, "||x||");
139 | let math: &typst::math::EquationElem = content.to::().unwrap();
140 | println!("{:#?}", math);
141 | println!("{:#?}", convert(&content));
142 | }
143 |
--------------------------------------------------------------------------------
/scripts/symbol_gen.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const vm = require('vm');
3 | const parser = require('@babel/parser');
4 | const traverse = require('@babel/traverse').default;
5 | const generator = require('@babel/generator').default;
6 |
7 | const data = fs.readFileSync('scripts/dist/symbols.js', 'utf8');
8 | const ast = parser.parse(data);
9 |
10 | global.rustLines = []
11 | global.atoms = ['bin', 'close', 'inner', 'open', 'punct', 'rel']
12 | global.nodeMap = {
13 | 'mathord': 'MathOrd',
14 | 'textord': 'TextOrd',
15 | }
16 |
17 | global.toPascalCase = function(str) {
18 | return str.replace(/(^\w|-\w|_\w)/g, (group) => group.toUpperCase()
19 | .replace('-', '')
20 | .replace('_', ''));
21 | }
22 |
23 | global.getRustGroup = function(group) {
24 | if (atoms.includes(group)) {
25 | return `Group::Atom(AtomGroup::${toPascalCase(group)})`;
26 | } else {
27 | if (group === 'mathord')
28 | group = 'MathOrd';
29 | else if (group === 'textord')
30 | group = 'TextOrd';
31 | return `Group::NonAtom(NonAtomGroup::${toPascalCase(group)})`;
32 | }
33 | }
34 |
35 | global.combinations = new Object();
36 |
37 | function newDefineSymbol(mode, font, group, replace, name, ...args) {
38 | let rustMode = mode === 'math' ? 'Mode::Math' : 'Mode::Text';
39 | let rustFont = font === 'main' ? 'Font::Main' : 'Font::Ams';
40 | let rustGroup = getRustGroup(group);
41 | if (replace === null)
42 | return
43 | let rustName = replace === '\\' ? '\\\\' : replace;
44 | if (replace === '0') {
45 | console.log('0');
46 | }
47 | if (replace === 'A') {
48 | console.log(mode, font, group);
49 | }
50 | let rustLine = `if mode == ${rustMode} && name == '${rustName}' { return Symbol::new(${rustMode}, ${rustFont}, ${rustGroup}, '${rustName}'); }`;
51 | global.combinations[JSON.stringify([rustMode, rustName])] = rustLine;
52 | // global.combinations.add(JSON.stringify([rustMode, rustName]));
53 | // rustLines.push(rustLine);
54 | }
55 |
56 | console.log('Im here');
57 |
58 | traverse(ast, {
59 | enter(path) {
60 | if (path.node.type === 'FunctionDeclaration' && path.node.id.name === 'defineSymbol') {
61 | path.replaceWith(
62 | parser.parse(newDefineSymbol.toString()).program.body[0]
63 | )
64 | }
65 | if (path.node.type === 'CallExpression' && path.node.callee.name === 'defineSymbol') {
66 | path.node.callee.name = 'newDefineSymbol';
67 | }
68 | }
69 | })
70 |
71 | let { code } = generator(ast);
72 | vm.runInThisContext(code);
73 |
74 | const blockStart = '//// --- AUTO GENERATED CODE --- ////'
75 | const blockEnd = '//// --------------------------- ////'
76 |
77 | const rustSource = fs.readFileSync('src/core/src/katex/symbol.rs', 'utf8');
78 |
79 | let lines = rustSource.split('\n');
80 | const blockStartIndex = lines.findIndex(line => line.includes(blockStart));
81 | const blockEndIndex = lines.findIndex(line => line.includes(blockEnd));
82 |
83 | let indentationSpace = lines[blockStartIndex].slice(0, lines[blockStartIndex].indexOf(blockStart));
84 | global.rustLines = Object.values(global.combinations);
85 | let rustLines = global.rustLines.map(s => indentationSpace + s);
86 | lines = lines.slice(0, blockStartIndex + 1).concat(rustLines).concat(lines.slice(blockEndIndex));
87 |
88 | fs.writeFileSync('src/core/src/katex/symbol.rs', lines.join('\n'), 'utf8');
89 |
90 |
91 |
92 | // console.log(data.slice(0, data.indexOf(blockStart)) + blockStart + '\n' + global.rustLines.slice(0, 3).join('\n') + '\n' + blockEnd + data.slice(data.indexOf(blockEnd) + blockEnd.length))
93 |
94 | // const result = data.replace(start)
95 | // console.log(data)
96 |
97 |
98 | // fs.readFile('src/core/src/katex/symbol.rs', 'utf8', function(err, data) {
99 | // if (err) {
100 | // return console.log(err);
101 | // }
102 |
103 | // let lines = data.split('\n');
104 | // const blockStartIndex = lines.findIndex(line => line.includes(blockStart))
105 | // const blockEndIndex = lines.findIndex(line => line.includes(blockEnd))
106 |
107 | // let indentationSpace = lines[blockStartIndex].slice(0, lines[blockStartIndex].indexOf(blockStart))
108 | // let rustLines = global.rustLines.map(s => indentationSpace + s).slice(0, 3)
109 | // lines = lines.slice(0, blockStartIndex + 1).concat(rustLines).concat(lines.slice(blockEndIndex))
110 | // fs.writeFileSync()
111 |
112 |
113 | // // console.log(data.slice(0, data.indexOf(blockStart)) + blockStart + '\n' + global.rustLines.slice(0, 3).join('\n') + '\n' + blockEnd + data.slice(data.indexOf(blockEnd) + blockEnd.length))
114 |
115 | // // const result = data.replace(start)
116 | // // console.log(data)
117 | // })
118 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/sqrt.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 | import delimiter from "../delimiter";
6 | import Style from "../Style";
7 | import {makeEm} from "../units";
8 |
9 | import * as html from "../buildHTML";
10 | import * as mml from "../buildMathML";
11 |
12 | defineFunction({
13 | type: "sqrt",
14 | names: ["\\sqrt"],
15 | props: {
16 | numArgs: 1,
17 | numOptionalArgs: 1,
18 | },
19 | handler({parser}, args, optArgs) {
20 | const index = optArgs[0];
21 | const body = args[0];
22 | return {
23 | type: "sqrt",
24 | mode: parser.mode,
25 | body,
26 | index,
27 | };
28 | },
29 | htmlBuilder(group, options) {
30 | // Square roots are handled in the TeXbook pg. 443, Rule 11.
31 |
32 | // First, we do the same steps as in overline to build the inner group
33 | // and line
34 | let inner = html.buildGroup(group.body, options.havingCrampedStyle());
35 | if (inner.height === 0) {
36 | // Render a small surd.
37 | inner.height = options.fontMetrics().xHeight;
38 | }
39 |
40 | // Some groups can return document fragments. Handle those by wrapping
41 | // them in a span.
42 | inner = buildCommon.wrapFragment(inner, options);
43 |
44 | // Calculate the minimum size for the \surd delimiter
45 | const metrics = options.fontMetrics();
46 | const theta = metrics.defaultRuleThickness;
47 |
48 | let phi = theta;
49 | if (options.style.id < Style.TEXT.id) {
50 | phi = options.fontMetrics().xHeight;
51 | }
52 |
53 | // Calculate the clearance between the body and line
54 | let lineClearance = theta + phi / 4;
55 |
56 | const minDelimiterHeight = (inner.height + inner.depth +
57 | lineClearance + theta);
58 |
59 | // Create a sqrt SVG of the required minimum size
60 | const {span: img, ruleWidth, advanceWidth} =
61 | delimiter.sqrtImage(minDelimiterHeight, options);
62 |
63 | const delimDepth = img.height - ruleWidth;
64 |
65 | // Adjust the clearance based on the delimiter size
66 | if (delimDepth > inner.height + inner.depth + lineClearance) {
67 | lineClearance =
68 | (lineClearance + delimDepth - inner.height - inner.depth) / 2;
69 | }
70 |
71 | // Shift the sqrt image
72 | const imgShift = img.height - inner.height - lineClearance - ruleWidth;
73 |
74 | inner.style.paddingLeft = makeEm(advanceWidth);
75 |
76 | // Overlay the image and the argument.
77 | const body = buildCommon.makeVList({
78 | positionType: "firstBaseline",
79 | children: [
80 | {type: "elem", elem: inner, wrapperClasses: ["svg-align"]},
81 | {type: "kern", size: -(inner.height + imgShift)},
82 | {type: "elem", elem: img},
83 | {type: "kern", size: ruleWidth},
84 | ],
85 | }, options);
86 |
87 | if (!group.index) {
88 | return buildCommon.makeSpan(["mord", "sqrt"], [body], options);
89 | } else {
90 | // Handle the optional root index
91 |
92 | // The index is always in scriptscript style
93 | const newOptions = options.havingStyle(Style.SCRIPTSCRIPT);
94 | const rootm = html.buildGroup(group.index, newOptions, options);
95 |
96 | // The amount the index is shifted by. This is taken from the TeX
97 | // source, in the definition of `\r@@t`.
98 | const toShift = 0.6 * (body.height - body.depth);
99 |
100 | // Build a VList with the superscript shifted up correctly
101 | const rootVList = buildCommon.makeVList({
102 | positionType: "shift",
103 | positionData: -toShift,
104 | children: [{type: "elem", elem: rootm}],
105 | }, options);
106 | // Add a class surrounding it so we can add on the appropriate
107 | // kerning
108 | const rootVListWrap = buildCommon.makeSpan(["root"], [rootVList]);
109 |
110 | return buildCommon.makeSpan(["mord", "sqrt"],
111 | [rootVListWrap, body], options);
112 | }
113 | },
114 | mathmlBuilder(group, options) {
115 | const {body, index} = group;
116 | return index ?
117 | new mathMLTree.MathNode(
118 | "mroot", [
119 | mml.buildGroup(body, options),
120 | mml.buildGroup(index, options),
121 | ]) :
122 | new mathMLTree.MathNode(
123 | "msqrt", [mml.buildGroup(body, options)]);
124 | },
125 | });
126 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/utils/assembleSupSub.js:
--------------------------------------------------------------------------------
1 | //
2 | import buildCommon from "../../buildCommon";
3 | import * as html from "../../buildHTML";
4 | import utils from "../../utils";
5 |
6 |
7 |
8 |
9 | import {makeEm} from "../../units";
10 |
11 | // For an operator with limits, assemble the base, sup, and sub into a span.
12 |
13 | export const assembleSupSub = (
14 | base ,
15 | supGroup ,
16 | subGroup ,
17 | options ,
18 | style ,
19 | slant ,
20 | baseShift ,
21 | ) => {
22 | base = buildCommon.makeSpan([], [base]);
23 | const subIsSingleCharacter = subGroup && utils.isCharacterBox(subGroup);
24 | let sub;
25 | let sup;
26 | // We manually have to handle the superscripts and subscripts. This,
27 | // aside from the kern calculations, is copied from supsub.
28 | if (supGroup) {
29 | const elem = html.buildGroup(
30 | supGroup, options.havingStyle(style.sup()), options);
31 |
32 | sup = {
33 | elem,
34 | kern: Math.max(
35 | options.fontMetrics().bigOpSpacing1,
36 | options.fontMetrics().bigOpSpacing3 - elem.depth),
37 | };
38 | }
39 |
40 | if (subGroup) {
41 | const elem = html.buildGroup(
42 | subGroup, options.havingStyle(style.sub()), options);
43 |
44 | sub = {
45 | elem,
46 | kern: Math.max(
47 | options.fontMetrics().bigOpSpacing2,
48 | options.fontMetrics().bigOpSpacing4 - elem.height),
49 | };
50 | }
51 |
52 | // Build the final group as a vlist of the possible subscript, base,
53 | // and possible superscript.
54 | let finalGroup;
55 | if (sup && sub) {
56 | const bottom = options.fontMetrics().bigOpSpacing5 +
57 | sub.elem.height + sub.elem.depth +
58 | sub.kern +
59 | base.depth + baseShift;
60 |
61 | finalGroup = buildCommon.makeVList({
62 | positionType: "bottom",
63 | positionData: bottom,
64 | children: [
65 | {type: "kern", size: options.fontMetrics().bigOpSpacing5},
66 | {type: "elem", elem: sub.elem, marginLeft: makeEm(-slant)},
67 | {type: "kern", size: sub.kern},
68 | {type: "elem", elem: base},
69 | {type: "kern", size: sup.kern},
70 | {type: "elem", elem: sup.elem, marginLeft: makeEm(slant)},
71 | {type: "kern", size: options.fontMetrics().bigOpSpacing5},
72 | ],
73 | }, options);
74 | } else if (sub) {
75 | const top = base.height - baseShift;
76 |
77 | // Shift the limits by the slant of the symbol. Note
78 | // that we are supposed to shift the limits by 1/2 of the slant,
79 | // but since we are centering the limits adding a full slant of
80 | // margin will shift by 1/2 that.
81 | finalGroup = buildCommon.makeVList({
82 | positionType: "top",
83 | positionData: top,
84 | children: [
85 | {type: "kern", size: options.fontMetrics().bigOpSpacing5},
86 | {type: "elem", elem: sub.elem, marginLeft: makeEm(-slant)},
87 | {type: "kern", size: sub.kern},
88 | {type: "elem", elem: base},
89 | ],
90 | }, options);
91 | } else if (sup) {
92 | const bottom = base.depth + baseShift;
93 |
94 | finalGroup = buildCommon.makeVList({
95 | positionType: "bottom",
96 | positionData: bottom,
97 | children: [
98 | {type: "elem", elem: base},
99 | {type: "kern", size: sup.kern},
100 | {type: "elem", elem: sup.elem, marginLeft: makeEm(slant)},
101 | {type: "kern", size: options.fontMetrics().bigOpSpacing5},
102 | ],
103 | }, options);
104 | } else {
105 | // This case probably shouldn't occur (this would mean the
106 | // supsub was sending us a group with no superscript or
107 | // subscript) but be safe.
108 | return base;
109 | }
110 |
111 | const parts = [finalGroup];
112 | if (sub && slant !== 0 && !subIsSingleCharacter) {
113 | // A negative margin-left was applied to the lower limit.
114 | // Avoid an overlap by placing a spacer on the left on the group.
115 | const spacer = buildCommon.makeSpan(["mspace"], [], options);
116 | spacer.style.marginRight = makeEm(slant);
117 | parts.unshift(spacer);
118 | }
119 | return buildCommon.makeSpan(["mop", "op-limits"], parts, options);
120 | };
121 |
--------------------------------------------------------------------------------
/lib/katex/src/Namespace.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 | /**
4 | * A `Namespace` refers to a space of nameable things like macros or lengths,
5 | * which can be `set` either globally or local to a nested group, using an
6 | * undo stack similar to how TeX implements this functionality.
7 | * Performance-wise, `get` and local `set` take constant time, while global
8 | * `set` takes time proportional to the depth of group nesting.
9 | */
10 |
11 | import ParseError from "./ParseError";
12 |
13 |
14 |
15 | export default class Namespace {
16 | current ;
17 | builtins ;
18 | undefStack ;
19 |
20 | /**
21 | * Both arguments are optional. The first argument is an object of
22 | * built-in mappings which never change. The second argument is an object
23 | * of initial (global-level) mappings, which will constantly change
24 | * according to any global/top-level `set`s done.
25 | */
26 | constructor(builtins = {},
27 | globalMacros = {}) {
28 | this.current = globalMacros;
29 | this.builtins = builtins;
30 | this.undefStack = [];
31 | }
32 |
33 | /**
34 | * Start a new nested group, affecting future local `set`s.
35 | */
36 | beginGroup() {
37 | this.undefStack.push({});
38 | }
39 |
40 | /**
41 | * End current nested group, restoring values before the group began.
42 | */
43 | endGroup() {
44 | if (this.undefStack.length === 0) {
45 | throw new ParseError("Unbalanced namespace destruction: attempt " +
46 | "to pop global namespace; please report this as a bug");
47 | }
48 | const undefs = this.undefStack.pop();
49 | for (const undef in undefs) {
50 | if (undefs.hasOwnProperty(undef)) {
51 | if (undefs[undef] == null) {
52 | delete this.current[undef];
53 | } else {
54 | this.current[undef] = undefs[undef];
55 | }
56 | }
57 | }
58 | }
59 |
60 | /**
61 | * Ends all currently nested groups (if any), restoring values before the
62 | * groups began. Useful in case of an error in the middle of parsing.
63 | */
64 | endGroups() {
65 | while (this.undefStack.length > 0) {
66 | this.endGroup();
67 | }
68 | }
69 |
70 | /**
71 | * Detect whether `name` has a definition. Equivalent to
72 | * `get(name) != null`.
73 | */
74 | has(name ) {
75 | return this.current.hasOwnProperty(name) ||
76 | this.builtins.hasOwnProperty(name);
77 | }
78 |
79 | /**
80 | * Get the current value of a name, or `undefined` if there is no value.
81 | *
82 | * Note: Do not use `if (namespace.get(...))` to detect whether a macro
83 | * is defined, as the definition may be the empty string which evaluates
84 | * to `false` in JavaScript. Use `if (namespace.get(...) != null)` or
85 | * `if (namespace.has(...))`.
86 | */
87 | get(name ) {
88 | if (this.current.hasOwnProperty(name)) {
89 | return this.current[name];
90 | } else {
91 | return this.builtins[name];
92 | }
93 | }
94 |
95 | /**
96 | * Set the current value of a name, and optionally set it globally too.
97 | * Local set() sets the current value and (when appropriate) adds an undo
98 | * operation to the undo stack. Global set() may change the undo
99 | * operation at every level, so takes time linear in their number.
100 | * A value of undefined means to delete existing definitions.
101 | */
102 | set(name , value , global = false) {
103 | if (global) {
104 | // Global set is equivalent to setting in all groups. Simulate this
105 | // by destroying any undos currently scheduled for this name,
106 | // and adding an undo with the *new* value (in case it later gets
107 | // locally reset within this environment).
108 | for (let i = 0; i < this.undefStack.length; i++) {
109 | delete this.undefStack[i][name];
110 | }
111 | if (this.undefStack.length > 0) {
112 | this.undefStack[this.undefStack.length - 1][name] = value;
113 | }
114 | } else {
115 | // Undo this set at end of this group (possibly to `undefined`),
116 | // unless an undo is already in place, in which case that older
117 | // value is the correct one.
118 | const top = this.undefStack[this.undefStack.length - 1];
119 | if (top && !top.hasOwnProperty(name)) {
120 | top[name] = this.current[name];
121 | }
122 | }
123 | if (value == null) {
124 | delete this.current[name];
125 | } else {
126 | this.current[name] = value;
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/lib/katex/src/wide-character.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 | /**
4 | * This file provides support for Unicode range U+1D400 to U+1D7FF,
5 | * Mathematical Alphanumeric Symbols.
6 | *
7 | * Function wideCharacterFont takes a wide character as input and returns
8 | * the font information necessary to render it properly.
9 | */
10 |
11 |
12 | import ParseError from "./ParseError";
13 |
14 | /**
15 | * Data below is from https://www.unicode.org/charts/PDF/U1D400.pdf
16 | * That document sorts characters into groups by font type, say bold or italic.
17 | *
18 | * In the arrays below, each subarray consists three elements:
19 | * * The CSS class of that group when in math mode.
20 | * * The CSS class of that group when in text mode.
21 | * * The font name, so that KaTeX can get font metrics.
22 | */
23 |
24 | const wideLatinLetterData = [
25 | ["mathbf", "textbf", "Main-Bold"], // A-Z bold upright
26 | ["mathbf", "textbf", "Main-Bold"], // a-z bold upright
27 |
28 | ["mathnormal", "textit", "Math-Italic"], // A-Z italic
29 | ["mathnormal", "textit", "Math-Italic"], // a-z italic
30 |
31 | ["boldsymbol", "boldsymbol", "Main-BoldItalic"], // A-Z bold italic
32 | ["boldsymbol", "boldsymbol", "Main-BoldItalic"], // a-z bold italic
33 |
34 | // Map fancy A-Z letters to script, not calligraphic.
35 | // This aligns with unicode-math and math fonts (except Cambria Math).
36 | ["mathscr", "textscr", "Script-Regular"], // A-Z script
37 | ["", "", ""], // a-z script. No font
38 |
39 | ["", "", ""], // A-Z bold script. No font
40 | ["", "", ""], // a-z bold script. No font
41 |
42 | ["mathfrak", "textfrak", "Fraktur-Regular"], // A-Z Fraktur
43 | ["mathfrak", "textfrak", "Fraktur-Regular"], // a-z Fraktur
44 |
45 | ["mathbb", "textbb", "AMS-Regular"], // A-Z double-struck
46 | ["mathbb", "textbb", "AMS-Regular"], // k double-struck
47 |
48 | // Note that we are using a bold font, but font metrics for regular Fraktur.
49 | ["mathboldfrak", "textboldfrak", "Fraktur-Regular"], // A-Z bold Fraktur
50 | ["mathboldfrak", "textboldfrak", "Fraktur-Regular"], // a-z bold Fraktur
51 |
52 | ["mathsf", "textsf", "SansSerif-Regular"], // A-Z sans-serif
53 | ["mathsf", "textsf", "SansSerif-Regular"], // a-z sans-serif
54 |
55 | ["mathboldsf", "textboldsf", "SansSerif-Bold"], // A-Z bold sans-serif
56 | ["mathboldsf", "textboldsf", "SansSerif-Bold"], // a-z bold sans-serif
57 |
58 | ["mathitsf", "textitsf", "SansSerif-Italic"], // A-Z italic sans-serif
59 | ["mathitsf", "textitsf", "SansSerif-Italic"], // a-z italic sans-serif
60 |
61 | ["", "", ""], // A-Z bold italic sans. No font
62 | ["", "", ""], // a-z bold italic sans. No font
63 |
64 | ["mathtt", "texttt", "Typewriter-Regular"], // A-Z monospace
65 | ["mathtt", "texttt", "Typewriter-Regular"], // a-z monospace
66 | ];
67 |
68 | const wideNumeralData = [
69 | ["mathbf", "textbf", "Main-Bold"], // 0-9 bold
70 | ["", "", ""], // 0-9 double-struck. No KaTeX font.
71 | ["mathsf", "textsf", "SansSerif-Regular"], // 0-9 sans-serif
72 | ["mathboldsf", "textboldsf", "SansSerif-Bold"], // 0-9 bold sans-serif
73 | ["mathtt", "texttt", "Typewriter-Regular"], // 0-9 monospace
74 | ];
75 |
76 | export const wideCharacterFont = function(
77 | wideChar ,
78 | mode ,
79 | ) {
80 |
81 | // IE doesn't support codePointAt(). So work with the surrogate pair.
82 | const H = wideChar.charCodeAt(0); // high surrogate
83 | const L = wideChar.charCodeAt(1); // low surrogate
84 | const codePoint = ((H - 0xD800) * 0x400) + (L - 0xDC00) + 0x10000;
85 |
86 | const j = mode === "math" ? 0 : 1; // column index for CSS class.
87 |
88 | if (0x1D400 <= codePoint && codePoint < 0x1D6A4) {
89 | // wideLatinLetterData contains exactly 26 chars on each row.
90 | // So we can calculate the relevant row. No traverse necessary.
91 | const i = Math.floor((codePoint - 0x1D400) / 26);
92 | return [wideLatinLetterData[i][2], wideLatinLetterData[i][j]];
93 |
94 | } else if (0x1D7CE <= codePoint && codePoint <= 0x1D7FF) {
95 | // Numerals, ten per row.
96 | const i = Math.floor((codePoint - 0x1D7CE) / 10);
97 | return [wideNumeralData[i][2], wideNumeralData[i][j]];
98 |
99 | } else if (codePoint === 0x1D6A5 || codePoint === 0x1D6A6) {
100 | // dotless i or j
101 | return [wideLatinLetterData[0][2], wideLatinLetterData[0][j]];
102 |
103 | } else if (0x1D6A6 < codePoint && codePoint < 0x1D7CE) {
104 | // Greek letters. Not supported, yet.
105 | return ["", ""];
106 |
107 | } else {
108 | // We don't support any wide characters outside 1D400–1D7FF.
109 | throw new ParseError("Unsupported character: " + wideChar);
110 | }
111 | };
112 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/horizBrace.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 | import buildCommon from "../buildCommon";
4 | import mathMLTree from "../mathMLTree";
5 | import stretchy from "../stretchy";
6 | import Style from "../Style";
7 | import {assertNodeType} from "../parseNode";
8 |
9 | import * as html from "../buildHTML";
10 | import * as mml from "../buildMathML";
11 |
12 |
13 |
14 |
15 | // NOTE: Unlike most `htmlBuilder`s, this one handles not only "horizBrace", but
16 | // also "supsub" since an over/underbrace can affect super/subscripting.
17 | export const htmlBuilder = (grp, options) => {
18 | const style = options.style;
19 |
20 | // Pull out the `ParseNode<"horizBrace">` if `grp` is a "supsub" node.
21 | let supSubGroup;
22 | let group ;
23 | if (grp.type === "supsub") {
24 | // Ref: LaTeX source2e: }}}}\limits}
25 | // i.e. LaTeX treats the brace similar to an op and passes it
26 | // with \limits, so we need to assign supsub style.
27 | supSubGroup = grp.sup ?
28 | html.buildGroup(grp.sup, options.havingStyle(style.sup()), options) :
29 | html.buildGroup(grp.sub, options.havingStyle(style.sub()), options);
30 | group = assertNodeType(grp.base, "horizBrace");
31 | } else {
32 | group = assertNodeType(grp, "horizBrace");
33 | }
34 |
35 | // Build the base group
36 | const body = html.buildGroup(
37 | group.base, options.havingBaseStyle(Style.DISPLAY));
38 |
39 | // Create the stretchy element
40 | const braceBody = stretchy.svgSpan(group, options);
41 |
42 | // Generate the vlist, with the appropriate kerns ┏━━━━━━━━┓
43 | // This first vlist contains the content and the brace: equation
44 | let vlist;
45 | if (group.isOver) {
46 | vlist = buildCommon.makeVList({
47 | positionType: "firstBaseline",
48 | children: [
49 | {type: "elem", elem: body},
50 | {type: "kern", size: 0.1},
51 | {type: "elem", elem: braceBody},
52 | ],
53 | }, options);
54 | // $FlowFixMe: Replace this with passing "svg-align" into makeVList.
55 | vlist.children[0].children[0].children[1].classes.push("svg-align");
56 | } else {
57 | vlist = buildCommon.makeVList({
58 | positionType: "bottom",
59 | positionData: body.depth + 0.1 + braceBody.height,
60 | children: [
61 | {type: "elem", elem: braceBody},
62 | {type: "kern", size: 0.1},
63 | {type: "elem", elem: body},
64 | ],
65 | }, options);
66 | // $FlowFixMe: Replace this with passing "svg-align" into makeVList.
67 | vlist.children[0].children[0].children[0].classes.push("svg-align");
68 | }
69 |
70 | if (supSubGroup) {
71 | // To write the supsub, wrap the first vlist in another vlist:
72 | // They can't all go in the same vlist, because the note might be
73 | // wider than the equation. We want the equation to control the
74 | // brace width.
75 |
76 | // note long note long note
77 | // ┏━━━━━━━━┓ or ┏━━━┓ not ┏━━━━━━━━━┓
78 | // equation eqn eqn
79 |
80 | const vSpan = buildCommon.makeSpan(
81 | ["mord", (group.isOver ? "mover" : "munder")],
82 | [vlist], options);
83 |
84 | if (group.isOver) {
85 | vlist = buildCommon.makeVList({
86 | positionType: "firstBaseline",
87 | children: [
88 | {type: "elem", elem: vSpan},
89 | {type: "kern", size: 0.2},
90 | {type: "elem", elem: supSubGroup},
91 | ],
92 | }, options);
93 | } else {
94 | vlist = buildCommon.makeVList({
95 | positionType: "bottom",
96 | positionData: vSpan.depth + 0.2 + supSubGroup.height +
97 | supSubGroup.depth,
98 | children: [
99 | {type: "elem", elem: supSubGroup},
100 | {type: "kern", size: 0.2},
101 | {type: "elem", elem: vSpan},
102 | ],
103 | }, options);
104 | }
105 | }
106 |
107 | return buildCommon.makeSpan(
108 | ["mord", (group.isOver ? "mover" : "munder")], [vlist], options);
109 | };
110 |
111 | const mathmlBuilder = (group, options) => {
112 | const accentNode = stretchy.mathMLnode(group.label);
113 | return new mathMLTree.MathNode(
114 | (group.isOver ? "mover" : "munder"),
115 | [mml.buildGroup(group.base, options), accentNode]
116 | );
117 | };
118 |
119 | // Horizontal stretchy braces
120 | defineFunction({
121 | type: "horizBrace",
122 | names: ["\\overbrace", "\\underbrace"],
123 | props: {
124 | numArgs: 1,
125 | },
126 | handler({parser, funcName}, args) {
127 | return {
128 | type: "horizBrace",
129 | mode: parser.mode,
130 | label: funcName,
131 | isOver: /^\\over/.test(funcName),
132 | base: args[0],
133 | };
134 | },
135 | htmlBuilder,
136 | mathmlBuilder,
137 | });
138 |
--------------------------------------------------------------------------------
/lib/katex/src/Lexer.js:
--------------------------------------------------------------------------------
1 | //
2 | /**
3 | * The Lexer class handles tokenizing the input in various ways. Since our
4 | * parser expects us to be able to backtrack, the lexer allows lexing from any
5 | * given starting point.
6 | *
7 | * Its main exposed function is the `lex` function, which takes a position to
8 | * lex from and a type of token to lex. It defers to the appropriate `_innerLex`
9 | * function.
10 | *
11 | * The various `_innerLex` functions perform the actual lexing of different
12 | * kinds.
13 | */
14 |
15 | import ParseError from "./ParseError";
16 | import SourceLocation from "./SourceLocation";
17 | import {Token} from "./Token";
18 |
19 |
20 |
21 |
22 | /* The following tokenRegex
23 | * - matches typical whitespace (but not NBSP etc.) using its first group
24 | * - does not match any control character \x00-\x1f except whitespace
25 | * - does not match a bare backslash
26 | * - matches any ASCII character except those just mentioned
27 | * - does not match the BMP private use area \uE000-\uF8FF
28 | * - does not match bare surrogate code units
29 | * - matches any BMP character except for those just described
30 | * - matches any valid Unicode surrogate pair
31 | * - matches a backslash followed by one or more whitespace characters
32 | * - matches a backslash followed by one or more letters then whitespace
33 | * - matches a backslash followed by any BMP character
34 | * Capturing groups:
35 | * [1] regular whitespace
36 | * [2] backslash followed by whitespace
37 | * [3] anything else, which may include:
38 | * [4] left character of \verb*
39 | * [5] left character of \verb
40 | * [6] backslash followed by word, excluding any trailing whitespace
41 | * Just because the Lexer matches something doesn't mean it's valid input:
42 | * If there is no matching function or symbol definition, the Parser will
43 | * still reject the input.
44 | */
45 | const spaceRegexString = "[ \r\n\t]";
46 | const controlWordRegexString = "\\\\[a-zA-Z@]+";
47 | const controlSymbolRegexString = "\\\\[^\uD800-\uDFFF]";
48 | const controlWordWhitespaceRegexString =
49 | `(${controlWordRegexString})${spaceRegexString}*`;
50 | const controlSpaceRegexString = "\\\\(\n|[ \r\t]+\n?)[ \r\t]*";
51 | const combiningDiacriticalMarkString = "[\u0300-\u036f]";
52 | export const combiningDiacriticalMarksEndRegex =
53 | new RegExp(`${combiningDiacriticalMarkString}+$`);
54 | const tokenRegexString = `(${spaceRegexString}+)|` + // whitespace
55 | `${controlSpaceRegexString}|` + // \whitespace
56 | "([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + // single codepoint
57 | `${combiningDiacriticalMarkString}*` + // ...plus accents
58 | "|[\uD800-\uDBFF][\uDC00-\uDFFF]" + // surrogate pair
59 | `${combiningDiacriticalMarkString}*` + // ...plus accents
60 | "|\\\\verb\\*([^]).*?\\4" + // \verb*
61 | "|\\\\verb([^*a-zA-Z]).*?\\5" + // \verb unstarred
62 | `|${controlWordWhitespaceRegexString}` + // \macroName + spaces
63 | `|${controlSymbolRegexString})`; // \\, \', etc.
64 |
65 | /** Main Lexer class */
66 | export default class Lexer {
67 | input ;
68 | settings ;
69 | tokenRegex ;
70 | // Category codes. The lexer only supports comment characters (14) for now.
71 | // MacroExpander additionally distinguishes active (13).
72 | catcodes ;
73 |
74 | constructor(input , settings ) {
75 | // Separate accents from characters
76 | this.input = input;
77 | this.settings = settings;
78 | this.tokenRegex = new RegExp(tokenRegexString, 'g');
79 | this.catcodes = {
80 | "%": 14, // comment character
81 | "~": 13, // active character
82 | };
83 | }
84 |
85 | setCatcode(char , code ) {
86 | this.catcodes[char] = code;
87 | }
88 |
89 | /**
90 | * This function lexes a single token.
91 | */
92 | lex() {
93 | const input = this.input;
94 | const pos = this.tokenRegex.lastIndex;
95 | if (pos === input.length) {
96 | return new Token("EOF", new SourceLocation(this, pos, pos));
97 | }
98 | const match = this.tokenRegex.exec(input);
99 | if (match === null || match.index !== pos) {
100 | throw new ParseError(
101 | `Unexpected character: '${input[pos]}'`,
102 | new Token(input[pos], new SourceLocation(this, pos, pos + 1)));
103 | }
104 | const text = match[6] || match[3] || (match[2] ? "\\ " : " ");
105 |
106 | if (this.catcodes[text] === 14) { // comment character
107 | const nlIndex = input.indexOf('\n', this.tokenRegex.lastIndex);
108 | if (nlIndex === -1) {
109 | this.tokenRegex.lastIndex = input.length; // EOF
110 | this.settings.reportNonstrict("commentAtEnd",
111 | "% comment has no terminating newline; LaTeX would " +
112 | "fail because of commenting the end of math mode (e.g. $)");
113 | } else {
114 | this.tokenRegex.lastIndex = nlIndex + 1;
115 | }
116 | return this.lex();
117 | }
118 |
119 | return new Token(text, new SourceLocation(this, pos,
120 | this.tokenRegex.lastIndex));
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/katex/src/functions/includegraphics.js:
--------------------------------------------------------------------------------
1 | //
2 | import defineFunction from "../defineFunction";
3 |
4 | import {calculateSize, validUnit, makeEm} from "../units";
5 | import ParseError from "../ParseError";
6 | import {Img} from "../domTree";
7 | import mathMLTree from "../mathMLTree";
8 | import {assertNodeType} from "../parseNode";
9 |
10 |
11 | const sizeData = function(str ) {
12 | if (/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(str)) {
13 | // str is a number with no unit specified.
14 | // default unit is bp, per graphix package.
15 | return {number: +str, unit: "bp"};
16 | } else {
17 | const match = (/([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/).exec(str);
18 | if (!match) {
19 | throw new ParseError("Invalid size: '" + str
20 | + "' in \\includegraphics");
21 | }
22 | const data = {
23 | number: +(match[1] + match[2]), // sign + magnitude, cast to number
24 | unit: match[3],
25 | };
26 | if (!validUnit(data)) {
27 | throw new ParseError("Invalid unit: '" + data.unit
28 | + "' in \\includegraphics.");
29 | }
30 | return data;
31 | }
32 | };
33 |
34 | defineFunction({
35 | type: "includegraphics",
36 | names: ["\\includegraphics"],
37 | props: {
38 | numArgs: 1,
39 | numOptionalArgs: 1,
40 | argTypes: ["raw", "url"],
41 | allowedInText: false,
42 | },
43 | handler: ({parser}, args, optArgs) => {
44 | let width = {number: 0, unit: "em"};
45 | let height = {number: 0.9, unit: "em"}; // sorta character sized.
46 | let totalheight = {number: 0, unit: "em"};
47 | let alt = "";
48 |
49 | if (optArgs[0]) {
50 | const attributeStr = assertNodeType(optArgs[0], "raw").string;
51 |
52 | // Parser.js does not parse key/value pairs. We get a string.
53 | const attributes = attributeStr.split(",");
54 | for (let i = 0; i < attributes.length; i++) {
55 | const keyVal = attributes[i].split("=");
56 | if (keyVal.length === 2) {
57 | const str = keyVal[1].trim();
58 | switch (keyVal[0].trim()) {
59 | case "alt":
60 | alt = str;
61 | break;
62 | case "width":
63 | width = sizeData(str);
64 | break;
65 | case "height":
66 | height = sizeData(str);
67 | break;
68 | case "totalheight":
69 | totalheight = sizeData(str);
70 | break;
71 | default:
72 | throw new ParseError("Invalid key: '" + keyVal[0] +
73 | "' in \\includegraphics.");
74 | }
75 | }
76 | }
77 | }
78 |
79 | const src = assertNodeType(args[0], "url").url;
80 |
81 | if (alt === "") {
82 | // No alt given. Use the file name. Strip away the path.
83 | alt = src;
84 | alt = alt.replace(/^.*[\\/]/, '');
85 | alt = alt.substring(0, alt.lastIndexOf('.'));
86 | }
87 |
88 | if (!parser.settings.isTrusted({
89 | command: "\\includegraphics",
90 | url: src,
91 | })) {
92 | return parser.formatUnsupportedCmd("\\includegraphics");
93 | }
94 |
95 | return {
96 | type: "includegraphics",
97 | mode: parser.mode,
98 | alt: alt,
99 | width: width,
100 | height: height,
101 | totalheight: totalheight,
102 | src: src,
103 | };
104 | },
105 | htmlBuilder: (group, options) => {
106 | const height = calculateSize(group.height, options);
107 | let depth = 0;
108 |
109 | if (group.totalheight.number > 0) {
110 | depth = calculateSize(group.totalheight, options) - height;
111 | }
112 |
113 | let width = 0;
114 | if (group.width.number > 0) {
115 | width = calculateSize(group.width, options);
116 | }
117 |
118 | const style = {height: makeEm(height + depth)};
119 | if (width > 0) {
120 | style.width = makeEm(width);
121 | }
122 | if (depth > 0) {
123 | style.verticalAlign = makeEm(-depth);
124 | }
125 |
126 | const node = new Img(group.src, group.alt, style);
127 | node.height = height;
128 | node.depth = depth;
129 |
130 | return node;
131 | },
132 | mathmlBuilder: (group, options) => {
133 | const node = new mathMLTree.MathNode("mglyph", []);
134 | node.setAttribute("alt", group.alt);
135 |
136 | const height = calculateSize(group.height, options);
137 | let depth = 0;
138 | if (group.totalheight.number > 0) {
139 | depth = calculateSize(group.totalheight, options) - height;
140 | node.setAttribute("valign", makeEm(-depth));
141 | }
142 | node.setAttribute("height", makeEm(height + depth));
143 |
144 | if (group.width.number > 0) {
145 | const width = calculateSize(group.width, options);
146 | node.setAttribute("width", makeEm(width));
147 | }
148 | node.setAttribute("src", group.src);
149 | return node;
150 | },
151 | });
152 |
--------------------------------------------------------------------------------
/lib/katex/contrib/auto-render/auto-render.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console:0 */
2 |
3 | import katex from "katex";
4 | import splitAtDelimiters from "./splitAtDelimiters";
5 |
6 | /* Note: optionsCopy is mutated by this method. If it is ever exposed in the
7 | * API, we should copy it before mutating.
8 | */
9 | const renderMathInText = function(text, optionsCopy) {
10 | const data = splitAtDelimiters(text, optionsCopy.delimiters);
11 | if (data.length === 1 && data[0].type === 'text') {
12 | // There is no formula in the text.
13 | // Let's return null which means there is no need to replace
14 | // the current text node with a new one.
15 | return null;
16 | }
17 |
18 | const fragment = document.createDocumentFragment();
19 |
20 | for (let i = 0; i < data.length; i++) {
21 | if (data[i].type === "text") {
22 | fragment.appendChild(document.createTextNode(data[i].data));
23 | } else {
24 | const span = document.createElement("span");
25 | let math = data[i].data;
26 | // Override any display mode defined in the settings with that
27 | // defined by the text itself
28 | optionsCopy.displayMode = data[i].display;
29 | try {
30 | if (optionsCopy.preProcess) {
31 | math = optionsCopy.preProcess(math);
32 | }
33 | katex.render(math, span, optionsCopy);
34 | } catch (e) {
35 | if (!(e instanceof katex.ParseError)) {
36 | throw e;
37 | }
38 | optionsCopy.errorCallback(
39 | "KaTeX auto-render: Failed to parse `" + data[i].data +
40 | "` with ",
41 | e
42 | );
43 | fragment.appendChild(document.createTextNode(data[i].rawData));
44 | continue;
45 | }
46 | fragment.appendChild(span);
47 | }
48 | }
49 |
50 | return fragment;
51 | };
52 |
53 | const renderElem = function(elem, optionsCopy) {
54 | for (let i = 0; i < elem.childNodes.length; i++) {
55 | const childNode = elem.childNodes[i];
56 | if (childNode.nodeType === 3) {
57 | // Text node
58 | // Concatenate all sibling text nodes.
59 | // Webkit browsers split very large text nodes into smaller ones,
60 | // so the delimiters may be split across different nodes.
61 | let textContentConcat = childNode.textContent;
62 | let sibling = childNode.nextSibling;
63 | let nSiblings = 0;
64 | while (sibling && (sibling.nodeType === Node.TEXT_NODE)) {
65 | textContentConcat += sibling.textContent;
66 | sibling = sibling.nextSibling;
67 | nSiblings++;
68 | }
69 | const frag = renderMathInText(textContentConcat, optionsCopy);
70 | if (frag) {
71 | // Remove extra text nodes
72 | for (let j = 0; j < nSiblings; j++) {
73 | childNode.nextSibling.remove();
74 | }
75 | i += frag.childNodes.length - 1;
76 | elem.replaceChild(frag, childNode);
77 | } else {
78 | // If the concatenated text does not contain math
79 | // the siblings will not either
80 | i += nSiblings;
81 | }
82 | } else if (childNode.nodeType === 1) {
83 | // Element node
84 | const className = ' ' + childNode.className + ' ';
85 | const shouldRender = optionsCopy.ignoredTags.indexOf(
86 | childNode.nodeName.toLowerCase()) === -1 &&
87 | optionsCopy.ignoredClasses.every(
88 | x => className.indexOf(' ' + x + ' ') === -1);
89 |
90 | if (shouldRender) {
91 | renderElem(childNode, optionsCopy);
92 | }
93 | }
94 | // Otherwise, it's something else, and ignore it.
95 | }
96 | };
97 |
98 | const renderMathInElement = function(elem, options) {
99 | if (!elem) {
100 | throw new Error("No element provided to render");
101 | }
102 |
103 | const optionsCopy = {};
104 |
105 | // Object.assign(optionsCopy, option)
106 | for (const option in options) {
107 | if (options.hasOwnProperty(option)) {
108 | optionsCopy[option] = options[option];
109 | }
110 | }
111 |
112 | // default options
113 | optionsCopy.delimiters = optionsCopy.delimiters || [
114 | {left: "$$", right: "$$", display: true},
115 | {left: "\\(", right: "\\)", display: false},
116 | // LaTeX uses $…$, but it ruins the display of normal `$` in text:
117 | // {left: "$", right: "$", display: false},
118 | // $ must come after $$
119 |
120 | // Render AMS environments even if outside $$…$$ delimiters.
121 | {left: "\\begin{equation}", right: "\\end{equation}", display: true},
122 | {left: "\\begin{align}", right: "\\end{align}", display: true},
123 | {left: "\\begin{alignat}", right: "\\end{alignat}", display: true},
124 | {left: "\\begin{gather}", right: "\\end{gather}", display: true},
125 | {left: "\\begin{CD}", right: "\\end{CD}", display: true},
126 |
127 | {left: "\\[", right: "\\]", display: true},
128 | ];
129 | optionsCopy.ignoredTags = optionsCopy.ignoredTags || [
130 | "script", "noscript", "style", "textarea", "pre", "code", "option",
131 | ];
132 | optionsCopy.ignoredClasses = optionsCopy.ignoredClasses || [];
133 | optionsCopy.errorCallback = optionsCopy.errorCallback || console.error;
134 |
135 | // Enable sharing of global macros defined via `\gdef` between different
136 | // math elements within a single call to `renderMathInElement`.
137 | optionsCopy.macros = optionsCopy.macros || {};
138 |
139 | renderElem(elem, optionsCopy);
140 | };
141 |
142 | export default renderMathInElement;
143 |
--------------------------------------------------------------------------------