diff --git a/src/Bounds.js b/src/Bounds.js index 83a9571..cb44b97 100644 --- a/src/Bounds.js +++ b/src/Bounds.js @@ -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, diff --git a/src/CanvasRenderer.js b/src/CanvasRenderer.js index d1416d3..b77f8cf 100644 --- a/src/CanvasRenderer.js +++ b/src/CanvasRenderer.js @@ -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) { diff --git a/src/NodeContainer.js b/src/NodeContainer.js index 8e22718..d0ea94b 100644 --- a/src/NodeContainer.js +++ b/src/NodeContainer.js @@ -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; 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 { + 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(); } diff --git a/src/parsing/background.js b/src/parsing/background.js index 170b42d..64b964f 100644 --- a/src/parsing/background.js +++ b/src/parsing/background.js @@ -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, @@ -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); } }; diff --git a/src/parsing/overflow.js b/src/parsing/overflow.js new file mode 100644 index 0000000..1b70b72 --- /dev/null +++ b/src/parsing/overflow.js @@ -0,0 +1,25 @@ +/* @flow */ +'use strict'; + +export const OVERFLOW = { + VISIBLE: 0, + HIDDEN: 1, + SCROLL: 2, + AUTO: 3 +}; + +export type Overflow = $Values; + +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; + } +}; diff --git a/tests/cases/overflow/overflow-transform.html b/tests/cases/overflow/overflow-transform.html new file mode 100644 index 0000000..3f08199 --- /dev/null +++ b/tests/cases/overflow/overflow-transform.html @@ -0,0 +1,49 @@ + + + + Overflow tests + + + + + + + +
+

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.

+
+
+
+ + + diff --git a/tests/cases/overflow.html b/tests/cases/overflow/overflow.html similarity index 59% rename from tests/cases/overflow.html rename to tests/cases/overflow/overflow.html index db6f086..4471697 100644 --- a/tests/cases/overflow.html +++ b/tests/cases/overflow/overflow.html @@ -3,7 +3,7 @@ Overflow tests - +