Implement RefTestRenderer

This commit is contained in:
Niklas von Hertzen
2017-08-07 00:26:09 +08:00
parent a2895691ba
commit 93f08c7547
13 changed files with 409 additions and 84 deletions

View File

@ -2,8 +2,8 @@
'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';
@ -11,26 +11,30 @@ import type {TextDecoration} from '../parsing/textDecoration';
import type {TextShadow} from '../parsing/textShadow';
import type {Matrix} from '../parsing/transform';
import type {Path, Bounds} from '../Bounds';
import type {Bounds} from '../Bounds';
import type {ImageElement} from '../ImageLoader';
import type {Gradient} from '../Gradient';
import type {TextBounds} from '../TextBounds';
import BezierCurve from '../drawing/BezierCurve';
import Circle from '../drawing/Circle';
import Vector from '../drawing/Vector';
import {PATH} from '../drawing/Path';
import {TEXT_DECORATION_LINE} from '../parsing/textDecoration';
export default class CanvasRenderer implements RenderTarget {
export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
options: RenderOptions;
constructor(canvas: HTMLCanvasElement, options: RenderOptions) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
constructor(canvas: ?HTMLCanvasElement) {
this.canvas = canvas ? canvas : document.createElement('canvas');
}
render(options: RenderOptions) {
this.ctx = this.canvas.getContext('2d');
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.textBaseline = 'bottom';
@ -84,25 +88,16 @@ export default class CanvasRenderer implements RenderTarget {
path(path: Path) {
this.ctx.beginPath();
if (path instanceof Circle) {
this.ctx.arc(
path.x + path.radius,
path.y + path.radius,
path.radius,
0,
Math.PI * 2,
true
);
} else {
if (Array.isArray(path)) {
path.forEach((point, index) => {
const start = point instanceof Vector ? point : point.start;
const start = point.type === PATH.VECTOR ? point : point.start;
if (index === 0) {
this.ctx.moveTo(start.x, start.y);
} else {
this.ctx.lineTo(start.x, start.y);
}
if (point instanceof BezierCurve) {
if (point.type === PATH.BEZIER_CURVE) {
this.ctx.bezierCurveTo(
point.startControl.x,
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();
@ -157,7 +161,7 @@ export default class CanvasRenderer implements RenderTarget {
textBounds: Array<TextBounds>,
color: Color,
font: Font,
textDecoration: TextDecoration,
textDecoration: TextDecoration | null,
textShadows: Array<TextShadow> | null
) {
this.ctx.font = [

View 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;