diff --git a/src/css/property-descriptors/overflow.ts b/src/css/property-descriptors/overflow.ts index 19ac544..ccb13a8 100644 --- a/src/css/property-descriptors/overflow.ts +++ b/src/css/property-descriptors/overflow.ts @@ -5,7 +5,8 @@ export enum OVERFLOW { VISIBLE = 0, HIDDEN = 1, SCROLL = 2, - AUTO = 3 + CLIP = 3, + AUTO = 4 } export const overflow: IPropertyListDescriptor = { @@ -20,6 +21,8 @@ export const overflow: IPropertyListDescriptor = { return OVERFLOW.HIDDEN; case 'scroll': return OVERFLOW.SCROLL; + case 'clip': + return OVERFLOW.CLIP; case 'auto': return OVERFLOW.AUTO; case 'visible': diff --git a/src/render/canvas/canvas-renderer.ts b/src/render/canvas/canvas-renderer.ts index 8ecedb5..48e7315 100644 --- a/src/render/canvas/canvas-renderer.ts +++ b/src/render/canvas/canvas-renderer.ts @@ -87,12 +87,12 @@ export class CanvasRenderer extends Renderer { ); } - applyEffects(effects: IElementEffect[], target: EffectTarget): void { + applyEffects(effects: IElementEffect[]): void { while (this._activeEffects.length) { this.popEffect(); } - effects.filter((effect) => contains(effect.target, target)).forEach((effect) => this.applyEffect(effect)); + effects.forEach((effect) => this.applyEffect(effect)); } applyEffect(effect: IElementEffect): void { @@ -293,7 +293,7 @@ export class CanvasRenderer extends Renderer { } async renderNodeContent(paint: ElementPaint): Promise { - this.applyEffects(paint.effects, EffectTarget.CONTENT); + this.applyEffects(paint.getEffects(EffectTarget.CONTENT)); const container = paint.container; const curves = paint.curves; const styles = container.styles; @@ -692,7 +692,7 @@ export class CanvasRenderer extends Renderer { } async renderNodeBackgroundAndBorders(paint: ElementPaint): Promise { - this.applyEffects(paint.effects, EffectTarget.BACKGROUND_BORDERS); + this.applyEffects(paint.getEffects(EffectTarget.BACKGROUND_BORDERS)); const styles = paint.container.styles; const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length; @@ -906,7 +906,7 @@ export class CanvasRenderer extends Renderer { const stack = parseStackingContexts(element); await this.renderStack(stack); - this.applyEffects([], EffectTarget.BACKGROUND_BORDERS); + this.applyEffects([]); return this.canvas; } } diff --git a/src/render/effects.ts b/src/render/effects.ts index 1bb4283..d7d1b95 100644 --- a/src/render/effects.ts +++ b/src/render/effects.ts @@ -20,36 +20,21 @@ export interface IElementEffect { export class TransformEffect implements IElementEffect { readonly type: EffectType = EffectType.TRANSFORM; readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT; - readonly offsetX: number; - readonly offsetY: number; - readonly matrix: Matrix; - constructor(offsetX: number, offsetY: number, matrix: Matrix) { - this.offsetX = offsetX; - this.offsetY = offsetY; - this.matrix = matrix; - } + constructor(readonly offsetX: number, readonly offsetY: number, readonly matrix: Matrix) {} } export class ClipEffect implements IElementEffect { readonly type: EffectType = EffectType.CLIP; - readonly target: number; - readonly path: Path[]; - constructor(path: Path[], target: EffectTarget) { - this.target = target; - this.path = path; - } + constructor(readonly path: Path[], readonly target: EffectTarget) {} } export class OpacityEffect implements IElementEffect { readonly type: EffectType = EffectType.OPACITY; readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT; - readonly opacity: number; - constructor(opacity: number) { - this.opacity = opacity; - } + constructor(readonly opacity: number) {} } export const isTransformEffect = (effect: IElementEffect): effect is TransformEffect => diff --git a/src/render/stacking-context.ts b/src/render/stacking-context.ts index 9da970c..c5ac088 100644 --- a/src/render/stacking-context.ts +++ b/src/render/stacking-context.ts @@ -1,13 +1,14 @@ import {ElementContainer, FLAGS} from '../dom/element-container'; import {contains} from '../core/bitwise'; import {BoundCurves, calculateBorderBoxPath, calculatePaddingBoxPath} from './bound-curves'; -import {ClipEffect, EffectTarget, IElementEffect, OpacityEffect, TransformEffect} from './effects'; +import {ClipEffect, EffectTarget, IElementEffect, isClipEffect, OpacityEffect, TransformEffect} from './effects'; import {OVERFLOW} from '../css/property-descriptors/overflow'; import {equalPath} from './path'; import {DISPLAY} from '../css/property-descriptors/display'; import {OLElementContainer} from '../dom/elements/ol-element-container'; import {LIElementContainer} from '../dom/elements/li-element-container'; import {createCounterText} from '../css/types/functions/counter'; +import {POSITION} from '../css/property-descriptors/position'; export class StackingContext { element: ElementPaint; @@ -32,27 +33,24 @@ export class StackingContext { } export class ElementPaint { - container: ElementContainer; - effects: IElementEffect[]; - curves: BoundCurves; + readonly effects: IElementEffect[] = []; + readonly curves: BoundCurves; listValue?: string; - constructor(element: ElementContainer, parentStack: IElementEffect[]) { - this.container = element; - this.effects = parentStack.slice(0); - this.curves = new BoundCurves(element); - if (element.styles.opacity < 1) { - this.effects.push(new OpacityEffect(element.styles.opacity)); + constructor(readonly container: ElementContainer, readonly parent: ElementPaint | null) { + this.curves = new BoundCurves(this.container); + if (this.container.styles.opacity < 1) { + this.effects.push(new OpacityEffect(this.container.styles.opacity)); } - if (element.styles.transform !== null) { - const offsetX = element.bounds.left + element.styles.transformOrigin[0].number; - const offsetY = element.bounds.top + element.styles.transformOrigin[1].number; - const matrix = element.styles.transform; + if (this.container.styles.transform !== null) { + const offsetX = this.container.bounds.left + this.container.styles.transformOrigin[0].number; + const offsetY = this.container.bounds.top + this.container.styles.transformOrigin[1].number; + const matrix = this.container.styles.transform; this.effects.push(new TransformEffect(offsetX, offsetY, matrix)); } - if (element.styles.overflowX !== OVERFLOW.VISIBLE) { + if (this.container.styles.overflowX !== OVERFLOW.VISIBLE) { const borderBox = calculateBorderBoxPath(this.curves); const paddingBox = calculatePaddingBoxPath(this.curves); @@ -65,16 +63,32 @@ export class ElementPaint { } } - getParentEffects(): IElementEffect[] { + getEffects(target: EffectTarget): IElementEffect[] { + let inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(this.container.styles.position) === -1; + let parent = this.parent; const effects = this.effects.slice(0); - if (this.container.styles.overflowX !== OVERFLOW.VISIBLE) { - const borderBox = calculateBorderBoxPath(this.curves); - const paddingBox = calculatePaddingBoxPath(this.curves); - if (!equalPath(borderBox, paddingBox)) { - effects.push(new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT)); + while (parent) { + const croplessEffects = parent.effects.filter((effect) => !isClipEffect(effect)); + if (inFlow || parent.container.styles.position !== POSITION.STATIC || !parent.parent) { + effects.unshift(...croplessEffects); + inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(parent.container.styles.position) === -1; + if (parent.container.styles.overflowX !== OVERFLOW.VISIBLE) { + const borderBox = calculateBorderBoxPath(parent.curves); + const paddingBox = calculatePaddingBoxPath(parent.curves); + if (!equalPath(borderBox, paddingBox)) { + effects.unshift( + new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT) + ); + } + } + } else { + effects.unshift(...croplessEffects); } + + parent = parent.parent; } - return effects; + + return effects.filter((effect) => contains(effect.target, target)); } } @@ -87,7 +101,7 @@ const parseStackTree = ( parent.container.elements.forEach((child) => { const treatAsRealStackingContext = contains(child.flags, FLAGS.CREATES_REAL_STACKING_CONTEXT); const createsStackingContext = contains(child.flags, FLAGS.CREATES_STACKING_CONTEXT); - const paintContainer = new ElementPaint(child, parent.getParentEffects()); + const paintContainer = new ElementPaint(child, parent); if (contains(child.styles.display, DISPLAY.LIST_ITEM)) { listItems.push(paintContainer); } @@ -182,7 +196,7 @@ const processListItems = (owner: ElementContainer, elements: ElementPaint[]) => }; export const parseStackingContexts = (container: ElementContainer): StackingContext => { - const paintContainer = new ElementPaint(container, []); + const paintContainer = new ElementPaint(container, null); const root = new StackingContext(paintContainer); const listItems: ElementPaint[] = []; parseStackTree(paintContainer, root, root, listItems); diff --git a/tests/reftests/overflow/overflow.html b/tests/reftests/overflow/overflow.html index 6a9f0e9..b9d1ea3 100644 --- a/tests/reftests/overflow/overflow.html +++ b/tests/reftests/overflow/overflow.html @@ -51,6 +51,9 @@ .scroll { overflow: scroll; } + .clip { + overflow: clip; + } .auto { overflow: auto; } @@ -70,6 +73,10 @@ scroll

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.

+
+ clip +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.

+
auto

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.

@@ -129,5 +136,17 @@
+ + + +