mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Implement linear-gradient rendering
This commit is contained in:
parent
44c336c1b3
commit
110c9b0eec
24
src/Angle.js
Normal file
24
src/Angle.js
Normal file
@ -0,0 +1,24 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
const ANGLE = /([+-]?\d*\.?\d+)(deg|grad|rad|turn)/i;
|
||||
|
||||
export const parseAngle = (angle: string): number | null => {
|
||||
const match = angle.match(ANGLE);
|
||||
|
||||
if (match) {
|
||||
const value = parseFloat(match[1]);
|
||||
switch (match[2].toLowerCase()) {
|
||||
case 'deg':
|
||||
return Math.PI * value / 180;
|
||||
case 'grad':
|
||||
return Math.PI / 200 * value;
|
||||
case 'rad':
|
||||
return value;
|
||||
case 'turn':
|
||||
return Math.PI * 2 * value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
@ -24,6 +24,7 @@ import {
|
||||
calculatePaddingBoxPath
|
||||
} from './Bounds';
|
||||
import {FontMetrics} from './Font';
|
||||
import {parseGradient} from './Gradient';
|
||||
import TextContainer from './TextContainer';
|
||||
|
||||
import {
|
||||
@ -211,6 +212,24 @@ export default class CanvasRenderer {
|
||||
container.style.background.backgroundImage.reverse().forEach(backgroundImage => {
|
||||
if (backgroundImage.source.method === 'url' && backgroundImage.source.args.length) {
|
||||
this.renderBackgroundRepeat(container, backgroundImage);
|
||||
} else {
|
||||
const gradient = parseGradient(backgroundImage.source, container.bounds);
|
||||
if (gradient) {
|
||||
const bounds = container.bounds;
|
||||
const grad = this.ctx.createLinearGradient(
|
||||
bounds.left + gradient.direction.x1,
|
||||
bounds.top + gradient.direction.y1,
|
||||
bounds.left + gradient.direction.x0,
|
||||
bounds.top + gradient.direction.y0
|
||||
);
|
||||
|
||||
gradient.colorStops.forEach(colorStop => {
|
||||
grad.addColorStop(colorStop.stop, colorStop.color.toString());
|
||||
});
|
||||
|
||||
this.ctx.fillStyle = grad;
|
||||
this.ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
165
src/Gradient.js
Normal file
165
src/Gradient.js
Normal file
@ -0,0 +1,165 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import type {BackgroundSource} from './parsing/background';
|
||||
import type {Bounds} from './Bounds';
|
||||
import {parseAngle} from './Angle';
|
||||
import Color from './Color';
|
||||
import Length, {LENGTH_TYPE} from './Length';
|
||||
|
||||
const SIDE_OR_CORNER = /^(to )?(left|top|right|bottom)( (left|top|right|bottom))?$/i;
|
||||
const ENDS_WITH_LENGTH = /(px)|%|( 0)$/i;
|
||||
|
||||
export type Direction = {
|
||||
x0: number,
|
||||
x1: number,
|
||||
y0: number,
|
||||
y1: number
|
||||
};
|
||||
|
||||
export type ColorStop = {
|
||||
color: Color,
|
||||
stop: number
|
||||
};
|
||||
|
||||
export type Gradient = {
|
||||
direction: Direction,
|
||||
colorStops: Array<ColorStop>
|
||||
};
|
||||
|
||||
export const parseGradient = ({args, method, prefix}: BackgroundSource, bounds: Bounds) => {
|
||||
if (method === 'linear-gradient') {
|
||||
return parseLinearGradient(args, bounds);
|
||||
}
|
||||
|
||||
// TODO: webkit-gradient syntax
|
||||
};
|
||||
|
||||
const parseLinearGradient = (args: Array<string>, bounds: Bounds): Gradient => {
|
||||
const angle = parseAngle(args[0]);
|
||||
const HAS_DIRECTION = SIDE_OR_CORNER.test(args[0]) || angle !== null;
|
||||
const direction = HAS_DIRECTION
|
||||
? angle !== null
|
||||
? calculateGradientDirection(angle, bounds)
|
||||
: parseSideOrCorner(args[0], bounds)
|
||||
: calculateGradientDirection(Math.PI, bounds);
|
||||
const colorStops = [];
|
||||
const firstColorStopIndex = HAS_DIRECTION ? 1 : 0;
|
||||
|
||||
for (let i = firstColorStopIndex; i < args.length; i++) {
|
||||
const value = args[i];
|
||||
const HAS_LENGTH = ENDS_WITH_LENGTH.test(value);
|
||||
const lastSpaceIndex = value.lastIndexOf(' ');
|
||||
const color = new Color(HAS_LENGTH ? value.substring(0, lastSpaceIndex) : value);
|
||||
const stop = HAS_LENGTH
|
||||
? new Length(value.substring(lastSpaceIndex + 1))
|
||||
: i === firstColorStopIndex
|
||||
? new Length('0%')
|
||||
: i === args.length - 1 ? new Length('100%') : null;
|
||||
colorStops.push({color, stop});
|
||||
}
|
||||
|
||||
// TODO: Fix some inaccuracy with color stops with px values
|
||||
const lineLength = Math.min(
|
||||
Math.sqrt(
|
||||
Math.pow(Math.abs(direction.x0) + Math.abs(direction.x1), 2) +
|
||||
Math.pow(Math.abs(direction.y0) + Math.abs(direction.y1), 2)
|
||||
),
|
||||
bounds.width * 2,
|
||||
bounds.height * 2
|
||||
);
|
||||
|
||||
const absoluteValuedColorStops = colorStops.map(({color, stop}) => {
|
||||
return {
|
||||
color,
|
||||
// $FlowFixMe
|
||||
stop: stop ? stop.getAbsoluteValue(lineLength) / lineLength : null
|
||||
};
|
||||
});
|
||||
|
||||
let previousColorStop = absoluteValuedColorStops[0].stop;
|
||||
for (let i = 0; i < absoluteValuedColorStops.length; i++) {
|
||||
if (previousColorStop !== null) {
|
||||
const stop = absoluteValuedColorStops[i].stop;
|
||||
if (stop === null) {
|
||||
let n = i;
|
||||
while (absoluteValuedColorStops[n].stop === null) {
|
||||
n++;
|
||||
}
|
||||
const steps = n - i + 1;
|
||||
const nextColorStep = absoluteValuedColorStops[n].stop;
|
||||
const stepSize = (nextColorStep - previousColorStop) / steps;
|
||||
for (; i < n; i++) {
|
||||
previousColorStop = absoluteValuedColorStops[i].stop =
|
||||
previousColorStop + stepSize;
|
||||
}
|
||||
} else {
|
||||
previousColorStop = stop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
direction,
|
||||
colorStops: absoluteValuedColorStops
|
||||
};
|
||||
};
|
||||
|
||||
const calculateGradientDirection = (radian: number, bounds: Bounds): Direction => {
|
||||
const width = bounds.width;
|
||||
const height = bounds.height;
|
||||
const HALF_WIDTH = width * 0.5;
|
||||
const HALF_HEIGHT = height * 0.5;
|
||||
const lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian));
|
||||
const HALF_LINE_LENGTH = lineLength / 2;
|
||||
|
||||
const x0 = HALF_WIDTH + Math.sin(radian) * HALF_LINE_LENGTH;
|
||||
const y0 = HALF_HEIGHT - Math.cos(radian) * HALF_LINE_LENGTH;
|
||||
const x1 = width - x0;
|
||||
const y1 = height - y0;
|
||||
|
||||
return {x0, x1, y0, y1};
|
||||
};
|
||||
|
||||
const parseTopRight = (bounds: Bounds) =>
|
||||
Math.acos(
|
||||
bounds.width / 2 / (Math.sqrt(Math.pow(bounds.width, 2) + Math.pow(bounds.height, 2)) / 2)
|
||||
);
|
||||
|
||||
const parseSideOrCorner = (side: string, bounds: Bounds): Direction => {
|
||||
switch (side) {
|
||||
case 'bottom':
|
||||
case 'to top':
|
||||
return calculateGradientDirection(0, bounds);
|
||||
case 'left':
|
||||
case 'to right':
|
||||
return calculateGradientDirection(Math.PI / 2, bounds);
|
||||
case 'right':
|
||||
case 'to left':
|
||||
return calculateGradientDirection(3 * Math.PI / 2, bounds);
|
||||
case 'top right':
|
||||
case 'right top':
|
||||
case 'to bottom left':
|
||||
case 'to left bottom':
|
||||
return calculateGradientDirection(Math.PI + parseTopRight(bounds), bounds);
|
||||
case 'top left':
|
||||
case 'left top':
|
||||
case 'to bottom right':
|
||||
case 'to right bottom':
|
||||
return calculateGradientDirection(Math.PI - parseTopRight(bounds), bounds);
|
||||
case 'bottom left':
|
||||
case 'left bottom':
|
||||
case 'to top right':
|
||||
case 'to right top':
|
||||
return calculateGradientDirection(parseTopRight(bounds), bounds);
|
||||
case 'bottom right':
|
||||
case 'right bottom':
|
||||
case 'to top left':
|
||||
case 'to left top':
|
||||
return calculateGradientDirection(2 * Math.PI - parseTopRight(bounds), bounds);
|
||||
case 'top':
|
||||
case 'to bottom':
|
||||
default:
|
||||
return calculateGradientDirection(Math.PI, bounds);
|
||||
}
|
||||
};
|
@ -335,7 +335,7 @@ const parseBackgroundImage = (image: string, imageLoader: ImageLoader): Array<Ba
|
||||
}
|
||||
|
||||
if (definition) {
|
||||
args.push(definition);
|
||||
args.push(definition.trim());
|
||||
}
|
||||
|
||||
const prefix_i = method.indexOf('-', 1) + 1;
|
||||
@ -402,7 +402,7 @@ const parseBackgroundImage = (image: string, imageLoader: ImageLoader): Array<Ba
|
||||
return;
|
||||
} else if (mode === 1) {
|
||||
if (numParen === 0 && !method.match(/^url$/i)) {
|
||||
args.push(definition);
|
||||
args.push(definition.trim());
|
||||
definition = '';
|
||||
return;
|
||||
}
|
||||
|
@ -16,14 +16,24 @@
|
||||
border:1px solid #000;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.medium div{
|
||||
width:200px;
|
||||
width:210px;
|
||||
height:200px;
|
||||
float:left;
|
||||
margin:10px;
|
||||
border:1px solid #000;
|
||||
}
|
||||
|
||||
.medium .horizontal div{
|
||||
width:200px;
|
||||
height:100px;
|
||||
}
|
||||
|
||||
.small, .medium{
|
||||
clear:both;
|
||||
}
|
||||
@ -110,15 +120,46 @@
|
||||
}
|
||||
|
||||
.linearGradient8 {
|
||||
background: linear-gradient(to top left, #fff 0%, #00263c 100%);
|
||||
background: linear-gradient(to bottom left, #fff 0%, #00263c 100%);
|
||||
}
|
||||
|
||||
.linearGradient9 {
|
||||
background: linear-gradient(to top left, white 0%, black 100%);
|
||||
background: linear-gradient(to bottom left, #0000Ff, rgb(255, 0,0) 50px, green 199px, rgba(0, 0, 0, 0.5) 100.0%);
|
||||
}
|
||||
|
||||
.linearGradient10 {
|
||||
background: linear-gradient(to left top, #0000Ff, rgb(255, 0,0) 50px, green 199px, rgba(0, 0, 0, 0.5) 100.0%);
|
||||
}
|
||||
|
||||
.linearGradient11 {
|
||||
background: linear-gradient(to bottom right, #0000Ff, rgb(255, 0,0) 50px, green 199px, rgba(0, 0, 0, 0.5) 100.0%);
|
||||
}
|
||||
|
||||
.linearGradient12 {
|
||||
background: linear-gradient(to top right, #0000Ff, rgb(255, 0,0) 50px, green 199px, rgba(0, 0, 0, 0.5) 100.0%);
|
||||
}
|
||||
|
||||
.linearGradient13 {
|
||||
background: linear-gradient(to top left, white 0%, black 100%);
|
||||
}
|
||||
|
||||
.linearGradient14 {
|
||||
background: linear-gradient(-375.5grad, yellow, blue, red 60%, blue);
|
||||
}
|
||||
|
||||
.linearGradient15 {
|
||||
background: linear-gradient(-375.5turn, yellow, red 60%, blue);
|
||||
}
|
||||
|
||||
.linearGradient16 {
|
||||
background: linear-gradient(-375.5rad, yellow, orange, red 60%, blue);
|
||||
}
|
||||
|
||||
.linearGradient17 {
|
||||
background: linear-gradient(-375.5deg, yellow, red 60%, blue);
|
||||
width: 800px !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
@ -134,6 +175,19 @@
|
||||
<div class="linearGradient8"> </div>
|
||||
<div class="linearGradient9"> </div>
|
||||
<div class="linearGradient10"> </div>
|
||||
<div class="linearGradient11"> </div>
|
||||
<div class="linearGradient12"> </div>
|
||||
<div class="horizontal">
|
||||
<div class="linearGradient9"> </div>
|
||||
<div class="linearGradient10"> </div>
|
||||
<div class="linearGradient11"> </div>
|
||||
<div class="linearGradient12"> </div>
|
||||
</div>
|
||||
<div class="linearGradient13"> </div>
|
||||
<div class="linearGradient14"> </div>
|
||||
<div class="linearGradient15"> </div>
|
||||
<div class="linearGradient16"> </div>
|
||||
<div class="linearGradient17"> </div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
x
Reference in New Issue
Block a user