tags.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  2. <script src="https://unpkg.com/papaparse@5.4.1/papaparse.min.js"></script>
  3. <link rel="preconnect" href="https://fonts.googleapis.com">
  4. <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  5. <body>
  6. <div id="app">
  7. <link v-for="family in uniqueFamilies" :href="familyLink(family)" rel="stylesheet">
  8. <h1>Google Fonts Tagger</h1>
  9. <div id="panel">
  10. <div class="panel-tile">
  11. <label>Current tag:</label>
  12. <select v-model="CurrentCategory" style="max-width: 300px;">
  13. <option v-for="category in sortedCategories" :key="category" :value="category">
  14. {{ category }}
  15. </option>
  16. </select>
  17. </div>
  18. <div class="panel-tile">
  19. <form @submit.prevent="AddFamily">
  20. <label>Add family:</label>
  21. <input list="items" v-model="newFamily" required placeholder="Family Name">
  22. <datalist id="items">
  23. <option v-for="family in uniqueFamilies" :value="family">
  24. </datalist>
  25. <input v-model="newWeight" required placeholder="Score">
  26. <button>Add</button>
  27. </form>
  28. </div>
  29. <div style="border: 0.1px solid rgb(161, 161, 161); margin-bottom: 10pt; margin-top: 10pt;">
  30. </div>
  31. <div class="panel-tile">
  32. <button @click="prCSV">Open Pull Request</button>
  33. <button style="float: right;" @click="saveCSV">Save CSV</button>
  34. </div>
  35. <div v-if="isEdited" id="edited-panel">Edited</div>
  36. </div>
  37. <div class="item" v-for="family in sortedFamilies" :key="family.Family">
  38. <div style="float: left; width: 150px;">
  39. <b>{{ family.Family }}</b>
  40. </div>
  41. <div style="float: left; width: 100px;">
  42. <input style="width: 50px;" v-model.lazy="family.Weight" @input="edited" placeholder="family.Weight">
  43. <button @click="removeFamily(family)">X</button>
  44. </div>
  45. <div v-if="ready" :style="familyStyle(family)" contenteditable="true">
  46. {{ familyPangram(family) }}
  47. </div>
  48. <div v-else>
  49. Loading...
  50. </div>
  51. </div>
  52. </div>
  53. </body>
  54. <script>
  55. var app = new Vue({
  56. el: '#app',
  57. data() {
  58. return {
  59. ready: false,
  60. isEdited: false,
  61. newFamily: '',
  62. newWeight: '',
  63. CurrentCategory: "/Expressive/Calm",
  64. Categories: new Set(),
  65. Families: [],
  66. Pangrams: new Map([
  67. ["English", "The quick brown fox jumps over the lazy dog."],
  68. ["Greek", "Ζαφείρι δέξου πάγκαλο, βαθῶν ψυχῆς τὸ σῆμα"],
  69. ["Cyrillic", "В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!"],
  70. ["Japanese", "いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす(ん"],
  71. ["Chinese", "視野無限廣,窗外有藍天"],
  72. ["Arabic", "نص حكيم له سر قاطع وذو شأن عظيم مكتوب على ثوب أخضر ومغلف بجلد أزرق"],
  73. ["Hebrew", "שפן אכל קצת גזר בטעם חסה, ודי."],
  74. ["Devanagari", "ऋषियों को सताने वाले दुष्ट राक्षसों के राजा रावण का सर्वनाश करने वाले विष्णुवतार भगवान श्रीराम, अयोध्या के महाराज दशरथ के बड़े सपुत्र थे।"],
  75. ["Bengali", "যেহেতু মানব পরিবারের সকল সদস্যের সমান ও অবিচ্ছেদ্য অধিকারসমূহ"],
  76. ["Gujarati", "કેમ કે માનવકુટુંબના દરેક સભ્યની પરંપરાપ્રાપ્ત પ્રતિષ્ઠાને અને"],
  77. ["Telugu", "మానవకుటంబమునందలి వ్యక్తులందరికిని గల ఆజన్మసిద్ధమైన ప్రతిపత్తిని"],
  78. ["Kannada", "ಎಲ್ಲಾ ಮಾನವರೂ ಸ್ವತಂತ್ರರಾಗಿಯೇ ಜನಿಸಿದ್ದಾರೆ. ಹಾಗೂ ಘನತೆ ಮತ್ತು ಹಕ್ಕುಗಳಲ್ಲಿ"],
  79. ["Khmer", "ដោយយល់ឃើញថា ការទទួលស្គាល់សេចក្ដីថ្លៃថ្នូរជាប់ពីកំណើត និងសិទ្ធិស្មើភាពគ្នា"],
  80. ["Phags Pa", "ꡗ ꡈꡱ ᠂ ꡒ ꡂ ꡈꡞ ᠂ ꡚꡖꡋ ꡈꡞꡋꡨꡖ ꡗꡛꡧꡖ ꡈꡋ ꡈꡱꡨꡖ ꡳꡬꡖ"],
  81. ["Tamil", "மனிதக் குடும்பத்தினைச் சேர்ந்த யாவரதும் உள்ளார்ந்த"],
  82. ]),
  83. FamilyScripts: new Map(),
  84. };
  85. },
  86. created() {
  87. this.loadCSV();
  88. this.loadFamilyPangrams();
  89. },
  90. computed: {
  91. sortedFamilies() {
  92. let ll = this.Families;
  93. let filtered = ll.filter(family => family["Group/Tag"] === this.CurrentCategory);
  94. filtered.sort(function(a, b) {return b.Weight - a.Weight;});
  95. return filtered;
  96. },
  97. uniqueFamilies() {
  98. return Array.from(new Set(this.Families.map((family) => family.Family)));
  99. },
  100. sortedCategories() {
  101. return Array.from(this.Categories).sort();
  102. }
  103. },
  104. methods: {
  105. familyPangram(family) {
  106. return this.Pangrams.get(this.FamilyScripts.get(family.Family));
  107. },
  108. edited() {
  109. this.isEdited = true;
  110. },
  111. parseUnicode(str) {
  112. let ranges = str.split(",");
  113. let script = "English";
  114. let scripts = {
  115. "U+600-6FF": "Arabic",
  116. "U+900-97F": "Devanagari",
  117. "U+590-5FF": "Hebrew",
  118. "U+A80-AFF": "Gujarati",
  119. "U+C00-C7F": "Telugu",
  120. "U+C80-CFF": "Kannada",
  121. "U+980-9FE": "Bengali",
  122. "U+1780-17FF": "Khmer",
  123. "U+A840-A877": "Phags Pa",
  124. "U+0B82-0BFA": "Tamil",
  125. }
  126. for (let i = 0; i < ranges.length; i++) {
  127. for (let key in scripts) {
  128. if (ranges[i].includes(key)) {
  129. script = scripts[key];
  130. break;
  131. }
  132. }
  133. }
  134. return script;
  135. },
  136. async loadFamilyPangrams(delay = 1000) {
  137. await document.fonts.ready;
  138. let result = new Map();
  139. let fonts = document.fonts;
  140. fonts.forEach((font) => {
  141. if (!result.has(font.family)) {
  142. result.set(font.family, this.parseUnicode(font.unicodeRange));
  143. }
  144. });
  145. console.log(result.size)
  146. if (result.size < 1000) {
  147. console.log("retry")
  148. setTimeout(() => this.loadFamilyPangrams(), delay);
  149. }
  150. this.FamilyScripts = result;
  151. this.ready = true;
  152. },
  153. familyLink(Family) {
  154. return "https://fonts.googleapis.com/css2?family=" + Family.replace(" ", "+") + "&display=swap"
  155. },
  156. familyCSSClass(Family) {
  157. let cssName = Family.family.replace(" ", "-").toLowerCase();
  158. return `.${cssName} {
  159. font-family: "${Family.family}", sans-serif;
  160. font-weight: 400;
  161. font-style: normal;
  162. }`
  163. },
  164. familySelector(Family) {
  165. let cssName = Family.Family.replace(" ", "-").toLowerCase();
  166. return cssName;
  167. },
  168. familyStyle(Family) {
  169. return `font-family: "${Family.Family}", "Adobe NotDef"; font-size: 32pt;`
  170. },
  171. AddFamily() {
  172. this.isEdited = true;
  173. this.Families.push({ Weight: this.newWeight, Family: this.newFamily, "Group/Tag": this.CurrentCategory });
  174. },
  175. removeFamily(Family) {
  176. this.isEdited = true;
  177. this.Families = this.Families.filter((t) => t !== Family)
  178. },
  179. saveCSV() {
  180. this.Families = this.Families.filter((t) => t.Family !== "");
  181. this.Families = Array.from(this.Families).sort((a, b) => {
  182. if (`${a.Family},${a['Group/Tag']}` < `${b.Family},${b['Group/Tag']}`) {
  183. return -1;
  184. }
  185. if (`${a.Family},${a['Group/Tag']}` > `${b.Family},${b['Group/Tag']}`) {
  186. return 1;
  187. }
  188. return 0;
  189. });
  190. let csv = Papa.unparse(this.Families,
  191. {
  192. columns: ["Family", "Group/Tag", "Weight"],
  193. skipEmptyLines: true,
  194. }
  195. );
  196. const blob = new Blob([csv], { type: 'text/csv' });
  197. const url = URL.createObjectURL(blob);
  198. const a = document.createElement('a');
  199. a.href = url;
  200. a.download = "families.csv";
  201. document.body.appendChild(a);
  202. a.click();
  203. document.body.removeChild(a);
  204. URL.revokeObjectURL(url);
  205. },
  206. prCSV() {
  207. this.Families = this.Families.filter((t) => t.Family !== "");
  208. this.Families = Array.from(this.Families).sort((a, b) => {
  209. if (`${a.Family},${a['Group/Tag']}` < `${b.Family},${b['Group/Tag']}`) {
  210. return -1;
  211. }
  212. if (`${a.Family},${a['Group/Tag']}` > `${b.Family},${b['Group/Tag']}`) {
  213. return 1;
  214. }
  215. return 0;
  216. });
  217. let csv = Papa.unparse(this.Families,
  218. {
  219. columns: ["Family", "Group/Tag", "Weight"],
  220. skipEmptyLines: true,
  221. }
  222. );
  223. alert("Tag data copied to clipboard. A github pull request page will open in a new tab. Please remove the old data and paste in the new.");
  224. navigator.clipboard.writeText(csv);
  225. window.open("https://github.com/google/fonts/edit/main/tags/all/families.csv")
  226. },
  227. loadCSV() {
  228. const csvFilePath = 'https://raw.githubusercontent.com/google/fonts/main/tags/all/families.csv'; // Update this path to your CSV file
  229. fetch(csvFilePath)
  230. .then(response => response.text())
  231. .then(csvText => {
  232. Papa.parse(csvText, {
  233. header: true,
  234. complete: (results) => {
  235. this.Categories = new Set(results.data.map((row) => row["Group/Tag"]));
  236. this.Families = results.data.map((row) => ({
  237. Weight: row.Weight,
  238. Family: row.Family,
  239. "Group/Tag": row["Group/Tag"]
  240. })
  241. );
  242. }
  243. });
  244. })
  245. .catch(error => {
  246. console.error('Error loading CSV file:', error);
  247. });
  248. }
  249. }
  250. } // methods
  251. )
  252. </script>
  253. <style>
  254. @font-face {
  255. font-family: "Adobe NotDef";
  256. src: url(https://cdn.jsdelivr.net/gh/adobe-fonts/adobe-notdef/AND-Regular.ttf);
  257. }
  258. #app {
  259. font-family: "Roboto", Helvetica, Arial, sans-serif;
  260. -webkit-font-smoothing: antialiased;
  261. -moz-osx-font-smoothing: grayscale;
  262. text-align: left;
  263. color: #2c3e50;
  264. margin-top: 60px;
  265. }
  266. #panel {
  267. position: fixed;
  268. top: 0;
  269. right: 0;
  270. padding: 10px;
  271. background-color: white;
  272. box-shadow: 3px 3px 3px lightgray;
  273. }
  274. .panel-tile {
  275. margin-bottom: 10px;
  276. }
  277. .familyy {
  278. padding-top: 10px;
  279. padding-bottom: 10px;
  280. }
  281. .item {
  282. margin-top: 10px;
  283. padding-top: 10px;
  284. padding-bottom: 10px;
  285. border-top: 1px solid #000;
  286. }
  287. #edited-panel {
  288. position: fixed;
  289. left: 0px;
  290. top: 0px;
  291. width: 80px;
  292. padding: 5px;
  293. background-color: black;
  294. color: white;
  295. font-weight: bold;
  296. }
  297. </style>