remove a pointless NULL check from i_flipxy()
[imager.git] / W32 / win32.c
CommitLineData
8457948a 1#define _WIN32_WINNT 0x500
718b8c97 2#include "imw32.h"
faa9b3e7
TC
3#define STRICT
4#include <windows.h>
718b8c97 5#include "imext.h"
faa9b3e7
TC
6
7/*
8=head1 NAME
9
10win32.c - implements some win32 specific code, specifically Win32 font support.
11
12=head1 SYNOPSIS
13
8d14daab 14 i_img_dim bbox[6];
faa9b3e7
TC
15 if (i_wf_bbox(facename, size, text, text_len, bbox)) {
16 // we have the bbox
17 }
bd4f550c
TC
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)
faa9b3e7 20
2c68060c 21=head1 DESCRIPTION
faa9b3e7 22
c13a1ea8 23An Imager interface to font output using the Win32 GDI.
faa9b3e7 24
2c68060c
TC
25=over
26
27=cut
28*/
faa9b3e7
TC
29
30#define fixed(x) ((x).value + ((x).fract) / 65536.0)
31
885e13c5 32static void set_logfont(const char *face, int size, LOGFONT *lf);
2c68060c 33
5091168a
TC
34static 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);
bd4f550c 36static LPWSTR utf8_to_wide_string(char const *text, int text_len, int *wide_chars);
5091168a 37static LPWSTR latin1_to_wide_string(char const *text, int text_len, int *wide_chars);
2c68060c 38
c13a1ea8 39/*
bd4f550c 40=item i_wf_bbox(face, size, text, length, bbox, utf8)
c13a1ea8
TC
41
42Calculate a bounding box for the text.
43
44=cut
45*/
46
8d14daab
TC
47int i_wf_bbox(const char *face, i_img_dim size, const char *text, size_t length,
48 i_img_dim *bbox, int utf8) {
faa9b3e7
TC
49 LOGFONT lf;
50 HFONT font, oldFont;
51 HDC dc;
52 SIZE sz;
53 TEXTMETRIC tm;
54 ABC first, last;
55 GLYPHMETRICS gm;
faa9b3e7 56 MAT2 mat;
a6d9b737 57 int ascent, descent, max_ascent = -size, min_descent = size;
bd4f550c 58 const char *workp;
fd0e4176 59 size_t work_len;
50e800e9
TC
60 int got_first_ch = 0;
61 unsigned long first_ch, last_ch;
faa9b3e7 62
8d14daab
TC
63 i_clear_error();
64
bd4f550c 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));
8457948a 66
faa9b3e7
TC
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
8457948a
TC
74 {
75 char facename[100];
76 if (GetTextFace(dc, sizeof(facename), facename)) {
77 mm_log((1, " face: %s\n", facename));
78 }
79 }
80
bd4f550c 81 workp = text;
50e800e9 82 work_len = length;
bd4f550c
TC
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 {
50e800e9 95 c = (unsigned char)*workp++;
bd4f550c
TC
96 --work_len;
97 }
50e800e9
TC
98 if (!got_first_ch) {
99 first_ch = c;
100 ++got_first_ch;
101 }
102 last_ch = c;
bd4f550c
TC
103
104 cp = c > '~' ? '.' : c < ' ' ? '.' : c;
a6d9b737
TC
105
106 memset(&mat, 0, sizeof(mat));
107 mat.eM11.value = 1;
108 mat.eM22.value = 1;
bd4f550c 109 if (GetGlyphOutline(dc, (UINT)c, GGO_METRICS, &gm, 0, NULL, &mat) != GDI_ERROR) {
a6d9b737
TC
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
bd4f550c
TC
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 }
faa9b3e7 149 }
50e800e9
TC
150 bbox[BBOX_GLOBAL_DESCENT] = -tm.tmDescent;
151 bbox[BBOX_DESCENT] = min_descent == size ? -tm.tmDescent : min_descent;
8457948a
TC
152 bbox[BBOX_POS_WIDTH] = sz.cx;
153 bbox[BBOX_ADVANCE_WIDTH] = sz.cx;
a6d9b737
TC
154 bbox[BBOX_GLOBAL_ASCENT] = tm.tmAscent;
155 bbox[BBOX_ASCENT] = max_ascent == -size ? tm.tmAscent : max_ascent;
faa9b3e7 156
8457948a 157 if (length
50e800e9
TC
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,
8457948a 161 first.abcA, first.abcB, first.abcC));
50e800e9 162 mm_log((1, "last: %d A: %d B: %d C: %d\n", last_ch,
8457948a
TC
163 last.abcA, last.abcB, last.abcC));
164 bbox[BBOX_NEG_WIDTH] = first.abcA;
165 bbox[BBOX_RIGHT_BEARING] = last.abcC;
faa9b3e7 166 if (last.abcC < 0)
8457948a 167 bbox[BBOX_POS_WIDTH] -= last.abcC;
faa9b3e7
TC
168 }
169 else {
8457948a
TC
170 bbox[BBOX_NEG_WIDTH] = 0;
171 bbox[BBOX_RIGHT_BEARING] = 0;
faa9b3e7 172 }
faa9b3e7
TC
173
174 SelectObject(dc, oldFont);
175 ReleaseDC(NULL, dc);
176 DeleteObject(font);
177
8d14daab
TC
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])));
8457948a
TC
183
184 return BBOX_RIGHT_BEARING + 1;
faa9b3e7
TC
185}
186
c13a1ea8
TC
187/*
188=item i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa)
189
190Draws the text in the given color.
191
192=cut
193*/
faa9b3e7 194
2c68060c 195int
8d14daab
TC
196i_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) {
2c68060c 198 unsigned char *bits;
2c68060c 199 SIZE sz;
5091168a 200 size_t line_width;
8d14daab 201 i_img_dim x, y;
2c68060c
TC
202 int ch;
203 TEXTMETRIC tm;
204 int top;
8d14daab 205 i_img_dim bbox[BOUNDING_BOX_COUNT];
5091168a
TC
206 i_render *r;
207 unsigned char *outp;
8d14daab
TC
208
209 i_clear_error();
50e800e9 210
8d14daab 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));
2c68060c 212
50e800e9
TC
213 if (!i_wf_bbox(face, size, text, len, bbox, utf8))
214 return 0;
215
5091168a 216 bits = render_text(face, size, text, len, aa, &sz, &tm, &line_width, bbox, utf8);
2c68060c
TC
217 if (!bits)
218 return 0;
5091168a 219
50e800e9 220 tx += bbox[BBOX_NEG_WIDTH];
2c68060c 221 top = ty;
a6d9b737 222 if (align) {
2c68060c 223 top -= tm.tmAscent;
a6d9b737
TC
224 }
225 else {
a6d9b737
TC
226 top -= tm.tmAscent - bbox[BBOX_ASCENT];
227 }
2c68060c 228
5091168a
TC
229 r = i_render_new(im, sz.cx);
230 outp = bits;
2c68060c 231 for (y = 0; y < sz.cy; ++y) {
5091168a
TC
232 i_render_color(r, tx, top + sz.cy - y - 1, sz.cx, outp, cl);
233 outp += line_width;
2c68060c 234 }
5091168a
TC
235 i_render_delete(r);
236 myfree(bits);
2c68060c
TC
237
238 return 1;
239}
240
c13a1ea8
TC
241/*
242=item i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa)
243
244Draws the text in the given channel.
245
246=cut
247*/
248
2c68060c 249int
8d14daab
TC
250i_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) {
2c68060c 252 unsigned char *bits;
2c68060c 253 SIZE sz;
5091168a 254 size_t line_width;
8d14daab 255 i_img_dim x, y;
2c68060c 256 TEXTMETRIC tm;
8d14daab
TC
257 i_img_dim top;
258 i_img_dim bbox[BOUNDING_BOX_COUNT];
5091168a 259 unsigned char *outp;
8d14daab
TC
260
261 i_clear_error();
2c68060c 262
8d14daab 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));
50e800e9
TC
264
265 if (!i_wf_bbox(face, size, text, len, bbox, utf8))
266 return 0;
267
5091168a 268 bits = render_text(face, size, text, len, aa, &sz, &tm, &line_width, bbox, utf8);
2c68060c
TC
269 if (!bits)
270 return 0;
271
2c68060c 272 top = ty;
a6d9b737 273 if (align) {
2c68060c 274 top -= tm.tmAscent;
a6d9b737
TC
275 }
276 else {
a6d9b737
TC
277 top -= tm.tmAscent - bbox[BBOX_ASCENT];
278 }
2c68060c 279
5091168a 280 outp = bits;
2c68060c
TC
281 for (y = 0; y < sz.cy; ++y) {
282 for (x = 0; x < sz.cx; ++x) {
283 i_color pel;
5091168a 284 int scale = outp[x];
2c68060c
TC
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 }
5091168a 289 outp += line_width;
2c68060c 290 }
5091168a 291 myfree(bits);
2c68060c
TC
292
293 return 1;
294}
295
8ceea84c
TC
296static HMODULE gdi_dll;
297typedef BOOL (CALLBACK *AddFontResourceExA_t)(LPCSTR, DWORD, PVOID);
298static AddFontResourceExA_t AddFontResourceExAp;
299typedef BOOL (CALLBACK *RemoveFontResourceExA_t)(LPCSTR, DWORD, PVOID);
300static RemoveFontResourceExA_t RemoveFontResourceExAp;
301
8457948a
TC
302/*
303=item i_wf_addfont(char const *filename, char const *resource_file)
304
305Adds a TTF font file as usable by the application.
306
8ceea84c
TC
307Where possible the font is added as private to the application.
308
309Under Windows 95/98/ME the font is added globally, since we don't have
310any choice. In either case call i_wf_delfont() to remove it.
8457948a
TC
311
312=cut
313 */
314int
315i_wf_addfont(char const *filename) {
316 i_clear_error();
317
b8b889ba 318 mm_log((1, "i_wf_addfont(%s)\n", filename));
8ceea84c
TC
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");
b8b889ba
TC
324 mm_log((1, "i_wf_addfont: AddFontResourceExA %p RemoveFontResourceExA %p\n",
325 AddFontResourceExAp, RemoveFontResourceExAp));
8ceea84c
TC
326 }
327 }
328
b8b889ba
TC
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 }
8457948a
TC
334 }
335 else {
b8b889ba
TC
336 mm_log((1, "i_wf_addfont: adding via AddFontResource()\n"));
337 if (AddFontResource(filename)) {
338 return 1;
339 }
8457948a 340 }
b8b889ba
TC
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;
8457948a
TC
345}
346
8ceea84c
TC
347/*
348=item i_wf_delfont(char const *filename, char const *resource_file)
349
350Deletes a TTF font file as usable by the application.
351
352=cut
353 */
354int
355i_wf_delfont(char const *filename) {
356 i_clear_error();
357
b8b889ba
TC
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;
8ceea84c
TC
364 }
365 else {
b8b889ba
TC
366 mm_log((1, "i_wf_delfont: adding via RemoveFontResourceEx()\n"));
367 if (RemoveFontResource(filename))
368 return 1;
8ceea84c 369 }
b8b889ba
TC
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;
8ceea84c
TC
374}
375
c13a1ea8
TC
376/*
377=back
378
379=head1 INTERNAL FUNCTIONS
380
381=over
382
383=item set_logfont(face, size, lf)
384
385Fills in a LOGFONT structure with reasonable defaults.
386
387=cut
388*/
389
885e13c5 390static void set_logfont(const char *face, int size, LOGFONT *lf) {
2c68060c
TC
391 memset(lf, 0, sizeof(LOGFONT));
392
393 lf->lfHeight = -size; /* character height rather than cell height */
ea9e6c3f 394 lf->lfCharSet = DEFAULT_CHARSET;
2c68060c
TC
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
c13a1ea8
TC
402/*
403=item render_text(face, size, text, length, aa, pbm, psz, tm)
404
405renders the text to an in-memory RGB bitmap
406
407It would be nice to render to greyscale, but Windows doesn't have
408native greyscale bitmaps.
409
410=cut
411*/
5091168a
TC
412static unsigned char *
413render_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) {
faa9b3e7
TC
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;
bd4f550c
TC
423 int wide_count;
424 LPWSTR wide_text;
5091168a 425 unsigned char *result;
bd4f550c 426
faa9b3e7
TC
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
bd4f550c
TC
437 if (utf8) {
438 wide_text = utf8_to_wide_string(text, length, &wide_count);
439 }
440 else {
5091168a 441 wide_text = latin1_to_wide_string(text, length, &wide_count);
bd4f550c
TC
442 }
443
faa9b3e7
TC
444 bmpDc = CreateCompatibleDC(dc);
445 if (bmpDc) {
446 font = CreateFontIndirect(&lf);
447 if (font) {
448 oldFont = SelectObject(bmpDc, font);
50e800e9 449
faa9b3e7 450 GetTextMetrics(bmpDc, tm);
50e800e9
TC
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];
faa9b3e7
TC
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;
a6d9b737 462 bmih->biXPelsPerMeter = (LONG)(72 / 2.54 * 100);
faa9b3e7
TC
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));
5091168a 473 TextOutW(bmpDc, -bbox[BBOX_NEG_WIDTH], 0, wide_text, wide_count);
faa9b3e7
TC
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);
5091168a 483 myfree(wide_text);
faa9b3e7
TC
484 return NULL;
485 }
486 SelectObject(bmpDc, oldFont);
487 DeleteObject(font);
488 }
489 else {
5091168a 490 myfree(wide_text);
faa9b3e7
TC
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 {
5091168a 500 myfree(wide_text);
faa9b3e7
TC
501 i_push_errorf(0, "Could not create rendering DC: %ld", GetLastError());
502 ReleaseDC(NULL, dc);
503 return NULL;
504 }
505
5091168a 506 myfree(wide_text);
bd4f550c 507
faa9b3e7
TC
508 ReleaseDC(NULL, dc);
509
faa9b3e7
TC
510 *psz = sz;
511
5091168a
TC
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;
faa9b3e7
TC
535}
536
c13a1ea8 537/*
bd4f550c
TC
538=item utf8_to_wide_string(text, text_len, wide_chars)
539
540=cut
541*/
542
543static
544LPWSTR
545utf8_to_wide_string(char const *text, int text_len, int *wide_chars) {
405c8105 546 int wide_count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, text_len, NULL, 0);
bd4f550c
TC
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);
405c8105 555 if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, text_len, result, wide_count) < 0) {
bd4f550c
TC
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
5091168a
TC
566static LPWSTR
567latin1_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}
bd4f550c
TC
579
580/*
581=back
582
c13a1ea8
TC
583=head1 BUGS
584
585Should really use a structure so we can set more attributes.
586
587Should support UTF8
588
589=head1 AUTHOR
590
591Tony Cook <tony@develop-help.com>
592
593=head1 SEE ALSO
594
595Imager(3)
596
597=cut
598*/