API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPFont.j
Go to the documentation of this file.
1 /*
2  * CPFont.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 CPFontDefaultSystemFontFace = @"Arial, sans-serif";
27 
33 
34 // For internal use only by this class and subclasses
35 _CPFontSystemFacePlaceholder = "_CPFontSystemFacePlaceholder";
36 
37 var _CPFontCache = {},
38  _CPSystemFontCache = {},
39  _CPFontSystemFontFace = CPFontDefaultSystemFontFace,
40  _CPFontSystemFontSize = 12,
41  _CPFontFallbackFaces = CPFontDefaultSystemFontFace.split(", "),
42  _CPFontStripRegExp = new RegExp("(^\\s*[\"']?|[\"']?\\s*$)", "g");
43 
44 
45 #define _CPRealFontSize(aSize) (aSize <= 0 ? _CPFontSystemFontSize : aSize)
46 #define _CPFontNormalizedNames(aName) _CPFontNormalizedNameArray(aName).join(", ")
47 #define _CPCachedFont(aName, aSize, isBold, isItalic) _CPFontCache[_CPFontCreateCSSString(_CPFontNormalizedNames(aName), aSize, isBold, isItalic)]
48 #define _CPUserFont(aName, aSize, isBold, isItalic) _CPCachedFont(aName, aSize, isBold, isItalic) || [[CPFont alloc] _initWithName:aName size:aSize bold:isBold italic:isItalic system:NO]
49 
50 #define _CPSystemFontCacheKey(aSize, isBold) (String(aSize) + (isBold ? "b" : ""))
51 #define _CPCachedSystemFont(aSize, isBold) _CPSystemFontCache[_CPSystemFontCacheKey(aSize, isBold)]
52 #define _CPSystemFont(aSize, isBold) (_CPCachedSystemFont(aSize, isBold) || [[CPFont alloc] _initWithName:_CPFontSystemFacePlaceholder size:aSize bold:isBold italic:NO system:YES])
53 
108 @implementation CPFont : CPObject
109 {
110  CPString _name;
111  float _size;
112  float _ascender;
113  float _descender;
114  float _lineHeight;
115  BOOL _isBold;
116  BOOL _isItalic;
117  BOOL _isSystem;
118 
119  CPString _cssString;
120 }
121 
122 + (void)initialize
123 {
124  if (self !== [CPFont class])
125  return;
126 
127  var systemFontFace = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPSystemFontFace"];
128 
129  if (!systemFontFace)
130  systemFontFace = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontFace"];
131 
132  if (systemFontFace)
133  _CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace);
134 
135  var systemFontSize = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPSystemFontSize"];
136 
137  if (!systemFontSize)
138  systemFontSize = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontSize"];
139 
140  if (systemFontSize)
141  _CPFontSystemFontSize = systemFontSize;
142 }
143 
147 + (CPString)systemFontFace
148 {
149  return _CPFontSystemFontFace;
150 }
151 
155 + (CPString)setSystemFontFace:(CPString)aFace
156 {
157  var normalizedFaces = _CPFontNormalizedNames(aFace);
158 
159  if (normalizedFaces === _CPFontSystemFontFace)
160  return;
161 
162  [self _invalidateSystemFontCache]
163  _CPFontSystemFontFace = aFace;
164 }
165 
169 + (float)systemFontSize
170 {
171  return _CPFontSystemFontSize;
172 }
173 
174 + (float)systemFontSizeForControlSize:(CPControlSize)aSize
175 {
176  // TODO These sizes should be themable or made less arbitrary in some other way.
177  switch (aSize)
178  {
179  case CPSmallControlSize:
180  return _CPFontSystemFontSize - 1;
181 
182  case CPMiniControlSize:
183  return _CPFontSystemFontSize - 2;
184 
186  default:
187  return _CPFontSystemFontSize;
188  }
189 }
190 
194 + (float)setSystemFontSize:(float)size
195 {
196  if (size > 0 && size !== _CPFontSystemFontSize)
197  {
198  [self _invalidateSystemFontCache];
199  _CPFontSystemFontSize = size;
200  }
201 }
202 
203 + (void)_invalidateSystemFontCache
204 {
205  var systemSize = String(_CPFontSystemFontSize),
206  currentSize = String(CPFontCurrentSystemSize);
207 
208  for (var key in _CPSystemFontCache)
209  {
210  if (_CPSystemFontCache.hasOwnProperty(key) &&
211  (key.indexOf(systemSize) === 0 || key.indexOf(currentSize) === 0))
212  {
213  delete _CPSystemFontCache[key];
214  }
215  }
216 }
217 
225 + (CPFont)fontWithName:(CPString)aName size:(float)aSize
226 {
227  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, NO, NO);
228 }
229 
238 + (CPFont)fontWithName:(CPString)aName size:(float)aSize italic:(BOOL)italic
239 {
240  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, NO, italic);
241 }
242 
250 + (CPFont)boldFontWithName:(CPString)aName size:(float)aSize
251 {
252  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, YES, NO);
253 }
254 
263 + (CPFont)boldFontWithName:(CPString)aName size:(float)aSize italic:(BOOL)italic
264 {
265  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, YES, italic);
266 }
267 
271 + (CPFont)_fontWithName:(CPString)aName size:(float)aSize bold:(BOOL)bold italic:(BOOL)italic
272 {
273  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, bold, italic);
274 }
275 
283 + (CPFont)systemFontOfSize:(CGSize)aSize
284 {
285  return _CPSystemFont(aSize === 0 ? _CPFontSystemFontSize : aSize, NO);
286 }
287 
295 + (CPFont)boldSystemFontOfSize:(CGSize)aSize
296 {
297  return _CPSystemFont(aSize === 0 ? _CPFontSystemFontSize : aSize, YES);
298 }
299 
300 - (id)_initWithName:(CPString)aName size:(float)aSize bold:(BOOL)isBold italic:(BOOL)isItalic system:(BOOL)isSystem
301 {
302  self = [super init];
303 
304  if (self)
305  {
306  _size = aSize;
307  _ascender = 0;
308  _descender = 0;
309  _lineHeight = 0;
310  _isBold = isBold;
311  _isItalic = isItalic;
312  _isSystem = isSystem;
313 
314  if (isSystem)
315  {
316  _name = aName;
317  _cssString = _CPFontCreateCSSString(_CPFontSystemFontFace, _size, _isBold, _isItalic);
318  _CPSystemFontCache[_CPSystemFontCacheKey(_size, _isBold)] = self;
319  }
320  else
321  {
322  _name = _CPFontNormalizedNames(aName);
323  _cssString = _CPFontCreateCSSString(_name, _size, _isBold, _isItalic);
324  _CPFontCache[_cssString] = self;
325  }
326  }
327 
328  return self;
329 }
330 
334 - (float)ascender
335 {
336  var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
337 
338  if (!font._ascender)
339  [font _getMetrics];
340 
341  return font._ascender;
342 }
343 
348 - (float)descender
349 {
350  var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
351 
352  if (!font._descender)
353  [font _getMetrics];
354 
355  return font._descender;
356 }
357 
363 - (float)defaultLineHeightForFont
364 {
365  var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
366 
367  if (!font._lineHeight)
368  [font _getMetrics];
369 
370  return font._lineHeight;
371 }
372 
376 - (float)size
377 {
378  return _CPRealFontSize(_size);
379 }
380 
384 - (CPString)cssString
385 {
386  var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
387 
388  return font._cssString;
389 }
390 
394 - (CPString)familyName
395 {
396  if (_isSystem)
397  return _CPFontSystemFontFace;
398 
399  return _name;
400 }
401 
402 - (BOOL)isSystemSize
403 {
404  return _size <= 0;
405 }
406 
407 - (BOOL)isEqual:(id)anObject
408 {
409  return [anObject isKindOfClass:[CPFont class]] && [anObject cssString] === _cssString;
410 }
411 
412 - (CPString)description
413 {
414  return [CPString stringWithFormat:@"%@ %@", [super description], [self cssString]];
415 }
416 
417 - (id)copy
418 {
419  return [[CPFont alloc] _initWithName:_name size:_size bold:_isBold italic:_isItalic system:_isSystem];
420 }
421 
422 - (void)_getMetrics
423 {
424  var metrics = [CPString metricsOfFont:self];
425 
426  _ascender = [metrics objectForKey:@"ascender"];
427  _descender = [metrics objectForKey:@"descender"];
428  _lineHeight = [metrics objectForKey:@"lineHeight"];
429 }
430 
431 @end
432 
433 var CPFontNameKey = @"CPFontNameKey",
434  CPFontSizeKey = @"CPFontSizeKey",
435  CPFontIsBoldKey = @"CPFontIsBoldKey",
436  CPFontIsItalicKey = @"CPFontIsItalicKey",
437  CPFontIsSystemKey = @"CPFontIsSystemKey";
438 
439 @implementation CPFont (CPCoding)
440 
446 - (id)initWithCoder:(CPCoder)aCoder
447 {
448  var fontName = [aCoder decodeObjectForKey:CPFontNameKey],
449  size = [aCoder decodeFloatForKey:CPFontSizeKey],
450  isBold = [aCoder decodeBoolForKey:CPFontIsBoldKey],
451  isItalic = [aCoder decodeBoolForKey:CPFontIsItalicKey],
452  isSystem = [aCoder decodeBoolForKey:CPFontIsSystemKey];
453 
454  return [self _initWithName:fontName size:size bold:isBold italic:isItalic system:isSystem];
455 }
456 
461 - (void)encodeWithCoder:(CPCoder)aCoder
462 {
463  [aCoder encodeObject:_name forKey:CPFontNameKey];
464  [aCoder encodeFloat:_size forKey:CPFontSizeKey];
465  [aCoder encodeBool:_isBold forKey:CPFontIsBoldKey];
466  [aCoder encodeBool:_isItalic forKey:CPFontIsItalicKey];
467  [aCoder encodeBool:_isSystem forKey:CPFontIsSystemKey];
468 }
469 
470 @end
471 
472 
473 // aName must be normalized
474 var _CPFontCreateCSSString = function(aName, aSize, isBold, isItalic)
475 {
476  var properties = (isItalic ? "italic " : "") + (isBold ? "bold " : "") + _CPRealFontSize(aSize) + "px ";
477 
478  return properties + _CPFontConcatNameWithFallback(aName);
479 };
480 
481 var _CPFontConcatNameWithFallback = function(aName)
482 {
483  var names = _CPFontNormalizedNameArray(aName),
484  fallbackFaces = _CPFontFallbackFaces.slice(0);
485 
486  // Remove the fallback names used in the names passed in
487  for (var i = 0; i < names.length; ++i)
488  {
489  for (var j = 0; j < fallbackFaces.length; ++j)
490  {
491  if (names[i].toLowerCase() === fallbackFaces[j].toLowerCase())
492  {
493  fallbackFaces.splice(j, 1);
494  break;
495  }
496  }
497 
498  if (names[i].indexOf(" ") > 0)
499  names[i] = '"' + names[i] + '"';
500  }
501 
502  return names.concat(fallbackFaces).join(", ");
503 };
504 
505 var _CPFontNormalizedNameArray = function(aName)
506 {
507  var names = aName.split(",");
508 
509  for (var i = 0; i < names.length; ++i)
510  names[i] = names[i].replace(_CPFontStripRegExp, "");
511 
512  return names;
513 };
514 
516 
520 - (BOOL)isBold
521 {
522  return _isBold;
523 }
524 
528 - (BOOL)isItalic
529 {
530  return _isItalic;
531 }
532 
536 - (BOOL)isSystem
537 {
538  return _isSystem;
539 }
540 
541 @end