@leafer-draw/node
Version:
1,397 lines (1,368 loc) • 88.1 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 { writeFileSync } from 'fs';
import { PaintImage, ColorConvert, PaintGradient, Export, Group, TextConvert, Paint, Effect, TwoPointBoundsHelper, Bounds as Bounds$1, FileHelper as FileHelper$1, Platform as Platform$1, Matrix, MathHelper as MathHelper$1, Creator as Creator$1, TaskProcessor, Resource, LeaferCanvasBase as LeaferCanvasBase$1, Debug as Debug$1, Plugin, UI } from '@leafer-ui/draw';
export * from '@leafer-ui/draw';
/******************************************************************************
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;
};
class LeaferCanvas extends LeaferCanvasBase {
get allowBackgroundColor() { return true; }
init() {
this.__createView();
this.__createContext();
this.resize(this.config);
if (Platform.roundRectPatch) {
this.context.__proto__.roundRect = null;
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);
this.clientBounds = this.bounds;
}
}
const { mineType, fileType } = FileHelper;
Object.assign(Creator, {
canvas: (options, manager) => new LeaferCanvas(options, manager),
image: (options) => new LeaferImage(options)
});
function useCanvas(canvasType, power) {
Platform.canvasType = canvasType;
if (!Platform.origin) {
if (canvasType === 'skia') {
const { Canvas, loadImage } = power;
Platform.origin = {
createCanvas: (width, height, format) => new Canvas(width, height, format),
canvasToDataURL: (canvas, type, quality) => canvas.toDataURLSync(type, { quality }),
canvasToBolb: (canvas, type, quality) => canvas.toBuffer(type, { quality }),
canvasSaveAs: (canvas, filename, quality) => canvas.saveAs(filename, { quality }),
download(_url, _filename) { return undefined; },
loadImage(src) { return loadImage(Platform.image.getRealURL(src)); }
};
Platform.roundRectPatch = true;
}
else if (canvasType === 'napi') {
const { Canvas, loadImage } = power;
Platform.origin = {
createCanvas: (width, height, format) => new Canvas(width, height, format),
canvasToDataURL: (canvas, type, quality) => canvas.toDataURL(mineType(type), quality),
canvasToBolb: (canvas, type, quality) => __awaiter(this, void 0, void 0, function* () { return canvas.toBuffer(mineType(type), quality); }),
canvasSaveAs: (canvas, filename, quality) => __awaiter(this, void 0, void 0, function* () { return writeFileSync(filename, canvas.toBuffer(mineType(fileType(filename)), quality)); }),
download(_url, _filename) { return undefined; },
loadImage(src) { return loadImage(Platform.image.getRealURL(src)); }
};
}
Platform.ellipseToCurve = true;
Platform.event = {
stopDefault(_origin) { },
stopNow(_origin) { },
stop(_origin) { }
};
Platform.canvas = Creator.canvas();
}
}
Platform.name = 'node';
Platform.backgrounder = true;
Platform.requestRender = function (render) { setTimeout(render, 16); };
defineKey(Platform, 'devicePixelRatio', { get() { return 1; } });
Platform.conicGradientSupport = true;
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$2 = 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$2.error(e);
}
this.layoutedBlocks = null;
}
layoutAgain() {
if (this.layouting) {
this.waitAgain = true;
}
else {
this.layoutOnce();
}
}
layoutOnce() {
if (this.layouting)
return debug$2.warn('layouting');
if (this.times > 3)
return debug$2.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$1 = 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$1.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$1.error(e);
}
debug$1.log('-------------|');
}
renderAgain() {
if (this.rendering) {
this.waitAgain = true;
}
else {
this.renderOnce();
}
}
renderOnce(callback) {
if (this.rendering)
return debug$1.warn('rendering');
if (this.times > 3)
return debug$1.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$1.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;
}
}
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, opa