![]() |
API 0.9.5
|
00001 /* 00002 * CGContextVML.j 00003 * AppKit 00004 * 00005 * Created by Francisco Tolmasky. 00006 * Copyright 2008, 280 North, Inc. 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 var VML_TRUTH_TABLE = [ "f", "t"], 00024 VML_LINECAP_TABLE = [ "flat", "round", "square" ], 00025 VML_LINEJOIN_TABLE = [ "miter", "round", "bevel" ], 00026 VML_ELEMENT_TABLE = [ " m ", " l ", "qb", " c ", " x ", [" at ", " wa "]]; 00027 00028 var _CGBitmapGraphicsContextCreate = CGBitmapGraphicsContextCreate; 00029 00030 function CGBitmapGraphicsContextCreate() 00031 { 00032 // The first time around, we have to set up our environment to support vml. 00033 document.namespaces.add("cg_vml_", "urn:schemas-microsoft-com:vml"); 00034 document.createStyleSheet().cssText = "cg_vml_\\:*{behavior:url(#default#VML)}"; 00035 00036 CGBitmapGraphicsContextCreate = _CGBitmapGraphicsContextCreate; 00037 00038 return _CGBitmapGraphicsContextCreate(); 00039 } 00040 00041 function CGContextSetFillColor(aContext, aColor) 00042 { 00043 if ([aColor patternImage]) 00044 // Prefix a marker character to the string so we know it's a pattern image filename 00045 aContext.gState.fillStyle = "!" + [[aColor patternImage] filename]; 00046 else 00047 aContext.gState.fillStyle = [aColor cssString]; 00048 } 00049 00050 // FIXME: aRect is ignored. 00051 function CGContextClearRect(aContext, aRect) 00052 { 00053 if (aContext.buffer != nil) 00054 aContext.buffer = ""; 00055 else 00056 aContext.DOMElement.innerHTML = ""; 00057 00058 aContext.path = NULL; 00059 } 00060 00061 var W = 10.0, 00062 H = 10.0, 00063 Z = 10.0, 00064 Z_2 = Z / 2.0; 00065 00066 #define COORD(aCoordinate) (aCoordinate === 0.0 ? 0 : ROUND(Z * (aCoordinate) - Z_2)) 00067 00068 function CGContextDrawImage(aContext, aRect, anImage) 00069 { 00070 var string = ""; 00071 00072 if (anImage.buffer != nil) 00073 string = anImage.buffer; 00074 else 00075 { 00076 var ctm = aContext.gState.CTM, 00077 origin = CGPointApplyAffineTransform(aRect.origin, ctm), 00078 similarity = ctm.a == ctm.d && ctm.b == -ctm.c, 00079 vml = ["<cg_vml_:group coordsize=\"1,1\" coordorigin=\"0,0\" style=\"width:1;height:1;position:absolute"]; 00080 00081 /*if (similarity) 00082 { 00083 var angle = CGPointMake(1.0, 0.0); 00084 00085 angle = _CGPointApplyAffineTransform(angle, ctm); 00086 00087 vml.push(";rotation:", ATAN2(angle.y - ctm.ty, angle.x - ctm.tx) * 180 / PI); 00088 }*/ 00089 00090 // Only create a filter if absolutely necessary. This actually 00091 // turns out to only be the case if our transform matrix is not 00092 // a similarity matrix, that is to say, the transform actually 00093 // morphs the shape beyond scaling and rotation. 00094 //if (!similarity && (ctm.a != 1.0 || ctm.b || ctm.c || ctm.d != 1.0)) 00095 { 00096 var transformedRect = CGRectApplyAffineTransform(aRect, ctm); 00097 00098 vml.push( ";padding:0 ", ROUND(_CGRectGetMaxX(transformedRect)), "px ", ROUND(_CGRectGetMaxY(transformedRect)), 00099 "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(", 00100 "M11='", ctm.a, "',M12='", ctm.c, "',M21='", ctm.b, "',M22='", ctm.d, "',", 00101 "Dx='", ROUND(origin.x), "', Dy='", ROUND(origin.y), "', sizingmethod='clip');"); 00102 } 00103 //else 00104 // vml.push(";top:", ROUND(origin.y - 0.5), "px;left:", ROUND(origin.x - 0.5), "px;"); 00105 00106 vml.push( "\"><cg_vml_:image src=\"", anImage._image.src, 00107 "\" style=\"width:", _CGRectGetWidth(aRect), "px;height:", _CGRectGetHeight(aRect), 00108 "px;\"/></g_vml_:group>"); 00109 00110 string = vml.join(""); 00111 } 00112 00113 if (aContext.buffer != nil) 00114 aContext.buffer += string; 00115 else 00116 aContext.DOMElement.insertAdjacentHTML("BeforeEnd", string); 00117 } 00118 00119 function CGContextDrawPath(aContext, aMode) 00120 { 00121 if (!aContext || CGPathIsEmpty(aContext.path)) 00122 return; 00123 00124 var elements = aContext.path.elements, 00125 00126 i = 0, 00127 count = aContext.path.count, 00128 00129 gState = aContext.gState, 00130 fill = (aMode == kCGPathFill || aMode == kCGPathFillStroke) ? 1 : 0, 00131 stroke = (aMode == kCGPathStroke || aMode == kCGPathFillStroke) ? 1 : 0, 00132 opacity = gState.alpha, 00133 vml = ["<cg_vml_:shape"]; 00134 00135 if (gState.fillStyle.charAt(0) !== "!") 00136 vml.push(" fillcolor=\"", gState.fillStyle, "\""); 00137 00138 vml.push( " filled=\"", VML_TRUTH_TABLE[fill], 00139 "\" style=\"position:absolute;width:", W, ";height:", H, 00140 ";\" coordorigin=\"0 0\" coordsize=\"", Z * W, " ", Z * H, 00141 "\" stroked=\"", VML_TRUTH_TABLE[stroke], 00142 "\" strokeweight=\"", gState.lineWidth, 00143 "\" strokecolor=\"", gState.strokeStyle, 00144 "\" path=\""); 00145 00146 for (; i < count; ++i) 00147 { 00148 var element = elements[i], 00149 type = element.type; 00150 00151 switch (type) 00152 { 00153 case kCGPathElementMoveToPoint: 00154 case kCGPathElementAddLineToPoint: vml.push(VML_ELEMENT_TABLE[type], COORD(element.x), ',', COORD(element.y)); 00155 break; 00156 00157 case kCGPathElementAddQuadCurveToPoint: vml.push(VML_ELEMENT_TABLE[type], 00158 COORD(element.cpx), ',', COORD(element.cpy), ',', 00159 COORD(element.x), ',', COORD(element.y)); 00160 break; 00161 00162 case kCGPathElementAddCurveToPoint: vml.push(VML_ELEMENT_TABLE[type], 00163 COORD(element.cp1x), ',', COORD(element.cp1y), ',', 00164 COORD(element.cp2x), ',', COORD(element.cp2y), ',', 00165 COORD(element.x), ',', COORD(element.y)); 00166 break; 00167 00168 case kCGPathElementCloseSubpath: vml.push(VML_ELEMENT_TABLE[type]); 00169 break; 00170 00171 case kCGPathElementAddArc: var x = element.x, 00172 y = element.y, 00173 radius = element.radius, 00174 clockwise = element.clockwise ? 1 : 0, 00175 endAngle = element.endAngle, 00176 startAngle = element.startAngle, 00177 00178 start = _CGPointMake(x + radius * COS(startAngle), y + radius * SIN(startAngle)); 00179 00180 // If the angle's are equal, then we won't actually draw an arc, but instead 00181 // simply move to its start/end to get the proper fill. 00182 // We only need this special case for anti-clockwise because start == end is 00183 // interpreted as a full circle with anti-clockwise, but empty for clockwise. 00184 if (startAngle == endAngle && !clockwise) 00185 { 00186 vml.push(VML_ELEMENT_TABLE[kCGPathElementMoveToPoint], COORD(start.x), ',', COORD(start.y)); 00187 00188 continue; 00189 } 00190 00191 var end = _CGPointMake(x + radius * COS(endAngle), y + radius * SIN(endAngle)); 00192 00193 // Only do the start correction if the angles aren't equal. If they are, then 00194 // let the circle be empty. 00195 // FIXME: Should this be |star.x - end.x| < 0.125 ? 00196 if (clockwise && startAngle != endAngle && _CGPointEqualToPoint(start, end)) 00197 if (start.x >= x) 00198 { 00199 if (start.y < y) 00200 start.x += 0.125; 00201 else 00202 start.y += 0.125; 00203 } 00204 else 00205 { 00206 if (end.y <= y) 00207 end.x += 0.125; 00208 else 00209 end.y += 0.125; 00210 } 00211 00212 vml.push(VML_ELEMENT_TABLE[type][clockwise], 00213 COORD(x - radius), ',', COORD(y - radius), " ", 00214 COORD(x + radius), ',', COORD(y + radius), " ", 00215 COORD(start.x), ',', COORD(start.y), " ", 00216 COORD(end.x), ',', COORD(end.y)); 00217 break; 00218 case kCGPathElementAddArcTo: break; 00219 } 00220 00221 // TODO: Following is broken for curves due to 00222 // move to proper paths. 00223 00224 // Figure out dimensions so we can do gradient fills 00225 // properly 00226 /*if(c) { 00227 if (min.x == null || c.x < min.x) { 00228 min.x = c.x; 00229 } 00230 if (max.x == null || c.x > max.x) { 00231 max.x = c.x; 00232 } 00233 if (min.y == null || c.y < min.y) { 00234 min.y = c.y; 00235 } 00236 if (max.y == null || c.y > max.y) { 00237 max.y = c.y; 00238 } 00239 }*/ 00240 } 00241 00242 vml.push("\">"); 00243 00244 if (gState.gradient) 00245 vml.push(gState.gradient) 00246 00247 else if (fill) 00248 { 00249 if (gState.fillStyle.charAt(0) === "!") 00250 vml.push("<cg_vml_:fill type=\"tile\" src=\"", gState.fillStyle.substring(1), "\" opacity=\"", opacity, "\" />"); 00251 else // should be a CSS color spec 00252 vml.push("<cg_vml_:fill color=\"", gState.fillStyle, "\" opacity=\"", opacity, "\" />"); 00253 } 00254 00255 if (stroke) 00256 vml.push( "<cg_vml_:stroke opacity=\"", opacity, 00257 "\" joinstyle=\"", VML_LINEJOIN_TABLE[gState.lineJoin], 00258 "\" miterlimit=\"", gState.miterLimit, 00259 "\" endcap=\"", VML_LINECAP_TABLE[gState.lineCap], 00260 "\" weight=\"", gState.lineWidth, "", 00261 "px\" color=\"", gState.strokeStyle,"\" />"); 00262 00263 var shadowColor = gState.shadowColor; 00264 //\"", [shadowColor cssString], "\" 00265 if (shadowColor) 00266 { 00267 var shadowOffset = gState.shadowOffset; 00268 00269 vml.push("<cg_vml_:shadow on=\"t\" offset=\"", 00270 shadowOffset.width, "pt ", shadowOffset.height, "pt\" opacity=\"", [shadowColor alphaComponent], "\" color=black />"); 00271 } 00272 00273 vml.push("</cg_vml_:shape>"); 00274 00275 if (aContext.buffer != nil) 00276 aContext.buffer += vml.join(""); 00277 else 00278 aContext.DOMElement.insertAdjacentHTML("BeforeEnd", vml.join("")); 00279 } 00280 00281 function to_string(aColor) 00282 { 00283 return "rgb(" + ROUND(aColor.components[0] * 255) + ", " + ROUND(aColor.components[1] * 255) + ", " + ROUND(255 * aColor.components[2]) + ")"; 00284 } 00285 00286 function CGContextDrawLinearGradient(aContext, aGradient, aStartPoint, anEndPoint, options) 00287 { 00288 if (!aContext || !aGradient) 00289 return; 00290 00291 var vml = nil; 00292 00293 if (aGradient.vml_gradient) 00294 { 00295 var stops = [[aGradient.vml_gradient stops] sortedArrayUsingSelector:@selector(comparePosition:)], 00296 count = [stops count]; 00297 00298 vml = ["<cg_vml_:fill type=\"gradient\" method=\"linear sigma\" "]; 00299 vml.push("angle=\"" + ([aGradient.vml_gradient angle] + 90) + "\" "); 00300 00301 vml.push("colors=\""); 00302 00303 for (var i = 0; i < count; i++) 00304 { 00305 vml.push(([stops[i] position] * 100).toFixed(0) + "% "); 00306 vml.push([[[stops[i] color] colorForSlideBase:nil] cssString]); 00307 00308 if (i < count - 1) 00309 vml.push(","); 00310 } 00311 00312 vml.push("\" />"); 00313 } 00314 else 00315 { 00316 var colors = aGradient.colors, 00317 count = colors.length; 00318 00319 vml = ["<cg_vml_:fill type=\"gradient\" "]; 00320 00321 vml.push("colors=\""); 00322 00323 for (var i = 0; i < count; i++) 00324 vml.push((aGradient.locations[i] * 100).toFixed(0)+"% " + to_string(colors[i])+(i < count - 1 ? "," : "")); 00325 00326 vml.push("\" />"); 00327 } 00328 00329 aContext.gState.gradient = vml.join(""); 00330 00331 // if (aContext.buffer != nil) 00332 // aContext.buffer += vml.join(""); 00333 // else 00334 // aContext.DOMElement.innerHTML = vml.join(""); 00335 }