| |
| import marpitPlugin from '../../plugin' |
| |
| const escape = (target) => |
| target.replace( |
| /[\\;:()]/g, |
| (matched) => `\\${matched[0].codePointAt(0).toString(16)} `, |
| ) |
| |
| const optionMatchers = new Map() |
| |
| |
| optionMatchers.set(/^(\d*\.)?\d+%$/, (matches) => ({ size: matches[0] })) |
| |
| |
| 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]) }), |
| ) |
| |
| |
| 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')]], |
| })) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function _parseImage(md) { |
| const { process } = md.core |
| |
| let refCount = 0 |
| |
| const finalizeTokenAttr = (token, state) => { |
| |
| if (token.type === 'inline') { |
| for (const t of token.children) finalizeTokenAttr(t, state) |
| } |
| |
| |
| 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) { |
| |
| 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') { |
| |
| 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, |
| } |
| |
| |
| 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, |
| }), |
| } |
| } |
| } |
| } |
| } |
| } |
| }) |
| } |
| |
| export const parseImage = marpitPlugin(_parseImage) |
| export default parseImage |