Skip to content

Commit

Permalink
Fix issues with rendering of rounded borders:
Browse files Browse the repository at this point in the history
 - Box path calculation has been improved to ensure a correct (squared) path when the shrink is greater than the radius.
 - Rewrote border renderer to use only fills with "eofill" subpaths to make the cut-outs for dashed/dotted/double styles.
 - Dashes are now centered along the edge like WebKit does.
 - Removed all logic around VML stroke as it is no longer used.
 - Flattened the getBoxPath method signature to avoid unnecessary transient object creation.
Fixes issue lojjic#11
  • Loading branch information
Jason Johnston committed Nov 13, 2011
1 parent 1c3fb0d commit 47dc9d8
Show file tree
Hide file tree
Showing 8 changed files with 416 additions and 233 deletions.
6 changes: 2 additions & 4 deletions sources/BackgroundRenderer.js
Expand Up @@ -50,8 +50,7 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {

shape.setSize( bounds.w, bounds.h );
shape.setAttrs(
'stroked', false,
'path', this.getBoxPath( 0, 2 )
'path', this.getBoxPath( 0, 0, 0, 0, 2 )
);
shape.setFillAttrs( 'color', color.colorValue( el ) );
alpha = color.alpha();
Expand Down Expand Up @@ -84,8 +83,7 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
shape = this.getShape( 'bgImage' + i, this.shapeZIndex + ( .5 - i / 1000 ) );

shape.setAttrs(
'stroked', false,
'path', this.getBoxPath( 0, 2 )
'path', this.getBoxPath( 0, 0, 0, 0, 2 )
);
shape.setSize( w, h );

Expand Down
1 change: 0 additions & 1 deletion sources/BorderImageRenderer.js
Expand Up @@ -100,7 +100,6 @@ PIE.BorderImageRenderer = PIE.RendererBase.newRenderer( {
var shape = this.getShape( 'borderImage' + name, this.shapeZIndex );
shape.tagName = 'rect';
shape.setAttrs(
'stroked', false,
'filled', false
);
return shape;
Expand Down
371 changes: 197 additions & 174 deletions sources/BorderRenderer.js

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions sources/BoxShadowOutsetRenderer.js
Expand Up @@ -50,7 +50,7 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
// round the corners of the expanded shadow shape rather than squaring them off.
radii = PIE.BorderRadiusStyleInfo.ALL_ZERO;
}
path = me.getBoxPath( { t: shrink, r: shrink, b: shrink, l: shrink }, 2, radii );
path = me.getBoxPath( shrink, shrink, shrink, shrink, 2, radii );

// Create the shape object
shape = me.getShape( 'shadow' + i, me.shapeZIndex + ( .5 - i / 1000 ) );
Expand Down Expand Up @@ -99,8 +99,6 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
}

shape.setAttrs(
'stroked', false,
'filled', true,
'path', path
);
shape.setFillAttrs( 'color', color );
Expand Down
14 changes: 7 additions & 7 deletions sources/ImgRenderer.js
Expand Up @@ -46,13 +46,13 @@ PIE.ImgRenderer = PIE.RendererBase.newRenderer( {
}

shape.setAttrs(
'stroked', false,
'path', this.getBoxPath( {
t: round( borderWidths['t'].pixels( el ) + getLength( cs.paddingTop ).pixels( el ) ),
r: round( borderWidths['r'].pixels( el ) + getLength( cs.paddingRight ).pixels( el ) ),
b: round( borderWidths['b'].pixels( el ) + getLength( cs.paddingBottom ).pixels( el ) ),
l: round( borderWidths['l'].pixels( el ) + getLength( cs.paddingLeft ).pixels( el ) )
}, 2 )
'path', this.getBoxPath(
round( borderWidths['t'].pixels( el ) + getLength( cs.paddingTop ).pixels( el ) ),
round( borderWidths['r'].pixels( el ) + getLength( cs.paddingRight ).pixels( el ) ),
round( borderWidths['b'].pixels( el ) + getLength( cs.paddingBottom ).pixels( el ) ),
round( borderWidths['l'].pixels( el ) + getLength( cs.paddingLeft ).pixels( el ) ),
2
)
);
shape.setFillAttrs(
'type', 'frame',
Expand Down
122 changes: 83 additions & 39 deletions sources/RendererBase_IE678.js
Expand Up @@ -95,59 +95,103 @@ PIE.merge(PIE.RendererBase, {

/**
* Return the VML path string for the element's background box, with corners rounded.
* @param {Object.<{t:number, r:number, b:number, l:number}>} shrink - if present, specifies number of
* pixels to shrink the box path inward from the element's four sides.
* @param {number=} mult If specified, all coordinates will be multiplied by this number
* @param {number} shrinkT - number of pixels to shrink the box path inward from the element's top side.
* @param {number} shrinkR - number of pixels to shrink the box path inward from the element's right side.
* @param {number} shrinkB - number of pixels to shrink the box path inward from the element's bottom side.
* @param {number} shrinkL - number of pixels to shrink the box path inward from the element's left side.
* @param {number} mult All coordinates will be multiplied by this number
* @param {Object=} radii If specified, this will be used for the corner radii instead of the properties
* from this renderer's borderRadiusInfo object.
* @return {string} the VML path
*/
getBoxPath: function( shrink, mult, radii ) {
mult = mult || 1;
getBoxPath: function( shrinkT, shrinkR, shrinkB, shrinkL, mult, radii ) {
var str, coords, bounds, w, h,
M = Math, floor = M.floor, ceil = M.ceil;

var r, str,
bounds = this.boundsInfo.getBounds(),
w = bounds.w * mult,
h = bounds.h * mult,
radInfo = this.styleInfos.borderRadiusInfo,
floor = Math.floor, ceil = Math.ceil,
shrinkT = shrink ? shrink.t * mult : 0,
shrinkR = shrink ? shrink.r * mult : 0,
shrinkB = shrink ? shrink.b * mult : 0,
shrinkL = shrink ? shrink.l * mult : 0,
tlX, tlY, trX, trY, brX, brY, blX, blY;

if( radii || radInfo.isActive() ) {
r = this.getRadiiPixels( radii || radInfo.getProps() );

tlX = r.x['tl'] * mult;
tlY = r.y['tl'] * mult;
trX = r.x['tr'] * mult;
trY = r.y['tr'] * mult;
brX = r.x['br'] * mult;
brY = r.y['br'] * mult;
blX = r.x['bl'] * mult;
blY = r.y['bl'] * mult;

str = 'm' + floor( shrinkL ) + ',' + floor( tlY ) +
'qy' + floor( tlX ) + ',' + floor( shrinkT ) +
'l' + ceil( w - trX ) + ',' + floor( shrinkT ) +
'qx' + ceil( w - shrinkR ) + ',' + floor( trY ) +
'l' + ceil( w - shrinkR ) + ',' + ceil( h - brY ) +
'qy' + ceil( w - brX ) + ',' + ceil( h - shrinkB ) +
'l' + floor( blX ) + ',' + ceil( h - shrinkB ) +
'qx' + floor( shrinkL ) + ',' + ceil( h - blY ) + ' x e';
if( radii || this.styleInfos.borderRadiusInfo.isActive() ) {
// rounded box path
coords = this.getBoxPathCoords( shrinkT, shrinkR, shrinkB, shrinkL, mult, radii );
str = 'm' + coords[ 0 ] + ',' + coords[ 1 ] +
'qy' + coords[ 2 ] + ',' + coords[ 3 ] +
'l' + coords[ 4 ] + ',' + coords[ 5 ] +
'qx' + coords[ 6 ] + ',' + coords[ 7 ] +
'l' + coords[ 8 ] + ',' + coords[ 9 ] +
'qy' + coords[ 10 ] + ',' + coords[ 11 ] +
'l' + coords[ 12 ] + ',' + coords[ 13 ] +
'qx' + coords[ 14 ] + ',' + coords[ 15 ] +
'x';
} else {
// simplified path for non-rounded box
// skip most of the heavy math for a non-rounded box
bounds = this.boundsInfo.getBounds();
w = bounds.w * mult;
h = bounds.h * mult;
shrinkT *= mult;
shrinkR *= mult;
shrinkB *= mult;
shrinkL *= mult;
str = 'm' + floor( shrinkL ) + ',' + floor( shrinkT ) +
'l' + ceil( w - shrinkR ) + ',' + floor( shrinkT ) +
'l' + ceil( w - shrinkR ) + ',' + ceil( h - shrinkB ) +
'l' + floor( shrinkL ) + ',' + ceil( h - shrinkB ) +
'xe';
'x';
}
return str;
},

/**
* Return the VML coordinates for all the vertices in the rounded box path.
* @param {number} shrinkT - number of pixels to shrink the box path inward from the element's top side.
* @param {number} shrinkR - number of pixels to shrink the box path inward from the element's right side.
* @param {number} shrinkB - number of pixels to shrink the box path inward from the element's bottom side.
* @param {number} shrinkL - number of pixels to shrink the box path inward from the element's left side.
* @param {number=} mult If specified, all coordinates will be multiplied by this number
* @param {Object=} radii If specified, this will be used for the corner radii instead of the properties
* from this renderer's borderRadiusInfo object.
* @return {Array.<number>} all the coordinates going clockwise, starting with the top-left corner's lower vertex
*/
getBoxPathCoords: function( shrinkT, shrinkR, shrinkB, shrinkL, mult, radii ) {
var bounds = this.boundsInfo.getBounds(),
w = bounds.w * mult,
h = bounds.h * mult,
M = Math,
floor = M.floor, ceil = M.ceil,
max = M.max, min = M.min,

r = this.getRadiiPixels( radii || this.styleInfos.borderRadiusInfo.getProps() ),
tlRadiusX = r.x['tl'] * mult,
tlRadiusY = r.y['tl'] * mult,
trRadiusX = r.x['tr'] * mult,
trRadiusY = r.y['tr'] * mult,
brRadiusX = r.x['br'] * mult,
brRadiusY = r.y['br'] * mult,
blRadiusX = r.x['bl'] * mult,
blRadiusY = r.y['bl'] * mult;

shrinkT *= mult;
shrinkR *= mult;
shrinkB *= mult;
shrinkL *= mult;

return [
floor( shrinkL ), // top-left lower x
floor( min( max( tlRadiusY, shrinkT ), h - shrinkB ) ), // top-left lower y
floor( min( max( tlRadiusX, shrinkL ), w - shrinkR ) ), // top-left upper x
floor( shrinkT ), // top-left upper y
ceil( max( shrinkL, w - max( trRadiusX, shrinkR ) ) ), // top-right upper x
floor( shrinkT ), // top-right upper y
ceil( w - shrinkR ), // top-right lower x
floor( min( max( trRadiusY, shrinkT ), h - shrinkB ) ), // top-right lower y
ceil( w - shrinkR ), // bottom-right upper x
ceil( max( shrinkT, h - max( brRadiusY, shrinkB ) ) ), // bottom-right upper y
ceil( max( shrinkL, w - max( brRadiusX, shrinkR ) ) ), // bottom-right lower x
ceil( h - shrinkB ), // bottom-right lower y
floor( min( max( blRadiusX, shrinkL ), w - shrinkR ) ), // bottom-left lower x
ceil( h - shrinkB ), // bottom-left lower y
floor( shrinkL ), // bottom-left upper x
ceil( max( shrinkT, h - max( blRadiusY, shrinkB ) ) ) // bottom-left upper y
];
},


/**
* Hide the actual border of the element. In IE7 and up we can just set its color to transparent;
Expand Down
6 changes: 1 addition & 5 deletions sources/VmlShape.js
Expand Up @@ -62,7 +62,7 @@ PIE.VmlShape = (function() {
VmlShape.prototype = {
behaviorStyle: 'behavior:url(#default#VML);',
defaultStyles: 'position:absolute;top:0px;left:0px;',
defaultAttrs: 'coordorigin="1,1" ',
defaultAttrs: 'coordorigin="1,1" stroked="false" ',
tagName: 'shape',
mightBeRendered: 0,

Expand All @@ -74,7 +74,6 @@ PIE.VmlShape = (function() {
setAttrs: createSetter( '' ),
setStyles: createSetter( 'style' ),
setFillAttrs: createSetter( 'fill' ),
setStrokeAttrs: createSetter( 'stroke' ),
setImageDataAttrs: createSetter( 'imagedata' ),

setSize: function( w, h ) {
Expand Down Expand Up @@ -104,8 +103,6 @@ PIE.VmlShape = (function() {
var m,
me = this,
tag = me.tagName,
fill = 'fill',
stroke = 'stroke',
tagStart = '<v:',
subElEnd = ' style="' + me.behaviorStyle + '" />';

Expand Down Expand Up @@ -135,7 +132,6 @@ PIE.VmlShape = (function() {
m.push( '>' );

pushElement( 'fill' );
pushElement( 'stroke' );
pushElement( 'imagedata' );

m.push( '</v:' + tag + '>' );
Expand Down
125 changes: 125 additions & 0 deletions tests/border-radius-tests.html
@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html>
<head>

<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<title>Test cases for border-radius</title>

<style type="text/css">

html { -pie-load-path: "../build"; }

body {
font-size: 12px;
}

#tests {
padding: 2em;
}
#tests div {
width: 50%;
padding: 1.5em;
margin: 1em 0;
background: white;
white-space: pre;
behavior: url(../build/PIE.htc);
position: relative;
background: #CCF;
}

</style>

</head>
<body>

<h1>Tests for border-radius</h1>

<div id="tests">
<h2>Uniform</h2>
<div>border-radius: 10px;</div>
<div>border-radius: 1em;</div>
<div>border-radius: 20%;</div>

<h2>Different per corner</h2>
<div>border-radius: 5px 10px 15px 20px;</div>
<div>border-radius: 0.5em 1em 1.5em 2em;</div>
<div>border-radius: 10% 20% 30% 40%;</div>

<h2>Single corner</h2>
<div>border-radius: 20px 0 0 0;</div>
<div>border-radius: 0 20px 0 0;</div>
<div>border-radius: 0 0 20px 0;</div>
<div>border-radius: 0 0 0 20px;</div>

<h2>X and Y</h2>
<div>border-radius: 30px / 10px;</div>
<div>border-radius: 3em / 1em;</div>
<div>border-radius: 20% / 50%;</div>
<div>border-radius: 5px 10px 15px 20px / 20px 15px 10px 5px;</div>

<h2>Uniform with border</h2>
<div>border-radius: 10px; border: 5px solid #000;</div>
<div>border-radius: 10px; border: 5px dotted #000;</div>
<div>border-radius: 10px; border: 5px dashed #000;</div>
<div>border-radius: 10px; border: 5px double #000;</div>
<!--
<div>border-radius: 10px; border: 5px groove #000;</div>
<div>border-radius: 10px; border: 5px ridge #000;</div>
<div>border-radius: 10px; border: 5px inset #000;</div>
<div>border-radius: 10px; border: 5px outset #000;</div>
-->

<h2>Differing border widths</h2>
<div>border-radius: 10px; border: solid #000; border-width: 6px 9px 6px 3px;</div>
<div>border-radius: 10px; border: dotted #000; border-width: 6px 9px 6px 3px;</div>
<div>border-radius: 10px; border: dashed #000; border-width: 6px 9px 6px 3px;</div>
<div>border-radius: 10px; border: double #000; border-width: 6px 9px 6px 3px;</div>

<h2>Border thicker than radius</h2>
<div>border-radius: 10px; border: 20px solid #000;</div>
<div>border-radius: 10px; border: 20px dotted #000;</div>
<div>border-radius: 10px; border: 20px dashed #000;</div>
<div>border-radius: 10px; border: 20px double #000;</div>
<div>border-radius: 10px; border: solid #000; border-width: 3px 20px 9px 15px;</div>
<div>border-radius: 10px; border: dotted #000; border-width: 3px 20px 9px 15px;</div>
<div>border-radius: 10px; border: dashed #000; border-width: 3px 20px 9px 15px;</div>
<div>border-radius: 10px; border: double #000; border-width: 3px 20px 9px 15px;</div>

<h2>Zero-width borders</h2>
<div>border-radius: 10px; border: 20px solid #000; border-top-width: 0;</div>
<div>border-radius: 10px; border: 20px dotted #000; border-right-width: 0;</div>
<div>border-radius: 10px; border: 20px dashed #000; border-bottom-width: 0;</div>
<div>border-radius: 10px; border: 20px double #000; border-left-width: 0;</div>

<h2>Differing border colors</h2>
<div>border-radius: 10px; border: 20px solid; border-color: red green blue orange;</div>
<div>border-radius: 10px; border: 20px dotted; border-color: red green blue orange;</div>
<div>border-radius: 10px; border: 20px dashed; border-color: red green blue orange;</div>
<div>border-radius: 10px; border: 20px double; border-color: red green blue orange;</div>

<h2>Differing border colors and styles</h2>
<div>border-radius: 10px; border: 20px; border-color: red green blue orange; border-style: solid dotted dashed double;</div>

<h2>Differing border colors and widths</h2>
<div>border-radius: 10px; border: solid; border-color: red green blue orange; border-width: 3px 20px 9px 15px;</div>
<div>border-radius: 10px; border: dotted; border-color: red green blue orange; border-width: 3px 20px 9px 15px;</div>
<div>border-radius: 10px; border: dashed; border-color: red green blue orange; border-width: 3px 20px 9px 15px;</div>
<div>border-radius: 10px; border: double; border-color: red green blue orange; border-width: 3px 20px 9px 15px;</div>

<h2>Differing border colors, styles, and widths</h2>
<div>border-radius: 10px; border-style: solid dotted dashed double; border-color: red green blue orange; border-width: 3px 20px 9px 15px;</div>
</div>

<script type="text/javascript">
(function() {
var divs= document.getElementById("tests").getElementsByTagName("div"),
i = 0, len = divs.length, css;
for(; i < len; i++) {
divs[i].style.cssText += divs[i].firstChild.nodeValue;
}
})();
</script>


</body>
</html>

0 comments on commit 47dc9d8

Please sign in to comment.