Begin implementing overflow clipping

This commit is contained in:
Niklas von Hertzen 2017-08-02 21:35:54 +08:00
parent 52a815a13f
commit f278ba4f22
7 changed files with 158 additions and 27 deletions

View File

@ -171,6 +171,24 @@ const createPathFromCurves = (
return path;
};
export const calculateBorderBoxPath = (curves: BoundCurves): Path => {
return [
curves.topLeftOuter,
curves.topRightOuter,
curves.bottomRightOuter,
curves.bottomLeftOuter
];
};
export const calculatePaddingBoxPath = (curves: BoundCurves): Path => {
return [
curves.topLeftInner,
curves.topRightInner,
curves.bottomRightInner,
curves.bottomLeftInner
];
};
export const parseBoundCurves = (
bounds: Bounds,
borders: Array<Border>,

View File

@ -16,7 +16,6 @@ import type {ImageStore} from './ImageLoader';
import type StackingContext from './StackingContext';
import {
BACKGROUND_CLIP,
BACKGROUND_ORIGIN,
calculateBackgroungPaintingArea,
calculateBackgroundPosition,
@ -25,10 +24,10 @@ import {
} from './parsing/background';
import {BORDER_STYLE} from './parsing/border';
import {
parseBoundCurves,
parsePathForBorder,
calculateContentBox,
calculatePaddingBox
calculatePaddingBox,
calculatePaddingBoxPath
} from './Bounds';
export type RenderOptions = {
@ -54,6 +53,15 @@ export default class CanvasRenderer {
}
renderNodeContent(container: NodeContainer) {
this.ctx.save();
const clipPaths = container.getClipPaths();
if (clipPaths.length) {
clipPaths.forEach(path => {
this.path(path);
this.ctx.clip();
});
}
if (container.textNodes.length) {
this.ctx.fillStyle = container.style.color.toString();
this.ctx.font = [
@ -92,17 +100,24 @@ export default class CanvasRenderer {
);
}
}
this.ctx.restore();
}
renderNodeBackgroundAndBorders(container: NodeContainer) {
const curvePoints = parseBoundCurves(
container.bounds,
container.style.border,
container.style.borderRadius
);
this.ctx.save();
if (container.parent) {
const clipPaths = container.parent.getClipPaths();
if (clipPaths.length) {
clipPaths.forEach(path => {
this.path(path);
this.ctx.clip();
});
}
}
const backgroungPaintingArea = calculateBackgroungPaintingArea(
curvePoints,
container.curvedBounds,
container.style.background.backgroundClip
);
this.path(backgroungPaintingArea);
@ -116,8 +131,9 @@ export default class CanvasRenderer {
this.renderBackgroundImage(container);
this.ctx.restore();
container.style.border.forEach((border, side) => {
this.renderBorder(border, side, curvePoints);
this.renderBorder(border, side, container.curvedBounds);
});
this.ctx.restore();
}
renderTextNode(textContainer: TextContainer) {

View File

@ -7,6 +7,7 @@ import type {BorderRadius} from './parsing/borderRadius';
import type {DisplayBit} from './parsing/display';
import type {Float} from './parsing/float';
import type {Font} from './parsing/font';
import type {Overflow} from './parsing/overflow';
import type {Padding} from './parsing/padding';
import type {Position} from './parsing/position';
import type {TextTransform} from './parsing/textTransform';
@ -14,7 +15,7 @@ import type {TextDecoration} from './parsing/textDecoration';
import type {Transform} from './parsing/transform';
import type {zIndex} from './parsing/zIndex';
import type {Bounds} from './Bounds';
import type {Bounds, BoundCurves, Path} from './Bounds';
import type ImageLoader from './ImageLoader';
import type TextContainer from './TextContainer';
@ -29,6 +30,7 @@ import {parseDisplay, DISPLAY} from './parsing/display';
import {parseCSSFloat, FLOAT} from './parsing/float';
import {parseFont} from './parsing/font';
import {parseLetterSpacing} from './parsing/letterSpacing';
import {parseOverflow, OVERFLOW} from './parsing/overflow';
import {parsePadding} from './parsing/padding';
import {parsePosition, POSITION} from './parsing/position';
import {parseTextDecoration} from './parsing/textDecoration';
@ -36,7 +38,7 @@ import {parseTextTransform} from './parsing/textTransform';
import {parseTransform} from './parsing/transform';
import {parseZIndex} from './parsing/zIndex';
import {parseBounds} from './Bounds';
import {parseBounds, parseBoundCurves, calculatePaddingBoxPath} from './Bounds';
type StyleDeclaration = {
background: Background,
@ -48,6 +50,7 @@ type StyleDeclaration = {
font: Font,
letterSpacing: number,
opacity: number,
overflow: Overflow,
padding: Padding,
position: Position,
textDecoration: TextDecoration,
@ -62,6 +65,7 @@ export default class NodeContainer {
style: StyleDeclaration;
textNodes: Array<TextContainer>;
bounds: Bounds;
curvedBounds: BoundCurves;
image: ?string;
constructor(node: HTMLElement, parent: ?NodeContainer, imageLoader: ImageLoader) {
@ -80,6 +84,7 @@ export default class NodeContainer {
font: parseFont(style),
letterSpacing: parseLetterSpacing(style.letterSpacing),
opacity: parseFloat(style.opacity),
overflow: parseOverflow(style.overflow),
padding: parsePadding(style),
position: parsePosition(style.position),
textDecoration: parseTextDecoration(style),
@ -97,12 +102,27 @@ export default class NodeContainer {
// $FlowFixMe
node.tagName === 'IMG' ? imageLoader.loadImage(node.currentSrc || node.src) : null;
this.bounds = parseBounds(node);
this.curvedBounds = parseBoundCurves(
this.bounds,
this.style.border,
this.style.borderRadius
);
if (__DEV__) {
this.name = `${node.tagName.toLowerCase()}${node.id
? `#${node.id}`
: ''}${node.className.split(' ').map(s => (s.length ? `.${s}` : '')).join('')}`;
}
}
getClipPaths(): Array<Path> {
const parentClips = this.parent ? this.parent.getClipPaths() : [];
const isClipped =
this.style.overflow === OVERFLOW.HIDDEN || this.style.overflow === OVERFLOW.SCROLL;
return isClipped
? parentClips.concat([calculatePaddingBoxPath(this.curvedBounds)])
: parentClips;
}
isInFlow(): boolean {
return this.isRootElement() && !this.isFloating() && !this.isAbsolutelyPositioned();
}

View File

@ -8,6 +8,7 @@ import Color from '../Color';
import Length from '../Length';
import Size from '../Size';
import Vector from '../Vector';
import {calculateBorderBoxPath, calculatePaddingBoxPath} from '../Bounds';
export type Background = {
backgroundImage: Array<BackgroundImage>,
@ -123,20 +124,10 @@ export const calculateBackgroungPaintingArea = (
// TODO support CONTENT_BOX
switch (clip) {
case BACKGROUND_CLIP.BORDER_BOX:
return [
curves.topLeftOuter,
curves.topRightOuter,
curves.bottomRightOuter,
curves.bottomLeftOuter
];
return calculateBorderBoxPath(curves);
case BACKGROUND_CLIP.PADDING_BOX:
default:
return [
curves.topLeftInner,
curves.topRightInner,
curves.bottomRightInner,
curves.bottomLeftInner
];
return calculatePaddingBoxPath(curves);
}
};

25
src/parsing/overflow.js Normal file
View File

@ -0,0 +1,25 @@
/* @flow */
'use strict';
export const OVERFLOW = {
VISIBLE: 0,
HIDDEN: 1,
SCROLL: 2,
AUTO: 3
};
export type Overflow = $Values<typeof OVERFLOW>;
export const parseOverflow = (overflow: string): Overflow => {
switch (overflow) {
case 'hidden':
return OVERFLOW.HIDDEN;
case 'scroll':
return OVERFLOW.SCROLL;
case 'auto':
return OVERFLOW.AUTO;
case 'visible':
default:
return OVERFLOW.VISIBLE;
}
};

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<title>Overflow tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<style>
#body-bg{
width: 300px;
overflow: hidden;
position: relative;
border:2px solid #000;
}
.img-1{
display: inline-block;
height: 617px;
left: -56.8px;
position: absolute;
resize: none;
top: -412.317px;
transform: rotate(3.5455rad);
width: 629px;
z-index: 2;
background: #f0f;
}
.img-2{
display: inline-block;
height: 620px;
left: -150px;
position: absolute;
resize: none;
top: 205px;
transform: rotate(3.5455rad);
width: 461px;background: #ff0000;
z-index: 1;
}
</style>
</head>
<body>
<div id="body-bg">
<p>Le Lorem Ipsum est simplement du faux texte employé dans la composition et la mise en page avant impression. Le Lorem Ipsum est le faux texte standard de l'imprimerie depuis les années 1500, quand un peintre anonyme assembla ensemble des morceaux de texte pour réaliser un livre spécimen de polices de texte. Il n'a pas fait que survivre cinq siècles, mais s'est aussi adapté à la bureautique informatique, sans que son contenu n'en soit modifié. Il a été popularisé dans les années 1960 grâce à la vente de feuilles Letraset contenant des passages du Lorem Ipsum, et, plus récemment, par son inclusion dans des applications de mise en page de texte, comme Aldus PageMaker. </p>
<div class="img-1"></div>
<div class="img-2"></div>
</div>
</body>
</html>

View File

@ -3,7 +3,7 @@
<head>
<title>Overflow tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../test.js"></script>
<script type="text/javascript" src="../../test.js"></script>
<style>
.small{
font-size:14px;
@ -29,15 +29,28 @@
</head>
<body>
<h1>Overflow: visible</h1>
<div> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
</div>
<h1>Overflow: hidden</h1>
<div style="overflow:hidden;float: right; height: 100px; border-radius: 100%; border-color: transparent;">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
</div>
</div>
<div style="overflow:hidden;">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
@ -47,6 +60,5 @@
</div>
</div>
</body>
</html>