API  0.9.6
 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  case CPMiniControlSize:
182  return _CPFontSystemFontSize - 2;
184  default:
185  return _CPFontSystemFontSize;
186  }
187 }
188 
192 + (float)setSystemFontSize:(float)size
193 {
194  if (size > 0 && size !== _CPFontSystemFontSize)
195  {
196  [self _invalidateSystemFontCache];
197  _CPFontSystemFontSize = size;
198  }
199 }
200 
201 + (void)_invalidateSystemFontCache
202 {
203  var systemSize = String(_CPFontSystemFontSize),
204  currentSize = String(CPFontCurrentSystemSize);
205 
206  for (key in _CPSystemFontCache)
207  {
208  if (_CPSystemFontCache.hasOwnProperty(key) &&
209  (key.indexOf(systemSize) === 0 || key.indexOf(currentSize) === 0))
210  {
211  delete _CPSystemFontCache[key];
212  }
213  }
214 }
215 
223 + (CPFont)fontWithName:(CPString)aName size:(float)aSize
224 {
225  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, NO, NO);
226 }
227 
236 + (CPFont)fontWithName:(CPString)aName size:(float)aSize italic:(BOOL)italic
237 {
238  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, NO, italic);
239 }
240 
248 + (CPFont)boldFontWithName:(CPString)aName size:(float)aSize
249 {
250  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, YES, NO);
251 }
252 
261 + (CPFont)boldFontWithName:(CPString)aName size:(float)aSize italic:(BOOL)italic
262 {
263  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, YES, italic);
264 }
265 
269 + (CPFont)_fontWithName:(CPString)aName size:(float)aSize bold:(BOOL)bold italic:(BOOL)italic
270 {
271  return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, bold, italic);
272 }
273 
281 + (CPFont)systemFontOfSize:(CPSize)aSize
282 {
283  return _CPSystemFont(aSize === 0 ? _CPFontSystemFontSize : aSize, NO);
284 }
285 
293 + (CPFont)boldSystemFontOfSize:(CPSize)aSize
294 {
295  return _CPSystemFont(aSize === 0 ? _CPFontSystemFontSize : aSize, YES);
296 }
297 
298 - (id)_initWithName:(CPString)aName size:(float)aSize bold:(BOOL)isBold italic:(BOOL)isItalic system:(BOOL)isSystem
299 {
300  self = [super init];
301 
302  if (self)
303  {
304  _size = aSize;
305  _ascender = 0;
306  _descender = 0;
307  _lineHeight = 0;
308  _isBold = isBold;
309  _isItalic = isItalic;
310  _isSystem = isSystem;
311 
312  if (isSystem)
313  {
314  _name = aName;
315  _cssString = _CPFontCreateCSSString(_CPFontSystemFontFace, _size, _isBold, _isItalic);
316  _CPSystemFontCache[_CPSystemFontCacheKey(_size, _isBold)] = self;
317  }
318  else
319  {
320  _name = _CPFontNormalizedNames(aName);
321  _cssString = _CPFontCreateCSSString(_name, _size, _isBold, _isItalic);
322  _CPFontCache[_cssString] = self;
323  }
324  }
325 
326  return self;
327 }
328 
332 - (float)ascender
333 {
334  var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
335 
336  if (!font._ascender)
337  [font _getMetrics];
338 
339  return font._ascender;
340 }
341 
346 - (float)descender
347 {
348  var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
349 
350  if (!font._descender)
351  [font _getMetrics];
352 
353  return font._descender;
354 }
355 
361 - (float)defaultLineHeightForFont
362 {
363  var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
364 
365  if (!font._lineHeight)
366  [font _getMetrics];
367 
368  return font._lineHeight;
369 }
370 
374 - (float)size
375 {
376  return _CPRealFontSize(_size);
377 }
378 
382 - (CPString)cssString
383 {
384  var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
385 
386  return font._cssString;
387 }
388 
392 - (CPString)familyName
393 {
394  if (_isSystem)
395  return _CPFontSystemFontFace;
396 
397  return _name;
398 }
399 
400 - (BOOL)isSystemSize
401 {
402  return _size <= 0;
403 }
404 
405 - (BOOL)isEqual:(id)anObject
406 {
407  return [anObject isKindOfClass:[CPFont class]] && [anObject cssString] === _cssString;
408 }
409 
410 - (CPString)description
411 {
412  return [CPString stringWithFormat:@"%@ %@", [super description], [self cssString]];
413 }
414 
415 - (id)copy
416 {
417  return [[CPFont alloc] _initWithName:_name size:_size bold:_isBold italic:_isItalic system:_isSystem];
418 }
419 
420 - (void)_getMetrics
421 {
422  var metrics = [CPString metricsOfFont:self];
423 
424  _ascender = [metrics objectForKey:@"ascender"];
425  _descender = [metrics objectForKey:@"descender"];
426  _lineHeight = [metrics objectForKey:@"lineHeight"];
427 }
428 
429 @end
430 
431 var CPFontNameKey = @"CPFontNameKey",
432  CPFontSizeKey = @"CPFontSizeKey",
433  CPFontIsBoldKey = @"CPFontIsBoldKey",
434  CPFontIsItalicKey = @"CPFontIsItalicKey",
435  CPFontIsSystemKey = @"CPFontIsSystemKey";
436 
437 @implementation CPFont (CPCoding)
438 
444 - (id)initWithCoder:(CPCoder)aCoder
445 {
446  var fontName = [aCoder decodeObjectForKey:CPFontNameKey],
447  size = [aCoder decodeFloatForKey:CPFontSizeKey],
448  isBold = [aCoder decodeBoolForKey:CPFontIsBoldKey],
449  isItalic = [aCoder decodeBoolForKey:CPFontIsItalicKey],
450  isSystem = [aCoder decodeBoolForKey:CPFontIsSystemKey];
451 
452  return [self _initWithName:fontName size:size bold:isBold italic:isItalic system:isSystem];
453 }
454 
459 - (void)encodeWithCoder:(CPCoder)aCoder
460 {
461  [aCoder encodeObject:_name forKey:CPFontNameKey];
462  [aCoder encodeFloat:_size forKey:CPFontSizeKey];
463  [aCoder encodeBool:_isBold forKey:CPFontIsBoldKey];
464  [aCoder encodeBool:_isItalic forKey:CPFontIsItalicKey];
465  [aCoder encodeBool:_isSystem forKey:CPFontIsSystemKey];
466 }
467 
468 @end
469 
470 
471 // aName must be normalized
472 var _CPFontCreateCSSString = function(aName, aSize, isBold, isItalic)
473 {
474  var properties = (isItalic ? "italic " : "") + (isBold ? "bold " : "") + _CPRealFontSize(aSize) + "px ";
475 
476  return properties + _CPFontConcatNameWithFallback(aName);
477 };
478 
479 var _CPFontConcatNameWithFallback = function(aName)
480 {
481  var names = _CPFontNormalizedNameArray(aName),
482  fallbackFaces = _CPFontFallbackFaces.slice(0);
483 
484  // Remove the fallback names used in the names passed in
485  for (var i = 0; i < names.length; ++i)
486  {
487  for (var j = 0; j < fallbackFaces.length; ++j)
488  {
489  if (names[i].toLowerCase() === fallbackFaces[j].toLowerCase())
490  {
491  fallbackFaces.splice(j, 1);
492  break;
493  }
494  }
495 
496  if (names[i].indexOf(" ") > 0)
497  names[i] = '"' + names[i] + '"';
498  }
499 
500  return names.concat(fallbackFaces).join(", ");
501 };
502 
503 var _CPFontNormalizedNameArray = function(aName)
504 {
505  var names = aName.split(",");
506 
507  for (var i = 0; i < names.length; ++i)
508  names[i] = names[i].replace(_CPFontStripRegExp, "");
509 
510  return names;
511 };
512 
514 
518 - (BOOL)isBold
519 {
520  return _isBold;
521 }
522 
526 - (BOOL)isItalic
527 {
528  return _isItalic;
529 }
530 
534 - (BOOL)isSystem
535 {
536  return _isSystem;
537 }
538 
539 @end