#define _WIN32_WINNT 0x500 #include "imw32.h" #define STRICT #include #include "imext.h" /* =head1 NAME win32.c - implements some win32 specific code, specifically Win32 font support. =head1 SYNOPSIS i_img_dim bbox[6]; if (i_wf_bbox(facename, size, text, text_len, bbox)) { // we have the bbox } i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa, utf8); i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa, utf8) =head1 DESCRIPTION An Imager interface to font output using the Win32 GDI. =over =cut */ #define fixed(x) ((x).value + ((x).fract) / 65536.0) static void set_logfont(const char *face, int size, LOGFONT *lf); static unsigned char *render_text(const char *face, int size, const char *text, size_t length, int aa, SIZE *psz, TEXTMETRIC *tm, size_t *bytes_per_line, i_img_dim *bbox, int utf8); static LPWSTR utf8_to_wide_string(char const *text, int text_len, int *wide_chars); static LPWSTR latin1_to_wide_string(char const *text, int text_len, int *wide_chars); /* =item i_wf_bbox(face, size, text, length, bbox, utf8) Calculate a bounding box for the text. =cut */ int i_wf_bbox(const char *face, i_img_dim size, const char *text, size_t length, i_img_dim *bbox, int utf8) { LOGFONT lf; HFONT font, oldFont; HDC dc; SIZE sz; TEXTMETRIC tm; ABC first, last; GLYPHMETRICS gm; MAT2 mat; int ascent, descent, max_ascent = -size, min_descent = size; const char *workp; size_t work_len; int got_first_ch = 0; unsigned long first_ch, last_ch; i_clear_error(); mm_log((1, "i_wf_bbox(face %s, size %d, text %p, length %d, bbox %p, utf8 %d)\n", face, size, text, length, bbox, utf8)); set_logfont(face, size, &lf); font = CreateFontIndirect(&lf); if (!font) return 0; dc = GetDC(NULL); oldFont = (HFONT)SelectObject(dc, font); { char facename[100]; if (GetTextFace(dc, sizeof(facename), facename)) { mm_log((1, " face: %s\n", facename)); } } workp = text; work_len = length; while (work_len > 0) { unsigned long c; unsigned char cp; if (utf8) { c = i_utf8_advance(&workp, &work_len); if (c == ~0UL) { i_push_error(0, "invalid UTF8 character"); return 0; } } else { c = (unsigned char)*workp++; --work_len; } if (!got_first_ch) { first_ch = c; ++got_first_ch; } last_ch = c; cp = c > '~' ? '.' : c < ' ' ? '.' : c; memset(&mat, 0, sizeof(mat)); mat.eM11.value = 1; mat.eM22.value = 1; if (GetGlyphOutline(dc, (UINT)c, GGO_METRICS, &gm, 0, NULL, &mat) != GDI_ERROR) { mm_log((2, " glyph '%c' (%02x): bbx (%u,%u) org (%d,%d) inc(%d,%d)\n", cp, c, gm.gmBlackBoxX, gm.gmBlackBoxY, gm.gmptGlyphOrigin.x, gm.gmptGlyphOrigin.y, gm.gmCellIncX, gm.gmCellIncY)); ascent = gm.gmptGlyphOrigin.y; descent = ascent - gm.gmBlackBoxY; if (ascent > max_ascent) max_ascent = ascent; if (descent < min_descent) min_descent = descent; } else { mm_log((1, " glyph '%c' (%02x): error %d\n", cp, c, GetLastError())); } } if (utf8) { int wide_chars; LPWSTR wide_text = utf8_to_wide_string(text, length, &wide_chars); if (!wide_text) return 0; if (!GetTextExtentPoint32W(dc, wide_text, wide_chars, &sz) || !GetTextMetrics(dc, &tm)) { SelectObject(dc, oldFont); ReleaseDC(NULL, dc); DeleteObject(font); return 0; } myfree(wide_text); } else { if (!GetTextExtentPoint32(dc, text, length, &sz) || !GetTextMetrics(dc, &tm)) { SelectObject(dc, oldFont); ReleaseDC(NULL, dc); DeleteObject(font); return 0; } } bbox[BBOX_GLOBAL_DESCENT] = -tm.tmDescent; bbox[BBOX_DESCENT] = min_descent == size ? -tm.tmDescent : min_descent; bbox[BBOX_POS_WIDTH] = sz.cx; bbox[BBOX_ADVANCE_WIDTH] = sz.cx; bbox[BBOX_GLOBAL_ASCENT] = tm.tmAscent; bbox[BBOX_ASCENT] = max_ascent == -size ? tm.tmAscent : max_ascent; if (length && GetCharABCWidths(dc, first_ch, first_ch, &first) && GetCharABCWidths(dc, last_ch, last_ch, &last)) { mm_log((1, "first: %d A: %d B: %d C: %d\n", first_ch, first.abcA, first.abcB, first.abcC)); mm_log((1, "last: %d A: %d B: %d C: %d\n", last_ch, last.abcA, last.abcB, last.abcC)); bbox[BBOX_NEG_WIDTH] = first.abcA; bbox[BBOX_RIGHT_BEARING] = last.abcC; if (last.abcC < 0) bbox[BBOX_POS_WIDTH] -= last.abcC; } else { bbox[BBOX_NEG_WIDTH] = 0; bbox[BBOX_RIGHT_BEARING] = 0; } SelectObject(dc, oldFont); ReleaseDC(NULL, dc); DeleteObject(font); mm_log((1, " bbox=> negw=%" i_DF " glob_desc=%" i_DF " pos_wid=%" i_DF " glob_asc=%" i_DF " desc=%" i_DF " asc=%" i_DF " adv_width=%" i_DF " rightb=%" i_DF "\n", i_DFc(bbox[0]), i_DFc(bbox[1]), i_DFc(bbox[2]), i_DFc(bbox[3]), i_DFc(bbox[4]), i_DFc(bbox[5]), i_DFc(bbox[6]), i_DFc(bbox[7]))); return BBOX_RIGHT_BEARING + 1; } /* =item i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa) Draws the text in the given color. =cut */ int i_wf_text(const char *face, i_img *im, i_img_dim tx, i_img_dim ty, const i_color *cl, i_img_dim size, const char *text, size_t len, int align, int aa, int utf8) { unsigned char *bits; SIZE sz; size_t line_width; i_img_dim x, y; int ch; TEXTMETRIC tm; int top; i_img_dim bbox[BOUNDING_BOX_COUNT]; i_render *r; unsigned char *outp; i_clear_error(); mm_log((1, "i_wf_text(face %s, im %p, tx %" i_DF ", ty %" i_DF ", cl %p, size %" i_DF ", text %p, length %lu, align %d, aa %d, utf8 %d)\n", face, im, i_DFcp(tx, ty), cl, i_DFc(size), text, (unsigned long)len, align, aa, aa, utf8)); if (!i_wf_bbox(face, size, text, len, bbox, utf8)) return 0; bits = render_text(face, size, text, len, aa, &sz, &tm, &line_width, bbox, utf8); if (!bits) return 0; tx += bbox[BBOX_NEG_WIDTH]; top = ty; if (align) { top -= tm.tmAscent; } else { top -= tm.tmAscent - bbox[BBOX_ASCENT]; } r = i_render_new(im, sz.cx); outp = bits; for (y = 0; y < sz.cy; ++y) { i_render_color(r, tx, top + sz.cy - y - 1, sz.cx, outp, cl); outp += line_width; } i_render_delete(r); myfree(bits); return 1; } /* =item i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa) Draws the text in the given channel. =cut */ int i_wf_cp(const char *face, i_img *im, i_img_dim tx, i_img_dim ty, int channel, i_img_dim size, const char *text, size_t len, int align, int aa, int utf8) { unsigned char *bits; SIZE sz; size_t line_width; i_img_dim x, y; TEXTMETRIC tm; i_img_dim top; i_img_dim bbox[BOUNDING_BOX_COUNT]; unsigned char *outp; i_clear_error(); mm_log((1, "i_wf_cp(face %s, im %p, tx %" i_DF ", ty %" i_DF ", channel %d, size %" i_DF ", text %p, length %lu, align %d, aa %d, utf8 %d)\n", face, im, i_DFcp(tx, ty), channel, i_DFc(size), text, (unsigned long)len, align, aa, aa, utf8)); if (!i_wf_bbox(face, size, text, len, bbox, utf8)) return 0; bits = render_text(face, size, text, len, aa, &sz, &tm, &line_width, bbox, utf8); if (!bits) return 0; top = ty; if (align) { top -= tm.tmAscent; } else { top -= tm.tmAscent - bbox[BBOX_ASCENT]; } outp = bits; for (y = 0; y < sz.cy; ++y) { for (x = 0; x < sz.cx; ++x) { i_color pel; int scale = outp[x]; i_gpix(im, tx+x, top+sz.cy-y-1, &pel); pel.channel[channel] = scale; i_ppix(im, tx+x, top+sz.cy-y-1, &pel); } outp += line_width; } myfree(bits); return 1; } static HMODULE gdi_dll; typedef BOOL (CALLBACK *AddFontResourceExA_t)(LPCSTR, DWORD, PVOID); static AddFontResourceExA_t AddFontResourceExAp; typedef BOOL (CALLBACK *RemoveFontResourceExA_t)(LPCSTR, DWORD, PVOID); static RemoveFontResourceExA_t RemoveFontResourceExAp; /* =item i_wf_addfont(char const *filename, char const *resource_file) Adds a TTF font file as usable by the application. Where possible the font is added as private to the application. Under Windows 95/98/ME the font is added globally, since we don't have any choice. In either case call i_wf_delfont() to remove it. =cut */ int i_wf_addfont(char const *filename) { i_clear_error(); mm_log((1, "i_wf_addfont(%s)\n", filename)); if (!gdi_dll) { gdi_dll = GetModuleHandle("GDI32"); if (gdi_dll) { AddFontResourceExAp = (AddFontResourceExA_t)GetProcAddress(gdi_dll, "AddFontResourceExA"); RemoveFontResourceExAp = (RemoveFontResourceExA_t)GetProcAddress(gdi_dll, "RemoveFontResourceExA"); mm_log((1, "i_wf_addfont: AddFontResourceExA %p RemoveFontResourceExA %p\n", AddFontResourceExAp, RemoveFontResourceExAp)); } } if (AddFontResourceExAp && RemoveFontResourceExAp) { mm_log((1, "i_wf_addfont: adding via AddFontResourceEx()\n")); if (AddFontResourceExAp(filename, FR_PRIVATE, 0)) { return 1; } } else { mm_log((1, "i_wf_addfont: adding via AddFontResource()\n")); if (AddFontResource(filename)) { return 1; } } mm_log((1, "i_wf_addfont failed: %ld\n", GetLastError())); i_push_errorf(0, "Could not add resource: %ld", GetLastError()); return 0; } /* =item i_wf_delfont(char const *filename, char const *resource_file) Deletes a TTF font file as usable by the application. =cut */ int i_wf_delfont(char const *filename) { i_clear_error(); mm_log((1, "i_wf_delfont(%s)\n", filename)); if (AddFontResourceExAp && RemoveFontResourceExAp) { mm_log((1, "i_wf_delfont: removing via RemoveFontResourceEx()\n")); if (RemoveFontResourceExAp(filename, FR_PRIVATE, 0)) return 1; } else { mm_log((1, "i_wf_delfont: adding via RemoveFontResourceEx()\n")); if (RemoveFontResource(filename)) return 1; } mm_log((1, "i_wf_delfont failed: %ld\n", GetLastError())); i_push_errorf(0, "Could not remove resource: %ld", GetLastError()); return 0; } /* =back =head1 INTERNAL FUNCTIONS =over =item set_logfont(face, size, lf) Fills in a LOGFONT structure with reasonable defaults. =cut */ static void set_logfont(const char *face, int size, LOGFONT *lf) { memset(lf, 0, sizeof(LOGFONT)); lf->lfHeight = -size; /* character height rather than cell height */ lf->lfCharSet = DEFAULT_CHARSET; lf->lfOutPrecision = OUT_TT_PRECIS; lf->lfClipPrecision = CLIP_DEFAULT_PRECIS; lf->lfQuality = PROOF_QUALITY; strncpy(lf->lfFaceName, face, sizeof(lf->lfFaceName)-1); /* NUL terminated by the memset at the top */ } /* =item render_text(face, size, text, length, aa, pbm, psz, tm) renders the text to an in-memory RGB bitmap It would be nice to render to greyscale, but Windows doesn't have native greyscale bitmaps. =cut */ static unsigned char * render_text(const char *face, int size, const char *text, size_t length, int aa, SIZE *psz, TEXTMETRIC *tm, size_t *bytes_per_line, i_img_dim *bbox, int utf8) { BITMAPINFO bmi; BITMAPINFOHEADER *bmih = &bmi.bmiHeader; HDC dc, bmpDc; LOGFONT lf; HFONT font, oldFont; SIZE sz; HBITMAP bm, oldBm; LPVOID bits; int wide_count; LPWSTR wide_text; unsigned char *result; dc = GetDC(NULL); set_logfont(face, size, &lf); #ifdef ANTIALIASED_QUALITY /* See KB article Q197076 "INFO: Controlling Anti-aliased Text via the LOGFONT Structure" */ lf.lfQuality = aa ? ANTIALIASED_QUALITY : NONANTIALIASED_QUALITY; #endif if (utf8) { wide_text = utf8_to_wide_string(text, length, &wide_count); } else { wide_text = latin1_to_wide_string(text, length, &wide_count); } bmpDc = CreateCompatibleDC(dc); if (bmpDc) { font = CreateFontIndirect(&lf); if (font) { oldFont = SelectObject(bmpDc, font); GetTextMetrics(bmpDc, tm); sz.cx = bbox[BBOX_ADVANCE_WIDTH] - bbox[BBOX_NEG_WIDTH] + bbox[BBOX_POS_WIDTH]; sz.cy = bbox[BBOX_GLOBAL_ASCENT] - bbox[BBOX_GLOBAL_DESCENT]; memset(&bmi, 0, sizeof(bmi)); bmih->biSize = sizeof(*bmih); bmih->biWidth = sz.cx; bmih->biHeight = sz.cy; bmih->biPlanes = 1; bmih->biBitCount = 24; bmih->biCompression = BI_RGB; bmih->biSizeImage = 0; bmih->biXPelsPerMeter = (LONG)(72 / 2.54 * 100); bmih->biYPelsPerMeter = bmih->biXPelsPerMeter; bmih->biClrUsed = 0; bmih->biClrImportant = 0; bm = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); if (bm) { oldBm = SelectObject(bmpDc, bm); SetTextColor(bmpDc, RGB(255, 255, 255)); SetBkColor(bmpDc, RGB(0, 0, 0)); TextOutW(bmpDc, -bbox[BBOX_NEG_WIDTH], 0, wide_text, wide_count); SelectObject(bmpDc, oldBm); } else { i_push_errorf(0, "Could not create DIB section for render: %ld", GetLastError()); SelectObject(bmpDc, oldFont); DeleteObject(font); DeleteDC(bmpDc); ReleaseDC(NULL, dc); myfree(wide_text); return NULL; } SelectObject(bmpDc, oldFont); DeleteObject(font); } else { myfree(wide_text); i_push_errorf(0, "Could not create logical font: %ld", GetLastError()); DeleteDC(bmpDc); ReleaseDC(NULL, dc); return NULL; } DeleteDC(bmpDc); } else { myfree(wide_text); i_push_errorf(0, "Could not create rendering DC: %ld", GetLastError()); ReleaseDC(NULL, dc); return NULL; } myfree(wide_text); ReleaseDC(NULL, dc); *psz = sz; /* convert into a map we can just pass to i_render_color() */ { i_img_dim x, y; unsigned char *outp, *ucbits; size_t bits_line_width; *bytes_per_line = sz.cx; result = mymalloc(sz.cx * sz.cy); outp = result; ucbits = bits; bits_line_width = sz.cx * 3; bits_line_width = (bits_line_width + 3) / 4 * 4; for (y = 0; y < sz.cy; ++y) { for (x = 0; x < sz.cx; ++x) { *outp++ = ucbits[3 * x]; } ucbits += bits_line_width; } } DeleteObject(bm); return result; } /* =item utf8_to_wide_string(text, text_len, wide_chars) =cut */ static LPWSTR utf8_to_wide_string(char const *text, int text_len, int *wide_chars) { int wide_count = MultiByteToWideChar(CP_UTF8, 0, text, text_len, NULL, 0); LPWSTR result; if (wide_count < 0) { i_push_errorf(0, "Could not convert utf8: %ld", GetLastError()); return NULL; } ++wide_count; result = mymalloc(sizeof(WCHAR) * wide_count); if (MultiByteToWideChar(CP_UTF8, 0, text, text_len, result, wide_count) < 0) { i_push_errorf(0, "Could not convert utf8: %ld", GetLastError()); return NULL; } result[wide_count-1] = (WCHAR)'\0'; *wide_chars = wide_count - 1; return result; } static LPWSTR latin1_to_wide_string(char const *text, int text_len, int *wide_chars) { LPWSTR result = mymalloc(sizeof(WCHAR) * (text_len + 1)); size_t i; for (i = 0; i < text_len; ++i) { result[i] = (unsigned char)text[i]; } result[i] = 0; *wide_chars = text_len; return result; } /* =back =head1 BUGS Should really use a structure so we can set more attributes. Should support UTF8 =head1 AUTHOR Tony Cook =head1 SEE ALSO Imager(3) =cut */