css-font-property-parser.js 7.8 KB

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