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