findFontFamily.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. 'use strict';
  2. const isNumbery = require('./isNumbery');
  3. const isStandardSyntaxValue = require('./isStandardSyntaxValue');
  4. const isValidFontSize = require('./isValidFontSize');
  5. const isVariable = require('./isVariable');
  6. const keywordSets = require('../reference/keywordSets');
  7. const postcssValueParser = require('postcss-value-parser');
  8. const nodeTypesToCheck = new Set(['word', 'string', 'space', 'div']);
  9. /** @typedef {import('postcss-value-parser').Node} Node */
  10. /**
  11. *
  12. * @param {Node} firstNode
  13. * @param {Node} secondNode
  14. * @param {string | null} charactersBetween
  15. *
  16. * @returns {Node}
  17. */
  18. function joinValueNodes(firstNode, secondNode, charactersBetween) {
  19. firstNode.value = firstNode.value + charactersBetween + secondNode.value;
  20. return firstNode;
  21. }
  22. /**
  23. * Get the font-families within a `font` shorthand property value.
  24. *
  25. * @param {string} value
  26. * @returns {Node[]} Collection font-family nodes
  27. */
  28. module.exports = function findFontFamily(value) {
  29. /** @type {Node[]} */
  30. const fontFamilies = [];
  31. const valueNodes = postcssValueParser(value);
  32. // Handle `inherit`, `initial` and etc
  33. if (
  34. valueNodes.nodes.length === 1 &&
  35. keywordSets.basicKeywords.has(valueNodes.nodes[0].value.toLowerCase())
  36. ) {
  37. return [valueNodes.nodes[0]];
  38. }
  39. let needMergeNodesByValue = false;
  40. /** @type {string | null} */
  41. let mergeCharacters = null;
  42. valueNodes.walk((valueNode, index, nodes) => {
  43. if (valueNode.type === 'function') {
  44. return false;
  45. }
  46. if (!nodeTypesToCheck.has(valueNode.type)) {
  47. return;
  48. }
  49. const valueLowerCase = valueNode.value.toLowerCase();
  50. // Ignore non standard syntax
  51. if (!isStandardSyntaxValue(valueLowerCase)) {
  52. return;
  53. }
  54. // Ignore variables
  55. if (isVariable(valueLowerCase)) {
  56. return;
  57. }
  58. // Ignore keywords for other font parts
  59. if (
  60. keywordSets.fontShorthandKeywords.has(valueLowerCase) &&
  61. !keywordSets.fontFamilyKeywords.has(valueLowerCase)
  62. ) {
  63. return;
  64. }
  65. // Ignore font-sizes
  66. if (isValidFontSize(valueNode.value)) {
  67. return;
  68. }
  69. // Ignore anything come after a <font-size>/, because it's a line-height
  70. if (
  71. nodes[index - 1] &&
  72. nodes[index - 1].value === '/' &&
  73. nodes[index - 2] &&
  74. isValidFontSize(nodes[index - 2].value)
  75. ) {
  76. return;
  77. }
  78. // Ignore number values
  79. if (isNumbery(valueLowerCase)) {
  80. return;
  81. }
  82. // Detect when a space or comma is dividing a list of font-families, and save the joining character.
  83. if (
  84. (valueNode.type === 'space' || (valueNode.type === 'div' && valueNode.value !== ',')) &&
  85. fontFamilies.length !== 0
  86. ) {
  87. needMergeNodesByValue = true;
  88. mergeCharacters = valueNode.value;
  89. return;
  90. }
  91. if (valueNode.type === 'space' || valueNode.type === 'div') {
  92. return;
  93. }
  94. const fontFamily = valueNode;
  95. if (needMergeNodesByValue) {
  96. joinValueNodes(fontFamilies[fontFamilies.length - 1], valueNode, mergeCharacters);
  97. needMergeNodesByValue = false;
  98. mergeCharacters = null;
  99. } else {
  100. fontFamilies.push(fontFamily);
  101. }
  102. });
  103. return fontFamilies;
  104. };