Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

599 lines (484 sloc) 15.106 kb
#define _WIN32_WINNT 0x500
#include "imw32.h"
#define STRICT
#include <windows.h>
#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 <tony@develop-help.com>
=head1 SEE ALSO
Imager(3)
=cut
*/
Jump to Line
Something went wrong with that request. Please try again.