| /* |
| * Copyright © 2026 Behdad Esfahbod |
| * |
| * This is part of HarfBuzz, a text shaping library. |
| * |
| * Permission is hereby granted, without written agreement and without |
| * license or royalty fees, to use, copy, modify, and distribute this |
| * software and its documentation for any purpose, provided that the |
| * above copyright notice and the following two paragraphs appear in |
| * all copies of this software. |
| * |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| * |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| * |
| * Author(s): Behdad Esfahbod |
| */ |
| |
| #include "hb.hh" |
| #include <hb-ot.h> |
| #include "hb-raster.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <math.h> |
| |
| |
| static void |
| write_ppm (hb_raster_image_t *img, const char *dir, unsigned gid) |
| { |
| hb_raster_extents_t ext; |
| hb_raster_image_get_extents (img, &ext); |
| if (!ext.width || !ext.height) return; |
| |
| char path[512]; |
| snprintf (path, sizeof path, "%s/%05u.ppm", dir, gid); |
| FILE *f = fopen (path, "wb"); |
| if (!f) { fprintf (stderr, "cannot write %s\n", path); return; } |
| |
| const uint8_t *buf = hb_raster_image_get_buffer (img); |
| hb_raster_format_t fmt = hb_raster_image_get_format (img); |
| |
| fprintf (f, "P6\n%u %u\n255\n", ext.width, ext.height); |
| uint8_t rgb[3]; |
| for (unsigned row = 0; row < ext.height; row++) |
| { |
| const uint8_t *src = buf + (ext.height - 1 - row) * ext.stride; |
| for (unsigned x = 0; x < ext.width; x++) |
| { |
| if (fmt == HB_RASTER_FORMAT_A8) |
| { |
| rgb[0] = rgb[1] = rgb[2] = (uint8_t) (255 - src[x]); |
| } |
| else /* BGRA32 — composite over white */ |
| { |
| uint32_t px; |
| hb_memcpy (&px, src + x * 4, 4); |
| uint8_t b = (uint8_t) (px & 0xFF); |
| uint8_t g = (uint8_t) ((px >> 8) & 0xFF); |
| uint8_t r = (uint8_t) ((px >> 16) & 0xFF); |
| uint8_t a = (uint8_t) (px >> 24); |
| unsigned inv_a = 255 - a; |
| rgb[0] = (uint8_t) (r + ((255 * inv_a + 128 + ((255 * inv_a + 128) >> 8)) >> 8)); |
| rgb[1] = (uint8_t) (g + ((255 * inv_a + 128 + ((255 * inv_a + 128) >> 8)) >> 8)); |
| rgb[2] = (uint8_t) (b + ((255 * inv_a + 128 + ((255 * inv_a + 128) >> 8)) >> 8)); |
| } |
| fwrite (rgb, 1, 3, f); |
| } |
| } |
| fclose (f); |
| } |
| |
| int |
| main (int argc, char **argv) |
| { |
| unsigned num_iterations = 1; |
| int argi = 1; |
| for (; argi < argc; argi++) |
| { |
| const char *arg = argv[argi]; |
| if (strcmp (arg, "--") == 0) |
| { |
| argi++; |
| break; |
| } |
| if (arg[0] != '-') |
| break; |
| if (strcmp (arg, "-h") == 0 || strcmp (arg, "--help") == 0) |
| { |
| fprintf (stderr, "Usage: %s [-n N|--num-iterations N] font-file [font-size] [output-dir]\n", argv[0]); |
| return 0; |
| } |
| if (strcmp (arg, "-n") == 0 || strcmp (arg, "--num-iterations") == 0) |
| { |
| if (++argi >= argc) |
| { |
| fprintf (stderr, "Missing value for %s\n", arg); |
| return 1; |
| } |
| char *end = nullptr; |
| long n = strtol (argv[argi], &end, 10); |
| if (!argv[argi][0] || end == argv[argi] || *end || n <= 0) |
| { |
| fprintf (stderr, "Invalid --num-iterations: %s\n", argv[argi]); |
| return 1; |
| } |
| num_iterations = (unsigned) n; |
| continue; |
| } |
| |
| fprintf (stderr, "Unknown option: %s\n", arg); |
| return 1; |
| } |
| |
| if (argc - argi < 1 || argc - argi > 3) |
| { |
| fprintf (stderr, "Usage: %s [-n N|--num-iterations N] font-file [font-size] [output-dir]\n", argv[0]); |
| return 1; |
| } |
| |
| hb_face_t *face = hb_face_create_from_file_or_fail (argv[argi], 0); |
| if (!face) { fprintf (stderr, "Failed to open font\n"); return 1; } |
| |
| unsigned upem = hb_face_get_upem (face); |
| |
| int font_size = (argc - argi > 1) ? atoi (argv[argi + 1]) : (int) upem; |
| if (font_size <= 0) font_size = (int) upem; |
| |
| const char *outdir = (argc - argi > 2) ? argv[argi + 2] : nullptr; |
| |
| hb_font_t *font = hb_font_create (face); |
| hb_font_set_scale (font, font_size, font_size); |
| |
| bool has_color = hb_ot_color_has_paint (face) || |
| hb_ot_color_has_layers (face) || |
| #ifndef HB_NO_SVG |
| hb_ot_color_has_svg (face); |
| #else |
| false; |
| #endif |
| |
| hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail (); |
| hb_raster_paint_t *pnt = has_color ? hb_raster_paint_create_or_fail () : nullptr; |
| unsigned glyph_count = hb_face_get_glyph_count (face); |
| |
| for (unsigned gid = 0; gid < glyph_count; gid++) |
| { |
| for (unsigned iter = 0; iter < num_iterations; iter++) |
| { |
| hb_raster_image_t *img = nullptr; |
| |
| if (pnt) |
| { |
| hb_glyph_extents_t gext; |
| if (hb_font_get_glyph_extents (font, gid, &gext) && |
| hb_raster_paint_set_glyph_extents (pnt, &gext)) |
| { |
| hb_raster_paint_set_transform (pnt, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f); |
| hb_bool_t painted = hb_raster_paint_glyph_or_fail (pnt, font, gid, 0.f, 0.f); |
| if (painted) |
| img = hb_raster_paint_render (pnt); |
| } |
| |
| if (img) |
| { |
| if (outdir && iter + 1 == num_iterations) |
| write_ppm (img, outdir, gid); |
| hb_raster_paint_recycle_image (pnt, img); |
| continue; |
| } |
| } |
| |
| hb_raster_draw_set_transform (rdr, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f); |
| hb_raster_draw_glyph (rdr, font, gid, 0.f, 0.f); |
| |
| img = hb_raster_draw_render (rdr); |
| if (img) |
| { |
| if (outdir && iter + 1 == num_iterations) |
| write_ppm (img, outdir, gid); |
| hb_raster_draw_recycle_image (rdr, img); |
| } |
| } |
| } |
| |
| hb_raster_paint_destroy (pnt); |
| hb_raster_draw_destroy (rdr); |
| hb_font_destroy (font); |
| hb_face_destroy (face); |
| return 0; |
| } |