UNPKG

@leafer-draw/worker

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