blob: 12f5b64c301e2778a6f6d2576e91a8e63daf3a25 [file] [edit]
/*
* 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;
}