API 0.9.5
AppKit/CoreGraphics/CGContextVML.j
Go to the documentation of this file.
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 }
 All Classes Files Functions Variables Defines