- start of external Imager API access:
[imager.git] / win32.c
CommitLineData
8457948a 1#define _WIN32_WINNT 0x500
92bda632 2#include "imager.h"
faa9b3e7
TC
3#define STRICT
4#include <windows.h>
5
6/*
7=head1 NAME
8
9win32.c - implements some win32 specific code, specifically Win32 font support.
10
11=head1 SYNOPSIS
12
13 int bbox[6];
14 if (i_wf_bbox(facename, size, text, text_len, bbox)) {
15 // we have the bbox
16 }
2c68060c
TC
17 i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa);
18 i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa)
faa9b3e7 19
2c68060c 20=head1 DESCRIPTION
faa9b3e7 21
c13a1ea8 22An Imager interface to font output using the Win32 GDI.
faa9b3e7 23
2c68060c
TC
24=over
25
26=cut
27*/
faa9b3e7
TC
28
29#define fixed(x) ((x).value + ((x).fract) / 65536.0)
30
2c68060c
TC
31static void set_logfont(char *face, int size, LOGFONT *lf);
32
33static LPVOID render_text(char *face, int size, char *text, int length, int aa,
34 HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm);
35
c13a1ea8
TC
36/*
37=item i_wf_bbox(face, size, text, length, bbox)
38
39Calculate a bounding box for the text.
40
41=cut
42*/
43
faa9b3e7
TC
44int i_wf_bbox(char *face, int size, char *text, int length, int *bbox) {
45 LOGFONT lf;
46 HFONT font, oldFont;
47 HDC dc;
48 SIZE sz;
49 TEXTMETRIC tm;
50 ABC first, last;
51 GLYPHMETRICS gm;
52 int i;
53 MAT2 mat;
a6d9b737 54 int ascent, descent, max_ascent = -size, min_descent = size;
faa9b3e7 55
8457948a
TC
56 mm_log((1, "i_wf_bbox(face %s, size %d, text %p, length %d, bbox %p)\n", face, size, text, length, bbox));
57
faa9b3e7
TC
58 set_logfont(face, size, &lf);
59 font = CreateFontIndirect(&lf);
60 if (!font)
61 return 0;
62 dc = GetDC(NULL);
63 oldFont = (HFONT)SelectObject(dc, font);
64
8457948a
TC
65 {
66 char facename[100];
67 if (GetTextFace(dc, sizeof(facename), facename)) {
68 mm_log((1, " face: %s\n", facename));
69 }
70 }
71
a6d9b737
TC
72 for (i = 0; i < length; ++i) {
73 unsigned char c = text[i];
74 unsigned char cp = c > '~' ? '.' : c < ' ' ? '.' : c;
75
76 memset(&mat, 0, sizeof(mat));
77 mat.eM11.value = 1;
78 mat.eM22.value = 1;
79 if (GetGlyphOutline(dc, c, GGO_METRICS, &gm, 0, NULL, &mat) != GDI_ERROR) {
80 mm_log((2, " glyph '%c' (%02x): bbx (%u,%u) org (%d,%d) inc(%d,%d)\n",
81 cp, c, gm.gmBlackBoxX, gm.gmBlackBoxY, gm.gmptGlyphOrigin.x,
82 gm.gmptGlyphOrigin.y, gm.gmCellIncX, gm.gmCellIncY));
83
84 ascent = gm.gmptGlyphOrigin.y;
85 descent = ascent - gm.gmBlackBoxY;
86 if (ascent > max_ascent) max_ascent = ascent;
87 if (descent < min_descent) min_descent = descent;
88 }
89 else {
90 mm_log((1, " glyph '%c' (%02x): error %d\n", cp, c, GetLastError()));
91 }
92 }
93
faa9b3e7
TC
94 if (!GetTextExtentPoint32(dc, text, length, &sz)
95 || !GetTextMetrics(dc, &tm)) {
96 SelectObject(dc, oldFont);
97 ReleaseDC(NULL, dc);
98 DeleteObject(font);
99 return 0;
100 }
a6d9b737
TC
101 bbox[BBOX_GLOBAL_DESCENT] = tm.tmDescent;
102 bbox[BBOX_DESCENT] = min_descent == size ? tm.tmDescent : min_descent;
8457948a
TC
103 bbox[BBOX_POS_WIDTH] = sz.cx;
104 bbox[BBOX_ADVANCE_WIDTH] = sz.cx;
a6d9b737
TC
105 bbox[BBOX_GLOBAL_ASCENT] = tm.tmAscent;
106 bbox[BBOX_ASCENT] = max_ascent == -size ? tm.tmAscent : max_ascent;
faa9b3e7 107
8457948a
TC
108 if (length
109 && GetCharABCWidths(dc, text[0], text[0], &first)
faa9b3e7 110 && GetCharABCWidths(dc, text[length-1], text[length-1], &last)) {
8457948a
TC
111 mm_log((1, "first: %d A: %d B: %d C: %d\n", text[0],
112 first.abcA, first.abcB, first.abcC));
a6d9b737 113 mm_log((1, "last: %d A: %d B: %d C: %d\n", text[length-1],
8457948a
TC
114 last.abcA, last.abcB, last.abcC));
115 bbox[BBOX_NEG_WIDTH] = first.abcA;
116 bbox[BBOX_RIGHT_BEARING] = last.abcC;
faa9b3e7 117 if (last.abcC < 0)
8457948a 118 bbox[BBOX_POS_WIDTH] -= last.abcC;
faa9b3e7
TC
119 }
120 else {
8457948a
TC
121 bbox[BBOX_NEG_WIDTH] = 0;
122 bbox[BBOX_RIGHT_BEARING] = 0;
faa9b3e7 123 }
faa9b3e7
TC
124
125 SelectObject(dc, oldFont);
126 ReleaseDC(NULL, dc);
127 DeleteObject(font);
128
8457948a
TC
129 mm_log((1, " bbox=> negw=%d glob_desc=%d pos_wid=%d glob_asc=%d desc=%d asc=%d adv_width=%d rightb=%d\n", bbox[0], bbox[1], bbox[2], bbox[3], bbox[4], bbox[5], bbox[6], bbox[7]));
130
131 return BBOX_RIGHT_BEARING + 1;
faa9b3e7
TC
132}
133
c13a1ea8
TC
134/*
135=item i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa)
136
137Draws the text in the given color.
138
139=cut
140*/
faa9b3e7 141
2c68060c
TC
142int
143i_wf_text(char *face, i_img *im, int tx, int ty, i_color *cl, int size,
144 char *text, int len, int align, int aa) {
145 unsigned char *bits;
146 HBITMAP bm;
147 SIZE sz;
148 int line_width;
149 int x, y;
150 int ch;
151 TEXTMETRIC tm;
152 int top;
153
154 bits = render_text(face, size, text, len, aa, &bm, &sz, &tm);
155 if (!bits)
156 return 0;
157
158 line_width = sz.cx * 3;
159 line_width = (line_width + 3) / 4 * 4;
160 top = ty;
a6d9b737 161 if (align) {
2c68060c 162 top -= tm.tmAscent;
a6d9b737
TC
163 }
164 else {
165 int bbox[BOUNDING_BOX_COUNT];
166
167 i_wf_bbox(face, size, text, len, bbox);
168 top -= tm.tmAscent - bbox[BBOX_ASCENT];
169 }
2c68060c
TC
170
171 for (y = 0; y < sz.cy; ++y) {
172 for (x = 0; x < sz.cx; ++x) {
173 i_color pel;
174 int scale = bits[3 * x];
175 i_gpix(im, tx+x, top+sz.cy-y-1, &pel);
176 for (ch = 0; ch < im->channels; ++ch) {
177 pel.channel[ch] =
a6d9b737 178 ((255-scale) * pel.channel[ch] + scale*cl->channel[ch]) / 255;
2c68060c
TC
179 }
180 i_ppix(im, tx+x, top+sz.cy-y-1, &pel);
181 }
182 bits += line_width;
183 }
184 DeleteObject(bm);
185
186 return 1;
187}
188
c13a1ea8
TC
189/*
190=item i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa)
191
192Draws the text in the given channel.
193
194=cut
195*/
196
2c68060c
TC
197int
198i_wf_cp(char *face, i_img *im, int tx, int ty, int channel, int size,
199 char *text, int len, int align, int aa) {
200 unsigned char *bits;
201 HBITMAP bm;
202 SIZE sz;
203 int line_width;
204 int x, y;
2c68060c
TC
205 TEXTMETRIC tm;
206 int top;
207
208 bits = render_text(face, size, text, len, aa, &bm, &sz, &tm);
209 if (!bits)
210 return 0;
211
212 line_width = sz.cx * 3;
213 line_width = (line_width + 3) / 4 * 4;
214 top = ty;
a6d9b737 215 if (align) {
2c68060c 216 top -= tm.tmAscent;
a6d9b737
TC
217 }
218 else {
219 int bbox[BOUNDING_BOX_COUNT];
220
221 i_wf_bbox(face, size, text, len, bbox);
222 top -= tm.tmAscent - bbox[BBOX_ASCENT];
223 }
2c68060c
TC
224
225 for (y = 0; y < sz.cy; ++y) {
226 for (x = 0; x < sz.cx; ++x) {
227 i_color pel;
228 int scale = bits[3 * x];
229 i_gpix(im, tx+x, top+sz.cy-y-1, &pel);
230 pel.channel[channel] = scale;
231 i_ppix(im, tx+x, top+sz.cy-y-1, &pel);
232 }
233 bits += line_width;
234 }
235 DeleteObject(bm);
236
237 return 1;
238}
239
8457948a
TC
240/*
241=item i_wf_addfont(char const *filename, char const *resource_file)
242
243Adds a TTF font file as usable by the application.
244
245The font is always added as private to the application.
246
247=cut
248 */
249int
250i_wf_addfont(char const *filename) {
251 i_clear_error();
252
253 if (AddFontResourceEx(filename, FR_PRIVATE, 0)) {
254 return 1;
255 }
256 else {
257 i_push_errorf(0, "Could not add resource: %ld", GetLastError());
258 return 0;
259 }
260}
261
c13a1ea8
TC
262/*
263=back
264
265=head1 INTERNAL FUNCTIONS
266
267=over
268
269=item set_logfont(face, size, lf)
270
271Fills in a LOGFONT structure with reasonable defaults.
272
273=cut
274*/
275
2c68060c
TC
276static void set_logfont(char *face, int size, LOGFONT *lf) {
277 memset(lf, 0, sizeof(LOGFONT));
278
279 lf->lfHeight = -size; /* character height rather than cell height */
ea9e6c3f 280 lf->lfCharSet = DEFAULT_CHARSET;
2c68060c
TC
281 lf->lfOutPrecision = OUT_TT_PRECIS;
282 lf->lfClipPrecision = CLIP_DEFAULT_PRECIS;
283 lf->lfQuality = PROOF_QUALITY;
284 strncpy(lf->lfFaceName, face, sizeof(lf->lfFaceName)-1);
285 /* NUL terminated by the memset at the top */
286}
287
c13a1ea8
TC
288/*
289=item render_text(face, size, text, length, aa, pbm, psz, tm)
290
291renders the text to an in-memory RGB bitmap
292
293It would be nice to render to greyscale, but Windows doesn't have
294native greyscale bitmaps.
295
296=cut
297*/
2c68060c 298static LPVOID render_text(char *face, int size, char *text, int length, int aa,
faa9b3e7
TC
299 HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm) {
300 BITMAPINFO bmi;
301 BITMAPINFOHEADER *bmih = &bmi.bmiHeader;
302 HDC dc, bmpDc;
303 LOGFONT lf;
304 HFONT font, oldFont;
305 SIZE sz;
306 HBITMAP bm, oldBm;
307 LPVOID bits;
308
309 dc = GetDC(NULL);
310 set_logfont(face, size, &lf);
311
312#ifdef ANTIALIASED_QUALITY
313 /* See KB article Q197076
314 "INFO: Controlling Anti-aliased Text via the LOGFONT Structure"
315 */
316 lf.lfQuality = aa ? ANTIALIASED_QUALITY : NONANTIALIASED_QUALITY;
317#endif
318
319 bmpDc = CreateCompatibleDC(dc);
320 if (bmpDc) {
321 font = CreateFontIndirect(&lf);
322 if (font) {
323 oldFont = SelectObject(bmpDc, font);
324 GetTextExtentPoint32(bmpDc, text, length, &sz);
325 GetTextMetrics(bmpDc, tm);
326
327 memset(&bmi, 0, sizeof(bmi));
328 bmih->biSize = sizeof(*bmih);
329 bmih->biWidth = sz.cx;
330 bmih->biHeight = sz.cy;
331 bmih->biPlanes = 1;
332 bmih->biBitCount = 24;
333 bmih->biCompression = BI_RGB;
334 bmih->biSizeImage = 0;
a6d9b737 335 bmih->biXPelsPerMeter = (LONG)(72 / 2.54 * 100);
faa9b3e7
TC
336 bmih->biYPelsPerMeter = bmih->biXPelsPerMeter;
337 bmih->biClrUsed = 0;
338 bmih->biClrImportant = 0;
339
340 bm = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);
341
342 if (bm) {
343 oldBm = SelectObject(bmpDc, bm);
344 SetTextColor(bmpDc, RGB(255, 255, 255));
345 SetBkColor(bmpDc, RGB(0, 0, 0));
346 TextOut(bmpDc, 0, 0, text, length);
347 SelectObject(bmpDc, oldBm);
348 }
349 else {
350 i_push_errorf(0, "Could not create DIB section for render: %ld",
351 GetLastError());
352 SelectObject(bmpDc, oldFont);
353 DeleteObject(font);
354 DeleteDC(bmpDc);
355 ReleaseDC(NULL, dc);
356 return NULL;
357 }
358 SelectObject(bmpDc, oldFont);
359 DeleteObject(font);
360 }
361 else {
362 i_push_errorf(0, "Could not create logical font: %ld",
363 GetLastError());
364 DeleteDC(bmpDc);
365 ReleaseDC(NULL, dc);
366 return NULL;
367 }
368 DeleteDC(bmpDc);
369 }
370 else {
371 i_push_errorf(0, "Could not create rendering DC: %ld", GetLastError());
372 ReleaseDC(NULL, dc);
373 return NULL;
374 }
375
376 ReleaseDC(NULL, dc);
377
378 *pbm = bm;
379 *psz = sz;
380
381 return bits;
382}
383
c13a1ea8
TC
384/*
385=head1 BUGS
386
387Should really use a structure so we can set more attributes.
388
389Should support UTF8
390
391=head1 AUTHOR
392
393Tony Cook <tony@develop-help.com>
394
395=head1 SEE ALSO
396
397Imager(3)
398
399=cut
400*/