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