/** * xrdp: A Remote Desktop Protocol server. * * Copyright (C) Jay Sorg 2004-2012 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include FT_FREETYPE_H /* See the FT2 documentation - this builds an error table */ #undef FTERRORS_H_ #define FT_ERRORDEF( e, v, s ) { e, s }, #define FT_ERROR_START_LIST { #define FT_ERROR_END_LIST { 0, NULL } }; static const struct { int err_code; const char *err_msg; } ft_errors[] = #include #ifdef __cppcheck__ // avoid syntaxError by providing the array contents {}; #endif #include "arch.h" #include "defines.h" #include "log.h" #include "os_calls.h" #include "string_calls.h" #include "fv1.h" #define DEFAULT_POINT_SIZE 10 #define DEFAULT_MAX_CHAR 0x4dff /** * sans10 compatibility choices */ enum sans10_compat { S10_OFF = 0, S10_ON, S10_AUTO }; /** * Parsed program arguments */ struct program_args { const char *input_file; const char *output_file; char font_name[FV1_MAX_FONT_NAME_SIZE + 1]; unsigned short point_size; /** Last character value in file */ unsigned int max_ucode; /** Are we generating san10 in compatibility mode? */ enum sans10_compat sans10_compatibility; }; struct x_dimensions { unsigned short width; short offset; unsigned short inc_by; }; /** * Table of some character settings in the original sans-10.fv1 font */ static const struct x_dimensions original_sans10_data[] = { /* 0x20 - 0x3f */ {1, 0, 4}, {1, 2, 5}, {3, 1, 5}, {9, 1, 11}, {7, 1, 8}, {11, 0, 12}, {9, 1, 11}, {1, 1, 3}, {3, 1, 5}, {3, 1, 5}, {7, 0, 7}, {9, 1, 11}, {2, 1, 4}, {3, 1, 5}, {1, 2, 4}, {4, 0, 4}, {6, 1, 8}, {5, 2, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {1, 2, 4}, {2, 1, 4}, {8, 1, 11}, {8, 1, 11}, {8, 1, 11}, {5, 1, 7}, /* 0x40 - 0x5f */ {11, 1, 13}, {9, 0, 9}, {7, 1, 9}, {7, 1, 9}, {8, 1, 10}, {6, 1, 8}, {5, 1, 7}, {8, 1, 10}, {8, 1, 10}, {1, 1, 3}, {3, -1, 3}, {7, 1, 8}, {6, 1, 7}, {9, 1, 11}, {8, 1, 10}, {8, 1, 10}, {6, 1, 8}, {8, 1, 10}, {7, 1, 8}, {7, 1, 9}, {7, 0, 7}, {8, 1, 10}, {9, 0, 9}, {11, 0, 11}, {8, 0, 8}, {7, 0, 7}, {8, 1, 10}, {3, 1, 5}, {4, 0, 4}, {3, 1, 5}, {8, 1, 11}, {7, 0, 7}, /* 0x60 - 0x7f */ {3, 1, 7}, {6, 1, 8}, {6, 1, 8}, {5, 1, 7}, {6, 1, 8}, {6, 1, 8}, {4, 0, 4}, {6, 1, 8}, {6, 1, 8}, {1, 1, 3}, {2, 0, 3}, {6, 1, 7}, {1, 1, 3}, {11, 1, 13}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {4, 1, 5}, {5, 1, 7}, {4, 0, 5}, {6, 1, 8}, {7, 0, 7}, {9, 0, 9}, {7, 0, 7}, {7, 0, 7}, {5, 1, 7}, {5, 2, 8}, {1, 2, 4}, {5, 2, 8}, {8, 1, 11}, {7, 1, 8}, /* 0x80 - 0x9f */ {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, {7, 1, 8}, /* 0xa0 - 0xbf */ {1, 0, 4}, {1, 2, 5}, {6, 1, 8}, {7, 0, 8}, {7, 0, 8}, {7, 1, 8}, {1, 2, 4}, {5, 1, 7}, {3, 2, 7}, {9, 2, 13}, {5, 1, 6}, {6, 1, 8}, {8, 1, 11}, {3, 1, 5}, {9, 2, 13}, {4, 1, 7}, {4, 1, 7}, {9, 1, 11}, {4, 1, 5}, {4, 1, 5}, {3, 2, 7}, {7, 1, 8}, {6, 1, 8}, {1, 1, 4}, {3, 2, 7}, {3, 1, 5}, {5, 1, 6}, {6, 1, 8}, {12, 1, 13}, {11, 1, 13}, {12, 1, 13}, {5, 1, 7}, /* 0xc0 - 0xdf */ {9, 0, 9}, {9, 0, 9}, {9, 0, 9}, {9, 0, 9}, {9, 0, 9}, {9, 0, 9}, {12, 0, 13}, {7, 1, 9}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {2, 0, 3}, {2, 1, 3}, {5, -1, 3}, {3, 0, 3}, {9, 0, 10}, {8, 1, 10}, {8, 1, 10}, {8, 1, 10}, {8, 1, 10}, {8, 1, 10}, {8, 1, 10}, {7, 2, 11}, {8, 1, 10}, {8, 1, 10}, {8, 1, 10}, {8, 1, 10}, {8, 1, 10}, {7, 0, 7}, {6, 1, 8}, {6, 1, 8}, /* 0xe0 - 0xff */ {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {11, 1, 13}, {5, 1, 7}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {3, 0, 3}, {3, 1, 3}, {5, -1, 3}, {3, 0, 3}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {8, 1, 11}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {6, 1, 8}, {7, 0, 7}, {6, 1, 8}, {7, 0, 7} }; /**************************************************************************//** * Parses a unicode character value * * @param str String containing value * @return Resulting character value */ static unsigned int parse_ucode_name(const char *str) { unsigned int rv; if (toupper(str[0]) == 'U' && str[1] == '+') { char *hex = g_strdup(str); hex[0] = '0'; hex[1] = 'x'; rv = g_atoix(hex); g_free(hex); } else if (str[0] == '@') { rv = str[1]; } else { rv = g_atoix(str); } return rv; } /**************************************************************************//** * Parses the program args * * @param argc Passed to main * @param @argv Passed to main * @param pa program_pargs structure for resulting values * @return !=0 for success */ static int parse_program_args(int argc, char *argv[], struct program_args *pa) { int params_ok = 1; int opt; pa->input_file = NULL; pa->output_file = NULL; pa->font_name[0] = '\0'; pa->point_size = DEFAULT_POINT_SIZE; pa->max_ucode = DEFAULT_MAX_CHAR; pa->sans10_compatibility = S10_AUTO; while ((opt = getopt(argc, argv, "n:p:m:C:")) != -1) { switch (opt) { case 'n': g_snprintf(pa->font_name, FV1_MAX_FONT_NAME_SIZE + 1, "%s", optarg); break; case 'p': pa->point_size = g_atoi(optarg); break; case 'm': pa->max_ucode = parse_ucode_name(optarg); break; case 'C': if (toupper(optarg[0]) == 'A') { pa->sans10_compatibility = S10_AUTO; } else if (g_text2bool(optarg)) { pa->sans10_compatibility = S10_ON; } else { pa->sans10_compatibility = S10_OFF; } break; default: LOG(LOG_LEVEL_ERROR, "Unrecognised switch '%c'", (char)opt); params_ok = 0; } } if (pa->max_ucode < FV1_MIN_CHAR) { LOG(LOG_LEVEL_ERROR, "-m argument must be at least %d", FV1_MIN_CHAR); params_ok = 0; } switch (argc - optind) { case 0: LOG(LOG_LEVEL_ERROR, "No input file specified"); params_ok = 0; break; case 1: LOG(LOG_LEVEL_ERROR, "No output file specified"); params_ok = 0; break; case 2: pa->input_file = argv[optind]; pa->output_file = argv[optind + 1]; break; default: LOG(LOG_LEVEL_ERROR, "Unexpected arguments after output file"); params_ok = 0; } return params_ok; } /**************************************************************************//** * Checks whether the specified glyph row is blank * @param g Glyph * @param row Row number between 0 and g->height - 1 * @result Boolean */ static int is_blank_glyph_row(const struct fv1_glyph *g, unsigned int row) { if (g->width == 0 || row >= g->height) { return 1; } const unsigned int glyph_row_size = ((g->width + 7) / 8); const unsigned char *dataptr = g->data + (row * glyph_row_size); const unsigned int masks[] = { 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff }; /* Check for set pixels in all the leading bytes */ unsigned int x; for (x = g->width ; x > 8 ; x -= 8) { if (*dataptr++ != 0) { return 0; } } /* Use a single test to check the pixels in the last byte */ return ((*dataptr & masks[x - 1]) == 0); } /**************************************************************************//** * Returns a string for a freetype2 error * @param error Freetype2 error code * @param buff Pointer to buffer for error string * @param bufflen Length of above */ static void get_ft_error(FT_Error error, char *buff, unsigned int bufflen) { const char *errstr = NULL; unsigned int i; for (i = 0 ; i < (sizeof(ft_errors) / sizeof(ft_errors[0])); ++i) { if (ft_errors[i].err_code == error) { errstr = ft_errors[i].err_msg; break; } } if (errstr != NULL) { g_snprintf(buff, bufflen, "[%s]", errstr); } else { g_snprintf(buff, bufflen, "[errorcode %d (no description)]", (int)error); } } /**************************************************************************//** * Implement sans10 compatibility for a glyph * * The original Windows font generator made a few different choices for the * character x offset than freetype2 does. These are particularly noticeable * with a small font. * * This routine checks the glyph, and implements the original offset size * for popular English characters, which are all that the user will probably * be displaying with xrdp v0.9.x * * @param g Glyph to check */ static void implement_sans10_compatibility(struct fv1_glyph *g, unsigned int ucode) { const unsigned int max_index = sizeof(original_sans10_data) / sizeof(original_sans10_data[0]); if (ucode < FV1_MIN_CHAR || ucode >= max_index + FV1_MIN_CHAR) { return; } const struct x_dimensions *d = &original_sans10_data[ucode - FV1_MIN_CHAR]; if (g->offset != d->offset) { if (g->width != d->width) { LOG(LOG_LEVEL_WARNING, "Can't set compatibility offset for U+%04X: width %d != %d", ucode, g->width, d->width); } else if (g->inc_by != d->inc_by) { LOG(LOG_LEVEL_WARNING, "Can't set compatibility offset for U+%04X: inc_by %d != %d", ucode, g->inc_by, d->inc_by); } else { LOG(LOG_LEVEL_INFO, "Changing compatibility offset for U+%04X: from %d to %d", ucode, g->offset, d->offset); } g->offset = d->offset; } } /**************************************************************************//** * Converts a freetype 2 bitmap to a fv1 glyph * @param ft_glyph Freetype2 glyph. Must be a monochrome bitmap * @param ucode Unicode character for error reporting * @param pa Program args * @return fv1 glyph, or NULL for error */ static struct fv1_glyph * convert_mono_glyph(FT_GlyphSlot ft_glyph, unsigned int ucode, const struct program_args *pa) { short width = ft_glyph->bitmap.width; short height = ft_glyph->bitmap.rows; struct fv1_glyph *g; /* Number of bytes in a glyph row */ const unsigned int glyph_row_size = ((width + 7) / 8); if ((g = fv1_alloc_glyph(ucode, width, height)) != NULL) { g->baseline = -(ft_glyph->metrics.horiBearingY / 64); g->offset = ft_glyph->metrics.horiBearingX / 64; g->inc_by = ft_glyph->metrics.horiAdvance / 64; if (FONT_DATASIZE(g) > 0) { const unsigned char *srcptr = ft_glyph->bitmap.buffer; unsigned char *dstptr = g->data; unsigned int y; for (y = 0; y < g->height; ++y) { g_memcpy(dstptr, srcptr, glyph_row_size); dstptr += glyph_row_size; srcptr += ft_glyph->bitmap.pitch; } /* Optimise the glyph by removing any blank lines at the bottom * and top */ if (g->width > 0) { while (g->height > 0 && is_blank_glyph_row(g, g->height - 1)) { --g->height; } y = 0; while (y < g->height && is_blank_glyph_row(g, y)) { ++y; } if (y > 0) { g->baseline += y; g->height -= y; g_memmove(g->data, g->data + (y * glyph_row_size), g->height * glyph_row_size); } } } } if (pa->sans10_compatibility != S10_OFF) { implement_sans10_compatibility(g, ucode); } return g; } /**************************************************************************//** * Main * * @param argc Argument count * @param argv Arguments */ int main(int argc, char *argv[]) { FT_Library library = NULL; /* handle to library */ FT_Face face = NULL; /* handle to face object */ FT_Error error; struct fv1_glyph *g; struct program_args pa; struct log_config *logging; int rv = 1; logging = log_config_init_for_console(LOG_LEVEL_WARNING, g_getenv("MKFV1_LOG_LEVEL")); log_start_from_param(logging); log_config_free(logging); struct fv1_file *fv1 = fv1_file_create(); if (fv1 == NULL) { LOG(LOG_LEVEL_ERROR, "Memory allocation failure"); } else if (parse_program_args(argc, argv, &pa)) { char errstr[128]; if ((error = FT_Init_FreeType(&library)) != 0) { get_ft_error(error, errstr, sizeof(errstr)); LOG(LOG_LEVEL_ERROR, "Error initializing freetype library %s", errstr); } else if ((error = FT_New_Face(library, pa.input_file, 0, &face)) != 0) { get_ft_error(error, errstr, sizeof(errstr)); LOG(LOG_LEVEL_ERROR, "Error loading font file %s %s", pa.input_file, errstr); } else if ((error = FT_Set_Char_Size(face, 0, pa.point_size * 64, FV1_DEVICE_DPI, 0)) != 0) { get_ft_error(error, errstr, sizeof(errstr)); LOG(LOG_LEVEL_ERROR, "Error setting point size to %u %s", pa.point_size, errstr); } else { const char *fname = (pa.font_name[0] != '\0') ? pa.font_name : (face->family_name != NULL) ? face->family_name : /* Default */ ""; g_snprintf(fv1->font_name, FV1_MAX_FONT_NAME_SIZE + 1, "%s", fname); fv1->point_size = pa.point_size; fv1->body_height = face->size->metrics.height / 64; fv1->min_descender = face->size->metrics.descender / 64; if (pa.sans10_compatibility == S10_AUTO) { if (g_strcmp(fv1->font_name, "DejaVu Sans") == 0 && fv1->point_size == 10) { pa.sans10_compatibility = S10_ON; } else { pa.sans10_compatibility = S10_OFF; } } unsigned int u; for (u = FV1_MIN_CHAR; u <= pa.max_ucode; ++u) { /* retrieve glyph index from character code */ FT_UInt glyph_index = FT_Get_Char_Index(face, u); /* load glyph image into the slot (erase previous one) */ error = FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER | FT_LOAD_TARGET_MONO); if (error) { get_ft_error(error, errstr, sizeof(errstr)); LOG(LOG_LEVEL_WARNING, "Unable to get bitmap for U+%04X %s", u, errstr); g = NULL; } else if (face->glyph->format != FT_GLYPH_FORMAT_BITMAP || face->glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO) { LOG(LOG_LEVEL_WARNING, "Internal error; U+%04X was not loaded as a bitmap", u); g = NULL; } else { g = convert_mono_glyph(face->glyph, u, &pa); } list_add_item(fv1->glyphs, (tintptr)g); } rv = fv1_file_save(fv1, pa.output_file); } } FT_Done_FreeType(library); fv1_file_free(fv1); log_end(); return rv; }