@antv/f-my
Version:
FEngine for alipay mini-program
423 lines (394 loc) • 15.1 kB
text/typescript
type TCanvasLineJoin = 'bevel' | 'round' | 'miter';
type TCanvasLineCap = 'butt' | 'round' | 'square';
type TCanvasTextBaseLine = 'top' | 'hanging' | 'middle' | 'alphabetic' | 'ideographic' | 'bottom';
type TCanvasTextAlign = 'left' | 'right' | 'center' | 'start' | 'end';
type TCanvasPatternRepeat = 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat';
type TCanvasGradient = any;
type TCanvasPattern = any;
type TCanvasImageSource = IMiniProgramCanvasContext_v1 | CanvasImageElement;
interface IMiniProgramCanvasContextImageInfo {
id: string | number | -1;
width: number;
height: number;
url: string;
}
function preloadCanvasImage(url: string, callback: (data: IMiniProgramCanvasContextImageInfo) => void) {
(my as any).preloadCanvasImage({
urls: [url],
complete(res: any) {
if (res && res.loaded) {
const img = res.loaded[url];
if (img.url == url && img.id !== -1) {
callback(img);
return;
}
}
callback(null);
}
})
}
interface IMiniProgramCanvasContext_v1 {
set fillStyle(value: string | TCanvasGradient | null | TCanvasPattern);
set font(value: string);
get font(): string;
set fontSize(value: any);
set strokeStyle(value: string | TCanvasGradient | null | TCanvasPattern);
set globalAlpha(value: number);
set lineWidth(value: number);
set lineCap(value: TCanvasLineCap);
set lineJoin(value: TCanvasLineJoin);
set miterLimit(value: number);
set textBaseline(value: TCanvasTextBaseLine);
set lineDashOffset(value: number);
set textAlign(value: TCanvasTextAlign);
set globalCompositeOperation(value: string);
setFillStyle(color: string | TCanvasGradient | null | TCanvasPattern): void;
fillRect(x: number, y: number, width: number, height: number): void;
strokeRect(x: number, y: number, width: number, height: number): void;
beginPath(): void;
arc(x: number, y: number, radius: number, startDeg: number, endDeg: number, anticlockwise?: boolean): void;
fill(): void;
clip(): void;
rect(x: number, y: number, width: number, height: number): void;
stroke(): void;
scale(x: number, y: number): void;
rotate(angle: number): void;
translate(x: number, y: number): void;
save(): void;
restore(): void;
clearRect(x: number, y: number, width: number, height: number): void;
fillText(text: string, x: number, y: number, maxWidth?: number): void;
moveTo(x: number, y: number): void;
lineTo(x: number, y: number): void;
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void;
transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void;
closePath(): void;
quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void;
bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void;
setLineDash(gaps: number[]): void;
strokeText(text: string, x: number, y: number, maxWidth?: number): void;
setStrokeStyle(color: string | TCanvasGradient | null | TCanvasPattern): void;
setGlobalAlpha(alpha: number): void;
setLineWidth(width: number): void;
setLineCap(lineCap: TCanvasLineCap): void;
setLineJoin(lineJoin: TCanvasLineJoin): void;
setMiterLimit(limit: number): void;
setTextBaseline(baseline: TCanvasTextBaseLine): void;
setLineDashOffset(offset: number): void;
setTextAlign(align: TCanvasTextAlign): void;
setGlobalCompositeOperation(op: string): void;
setShadow(offsetX?: number, offsetY?: number, blur?: number, color?: string): void;
setFontSize(size: number): void;
setFont(font: string): void;
createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): TCanvasGradient;
createCircularGradient(x: number, y: number, r: number): TCanvasGradient;
createLinearGradient(x0: number, y0: number, x1: number, y1: number): TCanvasGradient;
drawImage(
imageOrContext: TCanvasImageSource,
sx: number,
sy: number,
sWidth?: number,
sHeight?: number,
dx?: number,
dy?: number,
dWidth?: number,
dHeight?: number
): void;
draw(reserve?: boolean): void;
createPattern(image: TCanvasImageSource, repeat: TCanvasPatternRepeat): TCanvasPattern;
}
export function createCanvasAdapter(canvasContext: any) {
const element = new CanvasElement(canvasContext);
const context = new SimulatedCanvasContext(canvasContext);
context.canvas = element;
return {
element,
context,
// debugger for unimplemented property/method
// element: new Proxy(element, {
// get(target, p, receiver) {
// const descriptor = Object.getOwnPropertyDescriptor(target, p);
// const descriptor2 = Object.getOwnPropertyDescriptor((target as any).__proto__, p);
// if (!descriptor && !descriptor2) {
// debugger;
// }
// return Reflect.get(target, p, receiver);
// }
// }),
// context: new Proxy(context, {
// get(target, p, receiver) {
// const descriptor = Object.getOwnPropertyDescriptor(target, p);
// const descriptor2 = Object.getOwnPropertyDescriptor((target as any).__proto__, p);
// if (!descriptor && !descriptor2) {
// debugger;
// }
// return Reflect.get(target, p, receiver);
// }
// })
}
}
function bindDrawRunnable(fn: any, canvasContext: IMiniProgramCanvasContext_v1) {
return function (this: any, ...args: unknown[]) {
try {
fn.apply(this, args);
} finally {
canvasContext.draw(true);
}
}
}
class CanvasImageElement {
private _onload?: undefined | (() => void);
private _onerror?: undefined | (() => void);
private _invoked: boolean = false;
private _rawImgObj?: IMiniProgramCanvasContextImageInfo | {} = undefined;
public static toImageSource(imageOrContext: any): TCanvasImageSource | null {
let target = null;
if (imageOrContext instanceof CanvasImageElement) {
target = imageOrContext._rawImgObj;
} else if (typeof imageOrContext === 'string') {
target = imageOrContext;
}
return target;
}
constructor(public url: string, private _context: IMiniProgramCanvasContext_v1) {
preloadCanvasImage(url, (img) => {
this._rawImgObj = img || {};
this._complete(this._rawImgObj);
});
}
private _complete(img: Partial<IMiniProgramCanvasContextImageInfo>) {
if (!img || this._invoked) {
return;
}
if (img.url == this.url && img.id !== -1) {
if (this._onload) {
bindDrawRunnable(this._onload, this._context)();
this._invoked = true;
}
} else {
if (this._onerror) {
bindDrawRunnable(this._onerror, this._context)();
this._invoked = true;
}
}
}
set onload(fn: () => void) {
this._onload = fn;
this._complete(this._rawImgObj);
}
set onerror(fn: () => void) {
this._onerror = fn;
this._complete(this._rawImgObj);
}
}
class CanvasElement {
constructor(
private canvasContext: IMiniProgramCanvasContext_v1,
private offscreenCanvas = (my as any).createOffscreenCanvas
? (my as any).createOffscreenCanvas()
: { requestAnimationFrame: () => {} }
) {
}
createImage(url: string) {
return new CanvasImageElement(url, this.canvasContext);
}
requestAnimationFrame(fn: any) {
const frameFn = bindDrawRunnable(fn, this.canvasContext);
return this.offscreenCanvas.requestAnimationFrame(() => {
frameFn(Date.now());
});
}
cancelAnimationFrame(tid: any) {
return this.offscreenCanvas.cancelAnimationFrame(tid);
}
}
class SimulatedCanvasContext {
public canvas: CanvasElement;
constructor(
private ctx: IMiniProgramCanvasContext_v1
) {
}
private _fillStyle: string | TCanvasGradient | null | TCanvasPattern;
set fillStyle(value: string | TCanvasGradient | null | TCanvasPattern) { this._fillStyle = this.ctx.fillStyle = value; }
get fillStyle(): string | TCanvasGradient | null | TCanvasPattern { return this._fillStyle; }
// WontFIX: filter
set font(value: string) { this.ctx.font = value; }
get font(): string { return this.ctx.font; }
set fontSize(value: any) {
this.ctx.fontSize = value;
}
private _globalAlpha = 1;
set globalAlpha(value: number) { this._globalAlpha = this.ctx.globalAlpha = value; }
get globalAlpha() { return this._globalAlpha; }
private _globalCompositeOperation: string;
set globalCompositeOperation(value: string) { this._globalCompositeOperation = this.ctx.globalCompositeOperation = value; }
get globalCompositeOperation() { return this._globalCompositeOperation; }
// WontFIX: imageSmoothingEnabled
private _lineCap: TCanvasLineCap;
set lineCap(value: TCanvasLineCap) { this._lineCap = this.ctx.lineCap = value; }
get lineCap() { return this._lineCap; }
private _lineDashOffset: number;
set lineDashOffset(value: number) { this._lineDashOffset = this.ctx.lineDashOffset = value; }
get lineDashOffset() { return this._lineDashOffset; }
private _lineJoin: TCanvasLineJoin;
set lineJoin(value: TCanvasLineJoin) { this._lineJoin = this.ctx.lineJoin = value; }
get lineJoin() { return this._lineJoin; }
private _lineWidth: number;
set lineWidth(value: number) { this.ctx.lineWidth = value; }
get lineWidth() { return this._lineWidth; }
private _miterLimit: number;
set miterLimit(value: number) { this._miterLimit = this.ctx.miterLimit = value; }
get miterLimit() { return this._miterLimit; }
// WontFIX: shadowBlur
// WontFIX: shadowColor
// WontFIX: shadowOffsetX
// WontFIX: shadowOffsetY
private _strokeStyle: string | TCanvasGradient | null | TCanvasPattern;
set strokeStyle(value: string | TCanvasGradient | null | TCanvasPattern) { this._strokeStyle = this.ctx.strokeStyle = value; }
get strokeStyle() { return this._strokeStyle; }
private _textAlign: TCanvasTextAlign;
set textAlign(value: TCanvasTextAlign) { this._textAlign = this.ctx.textAlign = value; }
get textAlign() { return this._textAlign; }
private _textBaseline: TCanvasTextBaseLine;
set textBaseline(value: TCanvasTextBaseLine) { this._textBaseline = this.ctx.textBaseline = value; }
get textBaseline() { return this._textBaseline; }
public arc(x: number, y: number, radius: number, startDeg: number, endDeg: number, anticlockwise?: boolean): void {
if (anticlockwise === undefined) {
return this.ctx.arc(x, y, radius, startDeg, endDeg);
}
return this.ctx.arc(x, y, radius, startDeg, endDeg, anticlockwise);
}
public arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {
return this.ctx.arcTo(x1, y1, x2, y2, radius);
}
public beginPath(): void {
return this.ctx.beginPath();
}
public bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void {
return this.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
public clearRect(x: number, y: number, width: number, height: number): void {
return this.ctx.clearRect(x, y, width, height);
}
public clip(): void {
return this.ctx.clip();
}
public closePath(): void {
return this.ctx.closePath();
}
// WontFIX: createImageData
public createLinearGradient(x0: number, y0: number, x1: number, y1: number): TCanvasGradient {
return this.ctx.createLinearGradient(x0, y0, x1, y1);
}
public createPattern(image: TCanvasImageSource, repeat: TCanvasPatternRepeat): TCanvasPattern {
const target = CanvasImageElement.toImageSource(image);
return this.ctx.createPattern(target, repeat);
}
public createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): TCanvasGradient {
return this.ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
}
// WontFIX: createPath2D
drawImage(
imageOrContext: TCanvasImageSource,
sx: number,
sy: number,
sWidth: number,
sHeight: number,
dx: number,
dy: number,
dWidth: number,
dHeight: number
): void {
const target = CanvasImageElement.toImageSource(imageOrContext);
if (!target) {
return;
}
if (sWidth === undefined) {
this.ctx.drawImage(target, sx, sy);
} else if (dx === undefined) {
this.ctx.drawImage(target, sx, sy, sWidth, sHeight);
} else {
this.ctx.drawImage(target, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
}
}
public ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean) {
this.save();
this.translate(x, y);
this.rotate(rotation);
this.scale(radiusX, radiusY);
this.arc(0, 0, 1, startAngle, endAngle, anticlockwise);
this.restore();
}
public fill(): void {
return this.ctx.fill();
}
public fillRect(x: number, y: number, width: number, height: number): void {
return this.ctx.fillRect(x, y, width, height);
}
public fillText(text: string, x: number, y: number, maxWidth?: number): void {
if (maxWidth === undefined) {
return this.ctx.fillText(text, x, y);
}
return this.ctx.fillText(text, x, y, maxWidth);
}
// WontFIX: getImageData
// WontFIX: getLineDash
// WontFIX: getTransform
// Only For Offscreen: isPointInPath
// WontFIX: isPointInStroke
public lineTo(x: number, y: number): void {
return this.ctx.lineTo(x, y);
}
// Only For Offscreen: measureText
public moveTo(x: number, y: number): void {
return this.ctx.moveTo(x, y);
}
// WontFIX: putImageData
public quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void {
return this.ctx.quadraticCurveTo(cpx, cpy, x, y);
}
public rect(x: number, y: number, width: number, height: number): void {
return this.ctx.rect(x, y, width, height);
}
public resetTransform() {
return this.setTransform(1, 0, 0, 1, 0, 0);
}
public restore(): void {
return this.ctx.restore();
}
public rotate(angle: number): void {
return this.ctx.rotate(angle);
}
// WontFIX: roundRect
public save(): void {
return this.ctx.save();
}
public scale(x: number, y: number): void {
return this.ctx.scale(x, y);
}
public setLineDash(gaps: number[]): void {
return this.ctx.setLineDash(gaps);
}
public setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void {
return this.ctx.setTransform(a, b, c, d, e, f);
}
public stroke(): void {
return this.ctx.stroke();
}
public strokeRect(x: number, y: number, width: number, height: number): void {
return this.ctx.fillRect(x, y, width, height);
}
public strokeText(text: string, x: number, y: number, maxWidth?: number): void {
if (maxWidth === undefined) {
this.ctx.strokeText(text, x, y);
}
return this.ctx.strokeText(text, x, y, maxWidth);
}
public transform(a: number, b: number, c: number, d: number, e: number, f: number): void {
return this.ctx.transform(a, b, c, d, e, f);
}
public translate(x: number, y: number): void {
return this.ctx.translate(x, y);
}
}