From 7f56e5577506921a2727afcd46f8b58e85aba72c Mon Sep 17 00:00:00 2001 From: rain2o Date: Sat, 19 Feb 2022 17:58:42 +0100 Subject: [PATCH] add mix-blend-mode support --- docs/features.md | 2 +- src/css/index.ts | 3 + .../property-descriptors/mix-blend-mode.ts | 77 ++++++++++++++++++ src/global.d.ts | 1 + src/render/canvas/canvas-renderer.ts | 3 + tests/reftests/mix-blend-mode.html | 79 +++++++++++++++++++ 6 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/css/property-descriptors/mix-blend-mode.ts create mode 100644 tests/reftests/mix-blend-mode.html diff --git a/docs/features.md b/docs/features.md index d1e1c83..99acf51 100644 --- a/docs/features.md +++ b/docs/features.md @@ -46,6 +46,7 @@ Below is a list of all the supported CSS properties and values. - max-width - min-height - min-width + - mix-blend-mode (**Limited support**) - opacity - overflow - overflow-wrap @@ -79,7 +80,6 @@ These CSS properties are **NOT** currently supported - [box-shadow](https://github.com/niklasvh/html2canvas/pull/1086) - [filter](https://github.com/niklasvh/html2canvas/issues/493) - [font-variant-ligatures](https://github.com/niklasvh/html2canvas/pull/1085) - - [mix-blend-mode](https://github.com/niklasvh/html2canvas/issues/580) - [object-fit](https://github.com/niklasvh/html2canvas/issues/1064) - [repeating-linear-gradient()](https://github.com/niklasvh/html2canvas/issues/1162) - [writing-mode](https://github.com/niklasvh/html2canvas/issues/1258) diff --git a/src/css/index.ts b/src/css/index.ts index b338871..16544e7 100644 --- a/src/css/index.ts +++ b/src/css/index.ts @@ -41,6 +41,7 @@ import {listStyleImage} from './property-descriptors/list-style-image'; import {listStylePosition} from './property-descriptors/list-style-position'; import {listStyleType} from './property-descriptors/list-style-type'; import {marginBottom, marginLeft, marginRight, marginTop} from './property-descriptors/margin'; +import {mixBlendMode} from './property-descriptors/mix-blend-mode'; import {overflow, OVERFLOW} from './property-descriptors/overflow'; import {overflowWrap} from './property-descriptors/overflow-wrap'; import {paddingBottom, paddingLeft, paddingRight, paddingTop} from './property-descriptors/padding'; @@ -126,6 +127,7 @@ export class CSSParsedDeclaration { marginRight: CSSValue; marginBottom: CSSValue; marginLeft: CSSValue; + mixBlendMode: ReturnType; opacity: ReturnType; overflowX: OVERFLOW; overflowY: OVERFLOW; @@ -194,6 +196,7 @@ export class CSSParsedDeclaration { this.marginRight = parse(context, marginRight, declaration.marginRight); this.marginBottom = parse(context, marginBottom, declaration.marginBottom); this.marginLeft = parse(context, marginLeft, declaration.marginLeft); + this.mixBlendMode = parse(context, mixBlendMode, declaration.mixBlendMode); this.opacity = parse(context, opacity, declaration.opacity); const overflowTuple = parse(context, overflow, declaration.overflow); this.overflowX = overflowTuple[0]; diff --git a/src/css/property-descriptors/mix-blend-mode.ts b/src/css/property-descriptors/mix-blend-mode.ts new file mode 100644 index 0000000..cb34330 --- /dev/null +++ b/src/css/property-descriptors/mix-blend-mode.ts @@ -0,0 +1,77 @@ +import {IPropertyIdentValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor'; +import {Context} from '../../core/context'; + +export const enum MIX_BLEND_MODE { + NORMAL = 'normal', + MULTIPLY = 'multiply', + SCREEN = 'screen', + OVERLAY = 'overlay', + DARKEN = 'darken', + LIGHTEN = 'lighten', + COLOR_DODGE = 'color-dodge', + COLOR_BURN = 'color-burn', + HARD_LIGHT = 'hard-light', + SOFT_LIGHT = 'soft-light', + DIFFERENCE = 'difference', + EXCLUSION = 'exclusion', + HUE = 'hue', + SATURATION = 'saturation', + COLOR = 'color', + LUMINOSITY = 'luminosity', + INITIAL = 'initial', + INHERIT = 'inherit', + REVERT = 'revert', + UNSET = 'unset' +} + +export const mixBlendMode: IPropertyIdentValueDescriptor = { + name: 'mix-blend-mode', + initialValue: 'normal', + prefix: false, + type: PropertyDescriptorParsingType.IDENT_VALUE, + parse: (_context: Context, mode: string) => { + switch (mode) { + case 'multiply': + return MIX_BLEND_MODE.MULTIPLY; + case 'screen': + return MIX_BLEND_MODE.SCREEN; + case 'overlay': + return MIX_BLEND_MODE.OVERLAY; + case 'darken': + return MIX_BLEND_MODE.DARKEN; + case 'lighten': + return MIX_BLEND_MODE.LIGHTEN; + case 'color-dodge': + return MIX_BLEND_MODE.COLOR_DODGE; + case 'color-burn': + return MIX_BLEND_MODE.COLOR_BURN; + case 'hard-light': + return MIX_BLEND_MODE.HARD_LIGHT; + case 'soft-light': + return MIX_BLEND_MODE.SOFT_LIGHT; + case 'difference': + return MIX_BLEND_MODE.DIFFERENCE; + case 'exclusion': + return MIX_BLEND_MODE.EXCLUSION; + case 'hue': + return MIX_BLEND_MODE.HUE; + case 'saturation': + return MIX_BLEND_MODE.SATURATION; + case 'color': + return MIX_BLEND_MODE.COLOR; + case 'luminosity': + return MIX_BLEND_MODE.LUMINOSITY; + case 'initial': + return MIX_BLEND_MODE.INITIAL; + case 'inherit': + return MIX_BLEND_MODE.INHERIT; + case 'revert': + return MIX_BLEND_MODE.REVERT; + case 'unset': + return MIX_BLEND_MODE.UNSET; + case 'normal': + default: + return MIX_BLEND_MODE.NORMAL; + } + } +}; diff --git a/src/global.d.ts b/src/global.d.ts index 5e8b343..c0b8b39 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -2,6 +2,7 @@ interface CSSStyleDeclaration { textDecorationColor: string; textDecorationLine: string; overflowWrap: string; + mixBlendMode: string; } interface DocumentType extends Node, ChildNode { diff --git a/src/render/canvas/canvas-renderer.ts b/src/render/canvas/canvas-renderer.ts index 6efb648..5b378af 100644 --- a/src/render/canvas/canvas-renderer.ts +++ b/src/render/canvas/canvas-renderer.ts @@ -189,6 +189,7 @@ export class CanvasRenderer extends Renderer { switch (paintOrderLayer) { case PAINT_ORDER_LAYER.FILL: this.ctx.fillStyle = asString(styles.color); + this.ctx.globalCompositeOperation = styles.mixBlendMode; this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline); const textShadows: TextShadow = styles.textShadow; @@ -276,6 +277,7 @@ export class CanvasRenderer extends Renderer { this.path(path); this.ctx.save(); this.ctx.clip(); + this.ctx.globalCompositeOperation = container.styles.mixBlendMode; this.ctx.drawImage( image, 0, @@ -714,6 +716,7 @@ export class CanvasRenderer extends Renderer { if (!isTransparent(styles.backgroundColor)) { this.ctx.fillStyle = asString(styles.backgroundColor); + this.ctx.globalCompositeOperation = styles.mixBlendMode; this.ctx.fill(); } diff --git a/tests/reftests/mix-blend-mode.html b/tests/reftests/mix-blend-mode.html new file mode 100644 index 0000000..d7cc41d --- /dev/null +++ b/tests/reftests/mix-blend-mode.html @@ -0,0 +1,79 @@ + + + + Mix Blend Mode tests + + + + + +
+

Green circle: mix-blend-mode: screen

+

Blue circle: mix-blend-mode: color-burn

+
+
+
+
+
+
+
+

Red background with mix-blend-mode: overlay over a background image

+
+
+
+
+
+

Image with mix-blend-mode: multiply

+ +
+
+
+
+
+
+
+
+

+ mix-blend-mode: color; Large text over background colors. +

+
+ +