/* See LICENSE file for copyright and license details. */ #include "libskrift.h" #include #include #include #include #include #include #include #define TEST_TEXT "hello world" USAGE("[-b background-colour] [-f foreground-colour] [-a opacity] [-t character-transformation] ... " "[-T character-transformation] ... [-k kerning] [-i interletter-spacing] [-g grid_fineness] " "[-S smoothing-method[:subpixel-order]] [-H hiniting] [-o flags] ... [-s font-size] [-F font-file] " "[-D dpi-x:dpi-y | -D dpi] [-w width] [-h height] [-x x-position] [-y y-position] [text]"); static uint8_t decode_hexbyte(char hi, char lo) { int ret; ret = ((hi & 15) + 9 * (hi > '9')) << 4; ret |= ((lo & 15) + 9 * (lo > '9')) << 0; return (uint8_t)ret; } static void parse_colour(const char *s, uint8_t *redp, uint8_t *greenp, uint8_t *bluep, uint8_t *alphap) { char rh, rl, gh, gl, bh, bl, ah = 'f', al = 'f'; if (!isxdigit(s[0])) goto invalid; if (!isxdigit(s[1])) goto invalid; if (!isxdigit(s[2])) goto invalid; if (!isxdigit(s[3])) goto invalid; if (!isxdigit(s[4])) goto invalid; if (!isxdigit(s[5])) goto invalid; rh = *s++; rl = *s++; gh = *s++; gl = *s++; bh = *s++; bl = *s++; if (*s) { if (!isxdigit(s[0])) goto invalid; if (!isxdigit(s[1])) goto invalid; if (s[2]) goto invalid; ah = *s++; al = *s++; } *redp = decode_hexbyte(rh, rl); *greenp = decode_hexbyte(gh, gl); *bluep = decode_hexbyte(bh, bl); *alphap = decode_hexbyte(ah, al); return; invalid: fprintf(stderr, "%s: colours shall be encoded as rrggbb or rrggbbaa, in hexadecimal\n", argv0); exit(1); } _LIBSKRIFT_GCC_ONLY(__attribute__((__pure__))) static uint16_t parse_uint16(const char *s) { uint16_t ret, digit; for (ret = 0; *s; s++) { if (!isdigit(*s)) usage(); digit = (uint16_t)(*s & 15); if (ret > (UINT16_MAX - digit) / 10) usage(); ret *= 10; ret += digit; } return ret; } _LIBSKRIFT_GCC_ONLY(__attribute__((__pure__))) static int16_t parse_int16(const char *s, const char **end) { int16_t ret, digit; int neg = 0; if (*s == '+') { s++; } else if (*s == '-') { neg = 1; s++; } for (ret = 0; *s; s++) { if (!isdigit(*s)) break; digit = (int16_t)(*s & 15); if (ret < (INT16_MIN + digit) / 10) usage(); ret *= 10; ret -= digit; } if (!neg) ret = -ret; *end = s; return ret; } static void parse_transformation(char *s, double matrix[6]) { #define S(T) T, n = (sizeof(T) - 1) #define S0(T) (n = (sizeof(T) - 1), T) size_t n; double f, x, y, m[6]; while (*s) { if (*s == ',') { s++; } else if (!strncmp(s, S("rotate="))) { s += n; errno = 0; f = strtod(s, &s); if (errno) goto invalid; if (*s) { if (s[0] == 'd' && s[1] == 'e' && s[2] == 'g') { s += 3; libskrift_add_rotation_degrees(matrix, f); } else { libskrift_add_rotation(matrix, f); } if (*s && *s != ',') goto invalid; } } else if (!strncmp(s, S("shearx="))) { s += n; errno = 0; f = strtod(s, &s); if (errno || (*s && *s != ',')) goto invalid; libskrift_add_shear(matrix, f, 0); } else if (!strncmp(s, S("sheary="))) { s += n; errno = 0; f = strtod(s, &s); if (errno || (*s && *s != ',')) goto invalid; libskrift_add_shear(matrix, 0, f); } else if (!strncmp(s, S("shear="))) { s += n; errno = 0; x = strtod(s, &s); if (errno || *s != ':') goto invalid; y = strtod(&s[1], &s); if (errno || (*s && *s != ',')) goto invalid; libskrift_add_shear(matrix, x, y); } else if (!strncmp(s, S("scalex="))) { s += n; errno = 0; f = strtod(s, &s); if (errno || (*s && *s != ',')) goto invalid; libskrift_add_scaling(matrix, f, 1); } else if (!strncmp(s, S("scaley="))) { s += n; errno = 0; f = strtod(s, &s); if (errno || (*s && *s != ',')) goto invalid; libskrift_add_scaling(matrix, 1, f); } else if (!strncmp(s, S("scale="))) { s += n; errno = 0; x = strtod(s, &s); if (errno) goto invalid; if (*s == ':') { y = strtod(&s[1], &s); if (errno || (*s && *s != ',')) goto invalid; libskrift_add_scaling(matrix, x, y); } else if (*s && *s != ',') { goto invalid; } else { libskrift_add_scaling(matrix, x, x); } } else if (!strncmp(s, S("translate="))) { s += n; errno = 0; x = strtod(s, &s); if (errno || *s != ':') goto invalid; y = strtod(&s[1], &s); if (errno || (*s && *s != ',')) goto invalid; libskrift_add_translation(matrix, x, y); } else if (!strncmp(s, S("transform="))) { s += n; errno = 0; m[0] = strtod(s, &s); if (errno || *s != ':') goto invalid; m[1] = strtod(&s[1], &s); if (errno || *s != ':') goto invalid; m[2] = strtod(&s[1], &s); if (errno || s[0] != ':' || s[1] != ':') goto invalid; m[3] = strtod(&s[2], &s); if (errno || *s != ':') goto invalid; m[4] = strtod(&s[1], &s); if (errno || *s != ':') goto invalid; m[5] = strtod(&s[1], &s); if (errno || (*s && *s != ',')) goto invalid; libskrift_add_transformation(matrix, m); } else if (!strncmp(s, S("rotate90,")) || !strcmp(s, S0("rotate90"))) { s += n; libskrift_add_90_degree_rotation(matrix); } else if (!strncmp(s, S("rotate180,")) || !strcmp(s, S0("rotate180"))) { s += n; libskrift_add_180_degree_rotation(matrix); } else if (!strncmp(s, S("rotate270,")) || !strcmp(s, S0("rotate270"))) { s += n; libskrift_add_270_degree_rotation(matrix); } else if (!strncmp(s, S("transpose,")) || !strcmp(s, S0("transpose"))) { s += n; libskrift_add_transposition(matrix); } else { invalid: fprintf(stderr, "%s: valid transformations are:\n", argv0); fprintf(stderr, "%s: rotate=\n", argv0); fprintf(stderr, "%s: rotate=deg\n", argv0); fprintf(stderr, "%s: shearx=\n", argv0); fprintf(stderr, "%s: sheary=\n", argv0); fprintf(stderr, "%s: shear=:\n", argv0); fprintf(stderr, "%s: scalex=\n", argv0); fprintf(stderr, "%s: scaley=\n", argv0); fprintf(stderr, "%s: scale=\n", argv0); fprintf(stderr, "%s: scale=:\n", argv0); fprintf(stderr, "%s: translate=:\n", argv0); fprintf(stderr, "%s: transform=::::::\n", argv0); fprintf(stderr, "%s: rotate90\n", argv0); fprintf(stderr, "%s: rotate180\n", argv0); fprintf(stderr, "%s: rotate270\n", argv0); fprintf(stderr, "%s: transpose\n", argv0); exit(1); } } #undef S #undef S0 } static double apply_unit(double size, const char *unit, const struct libskrift_rendering *rendering, const char *unit_for) { if (!strcmp(unit, "px")) return size; if (!strcmp(unit, "pt")) return libskrift_points_to_pixels(size, rendering); if (!strcmp(unit, "in")) return libskrift_inches_to_pixels(size, rendering); if (!strcmp(unit, "mm")) return libskrift_millimeters_to_pixels(size, rendering); if (!strcmp(unit, "cm")) return libskrift_millimeters_to_pixels(size * 10, rendering); fprintf(stderr, "%s: valid %s units are: 'px', 'pt', 'in', 'mm', 'cm'", argv0, unit_for); exit(1); } int main(int argc, char *argv[]) { LIBSKRIFT_FONT *font; LIBSKRIFT_CONTEXT *ctx; struct libskrift_image image = {LIBSKRIFT_R8G8B8A8, LIBSKRIFT_BE_SUBPIXEL, 0, 800, 600, NULL, NULL, NULL}; struct libskrift_rendering rendering = LIBSKRIFT_DEFAULT_RENDERING; struct libskrift_colour colour; const char *font_file = DEMO_FONT; double font_size = 72; const char *font_size_unit = "pt"; const char *x_unit = "", *y_unit = ""; const char *kerning_unit = "", *interletter_spacing_unit = ""; double height, opacity = .80f, f; size_t size, i; char *end, *arg; const char *text = TEST_TEXT; size_t text_length = sizeof(TEST_TEXT) - 1; uint8_t background_red = 32U, foreground_red = 204U; uint8_t background_green = 48U, foreground_green = 128U; uint8_t background_blue = 64U, foreground_blue = 51U; uint8_t background_alpha = 250U, foreground_alpha = 128U; long int tmp_long; int16_t x = 0, y = 300; ARGBEGIN { case 'b': parse_colour(ARG(), &background_red, &background_green, &background_blue, &background_alpha); break; case 'f': parse_colour(ARG(), &foreground_red, &foreground_green, &foreground_blue, &foreground_alpha); break; case 'F': font_file = ARG(); break; case 'a': errno = 0; opacity = strtod(ARG(), &end); if (errno || *end) usage(); break; case 's': errno = 0; font_size = strtod(ARG(), &end); if (errno) usage(); font_size_unit = end; break; case 'S': errno = 0; arg = ARG(); if (!strncmp(arg, "monochrome", strlen("monochrome"))) { arg += strlen("monochrome"); rendering.smoothing = LIBSKRIFT_MONOCHROME; } else if (!strncmp(arg, "none", strlen("none"))) { arg += strlen("none"); rendering.smoothing = LIBSKRIFT_NONE; /* = LIBSKRIFT_MONOCHROME */ } else if (!strncmp(arg, "greyscale", strlen("greyscale"))) { arg += strlen("greyscale"); rendering.smoothing = LIBSKRIFT_GREYSCALE; } else if (!strncmp(arg, "subpixel", strlen("subpixel"))) { arg += strlen("subpixel"); rendering.smoothing = LIBSKRIFT_SUBPIXEL; } else { fprintf(stderr, "%s: valid smoothing methods are: 'monochrome', 'greyscale', 'subpixel'\n", argv0); return 1; } if (!*arg) { rendering.subpixel_order = LIBSKRIFT_RGB; break; } if (*arg++ != ':') usage(); if (!strcmp(arg, "other")) { rendering.subpixel_order = LIBSKRIFT_OTHER; } else if (!strcmp(arg, "none")) { rendering.subpixel_order = LIBSKRIFT_NONE; /* = LIBSKRIFT_OTHER */ } else if (!strcmp(arg, "rgb")) { rendering.subpixel_order = LIBSKRIFT_RGB; } else if (!strcmp(arg, "bgr")) { rendering.subpixel_order = LIBSKRIFT_BGR; } else if (!strcmp(arg, "vrgb")) { rendering.subpixel_order = LIBSKRIFT_VRGB; } else if (!strcmp(arg, "vbgr")) { rendering.subpixel_order = LIBSKRIFT_VBGR; } else { fprintf(stderr, "%s: valid subpixel orders are: 'other', 'rgb', 'bgr', 'vrgb', 'cbgr'\n", argv0); return 1; } break; case 'H': arg = ARG(); if (!strcmp(arg, "none")) { rendering.hinting = LIBSKRIFT_NONE; } else if (!strcmp(arg, "unhinted")) { rendering.hinting = LIBSKRIFT_UNHINTED; /* = LIBSKRIFT_NONE */ } else if (!strcmp(arg, "slight")) { rendering.hinting = LIBSKRIFT_SLIGHT; } else if (!strcmp(arg, "medium")) { rendering.hinting = LIBSKRIFT_MEDIUM; } else if (!strcmp(arg, "full")) { rendering.hinting = LIBSKRIFT_FULL; } else if (isdigit(*arg)) { rendering.hinting = 0; for (; isdigit(*arg); arg++) { rendering.hinting *= 10; rendering.hinting += *arg & 15; } if (*arg) usage(); } else { fprintf(stderr, "%s: valid hintings are: 'none', 'slight', 'medium', 'full', \n", argv0); return 1; } break; case 'D': errno = 0; rendering.horizontal_dpi = strtod(ARG(), &end); if (errno) usage(); if (!*end) { rendering.vertical_dpi = rendering.horizontal_dpi; } else if (*end != ':') { usage(); } else { rendering.vertical_dpi = strtod(&end[1], &end); if (errno || *end) usage(); } break; case 'w': image.width = parse_uint16(ARG()); break; case 'h': image.height = parse_uint16(ARG()); break; case 'x': x = parse_int16(ARG(), &x_unit); break; case 'y': y = parse_int16(ARG(), &y_unit); break; case 't': parse_transformation(ARG(), rendering.char_transformation); break; case 'T': parse_transformation(ARG(), rendering.text_transformation); break; case 'k': errno = 0; rendering.kerning = strtod(ARG(), &end); if (errno) usage(); kerning_unit = end; break; case 'i': errno = 0; rendering.interletter_spacing = strtod(ARG(), &end); if (errno) usage(); interletter_spacing_unit = end; break; case 'g': errno = 0; tmp_long = strtol(ARG(), &end, 10); rendering.grid_fineness = (int)tmp_long; if (errno || *end || tmp_long < INT_MIN || tmp_long > INT_MAX) usage(); break; case 'o': arg = ARG(); while (*arg) { if (*arg == ',') { arg++; continue; } #define TEST(T) strncmp(arg, T, sizeof(T) - 1) ? 0 : (arg += sizeof(T) - 1, 1) if (TEST("remove-gamma")) { rendering.flags |= LIBSKRIFT_REMOVE_GAMMA; } else if (TEST("y-increases-upwards")) { rendering.flags |= LIBSKRIFT_Y_INCREASES_UPWARDS; } else if (TEST("flip-text")) { rendering.flags ^= LIBSKRIFT_FLIP_TEXT; } else if (TEST("flip-chars")) { rendering.flags ^= LIBSKRIFT_FLIP_CHARS; } else if (TEST("mirror-text")) { rendering.flags ^= LIBSKRIFT_MIRROR_TEXT; } else if (TEST("mirror-chars")) { rendering.flags ^= LIBSKRIFT_MIRROR_CHARS; } else if (TEST("transpose-text")) { rendering.flags ^= LIBSKRIFT_TRANSPOSE_TEXT; } else if (TEST("transpose-chars")) { rendering.flags ^= LIBSKRIFT_TRANSPOSE_CHARS; } else if (TEST("no-ligatures")) { rendering.flags |= LIBSKRIFT_NO_LIGATURES; } else if (TEST("advance-char-to-grid")) { rendering.flags |= LIBSKRIFT_ADVANCE_CHAR_TO_GRID; } else if (TEST("regress-char-to-grid")) { rendering.flags |= LIBSKRIFT_REGRESS_CHAR_TO_GRID; } else if (TEST("advance-word-to-grid")) { rendering.flags |= LIBSKRIFT_ADVANCE_WORD_TO_GRID; } else if (TEST("regress-word-to-grid")) { rendering.flags |= LIBSKRIFT_REGRESS_WORD_TO_GRID; } else if (TEST("use-subpixel-grid")) { rendering.flags |= LIBSKRIFT_USE_SUBPIXEL_GRID; } else if (TEST("vertical-text")) { rendering.flags |= LIBSKRIFT_VERTICAL_TEXT; } else if (TEST("autohinting")) { rendering.flags |= LIBSKRIFT_AUTOHINTING; } else if (TEST("no-autohinting")) { rendering.flags |= LIBSKRIFT_NO_AUTOHINTING; } else if (TEST("autokerning")) { rendering.flags |= LIBSKRIFT_AUTOKERNING; } else if (TEST("no-autokerning")) { rendering.flags |= LIBSKRIFT_NO_AUTOKERNING; } else if (TEST("ignore-ignorable")) { rendering.flags |= LIBSKRIFT_IGNORE_IGNORABLE; } else { invalid_flag: fprintf(stderr, "%s: valid flags are:\n", argv0); fprintf(stderr, "%s: remove-gamma\n", argv0); fprintf(stderr, "%s: y-increases-upwards\n", argv0); fprintf(stderr, "%s: flip-text\n", argv0); fprintf(stderr, "%s: flip-chars\n", argv0); fprintf(stderr, "%s: mirror-text\n", argv0); fprintf(stderr, "%s: mirror-chars\n", argv0); fprintf(stderr, "%s: transpose-text\n", argv0); fprintf(stderr, "%s: transpose-chars\n", argv0); fprintf(stderr, "%s: no-ligatures\n", argv0); fprintf(stderr, "%s: advance-char-to-grid\n", argv0); fprintf(stderr, "%s: regress-char-to-grid\n", argv0); fprintf(stderr, "%s: advance-word-to-grid\n", argv0); fprintf(stderr, "%s: regress-word-to-grid\n", argv0); fprintf(stderr, "%s: use-subpixel-grid\n", argv0); fprintf(stderr, "%s: vertical-text\n", argv0); fprintf(stderr, "%s: autohinting\n", argv0); fprintf(stderr, "%s: no-autohinting\n", argv0); fprintf(stderr, "%s: autokerning\n", argv0); fprintf(stderr, "%s: no-autokerning\n", argv0); fprintf(stderr, "%s: ignore-ignorable\n", argv0); return 1; } #undef TEST if (*arg && *arg != ',') goto invalid_flag; } break; default: usage(); } ARGEND; colour = (struct libskrift_colour)LIBSKRIFT_PREMULTIPLY((float)opacity, (float)foreground_alpha / 255, (float)foreground_red / 255, (float)foreground_green / 255, (float)foreground_blue / 255); if (argc > 1) usage(); if (argc == 1) { text = argv[0]; text_length = strlen(text); } if (isatty(STDOUT_FILENO)) { fprintf(stderr, "Output file is a binary PAM image file, I am not writing that to a terminal.\n"); return 1; } if (*x_unit && strcmp(x_unit, "px")) { f = (double)x; f = apply_unit(f, x_unit, &rendering, "position"); x = (int16_t)f; } if (*y_unit && strcmp(y_unit, "px")) { f = (double)y; f = apply_unit(f, y_unit, &rendering, "position"); y = (int16_t)f; } if (*kerning_unit && strcmp(kerning_unit, "px")) { f = rendering.kerning; f = apply_unit(f, kerning_unit, &rendering, "spacing"); rendering.kerning = f; } if (*interletter_spacing_unit && strcmp(interletter_spacing_unit, "px")) { f = rendering.interletter_spacing; f = apply_unit(f, interletter_spacing_unit, &rendering, "spacing"); rendering.interletter_spacing = f; } if (libskrift_open_font_file(&font, font_file)) { perror("libskrift_open_font_file"); return 1; } height = apply_unit(font_size, font_size_unit, &rendering, "font size"); if (libskrift_create_context(&ctx, &font, 1, height, &rendering, NULL)) { perror("libskrift_create_context"); return 1; } libskrift_close_font(font); size = 4; size *= (size_t)image.width; size *= (size_t)image.height; if (size) { image.image = malloc(size); if (!image.image) { perror("malloc"); return 1; } } for (i = 0; i < size; i += 4) { ((uint8_t *)image.image)[i + 0] = background_red; ((uint8_t *)image.image)[i + 1] = background_green; ((uint8_t *)image.image)[i + 2] = background_blue; ((uint8_t *)image.image)[i + 3] = background_alpha; } if (libskrift_draw_text(ctx, text, text_length, &colour, x, y, &image) < 0) { perror("libskrift_draw_text"); return 1; } printf("P7\n"); printf("WIDTH %u\n", image.width); printf("HEIGHT %u\n", image.height); printf("DEPTH 4\n"); printf("MAXVAL 255\n"); printf("TUPLTYPE RGB_ALPHA\n"); printf("ENDHDR\n"); fwrite(image.image, 1, size, stdout); fflush(stdout); free(image.image); libskrift_free_context(ctx); return 0; }