UNPKG

@aurigma/design-atoms

Version:

Design Atoms is a part of Customer's Canvas SDK which allows for manipulating individual design elements through your code.

1,092 lines 53.1 kB
import { EventWithSenderArg } from "@aurigma/design-atoms-model/EventObject"; import { PointF, normalizeAngle } from "@aurigma/design-atoms-model/Math"; import Environment from "@aurigma/design-atoms-model/Utils/Environment"; import { DefaultCursorHelper } from "../Input/InputHandler/Default/DefaultCursorHandler"; import { UpdateStatus } from "../UpdateStatus"; import { Cursor } from "../Utils/Common"; import { CoordinatesConvertUtils } from "../Utils/CoordinatesConvertUtils"; import { RulersConfigUnit, getUnitScale } from "./Interfaces"; import { ScrollBarsStyle } from "./ScrollBarsStyle"; import { StandardIntervalCheckLoop } from "./StandardIntervalCheckLoop"; import { ViewportAlignment } from "./ViewportAlignment"; import { ZoomMode } from "./ZoomMode"; import { ZoomQuality } from "./ZoomQuality"; export class ZoomParams { } export class BaseViewer { constructor() { this._maxZoom = 16; this._minZoom = 0.05; this._screenXDpi = 72; this._screenYDpi = 72; this._scrollBarsStyle = ScrollBarsStyle.auto; this._scrollingPosition = new PointF(0, 0); this._viewportAlignment = ViewportAlignment.leftTop; this._zoom = 1; this._zoomMode = ZoomMode.none; this._zoomQuality = ZoomQuality.shrinkHighStretchLow; this._bestFitWhiteSpacePc = 0; this._status = UpdateStatus.ready; this._delayedRefreshTimeout = 1000; this._refreshTimer = null; this._contentElements = []; //Specify whether we need to refresh the image. this._needToRefresh = false; this._rulerEnabled = false; this._rulerWidth = 13; this._rulerBorderWidth = 1; this._rulerScale = 1; // to translate points to inches for example. this._rulerDivision = 5; this._rulerOffsetX = 0; this._rulerOffsetY = 0; this._rulersOnScrollDelegate = null; this._rulersOnZoomDelegate = null; this._pinchZoomEnabled = true; this._holderBounds = null; this._contentCtxDimension = {}; this._holderElement = null; this._rulers = {}; this._bodyCursor = null; this._ignoreDocumentClickOnce = false; this._statusChangedEvent = new EventWithSenderArg(); this._scrolledEvent = new EventWithSenderArg(); this._onresizeEvent = new EventWithSenderArg(); this._zoomedEvent = new EventWithSenderArg(); this._intervalCheckLoop = StandardIntervalCheckLoop; this._contentAngle = 0; this._rulersOnHover = (e) => { // used to draw slide bars just when somebody move mouse over content but don't hold some object // and don't use setCapture if (!this.rulerEnabled) return; var topSlideBar = this._rulers.topSlideBar; var leftSlideBar = this._rulers.leftSlideBar; var viewerLocation = CoordinatesConvertUtils.getElementPageCoord(this._viewerElement); var x = e.page.x - viewerLocation.left; var y = e.page.y - viewerLocation.top; topSlideBar.style.left = x + "px"; leftSlideBar.style.top = y + "px"; }; this._onDocumentClick = (e) => { }; this._onScroll = (e) => { if (this._scrollInitialized) { this._scrollInitialized = false; return; } this._scrollingPosition = this.getActualScrollPosition(); this._canvas.onScroll(); //Raise a client event. this._scrolledEvent.notify(this); }; } get element() { return this._viewerElement; } get id() { return this._viewerElement.id; } get contentAngle() { return this._contentAngle; } set contentAngle(value) { if (value == null || value === this._contentAngle) { return; } this._contentAngle = normalizeAngle(value); let translate = "(0, 0)"; if (this._contentAngle === 90) { translate = "(0px, -100%)"; } else if (this._contentAngle === 180) { translate = "(-100%, -100%)"; } else if (this._contentAngle === 270) { translate = "(-100%, 0px)"; } this._contentCtx.style.transform = "rotate(" + this._contentAngle + "deg) translate" + translate; this._contentCtx.style.transformOrigin = "left top"; this._updateViewport(); this._canvas.setZoom(this.zoom, undefined, { force: true }); this._canvas.updateSelection(); this._canvas.redraw(); } get holderBounds() { return this._getElementBounds(this.element); } /** Gets a value that represents the width of the control's content taking into account its horizontal resolution and zoom value */ get contentWidth() { return this._holderBounds.width; } /** Gets the width (in pixels) of the control area. */ get width() { return this._holderBounds.offsetWidth; } /** Gets a value that represents the height of the control's content taking into account its vertical resolution and zoom value */ get contentHeight() { return this._holderBounds.height; } /** Gets the height (in pixels) of the control area. */ get height() { return this._holderBounds.offsetHeight; } _notifySizeChanged() { this._onResize(null, false); } _renderContent(sb) { return sb; } get hasContent() { return true; } _render() { const wsize = `width:${this.contentWidth}px; height:${this.contentHeight}px;`; let sb = ""; sb += '\u003cdiv id="cvContent" style="position:absolute; overflow: hidden; box-sizing: content-box; -webkit-box-sizing: content-box; -moz-box-sizing: content-box;'; // todo: remove // only for debug sb += "display: none;"; if (this.hasContent) { sb += wsize; } else { sb += "display:none;"; } const vl = this.getViewportLocation(); sb += "left:" + vl.x + "px;top:" + vl.y + "px"; sb += '"\u003e'; sb = this._renderContent(sb); // close content. sb += "\u003c/div\u003e"; sb += "\u003c/div\u003e"; return sb; } _clearElement(el) { while (el.firstChild) { el.removeChild(el.firstChild); } } _rulersOnScroll() { this._updateRulersStyle(); } _rulersOnZoom() { this._drawRulers(); this._updateRulersStyle(); this._canvas.updateViewport(); } _initializeRulers() { // create Rulers. var el = this.element.parentNode; var doc = el.ownerDocument; const rulersStyle = "z-index: 2; -webkit-transform: translate3d(0px, 0px, 0px); -moz-transform: translate3d(0px, 0px, 0px)"; let topRuller = doc.createElement("div"); topRuller.setAttribute("style", rulersStyle); let fullTopRuller = doc.createElement("div"); let leftRuller = doc.createElement("div"); leftRuller.setAttribute("style", rulersStyle); let fullLeftRuller = doc.createElement("div"); let whiteRect = doc.createElement("div"); whiteRect.style.zIndex = "2"; let topSlideBar = doc.createElement("div"); let leftSlideBar = doc.createElement("div"); this._rulers.topRuller = topRuller; this._rulers.leftRuller = leftRuller; this._rulers.fullTopRuller = fullTopRuller; this._rulers.fullLeftRuller = fullLeftRuller; this._rulers.whiteRect = whiteRect; this._rulers.topSlideBar = topSlideBar; this._rulers.leftSlideBar = leftSlideBar; const id = this.element.id; topRuller.id = id + "_TopRuler"; fullTopRuller.id = id + "_FullTopRuler"; leftRuller.id = id + "_LeftRuler"; fullLeftRuller.id = id + "_FullLeftRuler"; whiteRect.id = id + "_WhiteRect"; topSlideBar.id = id + "_TopSlideBar"; leftSlideBar.id = id + "_LeftSlideBar"; fullTopRuller = topRuller.appendChild(fullTopRuller); fullLeftRuller = leftRuller.appendChild(fullLeftRuller); topSlideBar = topRuller.appendChild(topSlideBar); leftSlideBar = leftRuller.appendChild(leftSlideBar); topRuller = el.appendChild(topRuller); leftRuller = el.appendChild(leftRuller); whiteRect = el.appendChild(whiteRect); topRuller.style.position = fullTopRuller.style.position = leftRuller.style.position = fullLeftRuller.style.position = whiteRect.style.position = topSlideBar.style.position = leftSlideBar.style.position = "absolute"; leftSlideBar.style.width = topSlideBar.style.height = this.rulerWidth + "px"; leftSlideBar.style.height = topSlideBar.style.width = "1px"; leftSlideBar.style.backgroundColor = topSlideBar.style.backgroundColor = "#ff0000"; leftSlideBar.style.overflow = topSlideBar.style.overflow = "hidden"; leftSlideBar.style.webkitTransform = topSlideBar.style.webkitTransform = "translate3d(0, 0, 0)"; topRuller.style.overflow = leftRuller.style.overflow = whiteRect.style.overflow = "hidden"; topRuller.style.visibility = leftRuller.style.visibility = whiteRect.style.visibility = "hidden"; whiteRect.style.backgroundColor = "#909090"; el.style.position = "relative"; this._drawRulers(); this._updateRulersStyle(); if (!this._rulersOnScrollDelegate) { this._rulersOnScrollDelegate = this._rulersOnScroll.bind(this); this.add_scrolled(this._rulersOnScrollDelegate); } if (!this._rulersOnZoomDelegate) { this._rulersOnZoomDelegate = this._rulersOnZoom.bind(this); this.add_zoomed(this._rulersOnZoomDelegate); } } _disposeRulers() { if (this._rulersOnScrollDelegate) { this.remove_scrolled(this._rulersOnScrollDelegate); this._rulersOnScrollDelegate = null; } if (this._rulersOnZoomDelegate) { this.remove_zoomed(this._rulersOnZoomDelegate); this._rulersOnZoomDelegate = null; } } _updateRulersStyle() { // Set ruler style. let leftRuller = this._rulers.leftRuller; let topRuller = this._rulers.topRuller; let fullLeftRuller = this._rulers.fullLeftRuller; let fullTopRuller = this._rulers.fullTopRuller; let whiteRect = this._rulers.whiteRect; let width = this.width; let hight = this.height; let contentWidth = this.contentWidth; let contentHeight = this.contentHeight; let scrollBarWidth = this.scrollBarWidth; let leftRullerStyle = leftRuller.style; let topRullerStyle = topRuller.style; let fullTopRullerStyle = fullTopRuller.style; let fullLeftRullerStyle = fullLeftRuller.style; let whiteRectStyle = whiteRect.style; if (!this._rulerEnabled) { topRullerStyle.width = topRullerStyle.height = leftRullerStyle.width = leftRullerStyle.height = topRullerStyle.borderWidth = leftRullerStyle.borderWidth = whiteRectStyle.width = whiteRectStyle.height = "0px"; leftRullerStyle.visibility = topRullerStyle.visibility = whiteRectStyle.visibility = "hidden"; } else { leftRullerStyle.visibility = topRullerStyle.visibility = whiteRectStyle.visibility = "inherit"; leftRullerStyle.backgroundColor = topRullerStyle.backgroundColor = "#ffffff"; leftRullerStyle.borderRight = topRullerStyle.borderBottom = `${this._rulerBorderWidth}px solid black`; let sbAlways = this.scrollBarsStyle == ScrollBarsStyle.always; let sbAuto = this.scrollBarsStyle == ScrollBarsStyle.auto; let isThisScrollBar = sbAlways || (sbAuto && contentHeight > hight - this.rulerWidth); let isThatScrollBar = sbAlways || (sbAuto && contentWidth > width - this.rulerWidth); isThisScrollBar = isThisScrollBar || (isThatScrollBar && sbAuto && contentHeight > hight - this.rulerWidth - scrollBarWidth); isThatScrollBar = isThatScrollBar || (isThisScrollBar && sbAuto && contentWidth > width - this.rulerWidth - scrollBarWidth); let rw = isThisScrollBar ? width - scrollBarWidth : width; let rh = isThatScrollBar ? hight - scrollBarWidth : hight; topRullerStyle.width = rw + "px"; leftRullerStyle.height = rh + "px"; fullTopRullerStyle.height = topRullerStyle.height = whiteRect.style.height = this._rulerWidth + "px"; fullLeftRullerStyle.width = leftRullerStyle.width = whiteRect.style.width = this._rulerWidth + "px"; // to get clear scroll we need rulers positioned in top-left corner. topRullerStyle.top = leftRullerStyle.top = topRullerStyle.left = leftRullerStyle.left = whiteRect.style.top = whiteRect.style.left = "0px"; const sp = this.scrollingPosition; fullTopRullerStyle.left = -sp.x + "px"; fullLeftRullerStyle.top = -sp.y + "px"; } } _setRulersConfig(config) { if (config === null || config === void 0 ? void 0 : config.enabled) { var scale = getUnitScale(config); this._rulerEnabled = true; this._rulerOffsetX = (-1 * config.origin.X) / scale; this._rulerOffsetY = (-1 * config.origin.Y) / scale; this._rulerScale = scale; this._rulerDivision = config.unit === RulersConfigUnit.Inch ? 4 : 5; } else { this._rulerEnabled = false; } this._notifySizeChanged(); } _drawRulers() { if (!this._rulerEnabled || this._baseRulersOffset == null) return; const doc = this.element.parentNode.ownerDocument; const fullLeftRuler = this._rulers.fullLeftRuller; const fullTopRuler = this._rulers.fullTopRuller; const axes = [ { controlLength: this.width, contentLength: this.contentWidth, viewportLocation: this.getViewportLocation().x, factor: this.zoom * this.actualSizeHorizontalScale, ruler: fullTopRuler, startWorkspaceLocation: null, endWorkspaceLocation: null, rulerPixelLength: null, origin: null, }, { controlLength: this.height, contentLength: this.contentHeight, viewportLocation: this.getViewportLocation().y, factor: this.zoom * this.actualSizeHorizontalScale, ruler: fullLeftRuler, startWorkspaceLocation: null, endWorkspaceLocation: null, rulerPixelLength: null, origin: null, }, ]; const rulerOffsets = [this.rulerOffsetX + this._baseRulersOffset.x, this.rulerOffsetY + this._baseRulersOffset.y]; const sw = this.scrollBarWidth; const sbAlways = this.scrollBarsStyle == ScrollBarsStyle.always; const sbAuto = this.scrollBarsStyle == ScrollBarsStyle.auto; const rulerScale = this.rulerScale; for (let i = 0; i < axes.length; i++) { axes[i].origin = axes[i].viewportLocation + rulerOffsets[i] * axes[i].factor; let isThisScrollBar = sbAlways || (sbAuto && axes[1 - i].contentLength > axes[1 - i].controlLength - this.rulerWidth); let isThatScrollBar = sbAlways || (sbAuto && axes[i].contentLength > axes[i].controlLength - this.rulerWidth); isThisScrollBar = isThisScrollBar || (isThatScrollBar && sbAuto && axes[1 - i].contentLength > axes[1 - i].controlLength - this.rulerWidth - sw); isThatScrollBar = isThatScrollBar || (isThisScrollBar && sbAuto && axes[i].contentLength > axes[i].controlLength - this.rulerWidth - sw); if (isThatScrollBar) { axes[i].rulerPixelLength = Math.max(axes[i].contentLength + this.rulerWidth, axes[i].controlLength - (isThisScrollBar ? sw : 0)); } else { axes[i].rulerPixelLength = isThisScrollBar ? axes[i].controlLength - sw : axes[i].controlLength; } axes[i].startWorkspaceLocation = -axes[i].origin; axes[i].endWorkspaceLocation = axes[i].startWorkspaceLocation + axes[i].rulerPixelLength; axes[i].startWorkspaceLocation /= axes[i].factor; axes[i].endWorkspaceLocation /= axes[i].factor; axes[i].startWorkspaceLocation *= rulerScale; axes[i].endWorkspaceLocation *= rulerScale; // generate division. const maxCutLength = 80; // pixels; const minCutLength = 4; // pixels; let currentDivision = 1; let tmp = (maxCutLength / axes[i].factor) * rulerScale; let size = 1; while (tmp > 10) { tmp /= 10; size *= 10; } if (tmp > 5) { size = 5 * size; currentDivision = this._rulerDivision; } else if (tmp > 2) { size = 2 * size; currentDivision = 2; } // generate first fragmentation (with numbers); const cuts = [{ location: 0, index: 0 }]; let cur = 0; while (cur < axes[i].endWorkspaceLocation) { cur += size; cuts.push({ location: cur, index: 0 }); } cur = 0; while (cur > axes[i].startWorkspaceLocation) { cur -= size; cuts.push({ location: cur, index: 0 }); } // clear elements. this._clearElement(axes[i].ruler); // draw text. for (let j = 0; j < cuts.length; j++) { const txt = doc.createElement("span"); const labelText = Math.abs(Math.round(cuts[j].location)).toString(); txt.innerHTML = i == 1 ? labelText.split("").join("<br/>") : labelText; txt.style.position = "absolute"; txt.style.fontSize = "9px"; txt.style.margin = "0px"; txt.style.padding = "0px"; txt.style.fontFamily = "Tahoma, Verdana, Arial;"; txt.style.backgroundColor = "#ffffff"; const offset = Math.round((cuts[j].location * axes[i].factor) / rulerScale + axes[i].origin); txt.style.top = i == 0 ? "-1px" : offset + 2 + "px"; txt.style.left = i == 1 ? "2px" : offset + 2 + "px"; axes[i].ruler.appendChild(txt); } // create other cuts. let currentIndex = 1; const divs = { 1: { newDiv: this._rulerDivision, fractions: 2 }, 2: { newDiv: 1, fractions: 2 }, }; divs[this._rulerDivision] = { newDiv: 1, fractions: this._rulerDivision, }; let c = divs[currentDivision]; let newSize = size / c.fractions; while ((newSize * axes[i].factor) / rulerScale > minCutLength) { let l = cuts.length - 1; while (l >= 0) { for (let k = 0; k < c.fractions - 1; k++) cuts.push({ index: currentIndex, location: cuts[l].location + newSize * (k + 1), }); l--; } currentIndex++; c = divs[c.newDiv]; newSize = newSize / c.fractions; } for (let j = 0; j < cuts.length; j++) { const cut = doc.createElement("div"); cut.style.position = "absolute"; cut.style.overflow = "hidden"; cut.style.backgroundColor = "#000000"; cut.style.padding = "0px"; cut.style.margin = "0px"; const offset = Math.round((cuts[j].location * axes[i].factor) / rulerScale + axes[i].origin /* + this.rulerWidth*/); const rw = this.rulerWidth; const cutWidth = Math.max(Math.ceil(rw / Math.pow(2, cuts[j].index)), 2); cut.style.width = i == 0 ? "1px" : cutWidth + "px"; cut.style.height = i == 1 ? "1px" : cutWidth + "px"; cut.style.top = i == 0 ? rw - cutWidth + "px" : offset + "px"; cut.style.left = i == 1 ? rw - cutWidth + "px" : offset + "px"; cut.style.webkitTransform = "translate3d(0, 0, 0)"; axes[i].ruler.appendChild(cut); } } } getActualScrollPosition() { const holder = this.element; const scrollLeft = holder.scrollLeft; const scrollTop = holder.scrollTop; return new PointF(scrollLeft, scrollTop); } getViewportLocation() { const rulerWidth = this.actualRulerWidth; const elementBounds = this._holderBounds; const viewAreaWidth = elementBounds.width - rulerWidth; const viewAreaHeight = elementBounds.height - rulerWidth; const swapSides = this._contentAngle === 90 || this._contentAngle === 270; const contentWidth = swapSides ? this.contentHeight : this.contentWidth; const contentHeight = swapSides ? this.contentWidth : this.contentHeight; let x, y; switch (this._viewportAlignment) { case ViewportAlignment.centerBottom: case ViewportAlignment.centerCenter: case ViewportAlignment.centerTop: x = Math.floor((viewAreaWidth - contentWidth) / 2); break; case ViewportAlignment.leftBottom: case ViewportAlignment.leftCenter: case ViewportAlignment.leftTop: x = 0; break; case ViewportAlignment.rightBottom: case ViewportAlignment.rightCenter: case ViewportAlignment.rightTop: x = viewAreaWidth - contentWidth; break; } switch (this._viewportAlignment) { case ViewportAlignment.centerCenter: case ViewportAlignment.leftCenter: case ViewportAlignment.rightCenter: y = Math.floor((viewAreaHeight - contentHeight) / 2); break; case ViewportAlignment.centerTop: case ViewportAlignment.leftTop: case ViewportAlignment.rightTop: y = 0; break; case ViewportAlignment.centerBottom: case ViewportAlignment.leftBottom: case ViewportAlignment.rightBottom: y = viewAreaHeight - contentHeight; break; } return new PointF(Math.max(rulerWidth, x + rulerWidth), Math.max(rulerWidth, y + rulerWidth)); } ignoreDocumentClickOnce() { this._ignoreDocumentClickOnce = true; } _makeInactive(domElement) { domElement.ondrag = () => false; domElement.unselectable = "on"; } _resizeContentElements() { const contentWidth = this.contentWidth; const contentHeight = this.contentHeight; const el = this._contentElements; for (let i = 0; i < el.length; i++) { el[i].style.width = contentWidth + "px"; el[i].style.height = contentHeight + "px"; if (el[i].tagName == "IMG") { el[i].width = contentWidth; el[i].height = contentHeight; } } this._contentCtxDimension.width = contentWidth; this._contentCtxDimension.height = contentHeight; this._holderBounds = this._getElementBounds(this.element); } _getElementBounds(element) { //when the element is placed in the hidden element ("display: none" style) //then clientWidth, clientHeight, offsetWidth, offsetHeight, offsetTop, offsetLeft are 0 const hiddenElements = []; //remove display:none style from parent elements while (element.length > 0 && element != document) { if (element.style.display == "none") { hiddenElements.push({ element: element, display: element.style.display, visibility: element.style.visibility, }); element.style.visibility = "hidden"; element.style.display = "block"; } element = element.parent(); } //get element properties const width = element.clientWidth; const height = element.clientHeight; const offsetHeight = element.offsetHeight; const offsetWidth = element.offsetWidth; const offsetTop = element.offsetTop; const offsetLeft = element.offsetLeft; //restore display:none style for (let i = 0; i < hiddenElements.length; i++) { const hiddenElementBound = hiddenElements[i]; hiddenElementBound.element[0].style.visibility = hiddenElementBound.visibility; hiddenElementBound.element[0].style.display = hiddenElementBound.display; } return { width: width, height: height, offsetWidth: offsetWidth, offsetHeight: offsetHeight, offsetTop: offsetTop, offsetLeft: offsetLeft, }; } initialize(intervalCheckLoop = null) { if (intervalCheckLoop != null) this._intervalCheckLoop = intervalCheckLoop; const holderElement = this.element; this._holderBounds = this._getElementBounds(holderElement); this._holderElement = holderElement; const html = this._render(); holderElement.innerHTML = html; this._makeInactive(holderElement); const contentElement = holderElement.querySelectorAll("#cvContent"); this._contentElements.push((this._contentCtx = contentElement[0])); //Init scrolling position const sp = this._scrollingPosition; this._scrollInitialized = true; holderElement.scrollLeft = sp.x; holderElement.scrollTop = sp.y; this._initializeRulers(); //mouse/touch events handling document.addEventListener("click", this._onDocumentClick); this._holderElement.addEventListener("scroll", this._onScroll); this._holderElement.addEventListener("contextmenu", (e) => e.preventDefault()); this._contentCtxDimension = { width: this.contentWidth, height: this.contentHeight, }; this._updateViewport(); this._updateViewportAlignment(); this._updateRulersStyle(); this._startIntervalCheckLoop(); } _startIntervalCheckLoop() { this._intervalCheckLoop.start({ checkFunction: () => { const bounds = this._getElementBounds(this.element); if ((bounds.width > 0 && bounds.width !== this._holderBounds.width) || (bounds.height > 0 && bounds.height !== this._holderBounds.height)) { return true; } return false; }, onChange: () => { const bounds = this._getElementBounds(this.element); this._holderBounds = bounds; this._onResize(this, false); }, }); } setIntervalCheckLoop(intervalCheckLoop) { if (intervalCheckLoop == null) return; this._intervalCheckLoop = intervalCheckLoop; this._startIntervalCheckLoop(); } _onResize(sender, updateButtonGroup) { this._updateViewport(true); this._updateViewportAlignment(); this._updateRulersStyle(); this._scrollingPosition = this.getActualScrollPosition(); this._onresizeEvent.notify(this); } _updateViewport(ignoreZoomConfigRestrictions = false) { if (!this.hasContent) return; const mode = this.zoomMode; const calculatedZoom = this.calculateZoomByZoomMode(mode); if (calculatedZoom == null || calculatedZoom < 0) return; if (ignoreZoomConfigRestrictions) { this._zoom = calculatedZoom; } else { this._zoom = Math.min(Math.max(calculatedZoom, this.minZoom), this.getMaxZoom()); } this._resizeContentElements(); const vl = this.getViewportLocation(); this._contentCtx.style.left = vl.x + "px"; this._contentCtx.style.top = vl.y + "px"; //Raise the client event this._zoomedEvent.notify(this); } _zoomToPagePoint(zoom, pageX, pageY, ignoreZoomConfigRestrictions = false) { const workspacePt = CoordinatesConvertUtils.pageToWorkspacePoint(new PointF(pageX, pageY), this, true); this._zoomToWorkspacePoint(zoom, workspacePt, ignoreZoomConfigRestrictions); } _zoomToWorkspacePoint(zoom, workspacePt, ignoreZoomConfigRestrictions = false) { this._setZoom(zoom, ignoreZoomConfigRestrictions); const contentPt = CoordinatesConvertUtils.workspaceToContentPoint(workspacePt, this); const rulerWidth = this.rulerEnabled ? this._rulerWidth : 0; const scroll = new PointF(Math.round(contentPt.x - (this._holderBounds.width - rulerWidth) / 2), Math.round(contentPt.y - (this._holderBounds.height - rulerWidth) / 2)); this.scrollingPosition = scroll; } _updateViewportAlignment() { const vl = this.getViewportLocation(); this._contentCtx.style.left = vl.x + "px"; this._contentCtx.style.top = vl.y + "px"; } /** Gets a scroll bar length (in other words, the right-bottom point of the image fragment which is out of the visible area). */ get scrollingSize() { const holder = this.element; let w = holder.scrollWidth - holder.clientWidth; let h = holder.scrollHeight - holder.clientHeight; if (w < 0) w = 0; if (h < 0) h = 0; return new PointF(Math.round(w), Math.round(h)); } get contentCtx() { return this._contentCtx; } /** @deprecated */ get clientSideOptions() { console.warn("Field 'clientSideOptions' is deprecated and may be removed in future releases! Please do not use it."); return {}; } get zoomConfig() { //console.log("not implemented"); return null; } setCursor(cursor, onBody = false) { this._getElementForCursor().style.cursor = DefaultCursorHelper.toCss(cursor); if (window != null) { cursor = onBody === true ? cursor : Cursor.defaultCursor; if (cursor === this._bodyCursor) return; window.document.querySelector("body").style.cursor = DefaultCursorHelper.toCss(cursor); this._bodyCursor = cursor; } } _getElementForCursor() { return this._contentCtx; } /** Amount of milliseconds to wait before delayed refresh will be invoked */ get delayedRefreshTimeout() { return this._delayedRefreshTimeout; } set delayedRefreshTimeout(v) { this._delayedRefreshTimeout = v; } /** * Gets the maximum allowed zoom value. * @remarks Zoom values are measured in percents/100. * It means that value = 1 specify 100% zoom (i.e. actual size), value = 10 means 1000% zoom (10x), value = 0,5 means 50% zoom (half), etc. **/ getMaxZoom() { return this._maxZoom > 0 ? this._maxZoom : 2.5; } setMaxZoom(value) { this._maxZoom = value; if (this.zoom > this.getMaxZoom()) { this.setZoom(this.getMaxZoom(), null); } } /** * Gets the minimum allowed zoom value * @remarks Zoom values are measured in percents/100. * It means that value = 1 specify 100% zoom (i.e. actual size), value = 10 means 1000% zoom (10x), value = 0,5 means 50% zoom (half), etc. */ get minZoom() { let minZoom = 0; if (this.zoomConfig) { minZoom = this.zoomConfig.min; } else { minZoom = this._minZoom > 0 ? this._minZoom : 0.001; } return minZoom; } set minZoom(value) { this._minZoom = value; if (this.zoom < this.minZoom) this.setZoom(this.minZoom, null); } set screenXDpi(v) { this._screenXDpi = v; } get screenXDpi() { /// <value type="Number">The value representing horizontal resolution in DPI used to show content in the browser.</value> /// <summary>Gets a value representing horizontal resolution in DPI used to show content in the browser.</summary> /// <remarks><para>If the <see cref="P:J:BitmapViewer.scaleToActualSize" /> property is set to <b>true</b> the value of the <see cref="P:J:BaseViewer.screenXDpi" /> property is used to scale content width to its actual size. </para><para>This property corresponds to <see cref="P:Aurigma.AjaxControls.BaseViewer.ScreenXDpi">BaseViewer.ScreenXDpi</see> server-side member.</para></remarks> return this._screenXDpi; } set screenYDpi(v) { this._screenYDpi = v; } get screenYDpi() { /// <value type="Number">The value representing vertical resolution in DPI used to show content in the browser.</value> /// <summary>Gets a value representing vertical resolution in DPI used to show content in the browser.</summary> /// <remarks><para>If the <see cref="P:J:BitmapViewer.scaleToActualSize" /> property is set to <b>true</b> the value of the <see cref="P:J:BaseViewer.screenYDpi" /> property is used to scale content height to its actual size. </para><para>This property corresponds to <see cref="P:Aurigma.AjaxControls.BaseViewer.ScreenYDpi">BaseViewer.ScreenYDpi</see> server-side member.</para></remarks> return this._screenYDpi; } get scrollBarsStyle() { /// <value type="ScrollBarsStyle">The <see cref="T:J:ScrollBarsStyle" /> enumeration member that specifies when to display scroll bars.</value> /// <summary>Gets a value that specifies whether to display scroll bars and whether to hide them automatically when the displayed content is less than the control size.</summary> /// <remarks><para>This property corresponds to <see cref="P:Aurigma.AjaxControls.BaseViewer.ScrollBarsStyle">BaseViewer.ScrollBarsStyle</see> server-side member.</para></remarks> return this._scrollBarsStyle; } set scrollBarsStyle(value) { this._scrollBarsStyle = value; } get scrollBarWidth() { /// <value type="Number" integer="true">The value that represents a scroll bar width (in pixels) in calculations.</value> /// <summary>Gets a value that represents a scroll bar width (in pixels) in calculations.</summary> /// <remarks><para>Since there is no simple way to determine the scroll bar width from the JavaScript (taking into account different platform, accessibility modes, etc) the estimated value is specified by this property.</para> /// <para>This property corresponds to <see cref="P:Aurigma.AjaxControls.BaseViewer.ScrollBarWidth">BaseViewer.ScrollBarWidth</see> server-side member.</para></remarks> if (this._actualScrollbarWidth == null) { const outerWidth = 100; const outer = document.createElement("div"); outer.style.visibility = "hidden"; outer.style.width = `${outerWidth}px`; outer.style.overflow = "scroll"; document.body.appendChild(outer); const inner = document.createElement("div"); inner.style.width = "100%"; outer.appendChild(inner); this._actualScrollbarWidth = outerWidth - inner.offsetWidth; outer.remove(); } return this._actualScrollbarWidth; } /** Gets/sets the position of the scroll bars */ get scrollingPosition() { this._scrollingPosition = this.getActualScrollPosition(); return this._scrollingPosition.round(); } set scrollingPosition(value) { const holder = this.element; holder.scrollLeft = value.x; holder.scrollTop = value.y; const pt = this.scrollingPosition; this._scrollingPosition = new PointF(pt.x, pt.y); this._scrolledEvent.notify(this); } /** Gets/sets a value that specifies content alignment in the control. */ get viewportAlignment() { return this._viewportAlignment; } set viewportAlignment(v) { this._viewportAlignment = v; this._updateViewportAlignment(); this._drawRulers(); this._updateRulersStyle(); this._canvas.updateViewport(); } get zoom() { return this._zoom; } setZoom(zoom, params, ignoreZoomConfigRestrictions = false, withoutUpdate = false) { var _a, _b; let zoomCenterX; let zoomCenterY; if (!params) { const pageCoords = CoordinatesConvertUtils.getElementPageCoord(this.element); const rulerWidth = this.actualRulerWidth; const viewportWidth = this._holderBounds.width - rulerWidth; const viewportHeight = this._holderBounds.height - rulerWidth; zoomCenterX = Math.round(pageCoords.left + rulerWidth + viewportWidth / 2); zoomCenterY = Math.round(pageCoords.top + rulerWidth + viewportHeight / 2); } else if (params.skipZoomToCenter) { this._setZoom(zoom, ignoreZoomConfigRestrictions); return this._zoom; } else { zoomCenterX = (_a = params.centerPageX) !== null && _a !== void 0 ? _a : params.workspaceX; zoomCenterY = (_b = params.centerPageY) !== null && _b !== void 0 ? _b : params.workspaceY; } if ((params === null || params === void 0 ? void 0 : params.workspaceX) != null) { this._zoomToWorkspacePoint(zoom, new PointF(zoomCenterX, zoomCenterY), ignoreZoomConfigRestrictions); } else { this._zoomToPagePoint(zoom, zoomCenterX, zoomCenterY, ignoreZoomConfigRestrictions); } return this._zoom; } _setZoom(value, ignoreZoomConfigRestrictions = false) { if (this._zoom == value && this._zoomMode == ZoomMode.none) return; if (ignoreZoomConfigRestrictions) { this._zoom = value; } else { this._zoom = Math.min(Math.max(value, this.minZoom), this.getMaxZoom()); } this._zoomMode = ZoomMode.none; this._updateViewport(ignoreZoomConfigRestrictions); } get pinchZoomEnabled() { return this._pinchZoomEnabled; } set pinchZoomEnabled(v) { this._pinchZoomEnabled = v; } get rulerEnabled() { return this._rulerEnabled; } set rulerEnabled(v) { var displayStatus = v ? "block" : "none"; this._rulers.fullLeftRuller.style.display = displayStatus; this._rulers.fullTopRuller.style.display = displayStatus; this._rulers.leftRuller.style.display = displayStatus; this._rulers.topRuller.style.display = displayStatus; this._rulers.whiteRect.style.display = displayStatus; this._rulerEnabled = v; this.zoomMode = this.zoomMode; } get rulerScale() { return this._rulerScale; } set rulerScale(v) { if (v <= 0) { throw new Error("Ruler scale should be greater 0."); } this._rulerScale = v; this._updateViewport(); } get rulerDivision() { return this._rulerDivision; } set rulerDivision(v) { if (v <= 0) { throw new Error("Ruler division should be greater 0."); } this._rulerDivision = v; this._updateViewport(); } get rulerOffsetX() { return this._rulerOffsetX; } set rulerOffsetX(v) { this._rulerOffsetX = v; this._updateViewport(); } get rulerOffsetY() { return this._rulerOffsetY; } set rulerOffsetY(v) { this._rulerOffsetY = v; this._updateViewport(); } get actualRulerWidth() { return this.rulerEnabled ? this.rulerWidth : 0; } get rulerWidth() { return this._rulerWidth; } set rulerWidth(v) { this._rulerWidth = v; this._updateViewport(); } get zoomMode() { return this._zoomMode; } set zoomMode(v) { this._setZoomMode(v); } _setZoomMode(v) { this._zoomMode = v; this._updateViewport(); } /** Gets or sets white space (in percent from 1 to 99) between content maximal demention and canvas */ get bestFitWhiteSpacePc() { return this._bestFitWhiteSpacePc * 100; } set bestFitWhiteSpacePc(v) { if (typeof v == "number" && !isNaN(v) && v >= 0 && v < 99) { this._bestFitWhiteSpacePc = v / 100; } this.zoomMode = this.zoomMode; } /** Gets a value that specifies a zoom quality. */ get zoomQuality() { return this._zoomQuality; } /** The value which represents current status of Viewer control */ get status() { return this._status; } /** * The value which represents description of exception which was thrown during calling remote scripting method * @deprecated **/ get exceptionDescription() { console.warn("Field 'exceptionDescription' is deprecated and may be removed in future releases! Please do not use it."); return ""; } get returnValue() { console.warn("Field 'returnValue' is deprecated and may be removed in future releases! Please do not use it."); return ""; } /** Gets a value that represents the width (in points) of the associated with this */ get workspaceWidth() { return this._canvas.workspaceWidth; } /** Gets a value that represents the height (in points) of the associated with this */ get workspaceHeight() { return this._canvas.workspaceHeight; } /** * Gets value which represents the ratio of screen horizontal resolution to the canvas resolution. */ get actualSizeHorizontalScale() { return Environment.screenDpi / Environment.devicePixelRatio / 72; } calculateZoomByZoomMode(zoomMode, size = null, bestFitWhiteSpacePcArg = null) { var _a, _b; if (this._holderBounds == null) return; //Workspace width/height const cw = (((_a = size === null || size === void 0 ? void 0 : size.width) !== null && _a !== void 0 ? _a : this.workspaceWidth) * this.screenXDpi) / 72; const ch = (((_b = size === null || size === void 0 ? void 0 : size.height) !== null && _b !== void 0 ? _b : this.workspaceHeight) * this.screenYDpi) / 72; const rw = this.rulerEnabled ? this.rulerWidth + this._rulerBorderWidth : 0; const bounds = this._holderBounds; const viewPortTotalRectangle = { width: bounds.offsetWidth - rw, height: bounds.offsetHeight - rw, }; //Content width/height const swapSides = this._contentAngle === 90 || this._contentAngle === 270; const scw = swapSides ? ch : cw; const sch = swapSides ? cw : ch; //The horizontal zoom without the scroll bars let hzwsb = viewPortTotalRectangle.width / scw; //The vertical zoom without the scroll bars let vzwsb = viewPortTotalRectangle.height / sch; //Scroll bar width const s = this.scrollBarWidth; //The horizontal zoom with the scroll bars let hz = (viewPortTotalRectangle.width - s) / scw; //The vertical zoom with the scroll bars let vz = (viewPortTotalRectangle.height - s) / sch; const sbAlways = this._scrollBarsStyle === ScrollBarsStyle.always; //Zoom let zoom = this.zoom; switch (zoomMode) { case ZoomMode.bestFit: const bestFitWhiteSpacePc = bestFitWhiteSpacePcArg !== null && bestFitWhiteSpacePcArg !== void 0 ? bestFitWhiteSpacePcArg : this._bestFitWhiteSpacePc; if (bestFitWhiteSpacePc > 0) { hzwsb = (viewPortTotalRectangle.width - viewPortTotalRectangle.width * bestFitWhiteSpacePc) / scw; vzwsb = (viewPortTotalRectangle.height - viewPortTotalRectangle.height * bestFitWhiteSpacePc) / sch; hz = (viewPortTotalRectangle.width - s - (viewPortTotalRectangle.width - s) * bestFitWhiteSpacePc) / scw; vz = (viewPortTotalRectangle.height - s - (viewPortTotalRectangle.height - s) * bestFitWhiteSpacePc) / sch; } zoom = sbAlways ? Math.min(hz, vz) : Math.min(hzwsb, vzwsb); break; case ZoomMode.bestFitShrinkOnly: zoom = sbAlways ? Math.min(hz, vz) : Math.min(hzwsb, vzwsb); zoom = Math.min(1, zoom); break; case ZoomMode.fitToHeight: if (sbAlways) { zoom = vz; } else { zoom = Math.round(vzwsb * scw) <= viewPortTotalRectangle.width ? vzwsb : vz; } break; case ZoomMode.fitToHeightShrinkOnly: if (sbAlways) { zoom = Math.min(1, vz); } else { // We should use Math.min here instead of using it later. // For example, if we have vzwsb a little more than 1 and vz a little less. // With Math.min we get 1as result, without - vz; zoom = Math.round(Math.min(1, vzwsb) * scw) <= viewPortTotalRectangle.width ? Math.min(1, vzwsb) : Math.min(1, vz); } break; case ZoomMode.fitToWidth: if (sbAlways) { zoom = hz; } else { zoom = Math.round(hzwsb * sch) <= viewPortTotalRectangle.height ? hzwsb : hz; } break; case ZoomMode.fitToWidthShrinkOnly: if (sbAlways) { zoom = Math.min(1, hz); } else { zoom = Math.round(Math.min(1, hzwsb) * sch) <= viewPortTotalRectangle.height ? Math.min(1, hzwsb) : Math.min(1, hz); } break; case ZoomMode.fitToSelection: this.zoomToSelection(); return null; } return zoom; } zoomToSelection() { } zoomToItems(items = []) { if (!items || (items === null || items === void 0 ? void 0 : items.length) < 1) { this._setZoomMode(ZoomMode.bestFit); return; } const rulerWidth = this.rulerEnabled ? this.rulerWidth + this._rulerBorderWidth : 0; const viewPortRectangle = { width: this.holderBounds.offsetWidth - rulerWidth, height: this.holderBounds.offsetHeight - rulerWidth, }; const itemsBounds = items.map((i) => this._canvas.viewer.getHandler(i).bounds); const left = Math.min(...itemsBounds.map((bound) => bound.left)); const top = Math.min(...itemsBounds.map((bound) => bound.top)); const right = Math.max(...itemsBounds.map((bound) => bound.right)); const bottom = Math.max(...itemsBounds.map((bound) => bound.bottom)); const width = right - left; const height = bottom - top; const centerX = left + width / 2; const centerY = top + height / 2; //Workspace content width/height const renderingWidth = ((right - left) * this.screenXDpi) / 72; const renderingHeight = ((bottom - top) * this.screenXDpi) / 72; const horizontalZoom = viewPortRectangle.width / renderingWidth; const verticalZoom = viewPortRectangle.height / renderingHeight; this._setZoom(Math.min(verticalZoom, horizontalZoom)); // we ca