@leafer-draw/worker
Version:
1,419 lines (1,389 loc) • 79 kB
JavaScript
import { LeaferCanvasBase, Platform, canvasPatch, FileHelper, Creator, LeaferImage, defineKey, LeafList, DataHelper, RenderEvent, ChildEvent, WatchEvent, PropertyEvent, LeafHelper, BranchHelper, LeafBoundsHelper, Bounds, Debug, LeafLevelList, LayoutEvent, Run, ImageManager, ResizeEvent, BoundsHelper, MatrixHelper, MathHelper, AlignHelper, ImageEvent, AroundHelper, PointHelper, Direction4 } from '@leafer/core';
export * from '@leafer/core';
export { LeaferImage } from '@leafer/core';
import { PaintImage, ColorConvert, PaintGradient, Export, Group, TextConvert, Paint, Effect } from '@leafer-ui/draw';
export * from '@leafer-ui/draw';
class LeaferCanvas extends LeaferCanvasBase {
get allowBackgroundColor() { return true; }
init() {
this.__createView();
this.__createContext();
this.resize(this.config);
}
__createView() {
this.view = Platform.origin.createCanvas(1, 1);
}
updateViewSize() {
const { width, height, pixelRatio } = this;
this.view.width = Math.ceil(width * pixelRatio);
this.view.height = Math.ceil(height * pixelRatio);
this.clientBounds = this.bounds;
}
}
canvasPatch(OffscreenCanvasRenderingContext2D.prototype);
canvasPatch(Path2D.prototype);
const { mineType } = FileHelper;
Object.assign(Creator, {
canvas: (options, manager) => new LeaferCanvas(options, manager),
image: (options) => new LeaferImage(options)
});
function useCanvas(_canvasType, _power) {
Platform.origin = {
createCanvas: (width, height) => new OffscreenCanvas(width, height),
canvasToDataURL: (canvas, type, quality) => {
return new Promise((resolve, reject) => {
canvas.convertToBlob({ type: mineType(type), quality }).then((blob) => {
var reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = (e) => reject(e);
reader.readAsDataURL(blob);
}).catch((e) => {
reject(e);
});
});
},
canvasToBolb: (canvas, type, quality) => canvas.convertToBlob({ type: mineType(type), quality }),
canvasSaveAs: (_canvas, _filename, _quality) => new Promise((resolve) => resolve()),
download(_url, _filename) { return undefined; },
loadImage(src) {
return new Promise((resolve, reject) => {
let req = new XMLHttpRequest();
req.open('GET', Platform.image.getRealURL(src), true);
req.responseType = "blob";
req.onload = () => {
createImageBitmap(req.response).then(img => {
resolve(img);
}).catch(e => {
reject(e);
});
};
req.onerror = (e) => reject(e);
req.send();
});
}
};
Platform.canvas = Creator.canvas();
Platform.conicGradientSupport = !!Platform.canvas.context.createConicGradient;
}
Platform.name = 'web';
Platform.isWorker = true;
Platform.backgrounder = true;
Platform.requestRender = function (render) { requestAnimationFrame(render); };
defineKey(Platform, 'devicePixelRatio', { get() { return 1; } });
const { userAgent } = navigator;
if (userAgent.indexOf("Firefox") > -1) {
Platform.conicGradientRotate90 = true;
Platform.intWheelDeltaY = true;
}
else if (userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") === -1) {
Platform.fullImageShadow = true;
}
if (userAgent.indexOf('Windows') > -1) {
Platform.os = 'Windows';
Platform.intWheelDeltaY = true;
}
else if (userAgent.indexOf('Mac') > -1) {
Platform.os = 'Mac';
}
else if (userAgent.indexOf('Linux') > -1) {
Platform.os = 'Linux';
}
class Watcher {
get childrenChanged() { return this.hasAdd || this.hasRemove || this.hasVisible; }
get updatedList() {
if (this.hasRemove) {
const updatedList = new LeafList();
this.__updatedList.list.forEach(item => { if (item.leafer)
updatedList.add(item); });
return updatedList;
}
else {
return this.__updatedList;
}
}
constructor(target, userConfig) {
this.totalTimes = 0;
this.config = {};
this.__updatedList = new LeafList();
this.target = target;
if (userConfig)
this.config = DataHelper.default(userConfig, this.config);
this.__listenEvents();
}
start() {
if (this.disabled)
return;
this.running = true;
}
stop() {
this.running = false;
}
disable() {
this.stop();
this.__removeListenEvents();
this.disabled = true;
}
update() {
this.changed = true;
if (this.running)
this.target.emit(RenderEvent.REQUEST);
}
__onAttrChange(event) {
this.__updatedList.add(event.target);
this.update();
}
__onChildEvent(event) {
if (event.type === ChildEvent.ADD) {
this.hasAdd = true;
this.__pushChild(event.child);
}
else {
this.hasRemove = true;
this.__updatedList.add(event.parent);
}
this.update();
}
__pushChild(child) {
this.__updatedList.add(child);
if (child.isBranch)
this.__loopChildren(child);
}
__loopChildren(parent) {
const { children } = parent;
for (let i = 0, len = children.length; i < len; i++)
this.__pushChild(children[i]);
}
__onRquestData() {
this.target.emitEvent(new WatchEvent(WatchEvent.DATA, { updatedList: this.updatedList }));
this.__updatedList = new LeafList();
this.totalTimes++;
this.changed = false;
this.hasVisible = false;
this.hasRemove = false;
this.hasAdd = false;
}
__listenEvents() {
const { target } = this;
this.__eventIds = [
target.on_(PropertyEvent.CHANGE, this.__onAttrChange, this),
target.on_([ChildEvent.ADD, ChildEvent.REMOVE], this.__onChildEvent, this),
target.on_(WatchEvent.REQUEST, this.__onRquestData, this)
];
}
__removeListenEvents() {
this.target.off_(this.__eventIds);
}
destroy() {
if (this.target) {
this.stop();
this.__removeListenEvents();
this.target = null;
this.__updatedList = null;
}
}
}
const { updateAllMatrix: updateAllMatrix$1, updateBounds: updateOneBounds, updateChange: updateOneChange } = LeafHelper;
const { pushAllChildBranch, pushAllParent } = BranchHelper;
function updateMatrix(updateList, levelList) {
let layout;
updateList.list.forEach(leaf => {
layout = leaf.__layout;
if (levelList.without(leaf) && !layout.proxyZoom) {
if (layout.matrixChanged) {
updateAllMatrix$1(leaf, true);
levelList.add(leaf);
if (leaf.isBranch)
pushAllChildBranch(leaf, levelList);
pushAllParent(leaf, levelList);
}
else if (layout.boundsChanged) {
levelList.add(leaf);
if (leaf.isBranch)
leaf.__tempNumber = 0;
pushAllParent(leaf, levelList);
}
}
});
}
function updateBounds(boundsList) {
let list, branch, children;
boundsList.sort(true);
boundsList.levels.forEach(level => {
list = boundsList.levelMap[level];
for (let i = 0, len = list.length; i < len; i++) {
branch = list[i];
if (branch.isBranch && branch.__tempNumber) {
children = branch.children;
for (let j = 0, jLen = children.length; j < jLen; j++) {
if (!children[j].isBranch) {
updateOneBounds(children[j]);
}
}
}
updateOneBounds(branch);
}
});
}
function updateChange(updateList) {
updateList.list.forEach(updateOneChange);
}
const { worldBounds } = LeafBoundsHelper;
class LayoutBlockData {
constructor(list) {
this.updatedBounds = new Bounds();
this.beforeBounds = new Bounds();
this.afterBounds = new Bounds();
if (list instanceof Array)
list = new LeafList(list);
this.updatedList = list;
}
setBefore() {
this.beforeBounds.setListWithFn(this.updatedList.list, worldBounds);
}
setAfter() {
this.afterBounds.setListWithFn(this.updatedList.list, worldBounds);
this.updatedBounds.setList([this.beforeBounds, this.afterBounds]);
}
merge(data) {
this.updatedList.addList(data.updatedList.list);
this.beforeBounds.add(data.beforeBounds);
this.afterBounds.add(data.afterBounds);
this.updatedBounds.add(data.updatedBounds);
}
destroy() {
this.updatedList = null;
}
}
const { updateAllMatrix, updateAllChange } = LeafHelper;
const debug$1 = Debug.get('Layouter');
class Layouter {
constructor(target, userConfig) {
this.totalTimes = 0;
this.config = {};
this.__levelList = new LeafLevelList();
this.target = target;
if (userConfig)
this.config = DataHelper.default(userConfig, this.config);
this.__listenEvents();
}
start() {
if (this.disabled)
return;
this.running = true;
}
stop() {
this.running = false;
}
disable() {
this.stop();
this.__removeListenEvents();
this.disabled = true;
}
layout() {
if (!this.running)
return;
const { target } = this;
this.times = 0;
try {
target.emit(LayoutEvent.START);
this.layoutOnce();
target.emitEvent(new LayoutEvent(LayoutEvent.END, this.layoutedBlocks, this.times));
}
catch (e) {
debug$1.error(e);
}
this.layoutedBlocks = null;
}
layoutAgain() {
if (this.layouting) {
this.waitAgain = true;
}
else {
this.layoutOnce();
}
}
layoutOnce() {
if (this.layouting)
return debug$1.warn('layouting');
if (this.times > 3)
return debug$1.warn('layout max times');
this.times++;
this.totalTimes++;
this.layouting = true;
this.target.emit(WatchEvent.REQUEST);
if (this.totalTimes > 1) {
this.partLayout();
}
else {
this.fullLayout();
}
this.layouting = false;
if (this.waitAgain) {
this.waitAgain = false;
this.layoutOnce();
}
}
partLayout() {
var _a;
if (!((_a = this.__updatedList) === null || _a === void 0 ? void 0 : _a.length))
return;
const t = Run.start('PartLayout');
const { target, __updatedList: updateList } = this;
const { BEFORE, LAYOUT, AFTER } = LayoutEvent;
const blocks = this.getBlocks(updateList);
blocks.forEach(item => item.setBefore());
target.emitEvent(new LayoutEvent(BEFORE, blocks, this.times));
this.extraBlock = null;
updateList.sort();
updateMatrix(updateList, this.__levelList);
updateBounds(this.__levelList);
updateChange(updateList);
if (this.extraBlock)
blocks.push(this.extraBlock);
blocks.forEach(item => item.setAfter());
target.emitEvent(new LayoutEvent(LAYOUT, blocks, this.times));
target.emitEvent(new LayoutEvent(AFTER, blocks, this.times));
this.addBlocks(blocks);
this.__levelList.reset();
this.__updatedList = null;
Run.end(t);
}
fullLayout() {
const t = Run.start('FullLayout');
const { target } = this;
const { BEFORE, LAYOUT, AFTER } = LayoutEvent;
const blocks = this.getBlocks(new LeafList(target));
target.emitEvent(new LayoutEvent(BEFORE, blocks, this.times));
Layouter.fullLayout(target);
blocks.forEach(item => { item.setAfter(); });
target.emitEvent(new LayoutEvent(LAYOUT, blocks, this.times));
target.emitEvent(new LayoutEvent(AFTER, blocks, this.times));
this.addBlocks(blocks);
Run.end(t);
}
static fullLayout(target) {
updateAllMatrix(target, true);
if (target.isBranch) {
BranchHelper.updateBounds(target);
}
else {
LeafHelper.updateBounds(target);
}
updateAllChange(target);
}
addExtra(leaf) {
if (!this.__updatedList.has(leaf)) {
const { updatedList, beforeBounds } = this.extraBlock || (this.extraBlock = new LayoutBlockData([]));
updatedList.length ? beforeBounds.add(leaf.__world) : beforeBounds.set(leaf.__world);
updatedList.add(leaf);
}
}
createBlock(data) {
return new LayoutBlockData(data);
}
getBlocks(list) {
return [this.createBlock(list)];
}
addBlocks(current) {
this.layoutedBlocks ? this.layoutedBlocks.push(...current) : this.layoutedBlocks = current;
}
__onReceiveWatchData(event) {
this.__updatedList = event.data.updatedList;
}
__listenEvents() {
const { target } = this;
this.__eventIds = [
target.on_(LayoutEvent.REQUEST, this.layout, this),
target.on_(LayoutEvent.AGAIN, this.layoutAgain, this),
target.on_(WatchEvent.DATA, this.__onReceiveWatchData, this)
];
}
__removeListenEvents() {
this.target.off_(this.__eventIds);
}
destroy() {
if (this.target) {
this.stop();
this.__removeListenEvents();
this.target = this.config = null;
}
}
}
const debug = Debug.get('Renderer');
class Renderer {
get needFill() { return !!(!this.canvas.allowBackgroundColor && this.config.fill); }
constructor(target, canvas, userConfig) {
this.FPS = 60;
this.totalTimes = 0;
this.times = 0;
this.config = {
usePartRender: true,
maxFPS: 60
};
this.target = target;
this.canvas = canvas;
if (userConfig)
this.config = DataHelper.default(userConfig, this.config);
this.__listenEvents();
}
start() {
this.running = true;
this.update(false);
}
stop() {
this.running = false;
}
update(change = true) {
if (!this.changed)
this.changed = change;
this.__requestRender();
}
requestLayout() {
this.target.emit(LayoutEvent.REQUEST);
}
checkRender() {
if (this.running) {
const { target } = this;
if (target.isApp) {
target.emit(RenderEvent.CHILD_START, target);
target.children.forEach(leafer => {
leafer.renderer.FPS = this.FPS;
leafer.renderer.checkRender();
});
target.emit(RenderEvent.CHILD_END, target);
}
if (this.changed && this.canvas.view)
this.render();
this.target.emit(RenderEvent.NEXT);
}
}
render(callback) {
if (!(this.running && this.canvas.view))
return this.update();
const { target } = this;
this.times = 0;
this.totalBounds = new Bounds();
debug.log(target.innerName, '--->');
try {
this.emitRender(RenderEvent.START);
this.renderOnce(callback);
this.emitRender(RenderEvent.END, this.totalBounds);
ImageManager.clearRecycled();
}
catch (e) {
this.rendering = false;
debug.error(e);
}
debug.log('-------------|');
}
renderAgain() {
if (this.rendering) {
this.waitAgain = true;
}
else {
this.renderOnce();
}
}
renderOnce(callback) {
if (this.rendering)
return debug.warn('rendering');
if (this.times > 3)
return debug.warn('render max times');
this.times++;
this.totalTimes++;
this.rendering = true;
this.changed = false;
this.renderBounds = new Bounds();
this.renderOptions = {};
if (callback) {
this.emitRender(RenderEvent.BEFORE);
callback();
}
else {
this.requestLayout();
if (this.ignore) {
this.ignore = this.rendering = false;
return;
}
this.emitRender(RenderEvent.BEFORE);
if (this.config.usePartRender && this.totalTimes > 1) {
this.partRender();
}
else {
this.fullRender();
}
}
this.emitRender(RenderEvent.RENDER, this.renderBounds, this.renderOptions);
this.emitRender(RenderEvent.AFTER, this.renderBounds, this.renderOptions);
this.updateBlocks = null;
this.rendering = false;
if (this.waitAgain) {
this.waitAgain = false;
this.renderOnce();
}
}
partRender() {
const { canvas, updateBlocks: list } = this;
if (!list)
return;
this.mergeBlocks();
list.forEach(block => { if (canvas.bounds.hit(block) && !block.isEmpty())
this.clipRender(block); });
}
clipRender(block) {
const t = Run.start('PartRender');
const { canvas } = this, bounds = block.getIntersect(canvas.bounds), realBounds = new Bounds(bounds);
canvas.save();
bounds.spread(Renderer.clipSpread).ceil();
canvas.clearWorld(bounds, true);
canvas.clipWorld(bounds, true);
this.__render(bounds, realBounds);
canvas.restore();
Run.end(t);
}
fullRender() {
const t = Run.start('FullRender');
const { canvas } = this;
canvas.save();
canvas.clear();
this.__render(canvas.bounds);
canvas.restore();
Run.end(t);
}
__render(bounds, realBounds) {
const { canvas } = this, includes = bounds.includes(this.target.__world), options = includes ? { includes } : { bounds, includes };
if (this.needFill)
canvas.fillWorld(bounds, this.config.fill);
if (Debug.showRepaint)
Debug.drawRepaint(canvas, bounds);
this.target.__render(canvas, options);
this.renderBounds = realBounds = realBounds || bounds;
this.renderOptions = options;
this.totalBounds.isEmpty() ? this.totalBounds = realBounds : this.totalBounds.add(realBounds);
canvas.updateRender(realBounds);
}
addBlock(block) {
if (!this.updateBlocks)
this.updateBlocks = [];
this.updateBlocks.push(block);
}
mergeBlocks() {
const { updateBlocks: list } = this;
if (list) {
const bounds = new Bounds();
bounds.setList(list);
list.length = 0;
list.push(bounds);
}
}
__requestRender() {
const target = this.target;
if (this.requestTime || !target)
return;
if (target.parentApp)
return target.parentApp.requestRender(false);
const requestTime = this.requestTime = Date.now();
Platform.requestRender(() => {
this.FPS = Math.min(60, Math.ceil(1000 / (Date.now() - requestTime)));
this.requestTime = 0;
this.checkRender();
});
}
__onResize(e) {
if (this.canvas.unreal)
return;
if (e.bigger || !e.samePixelRatio) {
const { width, height } = e.old;
const bounds = new Bounds(0, 0, width, height);
if (!bounds.includes(this.target.__world) || this.needFill || !e.samePixelRatio) {
this.addBlock(this.canvas.bounds);
this.target.forceUpdate('surface');
return;
}
}
this.addBlock(new Bounds(0, 0, 1, 1));
this.update();
}
__onLayoutEnd(event) {
if (event.data)
event.data.map(item => {
let empty;
if (item.updatedList)
item.updatedList.list.some(leaf => {
empty = (!leaf.__world.width || !leaf.__world.height);
if (empty) {
if (!leaf.isLeafer)
debug.tip(leaf.innerName, ': empty');
empty = (!leaf.isBranch || leaf.isBranchLeaf);
}
return empty;
});
this.addBlock(empty ? this.canvas.bounds : item.updatedBounds);
});
}
emitRender(type, bounds, options) {
this.target.emitEvent(new RenderEvent(type, this.times, bounds, options));
}
__listenEvents() {
const { target } = this;
this.__eventIds = [
target.on_(RenderEvent.REQUEST, this.update, this),
target.on_(LayoutEvent.END, this.__onLayoutEnd, this),
target.on_(RenderEvent.AGAIN, this.renderAgain, this),
target.on_(ResizeEvent.RESIZE, this.__onResize, this)
];
}
__removeListenEvents() {
this.target.off_(this.__eventIds);
}
destroy() {
if (this.target) {
this.stop();
this.__removeListenEvents();
this.target = this.canvas = this.config = null;
}
}
}
Renderer.clipSpread = 10;
Object.assign(Creator, {
watcher: (target, options) => new Watcher(target, options),
layouter: (target, options) => new Layouter(target, options),
renderer: (target, canvas, options) => new Renderer(target, canvas, options),
selector: (_target, _options) => undefined,
interaction: (_target, _canvas, _selector, _options) => undefined
});
Platform.layout = Layouter.fullLayout;
function fillText(ui, canvas) {
const data = ui.__, { rows, decorationY } = data.__textDrawData;
if (data.__isPlacehold && data.placeholderColor)
canvas.fillStyle = data.placeholderColor;
let row;
for (let i = 0, len = rows.length; i < len; i++) {
row = rows[i];
if (row.text)
canvas.fillText(row.text, row.x, row.y);
else if (row.data)
row.data.forEach(charData => { canvas.fillText(charData.char, charData.x, row.y); });
}
if (decorationY) {
const { decorationColor, decorationHeight } = data.__textDrawData;
if (decorationColor)
canvas.fillStyle = decorationColor;
rows.forEach(row => decorationY.forEach(value => canvas.fillRect(row.x, row.y + value, row.width, decorationHeight)));
}
}
function fill(fill, ui, canvas) {
canvas.fillStyle = fill;
fillPathOrText(ui, canvas);
}
function fills(fills, ui, canvas) {
let item;
for (let i = 0, len = fills.length; i < len; i++) {
item = fills[i];
if (item.image) {
if (PaintImage.checkImage(ui, canvas, item, !ui.__.__font))
continue;
if (!item.style) {
if (!i && item.image.isPlacehold)
ui.drawImagePlaceholder(canvas, item.image);
continue;
}
}
canvas.fillStyle = item.style;
if (item.transform) {
canvas.save();
canvas.transform(item.transform);
if (item.blendMode)
canvas.blendMode = item.blendMode;
fillPathOrText(ui, canvas);
canvas.restore();
}
else {
if (item.blendMode) {
canvas.saveBlendMode(item.blendMode);
fillPathOrText(ui, canvas);
canvas.restoreBlendMode();
}
else
fillPathOrText(ui, canvas);
}
}
}
function fillPathOrText(ui, canvas) {
ui.__.__font ? fillText(ui, canvas) : (ui.__.windingRule ? canvas.fill(ui.__.windingRule) : canvas.fill());
}
function strokeText(stroke, ui, canvas) {
const { strokeAlign } = ui.__;
const isStrokes = typeof stroke !== 'string';
switch (strokeAlign) {
case 'center':
canvas.setStroke(isStrokes ? undefined : stroke, ui.__.strokeWidth, ui.__);
isStrokes ? drawStrokesStyle(stroke, true, ui, canvas) : drawTextStroke(ui, canvas);
break;
case 'inside':
drawAlignStroke('inside', stroke, isStrokes, ui, canvas);
break;
case 'outside':
drawAlignStroke('outside', stroke, isStrokes, ui, canvas);
break;
}
}
function drawAlignStroke(align, stroke, isStrokes, ui, canvas) {
const { __strokeWidth, __font } = ui.__;
const out = canvas.getSameCanvas(true, true);
out.setStroke(isStrokes ? undefined : stroke, __strokeWidth * 2, ui.__);
out.font = __font;
isStrokes ? drawStrokesStyle(stroke, true, ui, out) : drawTextStroke(ui, out);
out.blendMode = align === 'outside' ? 'destination-out' : 'destination-in';
fillText(ui, out);
out.blendMode = 'normal';
if (ui.__worldFlipped)
canvas.copyWorldByReset(out, ui.__nowWorld);
else
canvas.copyWorldToInner(out, ui.__nowWorld, ui.__layout.renderBounds);
out.recycle(ui.__nowWorld);
}
function drawTextStroke(ui, canvas) {
let row, data = ui.__.__textDrawData;
const { rows, decorationY } = data;
for (let i = 0, len = rows.length; i < len; i++) {
row = rows[i];
if (row.text)
canvas.strokeText(row.text, row.x, row.y);
else if (row.data)
row.data.forEach(charData => { canvas.strokeText(charData.char, charData.x, row.y); });
}
if (decorationY) {
const { decorationHeight } = data;
rows.forEach(row => decorationY.forEach(value => canvas.strokeRect(row.x, row.y + value, row.width, decorationHeight)));
}
}
function drawStrokesStyle(strokes, isText, ui, canvas) {
let item;
for (let i = 0, len = strokes.length; i < len; i++) {
item = strokes[i];
if (item.image && PaintImage.checkImage(ui, canvas, item, false))
continue;
if (item.style) {
canvas.strokeStyle = item.style;
if (item.blendMode) {
canvas.saveBlendMode(item.blendMode);
isText ? drawTextStroke(ui, canvas) : canvas.stroke();
canvas.restoreBlendMode();
}
else {
isText ? drawTextStroke(ui, canvas) : canvas.stroke();
}
}
}
}
function stroke(stroke, ui, canvas) {
const options = ui.__;
const { __strokeWidth, strokeAlign, __font } = options;
if (!__strokeWidth)
return;
if (__font) {
strokeText(stroke, ui, canvas);
}
else {
switch (strokeAlign) {
case 'center':
canvas.setStroke(stroke, __strokeWidth, options);
canvas.stroke();
if (options.__useArrow)
strokeArrow(ui, canvas);
break;
case 'inside':
canvas.save();
canvas.setStroke(stroke, __strokeWidth * 2, options);
options.windingRule ? canvas.clip(options.windingRule) : canvas.clip();
canvas.stroke();
canvas.restore();
break;
case 'outside':
const out = canvas.getSameCanvas(true, true);
out.setStroke(stroke, __strokeWidth * 2, options);
ui.__drawRenderPath(out);
out.stroke();
options.windingRule ? out.clip(options.windingRule) : out.clip();
out.clearWorld(ui.__layout.renderBounds);
if (ui.__worldFlipped)
canvas.copyWorldByReset(out, ui.__nowWorld);
else
canvas.copyWorldToInner(out, ui.__nowWorld, ui.__layout.renderBounds);
out.recycle(ui.__nowWorld);
break;
}
}
}
function strokes(strokes, ui, canvas) {
const options = ui.__;
const { __strokeWidth, strokeAlign, __font } = options;
if (!__strokeWidth)
return;
if (__font) {
strokeText(strokes, ui, canvas);
}
else {
switch (strokeAlign) {
case 'center':
canvas.setStroke(undefined, __strokeWidth, options);
drawStrokesStyle(strokes, false, ui, canvas);
if (options.__useArrow)
strokeArrow(ui, canvas);
break;
case 'inside':
canvas.save();
canvas.setStroke(undefined, __strokeWidth * 2, options);
options.windingRule ? canvas.clip(options.windingRule) : canvas.clip();
drawStrokesStyle(strokes, false, ui, canvas);
canvas.restore();
break;
case 'outside':
const { renderBounds } = ui.__layout;
const out = canvas.getSameCanvas(true, true);
ui.__drawRenderPath(out);
out.setStroke(undefined, __strokeWidth * 2, options);
drawStrokesStyle(strokes, false, ui, out);
options.windingRule ? out.clip(options.windingRule) : out.clip();
out.clearWorld(renderBounds);
if (ui.__worldFlipped)
canvas.copyWorldByReset(out, ui.__nowWorld);
else
canvas.copyWorldToInner(out, ui.__nowWorld, renderBounds);
out.recycle(ui.__nowWorld);
break;
}
}
}
function strokeArrow(ui, canvas) {
if (ui.__.dashPattern) {
canvas.beginPath();
ui.__drawPathByData(canvas, ui.__.__pathForArrow);
canvas.dashPattern = null;
canvas.stroke();
}
}
const { getSpread, getOuterOf, getByMove, getIntersectData } = BoundsHelper;
function shape(ui, current, options) {
const canvas = current.getSameCanvas();
const nowWorld = ui.__nowWorld;
let bounds, fitMatrix, shapeBounds, worldCanvas;
let { scaleX, scaleY } = nowWorld;
if (scaleX < 0)
scaleX = -scaleX;
if (scaleY < 0)
scaleY = -scaleY;
if (current.bounds.includes(nowWorld)) {
worldCanvas = canvas;
bounds = shapeBounds = nowWorld;
}
else {
const { renderShapeSpread: spread } = ui.__layout;
const worldClipBounds = getIntersectData(spread ? getSpread(current.bounds, scaleX === scaleY ? spread * scaleX : [spread * scaleY, spread * scaleX]) : current.bounds, nowWorld);
fitMatrix = current.bounds.getFitMatrix(worldClipBounds);
let { a: fitScaleX, d: fitScaleY } = fitMatrix;
if (fitMatrix.a < 1) {
worldCanvas = current.getSameCanvas();
ui.__renderShape(worldCanvas, options);
scaleX *= fitScaleX;
scaleY *= fitScaleY;
}
shapeBounds = getOuterOf(nowWorld, fitMatrix);
bounds = getByMove(shapeBounds, -fitMatrix.e, -fitMatrix.f);
if (options.matrix) {
const { matrix } = options;
fitMatrix.multiply(matrix);
fitScaleX *= matrix.scaleX;
fitScaleY *= matrix.scaleY;
}
options = Object.assign(Object.assign({}, options), { matrix: fitMatrix.withScale(fitScaleX, fitScaleY) });
}
ui.__renderShape(canvas, options);
return {
canvas, matrix: fitMatrix, bounds,
worldCanvas, shapeBounds, scaleX, scaleY
};
}
let recycleMap;
function compute(attrName, ui) {
const data = ui.__, leafPaints = [];
let paints = data.__input[attrName], hasOpacityPixel;
if (!(paints instanceof Array))
paints = [paints];
recycleMap = PaintImage.recycleImage(attrName, data);
for (let i = 0, len = paints.length, item; i < len; i++) {
item = getLeafPaint(attrName, paints[i], ui);
if (item)
leafPaints.push(item);
}
data['_' + attrName] = leafPaints.length ? leafPaints : undefined;
if (leafPaints.length && leafPaints[0].image)
hasOpacityPixel = leafPaints[0].image.hasOpacityPixel;
attrName === 'fill' ? data.__pixelFill = hasOpacityPixel : data.__pixelStroke = hasOpacityPixel;
}
function getLeafPaint(attrName, paint, ui) {
if (typeof paint !== 'object' || paint.visible === false || paint.opacity === 0)
return undefined;
const { boxBounds } = ui.__layout;
switch (paint.type) {
case 'solid':
let { type, blendMode, color, opacity } = paint;
return { type, blendMode, style: ColorConvert.string(color, opacity) };
case 'image':
return PaintImage.image(ui, attrName, paint, boxBounds, !recycleMap || !recycleMap[paint.url]);
case 'linear':
return PaintGradient.linearGradient(paint, boxBounds);
case 'radial':
return PaintGradient.radialGradient(paint, boxBounds);
case 'angular':
return PaintGradient.conicGradient(paint, boxBounds);
default:
return paint.r !== undefined ? { type: 'solid', style: ColorConvert.string(paint) } : undefined;
}
}
const PaintModule = {
compute,
fill,
fills,
fillPathOrText,
fillText,
stroke,
strokes,
strokeText,
drawTextStroke,
shape
};
let origin = {};
const { get: get$3, rotateOfOuter: rotateOfOuter$1, translate: translate$1, scaleOfOuter: scaleOfOuter$1, scale: scaleHelper, rotate } = MatrixHelper;
function fillOrFitMode(data, box, x, y, scaleX, scaleY, rotation) {
const transform = get$3();
translate$1(transform, box.x + x, box.y + y);
scaleHelper(transform, scaleX, scaleY);
if (rotation)
rotateOfOuter$1(transform, { x: box.x + box.width / 2, y: box.y + box.height / 2 }, rotation);
data.transform = transform;
}
function clipMode(data, box, x, y, scaleX, scaleY, rotation) {
const transform = get$3();
translate$1(transform, box.x + x, box.y + y);
if (scaleX)
scaleHelper(transform, scaleX, scaleY);
if (rotation)
rotate(transform, rotation);
data.transform = transform;
}
function repeatMode(data, box, width, height, x, y, scaleX, scaleY, rotation, align) {
const transform = get$3();
if (rotation) {
if (align === 'center') {
rotateOfOuter$1(transform, { x: width / 2, y: height / 2 }, rotation);
}
else {
rotate(transform, rotation);
switch (rotation) {
case 90:
translate$1(transform, height, 0);
break;
case 180:
translate$1(transform, width, height);
break;
case 270:
translate$1(transform, 0, width);
break;
}
}
}
origin.x = box.x + x;
origin.y = box.y + y;
translate$1(transform, origin.x, origin.y);
if (scaleX)
scaleOfOuter$1(transform, origin, scaleX, scaleY);
data.transform = transform;
}
const { get: get$2, translate } = MatrixHelper;
const tempBox = new Bounds();
const tempPoint = {};
const tempScaleData = {};
function createData(leafPaint, image, paint, box) {
const { blendMode, changeful, sync } = paint;
if (blendMode)
leafPaint.blendMode = blendMode;
if (changeful)
leafPaint.changeful = changeful;
if (sync)
leafPaint.sync = sync;
leafPaint.data = getPatternData(paint, box, image);
}
function getPatternData(paint, box, image) {
let { width, height } = image;
if (paint.padding)
box = tempBox.set(box).shrink(paint.padding);
if (paint.mode === 'strench')
paint.mode = 'stretch';
const { opacity, mode, align, offset, scale, size, rotation, repeat, filters } = paint;
const sameBox = box.width === width && box.height === height;
const data = { mode };
const swapSize = align !== 'center' && (rotation || 0) % 180 === 90;
const swapWidth = swapSize ? height : width, swapHeight = swapSize ? width : height;
let x = 0, y = 0, scaleX, scaleY;
if (!mode || mode === 'cover' || mode === 'fit') {
if (!sameBox || rotation) {
const sw = box.width / swapWidth, sh = box.height / swapHeight;
scaleX = scaleY = mode === 'fit' ? Math.min(sw, sh) : Math.max(sw, sh);
x += (box.width - width * scaleX) / 2, y += (box.height - height * scaleY) / 2;
}
}
else if (scale || size) {
MathHelper.getScaleData(scale, size, image, tempScaleData);
scaleX = tempScaleData.scaleX;
scaleY = tempScaleData.scaleY;
}
if (align) {
const imageBounds = { x, y, width: swapWidth, height: swapHeight };
if (scaleX)
imageBounds.width *= scaleX, imageBounds.height *= scaleY;
AlignHelper.toPoint(align, imageBounds, box, tempPoint, true);
x += tempPoint.x, y += tempPoint.y;
}
if (offset)
x += offset.x, y += offset.y;
switch (mode) {
case 'stretch':
if (!sameBox)
width = box.width, height = box.height;
break;
case 'normal':
case 'clip':
if (x || y || scaleX || rotation)
clipMode(data, box, x, y, scaleX, scaleY, rotation);
break;
case 'repeat':
if (!sameBox || scaleX || rotation)
repeatMode(data, box, width, height, x, y, scaleX, scaleY, rotation, align);
if (!repeat)
data.repeat = 'repeat';
break;
case 'fit':
case 'cover':
default:
if (scaleX)
fillOrFitMode(data, box, x, y, scaleX, scaleY, rotation);
}
if (!data.transform) {
if (box.x || box.y) {
data.transform = get$2();
translate(data.transform, box.x, box.y);
}
}
if (scaleX && mode !== 'stretch') {
data.scaleX = scaleX;
data.scaleY = scaleY;
}
data.width = width;
data.height = height;
if (opacity)
data.opacity = opacity;
if (filters)
data.filters = filters;
if (repeat)
data.repeat = typeof repeat === 'string' ? (repeat === 'x' ? 'repeat-x' : 'repeat-y') : 'repeat';
return data;
}
let cache, box = new Bounds();
const { isSame } = BoundsHelper;
function image(ui, attrName, paint, boxBounds, firstUse) {
let leafPaint, event;
const image = ImageManager.get(paint);
if (cache && paint === cache.paint && isSame(boxBounds, cache.boxBounds)) {
leafPaint = cache.leafPaint;
}
else {
leafPaint = { type: paint.type, image };
cache = image.use > 1 ? { leafPaint, paint, boxBounds: box.set(boxBounds) } : null;
}
if (firstUse || image.loading)
event = { image, attrName, attrValue: paint };
if (image.ready) {
checkSizeAndCreateData(ui, attrName, paint, image, leafPaint, boxBounds);
if (firstUse) {
onLoad(ui, event);
onLoadSuccess(ui, event);
}
}
else if (image.error) {
if (firstUse)
onLoadError(ui, event, image.error);
}
else {
if (firstUse) {
ignoreRender(ui, true);
onLoad(ui, event);
}
leafPaint.loadId = image.load(() => {
ignoreRender(ui, false);
if (!ui.destroyed) {
if (checkSizeAndCreateData(ui, attrName, paint, image, leafPaint, boxBounds)) {
if (image.hasOpacityPixel)
ui.__layout.hitCanvasChanged = true;
ui.forceUpdate('surface');
}
onLoadSuccess(ui, event);
}
leafPaint.loadId = null;
}, (error) => {
ignoreRender(ui, false);
onLoadError(ui, event, error);
leafPaint.loadId = null;
});
if (ui.placeholderColor)
setTimeout(() => {
if (!(image.ready || image.isPlacehold)) {
image.isPlacehold = true;
ui.forceUpdate('surface');
}
}, 100);
}
return leafPaint;
}
function checkSizeAndCreateData(ui, attrName, paint, image, leafPaint, boxBounds) {
if (attrName === 'fill' && !ui.__.__naturalWidth) {
const data = ui.__;
data.__naturalWidth = image.width / data.pixelRatio;
data.__naturalHeight = image.height / data.pixelRatio;
if (data.__autoSide) {
ui.forceUpdate('width');
if (ui.__proxyData) {
ui.setProxyAttr('width', data.width);
ui.setProxyAttr('height', data.height);
}
return false;
}
}
if (!leafPaint.data)
createData(leafPaint, image, paint, boxBounds);
return true;
}
function onLoad(ui, event) {
emit(ui, ImageEvent.LOAD, event);
}
function onLoadSuccess(ui, event) {
emit(ui, ImageEvent.LOADED, event);
}
function onLoadError(ui, event, error) {
event.error = error;
ui.forceUpdate('surface');
emit(ui, ImageEvent.ERROR, event);
}
function emit(ui, type, data) {
if (ui.hasEvent(type))
ui.emitEvent(new ImageEvent(type, data));
}
function ignoreRender(ui, value) {
const { leafer } = ui;
if (leafer && leafer.viewReady)
leafer.renderer.ignore = value;
}
const { get: get$1, scale, copy: copy$1 } = MatrixHelper;
const { ceil, abs: abs$1 } = Math;
function createPattern(ui, paint, pixelRatio) {
let { scaleX, scaleY } = ImageManager.patternLocked ? ui.__world : ui.__nowWorld;
const id = scaleX + '-' + scaleY + '-' + pixelRatio;
if (paint.patternId !== id && !ui.destroyed) {
scaleX = abs$1(scaleX);
scaleY = abs$1(scaleY);
const { image, data } = paint;
let imageScale, imageMatrix, { width, height, scaleX: sx, scaleY: sy, transform, repeat } = data;
if (sx) {
imageMatrix = get$1();
copy$1(imageMatrix, transform);
scale(imageMatrix, 1 / sx, 1 / sy);
scaleX *= sx;
scaleY *= sy;
}
scaleX *= pixelRatio;
scaleY *= pixelRatio;
width *= scaleX;
height *= scaleY;
const size = width * height;
if (!repeat) {
if (size > Platform.image.maxCacheSize)
return false;
}
let maxSize = Platform.image.maxPatternSize;
if (!image.isSVG) {
const imageSize = image.width * image.height;
if (maxSize > imageSize)
maxSize = imageSize;
}
if (size > maxSize)
imageScale = Math.sqrt(size / maxSize);
if (imageScale) {
scaleX /= imageScale;
scaleY /= imageScale;
width /= imageScale;
height /= imageScale;
}
if (sx) {
scaleX /= sx;
scaleY /= sy;
}
if (transform || scaleX !== 1 || scaleY !== 1) {
if (!imageMatrix) {
imageMatrix = get$1();
if (transform)
copy$1(imageMatrix, transform);
}
scale(imageMatrix, 1 / scaleX, 1 / scaleY);
}
const canvas = image.getCanvas(ceil(width) || 1, ceil(height) || 1, data.opacity, data.filters);
const pattern = image.getPattern(canvas, repeat || (Platform.origin.noRepeat || 'no-repeat'), imageMatrix, paint);
paint.style = pattern;
paint.patternId = id;
return true;
}
else {
return false;
}
}
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
const { abs } = Math;
function checkImage(ui, canvas, paint, allowDraw) {
const { scaleX, scaleY } = ImageManager.patternLocked ? ui.__world : ui.__nowWorld;
const { pixelRatio } = canvas, { data } = paint;
if (!data || (paint.patternId === scaleX + '-' + scaleY + '-' + pixelRatio && !Export.running)) {
return false;
}
else {
if (allowDraw) {
if (data.repeat) {
allowDraw = false;
}
else {
if (!(paint.changeful || ResizeEvent.isResizing(ui) || Export.running)) {
let { width, height } = data;
width *= abs(scaleX) * pixelRatio;
height *= abs(scaleY) * pixelRatio;
if (data.scaleX) {
width *= data.scaleX;
height *= data.scaleY;
}
allowDraw = (width * height > Platform.image.maxCacheSize);
}
}
}
if (allowDraw) {
drawImage(ui, canvas, paint, data);
return true;
}
else {
if (!paint.style || paint.sync || Export.running) {
createPattern(ui, paint, pixelRatio);
}
else {
if (!paint.patternTask) {
paint.patternTask = ImageManager.patternTasker.add(() => __awaiter(this, void 0, void 0, function* () {
paint.patternTask = null;
if (canvas.bounds.hit(ui.__nowWorld))
createPattern(ui, paint, pixelRatio);
ui.forceUpdate('surface');
}), 300);
}
}
return false;
}
}
}
function drawImage(ui, canvas, paint, data) {
canvas.save();
ui.windingRule ? canvas.clip(ui.windingRule) : canvas.clip();
if (paint.blendMode)
canvas.blendMode = paint.blendMode;
if (data.opacity)
canvas.opacity *= data.opacity;
if (data.transform)
canvas.transform(data.transform);
canvas.drawImage(paint.image.getFull(data.filters), 0, 0, data.width, data.height);
canvas.restore();
}
function recycleImage(attrName, data) {
const paints = data['_' + attrName];
if (paints instanceof Array) {
let paint, image, recycleMap, input, url;
for (let i = 0, len = paints.length; i < len; i++) {
paint = paints[i];
image = paint.image;
url = image && image.url;
if (url) {
if (!recycleMap)
recycleMap = {};
recycleMap[url] = true;
ImageManager.recycle(image);
if (image.loading) {
if (!input) {
input = (data.__input && data.__input[attrName]) || [];
if (!(input instanceof Array))
input = [input];
}
image.unload(paints[i].loadId, !input.some((item) => item.url === url));
}
}
}
return recycleMap;
}
return null;
}
const PaintImageModule = {
image,
checkImage,
createPattern,
recycleImage,
createData,
getPatternData,
fillOrFitMode,
clipMode,
repeatMode
};
const { toPoint: toPoint$2 } = AroundHelper;
const realFrom$2 = {};
const realTo$2 = {};
function linearGradient(paint, box) {
let { from, to, type, blendMode, opacity } = paint;
toPoint$2(from || 'top', box, realFrom$2);
toPoint$2(to || 'bottom', box, realTo$2);
const style = Platform.canvas.createLinearGradient(realFrom$2.x, realFrom$2.y, realTo$2.x, realTo$2.y);
applyStops(style, paint.stops, opacity);
const data = { type, style };
if (blendMode)
data.blendMode = blendMode;
return data;
}
function applyStops(gradient, stops, opacity) {
if (stops) {
let stop;
for (let i = 0, len = stops.length; i < len;