@leafer-draw/miniapp
Version:
1,437 lines (1,417 loc) • 84.8 kB
JavaScript
import { LeaferCanvasBase, Platform, canvasPatch, DataHelper, canvasSizeAttrs, ResizeEvent, FileHelper, Creator, LeaferImage, defineKey, LeafList, RenderEvent, ChildEvent, WatchEvent, PropertyEvent, LeafHelper, BranchHelper, LeafBoundsHelper, Bounds, Debug, LeafLevelList, LayoutEvent, Run, ImageManager, BoundsHelper, getMatrixData, MatrixHelper, MathHelper, AlignHelper, PointHelper, ImageEvent, AroundHelper, Direction4 } from '@leafer/core';
export * from '@leafer/core';
export { LeaferImage } from '@leafer/core';
import { PaintImage, Paint, ColorConvert, PaintGradient, Export, Group, TextConvert, Effect } from '@leafer-ui/draw';
export * from '@leafer-ui/draw';
class LeaferCanvas extends LeaferCanvasBase {
get allowBackgroundColor() { return false; }
init() {
const { config } = this;
let view = config.view || config.canvas;
if (view) {
if (typeof view === 'string') {
if (view[0] !== '#')
view = '#' + view;
this.viewSelect = Platform.miniapp.select(view);
}
else if (view.fields) {
this.viewSelect = view;
}
else {
this.initView(view);
}
if (this.viewSelect)
Platform.miniapp.getSizeView(this.viewSelect).then(sizeView => {
this.initView(sizeView);
});
}
else {
this.initView();
}
}
initView(view) {
if (!view) {
view = {};
this.__createView();
}
else {
this.view = view.view || view;
}
this.view.getContext ? this.__createContext() : this.unrealCanvas();
const { width, height, pixelRatio } = this.config;
const size = { width: width || view.width, height: height || view.height, pixelRatio };
this.resize(size);
if (this.context) {
if (this.viewSelect)
Platform.renderCanvas = this;
if (this.context.roundRect) {
this.roundRect = function (x, y, width, height, radius) {
this.context.roundRect(x, y, width, height, typeof radius === 'number' ? [radius] : radius);
};
}
canvasPatch(this.context.__proto__);
}
}
__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);
}
updateClientBounds(callback) {
if (this.viewSelect)
Platform.miniapp.getBounds(this.viewSelect).then(bounds => {
this.clientBounds = bounds;
if (callback)
callback();
});
}
startAutoLayout(autoBounds, listener) {
this.resizeListener = listener;
if (autoBounds) {
this.checkSize = this.checkSize.bind(this);
Platform.miniapp.onWindowResize(this.checkSize);
}
}
checkSize() {
if (this.viewSelect) {
setTimeout(() => {
this.updateClientBounds(() => {
const { width, height } = this.clientBounds;
const { pixelRatio } = this;
const size = { width, height, pixelRatio };
if (!this.isSameSize(size))
this.emitResize(size);
});
}, 500);
}
}
stopAutoLayout() {
this.autoLayout = false;
this.resizeListener = null;
Platform.miniapp.offWindowResize(this.checkSize);
}
unrealCanvas() {
this.unreal = true;
}
emitResize(size) {
const oldSize = {};
DataHelper.copyAttrs(oldSize, this, canvasSizeAttrs);
this.resize(size);
if (this.width !== undefined)
this.resizeListener(new ResizeEvent(size, oldSize));
}
}
const { mineType, fileType } = FileHelper;
Object.assign(Creator, {
canvas: (options, manager) => new LeaferCanvas(options, manager),
image: (options) => new LeaferImage(options)
});
function useCanvas(_canvasType, app) {
Platform.origin = {
createCanvas: (width, height, _format) => {
const data = { type: '2d', width, height };
return app.createOffscreenCanvas ? app.createOffscreenCanvas(data) : app.createOffScreenCanvas(data);
},
canvasToDataURL: (canvas, type, quality) => canvas.toDataURL(mineType(type), quality),
canvasToBolb: (canvas, type, quality) => canvas.toBuffer(type, { quality }),
canvasSaveAs: (canvas, filePath, quality) => {
let data = canvas.toDataURL(mineType(fileType(filePath)), quality);
data = data.substring(data.indexOf('64,') + 3);
return Platform.origin.download(data, filePath);
},
download(data, filePath) {
return new Promise((resolve, reject) => {
let toAlbum;
if (!filePath.includes('/')) {
filePath = `${app.env.USER_DATA_PATH}/` + filePath;
toAlbum = true;
}
const fs = app.getFileSystemManager();
fs.writeFile({
filePath,
data,
encoding: 'base64',
success() {
if (toAlbum) {
Platform.miniapp.saveToAlbum(filePath).then(() => {
fs.unlink({ filePath });
resolve();
});
}
else {
resolve();
}
},
fail(error) {
reject(error);
}
});
});
},
loadImage(src) {
return new Promise((resolve, reject) => {
const img = Platform.canvas.view.createImage();
img.onload = () => { resolve(img); };
img.onerror = (error) => { reject(error); };
img.src = Platform.image.getRealURL(src);
});
},
noRepeat: 'repeat-x'
};
Platform.miniapp = {
select(name) {
return app.createSelectorQuery().select(name);
},
getBounds(select) {
return new Promise((resolve) => {
select.boundingClientRect().exec((res) => {
const rect = res[1];
resolve({ x: rect.top, y: rect.left, width: rect.width, height: rect.height });
});
});
},
getSizeView(select) {
return new Promise((resolve) => {
select.fields({ node: true, size: true }).exec((res) => {
const data = res[0];
resolve({ view: data.node, width: data.width, height: data.height });
});
});
},
saveToAlbum(path) {
return new Promise((resolve) => {
app.getSetting({
success: (res) => {
if (res.authSetting['scope.writePhotosAlbum']) {
app.saveImageToPhotosAlbum({
filePath: path,
success() { resolve(true); }
});
}
else {
app.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
app.saveImageToPhotosAlbum({
filePath: path,
success() { resolve(true); }
});
},
fail: () => { }
});
}
}
});
});
},
onWindowResize(fun) {
app.onWindowResize(fun);
},
offWindowResize(fun) {
app.offWindowResize(fun);
}
};
Platform.event = {
stopDefault(_origin) { },
stopNow(_origin) { },
stop(_origin) { }
};
Platform.canvas = Creator.canvas();
Platform.conicGradientSupport = !!Platform.canvas.context.createConicGradient;
}
Platform.name = 'miniapp';
Platform.requestRender = function (render) {
const { view } = Platform.renderCanvas || Platform.canvas;
view.requestAnimationFrame ? view.requestAnimationFrame(render) : setTimeout(render, 16);
};
defineKey(Platform, 'devicePixelRatio', { get() { return Math.max(1, wx.getSystemInfoSync().pixelRatio); } });
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 = this.hasVisible = this.hasRemove = this.hasAdd = false;
}
__listenEvents() {
this.__eventIds = [
this.target.on_([
[PropertyEvent.CHANGE, this.__onAttrChange, this],
[[ChildEvent.ADD, ChildEvent.REMOVE], this.__onChildEvent, this],
[WatchEvent.REQUEST, this.__onRquestData, this]
])
];
}
__removeListenEvents() {
this.target.off_(this.__eventIds);
}
destroy() {
if (this.target) {
this.stop();
this.__removeListenEvents();
this.target = 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.layouting || !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() {
this.__eventIds = [
this.target.on_([
[LayoutEvent.REQUEST, this.layout, this],
[LayoutEvent.AGAIN, this.layoutAgain, this],
[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() {
this.__eventIds = [
this.target.on_([
[RenderEvent.REQUEST, this.update, this],
[LayoutEvent.END, this.__onLayoutEnd, this],
[RenderEvent.AGAIN, this.renderAgain, this],
[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 || item.scaleFixed) {
canvas.save();
if (item.transform)
canvas.transform(item.transform);
if (item.scaleFixed) {
const { scaleX, scaleY } = ui.getRenderScaleData(true);
canvas.scale(1 / scaleX, 1 / scaleY);
}
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) {
switch (ui.__.strokeAlign) {
case 'center':
drawCenter$1(stroke, 1, ui, canvas);
break;
case 'inside':
drawAlign(stroke, 'inside', ui, canvas);
break;
case 'outside':
ui.__.__fillAfterStroke ? drawCenter$1(stroke, 2, ui, canvas) : drawAlign(stroke, 'outside', ui, canvas);
break;
}
}
function drawCenter$1(stroke, strokeWidthScale, ui, canvas) {
const data = ui.__;
if (typeof stroke === 'object') {
drawStrokesStyle(stroke, strokeWidthScale, true, ui, canvas);
}
else {
canvas.setStroke(stroke, data.__strokeWidth * strokeWidthScale, data);
drawTextStroke(ui, canvas);
}
}
function drawAlign(stroke, align, ui, canvas) {
const out = canvas.getSameCanvas(true, true);
out.font = ui.__.__font;
drawCenter$1(stroke, 2, ui, out);
out.blendMode = align === 'outside' ? 'destination-out' : 'destination-in';
fillText(ui, out);
out.blendMode = 'normal';
LeafHelper.copyCanvasByWorld(ui, canvas, out);
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, strokeWidthScale, isText, ui, canvas) {
let item;
const data = ui.__, { __hasMultiStrokeStyle } = data;
__hasMultiStrokeStyle || canvas.setStroke(undefined, data.__strokeWidth * strokeWidthScale, data);
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) {
if (__hasMultiStrokeStyle) {
const { strokeStyle } = item;
strokeStyle ? canvas.setStroke(item.style, data.__getRealStrokeWidth(strokeStyle) * strokeWidthScale, data, strokeStyle) : canvas.setStroke(item.style, data.__strokeWidth * strokeWidthScale, data);
}
else
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 data = ui.__;
if (!data.__strokeWidth)
return;
if (data.__font) {
strokeText(stroke, ui, canvas);
}
else {
switch (data.strokeAlign) {
case 'center':
drawCenter(stroke, 1, ui, canvas);
break;
case 'inside':
drawInside(stroke, ui, canvas);
break;
case 'outside':
drawOutside(stroke, ui, canvas);
break;
}
}
}
function strokes(strokes, ui, canvas) {
stroke(strokes, ui, canvas);
}
function drawCenter(stroke, strokeWidthScale, ui, canvas) {
const data = ui.__;
if (typeof stroke === 'object') {
drawStrokesStyle(stroke, strokeWidthScale, false, ui, canvas);
}
else {
canvas.setStroke(stroke, data.__strokeWidth * strokeWidthScale, data);
canvas.stroke();
}
if (data.__useArrow)
Paint.strokeArrow(stroke, ui, canvas);
}
function drawInside(stroke, ui, canvas) {
canvas.save();
canvas.clipUI(ui);
drawCenter(stroke, 2, ui, canvas);
canvas.restore();
}
function drawOutside(stroke, ui, canvas) {
const data = ui.__;
if (data.__fillAfterStroke) {
drawCenter(stroke, 2, ui, canvas);
}
else {
const { renderBounds } = ui.__layout;
const out = canvas.getSameCanvas(true, true);
ui.__drawRenderPath(out);
drawCenter(stroke, 2, ui, out);
out.clipUI(data);
out.clearWorld(renderBounds);
LeafHelper.copyCanvasByWorld(ui, canvas, out);
out.recycle(ui.__nowWorld);
}
}
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;
const { stintSet } = DataHelper, { hasTransparent: hasTransparent$1 } = ColorConvert;
function compute(attrName, ui) {
const data = ui.__, leafPaints = [];
let paints = data.__input[attrName], isAlphaPixel, isTransparent;
if (!(paints instanceof Array))
paints = [paints];
recycleMap = PaintImage.recycleImage(attrName, data);
let maxChildStrokeWidth;
for (let i = 0, len = paints.length, item; i < len; i++) {
if (item = getLeafPaint(attrName, paints[i], ui)) {
leafPaints.push(item);
if (item.strokeStyle) {
maxChildStrokeWidth || (maxChildStrokeWidth = 1);
if (item.strokeStyle.strokeWidth)
maxChildStrokeWidth = Math.max(maxChildStrokeWidth, item.strokeStyle.strokeWidth);
}
}
}
data['_' + attrName] = leafPaints.length ? leafPaints : undefined;
if (leafPaints.length) {
if (leafPaints.every(item => item.isTransparent)) {
if (leafPaints.some(item => item.image))
isAlphaPixel = true;
isTransparent = true;
}
}
if (attrName === 'fill') {
stintSet(data, '__isAlphaPixelFill', isAlphaPixel);
stintSet(data, '__isTransparentFill', isTransparent);
}
else {
stintSet(data, '__isAlphaPixelStroke', isAlphaPixel);
stintSet(data, '__isTransparentStroke', isTransparent);
stintSet(data, '__hasMultiStrokeStyle', maxChildStrokeWidth);
}
}
function getLeafPaint(attrName, paint, ui) {
if (typeof paint !== 'object' || paint.visible === false || paint.opacity === 0)
return undefined;
let data;
const { boxBounds } = ui.__layout;
switch (paint.type) {
case 'image':
data = PaintImage.image(ui, attrName, paint, boxBounds, !recycleMap || !recycleMap[paint.url]);
break;
case 'linear':
data = PaintGradient.linearGradient(paint, boxBounds);
break;
case 'radial':
data = PaintGradient.radialGradient(paint, boxBounds);
break;
case 'angular':
data = PaintGradient.conicGradient(paint, boxBounds);
break;
case 'solid':
const { type, color, opacity } = paint;
data = { type, style: ColorConvert.string(color, opacity) };
break;
default:
if (paint.r !== undefined)
data = { type: 'solid', style: ColorConvert.string(paint) };
}
if (data) {
if (typeof data.style === 'string' && hasTransparent$1(data.style))
data.isTransparent = true;
if (paint.style) {
if (paint.style.strokeWidth === 0)
return undefined;
data.strokeStyle = paint.style;
}
if (paint.blendMode)
data.blendMode = paint.blendMode;
}
return data;
}
const PaintModule = {
compute,
fill,
fills,
fillPathOrText,
fillText,
stroke,
strokes,
strokeText,
drawTextStroke,
shape
};
let origin = {}, tempMatrix = getMatrixData();
const { get: get$3, rotateOfOuter: rotateOfOuter$1, translate: translate$1, scaleOfOuter: scaleOfOuter$1, multiplyParent, scale: scaleHelper, rotate, skew: skewHelper } = 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, skew, clipSize) {
const transform = get$3();
if (rotation)
rotate(transform, rotation);
if (skew)
skewHelper(transform, skew.x, skew.y);
if (scaleX)
scaleHelper(transform, scaleX, scaleY);
translate$1(transform, box.x + x, box.y + y);
if (clipSize) {
tempMatrix.a = box.width / clipSize.width, tempMatrix.d = box.height / clipSize.height;
multiplyParent(transform, tempMatrix);
}
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 tempScaleData = {};
const tempImage = {};
function createData(leafPaint, image, paint, box) {
const { changeful, sync, editing, scaleFixed } = paint;
if (changeful)
leafPaint.changeful = changeful;
if (sync)
leafPaint.sync = sync;
if (editing)
leafPaint.editing = editing;
if (scaleFixed)
leafPaint.scaleFixed = scaleFixed;
leafPaint.data = getPatternData(paint, box, image);
}
function getPatternData(paint, box, image) {
if (paint.padding)
box = tempBox.set(box).shrink(paint.padding);
if (paint.mode === 'strench')
paint.mode = 'stretch';
let { width, height } = image;
const { opacity, mode, align, offset, scale, size, rotation, skew, clipSize, repeat, filters } = paint;
const sameBox = box.width === width && box.height === height;
const data = { mode };
const swapSize = align !== 'center' && (rotation || 0) % 180 === 90;
BoundsHelper.set(tempImage, 0, 0, swapSize ? height : width, swapSize ? width : height);
let scaleX, scaleY;
if (!mode || mode === 'cover' || mode === 'fit') {
if (!sameBox || rotation) {
scaleX = scaleY = BoundsHelper.getFitScale(box, tempImage, mode !== 'fit');
BoundsHelper.put(box, image, align, scaleX, false, tempImage);
BoundsHelper.scale(tempImage, scaleX, scaleY, true);
}
}
else {
if (scale || size) {
MathHelper.getScaleData(scale, size, image, tempScaleData);
scaleX = tempScaleData.scaleX;
scaleY = tempScaleData.scaleY;
}
if (align) {
if (scaleX)
BoundsHelper.scale(tempImage, scaleX, scaleY, true);
AlignHelper.toPoint(align, tempImage, box, tempImage, true, true);
}
}
if (offset)
PointHelper.move(tempImage, offset);
switch (mode) {
case 'stretch':
if (!sameBox)
width = box.width, height = box.height;
break;
case 'normal':
case 'clip':
if (tempImage.x || tempImage.y || scaleX || clipSize || rotation || skew)
clipMode(data, box, tempImage.x, tempImage.y, scaleX, scaleY, rotation, skew, paint.clipSize);
break;
case 'repeat':
if (!sameBox || scaleX || rotation)
repeatMode(data, box, width, height, tempImage.x, tempImage.y, scaleX, scaleY, rotation, align);
if (!repeat)
data.repeat = 'repeat';
break;
case 'fit':
case 'cover':
default:
if (scaleX)
fillOrFitMode(data, box, tempImage.x, tempImage.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 };
if (image.hasAlphaPixel)
leafPaint.isTransparent = true;
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.hasAlphaPixel)
ui.__layout.hitCanvasChanged = true;
ui.forceUpdate('surface');
}
onLoadSuccess(ui, event);
}
leafPaint.loadId = undefined;
}, (error) => {
ignoreRender(ui, false);
onLoadError(ui, event, error);
leafPaint.loadId = undefined;
});
if (ui.placeholderColor) {
if (!ui.placeholderDelay)
image.isPlacehold = true;
else
setTimeout(() => {
if (!image.ready) {
image.isPlacehold = true;
ui.forceUpdate('surface');
}
}, ui.placeholderDelay);
}
}
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 } = Math;
function createPattern(ui, paint, pixelRatio) {
let { scaleX, scaleY } = ui.getRenderScaleData(true, paint.scaleFixed);
const id = scaleX + '-' + scaleY + '-' + pixelRatio;
if (paint.patternId !== id && !ui.destroyed) {
const { image, data } = paint;
let imageScale, imageMatrix, { width, height, scaleX: sx, scaleY: sy, transform, repeat } = data;
if (sx) {
sx = abs(sx);
sy = abs(sy);
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;