237 lines
11 KiB
JavaScript
237 lines
11 KiB
JavaScript
|
|
import { equalSizes, size } from "./size.mjs";
|
||
|
|
import { createObservable as createDevicePixelRatioObservable } from "./device-pixel-ratio.mjs";
|
||
|
|
var DevicePixelContentBoxBinding = /** @class */ (function () {
|
||
|
|
function DevicePixelContentBoxBinding(canvasElement, transformBitmapSize, options) {
|
||
|
|
var _a;
|
||
|
|
this._canvasElement = null;
|
||
|
|
this._bitmapSizeChangedListeners = [];
|
||
|
|
this._suggestedBitmapSize = null;
|
||
|
|
this._suggestedBitmapSizeChangedListeners = [];
|
||
|
|
// devicePixelRatio approach
|
||
|
|
this._devicePixelRatioObservable = null;
|
||
|
|
// ResizeObserver approach
|
||
|
|
this._canvasElementResizeObserver = null;
|
||
|
|
this._canvasElement = canvasElement;
|
||
|
|
this._canvasElementClientSize = size({
|
||
|
|
width: this._canvasElement.clientWidth,
|
||
|
|
height: this._canvasElement.clientHeight,
|
||
|
|
});
|
||
|
|
this._transformBitmapSize = transformBitmapSize !== null && transformBitmapSize !== void 0 ? transformBitmapSize : (function (size) { return size; });
|
||
|
|
this._allowResizeObserver = (_a = options === null || options === void 0 ? void 0 : options.allowResizeObserver) !== null && _a !== void 0 ? _a : true;
|
||
|
|
this._chooseAndInitObserver();
|
||
|
|
// we MAY leave the constuctor without any bitmap size observation mechanics initialized
|
||
|
|
}
|
||
|
|
DevicePixelContentBoxBinding.prototype.dispose = function () {
|
||
|
|
var _a, _b;
|
||
|
|
if (this._canvasElement === null) {
|
||
|
|
throw new Error('Object is disposed');
|
||
|
|
}
|
||
|
|
(_a = this._canvasElementResizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
||
|
|
this._canvasElementResizeObserver = null;
|
||
|
|
(_b = this._devicePixelRatioObservable) === null || _b === void 0 ? void 0 : _b.dispose();
|
||
|
|
this._devicePixelRatioObservable = null;
|
||
|
|
this._suggestedBitmapSizeChangedListeners.length = 0;
|
||
|
|
this._bitmapSizeChangedListeners.length = 0;
|
||
|
|
this._canvasElement = null;
|
||
|
|
};
|
||
|
|
Object.defineProperty(DevicePixelContentBoxBinding.prototype, "canvasElement", {
|
||
|
|
get: function () {
|
||
|
|
if (this._canvasElement === null) {
|
||
|
|
throw new Error('Object is disposed');
|
||
|
|
}
|
||
|
|
return this._canvasElement;
|
||
|
|
},
|
||
|
|
enumerable: false,
|
||
|
|
configurable: true
|
||
|
|
});
|
||
|
|
Object.defineProperty(DevicePixelContentBoxBinding.prototype, "canvasElementClientSize", {
|
||
|
|
get: function () {
|
||
|
|
return this._canvasElementClientSize;
|
||
|
|
},
|
||
|
|
enumerable: false,
|
||
|
|
configurable: true
|
||
|
|
});
|
||
|
|
Object.defineProperty(DevicePixelContentBoxBinding.prototype, "bitmapSize", {
|
||
|
|
get: function () {
|
||
|
|
return size({
|
||
|
|
width: this.canvasElement.width,
|
||
|
|
height: this.canvasElement.height,
|
||
|
|
});
|
||
|
|
},
|
||
|
|
enumerable: false,
|
||
|
|
configurable: true
|
||
|
|
});
|
||
|
|
/**
|
||
|
|
* Use this function to change canvas element client size until binding is disposed
|
||
|
|
* @param clientSize New client size for bound HTMLCanvasElement
|
||
|
|
*/
|
||
|
|
DevicePixelContentBoxBinding.prototype.resizeCanvasElement = function (clientSize) {
|
||
|
|
this._canvasElementClientSize = size(clientSize);
|
||
|
|
this.canvasElement.style.width = "".concat(this._canvasElementClientSize.width, "px");
|
||
|
|
this.canvasElement.style.height = "".concat(this._canvasElementClientSize.height, "px");
|
||
|
|
this._invalidateBitmapSize();
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype.subscribeBitmapSizeChanged = function (listener) {
|
||
|
|
this._bitmapSizeChangedListeners.push(listener);
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype.unsubscribeBitmapSizeChanged = function (listener) {
|
||
|
|
this._bitmapSizeChangedListeners = this._bitmapSizeChangedListeners.filter(function (l) { return l !== listener; });
|
||
|
|
};
|
||
|
|
Object.defineProperty(DevicePixelContentBoxBinding.prototype, "suggestedBitmapSize", {
|
||
|
|
get: function () {
|
||
|
|
return this._suggestedBitmapSize;
|
||
|
|
},
|
||
|
|
enumerable: false,
|
||
|
|
configurable: true
|
||
|
|
});
|
||
|
|
DevicePixelContentBoxBinding.prototype.subscribeSuggestedBitmapSizeChanged = function (listener) {
|
||
|
|
this._suggestedBitmapSizeChangedListeners.push(listener);
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype.unsubscribeSuggestedBitmapSizeChanged = function (listener) {
|
||
|
|
this._suggestedBitmapSizeChangedListeners = this._suggestedBitmapSizeChangedListeners.filter(function (l) { return l !== listener; });
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype.applySuggestedBitmapSize = function () {
|
||
|
|
if (this._suggestedBitmapSize === null) {
|
||
|
|
// nothing to apply
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var oldSuggestedSize = this._suggestedBitmapSize;
|
||
|
|
this._suggestedBitmapSize = null;
|
||
|
|
this._resizeBitmap(oldSuggestedSize);
|
||
|
|
this._emitSuggestedBitmapSizeChanged(oldSuggestedSize, this._suggestedBitmapSize);
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype._resizeBitmap = function (newSize) {
|
||
|
|
var oldSize = this.bitmapSize;
|
||
|
|
if (equalSizes(oldSize, newSize)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.canvasElement.width = newSize.width;
|
||
|
|
this.canvasElement.height = newSize.height;
|
||
|
|
this._emitBitmapSizeChanged(oldSize, newSize);
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype._emitBitmapSizeChanged = function (oldSize, newSize) {
|
||
|
|
var _this = this;
|
||
|
|
this._bitmapSizeChangedListeners.forEach(function (listener) { return listener.call(_this, oldSize, newSize); });
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype._suggestNewBitmapSize = function (newSize) {
|
||
|
|
var oldSuggestedSize = this._suggestedBitmapSize;
|
||
|
|
var finalNewSize = size(this._transformBitmapSize(newSize, this._canvasElementClientSize));
|
||
|
|
var newSuggestedSize = equalSizes(this.bitmapSize, finalNewSize) ? null : finalNewSize;
|
||
|
|
if (oldSuggestedSize === null && newSuggestedSize === null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (oldSuggestedSize !== null && newSuggestedSize !== null
|
||
|
|
&& equalSizes(oldSuggestedSize, newSuggestedSize)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._suggestedBitmapSize = newSuggestedSize;
|
||
|
|
this._emitSuggestedBitmapSizeChanged(oldSuggestedSize, newSuggestedSize);
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype._emitSuggestedBitmapSizeChanged = function (oldSize, newSize) {
|
||
|
|
var _this = this;
|
||
|
|
this._suggestedBitmapSizeChangedListeners.forEach(function (listener) { return listener.call(_this, oldSize, newSize); });
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype._chooseAndInitObserver = function () {
|
||
|
|
var _this = this;
|
||
|
|
if (!this._allowResizeObserver) {
|
||
|
|
this._initDevicePixelRatioObservable();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
isDevicePixelContentBoxSupported()
|
||
|
|
.then(function (isSupported) {
|
||
|
|
return isSupported ?
|
||
|
|
_this._initResizeObserver() :
|
||
|
|
_this._initDevicePixelRatioObservable();
|
||
|
|
});
|
||
|
|
};
|
||
|
|
// devicePixelRatio approach
|
||
|
|
DevicePixelContentBoxBinding.prototype._initDevicePixelRatioObservable = function () {
|
||
|
|
var _this = this;
|
||
|
|
if (this._canvasElement === null) {
|
||
|
|
// it looks like we are already dead
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var win = canvasElementWindow(this._canvasElement);
|
||
|
|
if (win === null) {
|
||
|
|
throw new Error('No window is associated with the canvas');
|
||
|
|
}
|
||
|
|
this._devicePixelRatioObservable = createDevicePixelRatioObservable(win);
|
||
|
|
this._devicePixelRatioObservable.subscribe(function () { return _this._invalidateBitmapSize(); });
|
||
|
|
this._invalidateBitmapSize();
|
||
|
|
};
|
||
|
|
DevicePixelContentBoxBinding.prototype._invalidateBitmapSize = function () {
|
||
|
|
var _a, _b;
|
||
|
|
if (this._canvasElement === null) {
|
||
|
|
// it looks like we are already dead
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var win = canvasElementWindow(this._canvasElement);
|
||
|
|
if (win === null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var ratio = (_b = (_a = this._devicePixelRatioObservable) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : win.devicePixelRatio;
|
||
|
|
var canvasRects = this._canvasElement.getClientRects();
|
||
|
|
var newSize =
|
||
|
|
// eslint-disable-next-line no-negated-condition
|
||
|
|
canvasRects[0] !== undefined ?
|
||
|
|
predictedBitmapSize(canvasRects[0], ratio) :
|
||
|
|
size({
|
||
|
|
width: this._canvasElementClientSize.width * ratio,
|
||
|
|
height: this._canvasElementClientSize.height * ratio,
|
||
|
|
});
|
||
|
|
this._suggestNewBitmapSize(newSize);
|
||
|
|
};
|
||
|
|
// ResizeObserver approach
|
||
|
|
DevicePixelContentBoxBinding.prototype._initResizeObserver = function () {
|
||
|
|
var _this = this;
|
||
|
|
if (this._canvasElement === null) {
|
||
|
|
// it looks like we are already dead
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._canvasElementResizeObserver = new ResizeObserver(function (entries) {
|
||
|
|
var entry = entries.find(function (entry) { return entry.target === _this._canvasElement; });
|
||
|
|
if (!entry || !entry.devicePixelContentBoxSize || !entry.devicePixelContentBoxSize[0]) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var entrySize = entry.devicePixelContentBoxSize[0];
|
||
|
|
var newSize = size({
|
||
|
|
width: entrySize.inlineSize,
|
||
|
|
height: entrySize.blockSize,
|
||
|
|
});
|
||
|
|
_this._suggestNewBitmapSize(newSize);
|
||
|
|
});
|
||
|
|
this._canvasElementResizeObserver.observe(this._canvasElement, { box: 'device-pixel-content-box' });
|
||
|
|
};
|
||
|
|
return DevicePixelContentBoxBinding;
|
||
|
|
}());
|
||
|
|
export function bindTo(canvasElement, target) {
|
||
|
|
if (target.type === 'device-pixel-content-box') {
|
||
|
|
return new DevicePixelContentBoxBinding(canvasElement, target.transform, target.options);
|
||
|
|
}
|
||
|
|
throw new Error('Unsupported binding target');
|
||
|
|
}
|
||
|
|
function canvasElementWindow(canvasElement) {
|
||
|
|
// According to DOM Level 2 Core specification, ownerDocument should never be null for HTMLCanvasElement
|
||
|
|
// see https://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#node-ownerDoc
|
||
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||
|
|
return canvasElement.ownerDocument.defaultView;
|
||
|
|
}
|
||
|
|
function isDevicePixelContentBoxSupported() {
|
||
|
|
return new Promise(function (resolve) {
|
||
|
|
var ro = new ResizeObserver(function (entries) {
|
||
|
|
resolve(entries.every(function (entry) { return 'devicePixelContentBoxSize' in entry; }));
|
||
|
|
ro.disconnect();
|
||
|
|
});
|
||
|
|
ro.observe(document.body, { box: 'device-pixel-content-box' });
|
||
|
|
})
|
||
|
|
.catch(function () { return false; });
|
||
|
|
}
|
||
|
|
function predictedBitmapSize(canvasRect, ratio) {
|
||
|
|
return size({
|
||
|
|
width: Math.round(canvasRect.left * ratio + canvasRect.width * ratio) -
|
||
|
|
Math.round(canvasRect.left * ratio),
|
||
|
|
height: Math.round(canvasRect.top * ratio + canvasRect.height * ratio) -
|
||
|
|
Math.round(canvasRect.top * ratio),
|
||
|
|
});
|
||
|
|
}
|