mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Implement RefTestRenderer
This commit is contained in:
parent
a2895691ba
commit
93f08c7547
@ -4,13 +4,11 @@
|
|||||||
import type {Border, BorderSide} from './parsing/border';
|
import type {Border, BorderSide} from './parsing/border';
|
||||||
import type {BorderRadius} from './parsing/borderRadius';
|
import type {BorderRadius} from './parsing/borderRadius';
|
||||||
import type {Padding} from './parsing/padding';
|
import type {Padding} from './parsing/padding';
|
||||||
import type Circle from './drawing/Circle';
|
import type {Path} from './drawing/Path';
|
||||||
|
|
||||||
import Vector from './drawing/Vector';
|
import Vector from './drawing/Vector';
|
||||||
import BezierCurve from './drawing/BezierCurve';
|
import BezierCurve from './drawing/BezierCurve';
|
||||||
|
|
||||||
export type Path = Array<Vector | BezierCurve> | Circle;
|
|
||||||
|
|
||||||
const TOP = 0;
|
const TOP = 0;
|
||||||
const RIGHT = 1;
|
const RIGHT = 1;
|
||||||
const BOTTOM = 2;
|
const BOTTOM = 2;
|
||||||
|
@ -17,9 +17,9 @@ import type {Transform} from './parsing/transform';
|
|||||||
import type {Visibility} from './parsing/visibility';
|
import type {Visibility} from './parsing/visibility';
|
||||||
import type {zIndex} from './parsing/zIndex';
|
import type {zIndex} from './parsing/zIndex';
|
||||||
|
|
||||||
import type {Bounds, BoundCurves, Path} from './Bounds';
|
import type {Bounds, BoundCurves} from './Bounds';
|
||||||
import type ImageLoader from './ImageLoader';
|
import type ImageLoader from './ImageLoader';
|
||||||
|
import type {Path} from './drawing/Path';
|
||||||
import type TextContainer from './TextContainer';
|
import type TextContainer from './TextContainer';
|
||||||
|
|
||||||
import Color from './Color';
|
import Color from './Color';
|
||||||
@ -64,7 +64,7 @@ type StyleDeclaration = {
|
|||||||
overflow: Overflow,
|
overflow: Overflow,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
position: Position,
|
position: Position,
|
||||||
textDecoration: TextDecoration,
|
textDecoration: TextDecoration | null,
|
||||||
textShadow: Array<TextShadow> | null,
|
textShadow: Array<TextShadow> | null,
|
||||||
textTransform: TextTransform,
|
textTransform: TextTransform,
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import type Color from './Color';
|
import type Color from './Color';
|
||||||
|
import type {Path} from './drawing/Path';
|
||||||
import type Size from './drawing/Size';
|
import type Size from './drawing/Size';
|
||||||
import type Logger from './Logger';
|
import type Logger from './Logger';
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ import type {TextDecoration} from './parsing/textDecoration';
|
|||||||
import type {TextShadow} from './parsing/textShadow';
|
import type {TextShadow} from './parsing/textShadow';
|
||||||
import type {Matrix} from './parsing/transform';
|
import type {Matrix} from './parsing/transform';
|
||||||
|
|
||||||
import type {Path, BoundCurves} from './Bounds';
|
import type {BoundCurves} from './Bounds';
|
||||||
import type {Gradient} from './Gradient';
|
import type {Gradient} from './Gradient';
|
||||||
import type {ImageStore, ImageElement} from './ImageLoader';
|
import type {ImageStore, ImageElement} from './ImageLoader';
|
||||||
import type NodeContainer from './NodeContainer';
|
import type NodeContainer from './NodeContainer';
|
||||||
@ -49,7 +50,7 @@ export type RenderOptions = {
|
|||||||
height: number
|
height: number
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface RenderTarget {
|
export interface RenderTarget<Output> {
|
||||||
clip(clipPaths: Array<Path>, callback: () => void): void,
|
clip(clipPaths: Array<Path>, callback: () => void): void,
|
||||||
|
|
||||||
drawImage(image: ImageElement, source: Bounds, destination: Bounds): void,
|
drawImage(image: ImageElement, source: Bounds, destination: Bounds): void,
|
||||||
@ -58,10 +59,12 @@ export interface RenderTarget {
|
|||||||
|
|
||||||
fill(color: Color): void,
|
fill(color: Color): void,
|
||||||
|
|
||||||
getTarget(): Promise<HTMLCanvasElement>,
|
getTarget(): Promise<Output>,
|
||||||
|
|
||||||
rectangle(x: number, y: number, width: number, height: number, color: Color): void,
|
rectangle(x: number, y: number, width: number, height: number, color: Color): void,
|
||||||
|
|
||||||
|
render(options: RenderOptions): void,
|
||||||
|
|
||||||
renderLinearGradient(bounds: Bounds, gradient: Gradient): void,
|
renderLinearGradient(bounds: Bounds, gradient: Gradient): void,
|
||||||
|
|
||||||
renderRepeat(
|
renderRepeat(
|
||||||
@ -76,7 +79,7 @@ export interface RenderTarget {
|
|||||||
textBounds: Array<TextBounds>,
|
textBounds: Array<TextBounds>,
|
||||||
color: Color,
|
color: Color,
|
||||||
font: Font,
|
font: Font,
|
||||||
textDecoration: TextDecoration,
|
textDecoration: TextDecoration | null,
|
||||||
textShadows: Array<TextShadow> | null
|
textShadows: Array<TextShadow> | null
|
||||||
): void,
|
): void,
|
||||||
|
|
||||||
@ -86,12 +89,14 @@ export interface RenderTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class Renderer {
|
export default class Renderer {
|
||||||
target: RenderTarget;
|
target: RenderTarget<*>;
|
||||||
options: RenderOptions;
|
options: RenderOptions;
|
||||||
|
_opacity: ?number;
|
||||||
|
|
||||||
constructor(target: RenderTarget, options: RenderOptions) {
|
constructor(target: RenderTarget<*>, options: RenderOptions) {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
target.render(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNode(container: NodeContainer) {
|
renderNode(container: NodeContainer) {
|
||||||
@ -102,7 +107,7 @@ export default class Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderNodeContent(container: NodeContainer) {
|
renderNodeContent(container: NodeContainer) {
|
||||||
this.target.clip(container.getClipPaths(), () => {
|
const callback = () => {
|
||||||
if (container.childNodes.length) {
|
if (container.childNodes.length) {
|
||||||
container.childNodes.forEach(child => {
|
container.childNodes.forEach(child => {
|
||||||
if (child instanceof TextContainer) {
|
if (child instanceof TextContainer) {
|
||||||
@ -136,11 +141,17 @@ export default class Renderer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
const paths = container.getClipPaths();
|
||||||
|
if (paths.length) {
|
||||||
|
this.target.clip(paths, callback);
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNodeBackgroundAndBorders(container: NodeContainer) {
|
renderNodeBackgroundAndBorders(container: NodeContainer) {
|
||||||
this.target.clip(container.parent ? container.parent.getClipPaths() : [], () => {
|
const callback = () => {
|
||||||
const backgroundPaintingArea = calculateBackgroungPaintingArea(
|
const backgroundPaintingArea = calculateBackgroungPaintingArea(
|
||||||
container.curvedBounds,
|
container.curvedBounds,
|
||||||
container.style.background.backgroundClip
|
container.style.background.backgroundClip
|
||||||
@ -156,7 +167,14 @@ export default class Renderer {
|
|||||||
container.style.border.forEach((border, side) => {
|
container.style.border.forEach((border, side) => {
|
||||||
this.renderBorder(border, side, container.curvedBounds);
|
this.renderBorder(border, side, container.curvedBounds);
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const paths = container.parent ? container.parent.getClipPaths() : [];
|
||||||
|
if (paths.length) {
|
||||||
|
this.target.clip(paths, callback);
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBackgroundImage(container: NodeContainer) {
|
renderBackgroundImage(container: NodeContainer) {
|
||||||
@ -213,7 +231,12 @@ export default class Renderer {
|
|||||||
|
|
||||||
renderStack(stack: StackingContext) {
|
renderStack(stack: StackingContext) {
|
||||||
if (stack.container.isVisible()) {
|
if (stack.container.isVisible()) {
|
||||||
this.target.setOpacity(stack.getOpacity());
|
const opacity = stack.getOpacity();
|
||||||
|
if (opacity !== this._opacity) {
|
||||||
|
this.target.setOpacity(stack.getOpacity());
|
||||||
|
this._opacity = opacity;
|
||||||
|
}
|
||||||
|
|
||||||
const transform = stack.container.style.transform;
|
const transform = stack.container.style.transform;
|
||||||
if (transform !== null) {
|
if (transform !== null) {
|
||||||
this.target.transform(
|
this.target.transform(
|
||||||
@ -270,7 +293,7 @@ export default class Renderer {
|
|||||||
positiveZIndex.sort(sortByZIndex).forEach(this.renderStack, this);
|
positiveZIndex.sort(sortByZIndex).forEach(this.renderStack, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(stack: StackingContext): Promise<HTMLCanvasElement> {
|
render(stack: StackingContext): Promise<*> {
|
||||||
if (this.options.backgroundColor) {
|
if (this.options.backgroundColor) {
|
||||||
this.target.rectangle(
|
this.target.rectangle(
|
||||||
0,
|
0,
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
import type {Drawable} from './Path';
|
||||||
|
import {PATH} from './Path';
|
||||||
import Vector from './Vector';
|
import Vector from './Vector';
|
||||||
|
|
||||||
const lerp = (a: Vector, b: Vector, t: number): Vector => {
|
const lerp = (a: Vector, b: Vector, t: number): Vector => {
|
||||||
return new Vector(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
|
return new Vector(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class BezierCurve {
|
export default class BezierCurve implements Drawable<1> {
|
||||||
|
type: 1;
|
||||||
start: Vector;
|
start: Vector;
|
||||||
startControl: Vector;
|
startControl: Vector;
|
||||||
endControl: Vector;
|
endControl: Vector;
|
||||||
end: Vector;
|
end: Vector;
|
||||||
|
|
||||||
constructor(start: Vector, startControl: Vector, endControl: Vector, end: Vector) {
|
constructor(start: Vector, startControl: Vector, endControl: Vector, end: Vector) {
|
||||||
|
this.type = PATH.BEZIER_CURVE;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.startControl = startControl;
|
this.startControl = startControl;
|
||||||
this.endControl = endControl;
|
this.endControl = endControl;
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
import type {Drawable} from './Path';
|
||||||
|
import {PATH} from './Path';
|
||||||
|
|
||||||
export default class Circle {
|
export default class Circle implements Drawable<2> {
|
||||||
|
type: 2;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
radius: number;
|
radius: number;
|
||||||
|
|
||||||
constructor(x: number, y: number, radius: number) {
|
constructor(x: number, y: number, radius: number) {
|
||||||
|
this.type = PATH.CIRCLE;
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.radius = radius;
|
this.radius = radius;
|
||||||
|
20
src/drawing/Path.js
Normal file
20
src/drawing/Path.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/* @flow */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import type Vector from './Vector';
|
||||||
|
import type BezierCurve from './BezierCurve';
|
||||||
|
import type Circle from './Circle';
|
||||||
|
|
||||||
|
export const PATH = {
|
||||||
|
VECTOR: 0,
|
||||||
|
BEZIER_CURVE: 1,
|
||||||
|
CIRCLE: 2
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PathType = $Values<typeof PATH>;
|
||||||
|
|
||||||
|
export interface Drawable<A> {
|
||||||
|
type: A
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Path = Array<Vector | BezierCurve> | Circle;
|
@ -1,11 +1,15 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
import type {Drawable} from './Path';
|
||||||
|
import {PATH} from './Path';
|
||||||
|
|
||||||
export default class Vector {
|
export default class Vector implements Drawable<0> {
|
||||||
|
type: 0;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
|
|
||||||
constructor(x: number, y: number) {
|
constructor(x: number, y: number) {
|
||||||
|
this.type = PATH.VECTOR;
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
33
src/index.js
33
src/index.js
@ -1,6 +1,8 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import type {RenderTarget} from './Renderer';
|
||||||
|
|
||||||
import {NodeParser} from './NodeParser';
|
import {NodeParser} from './NodeParser';
|
||||||
import Renderer from './Renderer';
|
import Renderer from './Renderer';
|
||||||
import CanvasRenderer from './renderer/CanvasRenderer';
|
import CanvasRenderer from './renderer/CanvasRenderer';
|
||||||
@ -19,12 +21,13 @@ export type Options = {
|
|||||||
proxy: ?string,
|
proxy: ?string,
|
||||||
removeContainer: ?boolean,
|
removeContainer: ?boolean,
|
||||||
scale: number,
|
scale: number,
|
||||||
|
target: RenderTarget<*> | Array<RenderTarget<*>>,
|
||||||
type: ?string,
|
type: ?string,
|
||||||
windowWidth: number,
|
windowWidth: number,
|
||||||
windowHeight: number
|
windowHeight: number
|
||||||
};
|
};
|
||||||
|
|
||||||
const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasElement> => {
|
const html2canvas = (element: HTMLElement, config: Options): Promise<*> => {
|
||||||
if (typeof console === 'object' && typeof console.log === 'function') {
|
if (typeof console === 'object' && typeof console.log === 'function') {
|
||||||
console.log(`html2canvas ${__VERSION__}`);
|
console.log(`html2canvas ${__VERSION__}`);
|
||||||
}
|
}
|
||||||
@ -37,11 +40,11 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
|||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
async: true,
|
async: true,
|
||||||
allowTaint: false,
|
allowTaint: false,
|
||||||
canvas: ownerDocument.createElement('canvas'),
|
|
||||||
imageTimeout: 10000,
|
imageTimeout: 10000,
|
||||||
proxy: null,
|
proxy: null,
|
||||||
removeContainer: true,
|
removeContainer: true,
|
||||||
scale: defaultView.devicePixelRatio || 1,
|
scale: defaultView.devicePixelRatio || 1,
|
||||||
|
target: new CanvasRenderer(config.canvas),
|
||||||
type: null,
|
type: null,
|
||||||
windowWidth: defaultView.innerWidth,
|
windowWidth: defaultView.innerWidth,
|
||||||
windowHeight: defaultView.innerHeight
|
windowHeight: defaultView.innerHeight
|
||||||
@ -56,12 +59,6 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
|||||||
options.windowHeight
|
options.windowHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
const canvas = options.canvas;
|
|
||||||
|
|
||||||
if (!(canvas instanceof HTMLCanvasElement)) {
|
|
||||||
return Promise.reject(__DEV__ ? `Invalid canvas element provided in options` : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = cloneWindow(
|
const result = cloneWindow(
|
||||||
ownerDocument,
|
ownerDocument,
|
||||||
ownerDocument,
|
ownerDocument,
|
||||||
@ -83,10 +80,6 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
|||||||
const size = options.type === 'view' ? windowBounds : parseDocumentSize(clonedDocument);
|
const size = options.type === 'view' ? windowBounds : parseDocumentSize(clonedDocument);
|
||||||
const width = size.width;
|
const width = size.width;
|
||||||
const height = size.height;
|
const height = size.height;
|
||||||
canvas.width = Math.floor(width * options.scale);
|
|
||||||
canvas.height = Math.floor(height * options.scale);
|
|
||||||
canvas.style.width = `${width}px`;
|
|
||||||
canvas.style.height = `${height}px`;
|
|
||||||
|
|
||||||
// http://www.w3.org/TR/css3-background/#special-backgrounds
|
// http://www.w3.org/TR/css3-background/#special-backgrounds
|
||||||
const backgroundColor =
|
const backgroundColor =
|
||||||
@ -121,10 +114,18 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
|||||||
width,
|
width,
|
||||||
height
|
height
|
||||||
};
|
};
|
||||||
const canvasTarget = new CanvasRenderer(canvas, renderOptions);
|
|
||||||
|
|
||||||
const renderer = new Renderer(canvasTarget, renderOptions);
|
if (Array.isArray(options.target)) {
|
||||||
return renderer.render(stack);
|
return Promise.all(
|
||||||
|
options.target.map(target => {
|
||||||
|
const renderer = new Renderer(target, renderOptions);
|
||||||
|
return renderer.render(stack);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const renderer = new Renderer(options.target, renderOptions);
|
||||||
|
return renderer.render(stack);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,4 +138,6 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<HTMLCanvasE
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
html2canvas.CanvasRenderer = CanvasRenderer;
|
||||||
|
|
||||||
module.exports = html2canvas;
|
module.exports = html2canvas;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
import type {Path} from '../drawing/Path';
|
||||||
import type {Bounds, BoundCurves, Path} from '../Bounds';
|
import type {Bounds, BoundCurves} from '../Bounds';
|
||||||
import type ImageLoader, {ImageElement} from '../ImageLoader';
|
import type ImageLoader, {ImageElement} from '../ImageLoader';
|
||||||
|
|
||||||
import Color from '../Color';
|
import Color from '../Color';
|
||||||
|
@ -29,7 +29,7 @@ export type TextDecoration = {
|
|||||||
textDecorationLine: Array<TextDecorationLine>,
|
textDecorationLine: Array<TextDecorationLine>,
|
||||||
textDecorationStyle: TextDecorationStyle,
|
textDecorationStyle: TextDecorationStyle,
|
||||||
textDecorationColor: Color | null
|
textDecorationColor: Color | null
|
||||||
} | null;
|
};
|
||||||
|
|
||||||
const parseLine = (line: string): TextDecorationLine => {
|
const parseLine = (line: string): TextDecorationLine => {
|
||||||
switch (line) {
|
switch (line) {
|
||||||
@ -65,7 +65,7 @@ const parseTextDecorationStyle = (style: string): TextDecorationStyle => {
|
|||||||
return TEXT_DECORATION_STYLE.SOLID;
|
return TEXT_DECORATION_STYLE.SOLID;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseTextDecoration = (style: CSSStyleDeclaration): TextDecoration => {
|
export const parseTextDecoration = (style: CSSStyleDeclaration): TextDecoration | null => {
|
||||||
const textDecorationLine = parseTextDecorationLine(
|
const textDecorationLine = parseTextDecorationLine(
|
||||||
style.textDecorationLine ? style.textDecorationLine : style.textDecoration
|
style.textDecorationLine ? style.textDecorationLine : style.textDecoration
|
||||||
);
|
);
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import type {RenderTarget, RenderOptions} from '../Renderer';
|
import type {RenderTarget, RenderOptions} from '../Renderer';
|
||||||
|
|
||||||
import type Color from '../Color';
|
import type Color from '../Color';
|
||||||
|
import type {Path} from '../drawing/Path';
|
||||||
import type Size from '../drawing/Size';
|
import type Size from '../drawing/Size';
|
||||||
|
|
||||||
import type {Font} from '../parsing/font';
|
import type {Font} from '../parsing/font';
|
||||||
@ -11,26 +11,30 @@ import type {TextDecoration} from '../parsing/textDecoration';
|
|||||||
import type {TextShadow} from '../parsing/textShadow';
|
import type {TextShadow} from '../parsing/textShadow';
|
||||||
import type {Matrix} from '../parsing/transform';
|
import type {Matrix} from '../parsing/transform';
|
||||||
|
|
||||||
import type {Path, Bounds} from '../Bounds';
|
import type {Bounds} from '../Bounds';
|
||||||
import type {ImageElement} from '../ImageLoader';
|
import type {ImageElement} from '../ImageLoader';
|
||||||
import type {Gradient} from '../Gradient';
|
import type {Gradient} from '../Gradient';
|
||||||
import type {TextBounds} from '../TextBounds';
|
import type {TextBounds} from '../TextBounds';
|
||||||
|
|
||||||
import BezierCurve from '../drawing/BezierCurve';
|
import {PATH} from '../drawing/Path';
|
||||||
import Circle from '../drawing/Circle';
|
|
||||||
import Vector from '../drawing/Vector';
|
|
||||||
|
|
||||||
import {TEXT_DECORATION_LINE} from '../parsing/textDecoration';
|
import {TEXT_DECORATION_LINE} from '../parsing/textDecoration';
|
||||||
|
|
||||||
export default class CanvasRenderer implements RenderTarget {
|
export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
ctx: CanvasRenderingContext2D;
|
ctx: CanvasRenderingContext2D;
|
||||||
options: RenderOptions;
|
options: RenderOptions;
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, options: RenderOptions) {
|
constructor(canvas: ?HTMLCanvasElement) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas ? canvas : document.createElement('canvas');
|
||||||
this.ctx = canvas.getContext('2d');
|
}
|
||||||
|
|
||||||
|
render(options: RenderOptions) {
|
||||||
|
this.ctx = this.canvas.getContext('2d');
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
this.canvas.width = Math.floor(options.width * options.scale);
|
||||||
|
this.canvas.height = Math.floor(options.height * options.scale);
|
||||||
|
this.canvas.style.width = `${options.width}px`;
|
||||||
|
this.canvas.style.height = `${options.height}px`;
|
||||||
|
|
||||||
this.ctx.scale(this.options.scale, this.options.scale);
|
this.ctx.scale(this.options.scale, this.options.scale);
|
||||||
this.ctx.textBaseline = 'bottom';
|
this.ctx.textBaseline = 'bottom';
|
||||||
@ -84,25 +88,16 @@ export default class CanvasRenderer implements RenderTarget {
|
|||||||
|
|
||||||
path(path: Path) {
|
path(path: Path) {
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
if (path instanceof Circle) {
|
if (Array.isArray(path)) {
|
||||||
this.ctx.arc(
|
|
||||||
path.x + path.radius,
|
|
||||||
path.y + path.radius,
|
|
||||||
path.radius,
|
|
||||||
0,
|
|
||||||
Math.PI * 2,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
path.forEach((point, index) => {
|
path.forEach((point, index) => {
|
||||||
const start = point instanceof Vector ? point : point.start;
|
const start = point.type === PATH.VECTOR ? point : point.start;
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
this.ctx.moveTo(start.x, start.y);
|
this.ctx.moveTo(start.x, start.y);
|
||||||
} else {
|
} else {
|
||||||
this.ctx.lineTo(start.x, start.y);
|
this.ctx.lineTo(start.x, start.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (point instanceof BezierCurve) {
|
if (point.type === PATH.BEZIER_CURVE) {
|
||||||
this.ctx.bezierCurveTo(
|
this.ctx.bezierCurveTo(
|
||||||
point.startControl.x,
|
point.startControl.x,
|
||||||
point.startControl.y,
|
point.startControl.y,
|
||||||
@ -113,6 +108,15 @@ export default class CanvasRenderer implements RenderTarget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.ctx.arc(
|
||||||
|
path.x + path.radius,
|
||||||
|
path.y + path.radius,
|
||||||
|
path.radius,
|
||||||
|
0,
|
||||||
|
Math.PI * 2,
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ctx.closePath();
|
this.ctx.closePath();
|
||||||
@ -157,7 +161,7 @@ export default class CanvasRenderer implements RenderTarget {
|
|||||||
textBounds: Array<TextBounds>,
|
textBounds: Array<TextBounds>,
|
||||||
color: Color,
|
color: Color,
|
||||||
font: Font,
|
font: Font,
|
||||||
textDecoration: TextDecoration,
|
textDecoration: TextDecoration | null,
|
||||||
textShadows: Array<TextShadow> | null
|
textShadows: Array<TextShadow> | null
|
||||||
) {
|
) {
|
||||||
this.ctx.font = [
|
this.ctx.font = [
|
||||||
|
249
src/renderer/RefTestRenderer.js
Normal file
249
src/renderer/RefTestRenderer.js
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
/* @flow */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import type {RenderTarget, RenderOptions} from '../Renderer';
|
||||||
|
import type Color from '../Color';
|
||||||
|
import type {Path} from '../drawing/Path';
|
||||||
|
import type Size from '../drawing/Size';
|
||||||
|
|
||||||
|
import type {Font} from '../parsing/font';
|
||||||
|
import type {
|
||||||
|
TextDecoration,
|
||||||
|
TextDecorationStyle,
|
||||||
|
TextDecorationLine
|
||||||
|
} from '../parsing/textDecoration';
|
||||||
|
import type {TextShadow} from '../parsing/textShadow';
|
||||||
|
import type {Matrix} from '../parsing/transform';
|
||||||
|
|
||||||
|
import type {Bounds} from '../Bounds';
|
||||||
|
import type {ImageElement} from '../ImageLoader';
|
||||||
|
import type {Gradient} from '../Gradient';
|
||||||
|
import type {TextBounds} from '../TextBounds';
|
||||||
|
|
||||||
|
import {TEXT_DECORATION_STYLE, TEXT_DECORATION_LINE} from '../parsing/textDecoration';
|
||||||
|
import {PATH} from '../drawing/Path';
|
||||||
|
|
||||||
|
class RefTestRenderer implements RenderTarget<string> {
|
||||||
|
options: RenderOptions;
|
||||||
|
indent: number;
|
||||||
|
lines: Array<string>;
|
||||||
|
|
||||||
|
render(options: RenderOptions) {
|
||||||
|
this.options = options;
|
||||||
|
this.indent = 0;
|
||||||
|
this.lines = [];
|
||||||
|
options.logger.log(`RefTest renderer initialized`);
|
||||||
|
}
|
||||||
|
|
||||||
|
clip(clipPaths: Array<Path>, callback: () => void) {
|
||||||
|
this.writeLine(`Clip ${clipPaths.map(this.formatPath, this).join(', ')}`);
|
||||||
|
this.indent += 2;
|
||||||
|
callback();
|
||||||
|
this.indent -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawImage(image: ImageElement, source: Bounds, destination: Bounds) {
|
||||||
|
this.writeLine(
|
||||||
|
`Draw image ${this.formatImage(image)} (source: ${this.formatBounds(
|
||||||
|
source
|
||||||
|
)} (destination: ${this.formatBounds(source)})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawShape(path: Path, color: Color) {
|
||||||
|
this.writeLine(`Shape ${color.toString()} ${this.formatPath(path)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
fill(color: Color) {
|
||||||
|
this.writeLine(`Fill ${color.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTarget(): Promise<string> {
|
||||||
|
return Promise.resolve(this.lines.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
rectangle(x: number, y: number, width: number, height: number, color: Color) {
|
||||||
|
const list = [x, y, width, height].map(v => Math.round(v)).join(', ');
|
||||||
|
this.writeLine(`Rectangle [${list}] ${color.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatBounds(bounds: Bounds): string {
|
||||||
|
const list = [bounds.left, bounds.top, bounds.width, bounds.height];
|
||||||
|
return `[${list.map(v => Math.round(v)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatImage(image: ImageElement): string {
|
||||||
|
// $FlowFixMe
|
||||||
|
return image.tagName === 'CANVAS' ? 'Canvas' : `Image src="${image.src}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatPath(path: Path): string {
|
||||||
|
if (!Array.isArray(path)) {
|
||||||
|
return `Circle(x: ${Math.round(path.x)}, y: ${Math.round(path.y)}, r: ${Math.round(
|
||||||
|
path.radius
|
||||||
|
)})`;
|
||||||
|
}
|
||||||
|
const string = path
|
||||||
|
.map(v => {
|
||||||
|
if (v.type === PATH.VECTOR) {
|
||||||
|
return `Vector(x: ${Math.round(v.x)}, y: ${Math.round(v.y)}))`;
|
||||||
|
}
|
||||||
|
if (v.type === PATH.BEZIER_CURVE) {
|
||||||
|
const values = [
|
||||||
|
`x0: ${Math.round(v.start.x)}`,
|
||||||
|
`y0: ${Math.round(v.start.y)}`,
|
||||||
|
`x1: ${Math.round(v.end.x)}`,
|
||||||
|
`y1: ${Math.round(v.end.y)}`,
|
||||||
|
`cx0: ${Math.round(v.startControl.x)}`,
|
||||||
|
`cy0: ${Math.round(v.startControl.y)}`,
|
||||||
|
`cx1: ${Math.round(v.endControl.x)}`,
|
||||||
|
`cy1: ${Math.round(v.endControl.y)}`
|
||||||
|
];
|
||||||
|
return `BezierCurve(${values.join(', ')})`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(', ');
|
||||||
|
return `Path (${string})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLinearGradient(bounds: Bounds, gradient: Gradient) {
|
||||||
|
const direction = [
|
||||||
|
`x0: ${Math.round(gradient.direction.x0)}`,
|
||||||
|
`x1: ${Math.round(gradient.direction.x1)}`,
|
||||||
|
`y0: ${Math.round(gradient.direction.y0)}`,
|
||||||
|
`y1: ${Math.round(gradient.direction.y1)}`
|
||||||
|
];
|
||||||
|
|
||||||
|
const stops = gradient.colorStops.map(stop => `${stop.color.toString()} ${stop.stop}px`);
|
||||||
|
|
||||||
|
this.writeLine(
|
||||||
|
`${this.formatBounds(bounds)} linear-gradient(${direction.join(', ')} ${stops.join(
|
||||||
|
', '
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRepeat(
|
||||||
|
path: Path,
|
||||||
|
image: ImageElement,
|
||||||
|
imageSize: Size,
|
||||||
|
offsetX: number,
|
||||||
|
offsetY: number
|
||||||
|
) {
|
||||||
|
this.writeLine(
|
||||||
|
`Repeat ${this.formatImage(
|
||||||
|
image
|
||||||
|
)} [${offsetX}, ${offsetY}] Size (${imageSize.width}, ${imageSize.height}) ${this.formatPath(
|
||||||
|
path
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTextNode(
|
||||||
|
textBounds: Array<TextBounds>,
|
||||||
|
color: Color,
|
||||||
|
font: Font,
|
||||||
|
textDecoration: TextDecoration | null,
|
||||||
|
textShadows: Array<TextShadow> | null
|
||||||
|
) {
|
||||||
|
const fontString = [
|
||||||
|
font.fontStyle,
|
||||||
|
font.fontVariant,
|
||||||
|
font.fontWeight,
|
||||||
|
font.fontSize,
|
||||||
|
font.fontFamily
|
||||||
|
]
|
||||||
|
.join(' ')
|
||||||
|
.split(',')[0];
|
||||||
|
|
||||||
|
const textDecorationString = this.textDecoration(textDecoration, color);
|
||||||
|
const shadowString = textShadows
|
||||||
|
? ` Shadows: (${textShadows
|
||||||
|
.map(
|
||||||
|
shadow =>
|
||||||
|
`${shadow.color.toString()} ${shadow.offsetX}px ${shadow.offsetY}px ${shadow.blur}px`
|
||||||
|
)
|
||||||
|
.join(', ')})`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
this.writeLine(
|
||||||
|
`Text ${color.toString()} ${fontString}${shadowString}${textDecorationString}`
|
||||||
|
);
|
||||||
|
|
||||||
|
this.indent += 2;
|
||||||
|
textBounds.forEach(textBound => {
|
||||||
|
this.writeLine(
|
||||||
|
`[${Math.round(textBound.bounds.left)}, ${Math.round(
|
||||||
|
textBound.bounds.top
|
||||||
|
)}]: ${textBound.text}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.indent -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
textDecoration(textDecoration: TextDecoration | null, color: Color): string {
|
||||||
|
if (textDecoration) {
|
||||||
|
const textDecorationColor = (textDecoration.textDecorationColor
|
||||||
|
? textDecoration.textDecorationColor
|
||||||
|
: color).toString();
|
||||||
|
const textDecorationLines = textDecoration.textDecorationLine.map(
|
||||||
|
this.textDecorationLine,
|
||||||
|
this
|
||||||
|
);
|
||||||
|
return textDecoration
|
||||||
|
? ` ${this.textDecorationStyle(
|
||||||
|
textDecoration.textDecorationStyle
|
||||||
|
)} ${textDecorationColor} ${textDecorationLines.join(', ')}`
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
textDecorationLine(textDecorationLine: TextDecorationLine): string {
|
||||||
|
switch (textDecorationLine) {
|
||||||
|
case TEXT_DECORATION_LINE.LINE_THROUGH:
|
||||||
|
return 'line-through';
|
||||||
|
case TEXT_DECORATION_LINE.OVERLINE:
|
||||||
|
return 'overline';
|
||||||
|
case TEXT_DECORATION_LINE.UNDERLINE:
|
||||||
|
return 'underline';
|
||||||
|
case TEXT_DECORATION_LINE.BLINK:
|
||||||
|
return 'blink';
|
||||||
|
}
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
|
||||||
|
textDecorationStyle(textDecorationStyle: TextDecorationStyle): string {
|
||||||
|
switch (textDecorationStyle) {
|
||||||
|
case TEXT_DECORATION_STYLE.SOLID:
|
||||||
|
return 'solid';
|
||||||
|
case TEXT_DECORATION_STYLE.DOTTED:
|
||||||
|
return 'dotted';
|
||||||
|
case TEXT_DECORATION_STYLE.DOUBLE:
|
||||||
|
return 'double';
|
||||||
|
case TEXT_DECORATION_STYLE.DASHED:
|
||||||
|
return 'dashed';
|
||||||
|
case TEXT_DECORATION_STYLE.WAVY:
|
||||||
|
return 'WAVY';
|
||||||
|
}
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpacity(opacity: number) {
|
||||||
|
this.writeLine(`Opacity ${opacity}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
transform(offsetX: number, offsetY: number, matrix: Matrix, callback: () => void) {
|
||||||
|
this.writeLine(`Transform (${offsetX}, ${offsetY}) [${matrix.join(', ')}]`);
|
||||||
|
this.indent += 2;
|
||||||
|
callback();
|
||||||
|
this.indent -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLine(text: string) {
|
||||||
|
this.lines.push(`${new Array(this.indent).join(' ')}${text}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RefTestRenderer;
|
@ -9,25 +9,41 @@ const banner =
|
|||||||
Copyright (c) ${(new Date()).getFullYear()} ${pkg.author.name} <${pkg.author.url}>
|
Copyright (c) ${(new Date()).getFullYear()} ${pkg.author.name} <${pkg.author.url}>
|
||||||
Released under ${pkg.license} License`;
|
Released under ${pkg.license} License`;
|
||||||
|
|
||||||
module.exports = {
|
const plugins = [
|
||||||
entry: './src/index.js',
|
new webpack.DefinePlugin({
|
||||||
output: {
|
'__DEV__': true,
|
||||||
filename: './dist/html2canvas.js',
|
'__VERSION__': JSON.stringify(pkg.version)
|
||||||
library: 'html2canvas',
|
}),
|
||||||
libraryTarget: 'umd'
|
new webpack.BannerPlugin(banner)
|
||||||
},
|
];
|
||||||
module: {
|
|
||||||
loaders: [{
|
const modules = {
|
||||||
test: /\.js$/,
|
loaders: [{
|
||||||
exclude: /node_modules/,
|
test: /\.js$/,
|
||||||
loader: 'babel-loader'
|
exclude: /node_modules/,
|
||||||
}]
|
loader: 'babel-loader'
|
||||||
},
|
}]
|
||||||
plugins: [
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'__DEV__': true,
|
|
||||||
'__VERSION__': JSON.stringify(pkg.version)
|
|
||||||
}),
|
|
||||||
new webpack.BannerPlugin(banner)
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
entry: './src/index.js',
|
||||||
|
output: {
|
||||||
|
filename: './dist/html2canvas.js',
|
||||||
|
library: 'html2canvas',
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
},
|
||||||
|
module: modules,
|
||||||
|
plugins
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entry: './src/renderer/RefTestRenderer.js',
|
||||||
|
output: {
|
||||||
|
filename: './dist/RefTestRenderer.js',
|
||||||
|
library: 'RefTestRenderer',
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
},
|
||||||
|
module: modules,
|
||||||
|
plugins
|
||||||
|
}
|
||||||
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user