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