#include "image.h" #define STRICT #include /* =head1 NAME win32.c - implements some win32 specific code, specifically Win32 font support. =head1 SYNOPSIS int 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); i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa) =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(char *face, int size, LOGFONT *lf); static LPVOID render_text(char *face, int size, char *text, int length, int aa, HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm); /* =item i_wf_bbox(face, size, text, length, bbox) Calculate a bounding box for the text. =cut */ int i_wf_bbox(char *face, int size, char *text, int length, int *bbox) { LOGFONT lf; HFONT font, oldFont; HDC dc; SIZE sz; TEXTMETRIC tm; ABC first, last; GLYPHMETRICS gm; int i; MAT2 mat; set_logfont(face, size, &lf); font = CreateFontIndirect(&lf); if (!font) return 0; dc = GetDC(NULL); oldFont = (HFONT)SelectObject(dc, font); #if 1 if (!GetTextExtentPoint32(dc, text, length, &sz) || !GetTextMetrics(dc, &tm)) { SelectObject(dc, oldFont); ReleaseDC(NULL, dc); DeleteObject(font); return 0; } /* if there's a way to get a characters ascent/descent reliably, I can't see it. GetGlyphOutline() seems to return the same size for all characters. */ bbox[1] = bbox[4] = tm.tmDescent; bbox[2] = sz.cx; bbox[3] = bbox[5] = tm.tmAscent; if (GetCharABCWidths(dc, text[0], text[0], &first) && GetCharABCWidths(dc, text[length-1], text[length-1], &last)) { bbox[0] = first.abcA; if (last.abcC < 0) bbox[2] -= last.abcC; } else { bbox[0] = 0; } #else for (i = 0; i < length; ++i) { memset(&gm, 0, sizeof(gm)); memset(&mat, 0, sizeof(mat)); mat.eM11.value = 1; mat.eM22.value = 1; if (GetGlyphOutline(dc, GGO_METRICS, text[i], &gm, 0, NULL, &mat) != GDI_ERROR) { printf("%02X: black (%d, %d) origin (%d, %d) cell(%d, %d)\n", text[i], gm.gmBlackBoxX, gm.gmBlackBoxY, gm.gmptGlyphOrigin.x, gm.gmptGlyphOrigin.y, gm.gmCellIncX, gm.gmCellIncY); printf(" : mat [ %-8f %-8f ]\n", fixed(mat.eM11), fixed(mat.eM12)); printf(" [ %-8f %-8f ]\n", fixed(mat.eM21), fixed(mat.eM22)); } else { printf("Could not get metrics for '\\x%02X'\n", text[i]); } if (GetCharABCWidths(dc, text[i], text[i], &first)) { printf("%02X: %d %d %d\n", text[i], first.abcA, first.abcB, first.abcC); } } #endif SelectObject(dc, oldFont); ReleaseDC(NULL, dc); DeleteObject(font); return 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(char *face, i_img *im, int tx, int ty, i_color *cl, int size, char *text, int len, int align, int aa) { unsigned char *bits; HBITMAP bm; SIZE sz; int line_width; int x, y; int ch; TEXTMETRIC tm; int top; bits = render_text(face, size, text, len, aa, &bm, &sz, &tm); if (!bits) return 0; line_width = sz.cx * 3; line_width = (line_width + 3) / 4 * 4; top = ty; if (align) top -= tm.tmAscent; for (y = 0; y < sz.cy; ++y) { for (x = 0; x < sz.cx; ++x) { i_color pel; int scale = bits[3 * x]; i_gpix(im, tx+x, top+sz.cy-y-1, &pel); for (ch = 0; ch < im->channels; ++ch) { pel.channel[ch] = ((255-scale) * pel.channel[ch] + scale*cl->channel[ch]) / 255.0; } i_ppix(im, tx+x, top+sz.cy-y-1, &pel); } bits += line_width; } DeleteObject(bm); 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(char *face, i_img *im, int tx, int ty, int channel, int size, char *text, int len, int align, int aa) { unsigned char *bits; HBITMAP bm; SIZE sz; int line_width; int x, y; int ch; TEXTMETRIC tm; int top; bits = render_text(face, size, text, len, aa, &bm, &sz, &tm); if (!bits) return 0; line_width = sz.cx * 3; line_width = (line_width + 3) / 4 * 4; top = ty; if (align) top -= tm.tmAscent; for (y = 0; y < sz.cy; ++y) { for (x = 0; x < sz.cx; ++x) { i_color pel; int scale = bits[3 * 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); } bits += line_width; } DeleteObject(bm); return 1; } /* =back =head1 INTERNAL FUNCTIONS =over =item set_logfont(face, size, lf) Fills in a LOGFONT structure with reasonable defaults. =cut */ static void set_logfont(char *face, int size, LOGFONT *lf) { memset(lf, 0, sizeof(LOGFONT)); lf->lfHeight = -size; /* character height rather than cell height */ lf->lfCharSet = ANSI_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 LPVOID render_text(char *face, int size, char *text, int length, int aa, HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm) { BITMAPINFO bmi; BITMAPINFOHEADER *bmih = &bmi.bmiHeader; HDC dc, bmpDc; LOGFONT lf; HFONT font, oldFont; SIZE sz; HBITMAP bm, oldBm; LPVOID bits; 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 bmpDc = CreateCompatibleDC(dc); if (bmpDc) { font = CreateFontIndirect(&lf); if (font) { oldFont = SelectObject(bmpDc, font); GetTextExtentPoint32(bmpDc, text, length, &sz); GetTextMetrics(bmpDc, tm); 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 = 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)); TextOut(bmpDc, 0, 0, text, length); 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); return NULL; } SelectObject(bmpDc, oldFont); DeleteObject(font); } else { i_push_errorf(0, "Could not create logical font: %ld", GetLastError()); DeleteDC(bmpDc); ReleaseDC(NULL, dc); return NULL; } DeleteDC(bmpDc); } else { i_push_errorf(0, "Could not create rendering DC: %ld", GetLastError()); ReleaseDC(NULL, dc); return NULL; } ReleaseDC(NULL, dc); *pbm = bm; *psz = sz; return bits; } /* =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 */