UNPKG

@leafer-draw/worker

Version:
1,367 lines (1,272 loc) 91.8 kB
"use strict"; var core = require("@leafer/core"); var draw = require("@leafer-ui/draw"); exports.PathNodeHandleType = void 0; (function(PathNodeHandleType) { PathNodeHandleType[PathNodeHandleType["none"] = 1] = "none"; PathNodeHandleType[PathNodeHandleType["free"] = 2] = "free"; PathNodeHandleType[PathNodeHandleType["mirrorAngle"] = 3] = "mirrorAngle"; PathNodeHandleType[PathNodeHandleType["mirror"] = 4] = "mirror"; })(exports.PathNodeHandleType || (exports.PathNodeHandleType = {})); 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 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: width, height: height, pixelRatio: 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); 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) => new Promise((resolve, reject) => { canvas.convertToBlob({ type: core.FileHelper.mimeType(type), quality: 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: core.FileHelper.mimeType(type), quality: quality }), canvasSaveAs: (_canvas, _filename, _quality) => new Promise(resolve => resolve()), download(_url, _filename) { return undefined; }, loadImage(src, _crossOrigin, _leaferImage) { 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(); }); }, loadContent(url_1) { return __awaiter(this, arguments, void 0, function*(url, responseType = "text") { const response = yield fetch(url); if (!response.ok) throw new Error(`${response.status}`); return yield response[responseType](); }); } }; 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: userAgent} = navigator; if (userAgent.indexOf("Firefox") > -1) { core.Platform.conicGradientRotate90 = true; core.Platform.intWheelDeltaY = true; } else if (/iPhone|iPad|iPod/.test(navigator.userAgent) || /Macintosh/.test(navigator.userAgent) && /Version\/[\d.]+.*Safari/.test(navigator.userAgent)) { 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 && this.config.usePartLayout) { 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) { if (this.config.usePartLayout) this.__updatedList.add(event.target); this.update(); } __onChildEvent(event) { if (this.config.usePartLayout) { 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: 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 = this.hasVisible = this.hasRemove = this.hasAdd = false; } __listenEvents() { this.__eventIds = [ this.target.on_([ [ core.PropertyEvent.CHANGE, this.__onAttrChange, this ], [ [ core.ChildEvent.ADD, core.ChildEvent.REMOVE ], this.__onChildEvent, this ], [ core.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} = core.LeafHelper; const {pushAllChildBranch: pushAllChildBranch, pushAllParent: 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: worldBounds} = core.LeafBoundsHelper; class LayoutBlockData { constructor(list) { this.updatedBounds = new core.Bounds; this.beforeBounds = new core.Bounds; this.afterBounds = new core.Bounds; if (core.isArray(list)) 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: updateAllMatrix, updateAllChange: updateAllChange} = core.LeafHelper; const debug$1 = core.Debug.get("Layouter"); class Layouter { constructor(target, userConfig) { this.totalTimes = 0; this.config = { usePartLayout: true }; 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.layouting || !this.running) return; const {target: 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.config.usePartLayout) { 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: target, __updatedList: updateList} = this; const {BEFORE: BEFORE, LAYOUT: LAYOUT, AFTER: 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: target} = this; const {BEFORE: BEFORE, LAYOUT: LAYOUT, AFTER: 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: updatedList, beforeBounds: 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_([ [ core.LayoutEvent.REQUEST, this.layout, this ], [ core.LayoutEvent.AGAIN, this.layoutAgain, this ], [ 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, ceilPartPixel: true, maxFPS: 120 }; this.frames = []; 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; if (!this.requestTime) this.__requestRender(); } requestLayout() { this.target.emit(core.LayoutEvent.REQUEST); } checkRender() { if (this.running) { const {target: 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: target} = this; this.times = 0; this.totalBounds = new core.Bounds; debug.log(target.innerName, "---\x3e"); 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: 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: canvas} = this, bounds = block.getIntersect(canvas.bounds), realBounds = new core.Bounds(bounds); canvas.save(); bounds.spread(Renderer.clipSpread).ceil(); const {ceilPartPixel: ceilPartPixel} = this.config; canvas.clipWorld(bounds, ceilPartPixel); canvas.clearWorld(bounds, ceilPartPixel); this.__render(bounds, realBounds); canvas.restore(); core.Run.end(t); } fullRender() { const t = core.Run.start("FullRender"); const {canvas: canvas} = this; canvas.save(); canvas.clear(); this.__render(canvas.bounds); canvas.restore(); core.Run.end(t); } __render(bounds, realBounds) { const {canvas: canvas, target: target} = this, includes = bounds.includes(target.__world), options = includes ? { includes: includes } : { bounds: bounds, includes: includes }; if (this.needFill) canvas.fillWorld(bounds, this.config.fill); if (core.Debug.showRepaint) core.Debug.drawRepaint(canvas, bounds); if (this.config.useCellRender) options.cellList = this.getCellList(); core.Platform.render(target, canvas, options); this.renderBounds = realBounds = realBounds || bounds; this.renderOptions = options; this.totalBounds.isEmpty() ? this.totalBounds = realBounds : this.totalBounds.add(realBounds); canvas.updateRender(realBounds); } getCellList() { return undefined; } addBlock(block, _leafList) { 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); this.requestTime = this.frameTime || Date.now(); const render = () => { const nowFPS = 1e3 / ((this.frameTime = Date.now()) - this.requestTime); const {maxFPS: maxFPS} = this.config; if (maxFPS && nowFPS > maxFPS) return core.Platform.requestRender(render); const {frames: frames} = this; if (frames.length > 30) frames.shift(); frames.push(nowFPS); this.FPS = Math.round(frames.reduce((a, b) => a + b, 0) / frames.length); this.requestTime = 0; this.checkRender(); }; core.Platform.requestRender(render); } __onResize(e) { if (this.canvas.unreal) return; if (e.bigger || !e.samePixelRatio) { const {width: width, height: 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; const {updatedList: updatedList} = item; if (updatedList) 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, updatedList); }); } emitRender(type, bounds, options) { this.target.emitEvent(new core.RenderEvent(type, this.times, bounds, options)); } __listenEvents() { this.__eventIds = [ this.target.on_([ [ core.RenderEvent.REQUEST, this.update, this ], [ core.LayoutEvent.END, this.__onLayoutEnd, this ], [ core.RenderEvent.AGAIN, this.renderAgain, this ], [ core.ResizeEvent.RESIZE, this.__onResize, this ] ]) ]; } __removeListenEvents() { this.target.off_(this.__eventIds); } destroy() { if (this.target) { this.stop(); this.__removeListenEvents(); this.config = {}; this.target = this.canvas = 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; core.Platform.render = function(target, canvas, options) { const topOptions = Object.assign(Object.assign({}, options), { topRendering: true }); options.topList = new core.LeafList; target.__render(canvas, options); if (options.topList.length) options.topList.forEach(item => item.__render(canvas, topOptions)); }; function fill(fill, ui, canvas, renderOptions) { canvas.fillStyle = fill; fillPathOrText(ui, canvas, renderOptions); } function fills(fills, ui, canvas, renderOptions) { let item, originPaint, countImage; for (let i = 0, len = fills.length; i < len; i++) { item = fills[i], originPaint = item.originPaint; if (item.image) { countImage ? countImage++ : countImage = 1; if (draw.PaintImage.checkImage(item, !ui.__.__font, ui, canvas, renderOptions)) continue; if (!item.style) { if (countImage === 1 && item.image.isPlacehold) ui.drawImagePlaceholder(item, canvas, renderOptions); continue; } } canvas.fillStyle = item.style; if (item.transform || originPaint.scaleFixed) { canvas.save(); if (item.transform) canvas.transform(item.transform); if (originPaint.scaleFixed) { const {scaleX: scaleX, scaleY: scaleY} = ui.getRenderScaleData(true, originPaint.scaleFixed, false); if (scaleX !== 1) canvas.scale(scaleX, scaleY); } if (originPaint.blendMode) canvas.blendMode = originPaint.blendMode; fillPathOrText(ui, canvas, renderOptions); canvas.restore(); } else { if (originPaint.blendMode) { canvas.saveBlendMode(originPaint.blendMode); fillPathOrText(ui, canvas, renderOptions); canvas.restoreBlendMode(); } else fillPathOrText(ui, canvas, renderOptions); } } } function fillPathOrText(ui, canvas, renderOptions) { ui.__.__font ? draw.Paint.fillText(ui, canvas, renderOptions) : ui.__.windingRule ? canvas.fill(ui.__.windingRule) : canvas.fill(); } function fillText(ui, canvas, _renderOptions) { const data = ui.__, {rows: rows, decorationY: 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: decorationColor, decorationHeight: 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 stroke(stroke, ui, canvas, renderOptions) { const data = ui.__; if (!data.__strokeWidth) return; if (data.__font) { draw.Paint.strokeText(stroke, ui, canvas, renderOptions); } else if (data.__pathForStroke) { draw.Paint.fillStroke(stroke, ui, canvas, renderOptions); } else { switch (data.strokeAlign) { case "center": drawCenter$1(stroke, 1, ui, canvas, renderOptions); break; case "inside": drawInside(stroke, ui, canvas, renderOptions); break; case "outside": drawOutside(stroke, ui, canvas, renderOptions); break; } } } function strokes(strokes, ui, canvas, renderOptions) { draw.Paint.stroke(strokes, ui, canvas, renderOptions); } function drawCenter$1(stroke, strokeWidthScale, ui, canvas, renderOptions) { const data = ui.__; if (core.isObject(stroke)) { draw.Paint.drawStrokesStyle(stroke, strokeWidthScale, false, ui, canvas, renderOptions); } else { canvas.setStroke(stroke, data.__strokeWidth * strokeWidthScale, data); canvas.stroke(); } if (data.__useArrow) draw.Paint.strokeArrow(stroke, ui, canvas, renderOptions); } function drawInside(stroke, ui, canvas, renderOptions) { canvas.save(); canvas.clipUI(ui); drawCenter$1(stroke, 2, ui, canvas, renderOptions); canvas.restore(); } function drawOutside(stroke, ui, canvas, renderOptions) { const data = ui.__; if (data.__fillAfterStroke) { drawCenter$1(stroke, 2, ui, canvas, renderOptions); } else { const {renderBounds: renderBounds} = ui.__layout; const out = canvas.getSameCanvas(true, true); ui.__drawRenderPath(out); drawCenter$1(stroke, 2, ui, out, renderOptions); out.clipUI(data); out.clearWorld(renderBounds); core.LeafHelper.copyCanvasByWorld(ui, canvas, out); out.recycle(ui.__nowWorld); } } function strokeText(stroke, ui, canvas, renderOptions) { switch (ui.__.strokeAlign) { case "center": drawCenter(stroke, 1, ui, canvas, renderOptions); break; case "inside": drawAlign(stroke, "inside", ui, canvas, renderOptions); break; case "outside": ui.__.__fillAfterStroke ? drawCenter(stroke, 2, ui, canvas, renderOptions) : drawAlign(stroke, "outside", ui, canvas, renderOptions); break; } } function drawCenter(stroke, strokeWidthScale, ui, canvas, renderOptions) { const data = ui.__; if (core.isObject(stroke)) { draw.Paint.drawStrokesStyle(stroke, strokeWidthScale, true, ui, canvas, renderOptions); } else { canvas.setStroke(stroke, data.__strokeWidth * strokeWidthScale, data); draw.Paint.drawTextStroke(ui, canvas, renderOptions); } } function drawAlign(stroke, align, ui, canvas, renderOptions) { const out = canvas.getSameCanvas(true, true); out.font = ui.__.__font; drawCenter(stroke, 2, ui, out, renderOptions); out.blendMode = align === "outside" ? "destination-out" : "destination-in"; draw.Paint.fillText(ui, out, renderOptions); out.blendMode = "normal"; core.LeafHelper.copyCanvasByWorld(ui, canvas, out); out.recycle(ui.__nowWorld); } function drawTextStroke(ui, canvas, _renderOptions) { let row, data = ui.__.__textDrawData; const {rows: rows, decorationY: 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: 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, renderOptions) { let item; const data = ui.__, {__hasMultiStrokeStyle: __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 && draw.PaintImage.checkImage(item, false, ui, canvas, renderOptions)) continue; if (item.style) { if (__hasMultiStrokeStyle) { const {strokeStyle: 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.originPaint.blendMode) { canvas.saveBlendMode(item.originPaint.blendMode); isText ? draw.Paint.drawTextStroke(ui, canvas, renderOptions) : canvas.stroke(); canvas.restoreBlendMode(); } else { isText ? draw.Paint.drawTextStroke(ui, canvas, renderOptions) : canvas.stroke(); } } } } const {getSpread: getSpread, copyAndSpread: copyAndSpread, toOuterOf: toOuterOf, getOuterOf: getOuterOf, getByMove: getByMove, move: move$1, getIntersectData: getIntersectData} = core.BoundsHelper; const tempBounds$1 = {}; function shape(ui, current, options) { const canvas = current.getSameCanvas(); const currentBounds = current.bounds, nowWorld = ui.__nowWorld, layout = ui.__layout; const nowWorldShapeBounds = ui.__nowWorldShapeBounds || (ui.__nowWorldShapeBounds = {}); toOuterOf(layout.strokeSpread ? (copyAndSpread(tempBounds$1, layout.boxBounds, layout.strokeSpread), tempBounds$1) : layout.boxBounds, nowWorld, nowWorldShapeBounds); let bounds, renderBounds, matrix, fitMatrix, shapeBounds, worldCanvas; let {scaleX: scaleX, scaleY: scaleY} = ui.getRenderScaleData(true); if (currentBounds.includes(nowWorldShapeBounds)) { worldCanvas = canvas; bounds = shapeBounds = nowWorldShapeBounds; renderBounds = nowWorld; } else { let worldClipBounds; if (core.Platform.fullImageShadow) { worldClipBounds = nowWorldShapeBounds; } else { const spreadBounds = layout.renderShapeSpread ? getSpread(currentBounds, core.FourNumberHelper.swapAndScale(layout.renderShapeSpread, scaleX, scaleY)) : currentBounds; worldClipBounds = getIntersectData(spreadBounds, nowWorldShapeBounds); } fitMatrix = currentBounds.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(nowWorldShapeBounds, fitMatrix); bounds = getByMove(shapeBounds, -fitMatrix.e, -fitMatrix.f); renderBounds = getOuterOf(nowWorld, fitMatrix); move$1(renderBounds, -fitMatrix.e, -fitMatrix.f); const userMatrix = options.matrix; if (userMatrix) { matrix = new core.Matrix(fitMatrix); matrix.multiply(userMatrix); fitScaleX *= userMatrix.scaleX; fitScaleY *= userMatrix.scaleY; } else matrix = fitMatrix; matrix.withScale(fitScaleX, fitScaleY); options = Object.assign(Object.assign({}, options), { matrix: matrix }); } ui.__renderShape(canvas, options); return { canvas: canvas, matrix: matrix, fitMatrix: fitMatrix, bounds: bounds, renderBounds: renderBounds, worldCanvas: worldCanvas, shapeBounds: shapeBounds, scaleX: scaleX, scaleY: scaleY }; } let recycleMap; const {stintSet: stintSet} = core.DataHelper, {hasTransparent: hasTransparent$1} = draw.ColorConvert; function compute(attrName, ui) { const data = ui.__, leafPaints = []; let paints = data.__input[attrName], isAlphaPixel, isTransparent; if (!core.isArray(paints)) paints = [ paints ]; recycleMap = draw.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); } } } if (leafPaints.length) { data["_" + attrName] = leafPaints; 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); } } else { data.__removePaint(attrName, false); data["_" + attrName] = ""; } } function getLeafPaint(attrName, paint, ui) { if (!core.isObject(paint) || paint.visible === false || paint.opacity === 0) return undefined; let leafPaint; const {boxBounds: boxBounds} = ui.__layout, {type: type} = paint; switch (type) { case "image": case "film": case "video": if (!paint.url) return undefined; leafPaint = draw.PaintImage.image(ui, attrName, paint, boxBounds, !recycleMap || !recycleMap[paint.url]); if (type !== "image") draw.PaintImage[type](leafPaint); break; case "linear": leafPaint = draw.PaintGradient.linearGradient(paint, boxBounds); break; case "radial": leafPaint = draw.PaintGradient.radialGradient(paint, boxBounds); break; case "angular": leafPaint = draw.PaintGradient.conicGradient(paint, boxBounds); break; case "solid": const {color: color, opacity: opacity} = paint; leafPaint = { type: type, style: draw.ColorConvert.string(color, opacity) }; break; default: if (!core.isUndefined(paint.r)) leafPaint = { type: "solid", style: draw.ColorConvert.string(paint) }; } if (leafPaint) { leafPaint.originPaint = paint; if (core.isString(leafPaint.style) && hasTransparent$1(leafPaint.style)) leafPaint.isTransparent = true; if (paint.style) { if (paint.style.strokeWidth === 0) return undefined; leafPaint.strokeStyle = paint.style; } } return leafPaint; } const PaintModule = { compute: compute, fill: fill, fills: fills, fillPathOrText: fillPathOrText, fillText: fillText, stroke: stroke, strokes: strokes, strokeText: strokeText, drawTextStroke: drawTextStroke, drawStrokesStyle: drawStrokesStyle, shape: shape }; let cache, box = new core.Bounds; const {isSame: isSame} = core.BoundsHelper; function image(ui, attrName, paint, boxBounds, firstUse) { let leafPaint, event; const image = core.ImageManager.get(paint, paint.type); if (cache && paint === cache.paint && isSame(boxBounds, cache.boxBounds)) { leafPaint = cache.leafPaint; } else { leafPaint = { type: paint.type, image: image }; if (image.hasAlphaPixel) leafPaint.isTransparent = true; cache = image.use > 1 ? { leafPaint: leafPaint, paint: paint, boxBounds: box.set(boxBounds) } : null; } if (firstUse || image.loading) event = { image: image, attrName: 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; }, paint.lod && image.getThumbSize(paint.lod)); 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) { let needUpdate = true; const data = ui.__; if (attrName === "fill" && !data.__naturalWidth) { data.__naturalWidth = image.width / data.pixelRatio; data.__naturalHeight = image.height / data.pixelRatio; if (data.__autoSide) { ui.forceUpdate("width"); core.LeafHelper.updateBounds(ui); if (ui.__proxyData) { ui.setProxyAttr("width", data.width); ui.setProxyAttr("height", data.height); } needUpdate = false; } } if (paint.mode === "brush") draw.PaintImage.brush(leafPaint); if (!leafPaint.data) { draw.PaintImage.createData(leafPaint, image, paint, boxBounds); const {transform: transform} = leafPaint.data, {opacity: opacity} = paint; const clip = (transform && !transform.onlyScale || data.path || data.cornerRadius) && !leafPaint.brush; if (clip || opacity && opacity < 1 || paint.blendMode) leafPaint.complex = clip ? 2 : true; } if (paint.filter) draw.PaintImage.applyFilter(leafPaint, image, paint.filter, ui); return needUpdate; } 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: leafer} = ui; if (leafer && leafer.viewReady) leafer.renderer.ignore = value; } const {get: get$3, translate: translate$1} = core.MatrixHelper; const tempBox = new core.Bounds; const tempScaleData = {}; const tempImage = {}; function createData(leafPaint, image, paint, box) { leafPaint.data = draw.PaintImage.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"; const {width: width, height: height} = image; const {mode: mode, align: align, offset: offset, scale: scale, size: size, rotation: rotation, skew: skew, clipSize: clipSize, repeat: repeat, gap: gap, interlace: interlace} = paint; const sameBox = box.width === width && box.height === height; const data = { mode: mode }; const swapSize = align !== "center" && (rotation || 0) % 180 === 90; core.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 = core.BoundsHelper.getFitScale(box, tempImage, mode !== "fit"); core.BoundsHelper.put(box, image, align, scaleX, false, tempImage); core.BoundsHelper.scale(tempImage, scaleX, scaleY, true); } } else { if (scale || size) { core.MathHelper.getScaleData(scale, size, image, tempScaleData); scaleX = tempScaleData.scaleX; scaleY = tempScaleData.scaleY; } if (align || gap || repeat) { if (scaleX) core.BoundsHelper.scale(tempImage, scaleX, scaleY, true); if (align) core.AlignHelper.toPoint(align, tempImage, box, tempImage, true, true); } } if (offset) core.PointHelper.move(tempImage, offset); switch (mode) { case "stretch": if (!sameBox) { scaleX = box.width / width, scaleY = box.height / height; draw.PaintImage.stretchMode(data, box, scaleX, scaleY); } else if (scaleX) scaleX = scaleY = undefined; break; case "normal": case "clip": if (tempImage.x || tempImage.y || scaleX || clipSize || rotation || skew) { let clipScaleX, clipScaleY; if (clipSize) clipScaleX = box.width / clipSize.width, clipScaleY = box.height / clipSize.height; draw.PaintImage.clipMode(data, box, tempImage.x, tempImage.y, scaleX, scaleY, rotation, skew, clipScaleX, clipScaleY); if (clipScaleX) scaleX = scaleX ? scaleX * clipScaleX : clipScaleX, scaleY = scaleY ? scaleY * clipScaleY : clipScaleY; } break; case "repeat": case "brush": if (!sameBox || scaleX || rotation || skew) draw.PaintImage.repeatMode(data, box, width, height, tempImage.x, tempImage.y, scaleX, scaleY, rotation, skew, align, paint.freeTransform); if (!repeat) data.repeat = "repeat"; const count = core.isObject(repeat); if (gap || count) data.gap = getGapData(gap, count && repeat, tempImage.width, tempImage.height, box); break; case "fit": case "cover": default: if (scaleX) draw.PaintImage.fillOrFitMode(data, box, tempImage.x, tempImage.y, scaleX, scaleY, rotation); } if (!data.transform) { if (box.x || box.y) translate$1(data.transform = get$3(), box.x, box.y); } if (scaleX) { data.scaleX = scaleX; data.scaleY = scaleY; } if (repeat) data.repeat = core.isString(repeat) ? repeat === "x" ? "repeat-x" : "repeat-y" : "repeat"; if (interlace) data.interlace = core.isNumber(interlace) || interlace.type === "percent" ? { type: "x", offset: interlace } : interlace; return data; } function getGapData(gap, repeat, width, height, box) { let xGap, yGap; if (core.isObject(gap)) xGap = gap.x, yGap = gap.y; else xGap = yGap = gap; return { x: getGapValue(xGap, width, box.width, repeat && repeat.x), y: getGapValue(yGap, height, box.height, repeat && repeat.y) }; } function getGapValue(gap, size, totalSize, rows) { const auto = core.isString(gap) || rows; const remain = rows ? totalSize - rows * size : totalSize % size; const value = auto ? remain / ((rows || Math.floor(totalSize / size)) - 1) : gap; return gap === "auto" ? value < 0 ? 0 : value : value; } let origin = {}, tempMatrix$1 = core.getMatrixData(); const {get: get$2, set: set, rotateOfOuter: rotateOfOuter$1, translate: translate, scaleOfOuter: scaleOfOuter$1, multiplyParent: multiplyParent, scale: scaleHelper, rotate: rotate, skew: skewHelper} = core.MatrixHelper; function stretchMode(data, box, scaleX, scaleY) { const transform = get$2(), {x: x, y: y} = box; if (x || y) translate(transform, x, y); else if (scaleX > 0 && scaleY > 0) transform.onlyScale = true; scaleHelper(transform, scaleX, scaleY); data.transform = transform; } function fillOrFitMode(data, box, x, y, scaleX, scaleY, rotation) { const transform = get$2(); translate(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, clipScaleX, clipScaleY) { const transform = get$2(); layout(transform, box, x, y, scaleX, scaleY, rotation, skew); if (clipScaleX) { if (rotation || skew) { set(tempMatrix$1); scaleOfOuter$1(tempMatrix$1, box, clipScaleX, clipScaleY); multiplyParent(transform, tempMatrix$1); } else scaleOfOuter$1(transform, box, clipScaleX, clipScaleY); } data.transform = transform; } function repeatMode(data, box, width, height, x, y, scaleX, scaleY, rotation, skew, align, freeTransform) { const transform = get$2(); if (freeTransform) { layout(transform, box, x, y, scaleX, scaleY, rotation, skew); } else { if (rotation) { if (align === "center") { rotateOfOuter$1(transform, { x: width / 2, y: height / 2 }, rotation); } else { rotate(transform, rotation); switch (rotation) { case 90: translate(transform, height, 0); break; case 180: translate(transform, width, height); break; case 270: translate(transform, 0, width); break; } } } origin.x = box.x