@leafer-draw/worker
Version:
1,412 lines (1,381 loc) • 80 kB
JavaScript
'use strict';
var core = require('@leafer/core');
var draw = require('@leafer-ui/draw');
class LeaferCanvas extends core.LeaferCanvasBase {
get allowBackgroundColor() { return true; }
init() {
this.__createView();
this.__createContext();
this.resize(this.config);
}
__createView() {
this.view = core.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;
}
}
core.canvasPatch(OffscreenCanvasRenderingContext2D.prototype);
core.canvasPatch(Path2D.prototype);
const { mineType } = core.FileHelper;
Object.assign(core.Creator, {
canvas: (options, manager) => new LeaferCanvas(options, manager),
image: (options) => new core.LeaferImage(options)
});
function useCanvas(_canvasType, _power) {
core.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', core.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();
});
}
};
core.Platform.canvas = core.Creator.canvas();
core.Platform.conicGradientSupport = !!core.Platform.canvas.context.createConicGradient;
}
core.Platform.name = 'web';
core.Platform.isWorker = true;
core.Platform.backgrounder = true;
core.Platform.requestRender = function (render) { requestAnimationFrame(render); };
core.defineKey(core.Platform, 'devicePixelRatio', { get() { return 1; } });
const { userAgent } = navigator;
if (userAgent.indexOf("Firefox") > -1) {
core.Platform.conicGradientRotate90 = true;
core.Platform.intWheelDeltaY = true;
}
else if (userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") === -1) {
core.Platform.fullImageShadow = true;
}
if (userAgent.indexOf('Windows') > -1) {
core.Platform.os = 'Windows';
core.Platform.intWheelDeltaY = true;
}
else if (userAgent.indexOf('Mac') > -1) {
core.Platform.os = 'Mac';
}
else if (userAgent.indexOf('Linux') > -1) {
core.Platform.os = 'Linux';
}
class Watcher {
get childrenChanged() { return this.hasAdd || this.hasRemove || this.hasVisible; }
get updatedList() {
if (this.hasRemove) {
const updatedList = new core.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 core.LeafList();
this.target = target;
if (userConfig)
this.config = core.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(core.RenderEvent.REQUEST);
}
__onAttrChange(event) {
this.__updatedList.add(event.target);
this.update();
}
__onChildEvent(event) {
if (event.type === core.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 core.WatchEvent(core.WatchEvent.DATA, { updatedList: this.updatedList }));
this.__updatedList = new core.LeafList();
this.totalTimes++;
this.changed = false;
this.hasVisible = false;
this.hasRemove = false;
this.hasAdd = false;
}
__listenEvents() {
const { target } = this;
this.__eventIds = [
target.on_(core.PropertyEvent.CHANGE, this.__onAttrChange, this),
target.on_([core.ChildEvent.ADD, core.ChildEvent.REMOVE], this.__onChildEvent, this),
target.on_(core.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 } = core.LeafHelper;
const { pushAllChildBranch, pushAllParent } = core.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 } = core.LeafBoundsHelper;
class LayoutBlockData {
constructor(list) {
this.updatedBounds = new core.Bounds();
this.beforeBounds = new core.Bounds();
this.afterBounds = new core.Bounds();
if (list instanceof Array)
list = new core.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 } = core.LeafHelper;
const debug$1 = core.Debug.get('Layouter');
class Layouter {
constructor(target, userConfig) {
this.totalTimes = 0;
this.config = {};
this.__levelList = new core.LeafLevelList();
this.target = target;
if (userConfig)
this.config = core.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(core.LayoutEvent.START);
this.layoutOnce();
target.emitEvent(new core.LayoutEvent(core.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(core.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 = core.Run.start('PartLayout');
const { target, __updatedList: updateList } = this;
const { BEFORE, LAYOUT, AFTER } = core.LayoutEvent;
const blocks = this.getBlocks(updateList);
blocks.forEach(item => item.setBefore());
target.emitEvent(new core.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 core.LayoutEvent(LAYOUT, blocks, this.times));
target.emitEvent(new core.LayoutEvent(AFTER, blocks, this.times));
this.addBlocks(blocks);
this.__levelList.reset();
this.__updatedList = null;
core.Run.end(t);
}
fullLayout() {
const t = core.Run.start('FullLayout');
const { target } = this;
const { BEFORE, LAYOUT, AFTER } = core.LayoutEvent;
const blocks = this.getBlocks(new core.LeafList(target));
target.emitEvent(new core.LayoutEvent(BEFORE, blocks, this.times));
Layouter.fullLayout(target);
blocks.forEach(item => { item.setAfter(); });
target.emitEvent(new core.LayoutEvent(LAYOUT, blocks, this.times));
target.emitEvent(new core.LayoutEvent(AFTER, blocks, this.times));
this.addBlocks(blocks);
core.Run.end(t);
}
static fullLayout(target) {
updateAllMatrix(target, true);
if (target.isBranch) {
core.BranchHelper.updateBounds(target);
}
else {
core.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_(core.LayoutEvent.REQUEST, this.layout, this),
target.on_(core.LayoutEvent.AGAIN, this.layoutAgain, this),
target.on_(core.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 = core.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 = core.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(core.LayoutEvent.REQUEST);
}
checkRender() {
if (this.running) {
const { target } = this;
if (target.isApp) {
target.emit(core.RenderEvent.CHILD_START, target);
target.children.forEach(leafer => {
leafer.renderer.FPS = this.FPS;
leafer.renderer.checkRender();
});
target.emit(core.RenderEvent.CHILD_END, target);
}
if (this.changed && this.canvas.view)
this.render();
this.target.emit(core.RenderEvent.NEXT);
}
}
render(callback) {
if (!(this.running && this.canvas.view))
return this.update();
const { target } = this;
this.times = 0;
this.totalBounds = new core.Bounds();
debug.log(target.innerName, '--->');
try {
this.emitRender(core.RenderEvent.START);
this.renderOnce(callback);
this.emitRender(core.RenderEvent.END, this.totalBounds);
core.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 core.Bounds();
this.renderOptions = {};
if (callback) {
this.emitRender(core.RenderEvent.BEFORE);
callback();
}
else {
this.requestLayout();
if (this.ignore) {
this.ignore = this.rendering = false;
return;
}
this.emitRender(core.RenderEvent.BEFORE);
if (this.config.usePartRender && this.totalTimes > 1) {
this.partRender();
}
else {
this.fullRender();
}
}
this.emitRender(core.RenderEvent.RENDER, this.renderBounds, this.renderOptions);
this.emitRender(core.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 = core.Run.start('PartRender');
const { canvas } = this, bounds = block.getIntersect(canvas.bounds), realBounds = new core.Bounds(bounds);
canvas.save();
bounds.spread(Renderer.clipSpread).ceil();
canvas.clearWorld(bounds, true);
canvas.clipWorld(bounds, true);
this.__render(bounds, realBounds);
canvas.restore();
core.Run.end(t);
}
fullRender() {
const t = core.Run.start('FullRender');
const { canvas } = this;
canvas.save();
canvas.clear();
this.__render(canvas.bounds);
canvas.restore();
core.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 (core.Debug.showRepaint)
core.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 core.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();
core.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 core.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 core.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 core.RenderEvent(type, this.times, bounds, options));
}
__listenEvents() {
const { target } = this;
this.__eventIds = [
target.on_(core.RenderEvent.REQUEST, this.update, this),
target.on_(core.LayoutEvent.END, this.__onLayoutEnd, this),
target.on_(core.RenderEvent.AGAIN, this.renderAgain, this),
target.on_(core.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(core.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
});
core.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 (draw.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 && draw.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 } = core.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 = draw.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: draw.ColorConvert.string(color, opacity) };
case 'image':
return draw.PaintImage.image(ui, attrName, paint, boxBounds, !recycleMap || !recycleMap[paint.url]);
case 'linear':
return draw.PaintGradient.linearGradient(paint, boxBounds);
case 'radial':
return draw.PaintGradient.radialGradient(paint, boxBounds);
case 'angular':
return draw.PaintGradient.conicGradient(paint, boxBounds);
default:
return paint.r !== undefined ? { type: 'solid', style: draw.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 } = core.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 } = core.MatrixHelper;
const tempBox = new core.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) {
core.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;
core.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 core.Bounds();
const { isSame } = core.BoundsHelper;
function image(ui, attrName, paint, boxBounds, firstUse) {
let leafPaint, event;
const image = core.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, core.ImageEvent.LOAD, event);
}
function onLoadSuccess(ui, event) {
emit(ui, core.ImageEvent.LOADED, event);
}
function onLoadError(ui, event, error) {
event.error = error;
ui.forceUpdate('surface');
emit(ui, core.ImageEvent.ERROR, event);
}
function emit(ui, type, data) {
if (ui.hasEvent(type))
ui.emitEvent(new core.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 } = core.MatrixHelper;
const { ceil, abs: abs$1 } = Math;
function createPattern(ui, paint, pixelRatio) {
let { scaleX, scaleY } = core.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 > core.Platform.image.maxCacheSize)
return false;
}
let maxSize = core.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 || (core.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 } = core.ImageManager.patternLocked ? ui.__world : ui.__nowWorld;
const { pixelRatio } = canvas, { data } = paint;
if (!data || (paint.patternId === scaleX + '-' + scaleY + '-' + pixelRatio && !draw.Export.running)) {
return false;
}
else {
if (allowDraw) {
if (data.repeat) {
allowDraw = false;
}
else {
if (!(paint.changeful || core.ResizeEvent.isResizing(ui) || draw.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 > core.Platform.image.maxCacheSize);
}
}
}
if (allowDraw) {
drawImage(ui, canvas, paint, data);
return true;
}
else {
if (!paint.style || paint.sync || draw.Export.running) {
createPattern(ui, paint, pixelRatio);
}
else {
if (!paint.patternTask) {
paint.patternTask = core.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;
core.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 } = core.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 = core.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;