forked from lojjic/PIE
/
IE9BorderImageRenderer.js
198 lines (172 loc) · 8.87 KB
/
IE9BorderImageRenderer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/**
* Renderer for border-image
* @constructor
* @param {Element} el The target element
* @param {Object} styleInfos The StyleInfo objects
* @param {PIE.RootRenderer} parent
*/
PIE.IE9BorderImageRenderer = PIE.RendererBase.newRenderer( {
REPEAT: 'repeat',
STRETCH: 'stretch',
ROUND: 'round',
bgLayerZIndex: 0,
needsUpdate: function() {
return this.styleInfos.borderImageInfo.changed();
},
isActive: function() {
return this.styleInfos.borderImageInfo.isActive();
},
draw: function() {
var me = this,
props = me.styleInfos.borderImageInfo.getProps(),
borderProps = me.styleInfos.borderInfo.getProps(),
bounds = me.boundsInfo.getBounds(),
repeat = props.repeat,
repeatH = repeat.h,
repeatV = repeat.v,
el = me.targetElement,
isAsync = 0;
PIE.Util.withImageSize( props.src, function( imgSize ) {
var elW = bounds.w,
elH = bounds.h,
imgW = imgSize.w,
imgH = imgSize.h,
// The image cannot be referenced as a URL directly in the SVG because IE9 throws a strange
// security exception (perhaps due to cross-origin policy within data URIs?) Therefore we
// work around this by converting the image data into a data URI itself using a transient
// canvas. This unfortunately requires the border-image src to be within the same domain,
// which isn't a limitation in true border-image, so we need to try and find a better fix.
imgSrc = me.imageToDataURI( props.src, imgW, imgH ),
REPEAT = me.REPEAT,
STRETCH = me.STRETCH,
ROUND = me.ROUND,
ceil = Math.ceil,
zero = PIE.getLength( '0' ),
widths = props.widths || ( borderProps ? borderProps.widths : { 't': zero, 'r': zero, 'b': zero, 'l': zero } ),
widthT = widths['t'].pixels( el ),
widthR = widths['r'].pixels( el ),
widthB = widths['b'].pixels( el ),
widthL = widths['l'].pixels( el ),
slices = props.slice,
sliceT = slices['t'].pixels( el ),
sliceR = slices['r'].pixels( el ),
sliceB = slices['b'].pixels( el ),
sliceL = slices['l'].pixels( el ),
centerW = elW - widthL - widthR,
middleH = elH - widthT - widthB,
imgCenterW = imgW - sliceL - sliceR,
imgMiddleH = imgH - sliceT - sliceB,
// Determine the size of each tile - 'round' is handled below
tileSizeT = repeatH === STRETCH ? centerW : imgCenterW * widthT / sliceT,
tileSizeR = repeatV === STRETCH ? middleH : imgMiddleH * widthR / sliceR,
tileSizeB = repeatH === STRETCH ? centerW : imgCenterW * widthB / sliceB,
tileSizeL = repeatV === STRETCH ? middleH : imgMiddleH * widthL / sliceL,
svg,
patterns = [],
rects = [],
i = 0;
// For 'round', subtract from each tile's size enough so that they fill the space a whole number of times
if (repeatH === ROUND) {
tileSizeT -= (tileSizeT - (centerW % tileSizeT || tileSizeT)) / ceil(centerW / tileSizeT);
tileSizeB -= (tileSizeB - (centerW % tileSizeB || tileSizeB)) / ceil(centerW / tileSizeB);
}
if (repeatV === ROUND) {
tileSizeR -= (tileSizeR - (middleH % tileSizeR || tileSizeR)) / ceil(middleH / tileSizeR);
tileSizeL -= (tileSizeL - (middleH % tileSizeL || tileSizeL)) / ceil(middleH / tileSizeL);
}
// Build the SVG for the border-image rendering. Add each piece as a pattern, which is then stretched
// or repeated as the fill of a rect of appropriate size.
svg = [
'<svg width="' + elW + '" height="' + elH + '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
];
function addImage( x, y, w, h, cropX, cropY, cropW, cropH, tileW, tileH ) {
patterns.push(
'<pattern patternUnits="userSpaceOnUse" id="pattern' + i + '" ' +
'x="' + (repeatH === REPEAT ? x + w / 2 - tileW / 2 : x) + '" ' +
'y="' + (repeatV === REPEAT ? y + h / 2 - tileH / 2 : y) + '" ' +
'width="' + tileW + '" height="' + tileH + '">' +
'<svg width="' + tileW + '" height="' + tileH + '" viewBox="' + cropX + ' ' + cropY + ' ' + cropW + ' ' + cropH + '" preserveAspectRatio="none">' +
'<image xlink:href="' + imgSrc + '" x="0" y="0" width="' + imgW + '" height="' + imgH + '" />' +
'</svg>' +
'</pattern>'
);
rects.push(
'<rect x="' + x + '" y="' + y + '" width="' + w + '" height="' + h + '" fill="url(#pattern' + i + ')" />'
);
i++;
}
addImage( 0, 0, widthL, widthT, 0, 0, sliceL, sliceT, widthL, widthT ); // top left
addImage( widthL, 0, centerW, widthT, sliceL, 0, imgCenterW, sliceT, tileSizeT, widthT ); // top center
addImage( elW - widthR, 0, widthR, widthT, imgW - sliceR, 0, sliceR, sliceT, widthR, widthT ); // top right
addImage( 0, widthT, widthL, middleH, 0, sliceT, sliceL, imgMiddleH, widthL, tileSizeL ); // middle left
if ( props.fill ) { // center fill
addImage( widthL, widthT, centerW, middleH, sliceL, sliceT, imgCenterW, imgMiddleH,
tileSizeT || tileSizeB || imgCenterW, tileSizeL || tileSizeR || imgMiddleH );
}
addImage( elW - widthR, widthT, widthR, middleH, imgW - sliceR, sliceT, sliceR, imgMiddleH, widthR, tileSizeR ); // middle right
addImage( 0, elH - widthB, widthL, widthB, 0, imgH - sliceB, sliceL, sliceB, widthL, widthB ); // bottom left
addImage( widthL, elH - widthB, centerW, widthB, sliceL, imgH - sliceB, imgCenterW, sliceB, tileSizeB, widthB ); // bottom center
addImage( elW - widthR, elH - widthB, widthR, widthB, imgW - sliceR, imgH - sliceB, sliceR, sliceB, widthR, widthB ); // bottom right
svg.push(
'<defs>' +
patterns.join('\n') +
'</defs>' +
rects.join('\n') +
'</svg>'
);
me.parent.setBackgroundLayer( me.bgLayerZIndex, 'url(data:image/svg+xml,' + escape( svg.join( '' ) ) + ') no-repeat border-box border-box' );
// If the border-image's src wasn't immediately available, the SVG for its background layer
// will have been created asynchronously after the main element's update has finished; we'll
// therefore need to force the root renderer to sync to the final background once finished.
if( isAsync ) {
me.parent.finishUpdate();
}
}, me );
isAsync = 1;
},
/**
* Convert a given image to a data URI
*/
imageToDataURI: (function() {
var uris = {};
return function( src, width, height ) {
var uri = uris[ src ],
image, canvas;
if ( !uri ) {
image = new Image();
canvas = doc.createElement( 'canvas' );
image.src = src;
canvas.width = width;
canvas.height = height;
canvas.getContext( '2d' ).drawImage( image, 0, 0 );
uri = uris[ src ] = canvas.toDataURL();
}
return uri;
}
})(),
prepareUpdate: function() {
if (this.isActive()) {
var me = this,
el = me.targetElement,
rs = el.runtimeStyle,
widths = me.styleInfos.borderImageInfo.getProps().widths;
// Force border-style to solid so it doesn't collapse
rs.borderStyle = 'solid';
// If widths specified in border-image shorthand, override border-width
if ( widths ) {
rs.borderTopWidth = widths['t'].pixels( el ) + 'px';
rs.borderRightWidth = widths['r'].pixels( el ) + 'px';
rs.borderBottomWidth = widths['b'].pixels( el ) + 'px';
rs.borderLeftWidth = widths['l'].pixels( el ) + 'px';
}
// Make the border transparent
me.hideBorder();
}
},
destroy: function() {
var me = this,
rs = me.targetElement.runtimeStyle;
me.parent.setBackgroundLayer( me.bgLayerZIndex );
rs.borderColor = rs.borderStyle = rs.borderWidth = '';
}
} );