- start of external Imager API access:
[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);
18    i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa)
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(char *face, int size, LOGFONT *lf);
32
33 static LPVOID render_text(char *face, int size, char *text, int length, int aa,
34                           HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm);
35
36 /*
37 =item i_wf_bbox(face, size, text, length, bbox)
38
39 Calculate a bounding box for the text.
40
41 =cut
42 */
43
44 int 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;
54   int ascent, descent, max_ascent = -size, min_descent = size;
55
56   mm_log((1, "i_wf_bbox(face %s, size %d, text %p, length %d, bbox %p)\n", face, size, text, length, bbox));
57
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
65   {
66     char facename[100];
67     if (GetTextFace(dc, sizeof(facename), facename)) {
68       mm_log((1, "  face: %s\n", facename));
69     }
70   }
71
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
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   }
101   bbox[BBOX_GLOBAL_DESCENT] = tm.tmDescent;
102   bbox[BBOX_DESCENT] = min_descent == size ? tm.tmDescent : min_descent;
103   bbox[BBOX_POS_WIDTH] = sz.cx;
104   bbox[BBOX_ADVANCE_WIDTH] = sz.cx;
105   bbox[BBOX_GLOBAL_ASCENT] = tm.tmAscent;
106   bbox[BBOX_ASCENT] = max_ascent == -size ? tm.tmAscent : max_ascent;
107   
108   if (length
109       && GetCharABCWidths(dc, text[0], text[0], &first)
110       && GetCharABCWidths(dc, text[length-1], text[length-1], &last)) {
111     mm_log((1, "first: %d A: %d  B: %d  C: %d\n", text[0],
112             first.abcA, first.abcB, first.abcC));
113     mm_log((1, "last: %d A: %d  B: %d  C: %d\n", text[length-1],
114             last.abcA, last.abcB, last.abcC));
115     bbox[BBOX_NEG_WIDTH] = first.abcA;
116     bbox[BBOX_RIGHT_BEARING] = last.abcC;
117     if (last.abcC < 0)
118       bbox[BBOX_POS_WIDTH] -= last.abcC;
119   }
120   else {
121     bbox[BBOX_NEG_WIDTH] = 0;
122     bbox[BBOX_RIGHT_BEARING] = 0;
123   }
124
125   SelectObject(dc, oldFont);
126   ReleaseDC(NULL, dc);
127   DeleteObject(font);
128
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;
132 }
133
134 /*
135 =item i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa)
136
137 Draws the text in the given color.
138
139 =cut
140 */
141
142 int
143 i_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;
161   if (align) {
162     top -= tm.tmAscent;
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   }
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] = 
178           ((255-scale) * pel.channel[ch] + scale*cl->channel[ch]) / 255;
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
189 /*
190 =item i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa)
191
192 Draws the text in the given channel.
193
194 =cut
195 */
196
197 int
198 i_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;
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;
215   if (align) {
216     top -= tm.tmAscent;
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   }
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
240 /*
241 =item i_wf_addfont(char const *filename, char const *resource_file)
242
243 Adds a TTF font file as usable by the application.
244
245 The font is always added as private to the application.
246
247 =cut
248  */
249 int
250 i_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
262 /*
263 =back
264
265 =head1 INTERNAL FUNCTIONS
266
267 =over
268
269 =item set_logfont(face, size, lf)
270
271 Fills in a LOGFONT structure with reasonable defaults.
272
273 =cut
274 */
275
276 static 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 */
280   lf->lfCharSet = DEFAULT_CHARSET;
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
288 /*
289 =item render_text(face, size, text, length, aa, pbm, psz, tm)
290
291 renders the text to an in-memory RGB bitmap 
292
293 It would be nice to render to greyscale, but Windows doesn't have
294 native greyscale bitmaps.
295
296 =cut
297 */
298 static LPVOID render_text(char *face, int size, char *text, int length, int aa,
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;
335       bmih->biXPelsPerMeter = (LONG)(72 / 2.54 * 100);
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
384 /*
385 =head1 BUGS
386
387 Should really use a structure so we can set more attributes.
388
389 Should support UTF8
390
391 =head1 AUTHOR
392
393 Tony Cook <tony@develop-help.com>
394
395 =head1 SEE ALSO
396
397 Imager(3)
398
399 =cut
400 */