Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement CSS filter for image and advanced backgrounds #14

Merged
merged 10 commits into from
May 4, 2018
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

* Implement CSS filter for image and advanced backgrounds ([#14](https://github.com/marp-team/marpit/pull/14))
* Fix PostCSS printable plugin to allow printing the advanced backgrounds ([#15](https://github.com/marp-team/marpit/pull/15))

## v0.0.3 - 2018-05-02
Expand Down
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ We provide a background image syntax to specify slide's background through Markd
![bg](https://example.com/background.jpg)
```

You can disable by `backgroundSyntax: false` in Marpit constructor option.
When you defined 2 or more background images in a slide, Marpit will show the last defined image only. If you want to show multiple images, try [the advanced backgrounds](#advanced-backgrounds-with-inline-svg-mode) by enabling [inline SVG mode](#inline-svg-slide-experimental).

You can disable by `backgroundSyntax: false` in Marpit constructor option if you not want the `bg` syntax.

#### Resize images

Expand Down Expand Up @@ -108,7 +110,9 @@ When you remove the underbar, the background would apply to current and _the fol

#### Advanced backgrounds with inline SVG mode

The advanced backgrounds will work only with [`inlineSVG: true`](#inline-svg-slide-experimental). It supports multiple background images.
The advanced backgrounds will work _only with [`inlineSVG: true`](#inline-svg-slide-experimental)_. It supports multiple background images and image filters.

##### Multiple background images

```
![bg](https://example.com/backgroundA.jpg)
Expand All @@ -117,9 +121,39 @@ The advanced backgrounds will work only with [`inlineSVG: true`](#inline-svg-sli

These images will arrange in a row.

### Image filters

You can apply CSS filters to image through markdown image syntax. Include `<filter-name>(:<param>(,<params>...))` to the space-separated alternate text of image syntax.

Filters can use in the inline image and [the advanced backgrounds](#advanced-backgrounds-with-inline-svg-mode). You can disable this feature with `filters: false` in Marpit constructor option.

#### Filters

We are following the function of the [`filter` style](https://developer.mozilla.org/en-US/docs/Web/CSS/filter).

| Markdown | (with arguments) | `filter` style |
| ------------------ | -------------------------------------------- | ------------------------------------------- |
| `![blur]()` | `![blur:10px]()` | `blur(10px)` |
| `![brightness]()` | `![brightness:1.5]()` | `brightness(1.5)` |
| `![contrast]()` | `![contrast:200%]()` | `contrast(200%)` |
| `![drop-shadow]()` | `![drop-shadow:0,5px,10px,rgba(0,0,0,.4)]()` | `drop-shadow(0 5px 10px rgba(0, 0, 0, .4))` |
| `![grayscale]()` | `![grayscale:1]()` | `grayscale(1)` |
| `![hue-rotate]()` | `![hue-rotate:180deg]()` | `hue-rotate(180deg)` |
| `![invert]()` | `![invert:100%]()` | `invert(100%)` |
| `![opacity]()` | `![opacity:.5]()` | `opacity(.5)` |
| `![saturate]()` | `![saturate:2.0]()` | `saturate(2.0)` |
| `![sepia]()` | `![sepia:1.0]()` | `sepia(1.0)` |

Marpit will use the default arguments shown in above when you omit arguments.

Naturally multiple filters can apply to a image.

```markdown
![brightness:.8 sepia:50%](https://example.com/image.jpg)
```

### ToDo

* [ ] Background filters in advanced backgrounds
* [ ] Header and footer directive
* [ ] Slide page number

Expand Down
20 changes: 16 additions & 4 deletions src/markdown/background_image.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,22 @@ function backgroundImage(md) {

tb.children.forEach(t => {
if (t.type !== 'image') return
const { background, backgroundSize, size, url } = t.meta.marpitImage

const {
background,
backgroundSize,
filter,
size,
url,
} = t.meta.marpitImage

if (background && !url.match(/^\s*$/)) {
current.images = [
...(current.images || []),
{
url,
size: size || backgroundSize || undefined,
filter,
},
]
}
Expand Down Expand Up @@ -149,9 +157,13 @@ function backgroundImage(md) {
...imgArr,
...wrapTokens('marpit_advanced_background_image', {
tag: 'figure',
style: `background-image:url("${img.url}");${
img.size ? `background-size:${img.size};` : ''
}`,
style: [
`background-image:url("${img.url}");`,
img.size && `background-size:${img.size};`,
img.filter && `filter:${img.filter};`,
]
.filter(s => s)
.join(''),
}),
],
[]
Expand Down
97 changes: 93 additions & 4 deletions src/markdown/parse_image.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/** @module */
const escapeStyle = target =>
target.replace(/[\\;:()]/g, matched => `\\${matched[0]}`)

/**
* Marpit parse image plugin.
Expand All @@ -9,8 +11,77 @@
*
* @alias module:markdown/parse_image
* @param {MarkdownIt} md markdown-it instance.
* @param {Object} [opts]
* @param {boolean} [opts.filters=true] Switch feature to support CSS filters.
*/
function parseImage(md) {
function parseImage(md, opts = {}) {
const pluginOptions = { filters: true, ...opts }
const optionMatchers = new Map()

// The scale percentage for resize
// TODO: Implement cross-browser image zoom without affecting DOM tree
// (Pre-released Marp uses `zoom` but it has not supported in Firefox)
optionMatchers.set(/^(\d*\.)?\d+%$/, matches => ({ size: matches[0] }))

if (pluginOptions.filters) {
// CSS filters
optionMatchers.set(/^blur(?::(.+))?$/, (matches, meta) => ({
filters: [...meta.filters, ['blur', escapeStyle(matches[1] || '10px')]],
}))
optionMatchers.set(/^brightness(?::(.+))?$/, (matches, meta) => ({
filters: [
...meta.filters,
['brightness', escapeStyle(matches[1] || '1.5')],
],
}))
optionMatchers.set(/^contrast(?::(.+))?$/, (matches, meta) => ({
filters: [...meta.filters, ['contrast', escapeStyle(matches[1] || '2')]],
}))
optionMatchers.set(
/^drop-shadow(?::(.+?),(.+?)(?:,(.+?))?(?:,(.+?))?)?$/,
(matches, meta) => {
const args = matches
.slice(1)
.filter(v => v)
.map(arg => {
const colorFunc = arg.match(/^(rgba?|hsla?)\((.*)\)$/)

return colorFunc
? `${colorFunc[1]}(${escapeStyle(colorFunc[2])})`
: escapeStyle(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', escapeStyle(matches[1] || '1')]],
}))
optionMatchers.set(/^hue-rotate(?::(.+))?$/, (matches, meta) => ({
filters: [
...meta.filters,
['hue-rotate', escapeStyle(matches[1] || '180deg')],
],
}))
optionMatchers.set(/^invert(?::(.+))?$/, (matches, meta) => ({
filters: [...meta.filters, ['invert', escapeStyle(matches[1] || '1')]],
}))
optionMatchers.set(/^opacity(?::(.+))?$/, (matches, meta) => ({
filters: [...meta.filters, ['opacity', escapeStyle(matches[1] || '.5')]],
}))
optionMatchers.set(/^saturate(?::(.+))?$/, (matches, meta) => ({
filters: [...meta.filters, ['saturate', escapeStyle(matches[1] || '2')]],
}))
optionMatchers.set(/^sepia(?::(.+))?$/, (matches, meta) => ({
filters: [...meta.filters, ['sepia', escapeStyle(matches[1] || '1')]],
}))
}

md.inline.ruler2.push('marpit_parse_image', ({ tokens }) => {
tokens.forEach(token => {
if (token.type === 'image') {
Expand All @@ -24,10 +95,28 @@ function parseImage(md) {
}

options.forEach(opt => {
// TODO: Implement cross-browser image zoom without affecting DOM tree
// (Pre-released Marp uses `zoom` but it has not supported in Firefox)
if (opt.match(/^(\d*\.)?\d+%$/)) token.meta.marpitImage.size = opt
optionMatchers.forEach((mergeFunc, regexp) => {
const matched = opt.match(regexp)

if (matched)
token.meta.marpitImage = {
...token.meta.marpitImage,
...mergeFunc(matched, {
filters: [],
...token.meta.marpitImage,
}),
}
})
})

// Build and apply filter style
if (token.meta.marpitImage.filters) {
token.meta.marpitImage.filter = token.meta.marpitImage.filters
.reduce((arr, fltrs) => [...arr, `${fltrs[0]}(${fltrs[1]})`], [])
.join(' ')

token.attrJoin('style', `filter:${token.meta.marpitImage.filter};`)
}
}
})
})
Expand Down
7 changes: 5 additions & 2 deletions src/marpit.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import marpItSweep from './markdown/sweep'
const defaultOptions = {
backgroundSyntax: true,
container: marpitContainer,
filters: true,
markdown: 'commonmark',
printable: true,
slideContainer: undefined,
Expand All @@ -33,10 +34,12 @@ class Marpit {
* @param {boolean} [opts.backgroundSyntax=true] Support markdown image syntax
* with the alternate text including `bg`. Normally it converts into spot
* directives about background image. If `inlineSVG` is enabled, it
* supports multiple image layouting.
* supports the advanced backgrounds.
* @param {Element|Element[]}
* [opts.container={@link module:element.marpitContainer}] Container
* element(s) wrapping whole slide deck.
* @param {boolean} [opts.filters=true] Support filter syntax for markdown
* image. It can apply to inline image and the advanced backgrounds.
* @param {string|Object|Array} [opts.markdown='commonmark'] markdown-it
* initialize option(s).
* @param {boolean} [opts.printable=true] Make style printable to PDF.
Expand Down Expand Up @@ -87,7 +90,7 @@ class Marpit {
.use(marpItApplyDirectives)
.use(marpItSlideContainer, this.slideContainers)
.use(marpItContainer, this.containers)
.use(marpItParseImage)
.use(marpItParseImage, { filters: this.options.filters })
.use(marpItSweep)
.use(marpItInlineSVG, this)

Expand Down
Loading