Skip to content

Commit eeb688c

Browse files
committed
feat: handles object-fit of img and video element
1 parent 6020386 commit eeb688c

File tree

6 files changed

+145
-15
lines changed

6 files changed

+145
-15
lines changed

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"workbench.colorCustomizations": {
3+
"activityBar.background": "#143402",
4+
"titleBar.activeBackground": "#1C4903",
5+
"titleBar.activeForeground": "#F0FEE9"
6+
}
7+
}

package-lock.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/css/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import {paintOrder} from './property-descriptors/paint-order';
8080
import {webkitTextStrokeColor} from './property-descriptors/webkit-text-stroke-color';
8181
import {webkitTextStrokeWidth} from './property-descriptors/webkit-text-stroke-width';
8282
import {Context} from '../core/context';
83+
import {objectFit} from './property-descriptors/object-fit';
8384

8485
export class CSSParsedDeclaration {
8586
animationDuration: ReturnType<typeof duration.parse>;
@@ -130,6 +131,7 @@ export class CSSParsedDeclaration {
130131
overflowX: OVERFLOW;
131132
overflowY: OVERFLOW;
132133
overflowWrap: ReturnType<typeof overflowWrap.parse>;
134+
objectFit: ReturnType<typeof objectFit.parse>;
133135
paddingTop: LengthPercentage;
134136
paddingRight: LengthPercentage;
135137
paddingBottom: LengthPercentage;
@@ -199,6 +201,7 @@ export class CSSParsedDeclaration {
199201
this.overflowX = overflowTuple[0];
200202
this.overflowY = overflowTuple[overflowTuple.length > 1 ? 1 : 0];
201203
this.overflowWrap = parse(context, overflowWrap, declaration.overflowWrap);
204+
this.objectFit = parse(context, objectFit, declaration.objectFit);
202205
this.paddingTop = parse(context, paddingTop, declaration.paddingTop);
203206
this.paddingRight = parse(context, paddingRight, declaration.paddingRight);
204207
this.paddingBottom = parse(context, paddingBottom, declaration.paddingBottom);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {Context} from '../../core/context';
2+
import {IPropertyIdentValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
3+
export const enum OBJECT_FIT {
4+
CONTAIN = 'contain',
5+
COVER = 'cover',
6+
FILL = 'fill'
7+
}
8+
9+
export const objectFit: IPropertyIdentValueDescriptor<OBJECT_FIT> = {
10+
name: 'object-fit',
11+
initialValue: 'fill',
12+
prefix: false,
13+
type: PropertyDescriptorParsingType.IDENT_VALUE,
14+
parse: function (_context: Context, token: string) {
15+
switch (token) {
16+
case 'contain':
17+
return OBJECT_FIT.CONTAIN;
18+
case 'cover':
19+
return OBJECT_FIT.COVER;
20+
default:
21+
return OBJECT_FIT.FILL;
22+
}
23+
}
24+
};
25+
26+
export const getObjectFitSize = (
27+
contains: boolean /* true = contain, false = cover */,
28+
containerWidth: number,
29+
containerHeight: number,
30+
width: number,
31+
height: number
32+
): {
33+
width: number;
34+
height: number;
35+
x: number;
36+
y: number;
37+
} => {
38+
const doRatio = width / height;
39+
const cRatio = containerWidth / containerHeight;
40+
let targetWidth = 0;
41+
let targetHeight = 0;
42+
const test = contains ? doRatio > cRatio : doRatio < cRatio;
43+
44+
if (test) {
45+
targetWidth = containerWidth;
46+
targetHeight = targetWidth / doRatio;
47+
} else {
48+
targetHeight = containerHeight;
49+
targetWidth = targetHeight * doRatio;
50+
}
51+
52+
return {
53+
width: targetWidth,
54+
height: targetHeight,
55+
x: (containerWidth - targetWidth) / 2,
56+
y: (containerHeight - targetHeight) / 2
57+
};
58+
};

src/dom/document-cloner.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {CSSParsedCounterDeclaration, CSSParsedPseudoDeclaration} from '../css/in
2424
import {getQuote} from '../css/property-descriptors/quotes';
2525
import {Context} from '../core/context';
2626
import {DebuggerType, isDebugging} from '../core/debugger';
27+
import {getObjectFitSize, OBJECT_FIT, objectFit} from '../css/property-descriptors/object-fit';
2728

2829
export interface CloneOptions {
2930
ignoreElements?: (element: Element) => boolean;
@@ -256,9 +257,38 @@ export class DocumentCloner {
256257

257258
try {
258259
if (ctx) {
259-
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
260-
if (!this.options.allowTaint) {
261-
ctx.getImageData(0, 0, canvas.width, canvas.height);
260+
const elObjectFit = objectFit.parse(
261+
this.context,
262+
video.ownerDocument.defaultView?.getComputedStyle(video).objectFit ?? ''
263+
);
264+
if (elObjectFit == OBJECT_FIT.CONTAIN || elObjectFit == OBJECT_FIT.COVER) {
265+
const dimension = getObjectFitSize(
266+
elObjectFit == OBJECT_FIT.CONTAIN,
267+
video.offsetWidth,
268+
video.offsetHeight,
269+
video.videoWidth,
270+
video.videoHeight
271+
);
272+
273+
ctx.drawImage(
274+
video,
275+
0,
276+
0,
277+
video.videoWidth,
278+
video.videoHeight,
279+
dimension.x,
280+
dimension.y,
281+
dimension.width,
282+
dimension.height
283+
);
284+
if (!this.options.allowTaint) {
285+
ctx.getImageData(0, 0, canvas.width, canvas.height);
286+
}
287+
} else {
288+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
289+
if (!this.options.allowTaint) {
290+
ctx.getImageData(0, 0, canvas.width, canvas.height);
291+
}
262292
}
263293
}
264294
return canvas;

src/render/canvas/canvas-renderer.ts

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {PAINT_ORDER_LAYER} from '../../css/property-descriptors/paint-order';
4444
import {Renderer} from '../renderer';
4545
import {Context} from '../../core/context';
4646
import {DIRECTION} from '../../css/property-descriptors/direction';
47+
import {getObjectFitSize} from '../../css/property-descriptors/object-fit';
4748

4849
export type RenderConfigurations = RenderOptions & {
4950
backgroundColor: Color | null;
@@ -270,23 +271,53 @@ export class CanvasRenderer extends Renderer {
270271
curves: BoundCurves,
271272
image: HTMLImageElement | HTMLCanvasElement
272273
): void {
274+
const objectFit = container.styles.objectFit;
273275
if (image && container.intrinsicWidth > 0 && container.intrinsicHeight > 0) {
274276
const box = contentBox(container);
275277
const path = calculatePaddingBoxPath(curves);
276278
this.path(path);
277279
this.ctx.save();
278280
this.ctx.clip();
279-
this.ctx.drawImage(
280-
image,
281-
0,
282-
0,
283-
container.intrinsicWidth,
284-
container.intrinsicHeight,
285-
box.left,
286-
box.top,
287-
box.width,
288-
box.height
289-
);
281+
282+
let newWidth;
283+
let newHeight;
284+
const newX = box.left;
285+
const newY = box.top;
286+
287+
if (objectFit === 'contain' || objectFit === 'cover') {
288+
const {width, height, x, y} = getObjectFitSize(
289+
objectFit === 'contain',
290+
box.width,
291+
box.height,
292+
image.width,
293+
image.height
294+
);
295+
newWidth = width;
296+
newHeight = height;
297+
this.ctx.drawImage(
298+
image,
299+
0,
300+
0,
301+
container.intrinsicWidth,
302+
container.intrinsicHeight,
303+
newX + x,
304+
newY + y,
305+
newWidth,
306+
newHeight
307+
);
308+
} else {
309+
this.ctx.drawImage(
310+
image,
311+
0,
312+
0,
313+
container.intrinsicWidth,
314+
container.intrinsicHeight,
315+
box.left,
316+
box.top,
317+
box.width,
318+
box.height
319+
);
320+
}
290321
this.ctx.restore();
291322
}
292323
}

0 commit comments

Comments
 (0)