mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Add support for CSS 'object-fit' property
This commit is contained in:
parent
6020386bbe
commit
ef09e7ae86
@ -41,6 +41,7 @@ import {listStyleImage} from './property-descriptors/list-style-image';
|
|||||||
import {listStylePosition} from './property-descriptors/list-style-position';
|
import {listStylePosition} from './property-descriptors/list-style-position';
|
||||||
import {listStyleType} from './property-descriptors/list-style-type';
|
import {listStyleType} from './property-descriptors/list-style-type';
|
||||||
import {marginBottom, marginLeft, marginRight, marginTop} from './property-descriptors/margin';
|
import {marginBottom, marginLeft, marginRight, marginTop} from './property-descriptors/margin';
|
||||||
|
import {objectFit} from './property-descriptors/object-fit';
|
||||||
import {overflow, OVERFLOW} from './property-descriptors/overflow';
|
import {overflow, OVERFLOW} from './property-descriptors/overflow';
|
||||||
import {overflowWrap} from './property-descriptors/overflow-wrap';
|
import {overflowWrap} from './property-descriptors/overflow-wrap';
|
||||||
import {paddingBottom, paddingLeft, paddingRight, paddingTop} from './property-descriptors/padding';
|
import {paddingBottom, paddingLeft, paddingRight, paddingTop} from './property-descriptors/padding';
|
||||||
@ -126,6 +127,7 @@ export class CSSParsedDeclaration {
|
|||||||
marginRight: CSSValue;
|
marginRight: CSSValue;
|
||||||
marginBottom: CSSValue;
|
marginBottom: CSSValue;
|
||||||
marginLeft: CSSValue;
|
marginLeft: CSSValue;
|
||||||
|
objectFit: ReturnType<typeof objectFit.parse>;
|
||||||
opacity: ReturnType<typeof opacity.parse>;
|
opacity: ReturnType<typeof opacity.parse>;
|
||||||
overflowX: OVERFLOW;
|
overflowX: OVERFLOW;
|
||||||
overflowY: OVERFLOW;
|
overflowY: OVERFLOW;
|
||||||
@ -194,6 +196,7 @@ export class CSSParsedDeclaration {
|
|||||||
this.marginRight = parse(context, marginRight, declaration.marginRight);
|
this.marginRight = parse(context, marginRight, declaration.marginRight);
|
||||||
this.marginBottom = parse(context, marginBottom, declaration.marginBottom);
|
this.marginBottom = parse(context, marginBottom, declaration.marginBottom);
|
||||||
this.marginLeft = parse(context, marginLeft, declaration.marginLeft);
|
this.marginLeft = parse(context, marginLeft, declaration.marginLeft);
|
||||||
|
this.objectFit = parse(context, objectFit, declaration.objectFit);
|
||||||
this.opacity = parse(context, opacity, declaration.opacity);
|
this.opacity = parse(context, opacity, declaration.opacity);
|
||||||
const overflowTuple = parse(context, overflow, declaration.overflow);
|
const overflowTuple = parse(context, overflow, declaration.overflow);
|
||||||
this.overflowX = overflowTuple[0];
|
this.overflowX = overflowTuple[0];
|
||||||
|
31
src/css/property-descriptors/object-fit.ts
Normal file
31
src/css/property-descriptors/object-fit.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import {IPropertyIdentValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
|
||||||
|
import {Context} from '../../core/context';
|
||||||
|
export const enum OBJECT_FIT {
|
||||||
|
FILL = 'fill',
|
||||||
|
CONTAIN = 'contain',
|
||||||
|
COVER = 'cover',
|
||||||
|
NONE = 'none',
|
||||||
|
SCALE_DOWN = 'scale-down'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const objectFit: IPropertyIdentValueDescriptor<OBJECT_FIT> = {
|
||||||
|
name: 'object-fit',
|
||||||
|
initialValue: 'fill',
|
||||||
|
prefix: false,
|
||||||
|
type: PropertyDescriptorParsingType.IDENT_VALUE,
|
||||||
|
parse: (_context: Context, objectFit: string) => {
|
||||||
|
switch (objectFit) {
|
||||||
|
case 'contain':
|
||||||
|
return OBJECT_FIT.CONTAIN;
|
||||||
|
case 'cover':
|
||||||
|
return OBJECT_FIT.COVER;
|
||||||
|
case 'none':
|
||||||
|
return OBJECT_FIT.NONE;
|
||||||
|
case 'scale-down':
|
||||||
|
return OBJECT_FIT.SCALE_DOWN;
|
||||||
|
case 'fill':
|
||||||
|
default:
|
||||||
|
return OBJECT_FIT.FILL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -44,6 +44,7 @@ import {PAINT_ORDER_LAYER} from '../../css/property-descriptors/paint-order';
|
|||||||
import {Renderer} from '../renderer';
|
import {Renderer} from '../renderer';
|
||||||
import {Context} from '../../core/context';
|
import {Context} from '../../core/context';
|
||||||
import {DIRECTION} from '../../css/property-descriptors/direction';
|
import {DIRECTION} from '../../css/property-descriptors/direction';
|
||||||
|
import {calculateObjectFitBounds} from '../object-fit';
|
||||||
|
|
||||||
export type RenderConfigurations = RenderOptions & {
|
export type RenderConfigurations = RenderOptions & {
|
||||||
backgroundColor: Color | null;
|
backgroundColor: Color | null;
|
||||||
@ -274,18 +275,25 @@ export class CanvasRenderer extends Renderer {
|
|||||||
const box = contentBox(container);
|
const box = contentBox(container);
|
||||||
const path = calculatePaddingBoxPath(curves);
|
const path = calculatePaddingBoxPath(curves);
|
||||||
this.path(path);
|
this.path(path);
|
||||||
|
const {src, dest} = calculateObjectFitBounds(
|
||||||
|
container.styles.objectFit,
|
||||||
|
container.intrinsicWidth,
|
||||||
|
container.intrinsicHeight,
|
||||||
|
box.width,
|
||||||
|
box.height
|
||||||
|
);
|
||||||
this.ctx.save();
|
this.ctx.save();
|
||||||
this.ctx.clip();
|
this.ctx.clip();
|
||||||
this.ctx.drawImage(
|
this.ctx.drawImage(
|
||||||
image,
|
image,
|
||||||
0,
|
src.left,
|
||||||
0,
|
src.top,
|
||||||
container.intrinsicWidth,
|
src.width,
|
||||||
container.intrinsicHeight,
|
src.height,
|
||||||
box.left,
|
box.left + dest.left,
|
||||||
box.top,
|
box.top + dest.top,
|
||||||
box.width,
|
dest.width,
|
||||||
box.height
|
dest.height
|
||||||
);
|
);
|
||||||
this.ctx.restore();
|
this.ctx.restore();
|
||||||
}
|
}
|
||||||
|
117
src/render/object-fit.ts
Normal file
117
src/render/object-fit.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import {Bounds} from '../css/layout/bounds';
|
||||||
|
import {OBJECT_FIT} from '../css/property-descriptors/object-fit';
|
||||||
|
|
||||||
|
export const calculateObjectFitBounds = (
|
||||||
|
objectFit: OBJECT_FIT,
|
||||||
|
naturalWidth: number,
|
||||||
|
naturalHeight: number,
|
||||||
|
clientWidth: number,
|
||||||
|
clientHeight: number
|
||||||
|
): {src: Bounds; dest: Bounds} => {
|
||||||
|
const naturalRatio = naturalWidth / naturalHeight;
|
||||||
|
const clientRatio = clientWidth / clientHeight;
|
||||||
|
|
||||||
|
// 'object-position' is not currently supported, so use default value of 50% 50%.
|
||||||
|
const objectPositionX = 0.5;
|
||||||
|
const objectPositionY = 0.5;
|
||||||
|
|
||||||
|
let srcX: number,
|
||||||
|
srcY: number,
|
||||||
|
srcWidth: number,
|
||||||
|
srcHeight: number,
|
||||||
|
destX: number,
|
||||||
|
destY: number,
|
||||||
|
destWidth: number,
|
||||||
|
destHeight: number;
|
||||||
|
|
||||||
|
if (objectFit === OBJECT_FIT.SCALE_DOWN) {
|
||||||
|
objectFit =
|
||||||
|
naturalWidth < clientWidth && naturalHeight < clientHeight
|
||||||
|
? OBJECT_FIT.NONE // src is smaller on both axes
|
||||||
|
: OBJECT_FIT.CONTAIN; // at least one axes is greater or equal in size
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (objectFit) {
|
||||||
|
case OBJECT_FIT.CONTAIN:
|
||||||
|
srcX = 0;
|
||||||
|
srcY = 0;
|
||||||
|
srcWidth = naturalWidth;
|
||||||
|
srcHeight = naturalHeight;
|
||||||
|
if (naturalRatio < clientRatio) {
|
||||||
|
// snap to top/bottom
|
||||||
|
destY = 0;
|
||||||
|
destHeight = clientHeight;
|
||||||
|
destWidth = destHeight * naturalRatio;
|
||||||
|
destX = (clientWidth - destWidth) * objectPositionX;
|
||||||
|
} else {
|
||||||
|
// snap to left/right
|
||||||
|
destX = 0;
|
||||||
|
destWidth = clientWidth;
|
||||||
|
destHeight = destWidth / naturalRatio;
|
||||||
|
destY = (clientHeight - destHeight) * objectPositionY;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBJECT_FIT.COVER:
|
||||||
|
destX = 0;
|
||||||
|
destY = 0;
|
||||||
|
destWidth = clientWidth;
|
||||||
|
destHeight = clientHeight;
|
||||||
|
if (naturalRatio < clientRatio) {
|
||||||
|
// fill left/right
|
||||||
|
srcX = 0;
|
||||||
|
srcWidth = naturalWidth;
|
||||||
|
srcHeight = clientHeight * (naturalWidth / clientWidth);
|
||||||
|
srcY = (naturalHeight - srcHeight) * objectPositionY;
|
||||||
|
} else {
|
||||||
|
// fill top/bottom
|
||||||
|
srcY = 0;
|
||||||
|
srcHeight = naturalHeight;
|
||||||
|
srcWidth = clientWidth * (naturalHeight / clientHeight);
|
||||||
|
srcX = (naturalWidth - srcWidth) * objectPositionX;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBJECT_FIT.NONE:
|
||||||
|
if (naturalWidth < clientWidth) {
|
||||||
|
srcX = 0;
|
||||||
|
srcWidth = naturalWidth;
|
||||||
|
destX = (clientWidth - naturalWidth) * objectPositionX;
|
||||||
|
destWidth = naturalWidth;
|
||||||
|
} else {
|
||||||
|
srcX = (naturalWidth - clientWidth) * objectPositionX;
|
||||||
|
srcWidth = clientWidth;
|
||||||
|
destX = 0;
|
||||||
|
destWidth = clientWidth;
|
||||||
|
}
|
||||||
|
if (naturalHeight < clientHeight) {
|
||||||
|
srcY = 0;
|
||||||
|
srcHeight = naturalHeight;
|
||||||
|
destY = (clientHeight - naturalHeight) * objectPositionY;
|
||||||
|
destHeight = naturalHeight;
|
||||||
|
} else {
|
||||||
|
srcY = (naturalHeight - clientHeight) * objectPositionY;
|
||||||
|
srcHeight = clientHeight;
|
||||||
|
destY = 0;
|
||||||
|
destHeight = clientHeight;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBJECT_FIT.FILL:
|
||||||
|
default:
|
||||||
|
srcX = 0;
|
||||||
|
srcY = 0;
|
||||||
|
srcWidth = naturalWidth;
|
||||||
|
srcHeight = naturalHeight;
|
||||||
|
destX = 0;
|
||||||
|
destY = 0;
|
||||||
|
destWidth = clientWidth;
|
||||||
|
destHeight = clientHeight;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
src: new Bounds(srcX, srcY, srcWidth, srcHeight),
|
||||||
|
dest: new Bounds(destX, destY, destWidth, destHeight)
|
||||||
|
};
|
||||||
|
};
|
44
tests/reftests/images/images-object-fit.html
Normal file
44
tests/reftests/images/images-object-fit.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Image tests for 'object-fit'</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- same size -->
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: contain;width:75px;height:75px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: cover;width:75px;height:75px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: fill;width:75px;height:75px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: none;width:75px;height:75px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: scale-down;width:75px;height:75px;border:5px solid black;" />
|
||||||
|
|
||||||
|
<!-- larger size -->
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: contain;width:250px;height:150px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: cover;width:250px;height:150px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: fill;width:250px;height:150px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: none;width:250px;height:150px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: scale-down;width:250px;height:150px;border:5px solid black;" />
|
||||||
|
|
||||||
|
<!-- larger width, smaller height -->
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: contain;width:250px;height:50px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: cover;width:250px;height:50px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: fill;width:250px;height:50px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: none;width:250px;height:50px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: scale-down;width:250px;height:50px;border:5px solid black;" />
|
||||||
|
|
||||||
|
<!-- smaller width, larger height -->
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: contain;width:50px;height:150px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: cover;width:50px;height:150px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: fill;width:50px;height:150px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: none;width:50px;height:150px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: scale-down;width:50px;height:150px;border:5px solid black;" />
|
||||||
|
|
||||||
|
<!-- smaller size -->
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: contain;width:60px;height:40px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: cover;width:60px;height:40px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: fill;width:60px;height:40px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: none;width:60px;height:40px;border:5px solid black;" />
|
||||||
|
<img src="../../assets/image.jpg" style="object-fit: scale-down;width:60px;height:40px;border:5px solid black;" />
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user