diff --git a/Makefile.am b/Makefile.am index 7e724f34..d7a5d846 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,7 +11,6 @@ EXTRA_DIST = \ astyle_config.as \ bootstrap \ coding_style.md \ - fontdump \ m4 \ vrplayer @@ -57,6 +56,7 @@ SUBDIRS = \ $(RFXCODECDIR) \ sesman \ xrdp \ + fontutils \ keygen \ docs \ instfiles \ diff --git a/README.md b/README.md index e2d05699..2617c44f 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ pulseaudio modules. The build instructions can be found at wiki. xrdp ├── common ······ common code ├── docs ········ documentation -├── fontdump ···· font dump for Windows +├── fontutils ··· font handling utilities ├── genkeymap ··· keymap generator ├── instfiles ··· installable data file ├── keygen ······ xrdp RSA key pair generator diff --git a/configure.ac b/configure.ac index 895319b9..3759cd6b 100644 --- a/configure.ac +++ b/configure.ac @@ -190,6 +190,8 @@ AM_CONDITIONAL(XRDP_RDPSNDAUDIN, [test x$enable_rdpsndaudin = xyes]) AC_ARG_WITH(imlib2, AC_HELP_STRING([--with-imlib2=ARG], [imlib2 library to use for non-BMP backgrounds (ARG=yes/no/)]),,) +AC_ARG_WITH(freetype2, AC_HELP_STRING([--with-freetype2=ARG], [freetype2 library to use for rendering fonts (ARG=yes/no/)]),,) + # Obsolete options AC_ARG_ENABLE(xrdpdebug, AS_HELP_STRING([--enable-xrdpdebug], [This option is no longer supported - use --enable-devel-all])) @@ -278,6 +280,41 @@ if test x$use_imlib2 = xyes; then AC_DEFINE([USE_IMLIB2],1, [Compile with imlib2 support]) fi +# Find freetype2 +case "$with_freetype2" in + '' | no) AC_MSG_NOTICE([freetype2 will not be supported]) + use_freetype2=no + ;; + yes) + PKG_CHECK_MODULES([FREETYPE2], [freetype2 >= 2.8], + [use_freetype2=yes], + [AC_MSG_ERROR([please install libfreetype6-dev or freetype-devel])]) + ;; + /*) AC_MSG_CHECKING([for freetype2 in $with_freetype2]) + if test -d $with_freetype2/lib; then + FREETYPE2_LIBS="-L$with_freetype2/lib -llibfreetype" + elif test -d $with_freetype2/lib64; then + FREETYPE2_LIBS="-L$with_freetype2/lib64 -llibfreetype" + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([Can't find libfreetype in $with_freetype2]) + fi + + if test -f $with_freetype2/include/freetype2/ft2build.h; then + FREETYPE2_CFLAGS="-I $with_freetype2/include/freetype2" + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([Can't find $with_freetype2/include/freetype2/ft2build.h]) + fi + AC_MSG_RESULT([yes]) + AC_SUBST([FREETYPE2_LIBS]) + AC_SUBST([FREETYPE2_CFLAGS]) + use_freetype2=yes + ;; + *) AC_MSG_ERROR([--with-freetype2 needs yes/no or absolute path]) +esac +AM_CONDITIONAL([USE_FREETYPE2], [test "x$use_freetype2" = xyes]) + # Check only one auth mechanism is specified, and give it a name auth_cnt=0 auth_mech="Builtin" @@ -494,6 +531,7 @@ AC_CONFIG_FILES([ common/Makefile docs/Makefile docs/man/Makefile + fontutils/Makefile genkeymap/Makefile instfiles/default/Makefile instfiles/init.d/Makefile @@ -551,11 +589,9 @@ echo " vsock $enable_vsock" echo " auth mechanism $auth_mech" echo " rdpsndaudin $enable_rdpsndaudin" echo -if test x$use_imlib2 = xyes; then - echo " with imlib2 yes" -else - echo " with imlib2 no" -fi +echo " with imlib2 $use_imlib2" +echo " with freetype2 $use_freetype2" + echo echo " development logging $devel_logging" echo " development streamcheck $devel_streamcheck" diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index b55919fe..8da6cd96 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -1,3 +1,9 @@ +if USE_FREETYPE2 + MKFV1_MAN = xrdp-mkfv1.8 +else + MKFV1_MAN = +endif + man_MANS = \ xrdp-dis.1 \ sesman.ini.5 \ @@ -8,7 +14,9 @@ man_MANS = \ xrdp-keygen.8 \ xrdp-sesadmin.8 \ xrdp-sesman.8 \ - xrdp-sesrun.8 + xrdp-sesrun.8 \ + xrdp-dumpfv1.8 \ + $(MKFV1_MAN) EXTRA_DIST = $(man_MANS:=.in) @@ -21,6 +29,7 @@ SUBST_VARS = sed \ -e 's|@socketdir[@]|$(socketdir)|g' \ -e 's|@sesmanruntimedir[@]|$(sesmanruntimedir)|g' \ -e 's|@xrdpconfdir[@]|$(sysconfdir)/xrdp|g' \ + -e 's|@xrdpdatadir[@]|$(datadir)/xrdp|g' \ -e 's|@xrdphomeurl[@]|http://www.xrdp.org/|g' subst_verbose = $(subst_verbose_@AM_V@) diff --git a/docs/man/xrdp-dumpfv1.8.in b/docs/man/xrdp-dumpfv1.8.in new file mode 100644 index 00000000..a175e22a --- /dev/null +++ b/docs/man/xrdp-dumpfv1.8.in @@ -0,0 +1,58 @@ +.TH "xrdp-dumpfv1" "8" "@PACKAGE_VERSION@" "xrdp team" +.SH NAME +xrdp\-dumpfv1 \- Display content of .fv1 font files + +.SH SYNOPSIS +\fBxrdp-dumpfv1\fR [ options ] fv1_file + +.SH DESCRIPTION +\fBxrdp\-dumpfv1\fP can be used to display the contents of an fv1 file. + +.SH OPTIONS +A summary of options is included below. + +One of \fB\-i\fR, \fB\-t\fR, or \fB\-c\fR must be specified. +.TP +\fB\-i\fR +Displays general information about the fv1 file. + +.TP +\fB\-t\fR +Displays a CSV table of all the glyphs in the font. This table can be +imported into a spreadsheet program for further manipulation. + +.TP +\fB\-c\fR +Displays detailed information about a particular glyph in the font, +including a representation of the bitmap for the glyph. + +Specify the character using one of the following strings: + +\fBU+\fR - Unicode character, e.g. \fBU+25\fR for a percentage symbol (%). + +\fB@\fR - Unicode character, e.g. \fB@%\fR for a percentage symbol. + +\fBnumber\fR - Unicode value as an integer, e.g. \fB37\fR for a +percentage symbol + +Note that the row numbers shown in the font data are relative to the +natural font baseline. If comparing two fonts, be aware that when the +glyph is drawn, the row number may be affected by the global descender +value for the font (displayed with \fB\-i\fR). + +.SH "EXAMPLES" +.TP +\fBxrdp\-dumpfv1 -i @xrdpdatadir@/sans-10.fv1\fR +Displays global information about the sans 10 font file distributed with xrdp. + +.TP +\fBxrdp\-dumpfv1 -c @'*' @xrdpdatadir@/sans-10.fv1\fR +Displays information about the asterisk symbol in the sans 10 font file distributed with xrdp. + +.SH SEE ALSO +.BR xrdp\-mkfv1(8). + +More info on \fBxrdp\fR can be found on the +.UR @xrdphomeurl@ +xrdp homepage +.UE diff --git a/docs/man/xrdp-mkfv1.8.in b/docs/man/xrdp-mkfv1.8.in new file mode 100644 index 00000000..10023e53 --- /dev/null +++ b/docs/man/xrdp-mkfv1.8.in @@ -0,0 +1,81 @@ +.TH "xrdp-mkfv1" "8" "@PACKAGE_VERSION@" "xrdp team" +.SH NAME +xrdp\-mkfv1 \- Create .fv1 font files from other font files + +.SH SYNOPSIS +\fBxrdp-mkfv1\fR [ options ] font_file fv1_file + +.SH DESCRIPTION +\fBxrdp\-mkfv1\fP can be used to convert a font file such as a TrueType +font to a fv1 file. + +.SH OPTIONS +A summary of options is included below. + +.TP +\fB\-n\fR +Give the font a name, which is stored in the font header. + +The default is to use the font family name from the source font. + +.TP +\fB\-p\fR +Set the point size of the font. A fixed DPI value of 96 is used for +converting this value into a pixel size. + +The default value for this option is '10'. + +.TP +\fB\-m\fR +Set the limit on the glyphs stored in the font file. The argument is the last +glyph stored in the font file. + +Specify the glyph using one of the following strings: + +\fBU+\fR - Unicode character, e.g. \fBU+25\fR for a percentage symbol (%). + +\fB@\fR - Unicode character, e.g. \fB@%\fR for a percentage symbol. + +\fBnumber\fR - Unicode value as an integer, e.g. \fB37\fR for a +percentage symbol + +The default value for this option is 'U+4DFF'. + +.TP +\fB\-C\fR +When used with the "DejaVu Sans" font at a point-size of 10, a small +number of glyphs are assigned a different x-offset than they have +when the original Windows font generation program is used. + +This switch can be used to preserve the original x-offsets for glyphs in +the range U+0020 - U+00FF when a 10 point DajaVu Sans font is generated. + +Use one of the following arguments to this option:- + +\fBauto\fR - Automatic mode. Offsets are preserved if a "DajaVu Sans" 10-point font is converted. + +\fBon / true / yes\fR - Preserve offsets if automatic font detection does not work. + +\fBoff / false / no\fR - Do not tamper with the offsets generated by the program. + +The default value of this switch is \fRauto\fR. + +To see the effects of this switch, set the \fBMKFV1_LOG_LEVEL\fR environment +variable to \fBinfo\fR before running the program. + +.SH "EXAMPLES" +.TP +\fBxrdp-mkfv1 -p18 /path/to/DejaVuSans.ttf ./sans-18.fv1\fR +Generate an 18-point Deja Sans font. + +.TP +\fBxrdp-mkfv1 -C off -p10 /path/to/DejaVuSans.ttf ./sans-10.fv1\fR +Generate a 10-point DajaVu Sans font using natural offsets. + +.SH SEE ALSO +.BR xrdp\-dumpfv1(8). + +More info on \fBxrdp\fR can be found on the +.UR @xrdphomeurl@ +xrdp homepage +.UE diff --git a/fontutils/Makefile.am b/fontutils/Makefile.am new file mode 100644 index 00000000..f9e65ab4 --- /dev/null +++ b/fontutils/Makefile.am @@ -0,0 +1,34 @@ +EXTRA_DIST = windows + +# Some programs need freetype2 to build +if USE_FREETYPE2 + MKFV1 = xrdp-mkfv1 +else + MKFV1 = +endif + +AM_CPPFLAGS = \ + -I$(top_builddir) \ + -I$(top_srcdir)/common \ + $(FREETYPE2_CFLAGS) + +bin_PROGRAMS = \ + $(MKFV1) \ + xrdp-dumpfv1 + +xrdp_mkfv1_SOURCES = \ + mkfv1.c \ + fv1.c \ + fv1.h + +xrdp_mkfv1_LDADD = \ + $(top_builddir)/common/libcommon.la \ + $(FREETYPE2_LIBS) + +xrdp_dumpfv1_SOURCES = \ + dumpfv1.c \ + fv1.c \ + fv1.h + +xrdp_dumpfv1_LDADD = \ + $(top_builddir)/common/libcommon.la diff --git a/fontutils/dumpfv1.c b/fontutils/dumpfv1.c new file mode 100644 index 00000000..d42c6fcd --- /dev/null +++ b/fontutils/dumpfv1.c @@ -0,0 +1,413 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2022 + * + * 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. + * + * fonts + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include +#include +#include + +#include "list.h" +#include "os_calls.h" +#include "parse.h" +#include "string_calls.h" +#include "fv1.h" + +/** + * What the program is doing + */ +enum program_mode +{ + PM_UNSPECIFIED = 0, + PM_INFO, + PM_GLYPH_INFO_TABLE, + PM_SHOW_CHAR +}; + +/** + * Parsed program arguments + */ +struct program_args +{ + const char *font_file; + enum program_mode mode; + int ucode; /* Unicode character to display in 'c' mode */ +}; + +/**************************************************************************//** + * 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->font_file = NULL; + pa->mode = PM_UNSPECIFIED; + pa->ucode = 0; + + while ((opt = getopt(argc, argv, "c:it")) != -1) + { + switch (opt) + { + case 'i': + if (pa->mode == PM_UNSPECIFIED) + { + pa->mode = PM_INFO; + } + else + { + LOG(LOG_LEVEL_ERROR, "Can't have two modes set"); + params_ok = 0; + } + break; + + case 't': + if (pa->mode == PM_UNSPECIFIED) + { + pa->mode = PM_GLYPH_INFO_TABLE; + } + else + { + LOG(LOG_LEVEL_ERROR, "Can't have two modes set"); + params_ok = 0; + } + break; + + case 'c': + if (pa->mode == PM_UNSPECIFIED) + { + pa->mode = PM_SHOW_CHAR; + if (toupper(optarg[0]) == 'U' && optarg[1] == '+') + { + char *hex = g_strdup(optarg); + hex[0] = '0'; + hex[1] = 'x'; + pa->ucode = g_atoix(hex); + g_free(hex); + } + else if (optarg[0] == '@') + { + pa->ucode = optarg[1]; + } + else + { + pa->ucode = g_atoix(optarg); + } + } + else + { + LOG(LOG_LEVEL_ERROR, "Can't have two modes set"); + params_ok = 0; + } + break; + + default: + LOG(LOG_LEVEL_ERROR, "Unrecognised switch '%c'", (char)opt); + params_ok = 0; + } + } + + if (argc <= optind) + { + LOG(LOG_LEVEL_ERROR, "No font file specified"); + params_ok = 0; + } + else if ((argc - optind) > 1) + { + LOG(LOG_LEVEL_ERROR, "Unexpected arguments after font file"); + params_ok = 0; + } + else + { + pa->font_file = argv[optind]; + } + + return params_ok; +} + +/**************************************************************************//** + * Displays information about a font file + * + * @param fv1 loaded font file + */ +static void +display_font_file_info(const struct fv1_file *fv1) +{ + g_printf("Font name : %s\n", fv1->font_name); + g_printf("Point size (%d DPI) : %d\n", FV1_DEVICE_DPI, fv1->point_size); + g_printf("Style : %d\n", fv1->style); + if (fv1->body_height == 0 && fv1->glyphs->count > 0) + { + const struct fv1_glyph *g = + (const struct fv1_glyph *)fv1->glyphs->items[0]; + g_printf("Body height : %d (from 1st glyph)\n", -g->baseline + 1); + } + else + { + g_printf("Body height : %d\n", fv1->body_height); + } + g_printf("Descender : %d\n", fv1->min_descender); + + if (fv1->glyphs->count == 0) + { + g_printf("\nFile contains no glyphs\n"); + } + else + { + g_printf("Min glyph index : U+%04X\n", FV1_MIN_CHAR); + g_printf("Max glyph index : U+%04X\n", FV1_GLYPH_END(fv1) - 1); + + /* Work out the statistics */ + unsigned short max_width = 0; + int max_width_ucode = 0; + unsigned short max_height = 0; + int max_height_ucode = 0; + short min_baseline = 0; + int min_baseline_ucode = 0; + short min_offset = 0; + int min_offset_ucode = 0; + short max_offset = 0; + int max_offset_ucode = 0; + unsigned short max_inc_by = 0; + int max_inc_by_ucode = 0; + + /* Derived quantities */ + short min_y_descender = SHRT_MAX; + int min_y_descender_ucode = 0; + int max_datasize = -1; + int max_datasize_ucode = 0; + + /* Loop and output macros */ +#define SET_MIN(ucode,field,val) \ + if ((field) < (val)) \ + { \ + val = (field); \ + val##_ucode = (ucode); \ + } + +#define SET_MAX(ucode,field,val) \ + if ((field) > (val)) \ + { \ + val = (field); \ + val##_ucode = (ucode); \ + } + +#define OUTPUT_INFO(string, val) \ + if (val##_ucode > 0) \ + { \ + g_printf(string, val, val##_ucode); \ + } + int u; + for (u = FV1_MIN_CHAR ; u < FV1_GLYPH_END(fv1); ++u) + { + const struct fv1_glyph *g = FV1_GET_GLYPH(fv1, u); + if (g != NULL) + { + short y_descender = - (g->baseline + g->height); + int datasize = FONT_DATASIZE(g); + + SET_MAX(u, g->width, max_width); + SET_MAX(u, g->height, max_height); + SET_MIN(u, g->baseline, min_baseline); + SET_MIN(u, g->offset, min_offset); + SET_MAX(u, g->offset, max_offset); + SET_MAX(u, g->inc_by, max_inc_by); + SET_MIN(u, y_descender, min_y_descender); + SET_MAX(u, datasize, max_datasize); + } + } + + OUTPUT_INFO("Max glyph width : %d (U+%04X)\n", max_width); + OUTPUT_INFO("Max glyph height : %d (U+%04X)\n", max_height); + OUTPUT_INFO("Min glyph y-baseline : %d (U+%04X)\n", min_baseline); + OUTPUT_INFO("Min glyph y-descender : %d (U+%04X)\n", min_y_descender); + OUTPUT_INFO("Min glyph x-offset : %d (U+%04X)\n", min_offset); + OUTPUT_INFO("Max glyph x-offset : %d (U+%04X)\n", max_offset); + OUTPUT_INFO("Max glyph x-inc_by : %d (U+%04X)\n", max_inc_by); + OUTPUT_INFO("Max glyph datasize : %d (U+%04X)\n", max_datasize); + +#undef SET_MIN +#undef SET_MAX +#undef OUTPUT_INFO + } +} + +/**************************************************************************//** + * Display info in a table about all the glyphs + * @param fv1 font file + */ +static void +display_glyph_info_table(const struct fv1_file *fv1) +{ + int u; + g_printf("character,width,height,baseline,offset,inc_by,datasize\n"); + + for (u = FV1_MIN_CHAR; u < FV1_GLYPH_END(fv1); ++u) + { + const struct fv1_glyph *g = FV1_GET_GLYPH(fv1, u); + if (g != NULL) + { + int datasize = FONT_DATASIZE(g); + g_printf("%d,%hu,%hu,%hd,%hd,%hu,%d\n", + u, g->width, g->height, g->baseline, + g->offset, g->inc_by, datasize); + } + } +} + +/**************************************************************************//** + * Converts a font data row to a printable string + * + * @param rowdata Pointer to the first byte of the row data + * @param width Number of pixels in the row + * @param out Output buffer. Must be sized by the caller to be at + * least width+1 bytes + */ +static void +row_to_str(const unsigned char *rowdata, int width, char *out) +{ + int x; + int mask = 1 << 7; + for (x = 0 ; x < width ; ++x) + { + out[x] = ((*rowdata & mask) != 0) ? 'X' : '.'; + mask >>= 1; + if (mask == 0) + { + mask = 1 << 7; + ++rowdata; + } + } + out[width] = '\0'; +} + +/**************************************************************************//** + * Display info about a specific glyph + * @param ucode Unicode character value + * @param g Glyph + */ +static void +display_glyph_info(int ucode, const struct fv1_glyph *g) +{ + + char *row_buffer = (char *)g_malloc(g->width + 1, 0); + if (row_buffer == NULL) + { + g_printf("\n"); + } + else + { + g_printf("Glyph : U+%04X\n", ucode); + g_printf(" Width : %d\n", g->width); + g_printf(" Height : %d\n", g->height); + g_printf(" Baseline : %d\n", g->baseline); + g_printf(" Offset : %d\n", g->offset); + g_printf(" Inc By : %d\n", g->inc_by); + + g_printf(" Data :\n"); + int y; + const unsigned char *dataptr = g->data; + + for (y = 0 ; y < g->height; ++y) + { + row_to_str(dataptr, g->width, row_buffer); + g_printf(" %+3d: %s\n", y + g->baseline, row_buffer); + dataptr += (g->width + 7) / 8; + } + g_free(row_buffer); + } +} + +/**************************************************************************//** + * Main + * + * @param argc Argument count + * @param argv Arguments + */ +int +main(int argc, char *argv[]) +{ + struct fv1_file *fv1 = NULL; + struct log_config *logging; + struct program_args pa; + int rv = 1; + + logging = log_config_init_for_console(LOG_LEVEL_WARNING, + g_getenv("DUMPFV1_LOG_LEVEL")); + log_start_from_param(logging); + log_config_free(logging); + + if (parse_program_args(argc, argv, &pa) && + (fv1 = fv1_file_load(pa.font_file)) != NULL) + { + switch (pa.mode) + { + case PM_INFO: + display_font_file_info(fv1); + rv = 0; + break; + + case PM_GLYPH_INFO_TABLE: + display_glyph_info_table(fv1); + rv = 0; + break; + + case PM_SHOW_CHAR: + if (pa.ucode < FV1_MIN_CHAR) + { + LOG(LOG_LEVEL_ERROR, "Value for -c must be at least U+%04X", + FV1_MIN_CHAR); + } + else if (pa.ucode >= FV1_GLYPH_END(fv1)) + { + LOG(LOG_LEVEL_ERROR, + "Value for -c must be less than U+%04X", + FV1_GLYPH_END(fv1)); + } + else + { + const struct fv1_glyph *g = + (const struct fv1_glyph *) + list_get_item(fv1->glyphs, pa.ucode - FV1_MIN_CHAR); + display_glyph_info(pa.ucode, g); + rv = 0; + } + break; + + default: + LOG(LOG_LEVEL_ERROR, "Specify one of '-i' or '-c'"); + break; + } + } + + fv1_file_free(fv1); + log_end(); + + return rv; +} diff --git a/fontutils/fv1.c b/fontutils/fv1.c new file mode 100644 index 00000000..af154c1d --- /dev/null +++ b/fontutils/fv1.c @@ -0,0 +1,352 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2022 + * + * 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. + */ + +/** + * @file fontutils/fv1.c + * @brief Definitions relating to fv1 font files + */ +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include +#include + +#include "list.h" +#include "os_calls.h" +#include "parse.h" +#include "string_calls.h" +#include "fv1.h" + +const static char FV1_SIGNATURE[] = {'F', 'N', 'T', '1'}; + +/*****************************************************************************/ +struct fv1_glyph * +fv1_alloc_glyph(int ucode, + unsigned short width, + unsigned short height) +{ + int datasize = FONT_DATASIZE_FROM_GEOMETRY(width, height); + struct fv1_glyph *glyph = NULL; + char ucode_str[16] = {'\0'}; + if (ucode < 0) + { + g_snprintf(ucode_str, sizeof(ucode_str), "Glyph"); + } + else + { + g_snprintf(ucode_str, sizeof(ucode_str), "Glyph:U+%04X", ucode); + } + + if (datasize < 0 || datasize > FV1_MAX_GLYPH_DATASIZE) + { + + /* shouldn't happen */ + LOG(LOG_LEVEL_ERROR, "%s - datasize %d out of bounds", + ucode_str, datasize); + } + else + { + glyph = (struct fv1_glyph *) + g_malloc( offsetof(struct fv1_glyph, data) + datasize, 1); + + if (glyph == NULL) + { + LOG(LOG_LEVEL_ERROR, "%s - out of memory", ucode_str); + } + else + { + glyph->width = width; + glyph->height = height; + } + } + + return glyph; +} + +/*****************************************************************************/ +struct fv1_file * +fv1_file_create(void) +{ + struct fv1_file *fv1 = (struct fv1_file *)g_new0(struct fv1_file, 1); + if (fv1 == NULL) + { + LOG(LOG_LEVEL_ERROR, "fv1_file_create - out of memory"); + } + else + { + fv1->style = 1; /* Unused at present - compatibility value */ + fv1->glyphs = list_create(); + fv1->glyphs->auto_free = 1; + } + return fv1; +} + +/*****************************************************************************/ +static void +add_glyphs_from_stream(struct fv1_file *fv1, struct stream *s) +{ + unsigned short width; + unsigned short height; + int datasize; + + struct fv1_glyph *glyph; + + while (s_check_rem(s, 4)) + { + in_sint16_le(s, width); + in_sint16_le(s, height); + + datasize = FONT_DATASIZE_FROM_GEOMETRY(width, height); + + if (datasize < 0 || datasize > FV1_MAX_GLYPH_DATASIZE) + { + LOG(LOG_LEVEL_ERROR, + "Font:%s Glyph:%d - datasize %d out of bounds", + fv1->font_name, FV1_GLYPH_END(fv1), datasize); + break; + } + + if (!s_check_rem(s, 6 + 6 + datasize)) + { + LOG(LOG_LEVEL_ERROR, + "Font:%s Glyph:%d - not enough data for glyph", + fv1->font_name, FV1_GLYPH_END(fv1)); + break; + } + + if ((glyph = fv1_alloc_glyph(FV1_GLYPH_END(fv1), width, height)) == NULL) + { + break; + } + + in_sint16_le(s, glyph->baseline); + in_sint16_le(s, glyph->offset); + in_sint16_le(s, glyph->inc_by); + in_uint8s(s, 6); + + in_uint8a(s, glyph->data, datasize); + + list_add_item(fv1->glyphs, (tintptr)glyph); + } +} + +/*****************************************************************************/ +struct fv1_file * +fv1_file_load(const char *filename) +{ + struct fv1_file *fv1 = NULL; + + if (!g_file_exist(filename)) + { + LOG(LOG_LEVEL_ERROR, "Can't find font file %s", filename); + } + else + { + int file_size = g_file_get_size(filename); + if (file_size < FV1_MIN_FILE_SIZE || file_size > FV1_MAX_FILE_SIZE) + { + LOG(LOG_LEVEL_ERROR, "Font file %s has bad size %d", + filename, file_size); + } + else + { + struct stream *s; + int fd; + make_stream(s); + init_stream(s, file_size + 1024); + fd = g_file_open(filename); + + if (fd < 0) + { + LOG(LOG_LEVEL_ERROR, "Can't open font file %s", filename); + } + else + { + int b = g_file_read(fd, s->data, file_size + 1024); + g_file_close(fd); + + if (b < FV1_MIN_FILE_SIZE) + { + LOG(LOG_LEVEL_ERROR, "Font file %s is too small", + filename); + } + else + { + char sig[sizeof(FV1_SIGNATURE)]; + s->end = s->data + b; + in_uint8a(s, sig, sizeof(FV1_SIGNATURE)); + if (g_memcmp(sig, FV1_SIGNATURE, sizeof(sig)) != 0) + { + LOG(LOG_LEVEL_ERROR, + "Font file %s has wrong signature", + filename); + } + else if ((fv1 = fv1_file_create()) != NULL) + { + in_uint8a(s, fv1->font_name, FV1_MAX_FONT_NAME_SIZE); + fv1->font_name[FV1_MAX_FONT_NAME_SIZE] = '\0'; + in_uint16_le(s, fv1->point_size); + in_uint16_le(s, fv1->style); + in_uint16_le(s, fv1->body_height); + in_uint16_le(s, fv1->min_descender); + in_uint8s(s, 4); + + add_glyphs_from_stream(fv1, s); + } + } + } + + free_stream(s); + } + } + + return fv1; +} + +/*****************************************************************************/ +void +fv1_file_free(struct fv1_file *fv1) +{ + if (fv1 != NULL) + { + list_delete(fv1->glyphs); + g_free(fv1); + } +} + +/*****************************************************************************/ +int +write_stream(int fd, struct stream *s) +{ + const char *p = s->data; + int rv = 1; + + while (p < s->end) + { + int len = g_file_write(fd, p, s->end - p); + if (len <= 0) + { + rv = 0; + break; + } + p += len; + } + + return rv; +} + +/*****************************************************************************/ +int +fv1_file_save(const struct fv1_file *fv1, const char *filename) +{ + int fd; + struct fv1_glyph *blank_glyph; /* Needed for bad characters */ + + fd = g_file_open_ex(filename, 0, 1, 1, 1); + int rv = 1; + if (fd < 0) + { + LOG(LOG_LEVEL_ERROR, "Unable to open %s for writing [%s]", filename, + g_get_strerror()); + } + else + { + struct stream *s; + make_stream(s); + init_stream(s, 1024); + + /* Write the header */ + out_uint8a(s, FV1_SIGNATURE, sizeof(FV1_SIGNATURE)); + int len = g_strlen(fv1->font_name); + if (len > FV1_MAX_FONT_NAME_SIZE) + { + len = FV1_MAX_FONT_NAME_SIZE; + } + out_uint8a(s, fv1->font_name, len); + while (len++ < FV1_MAX_FONT_NAME_SIZE) + { + out_uint8(s, '\0'); + } + out_uint16_le(s, fv1->point_size); + out_uint16_le(s, fv1->style); + out_uint16_le(s, fv1->body_height); + out_uint16_le(s, fv1->min_descender); + out_uint8a(s, "\0\0\0\0", 4); + s_mark_end(s); + if (!write_stream(fd, s)) + { + LOG(LOG_LEVEL_ERROR, "Unable to write file header [%s]", + g_get_strerror()); + } + else if ((blank_glyph = fv1_alloc_glyph(-1, 0, 0)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Unable to allocate blank glyph"); + } + else + { + int u; + + for (u = FV1_MIN_CHAR; u < FV1_GLYPH_END(fv1); ++u) + { + const struct fv1_glyph *g = FV1_GET_GLYPH(fv1, u); + int datasize; + if (g == NULL) + { + LOG(LOG_LEVEL_WARNING, "Glyph %d is not set", u); + g = blank_glyph; + } + else + { + datasize = FONT_DATASIZE(g); + if (datasize > FV1_MAX_GLYPH_DATASIZE) + { + LOG(LOG_LEVEL_WARNING, + "glyph %d datasize %d exceeds max of %d" + " - glyph will be blank", + u, datasize, FV1_MAX_GLYPH_DATASIZE); + g = blank_glyph; + } + } + init_stream(s, 16 + FONT_DATASIZE(g)); + out_uint16_le(s, g->width); + out_uint16_le(s, g->height); + out_uint16_le(s, g->baseline); + out_uint16_le(s, g->offset); + out_uint16_le(s, g->inc_by); + out_uint8a(s, "\0\0\0\0\0\0", 6); + out_uint8a(s, g->data, FONT_DATASIZE(g)); + s_mark_end(s); + + if (!write_stream(fd, s)) + { + LOG(LOG_LEVEL_ERROR, "Unable to write glyph %d [%s]", + u, g_get_strerror()); + break; + } + } + g_free(blank_glyph); + + rv = (u == FV1_GLYPH_END(fv1)) ? 0 : 1; + } + free_stream(s); + g_file_close(fd); + } + + return rv; +} diff --git a/fontutils/fv1.h b/fontutils/fv1.h new file mode 100644 index 00000000..d9bb64c0 --- /dev/null +++ b/fontutils/fv1.h @@ -0,0 +1,100 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2022 + * + * 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. + */ + +/** + * @file fontutils/fv1.h + * @brief Definitions relating to fv1 font files + */ +#if !defined(FV1_H) +#define FV1_H + +struct list; + +#define FV1_DEVICE_DPI 96 + +#define FV1_MIN_CHAR 0x20 /* First character value in file */ + +#define FV1_MIN_FILE_SIZE 48 +#define FV1_MAX_FILE_SIZE (10 * 1024 * 1024) +#define FV1_MAX_FONT_NAME_SIZE 32 + +#define FV1_MAX_GLYPH_DATASIZE 512 + +struct fv1_glyph +{ + unsigned short width; /* Width of glyph */ + unsigned short height; /* Height of glyph */ + short baseline; /* Offset from cursor pos to 1st row of glyph */ + short offset; /* Space before glyph (can be -ve) */ + unsigned short inc_by; /* Total width of glyph + spacing */ + /* Standard C++ does not yet support C99 flexible array members */ +#ifdef __cplusplus + unsigned char data[1]; +#else + unsigned char data[]; +#endif +}; + +struct fv1_file +{ + char font_name[FV1_MAX_FONT_NAME_SIZE + 1]; + short point_size; /* Input point size (for reference) */ + short style; + short body_height; /* Body height (pixels) */ + short min_descender; /* Min descender of the glyphs in the font */ + struct list *glyphs; /* Glyphs are struct fv1_glyph * */ + +}; + +/** + * Get a glyph pointer for a unicode character + */ +#define FV1_GET_GLYPH(fv1,ucode) \ + (((ucode) < FV1_MIN_CHAR) \ + ? NULL \ + : (struct fv1_glyph *)list_get_item(fv1->glyphs, (ucode) - FV1_MIN_CHAR)) + +/** + * First glyph not in file + */ +#define FV1_GLYPH_END(fv1) (fv1->glyphs->count + FV1_MIN_CHAR) + +struct fv1_file * +fv1_file_load(const char *filename); + +void +fv1_file_free(struct fv1_file *); + +struct fv1_file * +fv1_file_create(void); + +struct fv1_glyph * +fv1_alloc_glyph(int ucode, /* Unicode character for error reporting if known */ + unsigned short width, + unsigned short height); + +enum fv1_realloc_mode +{ + FV1_AT_TOP, + FV1_AT_BOTTOM +}; + +int +fv1_file_save(const struct fv1_file *fv1, const char *filename); + +#endif diff --git a/fontutils/mkfv1.c b/fontutils/mkfv1.c new file mode 100644 index 00000000..a0cdc337 --- /dev/null +++ b/fontutils/mkfv1.c @@ -0,0 +1,587 @@ +/** + * 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 + +#if 0 + /* These lines fix problems with astyle formatting following the ft_errors + * definition */ +} +#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; +} diff --git a/fontdump/.gitignore b/fontutils/windows/.gitignore similarity index 100% rename from fontdump/.gitignore rename to fontutils/windows/.gitignore diff --git a/fontdump/Makefile b/fontutils/windows/Makefile similarity index 100% rename from fontdump/Makefile rename to fontutils/windows/Makefile diff --git a/fontdump/fontdump.c b/fontutils/windows/fontdump.c similarity index 100% rename from fontdump/fontdump.c rename to fontutils/windows/fontdump.c