index.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // Inspriation from node-canvas (https://github.com/LearnBoost/node-canvas/)
  2. var cache = {};
  3. // regex hoisted from http://stackoverflow.com/questions/10135697/regex-to-parse-any-css-font
  4. var fontRegex = new RegExp([
  5. '^\\s*(?=(?:(?:[-a-z]+\\s*){0,2}(italic|oblique))?)',
  6. '(?=(?:(?:[-a-z]+\\s*){0,2}(small-caps))?)',
  7. '(?=(?:(?:[-a-z]+\\s*){0,2}(bold(?:er)?|lighter|[1-9]00 ))?)',
  8. '(?:(?:normal|\\1|\\2|\\3)\\s*){0,3}((?:xx?-)?',
  9. '(?:small|large)|medium|smaller|larger|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx]))',
  10. '(?:\\s*\\/\\s*(normal|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx])?))',
  11. '?\\s*([-,\\"\\\'\\sa-z]+?)\\s*$'
  12. ].join(''), 'i');
  13. var mapping = [
  14. 'style',
  15. 'variant',
  16. 'weight',
  17. 'size',
  18. 'lineHeight',
  19. 'family'
  20. ];
  21. var unitMatch = /([\.0-9]+)(.*)/;
  22. var numeric = function(val, parent, dpi) {
  23. var matches = val.match(unitMatch);
  24. if (!matches) {
  25. return;
  26. }
  27. val = parseFloat(matches[1]);
  28. var units = matches[2].toLowerCase().trim();
  29. var v = Math.round(val);
  30. if (v === val) {
  31. val = v;
  32. }
  33. switch (units) {
  34. case 'em':
  35. if (parent === null) {
  36. return;
  37. }
  38. return val * parent;
  39. break;
  40. case 'px':
  41. return val;
  42. break;
  43. case 'pt':
  44. return val / (72/dpi);
  45. break;
  46. case 'pc':
  47. return val / (6/dpi);
  48. break;
  49. case 'mm':
  50. return val * (dpi/25.4)
  51. break;
  52. case 'cm':
  53. return val * (dpi/2.54)
  54. break;
  55. case 'in':
  56. return val * dpi;
  57. break;
  58. case '%':
  59. if (parent === null) {
  60. return;
  61. }
  62. return parent * (val/100);
  63. break;
  64. }
  65. };
  66. var op = {
  67. size: numeric,
  68. lineHeight: numeric
  69. };
  70. var generics = {
  71. serif : 1,
  72. 'sans-serif': 1,
  73. cursive: 1,
  74. fantasy: 1,
  75. monospace: 1
  76. };
  77. var parse = module.exports = function(str, existing, dpi) {
  78. var cacheKey = str + '-' + (existing || 'null') +'@' + dpi;
  79. dpi = dpi || 96.0;
  80. if (typeof cache[cacheKey] !== 'undefined') {
  81. return cache[cacheKey];
  82. }
  83. if (existing) {
  84. existing = parse(existing, null, dpi);
  85. }
  86. if (str === 'inherit') {
  87. return existing;
  88. }
  89. var matches = fontRegex.exec(str);
  90. if (!matches) {
  91. cache[cacheKey] = null;
  92. return;
  93. }
  94. matches.shift();
  95. var collected = {};
  96. for (var i=0; i<matches.length; i++) {
  97. var key = mapping[i];
  98. var val = matches[i];
  99. if (op[key] && val) {
  100. var existingVal = (existing) ? existing[key] || null : null;
  101. var v = op[key](val, existingVal, dpi);
  102. if (typeof v === 'undefined' && key === 'lineHeight' && val) {
  103. val = collected.size * parseFloat(val);
  104. } else {
  105. val = v;
  106. }
  107. }
  108. if (!val || val === 'normal') {
  109. continue;
  110. } else if (val === 'inherit') {
  111. if (!existing) {
  112. return;
  113. }
  114. val = existing[key];
  115. }
  116. if (val.trim) {
  117. val = val.trim();
  118. }
  119. collected[key] = val;
  120. }
  121. if (!Object.keys(collected).length) {
  122. collected = null;
  123. }
  124. cache[cacheKey] = collected;
  125. var out = [];
  126. if (collected.style) {
  127. out.push(collected.style);
  128. }
  129. if (collected.variant) {
  130. out.push(collected.variant);
  131. }
  132. if (collected.weight &&
  133. collected.weight !== '400' &&
  134. collected.weight !== 'normal')
  135. {
  136. out.push(collected.weight);
  137. }
  138. out.push(collected.size + 'px');
  139. if (collected.lineHeight) {
  140. out[out.length-1] += '/' + collected.lineHeight + 'px';
  141. }
  142. var family = collected.family.split(',');
  143. collected.family = family.map(function(a) {
  144. a = a.trim();
  145. if (generics[a.toLowerCase()]) {
  146. a = a.toLowerCase();
  147. }
  148. return a;
  149. });
  150. out.push(collected.family);
  151. Object.defineProperty(collected, 'toString', {
  152. value: function() {
  153. return out.map(function(val) {
  154. if (Array.isArray(val)) {
  155. return val.map(function(a) {
  156. if (a.indexOf(' ') > -1) {
  157. return '"' + a.replace(/["']/g, '') + '"';
  158. } else {
  159. return a;
  160. }
  161. }).join(', ');
  162. } else {
  163. return val;
  164. }
  165. }).join(' ');
  166. }
  167. });
  168. return collected;
  169. };
  170. module.exports.generics = generics;