1
0

add initial marp implementation with sample content and build configuration

This commit is contained in:
2025-09-13 18:13:22 +02:00
parent dcacc9b409
commit e5f219507f
10319 changed files with 1402023 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
import { ColorPicker, StringColor } from './color_picker.js';
import { Highlighter } from './highlighter.js';
export interface Highlight {
node: HTMLElement;
opacity?: string;
background?: string;
foreground?: string;
box?: HTMLElement;
position?: string;
}
export declare abstract class AbstractHighlighter implements Highlighter {
counter: number;
protected ATTR: string;
protected color: ColorPicker;
protected mactionName: string;
private currentHighlights;
protected abstract highlightNode(node: HTMLElement): Highlight;
protected abstract unhighlightNode(highlight: Highlight): void;
highlight(nodes: HTMLElement[]): void;
highlightAll(node: HTMLElement): void;
unhighlight(): void;
unhighlightAll(): void;
setColor(color: ColorPicker): void;
colorString(): StringColor;
addEvents(node: HTMLElement, events: {
[key: string]: EventListener;
}): void;
getMactionNodes(node: HTMLElement): HTMLElement[];
isMactionNode(node: Element): boolean;
isHighlighted(node: HTMLElement): boolean;
setHighlighted(node: HTMLElement): void;
unsetHighlighted(node: HTMLElement): void;
colorizeAll(node: HTMLElement): void;
uncolorizeAll(node: HTMLElement): void;
colorize(node: HTMLElement): void;
uncolorize(node: HTMLElement): void;
}

View File

@@ -0,0 +1,94 @@
import * as XpathUtil from '../common/xpath_util.js';
import { addPrefix, Attribute } from '../enrich_mathml/enrich_attr.js';
let counter = 0;
export class AbstractHighlighter {
constructor() {
this.counter = counter++;
this.ATTR = 'sre-highlight-' + this.counter.toString();
this.color = null;
this.mactionName = '';
this.currentHighlights = [];
}
highlight(nodes) {
this.currentHighlights.push(nodes.map((node) => {
const info = this.highlightNode(node);
this.setHighlighted(node);
return info;
}));
}
highlightAll(node) {
const mactions = this.getMactionNodes(node);
for (let i = 0, maction; (maction = mactions[i]); i++) {
this.highlight([maction]);
}
}
unhighlight() {
const nodes = this.currentHighlights.pop();
if (!nodes) {
return;
}
nodes.forEach((highlight) => {
if (this.isHighlighted(highlight.node)) {
this.unhighlightNode(highlight);
this.unsetHighlighted(highlight.node);
}
});
}
unhighlightAll() {
while (this.currentHighlights.length > 0) {
this.unhighlight();
}
}
setColor(color) {
this.color = color;
}
colorString() {
return this.color.rgba();
}
addEvents(node, events) {
const mactions = this.getMactionNodes(node);
for (let i = 0, maction; (maction = mactions[i]); i++) {
for (const [key, event] of Object.entries(events)) {
maction.addEventListener(key, event);
}
}
}
getMactionNodes(node) {
return Array.from(node.getElementsByClassName(this.mactionName));
}
isMactionNode(node) {
const className = node.className || node.getAttribute('class');
return className ? !!className.match(new RegExp(this.mactionName)) : false;
}
isHighlighted(node) {
return node.hasAttribute(this.ATTR);
}
setHighlighted(node) {
node.setAttribute(this.ATTR, 'true');
}
unsetHighlighted(node) {
node.removeAttribute(this.ATTR);
}
colorizeAll(node) {
XpathUtil.updateEvaluator(node);
const allNodes = XpathUtil.evalXPath(`.//*[@${Attribute.ID}]`, node);
allNodes.forEach((x) => this.colorize(x));
}
uncolorizeAll(node) {
const allNodes = XpathUtil.evalXPath(`.//*[@${Attribute.ID}]`, node);
allNodes.forEach((x) => this.uncolorize(x));
}
colorize(node) {
const fore = addPrefix('foreground');
if (node.hasAttribute(fore)) {
node.setAttribute(fore + '-old', node.style.color);
node.style.color = node.getAttribute(fore);
}
}
uncolorize(node) {
const fore = addPrefix('foreground') + '-old';
if (node.hasAttribute(fore)) {
node.style.color = node.getAttribute(fore);
}
}
}

View File

@@ -0,0 +1,6 @@
import { CssHighlighter } from './css_highlighter.js';
export declare class ChtmlHighlighter extends CssHighlighter {
constructor();
isMactionNode(node: HTMLElement): boolean;
getMactionNodes(node: HTMLElement): HTMLElement[];
}

View File

@@ -0,0 +1,13 @@
import { CssHighlighter } from './css_highlighter.js';
export class ChtmlHighlighter extends CssHighlighter {
constructor() {
super();
}
isMactionNode(node) {
var _a;
return ((_a = node.tagName) === null || _a === void 0 ? void 0 : _a.toUpperCase()) === this.mactionName.toUpperCase();
}
getMactionNodes(node) {
return Array.from(node.getElementsByTagName(this.mactionName));
}
}

View File

@@ -0,0 +1,37 @@
interface NamedColor {
color: string;
alpha?: number;
}
interface ChannelColor {
red: number;
green: number;
blue: number;
alpha?: number;
}
export interface StringColor {
background: string;
alphaback?: string;
foreground: string;
alphafore?: string;
}
export type Color = ChannelColor | NamedColor;
export declare class ColorPicker {
private static DEFAULT_BACKGROUND_;
private static DEFAULT_FOREGROUND_;
foreground: ChannelColor;
background: ChannelColor;
private static toHex;
constructor(background: Color, foreground?: Color);
rgba(): StringColor;
rgb(): StringColor;
hex(): StringColor;
}
export declare class ContrastPicker {
hue: number;
sat: number;
light: number;
incr: number;
generate(): string;
increment(): void;
}
export {};

View File

@@ -0,0 +1,141 @@
const namedColors = {
red: { red: 255, green: 0, blue: 0 },
green: { red: 0, green: 255, blue: 0 },
blue: { red: 0, green: 0, blue: 255 },
yellow: { red: 255, green: 255, blue: 0 },
cyan: { red: 0, green: 255, blue: 255 },
magenta: { red: 255, green: 0, blue: 255 },
white: { red: 255, green: 255, blue: 255 },
black: { red: 0, green: 0, blue: 0 }
};
function getChannelColor(color, deflt) {
const col = color || { color: deflt };
let channel = Object.prototype.hasOwnProperty.call(col, 'color')
? namedColors[col.color]
: col;
if (!channel) {
channel = namedColors[deflt];
}
channel.alpha = Object.prototype.hasOwnProperty.call(col, 'alpha')
? col.alpha
: 1;
return normalizeColor(channel);
}
function normalizeColor(color) {
const normalizeCol = (col) => {
col = Math.max(col, 0);
col = Math.min(255, col);
return Math.round(col);
};
color.red = normalizeCol(color.red);
color.green = normalizeCol(color.green);
color.blue = normalizeCol(color.blue);
color.alpha = Math.max(color.alpha, 0);
color.alpha = Math.min(1, color.alpha);
return color;
}
export class ColorPicker {
static toHex(num) {
const hex = num.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
constructor(background, foreground) {
this.foreground = getChannelColor(foreground, ColorPicker.DEFAULT_FOREGROUND_);
this.background = getChannelColor(background, ColorPicker.DEFAULT_BACKGROUND_);
}
rgba() {
const rgba = function (col) {
return ('rgba(' +
col.red +
',' +
col.green +
',' +
col.blue +
',' +
col.alpha +
')');
};
return {
background: rgba(this.background),
foreground: rgba(this.foreground)
};
}
rgb() {
const rgb = function (col) {
return 'rgb(' + col.red + ',' + col.green + ',' + col.blue + ')';
};
return {
background: rgb(this.background),
alphaback: this.background.alpha.toString(),
foreground: rgb(this.foreground),
alphafore: this.foreground.alpha.toString()
};
}
hex() {
const hex = function (col) {
return ('#' +
ColorPicker.toHex(col.red) +
ColorPicker.toHex(col.green) +
ColorPicker.toHex(col.blue));
};
return {
background: hex(this.background),
alphaback: this.background.alpha.toString(),
foreground: hex(this.foreground),
alphafore: this.foreground.alpha.toString()
};
}
}
ColorPicker.DEFAULT_BACKGROUND_ = 'blue';
ColorPicker.DEFAULT_FOREGROUND_ = 'black';
function hsl2rgb(h, s, l) {
s = s > 1 ? s / 100 : s;
l = l > 1 ? l / 100 : l;
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = l - c / 2;
let r = 0, g = 0, b = 0;
if (0 <= h && h < 60) {
[r, g, b] = [c, x, 0];
}
else if (60 <= h && h < 120) {
[r, g, b] = [x, c, 0];
}
else if (120 <= h && h < 180) {
[r, g, b] = [0, c, x];
}
else if (180 <= h && h < 240) {
[r, g, b] = [0, x, c];
}
else if (240 <= h && h < 300) {
[r, g, b] = [x, 0, c];
}
else if (300 <= h && h < 360) {
[r, g, b] = [c, 0, x];
}
return { red: r + m, green: g + m, blue: b + m };
}
function rgb2RGB(rgb) {
return {
red: Math.round(255 * rgb.red),
green: Math.round(255 * rgb.green),
blue: Math.round(255 * rgb.blue)
};
}
function RGB2hex(col) {
return 'rgb(' + col.red + ',' + col.green + ',' + col.blue + ')';
}
export class ContrastPicker {
constructor() {
this.hue = 10;
this.sat = 100;
this.light = 50;
this.incr = 50;
}
generate() {
return RGB2hex(rgb2RGB(hsl2rgb(this.hue, this.sat, this.light)));
}
increment() {
this.hue = (this.hue + this.incr) % 360;
}
}

View File

@@ -0,0 +1,10 @@
import { AbstractHighlighter, Highlight } from './abstract_highlighter.js';
export declare class CssHighlighter extends AbstractHighlighter {
constructor();
highlightNode(node: HTMLElement): {
node: HTMLElement;
background: string;
foreground: string;
};
unhighlightNode(info: Highlight): void;
}

View File

@@ -0,0 +1,24 @@
import { AbstractHighlighter } from './abstract_highlighter.js';
export class CssHighlighter extends AbstractHighlighter {
constructor() {
super();
this.mactionName = 'mjx-maction';
}
highlightNode(node) {
const info = {
node: node,
background: node.style.backgroundColor,
foreground: node.style.color
};
if (!this.isHighlighted(node)) {
const color = this.colorString();
node.style.backgroundColor = color.background;
node.style.color = color.foreground;
}
return info;
}
unhighlightNode(info) {
info.node.style.backgroundColor = info.background;
info.node.style.color = info.foreground;
}
}

View File

@@ -0,0 +1,13 @@
import { ColorPicker, StringColor } from './color_picker.js';
export interface Highlighter {
highlight(nodes: HTMLElement[]): void;
unhighlight(): void;
highlightAll(node: HTMLElement): void;
unhighlightAll(): void;
isMactionNode(node: Element): boolean;
setColor(color: ColorPicker): void;
colorString(): StringColor;
addEvents(node: HTMLElement, events: {
[key: string]: EventListener;
}): void;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,13 @@
import { Color } from './color_picker.js';
import { Highlighter } from './highlighter.js';
export declare function highlighter(back: Color, fore: Color, rendererInfo: {
renderer: string;
browser?: string;
}): Highlighter;
export declare function update(back: Color, fore: Color, highlighter: Highlighter): void;
export declare function addEvents(node: HTMLElement, events: {
[key: string]: EventListener;
}, rendererInfo: {
renderer: string;
browser?: string;
}): void;

View File

@@ -0,0 +1,39 @@
import { ChtmlHighlighter } from './chtml_highlighter.js';
import { ColorPicker } from './color_picker.js';
import { CssHighlighter } from './css_highlighter.js';
import { HtmlHighlighter } from './html_highlighter.js';
import { MmlCssHighlighter } from './mml_css_highlighter.js';
import { MmlHighlighter } from './mml_highlighter.js';
import { SvgHighlighter } from './svg_highlighter.js';
import { SvgV3Highlighter } from './svg_v3_highlighter.js';
export function highlighter(back, fore, rendererInfo) {
const colorPicker = new ColorPicker(back, fore);
const renderer = rendererInfo.renderer === 'NativeMML' && rendererInfo.browser === 'Safari'
? 'MML-CSS'
: rendererInfo.renderer === 'SVG' && rendererInfo.browser === 'v3'
? 'SVG-V3'
: rendererInfo.renderer;
const highlighter = new (highlighterMapping[renderer] ||
highlighterMapping['NativeMML'])();
highlighter.setColor(colorPicker);
return highlighter;
}
export function update(back, fore, highlighter) {
const colorPicker = new ColorPicker(back, fore);
highlighter.setColor(colorPicker);
}
export function addEvents(node, events, rendererInfo) {
const highlight = highlighterMapping[rendererInfo.renderer];
if (highlight) {
new highlight().addEvents(node, events);
}
}
const highlighterMapping = {
SVG: SvgHighlighter,
'SVG-V3': SvgV3Highlighter,
NativeMML: MmlHighlighter,
'HTML-CSS': HtmlHighlighter,
'MML-CSS': MmlCssHighlighter,
CommonHTML: CssHighlighter,
CHTML: ChtmlHighlighter
};

View File

@@ -0,0 +1,6 @@
import { AbstractHighlighter, Highlight } from './abstract_highlighter.js';
export declare class HtmlHighlighter extends AbstractHighlighter {
constructor();
highlightNode(node: HTMLElement): Highlight;
unhighlightNode(info: Highlight): void;
}

View File

@@ -0,0 +1,45 @@
import * as DomUtil from '../common/dom_util.js';
import { AbstractHighlighter } from './abstract_highlighter.js';
export class HtmlHighlighter extends AbstractHighlighter {
constructor() {
super();
this.mactionName = 'maction';
}
highlightNode(node) {
const info = {
node: node,
foreground: node.style.color,
position: node.style.position
};
const color = this.color.rgb();
node.style.color = color.foreground;
node.style.position = 'relative';
const bbox = node.bbox;
if (bbox && bbox.w) {
const vpad = 0.05;
const hpad = 0;
const span = DomUtil.createElement('span');
const left = parseFloat(node.style.paddingLeft || '0');
span.style.backgroundColor = color.background;
span.style.opacity = color.alphaback.toString();
span.style.display = 'inline-block';
span.style.height = bbox.h + bbox.d + 2 * vpad + 'em';
span.style.verticalAlign = -bbox.d + 'em';
span.style.marginTop = span.style.marginBottom = -vpad + 'em';
span.style.width = bbox.w + 2 * hpad + 'em';
span.style.marginLeft = left - hpad + 'em';
span.style.marginRight = -bbox.w - hpad - left + 'em';
node.parentNode.insertBefore(span, node);
info.box = span;
}
return info;
}
unhighlightNode(info) {
const node = info.node;
node.style.color = info.foreground;
node.style.position = info.position;
if (info.box) {
info.box.parentNode.removeChild(info.box);
}
}
}

View File

@@ -0,0 +1,6 @@
import { CssHighlighter } from './css_highlighter.js';
export declare class MmlCssHighlighter extends CssHighlighter {
constructor();
getMactionNodes(node: HTMLElement): HTMLElement[];
isMactionNode(node: HTMLElement): boolean;
}

View File

@@ -0,0 +1,13 @@
import { CssHighlighter } from './css_highlighter.js';
export class MmlCssHighlighter extends CssHighlighter {
constructor() {
super();
this.mactionName = 'maction';
}
getMactionNodes(node) {
return Array.from(node.getElementsByTagName(this.mactionName));
}
isMactionNode(node) {
return node.tagName === this.mactionName;
}
}

View File

@@ -0,0 +1,11 @@
import { AbstractHighlighter, Highlight } from './abstract_highlighter.js';
export declare class MmlHighlighter extends AbstractHighlighter {
constructor();
highlightNode(node: HTMLElement): {
node: HTMLElement;
};
unhighlightNode(info: Highlight): void;
colorString(): import("./color_picker.js").StringColor;
getMactionNodes(node: HTMLElement): HTMLElement[];
isMactionNode(node: HTMLElement): boolean;
}

View File

@@ -0,0 +1,29 @@
import { AbstractHighlighter } from './abstract_highlighter.js';
export class MmlHighlighter extends AbstractHighlighter {
constructor() {
super();
this.mactionName = 'maction';
}
highlightNode(node) {
let style = node.getAttribute('style');
style += ';background-color: ' + this.colorString().background;
style += ';color: ' + this.colorString().foreground;
node.setAttribute('style', style);
return { node: node };
}
unhighlightNode(info) {
let style = info.node.getAttribute('style');
style = style.replace(';background-color: ' + this.colorString().background, '');
style = style.replace(';color: ' + this.colorString().foreground, '');
info.node.setAttribute('style', style);
}
colorString() {
return this.color.rgba();
}
getMactionNodes(node) {
return Array.from(node.getElementsByTagName(this.mactionName));
}
isMactionNode(node) {
return node.tagName === this.mactionName;
}
}

View File

@@ -0,0 +1,8 @@
import { AbstractHighlighter, Highlight } from './abstract_highlighter.js';
export declare class SvgHighlighter extends AbstractHighlighter {
constructor();
highlightNode(node: HTMLElement): Highlight;
setHighlighted(node: HTMLElement): void;
unhighlightNode(info: Highlight): void;
isMactionNode(node: HTMLElement): boolean;
}

View File

@@ -0,0 +1,83 @@
import * as DomUtil from '../common/dom_util.js';
import { AbstractHighlighter } from './abstract_highlighter.js';
export class SvgHighlighter extends AbstractHighlighter {
constructor() {
super();
this.mactionName = 'mjx-svg-maction';
}
highlightNode(node) {
let info;
if (this.isHighlighted(node)) {
info = {
node: node.previousSibling || node,
background: node.style.backgroundColor,
foreground: node.style.color
};
return info;
}
if (node.tagName === 'svg') {
const info = {
node: node,
background: node.style.backgroundColor,
foreground: node.style.color
};
node.style.backgroundColor = this.colorString().background;
node.style.color = this.colorString().foreground;
return info;
}
const rect = DomUtil.createElementNS('http://www.w3.org/2000/svg', 'rect');
const padding = 40;
let bbox;
if (node.nodeName === 'use') {
const g = DomUtil.createElementNS('http://www.w3.org/2000/svg', 'g');
node.parentNode.insertBefore(g, node);
g.appendChild(node);
bbox = g.getBBox();
g.parentNode.replaceChild(node, g);
}
else {
bbox = node.getBBox();
}
rect.setAttribute('x', (bbox.x - padding).toString());
rect.setAttribute('y', (bbox.y - padding).toString());
rect.setAttribute('width', (bbox.width + 2 * padding).toString());
rect.setAttribute('height', (bbox.height + 2 * padding).toString());
const transform = node.getAttribute('transform');
if (transform) {
rect.setAttribute('transform', transform);
}
rect.setAttribute('fill', this.colorString().background);
rect.setAttribute(this.ATTR, 'true');
node.parentNode.insertBefore(rect, node);
info = { node: rect, foreground: node.getAttribute('fill') };
node.setAttribute('fill', this.colorString().foreground);
return info;
}
setHighlighted(node) {
if (node.tagName === 'svg') {
super.setHighlighted(node);
}
}
unhighlightNode(info) {
if ('background' in info) {
info.node.style.backgroundColor = info.background;
info.node.style.color = info.foreground;
return;
}
info.foreground
? info.node.nextSibling.setAttribute('fill', info.foreground)
: info.node.nextSibling.removeAttribute('fill');
info.node.parentNode.removeChild(info.node);
}
isMactionNode(node) {
let className = node.className || node.getAttribute('class');
if (!className) {
return false;
}
className =
className.baseVal !== undefined
? className.baseVal
: className;
return className ? !!className.match(new RegExp(this.mactionName)) : false;
}
}

View File

@@ -0,0 +1,9 @@
import { Highlight } from './abstract_highlighter.js';
import { SvgHighlighter } from './svg_highlighter.js';
export declare class SvgV3Highlighter extends SvgHighlighter {
constructor();
highlightNode(node: HTMLElement): Highlight;
unhighlightNode(info: Highlight): void;
isMactionNode(node: HTMLElement): boolean;
getMactionNodes(node: HTMLElement): HTMLElement[];
}

View File

@@ -0,0 +1,73 @@
import * as DomUtil from '../common/dom_util.js';
import * as XpathUtil from '../common/xpath_util.js';
import { ColorPicker } from './color_picker.js';
import { SvgHighlighter } from './svg_highlighter.js';
export class SvgV3Highlighter extends SvgHighlighter {
constructor() {
super();
this.mactionName = 'maction';
}
highlightNode(node) {
let info;
if (this.isHighlighted(node)) {
info = {
node: node,
background: this.colorString().background,
foreground: this.colorString().foreground
};
return info;
}
if (node.tagName === 'svg' || node.tagName === 'MJX-CONTAINER') {
info = {
node: node,
background: node.style.backgroundColor,
foreground: node.style.color
};
node.style.backgroundColor = this.colorString().background;
node.style.color = this.colorString().foreground;
return info;
}
const rect = DomUtil.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('sre-highlighter-added', 'true');
const padding = 40;
const bbox = node.getBBox();
rect.setAttribute('x', (bbox.x - padding).toString());
rect.setAttribute('y', (bbox.y - padding).toString());
rect.setAttribute('width', (bbox.width + 2 * padding).toString());
rect.setAttribute('height', (bbox.height + 2 * padding).toString());
const transform = node.getAttribute('transform');
if (transform) {
rect.setAttribute('transform', transform);
}
rect.setAttribute('fill', this.colorString().background);
node.setAttribute(this.ATTR, 'true');
node.parentNode.insertBefore(rect, node);
info = { node: node, foreground: node.getAttribute('fill') };
if (node.nodeName === 'rect') {
const picker = new ColorPicker({ alpha: 0, color: 'black' });
node.setAttribute('fill', picker.rgba().foreground);
}
else {
node.setAttribute('fill', this.colorString().foreground);
}
return info;
}
unhighlightNode(info) {
const previous = info.node.previousSibling;
if (previous && previous.hasAttribute('sre-highlighter-added')) {
info.foreground
? info.node.setAttribute('fill', info.foreground)
: info.node.removeAttribute('fill');
info.node.parentNode.removeChild(previous);
return;
}
info.node.style.backgroundColor = info.background;
info.node.style.color = info.foreground;
}
isMactionNode(node) {
return node.getAttribute('data-mml-node') === this.mactionName;
}
getMactionNodes(node) {
return Array.from(XpathUtil.evalXPath(`.//*[@data-mml-node="${this.mactionName}"]`, node));
}
}