1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 25 /** 26 * @class 27 * This static class provides basic image support by using CSS and background 28 * images rather than <img> tags. 29 * 30 * @author Conrad Damon 31 * @author Ross Dargahi 32 * 33 * @private 34 */ 35 AjxImg = function() {}; 36 37 AjxImg.prototype = new Object; 38 AjxImg.prototype.constructor = null; 39 40 AjxImg._VIEWPORT_ID = "AjxImg_VP"; 41 42 AjxImg.DISABLED = true; 43 44 AjxImg.RE_COLOR = /^(.*?),color=(.*)$/; 45 46 /** 47 * This method will set the image for <i>parentEl</i>. <i>parentEl</i> should 48 * only contain this image and no other children 49 * 50 * @param parentEl the parent element for the image 51 * @param imageName the name of the image. The CSS class for the image will be "Img<imageName>". 52 * @param useParenEl if <code>true</code> will use the parent element as the root for the image and will not create an intermediate DIV 53 * @param _disabled if <code>true</code>, will append " ZDisabledImage" to the CSS class for the image, 54 * @param {array} classes array of class names to be applied to this image 55 * which will make the image partly transparent 56 * @param {string} altText alternative text for non-visual users 57 */ 58 AjxImg.setImage = 59 function(parentEl, imageName, useParentEl, _disabled, classes, altText) { 60 61 if (!parentEl) { return; } 62 63 classes = classes || []; 64 var origImageName = imageName; 65 var color, m = imageName && imageName.match(AjxImg.RE_COLOR); 66 if (m) { 67 imageName = m && m[1]; 68 color = m && m[2]; 69 } 70 71 var className = AjxImg.getClassForImage(imageName, _disabled); 72 if (useParentEl) { 73 classes.push(className); 74 parentEl.className = classes.join(" "); 75 return; 76 } 77 var id = parentEl.firstChild && parentEl.firstChild.id; 78 79 var overlayName = className+"Overlay"; 80 var maskName = className+"Mask"; 81 if (color && window.AjxImgData && AjxImgData[overlayName] && AjxImgData[maskName]) { 82 color = (color.match(/^\d$/) ? ZmOrganizer.COLOR_VALUES[color] : color) || 83 ZmOrganizer.COLOR_VALUES[ZmOrganizer.ORG_DEFAULT_COLOR]; 84 parentEl.innerHTML = AjxImg.getImageHtml({ 85 imageName: origImageName, 86 attrStr: id ? "id='"+id+"'" : null, 87 altText: altText, 88 disabled: _disabled 89 }); 90 return; 91 } 92 93 if (parentEl.firstChild == null || parentEl.firstChild.nodeName.toLowerCase() != "div") { 94 var html = [], i = 0; 95 html[i++] = "<div "; 96 if (id) { 97 html[i++] = " id='"; 98 html[i++] = id; 99 html[i++] = "' "; 100 } 101 if (className) { 102 classes.push(className); 103 } 104 html[i++] = AjxUtil.getClassAttr(classes); 105 html[i++] = ">"; 106 if (altText) { 107 html[i++] = "<div class='ScreenReaderOnly'>"; 108 html[i++] = AjxStringUtil.htmlEncode(altText); 109 html[i++] = "</div>"; 110 } 111 html[i++] = "</div>"; 112 parentEl.innerHTML = html.join(""); 113 return; 114 } else if (AjxEnv.isIE && !AjxEnv.isIE9up) { 115 parentEl.firstChild.innerHTML = ""; 116 } 117 if (className) { 118 classes.push(className); 119 } 120 parentEl.firstChild.className = classes.join(" "); 121 }; 122 123 AjxImg.setDisabledImage = function(parentEl, imageName, useParentEl, classes) { 124 return AjxImg.setImage(parentEl, imageName, useParentEl, true, classes); 125 }; 126 127 AjxImg.getClassForImage = 128 function(imageName, disabled) { 129 var className = imageName ? "Img" + imageName : ""; 130 if (disabled) className += " ZDisabledImage"; 131 return className; 132 }; 133 134 AjxImg.getImageClass = 135 function(parentEl) { 136 return parentEl.firstChild ? parentEl.firstChild.className : parentEl.className; 137 }; 138 139 AjxImg.getImageElement = 140 function(parentEl) { 141 return parentEl.firstChild ? parentEl.firstChild : parentEl; 142 }; 143 144 AjxImg.getParentElement = 145 function(imageEl) { 146 return imageEl.parentNode; 147 }; 148 149 AjxImg.GET_IMAGE_HTML_PARAMS = [ 150 "imageName", 151 "styles", 152 "attrStr", 153 "wrapInTable", 154 "disabled", 155 "classes", 156 "altText" 157 ]; 158 159 /** 160 * Returns the HTML needed to display the given image. 161 * 162 * @param {object} params hash of params: 163 * @param {string} imageName the image you want to render 164 * @param {string} styles optional style info (for example, "display:inline") 165 * @param {string} attrStr optional attributes (for example, "id=X748") 166 * @param {boolean} wrapInTable if true, wrap the HTML in a TABLE 167 * @param {boolean} disabled if true, show image as disabled 168 * @param {array} classes array of class names to be applied to this image 169 * @param {string} altText alternative text for non-visual users 170 * 171 * @return {string} the image string 172 */ 173 AjxImg.getImageHtml = 174 function() { 175 var params = Dwt.getParams(arguments, AjxImg.GET_IMAGE_HTML_PARAMS); 176 177 var imageName = params.imageName; 178 var styles = params.styles || ""; 179 var styleStr = styles ? " style='" + styles + "'" : ""; 180 var attrStr = params.attrStr ? " " + params.attrStr : ""; 181 var disabled = params.disabled; 182 var classes = params.classes || []; 183 var altText = params.altText; 184 185 var pre = params.wrapInTable ? "<table style='display:inline' cellpadding=0 cellspacing=0 border=0><tr><td align=center valign=bottom>" : ""; 186 var html = ""; 187 var post = params.wrapInTable ? "</td></tr></table>" : ""; 188 189 if (imageName) { 190 var color, m = imageName.match(AjxImg.RE_COLOR); 191 if (m) { 192 imageName = m && m[1]; 193 color = m && m[2]; 194 } 195 196 var className = AjxImg.getClassForImage(imageName, disabled); 197 var overlayName = className + "Overlay"; 198 var maskName = className + "Mask"; 199 if (color && window.AjxImgData && AjxImgData[overlayName] && AjxImgData[maskName]) { 200 color = (color.match(/^\d$/) ? ZmOrganizer.COLOR_VALUES[color] : color) || 201 ZmOrganizer.COLOR_VALUES[ZmOrganizer.ORG_DEFAULT_COLOR]; 202 203 var overlay = AjxImgData[overlayName], mask = AjxImgData[maskName]; 204 205 // we're creating IMG elements here, so we can use the alt attribute 206 if (altText) { 207 attrStr += " alt='" + AjxStringUtil.encodeQuotes(altText) + "'"; 208 } 209 210 if (AjxEnv.isIE && !AjxEnv.isIE9up) { 211 var size = [ 212 "width:", overlay.w, "px;", 213 "height:", overlay.h, "px;" 214 ].join(""); 215 var location = [ 216 "top:", mask.t, ";", 217 "left:", mask.l, ";" 218 ].join(""); 219 var clip = [ 220 'clip:rect(', 221 (-1 * mask.t) - 1, 'px,', 222 overlay.w - 1, 'px,', 223 (mask.t * -1) + overlay.h - 1, 'px,', 224 overlay.l, 'px);' 225 ].join(''); 226 var filter = 'filter:mask(color=' + color + ');'; 227 html = [ 228 // NOTE: Keep in sync with output of ImageMerger.java. 229 "<div class='IEImage' style='display:inline-block;zoom:1;position:relative;overflow:hidden;", size, styles, "' ", attrStr,">", 230 "<div class='IEImageMask' style='position:absolute;top:0px;left:0px;", size, "'>", 231 "<img src='", mask.ief, "?v=", window.cacheKillerVersion, "' border=0 style='position:absolute;", location, filter, "'>", 232 "</div>", 233 "<div class='IEImageOverlay ", overlayName, "' style='", size, "position:absolute;top:0;left:0;'></div>", 234 "</div>" 235 ].join(""); 236 } 237 else { 238 if (!overlay[color]) { 239 var width = overlay.w, height = overlay.h; 240 241 var canvas = document.createElement("CANVAS"); 242 canvas.width = width; 243 canvas.height = height; 244 245 var ctx = canvas.getContext("2d"); 246 247 ctx.save(); 248 ctx.clearRect(0,0,width,height); 249 250 ctx.save(); 251 var imgId = attrStr; 252 if (!imgId) { 253 imgId = Dwt.getNextId("CANVAS_IMG_"); //create an imgId in case we need to update the img.src for an element without an id 254 attrStr = " id='" + imgId + "'"; 255 } 256 else { 257 var match = attrStr.match(/id=[\"\']([^\"\']+)[\"\']+/); 258 if (match && match.length > 1) { 259 imgId = match[1]; //extract the ID value 260 } 261 AjxDebug.println(AjxDebug.TAG_ICON, "imgId = " + imgId); 262 } 263 var maskElement = document.getElementById(maskName); 264 var overlayElement = document.getElementById(overlayName); 265 if (!maskElement.complete || !overlayElement.complete) { 266 AjxDebug.println(AjxDebug.TAG_ICON, "mask status = " + maskElement.complete + " for " + imgId); 267 AjxDebug.println(AjxDebug.TAG_ICON, "overlay status = " + overlayElement.complete + " for " + imgId); 268 var maskImg = new Image(); 269 maskImg.onload = function() { 270 AjxDebug.println(AjxDebug.TAG_ICON, "mask image loaded"); 271 var overlayImg = new Image(); 272 overlayImg.onload = function() { 273 AjxImg._drawCanvasImage(ctx, maskImg, overlayImg, mask, overlay, color, width, height) 274 AjxDebug.println(AjxDebug.TAG_ICON, "overlay image loaded"); 275 var el = document.getElementById(imgId); 276 if (el) { 277 AjxDebug.println(AjxDebug.TAG_ICON, "element found for id = " + imgId); 278 el.src = canvas.toDataURL(); 279 overlay[color] = canvas.toDataURL(); //only save if successful 280 } 281 else { 282 AjxDebug.println(AjxDebug.TAG_ICON, "no element found for id = " + imgId); 283 } 284 } 285 overlayImg.src = document.getElementById(overlayName).src; 286 } 287 maskImg.src = document.getElementById(maskName).src; 288 } 289 else { 290 //image already downloaded 291 AjxImg._drawCanvasImage(ctx, maskElement, overlayElement, mask, overlay, color, width, height); 292 overlay[color] = canvas.toDataURL(); 293 } 294 } 295 296 html = [ 297 "<img src='", overlay[color], "'"," border=0 ", AjxUtil.getClassAttr(classes), styleStr, attrStr, ">" 298 ].join(""); 299 } 300 } 301 else { 302 classes.push("Img" + imageName); 303 html = [ 304 "<div ", AjxUtil.getClassAttr(classes), styleStr, attrStr, ">" 305 ]; 306 if (altText) { 307 // alt is invalid on DIVs, so use a hidden element 308 html.push( 309 "<span class='ScreenReaderOnly'>", 310 AjxStringUtil.htmlEncode(altText), 311 "</span>" 312 ); 313 }; 314 html.push("</div>"); 315 316 html = html.join(""); 317 } 318 } 319 else { 320 html = [ 321 "<div", styleStr, attrStr, ">" 322 ]; 323 if (altText) { 324 // alt is invalid on DIVs, so use a hidden element 325 html.push( 326 "<span class='ScreenReaderOnly'>", 327 AjxStringUtil.htmlEncode(altText), 328 "</span>" 329 ); 330 }; 331 html.push("</div>"); 332 html = html.join(""); 333 } 334 return pre || post ? [pre,html,post].join("") : html; 335 }; 336 337 /** 338 * Gets the "image" as an HTML string. 339 * 340 * @param imageName the image you want to render 341 * @param imageStyleStr optional style info (for example, "display:inline") 342 * @param attrStr optional attributes (for example, "id=X748") 343 * @param label the text that follows this image 344 * @param containerClassName class to use instead of the default inlineIcon class 345 * @return {string} the image string 346 */ 347 AjxImg.getImageSpanHtml = 348 function(imageName, imageStyleStr, attrStr, label, containerClassName) { 349 containerClassName = containerClassName || "inlineIcon"; 350 var html = [ 351 "<span style='white-space:nowrap'>", 352 "<span class='", 353 containerClassName, 354 "'>", 355 AjxImg.getImageHtml(imageName, imageStyleStr, attrStr), 356 (label || ""), 357 "</span>", 358 "</span>" 359 ]; 360 361 return html.join(""); 362 }; 363 364 /** 365 * Helper method to draw the image using both the mask image and the overlay image 366 * 367 * @param ctx {Object} canvas context 368 * @param maskImg {HtmlElement} mask image object 369 * @param overlayImg {HtmlElement} overlay image object 370 * @param mask {Object} mask object 371 * @param overlay {Object} overlay object 372 * @param color {String} color for fill 373 * @param width {int} width 374 * @param height {int} height 375 * 376 * @private 377 */ 378 AjxImg._drawCanvasImage = 379 function(ctx, maskImg, overlayImg, mask, overlay, color, width, height) { 380 ctx.drawImage(maskImg, mask.l, mask.t); 381 ctx.globalCompositeOperation = "source-out"; 382 ctx.fillStyle = color; 383 ctx.fillRect(0, 0, width, height); 384 ctx.restore(); 385 ctx.drawImage(overlayImg, overlay.l, overlay.t); 386 ctx.restore(); 387 }; 388