css-font-property-parser.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /*
  2. * The MIT License (MIT)
  3. *
  4. * Copyright (c) 2015 Jed Mao
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. * The above copyright notice and this permission notice shall be included in all
  13. * copies or substantial portions of the Software.
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. // derived from https://github.com/jedmao/parse-css-font/
  23. this.fontPropertyParser = (() => {
  24. const REGEXP_SIMPLE_QUOTES_STRING = /^'(.*?)'$/;
  25. const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;
  26. const globalKeywords = [
  27. "inherit",
  28. "initial",
  29. "unset"
  30. ];
  31. const systemFontKeywords = [
  32. "caption",
  33. "icon",
  34. "menu",
  35. "message-box",
  36. "small-caption",
  37. "status-bar"
  38. ];
  39. const fontWeightKeywords = [
  40. "normal",
  41. "bold",
  42. "bolder",
  43. "lighter",
  44. "100",
  45. "200",
  46. "300",
  47. "400",
  48. "500",
  49. "600",
  50. "700",
  51. "800",
  52. "900"
  53. ];
  54. const fontStyleKeywords = [
  55. "normal",
  56. "italic",
  57. "oblique"
  58. ];
  59. const fontStretchKeywords = [
  60. "normal",
  61. "condensed",
  62. "semi-condensed",
  63. "extra-condensed",
  64. "ultra-condensed",
  65. "expanded",
  66. "semi-expanded",
  67. "extra-expanded",
  68. "ultra-expanded"
  69. ];
  70. const cssFontSizeKeywords = [
  71. "xx-small",
  72. "x-small",
  73. "small",
  74. "medium",
  75. "large",
  76. "x-large",
  77. "xx-large",
  78. "larger",
  79. "smaller"
  80. ];
  81. const cssListHelpers = {
  82. splitBySpaces,
  83. split,
  84. splitByCommas
  85. };
  86. const helpers = {
  87. isSize
  88. };
  89. const errorPrefix = "[parse-css-font] ";
  90. return {
  91. parse(value) {
  92. if (typeof value !== "string") {
  93. throw new TypeError(errorPrefix + "Expected a string.");
  94. }
  95. if (value === "") {
  96. throw error("Cannot parse an empty string.");
  97. }
  98. if (systemFontKeywords.indexOf(value) !== -1) {
  99. return { system: value };
  100. }
  101. const font = {
  102. lineHeight: "normal",
  103. stretch: "normal",
  104. style: "normal",
  105. variant: "normal",
  106. weight: "normal",
  107. };
  108. let isLocked = false;
  109. const tokens = cssListHelpers.splitBySpaces(value);
  110. let token = tokens.shift();
  111. for (; token; token = tokens.shift()) {
  112. if (token === "normal" || globalKeywords.indexOf(token) !== -1) {
  113. ["style", "variant", "weight", "stretch"].forEach((prop) => {
  114. font[prop] = token;
  115. });
  116. isLocked = true;
  117. continue;
  118. }
  119. if (fontWeightKeywords.indexOf(token) !== -1) {
  120. if (isLocked) {
  121. continue;
  122. }
  123. font.weight = token;
  124. continue;
  125. }
  126. if (fontStyleKeywords.indexOf(token) !== -1) {
  127. if (isLocked) {
  128. continue;
  129. }
  130. font.style = token;
  131. continue;
  132. }
  133. if (fontStretchKeywords.indexOf(token) !== -1) {
  134. if (isLocked) {
  135. continue;
  136. }
  137. font.stretch = token;
  138. continue;
  139. }
  140. if (helpers.isSize(token)) {
  141. const parts = cssListHelpers.split(token, ["/"]);
  142. font.size = parts[0];
  143. if (parts[1]) {
  144. font.lineHeight = parseLineHeight(parts[1]);
  145. } else if (tokens[0] === "/") {
  146. tokens.shift();
  147. font.lineHeight = parseLineHeight(tokens.shift());
  148. }
  149. if (!tokens.length) {
  150. throw error("Missing required font-family.");
  151. }
  152. font.family = cssListHelpers.splitByCommas(tokens.join(" ")).map(removeQuotes);
  153. return font;
  154. }
  155. if (font.variant !== "normal") {
  156. throw error("Unknown or unsupported font token: " + font.variant);
  157. }
  158. if (isLocked) {
  159. continue;
  160. }
  161. font.variant = token;
  162. }
  163. throw error("Missing required font-size.");
  164. }
  165. };
  166. function error(message) {
  167. return new Error(errorPrefix + message);
  168. }
  169. function parseLineHeight(value) {
  170. const parsed = parseFloat(value);
  171. if (parsed.toString() === value) {
  172. return parsed;
  173. }
  174. return value;
  175. }
  176. /**
  177. * Splits a CSS declaration value (shorthand) using provided separators
  178. * as the delimiters.
  179. */
  180. function split(
  181. /**
  182. * A CSS declaration value (shorthand).
  183. */
  184. value,
  185. /**
  186. * Any number of separator characters used for splitting.
  187. */
  188. separators,
  189. {
  190. last = false,
  191. } = {},
  192. ) {
  193. if (typeof value !== "string") {
  194. throw new TypeError("expected a string");
  195. }
  196. if (!Array.isArray(separators)) {
  197. throw new TypeError("expected a string array of separators");
  198. }
  199. if (typeof last !== "boolean") {
  200. throw new TypeError("expected a Boolean value for options.last");
  201. }
  202. const array = [];
  203. let current = "";
  204. let splitMe = false;
  205. let func = 0;
  206. let quote = false;
  207. let escape = false;
  208. for (const char of value) {
  209. if (quote) {
  210. if (escape) {
  211. escape = false;
  212. } else if (char === "\\") {
  213. escape = true;
  214. } else if (char === quote) {
  215. quote = false;
  216. }
  217. } else if (char === "\"" || char === "'") {
  218. quote = char;
  219. } else if (char === "(") {
  220. func += 1;
  221. } else if (char === ")") {
  222. if (func > 0) {
  223. func -= 1;
  224. }
  225. } else if (func === 0) {
  226. if (separators.indexOf(char) !== -1) {
  227. splitMe = true;
  228. }
  229. }
  230. if (splitMe) {
  231. if (current !== "") {
  232. array.push(current.trim());
  233. }
  234. current = "";
  235. splitMe = false;
  236. } else {
  237. current += char;
  238. }
  239. }
  240. if (last || current !== "") {
  241. array.push(current.trim());
  242. }
  243. return array;
  244. }
  245. /**
  246. * Splits a CSS declaration value (shorthand) using whitespace characters
  247. * as the delimiters.
  248. */
  249. function splitBySpaces(
  250. /**
  251. * A CSS declaration value (shorthand).
  252. */
  253. value,
  254. ) {
  255. const spaces = [" ", "\n", "\t"];
  256. return split(value, spaces);
  257. }
  258. /**
  259. * Splits a CSS declaration value (shorthand) using commas as the delimiters.
  260. */
  261. function splitByCommas(
  262. /**
  263. * A CSS declaration value (shorthand).
  264. */
  265. value,
  266. ) {
  267. const comma = ",";
  268. return split(value, [comma], { last: true });
  269. }
  270. function isSize(value) {
  271. return !isNaN(parseFloat(value))
  272. || value.indexOf("/") !== -1
  273. || cssFontSizeKeywords.indexOf(value) !== -1;
  274. }
  275. function removeQuotes(string) {
  276. if (string.match(REGEXP_SIMPLE_QUOTES_STRING)) {
  277. string = string.replace(REGEXP_SIMPLE_QUOTES_STRING, "$1");
  278. } else {
  279. string = string.replace(REGEXP_DOUBLE_QUOTES_STRING, "$1");
  280. }
  281. return string.trim();
  282. }
  283. })();