175 lines
5.7 KiB
JavaScript
175 lines
5.7 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.parseImage = exports.default = void 0;
|
|
var _plugin = _interopRequireDefault(require("../../plugin"));
|
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
/** @module */
|
|
|
|
const escape = target => target.replace(/[\\;:()]/g, matched => `\\${matched[0].codePointAt(0).toString(16)} `);
|
|
const optionMatchers = new Map();
|
|
|
|
// The scale percentage for resize background
|
|
optionMatchers.set(/^(\d*\.)?\d+%$/, matches => ({
|
|
size: matches[0]
|
|
}));
|
|
|
|
// width and height
|
|
const normalizeLength = v => `${v}${/^(\d*\.)?\d+$/.test(v) ? 'px' : ''}`;
|
|
optionMatchers.set(/^w(?:idth)?:((?:\d*\.)?\d+(?:%|ch|cm|em|ex|in|mm|pc|pt|px)?|auto)$/, matches => ({
|
|
width: normalizeLength(matches[1])
|
|
}));
|
|
optionMatchers.set(/^h(?:eight)?:((?:\d*\.)?\d+(?:%|ch|cm|em|ex|in|mm|pc|pt|px)?|auto)$/, matches => ({
|
|
height: normalizeLength(matches[1])
|
|
}));
|
|
|
|
// CSS filters
|
|
optionMatchers.set(/^blur(?::(.+))?$/, (matches, meta) => ({
|
|
filters: [...meta.filters, ['blur', escape(matches[1] || '10px')]]
|
|
}));
|
|
optionMatchers.set(/^brightness(?::(.+))?$/, (matches, meta) => ({
|
|
filters: [...meta.filters, ['brightness', escape(matches[1] || '1.5')]]
|
|
}));
|
|
optionMatchers.set(/^contrast(?::(.+))?$/, (matches, meta) => ({
|
|
filters: [...meta.filters, ['contrast', escape(matches[1] || '2')]]
|
|
}));
|
|
optionMatchers.set(/^drop-shadow(?::(.+?),(.+?)(?:,(.+?))?(?:,(.+?))?)?$/, (matches, meta) => {
|
|
const args = [];
|
|
for (const arg of matches.slice(1)) {
|
|
if (arg) {
|
|
const colorFunc = arg.match(/^(rgba?|hsla?|hwb|(?:ok)?(?:lab|lch)|color)\((.*)\)$/);
|
|
args.push(colorFunc ? `${colorFunc[1]}(${escape(colorFunc[2])})` : escape(arg));
|
|
}
|
|
}
|
|
return {
|
|
filters: [...meta.filters, ['drop-shadow', args.join(' ') || '0 5px 10px rgba(0,0,0,.4)']]
|
|
};
|
|
});
|
|
optionMatchers.set(/^grayscale(?::(.+))?$/, (matches, meta) => ({
|
|
filters: [...meta.filters, ['grayscale', escape(matches[1] || '1')]]
|
|
}));
|
|
optionMatchers.set(/^hue-rotate(?::(.+))?$/, (matches, meta) => ({
|
|
filters: [...meta.filters, ['hue-rotate', escape(matches[1] || '180deg')]]
|
|
}));
|
|
optionMatchers.set(/^invert(?::(.+))?$/, (matches, meta) => ({
|
|
filters: [...meta.filters, ['invert', escape(matches[1] || '1')]]
|
|
}));
|
|
optionMatchers.set(/^opacity(?::(.+))?$/, (matches, meta) => ({
|
|
filters: [...meta.filters, ['opacity', escape(matches[1] || '.5')]]
|
|
}));
|
|
optionMatchers.set(/^saturate(?::(.+))?$/, (matches, meta) => ({
|
|
filters: [...meta.filters, ['saturate', escape(matches[1] || '2')]]
|
|
}));
|
|
optionMatchers.set(/^sepia(?::(.+))?$/, (matches, meta) => ({
|
|
filters: [...meta.filters, ['sepia', escape(matches[1] || '1')]]
|
|
}));
|
|
|
|
/**
|
|
* Marpit image parse plugin.
|
|
*
|
|
* Parse image tokens and store the result into `marpitImage` meta. It has an
|
|
* image url and options. The alternative text is regarded as space-separated
|
|
* options.
|
|
*
|
|
* @function parseImage
|
|
* @param {MarkdownIt} md markdown-it instance.
|
|
*/
|
|
function _parseImage(md) {
|
|
const {
|
|
process
|
|
} = md.core;
|
|
let refCount = 0;
|
|
const finalizeTokenAttr = (token, state) => {
|
|
// Apply finalization recursively to inline tokens
|
|
if (token.type === 'inline') {
|
|
for (const t of token.children) finalizeTokenAttr(t, state);
|
|
}
|
|
|
|
// Re-generate the alt text of image token to remove Marpit specific options
|
|
if (token.type === 'image' && token.meta && token.meta.marpitImage) {
|
|
let updatedAlt = '';
|
|
let hasConsumed = false;
|
|
for (const opt of token.meta.marpitImage.options) {
|
|
if (opt.consumed) {
|
|
hasConsumed = true;
|
|
} else {
|
|
updatedAlt += opt.leading + opt.content;
|
|
}
|
|
}
|
|
if (hasConsumed) {
|
|
let newTokens = [];
|
|
md.inline.parse(updatedAlt.trimStart(), state.md, state.env, newTokens);
|
|
token.children = newTokens;
|
|
}
|
|
}
|
|
};
|
|
md.core.process = state => {
|
|
try {
|
|
refCount += 1;
|
|
return process.call(md.core, state);
|
|
} finally {
|
|
refCount -= 1;
|
|
if (refCount === 0) {
|
|
// Apply finalization for every tokens
|
|
for (const token of state.tokens) finalizeTokenAttr(token, state);
|
|
}
|
|
}
|
|
};
|
|
md.inline.ruler2.push('marpit_parse_image', ({
|
|
tokens
|
|
}) => {
|
|
for (const token of tokens) {
|
|
if (token.type === 'image') {
|
|
// Parse alt text as options
|
|
const optsBase = token.content.split(/(\s+)/);
|
|
let currentIdx = 0;
|
|
let leading = '';
|
|
const options = optsBase.reduce((acc, opt, i) => {
|
|
if (i % 2 === 0 && opt.length > 0) {
|
|
currentIdx += leading.length;
|
|
acc.push({
|
|
content: opt,
|
|
index: currentIdx,
|
|
leading,
|
|
consumed: false
|
|
});
|
|
leading = '';
|
|
currentIdx += opt.length;
|
|
} else {
|
|
leading += opt;
|
|
}
|
|
return acc;
|
|
}, []);
|
|
const url = token.attrGet('src');
|
|
token.meta = token.meta || {};
|
|
token.meta.marpitImage = {
|
|
...(token.meta.marpitImage || {}),
|
|
url: url.toString(),
|
|
options
|
|
};
|
|
|
|
// Parse keyword through matchers
|
|
for (const opt of options) {
|
|
for (const [regexp, mergeFunc] of optionMatchers) {
|
|
if (opt.consumed) continue;
|
|
const matched = opt.content.match(regexp);
|
|
if (matched) {
|
|
opt.consumed = true;
|
|
token.meta.marpitImage = {
|
|
...token.meta.marpitImage,
|
|
...mergeFunc(matched, {
|
|
filters: [],
|
|
...token.meta.marpitImage
|
|
})
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
const parseImage = exports.parseImage = (0, _plugin.default)(_parseImage);
|
|
var _default = exports.default = parseImage; |