API  0.9.8
 All Classes Files Functions Variables Typedefs Macros Groups Pages
CPColor.j
Go to the documentation of this file.
1 /*
2  * CPColor.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 
26 
31 
36 
38 
39 var _redComponent = 0,
40  _greenComponent = 1,
41  _blueComponent = 2,
42  _alphaCompnent = 3;
43 
44 var _hueComponent = 0,
45  _saturationComponent = 1,
46  _brightnessComponent = 2;
47 
48 var cachedBlackColor,
49  cachedRedColor,
50  cachedGreenColor,
51  cachedBlueColor,
52  cachedYellowColor,
53  cachedGrayColor,
54  cachedLightGrayColor,
55  cachedDarkGrayColor,
56  cachedWhiteColor,
57  cachedBrownColor,
58  cachedCyanColor,
59  cachedMagentaColor,
60  cachedOrangeColor,
61  cachedPurpleColor,
62  cachedShadowColor,
63  cachedClearColor;
64 
66 
76 @implementation CPColor : CPObject
77 {
78  CPArray _components;
79 
80  CPImage _patternImage;
81  CPString _cssString;
82 }
83 
97 + (CPColor)colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
98 {
99  return [[CPColor alloc] _initWithRGBA:[MAX(0.0, MIN(1.0, red)), MAX(0.0, MIN(1.0, green)), MAX(0.0, MIN(1.0, blue)), MAX(0.0, MIN(1.0, alpha))]];
100 }
101 
117 + (CPColor)colorWithCalibratedRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
118 {
119  return [self colorWithRed:red green:green blue:blue alpha:alpha];
120 }
121 
122 
132 + (CPColor)colorWithWhite:(float)white alpha:(float)alpha
133 {
134  return [[CPColor alloc] _initWithRGBA:[white, white, white, alpha]];
135 }
136 
148 + (CPColor)colorWithCalibratedWhite:(float)white alpha:(float)alpha
149 {
150  return [self colorWithWhite:white alpha:alpha];
151 }
152 
166 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness
167 {
168  return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0];
169 }
170 
176 + (CPColor)colorWithCalibratedHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
177 {
178  return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
179 }
180 
195 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
196 {
197  // Clamp values.
198  hue = MAX(MIN(hue, 1.0), 0.0);
199  saturation = MAX(MIN(saturation, 1.0), 0.0);
200  brightness = MAX(MIN(brightness, 1.0), 0.0);
201 
202  if (saturation === 0.0)
203  return [CPColor colorWithCalibratedWhite:brightness alpha:alpha];
204 
205  var f = (hue * 360) % 60,
206  p = (brightness * (1 - saturation)),
207  q = (brightness * (60 - saturation * f)) / 60,
208  t = (brightness * (60 - saturation * (60 - f))) / 60,
209  b = brightness;
210 
211  switch (FLOOR(hue * 6))
212  {
213  case 0:
214  case 6:
215  return [CPColor colorWithCalibratedRed:b green:t blue:p alpha:alpha];
216  case 1:
217  return [CPColor colorWithCalibratedRed:q green:b blue:p alpha:alpha];
218  case 2:
219  return [CPColor colorWithCalibratedRed:p green:b blue:t alpha:alpha];
220  case 3:
221  return [CPColor colorWithCalibratedRed:p green:q blue:b alpha:alpha];
222  case 4:
223  return [CPColor colorWithCalibratedRed:t green:p blue:b alpha:alpha];
224  case 5:
225  return [CPColor colorWithCalibratedRed:b green:p blue:q alpha:alpha];
226  }
227 }
228 
239 + (CPColor)colorWithHexString:(string)hex
240 {
241  var rgba = hexToRGB(hex);
242  return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null;
243 }
244 
249 + (CPColor)colorWithSRGBRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
250 {
251  // TODO If Cappuccino is ported to a colorspace aware platform, this color should be in
252  // sRGBColorSpace.
253  return [self colorWithRed:red green:green blue:blue alpha:alpha];
254 }
255 
259 + (CPColor)blackColor
260 {
261  if (!cachedBlackColor)
262  cachedBlackColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0]];
263 
264  return cachedBlackColor;
265 }
266 
270 + (CPColor)blueColor
271 {
272  if (!cachedBlueColor)
273  cachedBlueColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 1.0, 1.0]];
274 
275  return cachedBlueColor;
276 }
277 
281 + (CPColor)darkGrayColor
282 {
283  if (!cachedDarkGrayColor)
284  cachedDarkGrayColor = [CPColor colorWithCalibratedWhite:1.0 / 3.0 alpha:1.0];
285 
286  return cachedDarkGrayColor;
287 }
288 
292 + (CPColor)grayColor
293 {
294  if (!cachedGrayColor)
295  cachedGrayColor = [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0];
296 
297  return cachedGrayColor;
298 }
299 
303 + (CPColor)greenColor
304 {
305  if (!cachedGreenColor)
306  cachedGreenColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 0.0, 1.0]];
307 
308  return cachedGreenColor;
309 }
310 
314 + (CPColor)lightGrayColor
315 {
316  if (!cachedLightGrayColor)
317  cachedLightGrayColor = [CPColor colorWithCalibratedWhite:2.0 / 3.0 alpha:1.0];
318 
319  return cachedLightGrayColor;
320 }
321 
325 + (CPColor)redColor
326 {
327  if (!cachedRedColor)
328  cachedRedColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 0.0, 1.0]];
329 
330  return cachedRedColor;
331 }
332 
336 + (CPColor)whiteColor
337 {
338  if (!cachedWhiteColor)
339  cachedWhiteColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 1.0, 1.0]];
340 
341  return cachedWhiteColor;
342 }
343 
347 + (CPColor)yellowColor
348 {
349  if (!cachedYellowColor)
350  cachedYellowColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 0.0, 1.0]];
351 
352  return cachedYellowColor;
353 }
354 
358 + (CPColor)brownColor
359 {
360  if (!cachedBrownColor)
361  cachedBrownColor = [[CPColor alloc] _initWithRGBA:[0.6, 0.4, 0.2, 1.0]];
362 
363  return cachedBrownColor;
364 }
365 
369 + (CPColor)cyanColor
370 {
371  if (!cachedCyanColor)
372  cachedCyanColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 1.0, 1.0]];
373 
374  return cachedCyanColor;
375 }
376 
380 + (CPColor)magentaColor
381 {
382  if (!cachedMagentaColor)
383  cachedMagentaColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 1.0, 1.0]];
384 
385  return cachedMagentaColor;
386 }
387 
391 + (CPColor)orangeColor
392 {
393  if (!cachedOrangeColor)
394  cachedOrangeColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.5, 0.0, 1.0]];
395 
396  return cachedOrangeColor;
397 }
398 
402 + (CPColor)purpleColor
403 {
404  if (!cachedPurpleColor)
405  cachedPurpleColor = [[CPColor alloc] _initWithRGBA:[0.5, 0.0, 0.5, 1.0]];
406 
407  return cachedPurpleColor;
408 }
409 
414 + (CPColor)shadowColor
415 {
416  if (!cachedShadowColor)
417  cachedShadowColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0 / 3.0]];
418 
419  return cachedShadowColor;
420 }
421 
426 + (CPColor)clearColor
427 {
428  if (!cachedClearColor)
429  cachedClearColor = [self colorWithCalibratedWhite:0.0 alpha:0.0];
430 
431  return cachedClearColor;
432 }
433 
434 + (CPColor)alternateSelectedControlColor
435 {
436  return [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]];
437 }
438 
439 + (CPColor)secondarySelectedControlColor
440 {
441  return [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]];
442 }
443 
449 + (CPColor)colorWithPatternImage:(CPImage)anImage
450 {
451  return [[CPColor alloc] _initWithPatternImage:anImage];
452 }
453 
460 + (CPColor)colorWithCSSString:(CPString)aString
461 {
462  return [[CPColor alloc] _initWithCSSString: aString];
463 }
464 
465 /* @ignore */
466 - (id)_initWithCSSString:(CPString)aString
467 {
468  if (aString.indexOf("rgb") == CPNotFound)
469  return nil;
470 
471  self = [super init];
472 
473  var startingIndex = aString.indexOf("("),
474  parts = aString.substring(startingIndex + 1).split(',');
475 
476  _components = [
477  parseInt(parts[0], 10) / 255.0,
478  parseInt(parts[1], 10) / 255.0,
479  parseInt(parts[2], 10) / 255.0,
480  parts[3] ? parseFloat(parts[3], 10) : 1.0
481  ];
482 
483  // We can't reuse aString as _cssString because the browser might not support the `rgba` syntax, and aString might
484  // use it (issue #1413.)
485  [self _initCSSStringFromComponents];
486 
487  return self;
488 }
489 
490 /* @ignore */
491 - (id)_initWithRGBA:(CPArray)components
492 {
493  self = [super init];
494 
495  if (self)
496  {
497  _components = components;
498 
499  [self _initCSSStringFromComponents];
500  }
501 
502  return self;
503 }
504 
505 - (void)_initCSSStringFromComponents
506 {
507  var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
508 
509  _cssString = (hasAlpha ? "rgba(" : "rgb(") +
510  parseInt(_components[0] * 255.0) + ", " +
511  parseInt(_components[1] * 255.0) + ", " +
512  parseInt(_components[2] * 255.0) +
513  (hasAlpha ? (", " + _components[3]) : "") + ")";
514 }
515 
516 /* @ignore */
517 - (id)_initWithPatternImage:(CPImage)anImage
518 {
519  self = [super init];
520 
521  if (self)
522  {
523  _patternImage = anImage;
524  _cssString = "url(\"" + [_patternImage filename] + "\")";
525  _components = [0.0, 0.0, 0.0, 1.0];
526  }
527 
528  return self;
529 }
530 
534 - (CPImage)patternImage
535 {
536  return _patternImage;
537 }
538 
542 - (float)alphaComponent
543 {
544  return _components[3];
545 }
546 
550 - (float)blueComponent
551 {
552  return _components[2];
553 }
554 
558 - (float)greenComponent
559 {
560  return _components[1];
561 }
562 
566 - (float)redComponent
567 {
568  return _components[0];
569 }
570 
582 - (CPArray)components
583 {
584  return _components;
585 }
586 
594 - (CPColor)colorWithAlphaComponent:(float)anAlphaComponent
595 {
596  var components = _components.slice();
597 
598  components[components.length - 1] = anAlphaComponent;
599 
600  return [[[self class] alloc] _initWithRGBA:components];
601 }
602 
606 - (CPColor)colorUsingColorSpaceName:(id)aColorSpaceName
607 {
608  return self;
609 }
610 
624 - (CPArray)hsbComponents
625 {
626  var red = ROUND(_components[_redComponent] * 255.0),
627  green = ROUND(_components[_greenComponent] * 255.0),
628  blue = ROUND(_components[_blueComponent] * 255.0);
629 
630  var max = MAX(red, green, blue),
631  min = MIN(red, green, blue),
632  delta = max - min;
633 
634  var brightness = max / 255.0,
635  saturation = (max != 0) ? delta / max : 0;
636 
637  var hue;
638 
639  if (saturation == 0)
640  {
641  hue = 0;
642  }
643  else
644  {
645  var rr = (max - red) / delta,
646  gr = (max - green) / delta,
647  br = (max - blue) / delta;
648 
649  if (red == max)
650  hue = br - gr;
651  else if (green == max)
652  hue = 2 + rr - br;
653  else
654  hue = 4 + gr - rr;
655 
656  hue /= 6;
657  if (hue < 0)
658  hue++;
659  }
660 
661  return [
662  hue,
663  saturation,
664  brightness
665  ];
666 }
667 
671 - (float)hueComponent
672 {
673  return [self hsbComponents][0];
674 }
675 
679 - (float)saturationComponent
680 {
681  return [self hsbComponents][1];
682 }
683 
687 - (float)brightnessComponent
688 {
689  return [self hsbComponents][2];
690 }
691 
701 - (CPString)cssString
702 {
703  return _cssString;
704 }
705 
709 - (CPString)hexString
710 {
711  return rgbToHex([self redComponent], [self greenComponent], [self blueComponent]);
712 }
713 
714 - (BOOL)isEqual:(CPColor)aColor
715 {
716  if (!aColor)
717  return NO;
718 
719  if (aColor === self)
720  return YES;
721 
722  if (![aColor isKindOfClass:CPColor])
723  return NO;
724 
725  if (_patternImage || [aColor patternImage])
726  return [_patternImage isEqual:[aColor patternImage]];
727 
728  // We don't require the components to be equal beyond 8 bits since otherwise
729  // simple rounding errors will make two colours which are exactly the same on
730  // screen compare unequal.
731  return ROUND([self redComponent] * 255.0) == ROUND([aColor redComponent] * 255.0) &&
732  ROUND([self greenComponent] * 255.0) == ROUND([aColor greenComponent] * 255.0) &&
733  ROUND([self blueComponent] * 255.0) == ROUND([aColor blueComponent] * 255.0) &&
734  [self alphaComponent] == [aColor alphaComponent];
735 }
736 
737 - (CPString)description
738 {
739  var description = [super description],
740  patternImage = [self patternImage];
741 
742  if (!patternImage)
743  return description + " " + [self cssString];
744 
745  description += " {\n";
746 
747  if ([patternImage isThreePartImage] || [patternImage isNinePartImage])
748  {
749  var slices = [patternImage imageSlices];
750 
751  if ([patternImage isThreePartImage])
752  description += " orientation: " + ([patternImage isVertical] ? "vertical" : "horizontal") + ",\n";
753 
754  description += " patternImage (" + slices.length + " part): [\n";
755 
756  for (var i = 0; i < slices.length; ++i)
757  {
758  var imgDescription = [slices[i] description] || "nil";
759 
760  description += imgDescription.replace(/^/mg, " ") + ",\n";
761  }
762 
763  description = description.substr(0, description.length - 2) + "\n ]\n}";
764  }
765  else
766  description += ([patternImage description] || "nil").replace(/^/mg, " ") + "\n}";
767 
768  return description;
769 }
770 
771 @end
772 
774 
778 - (void)set
779 {
780  [self setFill];
781  [self setStroke];
782 }
783 
787 - (void)setFill
788 {
790  CGContextSetFillColor(ctx, self);
791 }
792 
796 - (void)setStroke
797 {
799  CGContextSetStrokeColor(ctx, self);
800 }
801 
802 @end
803 
804 @implementation CPColor (Debugging)
805 
806 + (CPColor)randomColor
807 {
808  return [CPColor colorWithRed:RAND() green:RAND() blue:RAND() alpha:1.0];
809 }
810 
811 + (CPColor)checkerBoardColor
812 {
813  // Thanks to cocco http://stackoverflow.com/a/18368212/76900.
814  return [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEX////MzMw46qqDAAAAEElEQVQImWNg+M+AFeEQBgB+vw/xfUUZkgAAAABJRU5ErkJggg=="]];
815 }
816 
817 @end
818 
820 var CPColorComponentsKey = @"CPColorComponentsKey",
821  CPColorPatternImageKey = @"CPColorPatternImageKey";
823 
824 @implementation CPColor (CPCoding)
825 
830 - (id)initWithCoder:(CPCoder)aCoder
831 {
832  if ([aCoder containsValueForKey:CPColorPatternImageKey])
833  return [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]];
834 
835  return [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]];
836 }
837 
842 - (void)encodeWithCoder:(CPCoder)aCoder
843 {
844  if (_patternImage)
845  [aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey];
846  else
847  [aCoder encodeObject:_components forKey:CPColorComponentsKey];
848 }
849 
850 @end
851 
852 
854 var hexCharacters = "0123456789ABCDEF";
855 
856 /*
857  Used for the CPColor +colorWithHexString: implementation.
858  Returns an array of rgb components.
859 */
860 var hexToRGB = function(hex)
861 {
862  if (hex.length == 3)
863  hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
864 
865  if (hex.length != 6)
866  return null;
867 
868  hex = hex.toUpperCase();
869 
870  for (var i = 0; i < hex.length; i++)
871  if (hexCharacters.indexOf(hex.charAt(i)) == -1)
872  return null;
873 
874  var red = (hexCharacters.indexOf(hex.charAt(0)) * 16 + hexCharacters.indexOf(hex.charAt(1))) / 255.0,
875  green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0,
876  blue = (hexCharacters.indexOf(hex.charAt(4)) * 16 + hexCharacters.indexOf(hex.charAt(5))) / 255.0;
877 
878  return [red, green, blue, 1.0];
879 };
880 
881 var rgbToHex = function(r,g,b)
882 {
883  return byteToHex(r) + byteToHex(g) + byteToHex(b);
884 };
885 
886 var byteToHex = function(n)
887 {
888  if (!n || isNaN(n))
889  return "00";
890 
891  n = FLOOR(MIN(255, MAX(0, 256 * n)));
892 
893  return hexCharacters.charAt((n - n % 16) / 16) +
894  hexCharacters.charAt(n % 16);
895 };
896 
1116 function CPColorWithImages()
1117 {
1118  var slices = nil,
1119  numParts = 0,
1120  isVertical = false,
1121  imageFactory = CPImageInBundle,
1122  args = Array.prototype.slice.apply(arguments);
1123 
1124  if (typeof(args[args.length - 1]) === "function")
1125  imageFactory = args.pop();
1126 
1127  switch (args.length)
1128  {
1129  case 1:
1130  return imageFromSlices(args[0], isVertical, imageFactory);
1131 
1132  case 2:
1133  // New-style 3-part and 9-part images
1134  if (typeof(args[0]) === "string")
1135  return patternColorsFromPattern.call(this, args[0], args[1], imageFactory);
1136 
1137  return imageFromSlices(args[0], args[1], imageFactory);
1138 
1139  case 3:
1140  case 4:
1141  return [CPColor colorWithPatternImage:imageFactory(args[0], args[1], args[2], args[3])];
1142 
1143  default:
1144  throw("ERROR: Invalid argument count: " + args.length);
1145  }
1146 }
1147 
1148 var imageFromSlices = function(slices, isVertical, imageFactory)
1149 {
1150  var imageSlices = [];
1151 
1152  for (var i = 0; i < slices.length; ++i)
1153  {
1154  var slice = slices[i];
1155 
1156  imageSlices.push(slice ? imageFactory(slice[0], slice[1], slice[2], slice[3]) : nil);
1157  }
1158 
1159  switch (slices.length)
1160  {
1161  case 3:
1163 
1164  case 9:
1166 
1167  default:
1168  throw("ERROR: Invalid number of image slices: " + slices.length);
1169  }
1170 };
1171 
1172 var patternColorsFromPattern = function(pattern, attributes, imageFactory)
1173 {
1174  if (pattern.match(/^.*\{[^}]+\}/))
1175  {
1176  var width = attributes["width"],
1177  height = attributes["height"],
1178  separator = attributes["separator"] || "-",
1179  orientation = attributes["orientation"],
1180  rightWidth,
1181  bottomHeight,
1182  centerWidthHeight,
1183  centerIsNil,
1184  numParts;
1185 
1186  // positions are mandatory
1187  if (pattern.indexOf("{position}") < 0)
1188  throw("ERROR: Pattern strings must have a {position} placeholder (\"" + pattern + "\")");
1189 
1190  if (orientation === undefined)
1191  {
1192  numParts = 9;
1193 
1194  if (attributes["centerIsNil"] !== undefined)
1195  centerIsNil = attributes["centerIsNil"];
1196  }
1197  else
1198  {
1199  numParts = 3;
1200  isVertical = orientation === PatternIsVertical;
1201 
1202  if (isVertical)
1203  {
1204  if (attributes["centerHeight"])
1205  centerWidthHeight = attributes["centerHeight"];
1206  }
1207  else
1208  {
1209  if (attributes["centerWidth"])
1210  centerWidthHeight = attributes["centerWidth"];
1211  }
1212  }
1213 
1214  if (attributes["rightWidth"])
1215  rightWidth = attributes["rightWidth"];
1216 
1217  if (attributes["bottomHeight"])
1218  bottomHeight = attributes["bottomHeight"];
1219 
1220  var positions = attributes["positions"] || "@",
1221  states = nil,
1222  styles = nil;
1223 
1224  if (numParts === 3)
1225  {
1226  if (positions === "@")
1227  {
1228  if (isVertical)
1229  positions = ["top", "center", "bottom"];
1230  else
1231  positions = ["left", "center", "right"];
1232  }
1233  else if (positions === "#")
1234  positions = ["0", "1", "2"];
1235  else
1236  throw("ERROR: Invalid positions: " + positions)
1237  }
1238  else // numParts === 9
1239  {
1240  if (positions === "@" || positions === "abbrev")
1241  positions = ["top-left", "top", "top-right", "left", "center", "right", "bottom-left", "bottom", "bottom-right"];
1242  else if (positions === "full")
1243  positions = ["top-left", "top-center", "top-right", "center-left", "center-center", "center-right", "bottom-left", "bottom-center", "bottom-right"];
1244  else if (positions === "#")
1245  positions = ["0", "1", "2", "3", "4", "5", "6", "7", "8"];
1246  else
1247  throw("ERROR: Invalid positions: " + positions)
1248  }
1249 
1250  // states
1251  if (pattern.indexOf("{state}") >= 0)
1252  {
1253  states = attributes["states"];
1254 
1255  if (!states)
1256  throw("ERROR: {state} placeholder in the pattern (\"" + pattern + "\") but no states item in the attributes");
1257  }
1258 
1259  // styles
1260  if (pattern.indexOf("{style}") >= 0)
1261  {
1262  styles = attributes["styles"];
1263 
1264  if (!styles)
1265  throw("ERROR: {style} placeholder in the pattern (\"" + pattern + "\") but no styles item in the attributes");
1266  }
1267 
1268  // Now assemble the hierarchy
1269  var placeholder = "{position}",
1270  pos = pattern.indexOf(placeholder),
1271  i;
1272 
1273  for (i = 0; i < positions.length; ++i)
1274  positions[i] = pattern.replace(placeholder, pos === 0 ? positions[i] + separator : separator + positions[i]);
1275 
1276  var slices = positions,
1277  object = slices,
1278  key,
1279  sep;
1280 
1281  if (states)
1282  {
1283  placeholder = "{state}";
1284  pos = pattern.indexOf(placeholder);
1285  object = {};
1286 
1287  for (i = 0; i < states.length; ++i)
1288  {
1289  var state = states[i];
1290  key = state || "@";
1291  sep = state ? separator : "";
1292 
1293  object[key] = slices.slice(0);
1294  replacePlaceholderInArray(object[key], placeholder, pos === 0 ? state + sep : sep + state);
1295  }
1296  }
1297 
1298  if (styles)
1299  {
1300  placeholder = "{style}";
1301  pos = pattern.indexOf(placeholder);
1302 
1303  var styleObject = {};
1304 
1305  for (i = 0; i < styles.length; ++i)
1306  {
1307  var style = styles[i];
1308  key = style || "@";
1309  sep = style ? separator : "";
1310 
1311  if (states)
1312  {
1313  styleObject[key] = cloneObject(object);
1314  replacePlaceholderInObject(styleObject[key], placeholder, pos === 0 ? style + sep : sep + style);
1315  }
1316  else
1317  {
1318  styleObject[key] = slices.slice(0);
1319  replacePlaceholderInArray(styleObject[key], placeholder, pos === 0 ? style + sep : sep + style);
1320  }
1321  }
1322 
1323  object = styleObject;
1324  }
1325 
1326  if (styles || states)
1327  {
1328  if (numParts === 3)
1329  makeThreePartSlicesFromObject(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1330  else
1331  makeNinePartSlicesFromObject(object, width, height, rightWidth, bottomHeight, centerIsNil);
1332 
1333  makeImagesFromObject(object, isVertical, imageFactory);
1334  return object;
1335  }
1336  else
1337  {
1338  if (numParts === 3)
1339  makeThreePartSlicesFromArray(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1340  else
1341  makeNinePartSlicesFromArray(object, width, height, rightWidth, bottomHeight, centerIsNil);
1342 
1343  return imageFromSlices(object, isVertical, imageFactory);
1344  }
1345  }
1346  else
1347  throw("ERROR: No placeholders in slice pattern (\"" + pattern + "\")");
1348 };
1349 
1350 var replacePlaceholderInArray = function(array, find, replacement)
1351 {
1352  for (var i = 0; i < array.length; ++i)
1353  array[i] = array[i].replace(find, replacement);
1354 };
1355 
1356 var replacePlaceholderInObject = function(object, find, replacement)
1357 {
1358  for (var key in object)
1359  if (object.hasOwnProperty(key))
1360  if (object[key].constructor === Array)
1361  replacePlaceholderInArray(object[key], find, replacement);
1362  else
1363  replacePlaceholderInObject(object[key], find, replacement);
1364 };
1365 
1366 var cloneObject = function(object)
1367 {
1368  var clone = {};
1369 
1370  for (var key in object)
1371  if (object.hasOwnProperty(key))
1372  if (object[key].constructor === Array)
1373  clone[key] = object[key].slice(0);
1374  else if (typeof(object[key]) === "object")
1375  clone[key] = cloneObject(object[key]);
1376  else
1377  clone[key] = object[key];
1378 
1379  return clone;
1380 };
1381 
1382 var makeThreePartSlicesFromObject = function(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical)
1383 {
1384  for (var key in object)
1385  if (object.hasOwnProperty(key))
1386  if (object[key].constructor === Array)
1387  makeThreePartSlicesFromArray(object[key], width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1388  else // object
1389  makeThreePartSlicesFromObject(object[key], width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1390 };
1391 
1392 var makeThreePartSlicesFromArray = function(array, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical)
1393 {
1394  array[0] = [array[0], width, height];
1395 
1396  if (isVertical)
1397  {
1398  array[1] = [array[1], width, centerWidthHeight ? centerWidthHeight : 1.0];
1399  array[2] = [array[2], width, bottomHeight ? bottomHeight : height];
1400  }
1401  else
1402  {
1403  array[1] = [array[1], centerWidthHeight ? centerWidthHeight : 1.0, height];
1404  array[2] = [array[2], rightWidth ? rightWidth : width, height];
1405  }
1406 };
1407 
1408 var makeNinePartSlicesFromObject = function(object, width, height, rightWidth, bottomHeight, centerIsNil)
1409 {
1410  for (var key in object)
1411  if (object.hasOwnProperty(key))
1412  if (object[key].constructor === Array)
1413  makeNinePartSlicesFromArray(object[key], width, height, rightWidth, bottomHeight, centerIsNil);
1414  else // object
1415  makeNinePartSlicesFromObject(object[key], width, height, rightWidth, bottomHeight, centerIsNil);
1416 };
1417 
1418 var makeNinePartSlicesFromArray = function(array, width, height, rightWidth, bottomHeight, centerIsNil)
1419 {
1420  rightWidth = rightWidth ? rightWidth : width;
1421  bottomHeight = bottomHeight ? bottomHeight : height;
1422 
1423  array[0] = [array[0], width, height]; // top-left
1424  array[1] = [array[1], 1.0, height]; // top
1425  array[2] = [array[2], rightWidth, height]; // top-right
1426  array[3] = [array[3], width, 1.0]; // left
1427  array[4] = centerIsNil ? nil : [array[4], 1.0, 1.0]; // center
1428  array[5] = [array[5], rightWidth, 1.0]; // right
1429  array[6] = [array[6], width, bottomHeight]; // bottom-left
1430  array[7] = [array[7], 1.0, bottomHeight]; // bottom
1431  array[8] = [array[8], rightWidth, bottomHeight]; // bottom-right
1432 };
1433 
1434 var makeImagesFromObject = function(object, isVertical, imageFactory)
1435 {
1436  for (var key in object)
1437  if (object.hasOwnProperty(key))
1438  if (object[key].constructor === Array)
1439  object[key] = imageFromSlices(object[key], isVertical, imageFactory);
1440  else // object
1441  makeImagesFromObject(object[key], isVertical, imageFactory);
1442 };
1443