Skip to content

Commit

Permalink
feat: added combining accents, rationalized trig functions
Browse files Browse the repository at this point in the history
  • Loading branch information
arnog committed Jan 23, 2025
1 parent a2e4b35 commit f05d7e1
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 51 deletions.
24 changes: 21 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,27 @@

### Issues Resolved

- Generate only "standard" (i.e. available in amsmath package) trigonometric
functions. Use `\operatorname{}` for the others. Use `arc-` prefix for
hyperbolic functions.
- Generate only standard trigonometric functions, i.e. those available in the
`amsmath` package. Use `\operatorname{}` for the others. The standard commands
are:

- `\arccos`
- `\arcsin`
- `\arctan`
- `\arg`
- `\cos`
- `\cosh`
- `\cot`
- `\coth`
- `\csc`
- `\sec`
- `\sin`
- `\sinh`
- `\tan`
- `\tanh`

- Added support for `\dddot` and `\ddddot` commands.

- **#2132**, **#2548** Improved handling of multi-line mathfields. To use a
multi-line mathfield, include a multi-line environment:
- `\displaylines{}`: single column of left-aligned equations
Expand Down
2 changes: 1 addition & 1 deletion css/core.less
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@
width: 0;
}

.ML__accent-vec {
.ML__accent-combining-char {
position: relative;
left: 0.24em;
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { build } from 'esbuild';
import less from '@arnog/esbuild-plugin-less';

import pkg from '../package.json' assert { type: 'json' };
import pkg from '../package.json' with { type: 'json' };

process.env.BUILD = process.env.BUILD || 'development';
const PRODUCTION = process.env.BUILD.toLowerCase() === 'production';
Expand Down
75 changes: 75 additions & 0 deletions scripts/csp-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// Local server to test CSP headers
//
const http = require('http');
const fs = require('fs');
const path = require('path');

// Directory where your esbuild output is located
const servedir = path.join(__dirname, '../');

// Generate a random nonce
const nonce = 'a=base-64=encoded=nonce';

// Content Security Policy header
const cspHeader = `default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com; style-src 'self' 'unsafe-inline'; img-src 'unsafe-inline'`;

// Create an HTTP server
const server = http.createServer((req, res) => {
// Construct the file path
let filePath = path.join(servedir, req.url === '/' ? 'dist/smoke/' : req.url);
if (filePath.endsWith('/')) filePath += 'index.html';

// Check if the file exists
fs.exists(filePath, (exists) => {
if (exists) {
// Read and serve the file
fs.readFile(filePath, (err, content) => {
if (err) {
res.writeHead(500);
res.end('Server Error');
} else {
// Set the appropriate content type
const ext = path.extname(filePath);
const contentType = getContentType(ext);
res.writeHead(200, {
'Content-Type': contentType,
'Content-Security-Policy': cspHeader,
});
res.end(content, 'utf-8');
}
});
} else {
// If the file doesn't exist, return 404
res.writeHead(404);
res.end(`${filePath} not found`);
}
});
});

// Function to determine the content type based on file extension
function getContentType(ext) {
switch (ext) {
case '.html':
return 'text/html';
case '.js':
case '.mjs':
return 'application/javascript';
case '.css':
return 'text/css';
case '.json':
return 'application/json';
case '.png':
return 'image/png';
case '.jpg':
return 'image/jpeg';
default:
return 'application/octet-stream';
}
}

// Start the server
const PORT = 8080;
server.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}/`);
});
12 changes: 9 additions & 3 deletions src/atoms/accent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,18 @@ export class AccentAtom extends Atom {
// Remove the italic correction of the accent, because it only serves to
// shift the accent over to a place we don't want.
accent.italic = 0;
// The \vec character that the fonts use is a combining character, and

// Some of the accents are combining characters, and
// thus shows up much too far to the left. To account for this, we add a
// specific class which shifts the accent over to where we want it.
const vecClass = this.accent === 0x20d7 ? ' ML__accent-vec' : '';
const correctionClass =
this.accent === 0x20d7 ||
this.accent === 0x20db ||
this.accent === 0x20dc
? ' ML__accent-combining-char'
: '';
accentBox = new Box(new Box(accent), {
classes: 'ML__accent-body' + vecClass,
classes: 'ML__accent-body' + correctionClass,
});
}

Expand Down
56 changes: 28 additions & 28 deletions src/common/stylesheet.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
// @ts-ignore-error
import MATHFIELD_STYLESHEET from '../../css/mathfield.less';
import MATHFIELD_STYLESHEET from '../../css/mathfield.less' assert { type: 'css' };

// @ts-ignore-error
import CORE_STYLESHEET from '../../css/core.less';
import CORE_STYLESHEET from '../../css/core.less' assert { type: 'css' };

// @ts-ignore-error
import ENVIRONMENT_POPOVER_STYLESHEET from '../../css/environment-popover.less';
import ENVIRONMENT_POPOVER_STYLESHEET from '../../css/environment-popover.less' assert { type: 'css' };

// @ts-ignore-error
import SUGGESTION_POPOVER_STYLESHEET from '../../css/suggestion-popover.less';
import SUGGESTION_POPOVER_STYLESHEET from '../../css/suggestion-popover.less' assert { type: 'css' };

// @ts-ignore-error
import KEYSTROKE_CAPTION_STYLESHEET from '../../css/keystroke-caption.less';
import KEYSTROKE_CAPTION_STYLESHEET from '../../css/keystroke-caption.less' assert { type: 'css' };

// @ts-ignore-error
import VIRTUAL_KEYBOARD_STYLESHEET from '../../css/virtual-keyboard.less' assert { type: 'css' };

import UI_STYLESHEET from '../ui/style.less' assert { type: 'css' };
Expand Down Expand Up @@ -91,7 +85,6 @@ export function getStylesheet(id: StylesheetId): CSSStyleSheet {

gStylesheets[id] = new CSSStyleSheet();

// @ts-ignore
gStylesheets[id]!.replaceSync(getStylesheetContent(id));

return gStylesheets[id]!;
Expand All @@ -100,22 +93,30 @@ export function getStylesheet(id: StylesheetId): CSSStyleSheet {
let gInjectedStylesheets: Partial<Record<StylesheetId, number>>;

export function injectStylesheet(id: StylesheetId): void {
if (!('adoptedStyleSheets' in document)) {
if (window.document.getElementById(`mathlive-style-${id}`)) return;
const styleNode = window.document.createElement('style');
styleNode.id = `mathlive-style-${id}`;
styleNode.append(window.document.createTextNode(getStylesheetContent(id)));
window.document.head.appendChild(styleNode);
return;
}
try {
if (!('adoptedStyleSheets' in document)) {
if (window.document.getElementById(`mathlive-style-${id}`)) return;
const styleNode = window.document.createElement('style');
styleNode.id = `mathlive-style-${id}`;
styleNode.append(
window.document.createTextNode(getStylesheetContent(id))
);
window.document.head.appendChild(styleNode);
return;
}

if (!gInjectedStylesheets) gInjectedStylesheets = {};
if ((gInjectedStylesheets[id] ?? 0) !== 0) gInjectedStylesheets[id]! += 1;
else {
const stylesheet = getStylesheet(id);
// @ts-ignore
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
gInjectedStylesheets[id] = 1;
if (!gInjectedStylesheets) gInjectedStylesheets = {};
if ((gInjectedStylesheets[id] ?? 0) !== 0) gInjectedStylesheets[id]! += 1;
else {
const stylesheet = getStylesheet(id);
document.adoptedStyleSheets = [
...document.adoptedStyleSheets,
stylesheet,
];
gInjectedStylesheets[id] = 1;
}
} catch (error) {
console.error('Error injecting stylesheet', id, error);
}
}

Expand All @@ -127,7 +128,6 @@ export function releaseStylesheet(id: StylesheetId): void {
gInjectedStylesheets[id]! -= 1;
if (gInjectedStylesheets[id]! <= 0) {
const stylesheet = gStylesheets[id]!;
// @ts-ignore
document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
(x) => x !== stylesheet
);
Expand Down
2 changes: 1 addition & 1 deletion src/editor-mathfield/mode-editor-math.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-new */

import type { Expression } from '@cortex-js/compute-engine/dist/types/math-json/math-json-format';
import type { Expression } from '@cortex-js/compute-engine/dist/types/math-json';

import type { InsertOptions, Offset, OutputFormat } from '../public/core-types';

Expand Down
25 changes: 14 additions & 11 deletions src/editor/shortcuts-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,24 @@ export const INLINE_SHORTCUTS: InlineShortcutDefinitions = {
// Although we support rendering of non-standard commands (i.e. \arsinh)
// we avoid generating them when using the shortcuts.

'sin': '\\sin',
'cos': '\\cos',
'tan': '\\tan',

'arccos': '\\arccos',
'arcsin': '\\arcsin',
'arctan': '\\arctan',
'arctg': '\\operatorname{arctg}',
'arcsec': '\\operatornbame{arcsec}',
'arccsc': '\\operatornbame{arccsc}',
'arcsec': '\\operatorname{arcsec}',
'arccsc': '\\operatorname{arccsc}',

'arsinh': '\\operatorname{arsinh}',
'arccosh': '\\operatorname{arccosh}',
'arcosh': '\\operatorname{arcosh}',
'artanh': '\\operatorname{artanh}',
'arctanh': '\\operatorname{arctanh}',
'arcsech': '\\operatorname{arcsech}',
'arccsch': '\\operatorname{arccsch}',
'arg': '\\arg',
'ch': '\\operatorname{ch}',
'cosec': '\\operatorname{cosec}',
'cosh': '\\cosh',
Expand All @@ -101,16 +106,14 @@ export const INLINE_SHORTCUTS: InlineShortcutDefinitions = {
'ctg': '\\operatorname{ctg}',
'cth': '\\operatorname{cth}',
'sec': '\\sec',
'sech': '\\operatorname{sech}',
'sinh': '\\sinh',
'sh': '\\operatorname{sh}',
'tanh': '\\tanh',
'tg': '\\operatorname{tg}',
'th': '\\operatorname{th}',

'sin': '\\sin',
'cos': '\\cos',
'tan': '\\tan',

'arg': '\\arg',
'lg': '\\lg',
'lb': '\\operatorname{lb}', // not in amsmath
'log': '\\log',
Expand Down Expand Up @@ -160,10 +163,10 @@ export const INLINE_SHORTCUTS: InlineShortcutDefinitions = {

// Sets
'NN': '\\mathbb{N}', // Natural numbers
'ZZ': '\\Z', // Integers
'QQ': '\\Q', // Rational numbers
'RR': '\\R', // Real numbers
'CC': '\\C', // Complex numbers
'ZZ': '\\mathbb{Z}', // Integers
'QQ': '\\mathbb{Q}', // Rational numbers
'RR': '\\mathbb{R}', // Real numbers
'CC': '\\mathbb{C}', // Complex numbers

// Operators
'xx': '\\times',
Expand Down
2 changes: 2 additions & 0 deletions src/latex-commands/accents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const ACCENTS = {
grave: 0x02cb,
dot: 0x02d9,
ddot: 0x00a8,
dddot: 0x20db,
ddddot: 0x20dc,
mathring: 0x02da,
tilde: 0x007e,
bar: 0x02c9,
Expand Down
2 changes: 2 additions & 0 deletions src/latex-commands/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ defineSymbols(
);

// St Mary's Road symbols
// https://mirrors.mit.edu/CTAN/macros/latex/contrib/stmaryrd/stmaryrd.pdf
// https://mirrors.rit.edu/CTAN/fonts/stmaryrd/stmaryrd.pdf
defineSymbols([
['\\mapsfrom', 0x21a4, 'mrel'],
['\\Mapsfrom', 0x2906, 'mrel'],
Expand Down
4 changes: 2 additions & 2 deletions src/public/mathfield-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ export class MathfieldElement extends HTMLElement implements Mathfield {
static set decimalSeparator(value: ',' | '.') {
this._decimalSeparator = value;
if (this._computeEngine) {
this._computeEngine.latexOptions.decimalMarker =
this._computeEngine.decimalSeparator =
this.decimalSeparator === ',' ? '{,}' : '.';
}
}
Expand Down Expand Up @@ -1068,7 +1068,7 @@ export class MathfieldElement extends HTMLElement implements Mathfield {
this._computeEngine = new ComputeEngineCtor();

if (this._computeEngine && this.decimalSeparator === ',')
this._computeEngine.latexOptions.decimalMarker = '{,}';
this._computeEngine.decimalSeparator = '{,}';
}
return this._computeEngine ?? null;
}
Expand Down
3 changes: 2 additions & 1 deletion test/smoke/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ <h1>Smoke Test</h1>
</ul>
</header>
<main>
<math-field id="mf">\begin{align}a + 1 & b + 234 \\ c + 1234 & d + 12345\end{align}</math-field>
<math-field id="mf">\ddot{x}=\dddot{x}=\ddddot{x}=\vec{x}</math-field>
<!-- <math-field id="mf">\begin{align}a + 1 & b + 234 \\ c + 1234 & d + 12345\end{align}</math-field> -->

<!-- <math-field id="mf">a+\begin{matrix}[lc]x+1&2\\4&a+b+c+1234\end{matrix}+\alpha</math-field> -->

Expand Down

0 comments on commit f05d7e1

Please sign in to comment.