hb-subset.cc 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
  1. /*
  2. * Copyright © 2010 Behdad Esfahbod
  3. * Copyright © 2011,2012 Google, Inc.
  4. *
  5. * This is part of HarfBuzz, a text shaping library.
  6. *
  7. * Permission is hereby granted, without written agreement and without
  8. * license or royalty fees, to use, copy, modify, and distribute this
  9. * software and its documentation for any purpose, provided that the
  10. * above copyright notice and the following two paragraphs appear in
  11. * all copies of this software.
  12. *
  13. * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
  14. * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
  15. * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
  16. * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
  17. * DAMAGE.
  18. *
  19. * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
  20. * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  21. * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
  22. * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
  23. * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  24. *
  25. * Google Author(s): Garret Rieger, Rod Sheeter
  26. */
  27. #include "batch.hh"
  28. #include "face-options.hh"
  29. #include "glib.h"
  30. #include "main-font-text.hh"
  31. #include "output-options.hh"
  32. #include "helper-subset.hh"
  33. #include <hb-subset.h>
  34. static hb_face_t* preprocess_face(hb_face_t* face)
  35. {
  36. return hb_subset_preprocess (face);
  37. }
  38. /*
  39. * Command line interface to the harfbuzz font subsetter.
  40. */
  41. struct subset_main_t : option_parser_t, face_options_t, output_options_t<false>
  42. {
  43. subset_main_t ()
  44. : input (hb_subset_input_create_or_fail ())
  45. {}
  46. ~subset_main_t ()
  47. {
  48. hb_subset_input_destroy (input);
  49. }
  50. void parse_face (int argc, const char * const *argv)
  51. {
  52. option_parser_t parser;
  53. face_options_t face_opts;
  54. face_opts.add_options (&parser);
  55. GOptionEntry entries[] =
  56. {
  57. {G_OPTION_REMAINING, 0, G_OPTION_FLAG_IN_MAIN,
  58. G_OPTION_ARG_CALLBACK, (gpointer) &collect_face, nullptr, "[FONT-FILE] [TEXT]"},
  59. {nullptr}
  60. };
  61. parser.add_main_group (entries, &face_opts);
  62. parser.add_options ();
  63. g_option_context_set_ignore_unknown_options (parser.context, true);
  64. g_option_context_set_help_enabled (parser.context, false);
  65. char **args = (char **)
  66. #if GLIB_CHECK_VERSION (2, 68, 0)
  67. g_memdup2
  68. #else
  69. g_memdup
  70. #endif
  71. (argv, argc * sizeof (*argv));
  72. parser.parse (&argc, &args);
  73. g_free (args);
  74. set_face (face_opts.face);
  75. }
  76. void parse (int argc, char **argv)
  77. {
  78. bool help = false;
  79. for (auto i = 1; i < argc; i++)
  80. if (!strncmp ("--help", argv[i], 6))
  81. {
  82. help = true;
  83. break;
  84. }
  85. if (likely (!help))
  86. {
  87. /* Do a preliminary parse to load font-face, such that we can use it
  88. * during main option parsing. */
  89. parse_face (argc, argv);
  90. }
  91. add_options ();
  92. option_parser_t::parse (&argc, &argv);
  93. }
  94. int operator () (int argc, char **argv)
  95. {
  96. parse (argc, argv);
  97. hb_face_t* orig_face = face;
  98. if (preprocess)
  99. orig_face = preprocess_face (face);
  100. hb_face_t *new_face = nullptr;
  101. for (unsigned i = 0; i < num_iterations; i++)
  102. {
  103. hb_face_destroy (new_face);
  104. new_face = hb_subset_or_fail (orig_face, input);
  105. }
  106. bool success = new_face;
  107. if (success)
  108. {
  109. hb_blob_t *result = hb_face_reference_blob (new_face);
  110. write_file (output_file, result);
  111. hb_blob_destroy (result);
  112. }
  113. else if (hb_face_get_glyph_count (orig_face) == 0)
  114. fail (false, "Invalid font file.");
  115. hb_face_destroy (new_face);
  116. if (preprocess)
  117. hb_face_destroy (orig_face);
  118. return success ? 0 : 1;
  119. }
  120. bool
  121. write_file (const char *output_file, hb_blob_t *blob)
  122. {
  123. assert (out_fp);
  124. unsigned int size;
  125. const char* data = hb_blob_get_data (blob, &size);
  126. while (size)
  127. {
  128. size_t ret = fwrite (data, 1, size, out_fp);
  129. size -= ret;
  130. data += ret;
  131. if (size && ferror (out_fp))
  132. fail (false, "Failed to write output: %s", strerror (errno));
  133. }
  134. return true;
  135. }
  136. void add_options ();
  137. protected:
  138. static gboolean
  139. collect_face (const char *name,
  140. const char *arg,
  141. gpointer data,
  142. GError **error);
  143. static gboolean
  144. collect_rest (const char *name,
  145. const char *arg,
  146. gpointer data,
  147. GError **error);
  148. public:
  149. unsigned num_iterations = 1;
  150. gboolean preprocess = false;
  151. hb_subset_input_t *input = nullptr;
  152. };
  153. static gboolean
  154. parse_gids (const char *name G_GNUC_UNUSED,
  155. const char *arg,
  156. gpointer data,
  157. GError **error)
  158. {
  159. subset_main_t *subset_main = (subset_main_t *) data;
  160. hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
  161. hb_bool_t is_add = (name[strlen (name) - 1] == '+');
  162. hb_set_t *gids = hb_subset_input_glyph_set (subset_main->input);
  163. if (!is_remove && !is_add) hb_set_clear (gids);
  164. if (0 == strcmp (arg, "*"))
  165. {
  166. hb_set_clear (gids);
  167. if (!is_remove)
  168. hb_set_invert (gids);
  169. return true;
  170. }
  171. char *s = (char *) arg;
  172. char *p;
  173. while (s && *s)
  174. {
  175. while (*s && strchr (", ", *s))
  176. s++;
  177. if (!*s)
  178. break;
  179. errno = 0;
  180. hb_codepoint_t start_code = strtoul (s, &p, 10);
  181. if (s[0] == '-' || errno || s == p)
  182. {
  183. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  184. "Failed parsing glyph-index at: '%s'", s);
  185. return false;
  186. }
  187. if (p && p[0] == '-') // ranges
  188. {
  189. s = ++p;
  190. hb_codepoint_t end_code = strtoul (s, &p, 10);
  191. if (s[0] == '-' || errno || s == p)
  192. {
  193. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  194. "Failed parsing glyph-index at: '%s'", s);
  195. return false;
  196. }
  197. if (end_code < start_code)
  198. {
  199. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  200. "Invalid glyph-index range %u-%u", start_code, end_code);
  201. return false;
  202. }
  203. if (!is_remove)
  204. hb_set_add_range (gids, start_code, end_code);
  205. else
  206. hb_set_del_range (gids, start_code, end_code);
  207. }
  208. else
  209. {
  210. if (!is_remove)
  211. hb_set_add (gids, start_code);
  212. else
  213. hb_set_del (gids, start_code);
  214. }
  215. s = p;
  216. }
  217. return true;
  218. }
  219. static gboolean
  220. parse_glyphs (const char *name G_GNUC_UNUSED,
  221. const char *arg,
  222. gpointer data,
  223. GError **error G_GNUC_UNUSED)
  224. {
  225. subset_main_t *subset_main = (subset_main_t *) data;
  226. hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
  227. hb_bool_t is_add = (name[strlen (name) - 1] == '+');
  228. hb_set_t *gids = hb_subset_input_glyph_set (subset_main->input);
  229. if (!is_remove && !is_add) hb_set_clear (gids);
  230. if (0 == strcmp (arg, "*"))
  231. {
  232. hb_set_clear (gids);
  233. if (!is_remove)
  234. hb_set_invert (gids);
  235. return true;
  236. }
  237. const char *p = arg;
  238. const char *p_end = arg + strlen (arg);
  239. hb_font_t *font = hb_font_create (subset_main->face);
  240. while (p < p_end)
  241. {
  242. while (p < p_end && (*p == ' ' || *p == ','))
  243. p++;
  244. const char *end = p;
  245. while (end < p_end && *end != ' ' && *end != ',')
  246. end++;
  247. if (p < end)
  248. {
  249. hb_codepoint_t gid;
  250. if (!hb_font_get_glyph_from_name (font, p, end - p, &gid))
  251. {
  252. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  253. "Failed parsing glyph name: '%s'", p);
  254. return false;
  255. }
  256. if (!is_remove)
  257. hb_set_add (gids, gid);
  258. else
  259. hb_set_del (gids, gid);
  260. }
  261. p = end + 1;
  262. }
  263. hb_font_destroy (font);
  264. return true;
  265. }
  266. static gboolean
  267. parse_text (const char *name G_GNUC_UNUSED,
  268. const char *arg,
  269. gpointer data,
  270. GError **error G_GNUC_UNUSED)
  271. {
  272. subset_main_t *subset_main = (subset_main_t *) data;
  273. hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
  274. hb_bool_t is_add = (name[strlen (name) - 1] == '+');
  275. hb_set_t *unicodes = hb_subset_input_unicode_set (subset_main->input);
  276. if (!is_remove && !is_add) hb_set_clear (unicodes);
  277. if (0 == strcmp (arg, "*"))
  278. {
  279. hb_set_clear (unicodes);
  280. if (!is_remove)
  281. hb_set_invert (unicodes);
  282. return true;
  283. }
  284. for (gchar *c = (gchar *) arg;
  285. *c;
  286. c = g_utf8_find_next_char(c, nullptr))
  287. {
  288. gunichar cp = g_utf8_get_char(c);
  289. if (!is_remove)
  290. hb_set_add (unicodes, cp);
  291. else
  292. hb_set_del (unicodes, cp);
  293. }
  294. return true;
  295. }
  296. static gboolean
  297. parse_unicodes (const char *name G_GNUC_UNUSED,
  298. const char *arg,
  299. gpointer data,
  300. GError **error)
  301. {
  302. subset_main_t *subset_main = (subset_main_t *) data;
  303. hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
  304. hb_bool_t is_add = (name[strlen (name) - 1] == '+');
  305. hb_set_t *unicodes = hb_subset_input_unicode_set (subset_main->input);
  306. if (!is_remove && !is_add) hb_set_clear (unicodes);
  307. if (0 == strcmp (arg, "*"))
  308. {
  309. hb_set_clear (unicodes);
  310. if (!is_remove)
  311. hb_set_invert (unicodes);
  312. return true;
  313. }
  314. // XXX TODO Ranges
  315. #define DELIMITERS "<+->{},;&#\\xXuUnNiI\n\t\v\f\r "
  316. char *s = (char *) arg;
  317. char *p;
  318. while (s && *s)
  319. {
  320. while (*s && strchr (DELIMITERS, *s))
  321. s++;
  322. if (!*s)
  323. break;
  324. errno = 0;
  325. hb_codepoint_t start_code = strtoul (s, &p, 16);
  326. if (errno || s == p)
  327. {
  328. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  329. "Failed parsing Unicode at: '%s'", s);
  330. return false;
  331. }
  332. if (p && p[0] == '-') // ranges
  333. {
  334. s = ++p;
  335. hb_codepoint_t end_code = strtoul (s, &p, 16);
  336. if (s[0] == '-' || errno || s == p)
  337. {
  338. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  339. "Failed parsing Unicode at: '%s'", s);
  340. return false;
  341. }
  342. if (end_code < start_code)
  343. {
  344. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  345. "Invalid Unicode range %u-%u", start_code, end_code);
  346. return false;
  347. }
  348. if (!is_remove)
  349. hb_set_add_range (unicodes, start_code, end_code);
  350. else
  351. hb_set_del_range (unicodes, start_code, end_code);
  352. }
  353. else
  354. {
  355. if (!is_remove)
  356. hb_set_add (unicodes, start_code);
  357. else
  358. hb_set_del (unicodes, start_code);
  359. }
  360. s = p;
  361. }
  362. return true;
  363. }
  364. static gboolean
  365. parse_nameids (const char *name,
  366. const char *arg,
  367. gpointer data,
  368. GError **error)
  369. {
  370. subset_main_t *subset_main = (subset_main_t *) data;
  371. hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
  372. hb_bool_t is_add = (name[strlen (name) - 1] == '+');
  373. hb_set_t *name_ids = hb_subset_input_set (subset_main->input, HB_SUBSET_SETS_NAME_ID);
  374. if (!is_remove && !is_add) hb_set_clear (name_ids);
  375. if (0 == strcmp (arg, "*"))
  376. {
  377. hb_set_clear (name_ids);
  378. if (!is_remove)
  379. hb_set_invert (name_ids);
  380. return true;
  381. }
  382. char *s = (char *) arg;
  383. char *p;
  384. while (s && *s)
  385. {
  386. while (*s && strchr (", ", *s))
  387. s++;
  388. if (!*s)
  389. break;
  390. errno = 0;
  391. hb_codepoint_t u = strtoul (s, &p, 10);
  392. if (errno || s == p)
  393. {
  394. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  395. "Failed parsing nameID at: '%s'", s);
  396. return false;
  397. }
  398. if (!is_remove)
  399. {
  400. hb_set_add (name_ids, u);
  401. } else {
  402. hb_set_del (name_ids, u);
  403. }
  404. s = p;
  405. }
  406. return true;
  407. }
  408. static gboolean
  409. parse_name_languages (const char *name,
  410. const char *arg,
  411. gpointer data,
  412. GError **error)
  413. {
  414. subset_main_t *subset_main = (subset_main_t *) data;
  415. hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
  416. hb_bool_t is_add = (name[strlen (name) - 1] == '+');
  417. hb_set_t *name_languages = hb_subset_input_set (subset_main->input, HB_SUBSET_SETS_NAME_LANG_ID);
  418. if (!is_remove && !is_add) hb_set_clear (name_languages);
  419. if (0 == strcmp (arg, "*"))
  420. {
  421. hb_set_clear (name_languages);
  422. if (!is_remove)
  423. hb_set_invert (name_languages);
  424. return true;
  425. }
  426. char *s = (char *) arg;
  427. char *p;
  428. while (s && *s)
  429. {
  430. while (*s && strchr (", ", *s))
  431. s++;
  432. if (!*s)
  433. break;
  434. errno = 0;
  435. hb_codepoint_t u = strtoul (s, &p, 10);
  436. if (errno || s == p)
  437. {
  438. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  439. "Failed parsing name-language code at: '%s'", s);
  440. return false;
  441. }
  442. if (!is_remove)
  443. {
  444. hb_set_add (name_languages, u);
  445. } else {
  446. hb_set_del (name_languages, u);
  447. }
  448. s = p;
  449. }
  450. return true;
  451. }
  452. static gboolean
  453. _keep_everything (const char *name,
  454. const char *arg,
  455. gpointer data,
  456. GError **error G_GNUC_UNUSED)
  457. {
  458. subset_main_t *subset_main = (subset_main_t *) data;
  459. hb_subset_input_keep_everything (subset_main->input);
  460. return true;
  461. }
  462. template <hb_subset_flags_t flag>
  463. static gboolean
  464. set_flag (const char *name,
  465. const char *arg,
  466. gpointer data,
  467. GError **error G_GNUC_UNUSED)
  468. {
  469. subset_main_t *subset_main = (subset_main_t *) data;
  470. hb_subset_input_set_flags (subset_main->input,
  471. hb_subset_input_get_flags (subset_main->input) | flag);
  472. return true;
  473. }
  474. static gboolean
  475. parse_layout_tag_list (hb_subset_sets_t set_type,
  476. const char *name,
  477. const char *arg,
  478. gpointer data,
  479. GError **error G_GNUC_UNUSED)
  480. {
  481. subset_main_t *subset_main = (subset_main_t *) data;
  482. hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
  483. hb_bool_t is_add = (name[strlen (name) - 1] == '+');
  484. hb_set_t *layout_tags = hb_subset_input_set (subset_main->input, set_type);
  485. if (!is_remove && !is_add) hb_set_clear (layout_tags);
  486. if (0 == strcmp (arg, "*"))
  487. {
  488. hb_set_clear (layout_tags);
  489. if (!is_remove)
  490. hb_set_invert (layout_tags);
  491. return true;
  492. }
  493. char *s = strtok((char *) arg, ", ");
  494. while (s)
  495. {
  496. if (strlen (s) > 4) // tags are at most 4 bytes
  497. {
  498. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  499. "Failed parsing table tag at: '%s'", s);
  500. return false;
  501. }
  502. hb_tag_t tag = hb_tag_from_string (s, strlen (s));
  503. if (!is_remove)
  504. hb_set_add (layout_tags, tag);
  505. else
  506. hb_set_del (layout_tags, tag);
  507. s = strtok(nullptr, ", ");
  508. }
  509. return true;
  510. }
  511. static gboolean
  512. parse_layout_features (const char *name,
  513. const char *arg,
  514. gpointer data,
  515. GError **error)
  516. {
  517. return parse_layout_tag_list (HB_SUBSET_SETS_LAYOUT_FEATURE_TAG,
  518. name,
  519. arg,
  520. data,
  521. error);
  522. }
  523. static gboolean
  524. parse_layout_scripts (const char *name,
  525. const char *arg,
  526. gpointer data,
  527. GError **error)
  528. {
  529. return parse_layout_tag_list (HB_SUBSET_SETS_LAYOUT_SCRIPT_TAG,
  530. name,
  531. arg,
  532. data,
  533. error);
  534. }
  535. static gboolean
  536. parse_drop_tables (const char *name,
  537. const char *arg,
  538. gpointer data,
  539. GError **error)
  540. {
  541. subset_main_t *subset_main = (subset_main_t *) data;
  542. hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
  543. hb_bool_t is_add = (name[strlen (name) - 1] == '+');
  544. hb_set_t *drop_tables = hb_subset_input_set (subset_main->input, HB_SUBSET_SETS_DROP_TABLE_TAG);
  545. if (!is_remove && !is_add) hb_set_clear (drop_tables);
  546. if (0 == strcmp (arg, "*"))
  547. {
  548. hb_set_clear (drop_tables);
  549. if (!is_remove)
  550. hb_set_invert (drop_tables);
  551. return true;
  552. }
  553. char *s = strtok((char *) arg, ", ");
  554. while (s)
  555. {
  556. if (strlen (s) > 4) // Table tags are at most 4 bytes.
  557. {
  558. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  559. "Failed parsing table tag at: '%s'", s);
  560. return false;
  561. }
  562. hb_tag_t tag = hb_tag_from_string (s, strlen (s));
  563. if (!is_remove)
  564. hb_set_add (drop_tables, tag);
  565. else
  566. hb_set_del (drop_tables, tag);
  567. s = strtok(nullptr, ", ");
  568. }
  569. return true;
  570. }
  571. #ifndef HB_NO_VAR
  572. static gboolean
  573. parse_instance (const char *name,
  574. const char *arg,
  575. gpointer data,
  576. GError **error)
  577. {
  578. subset_main_t *subset_main = (subset_main_t *) data;
  579. if (!subset_main->face) {
  580. // There is no face, which is needed to set up instancing. Skip parsing these options.
  581. return true;
  582. }
  583. return parse_instancing_spec(arg, subset_main->face, subset_main->input, error);
  584. }
  585. #endif
  586. static gboolean
  587. parse_glyph_map (const char *name,
  588. const char *arg,
  589. gpointer data,
  590. GError **error)
  591. {
  592. // Glyph map has the following format:
  593. // <entry 1>,<entry 2>,...,<entry n>
  594. // <entry> = <old gid>:<new gid>
  595. subset_main_t *subset_main = (subset_main_t *) data;
  596. hb_subset_input_t* input = subset_main->input;
  597. hb_set_t *glyphs = hb_subset_input_glyph_set(input);
  598. char *s = (char *) arg;
  599. char *p;
  600. while (s && *s)
  601. {
  602. while (*s && strchr (", ", *s))
  603. s++;
  604. if (!*s)
  605. break;
  606. errno = 0;
  607. hb_codepoint_t start_code = strtoul (s, &p, 10);
  608. if (s[0] == '-' || errno || s == p)
  609. {
  610. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  611. "Failed parsing glyph map at: '%s'", s);
  612. return false;
  613. }
  614. if (!p || p[0] != ':') // ranges
  615. {
  616. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  617. "Failed parsing glyph map at: '%s'", s);
  618. return false;
  619. }
  620. s = ++p;
  621. hb_codepoint_t end_code = strtoul (s, &p, 10);
  622. if (s[0] == '-' || errno || s == p)
  623. {
  624. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
  625. "Failed parsing glyph map at: '%s'", s);
  626. return false;
  627. }
  628. hb_set_add(glyphs, start_code);
  629. hb_map_set (hb_subset_input_old_to_new_glyph_mapping (input), start_code, end_code);
  630. s = p;
  631. }
  632. return true;
  633. }
  634. template <GOptionArgFunc line_parser, bool allow_comments=true>
  635. static gboolean
  636. parse_file_for (const char *name,
  637. const char *arg,
  638. gpointer data,
  639. GError **error)
  640. {
  641. FILE *fp = nullptr;
  642. if (0 != strcmp (arg, "-"))
  643. fp = fopen (arg, "r");
  644. else
  645. fp = stdin;
  646. if (!fp)
  647. {
  648. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
  649. "Failed opening file `%s': %s",
  650. arg, strerror (errno));
  651. return false;
  652. }
  653. GString *gs = g_string_new (nullptr);
  654. do
  655. {
  656. g_string_set_size (gs, 0);
  657. char buf[BUFSIZ];
  658. while (fgets (buf, sizeof (buf), fp))
  659. {
  660. unsigned bytes = strlen (buf);
  661. if (bytes && buf[bytes - 1] == '\n')
  662. {
  663. bytes--;
  664. g_string_append_len (gs, buf, bytes);
  665. break;
  666. }
  667. g_string_append_len (gs, buf, bytes);
  668. }
  669. if (ferror (fp))
  670. {
  671. g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
  672. "Failed reading file `%s': %s",
  673. arg, strerror (errno));
  674. fclose (fp);
  675. return false;
  676. }
  677. g_string_append_c (gs, '\0');
  678. if (allow_comments)
  679. {
  680. char *comment = strchr (gs->str, '#');
  681. if (comment)
  682. *comment = '\0';
  683. }
  684. line_parser ("+", gs->str, data, error);
  685. if (*error)
  686. break;
  687. }
  688. while (!feof (fp));
  689. g_string_free (gs, false);
  690. fclose (fp);
  691. return true;
  692. }
  693. gboolean
  694. subset_main_t::collect_face (const char *name,
  695. const char *arg,
  696. gpointer data,
  697. GError **error)
  698. {
  699. face_options_t *thiz = (face_options_t *) data;
  700. if (!thiz->font_file)
  701. {
  702. thiz->font_file = g_strdup (arg);
  703. return true;
  704. }
  705. return true;
  706. }
  707. gboolean
  708. subset_main_t::collect_rest (const char *name,
  709. const char *arg,
  710. gpointer data,
  711. GError **error)
  712. {
  713. subset_main_t *thiz = (subset_main_t *) data;
  714. if (!thiz->font_file)
  715. {
  716. thiz->font_file = g_strdup (arg);
  717. return true;
  718. }
  719. parse_text (name, arg, data, error);
  720. return true;
  721. }
  722. void
  723. subset_main_t::add_options ()
  724. {
  725. set_summary ("Subset fonts to specification.");
  726. face_options_t::add_options (this);
  727. GOptionEntry glyphset_entries[] =
  728. {
  729. {"gids", 'g', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_gids,
  730. "Specify glyph IDs or ranges to include in the subset.\n"
  731. " "
  732. "Use --gids-=... to subtract codepoints from the current set.", "list of glyph indices/ranges or *"},
  733. {"gids-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_gids, "Specify glyph IDs or ranges to remove from the subset", "list of glyph indices/ranges or *"},
  734. {"gids+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_gids, "Specify glyph IDs or ranges to include in the subset", "list of glyph indices/ranges or *"},
  735. {"gids-file", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_gids>, "Specify file to read glyph IDs or ranges from", "filename"},
  736. {"glyphs", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_glyphs, "Specify glyph names to include in the subset. Use --glyphs-=... to subtract glyphs from the current set.", "list of glyph names or *"},
  737. {"glyphs+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_glyphs, "Specify glyph names to include in the subset", "list of glyph names"},
  738. {"glyphs-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_glyphs, "Specify glyph names to remove from the subset", "list of glyph names"},
  739. {"glyphs-file", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_glyphs>, "Specify file to read glyph names from", "filename"},
  740. {"text", 't', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Specify text to include in the subset. Use --text-=... to subtract codepoints from the current set.", "string"},
  741. {"text-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Specify text to remove from the subset", "string"},
  742. {"text+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Specify text to include in the subset", "string"},
  743. {"text-file", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_text, false>,"Specify file to read text from", "filename"},
  744. {"unicodes", 'u', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes,
  745. "Specify Unicode codepoints or ranges to include in the subset. Use * to include all codepoints.\n"
  746. " "
  747. "--unicodes-=... can be used to subtract codepoints from the current set.\n"
  748. " "
  749. "For example: --unicodes=* --unicodes-=41,42,43 would create a subset with all codepoints\n"
  750. " "
  751. "except for 41, 42, 43.",
  752. "list of hex numbers/ranges or *"},
  753. {"unicodes-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Specify Unicode codepoints or ranges to remove from the subset", "list of hex numbers/ranges or *"},
  754. {"unicodes+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Specify Unicode codepoints or ranges to include in the subset", "list of hex numbers/ranges or *"},
  755. {"unicodes-file", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_unicodes>,"Specify file to read Unicode codepoints or ranges from", "filename"},
  756. {nullptr}
  757. };
  758. add_group (glyphset_entries,
  759. "subset-glyphset",
  760. "Subset glyph-set option:",
  761. "Subsetting glyph-set options",
  762. this);
  763. GOptionEntry other_entries[] =
  764. {
  765. {"gid-map", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_glyph_map, "Specify a glyph mapping to use, any unmapped gids will be automatically assigned.", "List of pairs old_gid1:new_gid1,old_gid2:new_gid2,..."},
  766. {"name-IDs", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids. Use --name-IDs-=... to subtract from the current set.", "list of int numbers or *"},
  767. {"name-IDs-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids", "list of int numbers or *"},
  768. {"name-IDs+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids", "list of int numbers or *"},
  769. {"name-languages", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_name_languages, "Subset nameRecords with specified language IDs. Use --name-languages-=... to subtract from the current set.", "list of int numbers or *"},
  770. {"name-languages-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_name_languages, "Subset nameRecords with specified language IDs", "list of int numbers or *"},
  771. {"name-languages+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_name_languages, "Subset nameRecords with specified language IDs", "list of int numbers or *"},
  772. {"layout-features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_features, "Specify set of layout feature tags that will be preserved. Use --layout-features-=... to subtract from the current set.", "list of string table tags or *"},
  773. {"layout-features+",0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_features, "Specify set of layout feature tags that will be preserved", "list of string tags or *"},
  774. {"layout-features-",0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_features, "Specify set of layout feature tags that will be preserved", "list of string tags or *"},
  775. {"layout-scripts", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_scripts, "Specify set of layout script tags that will be preserved. Use --layout-scripts-=... to subtract from the current set.", "list of string table tags or *"},
  776. {"layout-scripts+",0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_scripts, "Specify set of layout script tags that will be preserved", "list of string tags or *"},
  777. {"layout-scripts-",0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_scripts, "Specify set of layout script tags that will be preserved", "list of string tags or *"},
  778. {"drop-tables", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables. Use --drop-tables-=... to subtract from the current set.", "list of string table tags or *"},
  779. {"drop-tables+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables.", "list of string table tags or *"},
  780. {"drop-tables-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables.", "list of string table tags or *"},
  781. #ifndef HB_NO_VAR
  782. {"variations", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_instance,
  783. "(Partially|Fully) Instantiate a variable font. A location consists of the tag "
  784. "of a variation axis, followed by '=', followed by a number or the literal "
  785. "string 'drop'. For example: --variations=\"wdth=100 wght=200\" or --variations=\"wdth=drop\""
  786. ,
  787. "list of comma separated axis-locations."
  788. },
  789. {"instance", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_instance,
  790. "Alias for --variations.", "list of comma separated axis-locations"},
  791. #endif
  792. {nullptr}
  793. };
  794. add_group (other_entries,
  795. "subset-other",
  796. "Subset other option:",
  797. "Subsetting other options",
  798. this);
  799. GOptionEntry flag_entries[] =
  800. {
  801. {"keep-everything", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &_keep_everything,
  802. "Keep everything by default. Options after this can override the operation.", nullptr},
  803. {"no-hinting", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NO_HINTING>, "Whether to drop hints", nullptr},
  804. {"retain-gids", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_RETAIN_GIDS>, "If set don't renumber glyph ids in the subset.", nullptr},
  805. {"desubroutinize", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_DESUBROUTINIZE>, "Remove CFF/CFF2 use of subroutines", nullptr},
  806. {"name-legacy", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NAME_LEGACY>, "Keep legacy (non-Unicode) 'name' table entries", nullptr},
  807. {"set-overlaps-flag", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_SET_OVERLAPS_FLAG>, "Set the overlaps flag on each glyph.", nullptr},
  808. {"notdef-outline", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NOTDEF_OUTLINE>, "Keep the outline of \'.notdef\' glyph", nullptr},
  809. {"no-prune-unicode-ranges", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES>, "Don't change the 'OS/2 ulUnicodeRange*' bits.", nullptr},
  810. {"no-layout-closure", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE>, "Don't perform glyph closure for layout substitution (GSUB).", nullptr},
  811. {"glyph-names", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_GLYPH_NAMES>, "Keep PS glyph names in TT-flavored fonts. ", nullptr},
  812. {"passthrough-tables", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED>, "Do not drop tables that the tool does not know how to subset.", nullptr},
  813. {"preprocess-face", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &this->preprocess,
  814. "Alternative name for --preprocess.", nullptr},
  815. {"preprocess", 0, 0, G_OPTION_ARG_NONE, &this->preprocess,
  816. "If set preprocesses the face with the add accelerator option before actually subsetting.", nullptr},
  817. #ifdef HB_EXPERIMENTAL_API
  818. {"iftb-requirements", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_IFTB_REQUIREMENTS>, "Enforce requirements needed to use the subset with incremental font transfer IFTB patches.", nullptr},
  819. #endif
  820. {"optimize", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS>, "Perform IUP delta optimization on the resulting gvar table's deltas", nullptr},
  821. {nullptr}
  822. };
  823. add_group (flag_entries,
  824. "subset-flags",
  825. "Subset boolean option:",
  826. "Subsetting boolean options",
  827. this);
  828. GOptionEntry app_entries[] =
  829. {
  830. {"num-iterations", 'n', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_INT,
  831. &this->num_iterations,
  832. "Run subsetter N times (default: 1)", "N"},
  833. {nullptr}
  834. };
  835. add_group (app_entries,
  836. "subset-app",
  837. "Subset app option:",
  838. "Subsetting application options",
  839. this);
  840. output_options_t::add_options (this);
  841. GOptionEntry entries[] =
  842. {
  843. {G_OPTION_REMAINING, 0, G_OPTION_FLAG_IN_MAIN,
  844. G_OPTION_ARG_CALLBACK, (gpointer) &collect_rest, nullptr, "[FONT-FILE] [TEXT]"},
  845. {nullptr}
  846. };
  847. add_main_group (entries, this);
  848. option_parser_t::add_options ();
  849. }
  850. int
  851. main (int argc, char **argv)
  852. {
  853. return batch_main<subset_main_t, true> (argc, argv);
  854. }