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 {listStyleType} from './property-descriptors/list-style-type';
|
||||
import {marginBottom, marginLeft, marginRight, marginTop} from './property-descriptors/margin';
|
||||
import {objectFit} from './property-descriptors/object-fit';
|
||||
import {overflow, OVERFLOW} from './property-descriptors/overflow';
|
||||
import {overflowWrap} from './property-descriptors/overflow-wrap';
|
||||
import {paddingBottom, paddingLeft, paddingRight, paddingTop} from './property-descriptors/padding';
|
||||
@ -126,6 +127,7 @@ export class CSSParsedDeclaration {
|
||||
marginRight: CSSValue;
|
||||
marginBottom: CSSValue;
|
||||
marginLeft: CSSValue;
|
||||
objectFit: ReturnType<typeof objectFit.parse>;
|
||||
opacity: ReturnType<typeof opacity.parse>;
|
||||
overflowX: OVERFLOW;
|
||||
overflowY: OVERFLOW;
|
||||
@ -194,6 +196,7 @@ export class CSSParsedDeclaration {
|
||||
this.marginRight = parse(context, marginRight, declaration.marginRight);
|
||||
this.marginBottom = parse(context, marginBottom, declaration.marginBottom);
|
||||
this.marginLeft = parse(context, marginLeft, declaration.marginLeft);
|
||||
this.objectFit = parse(context, objectFit, declaration.objectFit);
|
||||
this.opacity = parse(context, opacity, declaration.opacity);
|
||||
const overflowTuple = parse(context, overflow, declaration.overflow);
|
||||
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 {Context} from '../../core/context';
|
||||
import {DIRECTION} from '../../css/property-descriptors/direction';
|
||||
import {calculateObjectFitBounds} from '../object-fit';
|
||||
|
||||
export type RenderConfigurations = RenderOptions & {
|
||||
backgroundColor: Color | null;
|
||||
@ -274,18 +275,25 @@ export class CanvasRenderer extends Renderer {
|
||||
const box = contentBox(container);
|
||||
const path = calculatePaddingBoxPath(curves);
|
||||
this.path(path);
|
||||
const {src, dest} = calculateObjectFitBounds(
|
||||
container.styles.objectFit,
|
||||
container.intrinsicWidth,
|
||||
container.intrinsicHeight,
|
||||
box.width,
|
||||
box.height
|
||||
);
|
||||
this.ctx.save();
|
||||
this.ctx.clip();
|
||||
this.ctx.drawImage(
|
||||
image,
|
||||
0,
|
||||
0,
|
||||
container.intrinsicWidth,
|
||||
container.intrinsicHeight,
|
||||
box.left,
|
||||
box.top,
|
||||
box.width,
|
||||
box.height
|
||||
src.left,
|
||||
src.top,
|
||||
src.width,
|
||||
src.height,
|
||||
box.left + dest.left,
|
||||
box.top + dest.top,
|
||||
dest.width,
|
||||
dest.height
|
||||
);
|
||||
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