mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Merge branch 'vnmc-feature/HTC-0009_RadialGradients'
This commit is contained in:
commit
261702a693
@ -1,6 +1,7 @@
|
|||||||
### Changelog ###
|
### Changelog ###
|
||||||
|
|
||||||
#### v1.0.0-alpha4 - TBD ####
|
#### v1.0.0-alpha4 - TBD ####
|
||||||
|
* Add support for radial-gradients
|
||||||
* Fix logging option (#1302)
|
* Fix logging option (#1302)
|
||||||
* Add support for rendering webgl canvas content (#646)
|
* Add support for rendering webgl canvas content (#646)
|
||||||
* Fix external SVG loading with proxies (#802)
|
* Fix external SVG loading with proxies (#802)
|
||||||
|
@ -11,6 +11,7 @@ Below is a list of all the supported CSS properties and values.
|
|||||||
- background-image
|
- background-image
|
||||||
- url()
|
- url()
|
||||||
- linear-gradient()
|
- linear-gradient()
|
||||||
|
- radial-gradient()
|
||||||
- background-origin
|
- background-origin
|
||||||
- background-position
|
- background-position
|
||||||
- background-size
|
- background-size
|
||||||
@ -68,7 +69,6 @@ These CSS properties are **NOT** currently supported
|
|||||||
- [filter](https://github.com/niklasvh/html2canvas/issues/493)
|
- [filter](https://github.com/niklasvh/html2canvas/issues/493)
|
||||||
- [font-variant-ligatures](https://github.com/niklasvh/html2canvas/pull/1085)
|
- [font-variant-ligatures](https://github.com/niklasvh/html2canvas/pull/1085)
|
||||||
- [list-style](https://github.com/niklasvh/html2canvas/issues/177)
|
- [list-style](https://github.com/niklasvh/html2canvas/issues/177)
|
||||||
- radial-gradient()
|
|
||||||
- [repeating-linear-gradient()](https://github.com/niklasvh/html2canvas/issues/1162)
|
- [repeating-linear-gradient()](https://github.com/niklasvh/html2canvas/issues/1162)
|
||||||
- word-break
|
- word-break
|
||||||
- [writing-mode](https://github.com/niklasvh/html2canvas/issues/1258)
|
- [writing-mode](https://github.com/niklasvh/html2canvas/issues/1258)
|
||||||
|
442
src/Gradient.js
442
src/Gradient.js
@ -3,14 +3,22 @@
|
|||||||
|
|
||||||
import type {BackgroundSource} from './parsing/background';
|
import type {BackgroundSource} from './parsing/background';
|
||||||
import type {Bounds} from './Bounds';
|
import type {Bounds} from './Bounds';
|
||||||
|
import NodeContainer from './NodeContainer';
|
||||||
import {parseAngle} from './Angle';
|
import {parseAngle} from './Angle';
|
||||||
import Color from './Color';
|
import Color from './Color';
|
||||||
import Length, {LENGTH_TYPE} from './Length';
|
import Length, {LENGTH_TYPE, calculateLengthFromValueWithUnit} from './Length';
|
||||||
|
import {distance} from './Util';
|
||||||
|
|
||||||
const SIDE_OR_CORNER = /^(to )?(left|top|right|bottom)( (left|top|right|bottom))?$/i;
|
const SIDE_OR_CORNER = /^(to )?(left|top|right|bottom)( (left|top|right|bottom))?$/i;
|
||||||
const PERCENTAGE_ANGLES = /^([+-]?\d*\.?\d+)% ([+-]?\d*\.?\d+)%$/i;
|
const PERCENTAGE_ANGLES = /^([+-]?\d*\.?\d+)% ([+-]?\d*\.?\d+)%$/i;
|
||||||
const ENDS_WITH_LENGTH = /(px)|%|( 0)$/i;
|
const ENDS_WITH_LENGTH = /(px)|%|( 0)$/i;
|
||||||
const FROM_TO = /^(from|to)\((.+)\)$/i;
|
const FROM_TO_COLORSTOP = /^(from|to|color-stop)\((?:([\d.]+)(%)?,\s*)?(.+?)\)$/i;
|
||||||
|
const RADIAL_SHAPE_DEFINITION = /^\s*(circle|ellipse)?\s*((?:([\d.]+)(px|r?em|%)\s*(?:([\d.]+)(px|r?em|%))?)|closest-side|closest-corner|farthest-side|farthest-corner)?\s*(?:at\s*(?:(left|center|right)|([\d.]+)(px|r?em|%))\s+(?:(top|center|bottom)|([\d.]+)(px|r?em|%)))?(?:\s|$)/i;
|
||||||
|
|
||||||
|
export type Point = {
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
};
|
||||||
|
|
||||||
export type Direction = {
|
export type Direction = {
|
||||||
x0: number,
|
x0: number,
|
||||||
@ -24,12 +32,68 @@ export type ColorStop = {
|
|||||||
stop: number
|
stop: number
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Gradient = {
|
export interface Gradient {
|
||||||
direction: Direction,
|
type: GradientType,
|
||||||
colorStops: Array<ColorStop>
|
colorStops: Array<ColorStop>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GRADIENT_TYPE = {
|
||||||
|
LINEAR_GRADIENT: 0,
|
||||||
|
RADIAL_GRADIENT: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GradientType = $Values<typeof GRADIENT_TYPE>;
|
||||||
|
|
||||||
|
export const RADIAL_GRADIENT_SHAPE = {
|
||||||
|
CIRCLE: 0,
|
||||||
|
ELLIPSE: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RadialGradientShapeType = $Values<typeof RADIAL_GRADIENT_SHAPE>;
|
||||||
|
|
||||||
|
const LENGTH_FOR_POSITION = {
|
||||||
|
left: new Length('0%'),
|
||||||
|
top: new Length('0%'),
|
||||||
|
center: new Length('50%'),
|
||||||
|
right: new Length('100%'),
|
||||||
|
bottom: new Length('100%')
|
||||||
|
};
|
||||||
|
|
||||||
|
export class LinearGradient implements Gradient {
|
||||||
|
type: GradientType;
|
||||||
|
colorStops: Array<ColorStop>;
|
||||||
|
direction: Direction;
|
||||||
|
|
||||||
|
constructor(colorStops: Array<ColorStop>, direction: Direction) {
|
||||||
|
this.type = GRADIENT_TYPE.LINEAR_GRADIENT;
|
||||||
|
this.colorStops = colorStops;
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RadialGradient implements Gradient {
|
||||||
|
type: GradientType;
|
||||||
|
colorStops: Array<ColorStop>;
|
||||||
|
shape: RadialGradientShapeType;
|
||||||
|
center: Point;
|
||||||
|
radius: Point;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
colorStops: Array<ColorStop>,
|
||||||
|
shape: RadialGradientShapeType,
|
||||||
|
center: Point,
|
||||||
|
radius: Point
|
||||||
|
) {
|
||||||
|
this.type = GRADIENT_TYPE.RADIAL_GRADIENT;
|
||||||
|
this.colorStops = colorStops;
|
||||||
|
this.shape = shape;
|
||||||
|
this.center = center;
|
||||||
|
this.radius = radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const parseGradient = (
|
export const parseGradient = (
|
||||||
|
container: NodeContainer,
|
||||||
{args, method, prefix}: BackgroundSource,
|
{args, method, prefix}: BackgroundSource,
|
||||||
bounds: Bounds
|
bounds: Bounds
|
||||||
): ?Gradient => {
|
): ?Gradient => {
|
||||||
@ -38,37 +102,27 @@ export const parseGradient = (
|
|||||||
} else if (method === 'gradient' && args[0] === 'linear') {
|
} else if (method === 'gradient' && args[0] === 'linear') {
|
||||||
// TODO handle correct angle
|
// TODO handle correct angle
|
||||||
return parseLinearGradient(
|
return parseLinearGradient(
|
||||||
['to bottom'].concat(
|
['to bottom'].concat(transformObsoleteColorStops(args.slice(3))),
|
||||||
args
|
|
||||||
.slice(3)
|
|
||||||
.map(color => color.match(FROM_TO))
|
|
||||||
.filter(v => v !== null)
|
|
||||||
// $FlowFixMe
|
|
||||||
.map(v => v[2])
|
|
||||||
),
|
|
||||||
bounds,
|
bounds,
|
||||||
!!prefix
|
!!prefix
|
||||||
);
|
);
|
||||||
|
} else if (method === 'radial-gradient') {
|
||||||
|
return parseRadialGradient(
|
||||||
|
container,
|
||||||
|
prefix === '-webkit-' ? transformWebkitRadialGradientArgs(args) : args,
|
||||||
|
bounds
|
||||||
|
);
|
||||||
|
} else if (method === 'gradient' && args[0] === 'radial') {
|
||||||
|
return parseRadialGradient(
|
||||||
|
container,
|
||||||
|
transformObsoleteColorStops(transformWebkitRadialGradientArgs(args.slice(1))),
|
||||||
|
bounds
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseLinearGradient = (args: Array<string>, bounds: Bounds, hasPrefix: boolean): Gradient => {
|
const parseColorStops = (args: Array<string>, firstColorStopIndex: number, lineLength: number) => {
|
||||||
const angle = parseAngle(args[0]);
|
|
||||||
const HAS_SIDE_OR_CORNER = SIDE_OR_CORNER.test(args[0]);
|
|
||||||
const HAS_DIRECTION = HAS_SIDE_OR_CORNER || angle !== null || PERCENTAGE_ANGLES.test(args[0]);
|
|
||||||
const direction = HAS_DIRECTION
|
|
||||||
? angle !== null
|
|
||||||
? calculateGradientDirection(
|
|
||||||
// if there is a prefix, the 0° angle points due East (instead of North per W3C)
|
|
||||||
hasPrefix ? angle - Math.PI * 0.5 : angle,
|
|
||||||
bounds
|
|
||||||
)
|
|
||||||
: HAS_SIDE_OR_CORNER
|
|
||||||
? parseSideOrCorner(args[0], bounds)
|
|
||||||
: parsePercentageAngle(args[0], bounds)
|
|
||||||
: calculateGradientDirection(Math.PI, bounds);
|
|
||||||
const colorStops = [];
|
const colorStops = [];
|
||||||
const firstColorStopIndex = HAS_DIRECTION ? 1 : 0;
|
|
||||||
|
|
||||||
for (let i = firstColorStopIndex; i < args.length; i++) {
|
for (let i = firstColorStopIndex; i < args.length; i++) {
|
||||||
const value = args[i];
|
const value = args[i];
|
||||||
@ -83,16 +137,6 @@ const parseLinearGradient = (args: Array<string>, bounds: Bounds, hasPrefix: boo
|
|||||||
colorStops.push({color, stop});
|
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}) => {
|
const absoluteValuedColorStops = colorStops.map(({color, stop}) => {
|
||||||
return {
|
return {
|
||||||
color,
|
color,
|
||||||
@ -123,10 +167,104 @@ const parseLinearGradient = (args: Array<string>, bounds: Bounds, hasPrefix: boo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return absoluteValuedColorStops;
|
||||||
direction,
|
};
|
||||||
colorStops: absoluteValuedColorStops
|
|
||||||
|
const parseLinearGradient = (
|
||||||
|
args: Array<string>,
|
||||||
|
bounds: Bounds,
|
||||||
|
hasPrefix: boolean
|
||||||
|
): LinearGradient => {
|
||||||
|
const angle = parseAngle(args[0]);
|
||||||
|
const HAS_SIDE_OR_CORNER = SIDE_OR_CORNER.test(args[0]);
|
||||||
|
const HAS_DIRECTION = HAS_SIDE_OR_CORNER || angle !== null || PERCENTAGE_ANGLES.test(args[0]);
|
||||||
|
const direction = HAS_DIRECTION
|
||||||
|
? angle !== null
|
||||||
|
? calculateGradientDirection(
|
||||||
|
// if there is a prefix, the 0° angle points due East (instead of North per W3C)
|
||||||
|
hasPrefix ? angle - Math.PI * 0.5 : angle,
|
||||||
|
bounds
|
||||||
|
)
|
||||||
|
: HAS_SIDE_OR_CORNER
|
||||||
|
? parseSideOrCorner(args[0], bounds)
|
||||||
|
: parsePercentageAngle(args[0], bounds)
|
||||||
|
: calculateGradientDirection(Math.PI, bounds);
|
||||||
|
const firstColorStopIndex = HAS_DIRECTION ? 1 : 0;
|
||||||
|
|
||||||
|
// TODO: Fix some inaccuracy with color stops with px values
|
||||||
|
const lineLength = Math.min(
|
||||||
|
distance(
|
||||||
|
Math.abs(direction.x0) + Math.abs(direction.x1),
|
||||||
|
Math.abs(direction.y0) + Math.abs(direction.y1)
|
||||||
|
),
|
||||||
|
bounds.width * 2,
|
||||||
|
bounds.height * 2
|
||||||
|
);
|
||||||
|
|
||||||
|
return new LinearGradient(parseColorStops(args, firstColorStopIndex, lineLength), direction);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseRadialGradient = (
|
||||||
|
container: NodeContainer,
|
||||||
|
args: Array<string>,
|
||||||
|
bounds: Bounds
|
||||||
|
): RadialGradient => {
|
||||||
|
const m = args[0].match(RADIAL_SHAPE_DEFINITION);
|
||||||
|
const shape =
|
||||||
|
m &&
|
||||||
|
(m[1] === 'circle' || // explicit shape specification
|
||||||
|
(m[3] !== undefined && m[5] === undefined)) // only one radius coordinate
|
||||||
|
? RADIAL_GRADIENT_SHAPE.CIRCLE
|
||||||
|
: RADIAL_GRADIENT_SHAPE.ELLIPSE;
|
||||||
|
const radius = {};
|
||||||
|
const center = {};
|
||||||
|
|
||||||
|
if (m) {
|
||||||
|
// Radius
|
||||||
|
if (m[3] !== undefined) {
|
||||||
|
radius.x = calculateLengthFromValueWithUnit(container, m[3], m[4]).getAbsoluteValue(
|
||||||
|
bounds.width
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m[5] !== undefined) {
|
||||||
|
radius.y = calculateLengthFromValueWithUnit(container, m[5], m[6]).getAbsoluteValue(
|
||||||
|
bounds.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position
|
||||||
|
if (m[7]) {
|
||||||
|
center.x = LENGTH_FOR_POSITION[m[7].toLowerCase()];
|
||||||
|
} else if (m[8] !== undefined) {
|
||||||
|
center.x = calculateLengthFromValueWithUnit(container, m[8], m[9]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m[10]) {
|
||||||
|
center.y = LENGTH_FOR_POSITION[m[10].toLowerCase()];
|
||||||
|
} else if (m[11] !== undefined) {
|
||||||
|
center.y = calculateLengthFromValueWithUnit(container, m[11], m[12]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const gradientCenter = {
|
||||||
|
x: center.x === undefined ? bounds.width / 2 : center.x.getAbsoluteValue(bounds.width),
|
||||||
|
y: center.y === undefined ? bounds.height / 2 : center.y.getAbsoluteValue(bounds.height)
|
||||||
};
|
};
|
||||||
|
const gradientRadius = calculateRadius(
|
||||||
|
(m && m[2]) || 'farthest-corner',
|
||||||
|
shape,
|
||||||
|
gradientCenter,
|
||||||
|
radius,
|
||||||
|
bounds
|
||||||
|
);
|
||||||
|
|
||||||
|
return new RadialGradient(
|
||||||
|
parseColorStops(args, m ? 1 : 0, Math.min(gradientRadius.x, gradientRadius.y)),
|
||||||
|
shape,
|
||||||
|
gradientCenter,
|
||||||
|
gradientRadius
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateGradientDirection = (radian: number, bounds: Bounds): Direction => {
|
const calculateGradientDirection = (radian: number, bounds: Bounds): Direction => {
|
||||||
@ -146,9 +284,7 @@ const calculateGradientDirection = (radian: number, bounds: Bounds): Direction =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const parseTopRight = (bounds: Bounds) =>
|
const parseTopRight = (bounds: Bounds) =>
|
||||||
Math.acos(
|
Math.acos(bounds.width / 2 / (distance(bounds.width, bounds.height) / 2));
|
||||||
bounds.width / 2 / (Math.sqrt(Math.pow(bounds.width, 2) + Math.pow(bounds.height, 2)) / 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
const parseSideOrCorner = (side: string, bounds: Bounds): Direction => {
|
const parseSideOrCorner = (side: string, bounds: Bounds): Direction => {
|
||||||
switch (side) {
|
switch (side) {
|
||||||
@ -194,3 +330,221 @@ const parsePercentageAngle = (angle: string, bounds: Bounds): Direction => {
|
|||||||
|
|
||||||
return calculateGradientDirection(Math.atan(isNaN(ratio) ? 1 : ratio) + Math.PI / 2, bounds);
|
return calculateGradientDirection(Math.atan(isNaN(ratio) ? 1 : ratio) + Math.PI / 2, bounds);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findCorner = (bounds: Bounds, x: number, y: number, closest: boolean): Point => {
|
||||||
|
var corners = [
|
||||||
|
{x: 0, y: 0},
|
||||||
|
{x: 0, y: bounds.height},
|
||||||
|
{x: bounds.width, y: 0},
|
||||||
|
{x: bounds.width, y: bounds.height}
|
||||||
|
];
|
||||||
|
|
||||||
|
let optimumDistance = closest ? Infinity : -Infinity;
|
||||||
|
let optimumCorner = null;
|
||||||
|
|
||||||
|
for (let corner of corners) {
|
||||||
|
const d = distance(x - corner.x, y - corner.y);
|
||||||
|
if (closest ? d < optimumDistance : d > optimumDistance) {
|
||||||
|
optimumDistance = d;
|
||||||
|
optimumCorner = corner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
return optimumCorner;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateRadius = (
|
||||||
|
extent: string,
|
||||||
|
shape: RadialGradientShapeType,
|
||||||
|
center: Point,
|
||||||
|
radius: Point,
|
||||||
|
bounds: Bounds
|
||||||
|
): Point => {
|
||||||
|
const x = center.x;
|
||||||
|
const y = center.y;
|
||||||
|
let rx = 0;
|
||||||
|
let ry = 0;
|
||||||
|
|
||||||
|
switch (extent) {
|
||||||
|
case 'closest-side':
|
||||||
|
// The ending shape is sized so that that it exactly meets the side of the gradient box closest to the gradient’s center.
|
||||||
|
// If the shape is an ellipse, it exactly meets the closest side in each dimension.
|
||||||
|
if (shape === RADIAL_GRADIENT_SHAPE.CIRCLE) {
|
||||||
|
rx = ry = Math.min(
|
||||||
|
Math.abs(x),
|
||||||
|
Math.abs(x - bounds.width),
|
||||||
|
Math.abs(y),
|
||||||
|
Math.abs(y - bounds.height)
|
||||||
|
);
|
||||||
|
} else if (shape === RADIAL_GRADIENT_SHAPE.ELLIPSE) {
|
||||||
|
rx = Math.min(Math.abs(x), Math.abs(x - bounds.width));
|
||||||
|
ry = Math.min(Math.abs(y), Math.abs(y - bounds.height));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'closest-corner':
|
||||||
|
// The ending shape is sized so that that it passes through the corner of the gradient box closest to the gradient’s center.
|
||||||
|
// If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified.
|
||||||
|
if (shape === RADIAL_GRADIENT_SHAPE.CIRCLE) {
|
||||||
|
rx = ry = Math.min(
|
||||||
|
distance(x, y),
|
||||||
|
distance(x, y - bounds.height),
|
||||||
|
distance(x - bounds.width, y),
|
||||||
|
distance(x - bounds.width, y - bounds.height)
|
||||||
|
);
|
||||||
|
} else if (shape === RADIAL_GRADIENT_SHAPE.ELLIPSE) {
|
||||||
|
// Compute the ratio ry/rx (which is to be the same as for "closest-side")
|
||||||
|
const c =
|
||||||
|
Math.min(Math.abs(y), Math.abs(y - bounds.height)) /
|
||||||
|
Math.min(Math.abs(x), Math.abs(x - bounds.width));
|
||||||
|
const corner = findCorner(bounds, x, y, true);
|
||||||
|
rx = distance(corner.x - x, (corner.y - y) / c);
|
||||||
|
ry = c * rx;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'farthest-side':
|
||||||
|
// Same as closest-side, except the ending shape is sized based on the farthest side(s)
|
||||||
|
if (shape === RADIAL_GRADIENT_SHAPE.CIRCLE) {
|
||||||
|
rx = ry = Math.max(
|
||||||
|
Math.abs(x),
|
||||||
|
Math.abs(x - bounds.width),
|
||||||
|
Math.abs(y),
|
||||||
|
Math.abs(y - bounds.height)
|
||||||
|
);
|
||||||
|
} else if (shape === RADIAL_GRADIENT_SHAPE.ELLIPSE) {
|
||||||
|
rx = Math.max(Math.abs(x), Math.abs(x - bounds.width));
|
||||||
|
ry = Math.max(Math.abs(y), Math.abs(y - bounds.height));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'farthest-corner':
|
||||||
|
// Same as closest-corner, except the ending shape is sized based on the farthest corner.
|
||||||
|
// If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified.
|
||||||
|
if (shape === RADIAL_GRADIENT_SHAPE.CIRCLE) {
|
||||||
|
rx = ry = Math.max(
|
||||||
|
distance(x, y),
|
||||||
|
distance(x, y - bounds.height),
|
||||||
|
distance(x - bounds.width, y),
|
||||||
|
distance(x - bounds.width, y - bounds.height)
|
||||||
|
);
|
||||||
|
} else if (shape === RADIAL_GRADIENT_SHAPE.ELLIPSE) {
|
||||||
|
// Compute the ratio ry/rx (which is to be the same as for "farthest-side")
|
||||||
|
const c =
|
||||||
|
Math.max(Math.abs(y), Math.abs(y - bounds.height)) /
|
||||||
|
Math.max(Math.abs(x), Math.abs(x - bounds.width));
|
||||||
|
const corner = findCorner(bounds, x, y, false);
|
||||||
|
rx = distance(corner.x - x, (corner.y - y) / c);
|
||||||
|
ry = c * rx;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// pixel or percentage values
|
||||||
|
rx = radius.x || 0;
|
||||||
|
ry = radius.y !== undefined ? radius.y : rx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: rx,
|
||||||
|
y: ry
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformWebkitRadialGradientArgs = (args: Array<string>): Array<string> => {
|
||||||
|
let shape = '';
|
||||||
|
let radius = '';
|
||||||
|
let extent = '';
|
||||||
|
let position = '';
|
||||||
|
let idx = 0;
|
||||||
|
|
||||||
|
const POSITION = /^(left|center|right|\d+(?:px|r?em|%)?)(?:\s+(top|center|bottom|\d+(?:px|r?em|%)?))?$/i;
|
||||||
|
const SHAPE_AND_EXTENT = /^(circle|ellipse)?\s*(closest-side|closest-corner|farthest-side|farthest-corner|contain|cover)?$/i;
|
||||||
|
const RADIUS = /^\d+(px|r?em|%)?(?:\s+\d+(px|r?em|%)?)?$/i;
|
||||||
|
|
||||||
|
const matchStartPosition = args[idx].match(POSITION);
|
||||||
|
if (matchStartPosition) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchShapeExtent = args[idx].match(SHAPE_AND_EXTENT);
|
||||||
|
if (matchShapeExtent) {
|
||||||
|
shape = matchShapeExtent[1] || '';
|
||||||
|
extent = matchShapeExtent[2] || '';
|
||||||
|
if (extent === 'contain') {
|
||||||
|
extent = 'closest-side';
|
||||||
|
} else if (extent === 'cover') {
|
||||||
|
extent = 'farthest-corner';
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchStartRadius = args[idx].match(RADIUS);
|
||||||
|
if (matchStartRadius) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchEndPosition = args[idx].match(POSITION);
|
||||||
|
if (matchEndPosition) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchEndRadius = args[idx].match(RADIUS);
|
||||||
|
if (matchEndRadius) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchPosition = matchEndPosition || matchStartPosition;
|
||||||
|
if (matchPosition && matchPosition[1]) {
|
||||||
|
position = matchPosition[1] + (/^\d+$/.test(matchPosition[1]) ? 'px' : '');
|
||||||
|
if (matchPosition[2]) {
|
||||||
|
position += ' ' + matchPosition[2] + (/^\d+$/.test(matchPosition[2]) ? 'px' : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchRadius = matchEndRadius || matchStartRadius;
|
||||||
|
if (matchRadius) {
|
||||||
|
radius = matchRadius[0];
|
||||||
|
if (!matchRadius[1]) {
|
||||||
|
radius += 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position && !shape && !radius && !extent) {
|
||||||
|
radius = position;
|
||||||
|
position = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position) {
|
||||||
|
position = `at ${position}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [[shape, extent, radius, position].filter(s => !!s).join(' ')].concat(args.slice(idx));
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformObsoleteColorStops = (args: Array<string>): Array<string> => {
|
||||||
|
return (
|
||||||
|
args
|
||||||
|
.map(color => color.match(FROM_TO_COLORSTOP))
|
||||||
|
// $FlowFixMe
|
||||||
|
.map((v: Array<string>, index: number) => {
|
||||||
|
if (!v) {
|
||||||
|
return args[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (v[1]) {
|
||||||
|
case 'from':
|
||||||
|
return `${v[4]} 0%`;
|
||||||
|
case 'to':
|
||||||
|
return `${v[4]} 100%`;
|
||||||
|
case 'color-stop':
|
||||||
|
if (v[3] === '%') {
|
||||||
|
return `${v[4]} ${v[2]}`;
|
||||||
|
}
|
||||||
|
return `${v[4]} ${parseFloat(v[2]) * 100}%`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import NodeContainer from './NodeContainer';
|
||||||
|
|
||||||
|
const LENGTH_WITH_UNIT = /([\d.]+)(px|r?em|%)/i;
|
||||||
|
|
||||||
export const LENGTH_TYPE = {
|
export const LENGTH_TYPE = {
|
||||||
PX: 0,
|
PX: 0,
|
||||||
PERCENTAGE: 1
|
PERCENTAGE: 1
|
||||||
@ -34,3 +38,31 @@ export default class Length {
|
|||||||
return new Length(v);
|
return new Length(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getRootFontSize = (container: NodeContainer): number => {
|
||||||
|
const parent = container.parent;
|
||||||
|
return parent ? getRootFontSize(parent) : parseFloat(container.style.font.fontSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const calculateLengthFromValueWithUnit = (
|
||||||
|
container: NodeContainer,
|
||||||
|
value: string,
|
||||||
|
unit: string
|
||||||
|
): Length => {
|
||||||
|
switch (unit) {
|
||||||
|
case 'px':
|
||||||
|
case '%':
|
||||||
|
return new Length(value + unit);
|
||||||
|
case 'em':
|
||||||
|
case 'rem':
|
||||||
|
const length = new Length(value);
|
||||||
|
length.value *=
|
||||||
|
unit === 'em'
|
||||||
|
? parseFloat(container.style.font.fontSize)
|
||||||
|
: getRootFontSize(container);
|
||||||
|
return length;
|
||||||
|
default:
|
||||||
|
// TODO: handle correctly if unknown unit is used
|
||||||
|
return new Length('0');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -14,7 +14,7 @@ import type {TextShadow} from './parsing/textShadow';
|
|||||||
import type {Matrix} from './parsing/transform';
|
import type {Matrix} from './parsing/transform';
|
||||||
|
|
||||||
import type {BoundCurves} from './Bounds';
|
import type {BoundCurves} from './Bounds';
|
||||||
import type {Gradient} from './Gradient';
|
import type {LinearGradient, RadialGradient} from './Gradient';
|
||||||
import type {ResourceStore, ImageElement} from './ResourceLoader';
|
import type {ResourceStore, ImageElement} from './ResourceLoader';
|
||||||
import type NodeContainer from './NodeContainer';
|
import type NodeContainer from './NodeContainer';
|
||||||
import type StackingContext from './StackingContext';
|
import type StackingContext from './StackingContext';
|
||||||
@ -22,7 +22,7 @@ import type {TextBounds} from './TextBounds';
|
|||||||
|
|
||||||
import {Bounds, parsePathForBorder, calculateContentBox, calculatePaddingBoxPath} from './Bounds';
|
import {Bounds, parsePathForBorder, calculateContentBox, calculatePaddingBoxPath} from './Bounds';
|
||||||
import {FontMetrics} from './Font';
|
import {FontMetrics} from './Font';
|
||||||
import {parseGradient} from './Gradient';
|
import {parseGradient, GRADIENT_TYPE} from './Gradient';
|
||||||
import TextContainer from './TextContainer';
|
import TextContainer from './TextContainer';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -62,7 +62,9 @@ export interface RenderTarget<Output> {
|
|||||||
|
|
||||||
render(options: RenderOptions): void,
|
render(options: RenderOptions): void,
|
||||||
|
|
||||||
renderLinearGradient(bounds: Bounds, gradient: Gradient): void,
|
renderLinearGradient(bounds: Bounds, gradient: LinearGradient): void,
|
||||||
|
|
||||||
|
renderRadialGradient(bounds: Bounds, gradient: RadialGradient): void,
|
||||||
|
|
||||||
renderRepeat(
|
renderRepeat(
|
||||||
path: Path,
|
path: Path,
|
||||||
@ -265,9 +267,18 @@ export default class Renderer {
|
|||||||
backgroundImageSize.height
|
backgroundImageSize.height
|
||||||
);
|
);
|
||||||
|
|
||||||
const gradient = parseGradient(background.source, gradientBounds);
|
const gradient = parseGradient(container, background.source, gradientBounds);
|
||||||
if (gradient) {
|
if (gradient) {
|
||||||
this.target.renderLinearGradient(gradientBounds, gradient);
|
switch (gradient.type) {
|
||||||
|
case GRADIENT_TYPE.LINEAR_GRADIENT:
|
||||||
|
// $FlowFixMe
|
||||||
|
this.target.renderLinearGradient(gradientBounds, gradient);
|
||||||
|
break;
|
||||||
|
case GRADIENT_TYPE.RADIAL_GRADIENT:
|
||||||
|
// $FlowFixMe
|
||||||
|
this.target.renderRadialGradient(gradientBounds, gradient);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
export const contains = (bit: number, value: number): boolean => (bit & value) !== 0;
|
export const contains = (bit: number, value: number): boolean => (bit & value) !== 0;
|
||||||
|
|
||||||
|
export const distance = (a: number, b: number): number => Math.sqrt(a * a + b * b);
|
||||||
|
|
||||||
export const copyCSSStyles = (style: CSSStyleDeclaration, target: HTMLElement): HTMLElement => {
|
export const copyCSSStyles = (style: CSSStyleDeclaration, target: HTMLElement): HTMLElement => {
|
||||||
// Edge does not provide value for cssText
|
// Edge does not provide value for cssText
|
||||||
for (let i = style.length - 1; i >= 0; i--) {
|
for (let i = style.length - 1; i >= 0; i--) {
|
||||||
|
@ -13,12 +13,23 @@ import type {Matrix} from '../parsing/transform';
|
|||||||
|
|
||||||
import type {Bounds} from '../Bounds';
|
import type {Bounds} from '../Bounds';
|
||||||
import type {ImageElement} from '../ResourceLoader';
|
import type {ImageElement} from '../ResourceLoader';
|
||||||
import type {Gradient} from '../Gradient';
|
import type {LinearGradient, RadialGradient} from '../Gradient';
|
||||||
import type {TextBounds} from '../TextBounds';
|
import type {TextBounds} from '../TextBounds';
|
||||||
|
|
||||||
import {PATH} from '../drawing/Path';
|
import {PATH} from '../drawing/Path';
|
||||||
import {TEXT_DECORATION_LINE} from '../parsing/textDecoration';
|
import {TEXT_DECORATION_LINE} from '../parsing/textDecoration';
|
||||||
|
|
||||||
|
const addColorStops = (
|
||||||
|
gradient: LinearGradient | RadialGradient,
|
||||||
|
canvasGradient: CanvasGradient
|
||||||
|
): void => {
|
||||||
|
const maxStop = Math.max.apply(null, gradient.colorStops.map(colorStop => colorStop.stop));
|
||||||
|
const f = 1 / Math.max(1, maxStop);
|
||||||
|
gradient.colorStops.forEach(colorStop => {
|
||||||
|
canvasGradient.addColorStop(f * colorStop.stop, colorStop.color.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
|
export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
ctx: CanvasRenderingContext2D;
|
ctx: CanvasRenderingContext2D;
|
||||||
@ -131,7 +142,7 @@ export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
|
|||||||
this.ctx.fillRect(x, y, width, height);
|
this.ctx.fillRect(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLinearGradient(bounds: Bounds, gradient: Gradient) {
|
renderLinearGradient(bounds: Bounds, gradient: LinearGradient) {
|
||||||
const linearGradient = this.ctx.createLinearGradient(
|
const linearGradient = this.ctx.createLinearGradient(
|
||||||
bounds.left + gradient.direction.x1,
|
bounds.left + gradient.direction.x1,
|
||||||
bounds.top + gradient.direction.y1,
|
bounds.top + gradient.direction.y1,
|
||||||
@ -139,14 +150,43 @@ export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
|
|||||||
bounds.top + gradient.direction.y0
|
bounds.top + gradient.direction.y0
|
||||||
);
|
);
|
||||||
|
|
||||||
gradient.colorStops.forEach(colorStop => {
|
addColorStops(gradient, linearGradient);
|
||||||
linearGradient.addColorStop(colorStop.stop, colorStop.color.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ctx.fillStyle = linearGradient;
|
this.ctx.fillStyle = linearGradient;
|
||||||
this.ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height);
|
this.ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderRadialGradient(bounds: Bounds, gradient: RadialGradient) {
|
||||||
|
const x = bounds.left + gradient.center.x;
|
||||||
|
const y = bounds.top + gradient.center.y;
|
||||||
|
|
||||||
|
const radialGradient = this.ctx.createRadialGradient(x, y, 0, x, y, gradient.radius.x);
|
||||||
|
if (!radialGradient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addColorStops(gradient, radialGradient);
|
||||||
|
this.ctx.fillStyle = radialGradient;
|
||||||
|
|
||||||
|
if (gradient.radius.x !== gradient.radius.y) {
|
||||||
|
// transforms for elliptical radial gradient
|
||||||
|
const midX = bounds.left + 0.5 * bounds.width;
|
||||||
|
const midY = bounds.top + 0.5 * bounds.height;
|
||||||
|
const f = gradient.radius.y / gradient.radius.x;
|
||||||
|
const invF = 1 / f;
|
||||||
|
|
||||||
|
this.transform(midX, midY, [1, 0, 0, f, 0, 0], () =>
|
||||||
|
this.ctx.fillRect(
|
||||||
|
bounds.left,
|
||||||
|
invF * (bounds.top - midY) + midY,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height * invF
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderRepeat(
|
renderRepeat(
|
||||||
path: Path,
|
path: Path,
|
||||||
image: ImageElement,
|
image: ImageElement,
|
||||||
|
@ -18,7 +18,7 @@ import type {Matrix} from '../parsing/transform';
|
|||||||
|
|
||||||
import type {Bounds} from '../Bounds';
|
import type {Bounds} from '../Bounds';
|
||||||
import type {ImageElement} from '../ResourceLoader';
|
import type {ImageElement} from '../ResourceLoader';
|
||||||
import type {Gradient} from '../Gradient';
|
import type {LinearGradient, RadialGradient} from '../Gradient';
|
||||||
import type {TextBounds} from '../TextBounds';
|
import type {TextBounds} from '../TextBounds';
|
||||||
|
|
||||||
import {TEXT_DECORATION_STYLE, TEXT_DECORATION_LINE} from '../parsing/textDecoration';
|
import {TEXT_DECORATION_STYLE, TEXT_DECORATION_LINE} from '../parsing/textDecoration';
|
||||||
@ -110,7 +110,7 @@ class RefTestRenderer implements RenderTarget<string> {
|
|||||||
return `Path (${string})`;
|
return `Path (${string})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLinearGradient(bounds: Bounds, gradient: Gradient) {
|
renderLinearGradient(bounds: Bounds, gradient: LinearGradient) {
|
||||||
const direction = [
|
const direction = [
|
||||||
`x0: ${Math.round(gradient.direction.x0)}`,
|
`x0: ${Math.round(gradient.direction.x0)}`,
|
||||||
`x1: ${Math.round(gradient.direction.x1)}`,
|
`x1: ${Math.round(gradient.direction.x1)}`,
|
||||||
@ -129,6 +129,19 @@ class RefTestRenderer implements RenderTarget<string> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderRadialGradient(bounds: Bounds, gradient: RadialGradient) {
|
||||||
|
const stops = gradient.colorStops.map(
|
||||||
|
stop => `${stop.color.toString()} ${Math.ceil(stop.stop * 100) / 100}`
|
||||||
|
);
|
||||||
|
|
||||||
|
this.writeLine(
|
||||||
|
`RadialGradient: ${this.formatBounds(bounds)} radial-gradient(${gradient.radius
|
||||||
|
.x} ${gradient.radius.y} at ${gradient.center.x} ${gradient.center.y}, ${stops.join(
|
||||||
|
', '
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderRepeat(
|
renderRepeat(
|
||||||
path: Path,
|
path: Path,
|
||||||
image: ImageElement,
|
image: ImageElement,
|
||||||
|
151
tests/node/gradient.js
Normal file
151
tests/node/gradient.js
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
const Gradient = require('../../dist/npm/Gradient');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
describe('Gradient', () => {
|
||||||
|
describe('transformWebkitRadialGradientArgs', () => {
|
||||||
|
it('white, black', () => {
|
||||||
|
assert.equal(Gradient.transformWebkitRadialGradientArgs(['white', 'black'])[0], '');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('circle, white, black', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs(['circle', 'white', 'black'])[0],
|
||||||
|
'circle'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('10% 30%, white, black', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs(['10% 30%', 'white', 'black'])[0],
|
||||||
|
'10% 30%'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('30% 30%, closest-corner, white, black', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'30% 30%',
|
||||||
|
'closest-corner',
|
||||||
|
'white',
|
||||||
|
'black'
|
||||||
|
])[0],
|
||||||
|
'closest-corner at 30% 30%'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('30% 30%, circle closest-corner, white, black', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'30% 30%',
|
||||||
|
'circle closest-corner',
|
||||||
|
'white',
|
||||||
|
'black'
|
||||||
|
])[0],
|
||||||
|
'circle closest-corner at 30% 30%'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('center, 5em 40px, white, black', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'center',
|
||||||
|
'5em 40px',
|
||||||
|
'white',
|
||||||
|
'black'
|
||||||
|
])[0],
|
||||||
|
'5em 40px at center'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('45 45, 10, 52 50, 30, from(#A7D30C), to(red)', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'45 45',
|
||||||
|
'10',
|
||||||
|
'52 50',
|
||||||
|
'30',
|
||||||
|
'from(#A7D30C)'
|
||||||
|
])[0],
|
||||||
|
'30px at 52px 50px'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'75% 19%',
|
||||||
|
'ellipse closest-side',
|
||||||
|
'#ababab',
|
||||||
|
'#0000ff 33%',
|
||||||
|
'#991f1f 100%'
|
||||||
|
])[0],
|
||||||
|
'ellipse closest-side at 75% 19%'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('75% 19%, circle contain, #ababab, #0000ff 33%,#991f1f 100%', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'75% 19%',
|
||||||
|
'circle contain',
|
||||||
|
'#ababab',
|
||||||
|
'#0000ff 33%',
|
||||||
|
'#991f1f 100%'
|
||||||
|
])[0],
|
||||||
|
'circle closest-side at 75% 19%'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('75% 19%, circle cover, #ababab, #0000ff 33%,#991f1f 100%', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'75% 19%',
|
||||||
|
'circle cover',
|
||||||
|
'#ababab',
|
||||||
|
'#0000ff 33%',
|
||||||
|
'#991f1f 100%'
|
||||||
|
])[0],
|
||||||
|
'circle farthest-corner at 75% 19%'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('right 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'right 19%',
|
||||||
|
'ellipse cover',
|
||||||
|
'#ababab',
|
||||||
|
'#0000ff 33%',
|
||||||
|
'#991f1f 100%'
|
||||||
|
])[0],
|
||||||
|
'ellipse farthest-corner at right 19%'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('left 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'left 19%',
|
||||||
|
'ellipse cover',
|
||||||
|
'#ababab',
|
||||||
|
'#0000ff 33%',
|
||||||
|
'#991f1f 100%'
|
||||||
|
])[0],
|
||||||
|
'ellipse farthest-corner at left 19%'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('left top, circle cover, #ababab, #0000ff 33%,#991f1f 100%', () => {
|
||||||
|
assert.equal(
|
||||||
|
Gradient.transformWebkitRadialGradientArgs([
|
||||||
|
'left top',
|
||||||
|
'circle cover',
|
||||||
|
'#ababab',
|
||||||
|
'#0000ff 33%',
|
||||||
|
'#991f1f 100%'
|
||||||
|
])[0],
|
||||||
|
'circle farthest-corner at left top'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
51
tests/reftests/background/radial-gradient2.html
Normal file
51
tests/reftests/background/radial-gradient2.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Background attribute tests</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 5px;
|
||||||
|
border: 15px solid black;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="background: radial-gradient(red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(circle, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(ellipse, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(circle, red, blue); width:200px"></div>
|
||||||
|
<div style="background: radial-gradient(ellipse, red, blue); width:200px"></div>
|
||||||
|
<div style="background: radial-gradient(closest-side, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(closest-corner, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(farthest-side, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(farthest-corner, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(circle 20px, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(ellipse 20px 30px, red, blue)"></div>
|
||||||
|
<div style="font-size: 24px; border: none; padding: 0; width: auto;">
|
||||||
|
<div style="background: radial-gradient(circle 20px at 2em 80px, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(circle 20px at 6rem 80px, red, blue)"></div>
|
||||||
|
</div>
|
||||||
|
<div style="background: radial-gradient(circle farthest-side, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(at 20px 20px, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(ellipse farthest-corner at 45px 45px , #00FFFF 0%, rgba(0, 0, 255, 0) 50%, #0000FF 95%);"></div>
|
||||||
|
<div style="background: radial-gradient(16px at 70px 50% , #000000 0%, #000000 14px, rgba(0, 0, 0, 0.3) 18px, rgba(0, 0, 0, 0) 19px);"></div>
|
||||||
|
<div style="background: radial-gradient(16px at 70px 50% , #000000 0%, #000000 87.5%, rgba(0, 0, 0, 0.3) 112.5%, rgba(0, 0, 0, 0) 118.75%);"></div>
|
||||||
|
<div style="background: radial-gradient(19px at 70px 50% , #000000 0%, #000000 73.68%, rgba(0, 0, 0, 0.3) 94.74%, rgba(0, 0, 0, 0) 100%);"></div>
|
||||||
|
<div style="background: radial-gradient(ellipse 60px 30px at 70px 50%, #000000 0%, blue 10%, yellow 15%, red 18%);"></div>
|
||||||
|
<div style="background: radial-gradient(circle farthest-corner at left top, aquamarine, deeppink);"></div>
|
||||||
|
<div style="width: 300px; height: 200px; transform: translate(20px, 30px) rotate(20deg)">
|
||||||
|
<div style="width: 200px; height: 150px; transform: translate(-10px, -20px) rotate(10deg)">
|
||||||
|
<div style="background: radial-gradient(circle, red, blue)"></div>
|
||||||
|
<div style="background: radial-gradient(ellipse, red, blue); width:150px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user