1 /* Copyright © Євгеній Мещеряков <eugen@debian.org>
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16 #include <assert.h>
17 #include <errno.h>
18 #include <ft2build.h>
19 #include FT_FREETYPE_H
20 #include <cairo.h>
21 #include <cairo-pdf.h>
22 #include <cairo-ps.h>
23 #include <cairo-svg.h>
24 #include <cairo-ft.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <glib.h>
29 #include <stdbool.h>
30 #include <unistd.h>
31 #include <getopt.h>
32 #include <stdint.h>
33 #include <pango/pangocairo.h>
34 #include <pango/pangofc-fontmap.h>
35 #include <math.h>
36 #include <libintl.h>
37 #include <locale.h>
38
39 #include "unicode_blocks.h"
40 #include "static_unicode_blocks.h"
41 #include "config.h"
42
43 #define _(str) gettext(str)
44
45 #define POINTS_PER_INCH 72
46
47 #define A4_WIDTH (8.3 * POINTS_PER_INCH)
48 #define A4_HEIGHT (11.7 * POINTS_PER_INCH)
49
50 #define xmin_border (POINTS_PER_INCH / 1.5)
51 #define ymin_border POINTS_PER_INCH
52 #define cell_width ((A4_WIDTH - 2 * xmin_border) / 16)
53 #define cell_height ((A4_HEIGHT - 2 * ymin_border) / 16)
54
cell_x(double x_min,int pos)55 static double cell_x(double x_min, int pos)
56 {
57 return x_min + cell_width * (pos / 16);
58 }
59
cell_y(int pos)60 static double cell_y(int pos)
61 {
62 return ymin_border + cell_height * (pos % 16);
63 }
64
65 static struct option longopts[] = {
66 {"blocks-file", 1, 0, 'b'},
67 {"font-file", 1, 0, 'f'},
68 {"output-file", 1, 0, 'o'},
69 {"help", 0, 0, 'h'},
70 {"other-font-file", 1, 0, 'd'},
71 {"postscript-output", 0, 0, 's'},
72 {"svg", 0, 0, 'g'},
73 {"print-outline", 0, 0, 'l'},
74 {"write-outline", 0, 0, 'w'},
75 {"include-range", 1, 0, 'i'},
76 {"exclude-range", 1, 0, 'x'},
77 {"style", 1, 0, 't'},
78 {"font-index", 1, 0, 'n'},
79 {"other-index", 1, 0, 'm'},
80 {"no-embed", 0, 0, 'e'},
81 {"use-pango", 0, 0, 'p'}, /* For compatibility with version <= 5.3 */
82 {0, 0, 0, 0}
83 };
84
85 struct range {
86 uint32_t first;
87 uint32_t last;
88 bool include;
89 struct range *next;
90 };
91
92 static const char *font_file_name;
93 static const char *other_font_file_name;
94 static const char *output_file_name;
95 static bool postscript_output;
96 static bool svg_output;
97 static bool print_outline;
98 static bool write_outline;
99 static bool no_embed;
100 static struct range *ranges;
101 static struct range *last_range;
102 static int font_index;
103 static int other_index;
104
105 struct fntsample_style {
106 const char *const name;
107 const char *const default_val;
108 char *val;
109 };
110
111 static struct fntsample_style styles[] = {
112 { "header-font", "Sans Bold 12", NULL },
113 { "font-name-font", "Serif Bold 12", NULL },
114 { "table-numbers-font", "Sans 10", NULL },
115 { "cell-numbers-font", "Mono 8", NULL },
116 { NULL, NULL, NULL }
117 };
118
119 struct table_fonts {
120 PangoFontDescription *header;
121 PangoFontDescription *font_name;
122 PangoFontDescription *table_numbers;
123 PangoFontDescription *cell_numbers;
124 };
125
126 static struct table_fonts table_fonts;
127
128 static double cell_label_offset;
129 static double cell_glyph_bot_offset;
130 static double glyph_baseline_offset;
131 static double font_scale;
132
133 static const struct unicode_block *unicode_blocks;
134
135 static void usage(const char *);
136
find_style(const char * name)137 static struct fntsample_style *find_style(const char *name)
138 {
139 for (struct fntsample_style *style = styles; style->name; style++) {
140 if (!strcmp(name, style->name)) {
141 return style;
142 }
143 }
144
145 return NULL;
146 }
147
set_style(const char * name,const char * val)148 static int set_style(const char *name, const char *val)
149 {
150 struct fntsample_style *style = find_style(name);
151
152 if (!style) {
153 return -1;
154 }
155
156 char *new_val = strdup(val);
157 if (!new_val) {
158 return -1;
159 }
160
161 if (style->val) {
162 free(style->val);
163 }
164
165 style->val = new_val;
166
167 return 0;
168 }
169
get_style(const char * name)170 static const char *get_style(const char *name)
171 {
172 struct fntsample_style *style = find_style(name);
173
174 if (!style) {
175 return NULL;
176 }
177
178 return style->val ? style->val : style->default_val;
179 }
180
parse_style_string(char * s)181 static int parse_style_string(char *s)
182 {
183 char *n = strchr(s, ':');
184 if (!n) {
185 return -1;
186 }
187
188 *n++ = '\0';
189 return set_style(s, n);
190 }
191
192 /*
193 * Update output range.
194 *
195 * Returns -1 on error.
196 */
add_range(char * range,bool include)197 static int add_range(char *range, bool include)
198 {
199 uint32_t first = 0, last = 0xffffffff;
200 char *endptr;
201
202 char *minus = strchr(range, '-');
203
204 if (minus) {
205 if (minus != range) {
206 *minus = '\0';
207 first = strtoul(range, &endptr, 0);
208 if (*endptr) {
209 return -1;
210 }
211 }
212
213 if (*(minus + 1)) {
214 last = strtoul(minus + 1, &endptr, 0);
215 if (*endptr) {
216 return -1;
217 }
218 } else if (minus == range) {
219 return -1;
220 }
221 } else {
222 first = strtoul(range, &endptr, 0);
223 if (*endptr)
224 return -1;
225 last = first;
226 }
227
228 if (first > last) {
229 return -1;
230 }
231
232 struct range *r = malloc(sizeof(*r));
233 if (!r) {
234 return -1;
235 }
236
237 r->first = first;
238 r->last = last;
239 r->include = include;
240 r->next = NULL;
241
242 if (ranges) {
243 last_range->next = r;
244 } else {
245 ranges = r;
246 }
247
248 last_range = r;
249
250 return 0;
251 }
252
253 /*
254 * Check if character with the given code belongs
255 * to output range specified by the user.
256 */
in_range(uint32_t c)257 static bool in_range(uint32_t c)
258 {
259 bool in = ranges ? (!ranges->include) : 1;
260
261 for (struct range *r = ranges; r; r = r->next) {
262 if ((c >= r->first) && (c <= r->last)) {
263 in = r->include;
264 }
265 }
266 return in;
267 }
268
269 /*
270 * Get glyph index for the next glyph from the given font face, that
271 * represents character from output range specified by the user.
272 *
273 * Returns character code, updates 'idx'.
274 * 'idx' can became 0 if there are no more glyphs.
275 */
get_next_char(FT_Face face,FT_ULong charcode,FT_UInt * idx)276 static FT_ULong get_next_char(FT_Face face, FT_ULong charcode, FT_UInt *idx)
277 {
278 FT_ULong rval = charcode;
279
280 do {
281 rval = FT_Get_Next_Char(face, rval, idx);
282 } while (*idx && !in_range(rval));
283
284 return rval;
285 }
286
287 /*
288 * Locate first character from the given font face that belongs
289 * to the user-specified output range.
290 *
291 * Returns character code, updates 'idx' with glyph index.
292 * Glyph index can became 0 if there are no matching glyphs in the font.
293 */
get_first_char(FT_Face face,FT_UInt * idx)294 static FT_ULong get_first_char(FT_Face face, FT_UInt *idx)
295 {
296 FT_ULong rval = FT_Get_First_Char(face, idx);
297
298 if (*idx && !in_range(rval)) {
299 rval = get_next_char(face, rval, idx);
300 }
301
302 return rval;
303 }
304
305 /*
306 * Create Pango layout for the given text.
307 * Updates 'r' with text extents.
308 * Returned layout should be freed using g_object_unref().
309 */
layout_text(cairo_t * cr,PangoFontDescription * ftdesc,const char * text,PangoRectangle * r)310 static PangoLayout *layout_text(cairo_t *cr, PangoFontDescription *ftdesc, const char *text, PangoRectangle *r)
311 {
312 PangoLayout *layout = pango_cairo_create_layout(cr);
313 pango_layout_set_font_description(layout, ftdesc);
314 pango_layout_set_text(layout, text, -1);
315 pango_layout_get_extents(layout, r, NULL);
316
317 return layout;
318 }
319
parse_options(int argc,char * const argv[])320 static void parse_options(int argc, char * const argv[])
321 {
322 for (;;) {
323 int n;
324 int c = getopt_long(argc, argv, "b:f:o:hd:sglwi:x:t:n:m:ep", longopts, NULL);
325
326 if (c == -1) {
327 break;
328 }
329
330 switch (c) {
331 case 'b':
332 if (unicode_blocks) {
333 fprintf(stderr, _("Unicode blocks file should be given at most once!\n"));
334 exit(1);
335 }
336
337 unicode_blocks = read_blocks(optarg, &n);
338 if (n == 0) {
339 fprintf(stderr, _("Failed to load any blocks from the blocks file!\n"));
340 exit(6);
341 }
342 break;
343 case 'f':
344 if (font_file_name) {
345 fprintf(stderr, _("Font file name should be given only once!\n"));
346 exit(1);
347 }
348 font_file_name = optarg;
349 break;
350 case 'o':
351 if (output_file_name) {
352 fprintf(stderr, _("Output file name should be given only once!\n"));
353 exit(1);
354 }
355 output_file_name = optarg;
356 break;
357 case 'h':
358 usage(argv[0]);
359 exit(0);
360 break;
361 case 'd':
362 if (other_font_file_name) {
363 fprintf(stderr, _("Font file name should be given only once!\n"));
364 exit(1);
365 }
366 other_font_file_name = optarg;
367 break;
368 case 's':
369 postscript_output = true;
370 break;
371 case 'g':
372 svg_output = true;
373 break;
374 case 'l':
375 print_outline = true;
376 break;
377 case 'w':
378 write_outline = true;
379 break;
380 case 'i':
381 case 'x':
382 if (add_range(optarg, c == 'i')) {
383 usage(argv[0]);
384 exit(1);
385 }
386 break;
387 case 't':
388 if (parse_style_string(optarg) == -1) {
389 usage(argv[0]);
390 exit(1);
391 }
392 break;
393 case 'n':
394 font_index = atoi(optarg);
395 break;
396 case 'm':
397 other_index = atoi(optarg);
398 break;
399 case 'e':
400 no_embed = true;
401 break;
402 case 'p':
403 /* Ignored for compatibility */
404 break;
405 case '?':
406 default:
407 usage(argv[0]);
408 exit(1);
409 break;
410 }
411 }
412
413 if (!font_file_name || !output_file_name) {
414 usage(argv[0]);
415 exit(1);
416 }
417
418 if (font_index < 0 || other_index < 0) {
419 fprintf(stderr, _("Font index should be non-negative!\n"));
420 exit(1);
421 }
422
423 if (postscript_output && svg_output) {
424 fprintf(stderr, _("-s and -g cannot be used together!\n"));
425 exit(1);
426 }
427
428 if (!unicode_blocks) {
429 unicode_blocks = static_unicode_blocks;
430 }
431 }
432
433 /*
434 * Locate unicode block that contains given character code.
435 * Returns this block or NULL if not found.
436 */
get_unicode_block(unsigned long charcode)437 static const struct unicode_block *get_unicode_block(unsigned long charcode)
438 {
439 for (const struct unicode_block *block = unicode_blocks; block->name; block++) {
440 if ((charcode >= block->start) && (charcode <= block->end)) {
441 return block;
442 }
443 }
444
445 return NULL;
446 }
447
448 /*
449 * Check if the given character code belongs to the given Unicode block.
450 */
is_in_block(unsigned long charcode,const struct unicode_block * block)451 static bool is_in_block(unsigned long charcode, const struct unicode_block *block)
452 {
453 return ((charcode >= block->start) && (charcode <= block->end));
454 }
455
456 /*
457 * Format and print/write outline information, if requested by the user.
458 */
outline(cairo_surface_t * surface,int level,int page,const char * text)459 static void outline(cairo_surface_t *surface, int level, int page, const char *text)
460 {
461 if (print_outline) {
462 printf("%d %d %s\n", level, page, text);
463 }
464
465 if (write_outline && cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_PDF) {
466 int len = snprintf(0, 0, "page=%d", page);
467 char *dest = malloc(len + 1);
468 sprintf(dest, "page=%d", page);
469
470 /* FIXME passing level here is not correct. */
471 cairo_pdf_surface_add_outline(surface, level, text, dest, CAIRO_PDF_OUTLINE_FLAG_OPEN);
472 free(dest);
473 }
474 }
475
476 /*
477 * Draw header of a page.
478 * Header shows font name and current Unicode block.
479 */
draw_header(cairo_t * cr,const char * face_name,const char * block_name)480 static void draw_header(cairo_t *cr, const char *face_name, const char *block_name)
481 {
482 PangoRectangle r;
483
484 PangoLayout *layout = layout_text(cr, table_fonts.font_name, face_name, &r);
485 cairo_move_to(cr, (A4_WIDTH - pango_units_to_double(r.width))/2.0, 30.0);
486 pango_cairo_show_layout_line(cr, pango_layout_get_line_readonly(layout, 0));
487 g_object_unref(layout);
488
489 layout = layout_text(cr, table_fonts.header, block_name, &r);
490 cairo_move_to(cr, (A4_WIDTH - pango_units_to_double(r.width))/2.0, 50.0);
491 pango_cairo_show_layout_line(cr, pango_layout_get_line_readonly(layout, 0));
492 g_object_unref(layout);
493 }
494
495 /*
496 * Highlight the cell with given coordinates.
497 * Used to highlight new glyphs.
498 */
highlight_cell(cairo_t * cr,double x,double y)499 static void highlight_cell(cairo_t *cr, double x, double y)
500 {
501 cairo_save(cr);
502 cairo_set_source_rgb(cr, 1.0, 1.0, 0.6);
503 cairo_rectangle(cr, x, y, cell_width, cell_height);
504 cairo_fill(cr);
505 cairo_restore(cr);
506 }
507
508 /*
509 * Draw table grid with row and column numbers.
510 */
draw_grid(cairo_t * cr,unsigned int x_cells,unsigned long block_start)511 static void draw_grid(cairo_t *cr, unsigned int x_cells,
512 unsigned long block_start)
513 {
514 const double x_min = (A4_WIDTH - x_cells * cell_width) / 2;
515 const double x_max = (A4_WIDTH + x_cells * cell_width) / 2;
516 const double table_height = A4_HEIGHT - ymin_border * 2;
517
518 cairo_set_line_width(cr, 1.0);
519 cairo_rectangle(cr, x_min, ymin_border, x_max - x_min, table_height);
520 cairo_move_to(cr, x_min, ymin_border);
521 cairo_line_to(cr, x_min, ymin_border - 15.0);
522 cairo_move_to(cr, x_max, ymin_border);
523 cairo_line_to(cr, x_max, ymin_border - 15.0);
524 cairo_stroke(cr);
525
526 cairo_set_line_width(cr, 0.5);
527 /* draw horizontal lines */
528 for (int i = 1; i < 16; i++) {
529 // TODO: use better name instead of just POINTS_PER_INCH
530 cairo_move_to(cr, x_min, POINTS_PER_INCH + i * table_height/16);
531 cairo_line_to(cr, x_max, POINTS_PER_INCH + i * table_height/16);
532 }
533
534 /* draw vertical lines */
535 for (unsigned int i = 1; i < x_cells; i++) {
536 cairo_move_to(cr, x_min + i * cell_width, ymin_border);
537 cairo_line_to(cr, x_min + i * cell_width, A4_HEIGHT - ymin_border);
538 }
539 cairo_stroke(cr);
540
541 /* draw glyph numbers */
542 char buf[17];
543 buf[1] = '\0';
544 #define hexdigs "0123456789ABCDEF"
545
546 for (int i = 0; i < 16; i++) {
547 buf[0] = hexdigs[i];
548
549 PangoRectangle r;
550 PangoLayout *layout = layout_text(cr, table_fonts.table_numbers, buf, &r);
551 cairo_move_to(cr,
552 x_min - pango_units_to_double(PANGO_RBEARING(r)) - 5.0,
553 POINTS_PER_INCH + (i+0.5) * table_height/16 + pango_units_to_double(PANGO_DESCENT(r))/2);
554 pango_cairo_show_layout_line(cr, pango_layout_get_line_readonly(layout, 0));
555 cairo_move_to(cr, x_min + x_cells * cell_width + 5.0,
556 POINTS_PER_INCH + (i+0.5) * table_height/16 + pango_units_to_double(PANGO_DESCENT(r))/2);
557 pango_cairo_show_layout_line(cr, pango_layout_get_line_readonly(layout, 0));
558 g_object_unref(layout);
559 }
560
561 for (unsigned int i = 0; i < x_cells; i++) {
562 snprintf(buf, sizeof(buf), "%03lX", block_start / 16 + i);
563
564 PangoRectangle r;
565 PangoLayout *layout = layout_text(cr, table_fonts.table_numbers, buf, &r);
566 cairo_move_to(cr,
567 x_min + i*cell_width + (cell_width - pango_units_to_double(r.width))/2,
568 ymin_border - 5.0);
569 pango_cairo_show_layout_line(cr, pango_layout_get_line_readonly(layout, 0));
570 g_object_unref(layout);
571 }
572 }
573
574 /*
575 * Fill empty cell. Color of the fill depends on the character properties.
576 */
fill_empty_cell(cairo_t * cr,double x,double y,unsigned long charcode)577 static void fill_empty_cell(cairo_t *cr, double x, double y, unsigned long charcode)
578 {
579 cairo_save(cr);
580 if (g_unichar_isdefined(charcode)) {
581 if (g_unichar_iscntrl(charcode))
582 cairo_set_source_rgb(cr, 0.0, 0.0, 0.5);
583 else
584 cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
585 }
586 cairo_rectangle(cr, x, y, cell_width, cell_height);
587 cairo_fill(cr);
588 cairo_restore(cr);
589 }
590
591 /*
592 * Draw label with character code.
593 */
draw_charcode(cairo_t * cr,double x,double y,FT_ULong charcode)594 static void draw_charcode(cairo_t *cr, double x, double y, FT_ULong charcode)
595 {
596 char buf[9];
597 snprintf(buf, sizeof(buf), "%04lX", charcode);
598
599 PangoRectangle r;
600 PangoLayout *layout = layout_text(cr, table_fonts.cell_numbers, buf, &r);
601 cairo_move_to(cr, x + (cell_width - pango_units_to_double(r.width))/2.0,
602 y + cell_height - cell_label_offset);
603 pango_cairo_show_layout_line(cr, pango_layout_get_line_readonly(layout, 0));
604 g_object_unref(layout);
605 }
606
607 /*
608 * Draws tables for all characters in the given Unicode block.
609 * Use font described by face and ft_face. Start from character
610 * with given charcode (it should belong to the given Unicode
611 * block). After return 'charcode' equals the last character code
612 * of the block.
613 *
614 * Returns number of pages drawn.
615 */
draw_unicode_block(cairo_t * cr,PangoLayout * layout,FT_Face ft_face,const char * font_name,unsigned long * charcode,const struct unicode_block * block,FT_Face ft_other_face)616 static int draw_unicode_block(cairo_t *cr, PangoLayout *layout,
617 FT_Face ft_face, const char *font_name, unsigned long *charcode,
618 const struct unicode_block *block, FT_Face ft_other_face)
619 {
620 unsigned long prev_charcode;
621 unsigned long prev_cell;
622 int npages = 0;
623 FT_UInt idx = FT_Get_Char_Index(ft_face, *charcode);
624
625 do {
626 unsigned long offset = ((*charcode - block->start) / 0x100) * 0x100;
627 unsigned long tbl_start = block->start + offset;
628 unsigned long tbl_end = tbl_start + 0xFF > block->end ?
629 block->end + 1 : tbl_start + 0x100;
630 unsigned int rows = (tbl_end - tbl_start) / 16;
631 double x_min = (A4_WIDTH - rows * cell_width) / 2;
632 bool filled_cells[256]; /* 16x16 glyphs max */
633
634 cairo_save(cr);
635 draw_header(cr, font_name, block->name);
636 prev_cell = tbl_start - 1;
637
638 memset(filled_cells, '\0', sizeof(filled_cells));
639
640 /*
641 * Fill empty cells and calculate coordinates of the glyphs.
642 * Also highlight cells if needed.
643 */
644 do {
645 /* the current glyph position in the table */
646 int charpos = *charcode - tbl_start;
647
648 /* fill empty cells before the current glyph */
649 for (unsigned long i = prev_cell + 1; i < *charcode; i++) {
650 int pos = i - tbl_start;
651 fill_empty_cell(cr, cell_x(x_min, pos), cell_y(pos), i);
652 }
653
654 /* if it is new glyph - highlight the cell */
655 if (ft_other_face && !FT_Get_Char_Index(ft_other_face, *charcode)) {
656 highlight_cell(cr, cell_x(x_min, charpos), cell_y(charpos));
657 }
658
659 /* draw the character */
660 char buf[9];
661 gint len = g_unichar_to_utf8((gunichar)*charcode, buf);
662 pango_layout_set_text(layout, buf, len);
663
664 double baseline = pango_units_to_double(pango_layout_get_baseline(layout));
665 cairo_move_to(cr, cell_x(x_min, charpos), cell_y(charpos) + glyph_baseline_offset - baseline);
666
667 if (no_embed) {
668 pango_cairo_layout_path(cr, layout);
669 } else {
670 pango_cairo_show_layout(cr, layout);
671 }
672
673 filled_cells[charpos] = true;
674
675 prev_charcode = *charcode;
676 prev_cell = *charcode;
677 *charcode = get_next_char(ft_face, *charcode, &idx);
678 } while (idx && (*charcode < tbl_end) && is_in_block(*charcode, block));
679
680 /* Fill remaining empty cells */
681 for (unsigned long i = prev_cell + 1; i < tbl_end; i++) {
682 int pos = i - tbl_start;
683 fill_empty_cell(cr, cell_x(x_min, pos), cell_y(pos), i);
684 }
685
686 /*
687 * Charcodes are drawn here to avoid switching between the charcode
688 * font and the cell font for each filled cell.
689 */
690 for (unsigned long i = 0; i < tbl_end - tbl_start; i++) {
691 if (filled_cells[i]) {
692 draw_charcode(cr, cell_x(x_min, i), cell_y(i),
693 i + tbl_start);
694 }
695 }
696
697 draw_grid(cr, rows, tbl_start);
698 npages++;
699 cairo_show_page(cr);
700 cairo_restore(cr);
701 } while (idx && is_in_block(*charcode, block));
702
703 *charcode = prev_charcode;
704 return npages;
705 }
706
create_glyph_layout(cairo_t * cr,FcConfig * fc_config,FcPattern * fc_font)707 static PangoLayout *create_glyph_layout(cairo_t *cr, FcConfig *fc_config, FcPattern *fc_font)
708 {
709 PangoFontMap *fontmap = pango_cairo_font_map_new_for_font_type(CAIRO_FONT_TYPE_FT);
710 pango_fc_font_map_set_config(PANGO_FC_FONT_MAP(fontmap), fc_config);
711 PangoContext *context = pango_font_map_create_context(fontmap);
712 pango_cairo_update_context(cr, context);
713
714 PangoFontDescription *font_desc = pango_fc_font_description_from_pattern(fc_font, FALSE);
715 PangoLayout *layout = pango_layout_new(context);
716 pango_layout_set_font_description(layout, font_desc);
717 pango_layout_set_width(layout, pango_units_from_double(cell_width));
718 pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
719
720 g_object_unref(context);
721 g_object_unref(fontmap);
722 pango_font_description_free(font_desc);
723
724 return layout;
725 }
726
727 /*
728 * The main drawing function.
729 */
draw_glyphs(cairo_t * cr,FT_Face ft_face,FT_Face ft_other_face)730 static void draw_glyphs(cairo_t *cr, FT_Face ft_face,
731 FT_Face ft_other_face)
732 {
733 FcConfig *fc_config = FcConfigCreate();
734 FcConfigAppFontAddFile(fc_config, (const FcChar8 *)font_file_name);
735
736 FcPattern *fc_pat = FcPatternCreate();
737 FcPatternAddInteger(fc_pat, FC_INDEX, font_index);
738
739 FcFontSet *fc_fontset = FcFontList(fc_config, fc_pat, NULL);
740 assert(fc_fontset->nfont > 0);
741 FcPattern *fc_font = fc_fontset->fonts[0];
742
743 const char *font_name;
744 if (FcPatternGetString(fc_font, FC_FULLNAME, 0, (FcChar8 **)&font_name) != FcResultMatch) {
745 font_name = "Unknown";
746 }
747
748 cairo_surface_t *surface = cairo_get_target(cr);
749
750 int pageno = 1;
751 outline(surface, 0, pageno, font_name);
752
753 PangoLayout *layout = create_glyph_layout(cr, fc_config, fc_font);
754
755 FT_UInt idx;
756 FT_ULong charcode = get_first_char(ft_face, &idx);
757
758 while (idx) {
759 const struct unicode_block *block = get_unicode_block(charcode);
760 if (block) {
761 outline(surface, 1, pageno, block->name);
762 int npages = draw_unicode_block(cr, layout, ft_face, font_name,
763 &charcode, block, ft_other_face);
764 pageno += npages;
765 }
766
767 charcode = get_next_char(ft_face, charcode, &idx);
768 }
769
770 g_object_unref(layout);
771
772 FcPatternDestroy(fc_pat);
773 FcFontSetDestroy(fc_fontset);
774 FcConfigDestroy(fc_config);
775 }
776
777 /*
778 * Print usage instructions and default values for styles
779 */
usage(const char * cmd)780 static void usage(const char *cmd)
781 {
782 fprintf(stderr, _("Usage: %s [ OPTIONS ] -f FONT-FILE -o OUTPUT-FILE\n"
783 " %s -h\n\n") , cmd, cmd);
784 fprintf(stderr, _("Options:\n"
785 " --blocks-file, -b BLOCKS-FILE Read Unicode blocks information from BLOCKS-FILE\n"
786 " --font-file, -f FONT-FILE Create samples of FONT-FILE\n"
787 " --font-index, -n IDX Font index in FONT-FILE\n"
788 " --output-file, -o OUTPUT-FILE Save samples to OUTPUT-FILE\n"
789 " --help, -h Show this information message and exit\n"
790 " --other-font-file, -d OTHER-FONT Compare FONT-FILE with OTHER-FONT and highlight added glyphs\n"
791 " --other-index, -m IDX Font index in OTHER-FONT\n"
792 " --postscript-output, -s Use PostScript format for output instead of PDF\n"
793 " --svg, -g Use SVG format for output\n"
794 " --print-outline, -l Print document outlines data to standard output\n"
795 " --write-outline, -w Write document outlines (only in PDF output)\n"
796 " --no-embed, -e Don't embed the font in the output file, draw the glyphs instead\n"
797 " --include-range, -i RANGE Show characters in RANGE\n"
798 " --exclude-range, -x RANGE Do not show characters in RANGE\n"
799 " --style, -t \"STYLE: VAL\" Set STYLE to value VAL\n"));
800
801 fprintf(stderr, _("\nSupported styles (and default values):\n"));
802
803 for (const struct fntsample_style *style = styles; style->name; style++) {
804 fprintf(stderr, "\t%s (%s)\n", style->name, style->default_val);
805 }
806 }
807
808 /*
809 * Initialize fonts used to print table heders and character codes.
810 */
init_table_fonts(void)811 static void init_table_fonts(void)
812 {
813 /* FIXME is this correct? */
814 PangoCairoFontMap *map = (PangoCairoFontMap *)pango_cairo_font_map_get_default();
815
816 pango_cairo_font_map_set_resolution(map, POINTS_PER_INCH);
817
818 table_fonts.header = pango_font_description_from_string(get_style("header-font"));
819 table_fonts.font_name = pango_font_description_from_string(get_style("font-name-font"));
820 table_fonts.table_numbers = pango_font_description_from_string(get_style("table-numbers-font"));
821 table_fonts.cell_numbers = pango_font_description_from_string(get_style("cell-numbers-font"));
822 }
823
824 /*
825 * Calculate various offsets.
826 */
calculate_offsets(cairo_t * cr)827 static void calculate_offsets(cairo_t *cr)
828 {
829 PangoRectangle extents;
830 /* Assume that vertical extents does not depend on actual text */
831 PangoLayout *l = layout_text(cr, table_fonts.cell_numbers, "0123456789ABCDEF", &extents);
832 g_object_unref(l);
833 /* Unsolved mistery of pango's font metrics.... */
834 double digits_ascent = pango_units_to_double(PANGO_DESCENT(extents));
835 double digits_descent = -pango_units_to_double(PANGO_ASCENT(extents));
836
837 cell_label_offset = digits_descent + 2;
838 cell_glyph_bot_offset = cell_label_offset + digits_ascent + 2;
839 }
840
841 /*
842 * Calculate font scaling
843 */
calc_font_scaling(FT_Face ft_face)844 void calc_font_scaling(FT_Face ft_face)
845 {
846 cairo_font_face_t *cr_face = cairo_ft_font_face_create_for_ft_face(ft_face, 0);
847 cairo_font_options_t *options = cairo_font_options_create();
848
849 /* First create font with size 1 and measure it */
850 cairo_matrix_t font_matrix;
851 cairo_matrix_init_identity(&font_matrix);
852 cairo_matrix_t ctm;
853 cairo_matrix_init_identity(&ctm);
854
855 /* Turn off rounding, so we can get real metrics */
856 cairo_font_options_set_hint_metrics(options, CAIRO_HINT_METRICS_OFF);
857 cairo_scaled_font_t *cr_font = cairo_scaled_font_create(cr_face, &font_matrix, &ctm, options);
858 cairo_font_extents_t extents;
859 cairo_scaled_font_extents(cr_font, &extents);
860
861 /* Use some magic to find the best font size... */
862 double tgt_size = cell_height - cell_glyph_bot_offset - 2;
863 if (tgt_size <= 0) {
864 fprintf(stderr, _("Not enough space for rendering glyphs. Make cell font smaller.\n"));
865 exit(5);
866 }
867
868 double act_size = extents.ascent + extents.descent;
869 if (act_size <= 0) {
870 fprintf(stderr, _("The font has strange metrics: ascent + descent = %g\n"), act_size);
871 exit(5);
872 }
873
874 font_scale = tgt_size / act_size;
875 if (font_scale > 1)
876 font_scale = trunc(font_scale); // just to make numbers nicer
877 if (font_scale > 20)
878 font_scale = 20; // Do not make font larger than in previous versions
879
880 cairo_scaled_font_destroy(cr_font);
881
882 /* Create the font once again, but this time scaled */
883 cairo_matrix_init_scale(&font_matrix, font_scale, font_scale);
884 cr_font = cairo_scaled_font_create(cr_face, &font_matrix, &ctm, options);
885 cairo_scaled_font_extents(cr_font, &extents);
886 glyph_baseline_offset = (tgt_size - (extents.ascent + extents.descent)) / 2 + 2 + extents.ascent;
887 cairo_scaled_font_destroy(cr_font);
888 }
889
890 /*
891 * Configure DPF surface metadata so fntsample can be used with
892 * repeatable builds.
893 */
set_repeatable_pdf_metadata(cairo_surface_t * surface)894 static void set_repeatable_pdf_metadata(cairo_surface_t *surface)
895 {
896 char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
897
898 if (source_date_epoch) {
899 char *endptr;
900 time_t now = strtoul(source_date_epoch, &endptr, 10);
901
902 if (*endptr != 0) {
903 fprintf(stderr, _("Failed to parse environment variable SOURCE_DATE_EPOCH.\n"));
904 exit(1);
905 }
906 struct tm *build_time = gmtime(&now);
907 char buffer[25];
908 strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S%z", build_time);
909
910 cairo_pdf_surface_set_metadata(surface,
911 CAIRO_PDF_METADATA_CREATE_DATE,
912 buffer);
913 }
914 }
915
main(int argc,char ** argv)916 int main(int argc, char **argv)
917 {
918 setlocale(LC_ALL, "");
919 bindtextdomain(CMAKE_PROJECT_NAME, CMAKE_INSTALL_FULL_LOCALEDIR);
920 textdomain(CMAKE_PROJECT_NAME);
921
922 parse_options(argc, argv);
923
924 FT_Library library;
925 FT_Error error = FT_Init_FreeType(&library);
926
927 if (error) {
928 /* TRANSLATORS: 'freetype' is a name of a library, and should be left untranslated */
929 fprintf(stderr, _("%s: freetype error\n"), argv[0]);
930 exit(3);
931 }
932
933 FT_Face face;
934 error = FT_New_Face(library, font_file_name, font_index, &face);
935
936 if (error) {
937 fprintf(stderr, _("%s: failed to open font file %s\n"), argv[0], font_file_name);
938 exit(4);
939 }
940
941 FT_Face other_face = NULL;
942
943 if (other_font_file_name) {
944 error = FT_New_Face(library, other_font_file_name, other_index, &other_face);
945
946 if (error) {
947 fprintf(stderr, _("%s: failed to create new font face\n"), argv[0]);
948 exit(4);
949 }
950 }
951
952 cairo_surface_t *surface;
953
954 if (postscript_output) {
955 surface = cairo_ps_surface_create(output_file_name, A4_WIDTH, A4_HEIGHT);
956 } else if (svg_output) {
957 surface = cairo_svg_surface_create(output_file_name, A4_WIDTH, A4_HEIGHT);
958 } else {
959 surface = cairo_pdf_surface_create(output_file_name, A4_WIDTH, A4_HEIGHT); /* A4 paper */
960 set_repeatable_pdf_metadata(surface);
961 }
962
963 cairo_status_t cr_status = cairo_surface_status(surface);
964 if (cr_status != CAIRO_STATUS_SUCCESS) {
965 /* TRANSLATORS: 'cairo' is a name of a library, and should be left untranslated */
966 fprintf(stderr, _("%s: failed to create cairo surface: %s\n"),
967 argv[0], cairo_status_to_string(cr_status));
968 exit(1);
969 }
970
971 cairo_t *cr = cairo_create(surface);
972 cr_status = cairo_status(cr);
973 if (cr_status != CAIRO_STATUS_SUCCESS) {
974 fprintf(stderr, _("%s: cairo_create failed: %s\n"),
975 argv[0], cairo_status_to_string(cr_status));
976 exit(1);
977 }
978
979 cairo_surface_destroy(surface);
980
981 init_table_fonts();
982 calculate_offsets(cr);
983
984 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
985 calc_font_scaling(face);
986 draw_glyphs(cr, face, other_face);
987 cairo_destroy(cr);
988
989 return 0;
990 }
991