findFontFamily.js 3.1 KB

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