You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
6.2 KiB

4 years ago
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = function (nodes, opts) {
  6. let family = [];
  7. let last = null;
  8. let i, max;
  9. nodes.forEach((node, index, arr) => {
  10. if (node.type === 'string' || node.type === 'function') {
  11. family.push(node);
  12. } else if (node.type === 'word') {
  13. if (!last) {
  14. last = { type: 'word', value: '' };
  15. family.push(last);
  16. }
  17. last.value += node.value;
  18. } else if (node.type === 'space') {
  19. if (last && index !== arr.length - 1) {
  20. last.value += ' ';
  21. }
  22. } else {
  23. last = null;
  24. }
  25. });
  26. family = family.map(node => {
  27. if (node.type === 'string') {
  28. const isKeyword = regexKeyword.test(node.value);
  29. if (!opts.removeQuotes || isKeyword || /[0-9]/.test(node.value.slice(0, 1))) {
  30. return (0, _postcssValueParser.stringify)(node);
  31. }
  32. let escaped = escapeIdentifierSequence(node.value);
  33. if (escaped.length < node.value.length + 2) {
  34. return escaped;
  35. }
  36. }
  37. return (0, _postcssValueParser.stringify)(node);
  38. });
  39. if (opts.removeAfterKeyword) {
  40. for (i = 0, max = family.length; i < max; i += 1) {
  41. if (~genericFontFamilykeywords.indexOf(family[i].toLowerCase())) {
  42. family = family.slice(0, i + 1);
  43. break;
  44. }
  45. }
  46. }
  47. if (opts.removeDuplicates) {
  48. family = uniqs(family);
  49. }
  50. return [{
  51. type: 'word',
  52. value: family.join()
  53. }];
  54. };
  55. var _postcssValueParser = require('postcss-value-parser');
  56. var _uniqs = require('./uniqs');
  57. var _uniqs2 = _interopRequireDefault(_uniqs);
  58. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  59. const uniqs = (0, _uniqs2.default)('monospace');
  60. const globalKeywords = ['inherit', 'initial', 'unset'];
  61. const genericFontFamilykeywords = ['sans-serif', 'serif', 'fantasy', 'cursive', 'monospace', 'system-ui'];
  62. function makeArray(value, length) {
  63. let array = [];
  64. while (length--) {
  65. array[length] = value;
  66. }
  67. return array;
  68. }
  69. const regexSimpleEscapeCharacters = /[ !"#$%&'()*+,.\/;<=>?@\[\\\]^`{|}~]/;
  70. function escape(string, escapeForString) {
  71. let counter = 0;
  72. let character = null;
  73. let charCode = null;
  74. let value = null;
  75. let output = '';
  76. while (counter < string.length) {
  77. character = string.charAt(counter++);
  78. charCode = character.charCodeAt();
  79. // \r is already tokenized away at this point
  80. // `:` can be escaped as `\:`, but that fails in IE < 8
  81. if (!escapeForString && /[\t\n\v\f:]/.test(character)) {
  82. value = '\\' + charCode.toString(16) + ' ';
  83. } else if (!escapeForString && regexSimpleEscapeCharacters.test(character)) {
  84. value = '\\' + character;
  85. } else {
  86. value = character;
  87. }
  88. output += value;
  89. }
  90. if (!escapeForString) {
  91. if (/^-[-\d]/.test(output)) {
  92. output = '\\-' + output.slice(1);
  93. }
  94. const firstChar = string.charAt(0);
  95. if (/\d/.test(firstChar)) {
  96. output = '\\3' + firstChar + ' ' + output.slice(1);
  97. }
  98. }
  99. return output;
  100. }
  101. const regexKeyword = new RegExp(genericFontFamilykeywords.concat(globalKeywords).join('|'), 'i');
  102. const regexInvalidIdentifier = /^(-?\d|--)/;
  103. const regexSpaceAtStart = /^\x20/;
  104. const regexWhitespace = /[\t\n\f\r\x20]/g;
  105. const regexIdentifierCharacter = /^[a-zA-Z\d\xa0-\uffff_-]+$/;
  106. const regexConsecutiveSpaces = /(\\(?:[a-fA-F0-9]{1,6}\x20|\x20))?(\x20{2,})/g;
  107. const regexTrailingEscape = /\\[a-fA-F0-9]{0,6}\x20$/;
  108. const regexTrailingSpace = /\x20$/;
  109. function escapeIdentifierSequence(string) {
  110. let identifiers = string.split(regexWhitespace);
  111. let index = 0;
  112. let result = [];
  113. let escapeResult;
  114. while (index < identifiers.length) {
  115. let subString = identifiers[index++];
  116. if (subString === '') {
  117. result.push(subString);
  118. continue;
  119. }
  120. escapeResult = escape(subString, false);
  121. if (regexIdentifierCharacter.test(subString)) {
  122. // the font family name part consists of allowed characters exclusively
  123. if (regexInvalidIdentifier.test(subString)) {
  124. // the font family name part starts with two hyphens, a digit, or a
  125. // hyphen followed by a digit
  126. if (index === 1) {
  127. // if this is the first item
  128. result.push(escapeResult);
  129. } else {
  130. // if it’s not the first item, we can simply escape the space
  131. // between the two identifiers to merge them into a single
  132. // identifier rather than escaping the start characters of the
  133. // second identifier
  134. result[index - 2] += '\\';
  135. result.push(escape(subString, true));
  136. }
  137. } else {
  138. // the font family name part doesn’t start with two hyphens, a digit,
  139. // or a hyphen followed by a digit
  140. result.push(escapeResult);
  141. }
  142. } else {
  143. // the font family name part contains invalid identifier characters
  144. result.push(escapeResult);
  145. }
  146. }
  147. result = result.join(' ').replace(regexConsecutiveSpaces, ($0, $1, $2) => {
  148. const spaceCount = $2.length;
  149. const escapesNeeded = Math.floor(spaceCount / 2);
  150. const array = makeArray('\\ ', escapesNeeded);
  151. if (spaceCount % 2) {
  152. array[escapesNeeded - 1] += '\\ ';
  153. }
  154. return ($1 || '') + ' ' + array.join(' ');
  155. });
  156. // Escape trailing spaces unless they’re already part of an escape
  157. if (regexTrailingSpace.test(result) && !regexTrailingEscape.test(result)) {
  158. result = result.replace(regexTrailingSpace, '\\ ');
  159. }
  160. if (regexSpaceAtStart.test(result)) {
  161. result = '\\ ' + result.slice(1);
  162. }
  163. return result;
  164. }
  165. ;
  166. module.exports = exports['default'];