diff --git a/src/css/index.ts b/src/css/index.ts index b338871ec..dad552953 100644 --- a/src/css/index.ts +++ b/src/css/index.ts @@ -80,6 +80,7 @@ import {paintOrder} from './property-descriptors/paint-order'; import {webkitTextStrokeColor} from './property-descriptors/webkit-text-stroke-color'; import {webkitTextStrokeWidth} from './property-descriptors/webkit-text-stroke-width'; import {Context} from '../core/context'; +import {objectFit} from './property-descriptors/object-fit'; export class CSSParsedDeclaration { animationDuration: ReturnType; @@ -130,6 +131,7 @@ export class CSSParsedDeclaration { overflowX: OVERFLOW; overflowY: OVERFLOW; overflowWrap: ReturnType; + objectFit: ReturnType; paddingTop: LengthPercentage; paddingRight: LengthPercentage; paddingBottom: LengthPercentage; @@ -199,6 +201,7 @@ export class CSSParsedDeclaration { this.overflowX = overflowTuple[0]; this.overflowY = overflowTuple[overflowTuple.length > 1 ? 1 : 0]; this.overflowWrap = parse(context, overflowWrap, declaration.overflowWrap); + this.objectFit = parse(context, objectFit, declaration.objectFit); this.paddingTop = parse(context, paddingTop, declaration.paddingTop); this.paddingRight = parse(context, paddingRight, declaration.paddingRight); this.paddingBottom = parse(context, paddingBottom, declaration.paddingBottom); diff --git a/src/css/property-descriptors/object-fit.ts b/src/css/property-descriptors/object-fit.ts new file mode 100644 index 000000000..2b0e84797 --- /dev/null +++ b/src/css/property-descriptors/object-fit.ts @@ -0,0 +1,58 @@ +import {Context} from '../../core/context'; +import {IPropertyIdentValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor'; +export const enum OBJECT_FIT { + CONTAIN = 'contain', + COVER = 'cover', + FILL = 'fill' +} + +export const objectFit: IPropertyIdentValueDescriptor = { + name: 'object-fit', + initialValue: 'fill', + prefix: false, + type: PropertyDescriptorParsingType.IDENT_VALUE, + parse: function (_context: Context, token: string) { + switch (token) { + case 'contain': + return OBJECT_FIT.CONTAIN; + case 'cover': + return OBJECT_FIT.COVER; + default: + return OBJECT_FIT.FILL; + } + } +}; + +export const getObjectFitSize = ( + contains: boolean /* true = contain, false = cover */, + containerWidth: number, + containerHeight: number, + width: number, + height: number +): { + width: number; + height: number; + x: number; + y: number; +} => { + const doRatio = width / height; + const cRatio = containerWidth / containerHeight; + let targetWidth = 0; + let targetHeight = 0; + const test = contains ? doRatio > cRatio : doRatio < cRatio; + + if (test) { + targetWidth = containerWidth; + targetHeight = targetWidth / doRatio; + } else { + targetHeight = containerHeight; + targetWidth = targetHeight * doRatio; + } + + return { + width: targetWidth, + height: targetHeight, + x: (containerWidth - targetWidth) / 2, + y: (containerHeight - targetHeight) / 2 + }; +}; diff --git a/src/dom/document-cloner.ts b/src/dom/document-cloner.ts index 08faa298d..d517694b4 100644 --- a/src/dom/document-cloner.ts +++ b/src/dom/document-cloner.ts @@ -24,6 +24,7 @@ import {CSSParsedCounterDeclaration, CSSParsedPseudoDeclaration} from '../css/in import {getQuote} from '../css/property-descriptors/quotes'; import {Context} from '../core/context'; import {DebuggerType, isDebugging} from '../core/debugger'; +import {getObjectFitSize, OBJECT_FIT, objectFit} from '../css/property-descriptors/object-fit'; export interface CloneOptions { ignoreElements?: (element: Element) => boolean; @@ -256,9 +257,38 @@ export class DocumentCloner { try { if (ctx) { - ctx.drawImage(video, 0, 0, canvas.width, canvas.height); - if (!this.options.allowTaint) { - ctx.getImageData(0, 0, canvas.width, canvas.height); + const elObjectFit = objectFit.parse( + this.context, + video.ownerDocument.defaultView?.getComputedStyle(video).objectFit ?? '' + ); + if (elObjectFit == OBJECT_FIT.CONTAIN || elObjectFit == OBJECT_FIT.COVER) { + const dimension = getObjectFitSize( + elObjectFit == OBJECT_FIT.CONTAIN, + video.offsetWidth, + video.offsetHeight, + video.videoWidth, + video.videoHeight + ); + + ctx.drawImage( + video, + 0, + 0, + video.videoWidth, + video.videoHeight, + dimension.x, + dimension.y, + dimension.width, + dimension.height + ); + if (!this.options.allowTaint) { + ctx.getImageData(0, 0, canvas.width, canvas.height); + } + } else { + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + if (!this.options.allowTaint) { + ctx.getImageData(0, 0, canvas.width, canvas.height); + } } } return canvas; diff --git a/src/render/canvas/canvas-renderer.ts b/src/render/canvas/canvas-renderer.ts index 6efb648bf..150f0d7cd 100644 --- a/src/render/canvas/canvas-renderer.ts +++ b/src/render/canvas/canvas-renderer.ts @@ -44,6 +44,7 @@ import {PAINT_ORDER_LAYER} from '../../css/property-descriptors/paint-order'; import {Renderer} from '../renderer'; import {Context} from '../../core/context'; import {DIRECTION} from '../../css/property-descriptors/direction'; +import {getObjectFitSize} from '../../css/property-descriptors/object-fit'; export type RenderConfigurations = RenderOptions & { backgroundColor: Color | null; @@ -270,23 +271,53 @@ export class CanvasRenderer extends Renderer { curves: BoundCurves, image: HTMLImageElement | HTMLCanvasElement ): void { + const objectFit = container.styles.objectFit; if (image && container.intrinsicWidth > 0 && container.intrinsicHeight > 0) { const box = contentBox(container); const path = calculatePaddingBoxPath(curves); this.path(path); this.ctx.save(); this.ctx.clip(); - this.ctx.drawImage( - image, - 0, - 0, - container.intrinsicWidth, - container.intrinsicHeight, - box.left, - box.top, - box.width, - box.height - ); + + let newWidth; + let newHeight; + const newX = box.left; + const newY = box.top; + + if (objectFit === 'contain' || objectFit === 'cover') { + const {width, height, x, y} = getObjectFitSize( + objectFit === 'contain', + box.width, + box.height, + image.width, + image.height + ); + newWidth = width; + newHeight = height; + this.ctx.drawImage( + image, + 0, + 0, + container.intrinsicWidth, + container.intrinsicHeight, + newX + x, + newY + y, + newWidth, + newHeight + ); + } else { + this.ctx.drawImage( + image, + 0, + 0, + container.intrinsicWidth, + container.intrinsicHeight, + box.left, + box.top, + box.width, + box.height + ); + } this.ctx.restore(); } } diff --git a/tests/reftests/images/images.html b/tests/reftests/images/images.html index acf3dd995..561b1d04d 100644 --- a/tests/reftests/images/images.html +++ b/tests/reftests/images/images.html @@ -27,6 +27,10 @@ + + + +