switch to using size_t and i_img_dim strictly
[imager.git] / W32 / win32.c
1 #define _WIN32_WINNT 0x500
2 #include "imw32.h"
3 #define STRICT
4 #include <windows.h>
5 #include "imext.h"
6
7 /*
8 =head1 NAME
9
10 win32.c - implements some win32 specific code, specifically Win32 font support.
11
12 =head1 SYNOPSIS
13
14    i_img_dim bbox[6];
15    if (i_wf_bbox(facename, size, text, text_len, bbox)) {
16      // we have the bbox
17    }
18    i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa, utf8);
19    i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa, utf8)
20
21 =head1 DESCRIPTION
22
23 An Imager interface to font output using the Win32 GDI.
24
25 =over
26
27 =cut
28 */
29
30 #define fixed(x) ((x).value + ((x).fract) / 65536.0)
31
32 static void set_logfont(const char *face, int size, LOGFONT *lf);
33
34 static LPVOID render_text(const char *face, int size, const char *text, int length, int aa,
35                           HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm, i_img_dim *bbox, int utf8);
36 static LPWSTR utf8_to_wide_string(char const *text, int text_len, int *wide_chars);
37
38 /*
39 =item i_wf_bbox(face, size, text, length, bbox, utf8)
40
41 Calculate a bounding box for the text.
42
43 =cut
44 */
45
46 int i_wf_bbox(const char *face, i_img_dim size, const char *text, size_t length,
47               i_img_dim *bbox, int utf8) {
48   LOGFONT lf;
49   HFONT font, oldFont;
50   HDC dc;
51   SIZE sz;
52   TEXTMETRIC tm;
53   ABC first, last;
54   GLYPHMETRICS gm;
55   MAT2 mat;
56   int ascent, descent, max_ascent = -size, min_descent = size;
57   const char *workp;
58   size_t work_len;
59   int got_first_ch = 0;
60   unsigned long first_ch, last_ch;
61
62   i_clear_error();
63
64   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));
65
66   set_logfont(face, size, &lf);
67   font = CreateFontIndirect(&lf);
68   if (!font) 
69     return 0;
70   dc = GetDC(NULL);
71   oldFont = (HFONT)SelectObject(dc, font);
72
73   {
74     char facename[100];
75     if (GetTextFace(dc, sizeof(facename), facename)) {
76       mm_log((1, "  face: %s\n", facename));
77     }
78   }
79
80   workp = text;
81   work_len = length;
82   while (work_len > 0) {
83     unsigned long c;
84     unsigned char cp;
85
86     if (utf8) {
87       c = i_utf8_advance(&workp, &work_len);
88       if (c == ~0UL) {
89         i_push_error(0, "invalid UTF8 character");
90         return 0;
91       }
92     }
93     else {
94       c = (unsigned char)*workp++;
95       --work_len;
96     }
97     if (!got_first_ch) {
98       first_ch = c;
99       ++got_first_ch;
100     }
101     last_ch = c;
102
103     cp = c > '~' ? '.' : c < ' ' ? '.' : c;
104     
105     memset(&mat, 0, sizeof(mat));
106     mat.eM11.value = 1;
107     mat.eM22.value = 1;
108     if (GetGlyphOutline(dc, (UINT)c, GGO_METRICS, &gm, 0, NULL, &mat) != GDI_ERROR) {
109       mm_log((2, "  glyph '%c' (%02x): bbx (%u,%u) org (%d,%d) inc(%d,%d)\n",
110               cp, c, gm.gmBlackBoxX, gm.gmBlackBoxY, gm.gmptGlyphOrigin.x,
111                 gm.gmptGlyphOrigin.y, gm.gmCellIncX, gm.gmCellIncY));
112
113       ascent = gm.gmptGlyphOrigin.y;
114       descent = ascent - gm.gmBlackBoxY;
115       if (ascent > max_ascent) max_ascent = ascent;
116       if (descent < min_descent) min_descent = descent;
117     }
118     else {
119         mm_log((1, "  glyph '%c' (%02x): error %d\n", cp, c, GetLastError()));
120     }
121   }
122
123   if (utf8) {
124     int wide_chars;
125     LPWSTR wide_text = utf8_to_wide_string(text, length, &wide_chars);
126
127     if (!wide_text)
128       return 0;
129
130     if (!GetTextExtentPoint32W(dc, wide_text, wide_chars, &sz)
131         || !GetTextMetrics(dc, &tm)) {
132       SelectObject(dc, oldFont);
133       ReleaseDC(NULL, dc);
134       DeleteObject(font);
135       return 0;
136     }
137
138     myfree(wide_text);
139   }
140   else {
141     if (!GetTextExtentPoint32(dc, text, length, &sz)
142         || !GetTextMetrics(dc, &tm)) {
143       SelectObject(dc, oldFont);
144       ReleaseDC(NULL, dc);
145       DeleteObject(font);
146       return 0;
147     }
148   }
149   bbox[BBOX_GLOBAL_DESCENT] = -tm.tmDescent;
150   bbox[BBOX_DESCENT] = min_descent == size ? -tm.tmDescent : min_descent;
151   bbox[BBOX_POS_WIDTH] = sz.cx;
152   bbox[BBOX_ADVANCE_WIDTH] = sz.cx;
153   bbox[BBOX_GLOBAL_ASCENT] = tm.tmAscent;
154   bbox[BBOX_ASCENT] = max_ascent == -size ? tm.tmAscent : max_ascent;
155   
156   if (length
157       && GetCharABCWidths(dc, first_ch, first_ch, &first)
158       && GetCharABCWidths(dc, last_ch, last_ch, &last)) {
159     mm_log((1, "first: %d A: %d  B: %d  C: %d\n", first_ch,
160             first.abcA, first.abcB, first.abcC));
161     mm_log((1, "last: %d A: %d  B: %d  C: %d\n", last_ch,
162             last.abcA, last.abcB, last.abcC));
163     bbox[BBOX_NEG_WIDTH] = first.abcA;
164     bbox[BBOX_RIGHT_BEARING] = last.abcC;
165     if (last.abcC < 0)
166       bbox[BBOX_POS_WIDTH] -= last.abcC;
167   }
168   else {
169     bbox[BBOX_NEG_WIDTH] = 0;
170     bbox[BBOX_RIGHT_BEARING] = 0;
171   }
172
173   SelectObject(dc, oldFont);
174   ReleaseDC(NULL, dc);
175   DeleteObject(font);
176
177   mm_log((1, " bbox=> negw=%" i_DF " glob_desc=%" i_DF " pos_wid=%" i_DF
178           " glob_asc=%" i_DF " desc=%" i_DF " asc=%" i_DF " adv_width=%" i_DF 
179           " rightb=%" i_DF "\n", i_DFc(bbox[0]), i_DFc(bbox[1]), i_DFc(bbox[2]),
180           i_DFc(bbox[3]), i_DFc(bbox[4]), i_DFc(bbox[5]), i_DFc(bbox[6]),
181           i_DFc(bbox[7])));
182
183   return BBOX_RIGHT_BEARING + 1;
184 }
185
186 /*
187 =item i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa)
188
189 Draws the text in the given color.
190
191 =cut
192 */
193
194 int
195 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, 
196           const char *text, size_t len, int align, int aa, int utf8) {
197   unsigned char *bits;
198   HBITMAP bm;
199   SIZE sz;
200   i_img_dim line_width;
201   i_img_dim x, y;
202   int ch;
203   TEXTMETRIC tm;
204   int top;
205   i_img_dim bbox[BOUNDING_BOX_COUNT];
206
207   i_clear_error();
208
209   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));
210
211   if (!i_wf_bbox(face, size, text, len, bbox, utf8))
212     return 0;
213
214   bits = render_text(face, size, text, len, aa, &bm, &sz, &tm, bbox, utf8);
215   if (!bits)
216     return 0;
217   
218   tx += bbox[BBOX_NEG_WIDTH];
219   line_width = sz.cx * 3;
220   line_width = (line_width + 3) / 4 * 4;
221   top = ty;
222   if (align) {
223     top -= tm.tmAscent;
224   }
225   else {
226     top -= tm.tmAscent - bbox[BBOX_ASCENT];
227   }
228
229   for (y = 0; y < sz.cy; ++y) {
230     for (x = 0; x < sz.cx; ++x) {
231       i_color pel;
232       int scale = bits[3 * x];
233       i_gpix(im, tx+x, top+sz.cy-y-1, &pel);
234       for (ch = 0; ch < im->channels; ++ch) {
235         pel.channel[ch] = 
236           ((255-scale) * pel.channel[ch] + scale*cl->channel[ch]) / 255;
237       }
238       i_ppix(im, tx+x, top+sz.cy-y-1, &pel);
239     }
240     bits += line_width;
241   }
242   DeleteObject(bm);
243
244   return 1;
245 }
246
247 /*
248 =item i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa)
249
250 Draws the text in the given channel.
251
252 =cut
253 */
254
255 int
256 i_wf_cp(const char *face, i_img *im, i_img_dim tx, i_img_dim ty, int channel, i_img_dim size, 
257           const char *text, size_t len, int align, int aa, int utf8) {
258   unsigned char *bits;
259   HBITMAP bm;
260   SIZE sz;
261   int line_width;
262   i_img_dim x, y;
263   TEXTMETRIC tm;
264   i_img_dim top;
265   i_img_dim bbox[BOUNDING_BOX_COUNT];
266
267   i_clear_error();
268
269   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));
270
271   if (!i_wf_bbox(face, size, text, len, bbox, utf8))
272     return 0;
273
274   bits = render_text(face, size, text, len, aa, &bm, &sz, &tm, bbox, utf8);
275   if (!bits)
276     return 0;
277   
278   line_width = sz.cx * 3;
279   line_width = (line_width + 3) / 4 * 4;
280   top = ty;
281   if (align) {
282     top -= tm.tmAscent;
283   }
284   else {
285     top -= tm.tmAscent - bbox[BBOX_ASCENT];
286   }
287
288   for (y = 0; y < sz.cy; ++y) {
289     for (x = 0; x < sz.cx; ++x) {
290       i_color pel;
291       int scale = bits[3 * x];
292       i_gpix(im, tx+x, top+sz.cy-y-1, &pel);
293       pel.channel[channel] = scale;
294       i_ppix(im, tx+x, top+sz.cy-y-1, &pel);
295     }
296     bits += line_width;
297   }
298   DeleteObject(bm);
299
300   return 1;
301 }
302
303 static HMODULE gdi_dll;
304 typedef BOOL (CALLBACK *AddFontResourceExA_t)(LPCSTR, DWORD, PVOID);
305 static AddFontResourceExA_t AddFontResourceExAp;
306 typedef BOOL (CALLBACK *RemoveFontResourceExA_t)(LPCSTR, DWORD, PVOID);
307 static RemoveFontResourceExA_t RemoveFontResourceExAp;
308
309 /*
310 =item i_wf_addfont(char const *filename, char const *resource_file)
311
312 Adds a TTF font file as usable by the application.
313
314 Where possible the font is added as private to the application.
315
316 Under Windows 95/98/ME the font is added globally, since we don't have
317 any choice.  In either case call i_wf_delfont() to remove it.
318
319 =cut
320  */
321 int
322 i_wf_addfont(char const *filename) {
323   i_clear_error();
324
325   if (!gdi_dll) {
326     gdi_dll = GetModuleHandle("GDI32");
327     if (gdi_dll) {
328       AddFontResourceExAp = (AddFontResourceExA_t)GetProcAddress(gdi_dll, "AddFontResourceExA");
329       RemoveFontResourceExAp = (RemoveFontResourceExA_t)GetProcAddress(gdi_dll, "RemoveFontResourceExA");
330     }
331   }
332
333   if (AddFontResourceExAp && RemoveFontResourceExAp
334       && AddFontResourceExAp(filename, FR_PRIVATE, 0)) {
335     return 1;
336   }
337   else if (AddFontResource(filename)) {
338     return 1;
339   }
340   else {
341     i_push_errorf(0, "Could not add resource: %ld", GetLastError());
342     return 0;
343   }
344 }
345
346 /*
347 =item i_wf_delfont(char const *filename, char const *resource_file)
348
349 Deletes a TTF font file as usable by the application.
350
351 =cut
352  */
353 int
354 i_wf_delfont(char const *filename) {
355   i_clear_error();
356
357   if (AddFontResourceExAp && RemoveFontResourceExAp
358       && RemoveFontResourceExAp(filename, FR_PRIVATE, 0)) {
359     return 1;
360   }
361   else if (RemoveFontResource(filename)) {
362     return 1;
363   }
364   else {
365     i_push_errorf(0, "Could not remove resource: %ld", GetLastError());
366     return 0;
367   }
368 }
369
370 /*
371 =back
372
373 =head1 INTERNAL FUNCTIONS
374
375 =over
376
377 =item set_logfont(face, size, lf)
378
379 Fills in a LOGFONT structure with reasonable defaults.
380
381 =cut
382 */
383
384 static void set_logfont(const char *face, int size, LOGFONT *lf) {
385   memset(lf, 0, sizeof(LOGFONT));
386
387   lf->lfHeight = -size; /* character height rather than cell height */
388   lf->lfCharSet = DEFAULT_CHARSET;
389   lf->lfOutPrecision = OUT_TT_PRECIS;
390   lf->lfClipPrecision = CLIP_DEFAULT_PRECIS;
391   lf->lfQuality = PROOF_QUALITY;
392   strncpy(lf->lfFaceName, face, sizeof(lf->lfFaceName)-1);
393   /* NUL terminated by the memset at the top */
394 }
395
396 /*
397 =item render_text(face, size, text, length, aa, pbm, psz, tm)
398
399 renders the text to an in-memory RGB bitmap 
400
401 It would be nice to render to greyscale, but Windows doesn't have
402 native greyscale bitmaps.
403
404 =cut
405 */
406 static LPVOID render_text(const char *face, int size, const char *text, int length, int aa,
407                    HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm, i_img_dim *bbox, int utf8) {
408   BITMAPINFO bmi;
409   BITMAPINFOHEADER *bmih = &bmi.bmiHeader;
410   HDC dc, bmpDc;
411   LOGFONT lf;
412   HFONT font, oldFont;
413   SIZE sz;
414   HBITMAP bm, oldBm;
415   LPVOID bits;
416   int wide_count;
417   LPWSTR wide_text;
418
419   dc = GetDC(NULL);
420   set_logfont(face, size, &lf);
421
422 #ifdef ANTIALIASED_QUALITY
423   /* See KB article Q197076
424      "INFO: Controlling Anti-aliased Text via the LOGFONT Structure"
425   */
426   lf.lfQuality = aa ? ANTIALIASED_QUALITY : NONANTIALIASED_QUALITY;
427 #endif
428
429   if (utf8) {
430     wide_text = utf8_to_wide_string(text, length, &wide_count);
431   }
432   else {
433     wide_text = NULL;
434   }
435
436   bmpDc = CreateCompatibleDC(dc);
437   if (bmpDc) {
438     font = CreateFontIndirect(&lf);
439     if (font) {
440       oldFont = SelectObject(bmpDc, font);
441
442       GetTextMetrics(bmpDc, tm);
443       sz.cx = bbox[BBOX_ADVANCE_WIDTH] - bbox[BBOX_NEG_WIDTH] + bbox[BBOX_POS_WIDTH];
444       sz.cy = bbox[BBOX_GLOBAL_ASCENT] - bbox[BBOX_GLOBAL_DESCENT];
445       
446       memset(&bmi, 0, sizeof(bmi));
447       bmih->biSize = sizeof(*bmih);
448       bmih->biWidth = sz.cx;
449       bmih->biHeight = sz.cy;
450       bmih->biPlanes = 1;
451       bmih->biBitCount = 24;
452       bmih->biCompression = BI_RGB;
453       bmih->biSizeImage = 0;
454       bmih->biXPelsPerMeter = (LONG)(72 / 2.54 * 100);
455       bmih->biYPelsPerMeter = bmih->biXPelsPerMeter;
456       bmih->biClrUsed = 0;
457       bmih->biClrImportant = 0;
458       
459       bm = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);
460
461       if (bm) {
462         oldBm = SelectObject(bmpDc, bm);
463         SetTextColor(bmpDc, RGB(255, 255, 255));
464         SetBkColor(bmpDc, RGB(0, 0, 0));
465         if (utf8) {
466           TextOutW(bmpDc, -bbox[BBOX_NEG_WIDTH], 0, wide_text, wide_count);
467         }
468         else {
469           TextOut(bmpDc, -bbox[BBOX_NEG_WIDTH], 0, text, length);
470         }
471         SelectObject(bmpDc, oldBm);
472       }
473       else {
474         i_push_errorf(0, "Could not create DIB section for render: %ld",
475                       GetLastError());
476         SelectObject(bmpDc, oldFont);
477         DeleteObject(font);
478         DeleteDC(bmpDc);
479         ReleaseDC(NULL, dc);
480         if (wide_text)
481           myfree(wide_text);
482         return NULL;
483       }
484       SelectObject(bmpDc, oldFont);
485       DeleteObject(font);
486     }
487     else {
488       if (wide_text)
489         myfree(wide_text);
490       i_push_errorf(0, "Could not create logical font: %ld",
491                     GetLastError());
492       DeleteDC(bmpDc);
493       ReleaseDC(NULL, dc);
494       return NULL;
495     }
496     DeleteDC(bmpDc);
497   }
498   else {
499     if (wide_text)
500       myfree(wide_text);
501     i_push_errorf(0, "Could not create rendering DC: %ld", GetLastError());
502     ReleaseDC(NULL, dc);
503     return NULL;
504   }
505
506   if (wide_text)
507     myfree(wide_text);
508
509   ReleaseDC(NULL, dc);
510
511   *pbm = bm;
512   *psz = sz;
513
514   return bits;
515 }
516
517 /*
518 =item utf8_to_wide_string(text, text_len, wide_chars)
519
520 =cut
521 */
522
523 static
524 LPWSTR
525 utf8_to_wide_string(char const *text, int text_len, int *wide_chars) {
526   int wide_count = MultiByteToWideChar(CP_UTF8, 0, text, text_len, NULL, 0);
527   LPWSTR result;
528
529   if (wide_count < 0) {
530     i_push_errorf(0, "Could not convert utf8: %ld", GetLastError());
531     return NULL;
532   }
533   ++wide_count;
534   result = mymalloc(sizeof(WCHAR) * wide_count);
535   if (MultiByteToWideChar(CP_UTF8, 0, text, text_len, result, wide_count) < 0) {
536     i_push_errorf(0, "Could not convert utf8: %ld", GetLastError());
537     return NULL;
538   }
539
540   result[wide_count-1] = (WCHAR)'\0';
541   *wide_chars = wide_count - 1;
542
543   return result;
544 }
545
546
547 /*
548 =back
549
550 =head1 BUGS
551
552 Should really use a structure so we can set more attributes.
553
554 Should support UTF8
555
556 =head1 AUTHOR
557
558 Tony Cook <tony@develop-help.com>
559
560 =head1 SEE ALSO
561
562 Imager(3)
563
564 =cut
565 */