/src/graphicsmagick/magick/annotate.c
Line | Count | Source |
1 | | /* |
2 | | % Copyright (C) 2003 - 2018 GraphicsMagick Group |
3 | | % Copyright (C) 2002 ImageMagick Studio |
4 | | % Copyright 1991-1999 E. I. du Pont de Nemours and Company |
5 | | % |
6 | | % This program is covered by multiple licenses, which are described in |
7 | | % Copyright.txt. You should have received a copy of Copyright.txt with this |
8 | | % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html. |
9 | | % |
10 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
11 | | % % |
12 | | % % |
13 | | % % |
14 | | % AAA N N N N OOO TTTTT AAA TTTTT EEEEE % |
15 | | % A A NN N NN N O O T A A T E % |
16 | | % AAAAA N N N N N N O O T AAAAA T EEE % |
17 | | % A A N NN N NN O O T A A T E % |
18 | | % A A N N N N OOO T A A T EEEEE % |
19 | | % % |
20 | | % % |
21 | | % GraphicsMagick Image Annotation Methods % |
22 | | % % |
23 | | % % |
24 | | % Software Design % |
25 | | % John Cristy % |
26 | | % July 1992 % |
27 | | % % |
28 | | % % |
29 | | % % |
30 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
31 | | % |
32 | | % Digital Applications (www.digapp.com) contributed the stroked text algorithm. |
33 | | % It was written by Leonard Rosenthol. |
34 | | % |
35 | | % |
36 | | */ |
37 | | |
38 | | /* |
39 | | Include declarations. |
40 | | */ |
41 | | #include "magick/studio.h" |
42 | | #include "magick/alpha_composite.h" |
43 | | #include "magick/analyze.h" |
44 | | #include "magick/color.h" |
45 | | #include "magick/color_lookup.h" |
46 | | #include "magick/composite.h" |
47 | | #include "magick/constitute.h" |
48 | | #include "magick/gem.h" |
49 | | #include "magick/log.h" |
50 | | #include "magick/pixel_cache.h" |
51 | | #include "magick/render.h" |
52 | | #include "magick/tempfile.h" |
53 | | #include "magick/transform.h" |
54 | | #include "magick/utility.h" |
55 | | #include "magick/xwindow.h" |
56 | | #if defined(HasTTF) |
57 | | |
58 | | # if defined(__MINGW32__) |
59 | | # undef interface /* Remove interface define */ |
60 | | # endif |
61 | | |
62 | | # if defined(HAVE_FT2BUILD_H) |
63 | | /* |
64 | | Modern FreeType2 installs require that <ft2build.h> be included |
65 | | before including other FreeType2 headers. Including |
66 | | <ft2build.h> establishes definitions used by other FreeType2 |
67 | | headers. |
68 | | */ |
69 | | # include <ft2build.h> |
70 | | # include FT_FREETYPE_H |
71 | | # include FT_GLYPH_H |
72 | | # include FT_OUTLINE_H |
73 | | # include FT_BBOX_H |
74 | | # else |
75 | | /* |
76 | | Very old way to include FreeType2 |
77 | | */ |
78 | | # include <freetype/freetype.h> |
79 | | # include <freetype/ftglyph.h> |
80 | | # include <freetype/ftoutln.h> |
81 | | # include <freetype/ftbbox.h> |
82 | | # endif /* defined(HAVE_FT2BUILD_H) */ |
83 | | |
84 | | #endif /* defined(HasTTF) */ |
85 | | |
86 | | /* |
87 | | Forward declarations. |
88 | | */ |
89 | | typedef magick_int32_t magick_code_point_t; |
90 | | |
91 | | static unsigned int |
92 | | RenderType(Image *,const DrawInfo *,const PointInfo *,TypeMetric *), |
93 | | RenderPostscript(Image *,const DrawInfo *,const PointInfo *,TypeMetric *), |
94 | | RenderFreetype(Image *,const DrawInfo *,const char *,const PointInfo *, |
95 | | TypeMetric *), |
96 | | RenderX11(Image *,const DrawInfo *,const PointInfo *,TypeMetric *); |
97 | | |
98 | | /* |
99 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
100 | | % % |
101 | | % % |
102 | | % % |
103 | | % A n n o t a t e I m a g e % |
104 | | % % |
105 | | % % |
106 | | % % |
107 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
108 | | % |
109 | | % AnnotateImage() annotates an image with DrawInfo 'text' based on other |
110 | | % parameters from DrawInfo such as 'affine', 'align', 'decorate', and |
111 | | % 'gravity'. |
112 | | % |
113 | | % Originally this function additionally transformed 'text' using |
114 | | % TranslateText() but it no longer does so as of GraphicsMagick 1.3.32. |
115 | | % |
116 | | % The format of the AnnotateImage method is: |
117 | | % |
118 | | % MagickPassFail AnnotateImage(Image *image,DrawInfo *draw_info) |
119 | | % |
120 | | % A description of each parameter follows: |
121 | | % |
122 | | % o status: Method AnnotateImage returns MagickPass if the image is annotated |
123 | | % otherwise MagickFail. |
124 | | % |
125 | | % o image: The image. |
126 | | % |
127 | | % o draw_info: The draw info. |
128 | | % |
129 | | % |
130 | | */ |
131 | | MagickExport MagickPassFail AnnotateImage(Image *image,const DrawInfo *draw_info) |
132 | 24.8k | { |
133 | 24.8k | char |
134 | 24.8k | primitive[MaxTextExtent], |
135 | 24.8k | *p, |
136 | 24.8k | *text, |
137 | 24.8k | **textlist; |
138 | | |
139 | 24.8k | DrawInfo |
140 | 24.8k | *annotate, |
141 | 24.8k | *clone_info; |
142 | | |
143 | 24.8k | PointInfo |
144 | 24.8k | offset; |
145 | | |
146 | 24.8k | RectangleInfo |
147 | 24.8k | geometry; |
148 | | |
149 | 24.8k | register size_t |
150 | 24.8k | i; |
151 | | |
152 | 24.8k | TypeMetric |
153 | 24.8k | metrics; |
154 | | |
155 | 24.8k | unsigned int |
156 | 24.8k | matte; |
157 | | |
158 | 24.8k | MagickPassFail |
159 | 24.8k | status=MagickPass; |
160 | | |
161 | 24.8k | unsigned long |
162 | 24.8k | height, |
163 | 24.8k | number_lines; |
164 | | |
165 | 24.8k | MagickBool |
166 | 24.8k | metrics_initialized = MagickFalse; |
167 | | |
168 | 24.8k | assert(image != (Image *) NULL); |
169 | 24.8k | assert(image->signature == MagickSignature); |
170 | 24.8k | assert(draw_info != (DrawInfo *) NULL); |
171 | 24.8k | assert(draw_info->signature == MagickSignature); |
172 | 24.8k | if (draw_info->text == (char *) NULL) |
173 | 0 | return(MagickFail); |
174 | 24.8k | if (*draw_info->text == '\0') |
175 | 19.8k | return(MagickPass); |
176 | 4.98k | annotate=CloneDrawInfo((ImageInfo *) NULL,draw_info); |
177 | 4.98k | text=annotate->text; |
178 | 4.98k | annotate->text=(char *) NULL; |
179 | 4.98k | clone_info=CloneDrawInfo((ImageInfo *) NULL,annotate); |
180 | | /* |
181 | | Split text into list based on new-lines |
182 | | */ |
183 | 4.98k | number_lines=1; |
184 | 8.09M | for (p=text; *p != '\0'; p++) |
185 | 8.09M | if (*p == '\n') |
186 | 134k | number_lines++; |
187 | 4.98k | textlist=MagickAllocateArray(char **,((size_t) number_lines+1),sizeof(char *)); |
188 | 4.98k | if (textlist == (char **) NULL) |
189 | 0 | MagickFatalError3(ResourceLimitFatalError,MemoryAllocationFailed, |
190 | 4.98k | UnableToConvertText); |
191 | 4.98k | p=text; |
192 | 144k | for (i=0; i < number_lines; i++) |
193 | 139k | { |
194 | 139k | char *q; |
195 | 139k | textlist[i]=p; |
196 | 7.06M | for (q=(char *) p; *q != '\0'; q++) |
197 | 7.06M | if ((*q == '\r') || (*q == '\n')) |
198 | 136k | break; |
199 | 139k | if (*q == '\r') |
200 | 1.99k | { |
201 | 1.99k | *q='\0'; |
202 | 1.99k | q++; |
203 | 1.99k | } |
204 | 139k | *q='\0'; |
205 | 139k | p=q+1; |
206 | 139k | } |
207 | 4.98k | textlist[i]=(char *) NULL; |
208 | | |
209 | 4.98k | SetGeometry(image,&geometry); |
210 | 4.98k | if (draw_info->geometry != (char *) NULL) |
211 | 4.98k | (void) GetGeometry(draw_info->geometry,&geometry.x,&geometry.y, |
212 | 4.98k | &geometry.width,&geometry.height); |
213 | 4.98k | matte=image->matte; |
214 | 4.98k | status=MagickPass; |
215 | 18.9k | for (i=0; textlist[i] != (char *) NULL; i++) |
216 | 15.1k | { |
217 | 15.1k | if (*textlist[i] == '\0') |
218 | 13.9k | continue; |
219 | | /* |
220 | | Position text relative to image. |
221 | | */ |
222 | 1.21k | (void) CloneString(&annotate->text,textlist[i]); |
223 | 1.21k | if ((!metrics_initialized) || (annotate->gravity != NorthWestGravity)) |
224 | 1.21k | { |
225 | 1.21k | metrics_initialized=MagickTrue; |
226 | 1.21k | (void) GetTypeMetrics(image,annotate,&metrics); |
227 | 1.21k | } |
228 | 1.21k | height=(unsigned long) (metrics.ascent-metrics.descent); |
229 | 1.21k | switch (annotate->gravity) |
230 | 1.21k | { |
231 | 0 | case ForgetGravity: |
232 | 601 | case NorthWestGravity: |
233 | 601 | default: |
234 | 601 | { |
235 | 601 | offset.x=(double)geometry.x+(double)i*draw_info->affine.ry*height; |
236 | 601 | offset.y=(double)geometry.y+(double)i*draw_info->affine.sy*height; |
237 | 601 | break; |
238 | 601 | } |
239 | 612 | case NorthGravity: |
240 | 612 | { |
241 | 612 | offset.x=(double)geometry.x+(double)geometry.width/2+(double)i*draw_info->affine.ry*height- |
242 | 612 | (double)draw_info->affine.sx*metrics.width/2.0; |
243 | 612 | offset.y=(double)geometry.y+(double)i*draw_info->affine.sy*height-draw_info->affine.rx* |
244 | 612 | metrics.width/2.0; |
245 | 612 | break; |
246 | 601 | } |
247 | 0 | case NorthEastGravity: |
248 | 0 | { |
249 | 0 | offset.x=(geometry.width == 0 ? 1.0 : -1.0)*(double)geometry.x+(double)geometry.width+i* |
250 | 0 | draw_info->affine.ry*height-(double)draw_info->affine.sx*metrics.width; |
251 | 0 | offset.y=(double)geometry.y+(double)i*draw_info->affine.sy*height-(double)draw_info->affine.rx* |
252 | 0 | metrics.width; |
253 | 0 | break; |
254 | 601 | } |
255 | 1 | case WestGravity: |
256 | 1 | { |
257 | 1 | offset.x=(double)geometry.x+(double)i*draw_info->affine.ry*height+(double)draw_info->affine.ry* |
258 | 1 | (metrics.ascent+metrics.descent-(double)(number_lines-1)*(double) height)/2.0; |
259 | 1 | offset.y=(double) geometry.y+(double)geometry.height/2.0+(double)i*draw_info->affine.sy*height+ |
260 | 1 | (double)draw_info->affine.sy*((double)metrics.ascent+metrics.descent-(double)(number_lines-1)* |
261 | 1 | height)/2.0; |
262 | 1 | break; |
263 | 601 | } |
264 | 0 | case StaticGravity: |
265 | 1 | case CenterGravity: |
266 | 1 | { |
267 | 1 | offset.x=(double)geometry.x+(double)geometry.width/2.0+(double)i*draw_info->affine.ry*height- |
268 | 1 | (double)draw_info->affine.sx*metrics.width/2.0+draw_info->affine.ry* |
269 | 1 | ((double)metrics.ascent+metrics.descent-(double)(number_lines-1)*height)/2.0; |
270 | 1 | offset.y=(double)geometry.y+(double)geometry.height/2.0+(double)i*draw_info->affine.sy*height- |
271 | 1 | (double)draw_info->affine.rx*metrics.width/2.0+(double)draw_info->affine.sy* |
272 | 1 | ((double)metrics.ascent+metrics.descent-(double)(number_lines-1)*height)/2.0; |
273 | 1 | break; |
274 | 0 | } |
275 | 1 | case EastGravity: |
276 | 1 | { |
277 | 1 | offset.x=(geometry.width == 0 ? 1.0 : -1.0)*(double)geometry.x+(double)geometry.width+(double)i* |
278 | 1 | (double)draw_info->affine.ry*height-(double)draw_info->affine.sx*metrics.width+ |
279 | 1 | (double)draw_info->affine.ry*((double)metrics.ascent+(double)metrics.descent-(number_lines-1)* |
280 | 1 | (double)height)/2.0; |
281 | 1 | offset.y=(double)geometry.y+(double)geometry.height/2.0+(double)i*draw_info->affine.sy*height- |
282 | 1 | (double)draw_info->affine.rx*metrics.width+(double)draw_info->affine.sy* |
283 | 1 | ((double)metrics.ascent+metrics.descent-(number_lines-1)*(double)height)/2.0; |
284 | 1 | break; |
285 | 0 | } |
286 | 0 | case SouthWestGravity: |
287 | 0 | { |
288 | 0 | offset.x= (double)geometry.x+ (double)i*draw_info->affine.ry*height-draw_info->affine.ry* |
289 | 0 | (number_lines-1)*height; |
290 | 0 | offset.y= (double)(geometry.height == 0 ? 1 : -1)*geometry.y+geometry.height+i* |
291 | 0 | draw_info->affine.sy*height-draw_info->affine.sy*(number_lines-1)* |
292 | 0 | height; |
293 | 0 | break; |
294 | 0 | } |
295 | 1 | case SouthGravity: |
296 | 1 | { |
297 | 1 | offset.x= (double)geometry.x+(double)geometry.width/2.0+(double)i*draw_info->affine.ry* |
298 | 1 | height- (double)draw_info->affine.sx*metrics.width/2.0- |
299 | 1 | (double)draw_info->affine.ry*(number_lines-1)*height; |
300 | 1 | offset.y=(geometry.height == 0 ? 1.0 : -1.0)*geometry.y+geometry.height+(double)i* |
301 | 1 | draw_info->affine.sy*height-(double)draw_info->affine.rx* |
302 | 1 | metrics.width/2.0-(double)draw_info->affine.sy*(number_lines-1)*height; |
303 | 1 | break; |
304 | 0 | } |
305 | 0 | case SouthEastGravity: |
306 | 0 | { |
307 | 0 | offset.x=(geometry.width == 0 ? 1.0 : -1.0)*geometry.x+geometry.width+(double)i* |
308 | 0 | draw_info->affine.ry*height-(double)draw_info->affine.sx*metrics.width- |
309 | 0 | (double)draw_info->affine.ry*(number_lines-1)*height; |
310 | 0 | offset.y=(geometry.height == 0 ? 1.0 : -1.0)*geometry.y+(double)geometry.height+i* |
311 | 0 | draw_info->affine.sy*height-(double)draw_info->affine.rx*metrics.width- |
312 | 0 | (double)draw_info->affine.sy*(number_lines-1)*height; |
313 | 0 | break; |
314 | 0 | } |
315 | 1.21k | } |
316 | 1.21k | switch (annotate->align) |
317 | 1.21k | { |
318 | 0 | case LeftAlign: |
319 | 0 | { |
320 | 0 | offset.x=geometry.x+i*draw_info->affine.ry*height; |
321 | 0 | offset.y=geometry.y+i*draw_info->affine.sy*height; |
322 | 0 | break; |
323 | 0 | } |
324 | 1 | case CenterAlign: |
325 | 1 | { |
326 | 1 | offset.x=geometry.x+i*draw_info->affine.ry*height-draw_info->affine.sx* |
327 | 1 | metrics.width/2; |
328 | 1 | offset.y=geometry.y+i*draw_info->affine.sy*height-draw_info->affine.rx* |
329 | 1 | metrics.width/2; |
330 | 1 | break; |
331 | 0 | } |
332 | 0 | case RightAlign: |
333 | 0 | { |
334 | 0 | offset.x=geometry.x+i*draw_info->affine.ry*height-draw_info->affine.sx* |
335 | 0 | metrics.width; |
336 | 0 | offset.y=geometry.y+i*draw_info->affine.sy*height-draw_info->affine.rx* |
337 | 0 | metrics.width; |
338 | 0 | break; |
339 | 0 | } |
340 | 1.21k | default: |
341 | 1.21k | break; |
342 | 1.21k | } |
343 | 1.21k | if (draw_info->undercolor.opacity != TransparentOpacity) |
344 | 1 | { |
345 | | /* |
346 | | Text box. |
347 | | */ |
348 | 1 | clone_info->fill=draw_info->undercolor; |
349 | 1 | clone_info->affine.tx=offset.x-draw_info->affine.ry*(metrics.ascent- |
350 | 1 | metrics.max_advance/4); |
351 | 1 | clone_info->affine.ty=offset.y-draw_info->affine.sy*metrics.ascent; |
352 | 1 | FormatString(primitive,"rectangle 0,0 %g,%ld",metrics.width+ |
353 | 1 | metrics.max_advance/2.0,height); |
354 | 1 | (void) CloneString(&clone_info->primitive,primitive); |
355 | 1 | (void) DrawImage(image,clone_info); |
356 | 1 | } |
357 | 1.21k | clone_info->affine.tx=offset.x; |
358 | 1.21k | clone_info->affine.ty=offset.y; |
359 | 1.21k | FormatString(primitive,"stroke-width %g line 0,0 %g,0", |
360 | 1.21k | metrics.underline_thickness,metrics.width); |
361 | 1.21k | if (annotate->decorate == OverlineDecoration) |
362 | 1 | { |
363 | 1 | clone_info->affine.ty-=(draw_info->affine.sy* |
364 | 1 | (metrics.ascent+metrics.descent)-metrics.underline_position); |
365 | 1 | (void) CloneString(&clone_info->primitive,primitive); |
366 | 1 | (void) DrawImage(image,clone_info); |
367 | 1 | } |
368 | 1.21k | else |
369 | 1.21k | if (annotate->decorate == UnderlineDecoration) |
370 | 0 | { |
371 | 0 | clone_info->affine.ty-=metrics.underline_position; |
372 | 0 | (void) CloneString(&clone_info->primitive,primitive); |
373 | 0 | (void) DrawImage(image,clone_info); |
374 | 0 | } |
375 | | /* |
376 | | Annotate image with text. |
377 | | */ |
378 | 1.21k | status=RenderType(image,annotate,&offset,&metrics); |
379 | 1.21k | if (status == MagickFail) |
380 | 1.21k | break; |
381 | 0 | if (annotate->decorate == LineThroughDecoration) |
382 | 0 | { |
383 | 0 | clone_info->affine.ty-=(draw_info->affine.sy*height+ |
384 | 0 | metrics.underline_position)/2.0; |
385 | 0 | (void) CloneString(&clone_info->primitive,primitive); |
386 | 0 | (void) DrawImage(image,clone_info); |
387 | 0 | } |
388 | 0 | } |
389 | 4.98k | image->matte=matte; |
390 | | /* |
391 | | Free resources. |
392 | | */ |
393 | 4.98k | DestroyDrawInfo(clone_info); |
394 | 4.98k | DestroyDrawInfo(annotate); |
395 | 4.98k | MagickFreeMemory(textlist); |
396 | 4.98k | MagickFreeMemory(text); |
397 | 4.98k | return(status); |
398 | 4.98k | } |
399 | | |
400 | | #if defined(HasTTF) |
401 | | /* |
402 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
403 | | % % |
404 | | % % |
405 | | % % |
406 | | + E n c o d e S J I S % |
407 | | % % |
408 | | % % |
409 | | % % |
410 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
411 | | % |
412 | | % EncodeSJIS() converts an ASCII text string to 2-bytes per character code |
413 | | % (like UCS-2). Returns the translated codes and the character count. |
414 | | % Characters under 0x7f are just copied, characters over 0x80 are tied with |
415 | | % the next character. |
416 | | % |
417 | | % Katsutoshi Shibuya contributed this method. |
418 | | % |
419 | | % The format of the EncodeSJIS function is: |
420 | | % |
421 | | % encoding=EncodeSJIS(const char *text,size_t count) |
422 | | % |
423 | | % A description of each parameter follows: |
424 | | % |
425 | | % o encoding: EncodeSJIS() returns a pointer to an unsigned short |
426 | | % array representing the encoded version of the ASCII string. |
427 | | % |
428 | | % o text: The text. |
429 | | % |
430 | | % o count: return the number of characters generated by the encoding. |
431 | | % |
432 | | % |
433 | | */ |
434 | | |
435 | | static int GetOneCharacter(const unsigned char *text,size_t *length) |
436 | 0 | { |
437 | 0 | unsigned int |
438 | 0 | c; |
439 | |
|
440 | 0 | if (*length < 1) |
441 | 0 | return(-1); |
442 | 0 | c=text[0]; |
443 | 0 | if (!(c & 0x80)) |
444 | 0 | { |
445 | 0 | *length=1; |
446 | 0 | return((int) c); |
447 | 0 | } |
448 | 0 | if (*length < 2) |
449 | 0 | { |
450 | 0 | *length=0; |
451 | 0 | return(-1); |
452 | 0 | } |
453 | 0 | *length=2; |
454 | 0 | c=((int) (text[0]) << 8); |
455 | 0 | c|=text[1]; |
456 | 0 | return((int) c); |
457 | 0 | } |
458 | | |
459 | | static magick_code_point_t *EncodeSJIS(const char *text,size_t *count) |
460 | 0 | { |
461 | 0 | int |
462 | 0 | c; |
463 | |
|
464 | 0 | register const char |
465 | 0 | *p; |
466 | |
|
467 | 0 | register magick_code_point_t |
468 | 0 | *q; |
469 | |
|
470 | 0 | size_t |
471 | 0 | length; |
472 | |
|
473 | 0 | magick_code_point_t |
474 | 0 | *encoding; |
475 | |
|
476 | 0 | *count=0; |
477 | 0 | if ((text == (char *) NULL) || (*text == '\0')) |
478 | 0 | return((magick_code_point_t *) NULL); |
479 | 0 | encoding=MagickAllocateArray(magick_code_point_t *, |
480 | 0 | (strlen(text)+MaxTextExtent), |
481 | 0 | sizeof(magick_code_point_t)); |
482 | 0 | if (encoding == (magick_code_point_t *) NULL) |
483 | 0 | MagickFatalError3(ResourceLimitFatalError,MemoryAllocationFailed, |
484 | 0 | UnableToConvertText); |
485 | 0 | q=encoding; |
486 | 0 | for (p=text; *p != '\0'; p+=length) |
487 | 0 | { |
488 | 0 | length=strlen(p); |
489 | 0 | c=GetOneCharacter((const unsigned char *) p,&length); |
490 | 0 | if (c < 0) |
491 | 0 | { |
492 | 0 | q=encoding; |
493 | 0 | for (p=text; *p != '\0'; p++) |
494 | 0 | *q++=(unsigned char) *p; |
495 | 0 | break; |
496 | 0 | } |
497 | 0 | *q=(magick_code_point_t) c; |
498 | 0 | q++; |
499 | 0 | } |
500 | 0 | *count=q-encoding; |
501 | 0 | return(encoding); |
502 | 0 | } |
503 | | |
504 | | /* |
505 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
506 | | % % |
507 | | % % |
508 | | % % |
509 | | + E n c o d e T e x t % |
510 | | % % |
511 | | % % |
512 | | % % |
513 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
514 | | % |
515 | | % EncodeText() converts an ASCII text string to wide text and returns the |
516 | | % translation and the character count. |
517 | | % |
518 | | % The format of the EncodeText function is: |
519 | | % |
520 | | % encoding=EncodeText(const char *text,size_t count) |
521 | | % |
522 | | % A description of each parameter follows: |
523 | | % |
524 | | % o encoding: EncodeText() returns a pointer to an unsigned short array |
525 | | % array representing the encoded version of the ASCII string. |
526 | | % |
527 | | % o text: The text. |
528 | | % |
529 | | % o count: return the number of characters generated by the encoding. |
530 | | % |
531 | | % |
532 | | */ |
533 | | static magick_code_point_t *EncodeText(const char *text,size_t *count) |
534 | 0 | { |
535 | 0 | register const char |
536 | 0 | *p; |
537 | |
|
538 | 0 | register magick_code_point_t |
539 | 0 | *q; |
540 | |
|
541 | 0 | magick_code_point_t |
542 | 0 | *encoding; |
543 | |
|
544 | 0 | *count=0; |
545 | 0 | if ((text == (char *) NULL) || (*text == '\0')) |
546 | 0 | return((magick_code_point_t *) NULL); |
547 | 0 | encoding=MagickAllocateArray(magick_code_point_t *, |
548 | 0 | (strlen(text)+MaxTextExtent), |
549 | 0 | sizeof(magick_code_point_t)); |
550 | 0 | if (encoding == (magick_code_point_t *) NULL) |
551 | 0 | MagickFatalError3(ResourceLimitFatalError,MemoryAllocationFailed, |
552 | 0 | UnableToConvertText); |
553 | 0 | q=encoding; |
554 | 0 | for (p=text; *p != '\0'; p++) |
555 | 0 | *q++=(unsigned char) *p; |
556 | 0 | *count=q-encoding; |
557 | 0 | return(encoding); |
558 | 0 | } |
559 | | |
560 | | /* |
561 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
562 | | % % |
563 | | % % |
564 | | % % |
565 | | + E n c o d e U n i c o d e % |
566 | | % % |
567 | | % % |
568 | | % % |
569 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
570 | | % |
571 | | % EncodeUnicode() converts an ASCII text string to Unicode and returns the |
572 | | % Unicode translation and the character count. Characters under 0x7f are |
573 | | % just copied, characters over 0x80 are tied with the next character. |
574 | | % |
575 | | % The format of the EncodeUnicode function is: |
576 | | % |
577 | | % unicode=EncodeUnicode(const unsigned char *text,size_t count) |
578 | | % |
579 | | % A description of each parameter follows: |
580 | | % |
581 | | % o unicode: EncodeUnicode() returns a pointer to an unsigned short array |
582 | | % array representing the encoded version of the ASCII string. |
583 | | % |
584 | | % o text: The text. |
585 | | % |
586 | | % o count: return the number of characters generated by the encoding. |
587 | | % |
588 | | % |
589 | | */ |
590 | | static int GetUnicodeCharacter(const unsigned char *text,size_t *length) |
591 | 0 | { |
592 | 0 | unsigned int |
593 | 0 | c; |
594 | |
|
595 | 0 | if (*length < 1) |
596 | 0 | return(-1); |
597 | 0 | c=text[0]; |
598 | 0 | if (!(c & 0x80)) |
599 | 0 | { |
600 | 0 | *length=1; |
601 | 0 | return((int) c); |
602 | 0 | } |
603 | 0 | if ((*length < 2) || ((text[1] & 0xc0) != 0x80)) |
604 | 0 | { |
605 | 0 | *length=0; |
606 | 0 | return(-1); |
607 | 0 | } |
608 | 0 | if ((c & 0xe0) != 0xe0) |
609 | 0 | { |
610 | 0 | *length=2; |
611 | 0 | c=(text[0] & 0x1f) << 6; |
612 | 0 | c|=text[1] & 0x3f; |
613 | 0 | return((int) c); |
614 | 0 | } |
615 | 0 | if ((*length < 3) || ((text[2] & 0xc0) != 0x80)) |
616 | 0 | { |
617 | 0 | *length=0; |
618 | 0 | return(-1); |
619 | 0 | } |
620 | 0 | if ((c & 0xf0) != 0xf0) |
621 | 0 | { |
622 | 0 | *length=3; |
623 | 0 | c=(text[0] & 0xf) << 12; |
624 | 0 | c|=(text[1] & 0x3f) << 6; |
625 | 0 | c|=text[2] & 0x3f; |
626 | 0 | return((int) c); |
627 | 0 | } |
628 | 0 | if ((*length < 4) || ((c & 0xf8) != 0xf0) || ((text[3] & 0xc0) != 0x80)) |
629 | 0 | { |
630 | 0 | *length=0; |
631 | 0 | return(-1); |
632 | 0 | } |
633 | 0 | *length=4; |
634 | 0 | c=(text[0] & 0x7) << 18; |
635 | 0 | c|=(text[1] & 0x3f) << 12; |
636 | 0 | c|=(text[2] & 0x3f) << 6; |
637 | 0 | c|=text[3] & 0x3f; |
638 | 0 | return((int) c); |
639 | 0 | } |
640 | | |
641 | | static magick_code_point_t *EncodeUnicode(const char *text,size_t *count) |
642 | 0 | { |
643 | 0 | int |
644 | 0 | c; |
645 | |
|
646 | 0 | register const char |
647 | 0 | *p; |
648 | |
|
649 | 0 | register magick_code_point_t |
650 | 0 | *q; |
651 | |
|
652 | 0 | size_t |
653 | 0 | length; |
654 | |
|
655 | 0 | magick_code_point_t |
656 | 0 | *unicode; |
657 | |
|
658 | 0 | *count=0; |
659 | 0 | if ((text == (char *) NULL) || (*text == '\0')) |
660 | 0 | return((magick_code_point_t *) NULL); |
661 | 0 | unicode=MagickAllocateArray(magick_code_point_t *, |
662 | 0 | (strlen(text)+MaxTextExtent), |
663 | 0 | sizeof(magick_code_point_t)); |
664 | 0 | if (unicode == (magick_code_point_t *) NULL) |
665 | 0 | MagickFatalError3(ResourceLimitFatalError,MemoryAllocationFailed, |
666 | 0 | UnableToConvertText); |
667 | 0 | q=unicode; |
668 | 0 | for (p=text; *p != '\0'; p+=length) |
669 | 0 | { |
670 | 0 | length=strlen(p); |
671 | 0 | c=GetUnicodeCharacter((const unsigned char *) p,&length); |
672 | 0 | if (c < 0) |
673 | 0 | { |
674 | 0 | q=unicode; |
675 | 0 | for (p=text; *p != '\0'; p++) |
676 | 0 | *q++=(unsigned char) *p; |
677 | 0 | break; |
678 | 0 | } |
679 | 0 | *q=(magick_code_point_t) c; |
680 | 0 | q++; |
681 | 0 | } |
682 | 0 | *count=q-unicode; |
683 | 0 | return(unicode); |
684 | 0 | } |
685 | | |
686 | | #endif /* HasTTF */ |
687 | | /* |
688 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
689 | | % % |
690 | | % % |
691 | | % % |
692 | | % G e t T y p e M e t r i c s % |
693 | | % % |
694 | | % % |
695 | | % % |
696 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
697 | | % |
698 | | % GetTypeMetrics() returns the following information for the specified font |
699 | | % and text: |
700 | | % |
701 | | % o character width |
702 | | % o character height |
703 | | % o ascent |
704 | | % o descent |
705 | | % o text width |
706 | | % o text height |
707 | | % o maximum horizontal advance |
708 | | % o underline position |
709 | | % o underline thickness |
710 | | % |
711 | | % The format of the GetTypeMetrics method is: |
712 | | % |
713 | | % unsigned int GetTypeMetrics(Image *image,const DrawInfo *draw_info, |
714 | | % TypeMetric *metrics) |
715 | | % |
716 | | % A description of each parameter follows: |
717 | | % |
718 | | % o image: The image. |
719 | | % |
720 | | % o draw_info: The draw info. |
721 | | % |
722 | | % o metrics: Return the font metrics in this structure. |
723 | | % |
724 | | % |
725 | | */ |
726 | | MagickExport MagickPassFail GetTypeMetrics(Image *image,const DrawInfo *draw_info, |
727 | | TypeMetric *metrics) |
728 | 42.6k | { |
729 | 42.6k | DrawInfo |
730 | 42.6k | *clone_info; |
731 | | |
732 | 42.6k | PointInfo |
733 | 42.6k | offset; |
734 | | |
735 | 42.6k | MagickPassFail |
736 | 42.6k | status; |
737 | | |
738 | 42.6k | assert(draw_info != (DrawInfo *) NULL); |
739 | 42.6k | assert(draw_info->text != (char *) NULL); |
740 | 42.6k | assert(draw_info->signature == MagickSignature); |
741 | 42.6k | clone_info=CloneDrawInfo((ImageInfo *) NULL,draw_info); |
742 | 42.6k | clone_info->render=False; |
743 | 42.6k | (void) memset(metrics,0,sizeof(TypeMetric)); |
744 | 42.6k | offset.x=0.0; |
745 | 42.6k | offset.y=0.0; |
746 | 42.6k | status=RenderType(image,clone_info,&offset,metrics); |
747 | 42.6k | DestroyDrawInfo(clone_info); |
748 | 42.6k | return(status); |
749 | 42.6k | } |
750 | | |
751 | | |
752 | | /* |
753 | | Find a single font family name in a comma-separated list; returns a pointer |
754 | | to where next search should start (i.e., to the terminating character), or null |
755 | | if not found. Trims leading and trailing white space, and surrounding single |
756 | | quotes. |
757 | | */ |
758 | | static |
759 | | char const *FindCommaDelimitedName |
760 | | ( |
761 | | char const * pSearchStart, /*start search here*/ |
762 | | char const ** ppStart, /*return pointer to first character in found string*/ |
763 | | char const ** ppEnd /*return pointer to just past last character in found string*/ |
764 | | ) |
765 | 29.5k | { /*FindCommaDelimitedName*/ |
766 | | |
767 | 29.5k | int c; |
768 | 29.5k | char const * pStart; |
769 | 29.5k | char const * pEnd; |
770 | 29.5k | char const * pNextSearchStart; |
771 | | |
772 | 29.5k | if ( pSearchStart == 0 ) |
773 | 0 | return(0); |
774 | | |
775 | 79.8k | for ( pStart = pSearchStart; (c = *pStart) && (isspace(c) || (c == ',')); |
776 | 50.3k | pStart++ ); /*skip leading spaces and commas*/ |
777 | 29.5k | if ( c == '\0' ) |
778 | 5.51k | return(0); /*didn't find anything!*/ |
779 | | |
780 | 2.48M | for ( pEnd = pStart + 1; (c = *pEnd) && (c != ','); pEnd++ ); /*find terminating comma*/ |
781 | 24.0k | pNextSearchStart = pEnd; |
782 | | |
783 | 24.0k | for ( ; isspace((int) pEnd[-1]); pEnd-- ); /*trim trailing space; we know there is a non-space character there*/ |
784 | | |
785 | | /* trim off surrounding single quotes */ |
786 | 24.0k | if ((*pStart == '\'') && (*pEnd == '\'') && ((pEnd-pStart) >= 3)) |
787 | 0 | { |
788 | 0 | pStart++; |
789 | 0 | pEnd--; |
790 | 0 | } |
791 | | |
792 | 24.0k | *ppStart = pStart; |
793 | 24.0k | *ppEnd = pEnd; |
794 | 24.0k | return(pNextSearchStart); |
795 | | |
796 | 29.5k | } /*FindCommaDelimitedName*/ |
797 | | |
798 | | |
799 | | /* |
800 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
801 | | % % |
802 | | % % |
803 | | % % |
804 | | + R e n d e r T y p e % |
805 | | % % |
806 | | % % |
807 | | % % |
808 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
809 | | % |
810 | | % Method RenderType renders text on the image. It also returns the bounding |
811 | | % box of the text relative to the image. |
812 | | % |
813 | | % The format of the RenderType method is: |
814 | | % |
815 | | % unsigned int RenderType(Image *image,DrawInfo *draw_info, |
816 | | % const PointInfo *offset,TypeMetric *metrics) |
817 | | % |
818 | | % A description of each parameter follows: |
819 | | % |
820 | | % o status: Method RenderType returns True if the text is rendered on the |
821 | | % image, otherwise False. |
822 | | % |
823 | | % o image: The image. |
824 | | % |
825 | | % o draw_info: The draw info. |
826 | | % |
827 | | % o offset: (x,y) location of text relative to image. |
828 | | % |
829 | | % o metrics: bounding box of text. |
830 | | % |
831 | | % |
832 | | */ |
833 | | static MagickPassFail RenderType(Image *image,const DrawInfo *draw_info, |
834 | | const PointInfo *offset,TypeMetric *metrics) |
835 | 43.8k | { |
836 | 43.8k | const TypeInfo |
837 | 43.8k | *type_info; |
838 | | |
839 | 43.8k | DrawInfo |
840 | 43.8k | *clone_info; |
841 | | |
842 | 43.8k | MagickPassFail |
843 | 43.8k | status; |
844 | | |
845 | 43.8k | char |
846 | 43.8k | OneFontFamilyName[2048]; /*special handling only if font name this long or less*/ |
847 | | |
848 | 43.8k | char const * |
849 | 43.8k | pTheFoundFontFamilyName; |
850 | | |
851 | 43.8k | type_info=(const TypeInfo *) NULL; |
852 | 43.8k | if (draw_info->font != (char *) NULL) |
853 | 18.9k | { |
854 | 18.9k | if (*draw_info->font == '@') |
855 | 208 | return(RenderFreetype(image,draw_info,(char *) NULL,offset,metrics)); |
856 | 18.7k | if (*draw_info->font == '-') |
857 | 276 | return(RenderX11(image,draw_info,offset,metrics)); |
858 | 18.4k | type_info=GetTypeInfo(draw_info->font,&image->exception); |
859 | 18.4k | if (type_info == (const TypeInfo *) NULL) |
860 | 14.6k | if (IsAccessible(draw_info->font)) |
861 | 1.64k | return(RenderFreetype(image,draw_info,(char *) NULL,offset,metrics)); |
862 | 18.4k | } |
863 | | |
864 | | /* draw_info->family may be a comma-separated list of names ... */ |
865 | 41.7k | pTheFoundFontFamilyName = draw_info->family; |
866 | 41.7k | if (type_info == (const TypeInfo *) NULL) |
867 | 37.8k | { /*type_info not yet found*/ |
868 | | |
869 | | /* stay consistent with previous behavior unless font family contains comma(s) */ |
870 | 37.8k | if (draw_info->family == 0 || (strchr(draw_info->family,',') == 0)) |
871 | 32.3k | { /*null ptr, or no commas in string; preserve previous behavior*/ |
872 | | |
873 | 32.3k | type_info=GetTypeInfoByFamily(draw_info->family,draw_info->style, |
874 | 32.3k | draw_info->stretch,draw_info->weight, |
875 | 32.3k | &image->exception); |
876 | | |
877 | 32.3k | } /*null ptr, or no commas in string; preserve previous behavior*/ |
878 | 5.51k | else |
879 | 5.51k | { /*process as font family list*/ |
880 | | |
881 | 5.51k | char const * pNext = draw_info->family, * pStart = 0, * pEnd = 0; |
882 | 29.5k | while ((pNext = FindCommaDelimitedName(pNext,&pStart,&pEnd)) != 0) |
883 | 24.0k | { /*found a name*/ |
884 | | |
885 | 24.0k | unsigned int NameLength = pEnd - pStart; |
886 | 24.0k | if ( NameLength >= sizeof(OneFontFamilyName) ) |
887 | 210 | continue; |
888 | 23.8k | memcpy(OneFontFamilyName,pStart,NameLength); |
889 | 23.8k | OneFontFamilyName[NameLength] = '\0'; |
890 | 23.8k | type_info = GetTypeInfoByFamily(OneFontFamilyName, |
891 | 23.8k | draw_info->style, |
892 | 23.8k | draw_info->stretch, |
893 | 23.8k | draw_info->weight, |
894 | 23.8k | &image->exception); |
895 | | /*do not allow font substitution*/ |
896 | 23.8k | if ( type_info && (LocaleCompare(OneFontFamilyName, |
897 | 0 | type_info->family) == 0) ) |
898 | 0 | { |
899 | 0 | pTheFoundFontFamilyName = OneFontFamilyName; |
900 | 0 | break; |
901 | 0 | } |
902 | | |
903 | 23.8k | } /*found a name*/ |
904 | | |
905 | 5.51k | } /*process as font family list*/ |
906 | | |
907 | 37.8k | } /*type_info not yet found*/ |
908 | | |
909 | | /* |
910 | | We may have performed font substitution. If so (i.e., font family |
911 | | name does not match), try again assuming draw_info->family is |
912 | | actually a font name. If we get a font name match, that will |
913 | | override the font substitution. |
914 | | */ |
915 | 41.7k | if ((type_info == (const TypeInfo *) NULL) |
916 | 3.84k | || /*found font family, but ...*/ |
917 | 3.84k | (pTheFoundFontFamilyName && |
918 | 202 | (LocaleCompare(pTheFoundFontFamilyName,type_info->family) != 0))) |
919 | 38.0k | {/*either not found, or different font family (probably font substitution)*/ |
920 | | |
921 | | /* try to match a font name */ |
922 | 38.0k | const TypeInfo *type_info2 = 0; |
923 | 38.0k | if (((type_info2 = GetTypeInfo(pTheFoundFontFamilyName, |
924 | 38.0k | &image->exception)) |
925 | 38.0k | == (const TypeInfo *) NULL) |
926 | 26.8k | && (pTheFoundFontFamilyName != 0) |
927 | 26.8k | && strlen(pTheFoundFontFamilyName) < sizeof(OneFontFamilyName)) |
928 | 26.7k | {/*change ' ' to '-' and try again*/ |
929 | | |
930 | | /* |
931 | | Change blanks to hyphens (i.e. make it look like a font |
932 | | name vs. font family). Will only do this for font names |
933 | | sizeof(OneFontFamilyName) long or less. |
934 | | */ |
935 | 26.7k | char FontNameWithHyphens[sizeof(OneFontFamilyName)]; |
936 | 26.7k | char *pWithHyphens = FontNameWithHyphens; |
937 | 26.7k | char c; |
938 | 26.7k | const char *pFound; |
939 | 26.7k | for (pFound = pTheFoundFontFamilyName; |
940 | 470k | (*pWithHyphens = (((c = *pFound) != ' ') ? c : '-')); |
941 | 443k | pFound++, pWithHyphens++); |
942 | 26.7k | type_info2 = GetTypeInfo(FontNameWithHyphens,&image->exception); |
943 | | |
944 | 26.7k | } /*change ' ' to '-' and try again*/ |
945 | | |
946 | 38.0k | if ( type_info2 != (const TypeInfo *) NULL ) |
947 | 11.1k | type_info = type_info2; |
948 | | |
949 | 38.0k | } /*either not found, or different font family (probably font substitution)*/ |
950 | | |
951 | 41.7k | if (type_info == (const TypeInfo *) NULL) |
952 | 26.6k | return(RenderPostscript(image,draw_info,offset,metrics)); |
953 | 15.0k | clone_info=CloneDrawInfo((ImageInfo *) NULL,draw_info); |
954 | 15.0k | if (type_info->glyphs != (char *) NULL) |
955 | 0 | (void) CloneString(&clone_info->font,type_info->glyphs); |
956 | 15.0k | status=RenderFreetype(image,clone_info,type_info->encoding,offset,metrics); |
957 | 15.0k | DestroyDrawInfo(clone_info); |
958 | 15.0k | return(status); |
959 | 41.7k | } |
960 | | |
961 | | /* |
962 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
963 | | % % |
964 | | % % |
965 | | % % |
966 | | + R e n d e r F r e e t y p e % |
967 | | % % |
968 | | % % |
969 | | % % |
970 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
971 | | % |
972 | | % Method RenderFreetype renders text on the image with a Truetype font. It |
973 | | % also returns the bounding box of the text relative to the image. |
974 | | % |
975 | | % The format of the RenderFreetype method is: |
976 | | % |
977 | | % unsigned int RenderFreetype(Image *image,DrawInfo *draw_info, |
978 | | % const char *encoding,const PointInfo *offset,TypeMetric *metrics) |
979 | | % |
980 | | % A description of each parameter follows: |
981 | | % |
982 | | % o status: Method RenderFreetype returns True if the text is rendered on the |
983 | | % image, otherwise False. |
984 | | % |
985 | | % o image: The image. |
986 | | % |
987 | | % o draw_info: The draw info. |
988 | | % |
989 | | % o encoding: The font encoding. |
990 | | % |
991 | | % o offset: (x,y) location of text relative to image. |
992 | | % |
993 | | % o metrics: bounding box of text. |
994 | | % |
995 | | % |
996 | | */ |
997 | | |
998 | | #if defined(HasTTF) |
999 | | static int TraceCubicBezier(FT_Vector *p,FT_Vector *q,FT_Vector *to, |
1000 | | DrawInfo *draw_info) |
1001 | 0 | { |
1002 | 0 | AffineMatrix |
1003 | 0 | affine; |
1004 | |
|
1005 | 0 | char |
1006 | 0 | path[MaxTextExtent]; |
1007 | |
|
1008 | 0 | affine=draw_info->affine; |
1009 | 0 | FormatString(path,"C%g,%g %g,%g %g,%g",affine.tx+p->x/64.0, |
1010 | 0 | affine.ty-p->y/64.0,affine.tx+q->x/64.0,affine.ty-q->y/64.0, |
1011 | 0 | affine.tx+to->x/64.0,affine.ty-to->y/64.0); |
1012 | 0 | (void) ConcatenateString(&draw_info->primitive,path); |
1013 | 0 | return(0); |
1014 | 0 | } |
1015 | | |
1016 | | static int TraceLineTo(FT_Vector *to,DrawInfo *draw_info) |
1017 | 0 | { |
1018 | 0 | AffineMatrix |
1019 | 0 | affine; |
1020 | |
|
1021 | 0 | char |
1022 | 0 | path[MaxTextExtent]; |
1023 | |
|
1024 | 0 | affine=draw_info->affine; |
1025 | 0 | FormatString(path,"L%g,%g",affine.tx+to->x/64.0,affine.ty-to->y/64.0); |
1026 | 0 | (void) ConcatenateString(&draw_info->primitive,path); |
1027 | 0 | return(0); |
1028 | 0 | } |
1029 | | |
1030 | | static int TraceMoveTo(FT_Vector *to,DrawInfo *draw_info) |
1031 | 0 | { |
1032 | 0 | AffineMatrix |
1033 | 0 | affine; |
1034 | |
|
1035 | 0 | char |
1036 | 0 | path[MaxTextExtent]; |
1037 | |
|
1038 | 0 | affine=draw_info->affine; |
1039 | 0 | FormatString(path,"M%g,%g",affine.tx+to->x/64.0,affine.ty-to->y/64.0); |
1040 | 0 | (void) ConcatenateString(&draw_info->primitive,path); |
1041 | 0 | return(0); |
1042 | 0 | } |
1043 | | |
1044 | | static int TraceQuadraticBezier(FT_Vector *control,FT_Vector *to, |
1045 | | DrawInfo *draw_info) |
1046 | 0 | { |
1047 | 0 | AffineMatrix |
1048 | 0 | affine; |
1049 | |
|
1050 | 0 | char |
1051 | 0 | path[MaxTextExtent]; |
1052 | |
|
1053 | 0 | affine=draw_info->affine; |
1054 | 0 | FormatString(path,"Q%g,%g %g,%g",affine.tx+control->x/64.0, |
1055 | 0 | affine.ty-control->y/64.0,affine.tx+to->x/64.0,affine.ty-to->y/64.0); |
1056 | 0 | (void) ConcatenateString(&draw_info->primitive,path); |
1057 | 0 | return(0); |
1058 | 0 | } |
1059 | | |
1060 | | static MagickPassFail RenderFreetype(Image *image,const DrawInfo *draw_info, |
1061 | | const char *encoding,const PointInfo *offset,TypeMetric *metrics) |
1062 | 16.8k | { |
1063 | 16.8k | typedef struct _GlyphInfo |
1064 | 16.8k | { |
1065 | 16.8k | FT_UInt |
1066 | 16.8k | id; |
1067 | | |
1068 | 16.8k | FT_Vector |
1069 | 16.8k | origin; |
1070 | | |
1071 | 16.8k | FT_Glyph |
1072 | 16.8k | image; |
1073 | 16.8k | } GlyphInfo; |
1074 | | |
1075 | 16.8k | double |
1076 | 16.8k | opacity; |
1077 | | |
1078 | 16.8k | DrawInfo |
1079 | 16.8k | *clone_info; |
1080 | | |
1081 | 16.8k | FT_BBox |
1082 | 16.8k | bounds; |
1083 | | |
1084 | 16.8k | FT_BitmapGlyph |
1085 | 16.8k | bitmap; |
1086 | | |
1087 | 16.8k | FT_Encoding |
1088 | 16.8k | encoding_type; |
1089 | | |
1090 | 16.8k | FT_Error |
1091 | 16.8k | ft_status; |
1092 | | |
1093 | 16.8k | FT_Face |
1094 | 16.8k | face; |
1095 | | |
1096 | 16.8k | FT_Library |
1097 | 16.8k | library; |
1098 | | |
1099 | 16.8k | FT_Matrix |
1100 | 16.8k | affine; |
1101 | | |
1102 | 16.8k | FT_Vector |
1103 | 16.8k | origin; |
1104 | | |
1105 | 16.8k | GlyphInfo |
1106 | 16.8k | glyph, |
1107 | 16.8k | last_glyph; |
1108 | | |
1109 | 16.8k | Image |
1110 | 16.8k | *pattern; |
1111 | | |
1112 | 16.8k | long |
1113 | 16.8k | y; |
1114 | | |
1115 | 16.8k | PixelPacket |
1116 | 16.8k | fill_color; |
1117 | | |
1118 | 16.8k | PointInfo |
1119 | 16.8k | point, |
1120 | 16.8k | resolution; |
1121 | | |
1122 | 16.8k | register long |
1123 | 16.8k | i, |
1124 | 16.8k | x; |
1125 | | |
1126 | 16.8k | register PixelPacket |
1127 | 16.8k | *q; |
1128 | | |
1129 | 16.8k | register unsigned char |
1130 | 16.8k | *p; |
1131 | | |
1132 | 16.8k | size_t |
1133 | 16.8k | length = 0; |
1134 | | |
1135 | 16.8k | static FT_Outline_Funcs |
1136 | 16.8k | OutlineMethods = |
1137 | 16.8k | { |
1138 | 16.8k | (FT_Outline_MoveTo_Func) TraceMoveTo, |
1139 | 16.8k | (FT_Outline_LineTo_Func) TraceLineTo, |
1140 | 16.8k | (FT_Outline_ConicTo_Func) TraceQuadraticBezier, |
1141 | 16.8k | (FT_Outline_CubicTo_Func) TraceCubicBezier, |
1142 | 16.8k | 0, 0 |
1143 | 16.8k | }; |
1144 | | |
1145 | 16.8k | MagickBool |
1146 | 16.8k | active; |
1147 | | |
1148 | 16.8k | magick_code_point_t |
1149 | 16.8k | *text; |
1150 | | |
1151 | 16.8k | MagickPassFail |
1152 | 16.8k | status=MagickPass; |
1153 | | |
1154 | 16.8k | if (draw_info->font == (char *) NULL) |
1155 | 10.1k | ThrowBinaryException(TypeError,FontNotSpecified,image->filename); |
1156 | | |
1157 | 6.77k | glyph.image=(FT_Glyph) 0; |
1158 | 6.77k | last_glyph.image=(FT_Glyph) 0; |
1159 | | |
1160 | | /* |
1161 | | Initialize Truetype library. |
1162 | | */ |
1163 | 6.77k | ft_status=FT_Init_FreeType(&library); |
1164 | 6.77k | if (ft_status) |
1165 | 0 | ThrowBinaryException(TypeError,UnableToInitializeFreetypeLibrary, |
1166 | 6.77k | draw_info->font); |
1167 | 6.77k | if (*draw_info->font != '@') |
1168 | 6.57k | ft_status=FT_New_Face(library,draw_info->font,0,&face); |
1169 | 208 | else |
1170 | 208 | ft_status=FT_New_Face(library,draw_info->font+1,0,&face); |
1171 | 6.77k | if (ft_status != 0) |
1172 | 6.77k | { |
1173 | 6.77k | (void) FT_Done_FreeType(library); |
1174 | 6.77k | ThrowBinaryException(TypeError,UnableToReadFont,draw_info->font) |
1175 | 6.77k | } |
1176 | | /* |
1177 | | Select a charmap |
1178 | | */ |
1179 | 0 | if (face->num_charmaps != 0) |
1180 | 0 | /* ft_status= */ (void) FT_Set_Charmap(face,face->charmaps[0]); |
1181 | 0 | encoding_type=ft_encoding_unicode; |
1182 | 0 | ft_status=FT_Select_Charmap(face,encoding_type); |
1183 | 0 | if (ft_status != 0) |
1184 | 0 | { |
1185 | 0 | encoding_type=ft_encoding_none; |
1186 | 0 | /* ft_status= */ (void) FT_Select_Charmap(face,encoding_type); |
1187 | 0 | } |
1188 | 0 | if (encoding != (char *) NULL) |
1189 | 0 | { |
1190 | 0 | if (LocaleCompare(encoding,"AdobeCustom") == 0) |
1191 | 0 | encoding_type=ft_encoding_adobe_custom; |
1192 | 0 | if (LocaleCompare(encoding,"AdobeExpert") == 0) |
1193 | 0 | encoding_type=ft_encoding_adobe_expert; |
1194 | 0 | if (LocaleCompare(encoding,"AdobeStandard") == 0) |
1195 | 0 | encoding_type=ft_encoding_adobe_standard; |
1196 | 0 | if (LocaleCompare(encoding,"AppleRoman") == 0) |
1197 | 0 | encoding_type=ft_encoding_apple_roman; |
1198 | 0 | if (LocaleCompare(encoding,"BIG5") == 0) |
1199 | 0 | encoding_type=ft_encoding_big5; |
1200 | 0 | if (LocaleCompare(encoding,"GB2312") == 0) |
1201 | 0 | encoding_type=ft_encoding_gb2312; |
1202 | 0 | #if defined(ft_encoding_johab) |
1203 | 0 | if (LocaleCompare(encoding,"Johab") == 0) |
1204 | 0 | encoding_type=ft_encoding_johab; |
1205 | 0 | #endif |
1206 | 0 | #if defined(ft_encoding_latin_1) |
1207 | 0 | if (LocaleCompare(encoding,"Latin-1") == 0) |
1208 | 0 | encoding_type=ft_encoding_latin_1; |
1209 | 0 | #endif |
1210 | 0 | #if defined(ft_encoding_latin_2) |
1211 | 0 | if (LocaleCompare(encoding,"Latin-2") == 0) |
1212 | 0 | encoding_type=ft_encoding_latin_2; |
1213 | 0 | #endif |
1214 | 0 | if (LocaleCompare(encoding,"None") == 0) |
1215 | 0 | encoding_type=ft_encoding_none; |
1216 | 0 | if (LocaleCompare(encoding,"SJIScode") == 0) |
1217 | 0 | encoding_type=ft_encoding_sjis; |
1218 | 0 | if (LocaleCompare(encoding,"Symbol") == 0) |
1219 | 0 | encoding_type=ft_encoding_symbol; |
1220 | 0 | if (LocaleCompare(encoding,"Unicode") == 0) |
1221 | 0 | encoding_type=ft_encoding_unicode; |
1222 | 0 | if (LocaleCompare(encoding,"Wansung") == 0) |
1223 | 0 | encoding_type=ft_encoding_wansung; |
1224 | 0 | ft_status=FT_Select_Charmap(face,encoding_type); |
1225 | 0 | if (ft_status != 0) |
1226 | 0 | ThrowBinaryException(TypeError,UnrecognizedFontEncoding,encoding); |
1227 | 0 | } |
1228 | | /* |
1229 | | Set text size. |
1230 | | */ |
1231 | 0 | resolution.x=72.0; |
1232 | 0 | resolution.y=72.0; |
1233 | 0 | if (draw_info->density != (char *) NULL) |
1234 | 0 | { |
1235 | 0 | i=GetMagickDimension(draw_info->density,&resolution.x,&resolution.y,NULL,NULL); |
1236 | 0 | if (i != 2) |
1237 | 0 | resolution.y=resolution.x; |
1238 | 0 | } |
1239 | 0 | (void) FT_Set_Char_Size(face,(FT_F26Dot6) (64.0*draw_info->pointsize), |
1240 | 0 | (FT_F26Dot6) (64.0*draw_info->pointsize),(FT_UInt) resolution.x, |
1241 | 0 | (FT_UInt) resolution.y); |
1242 | 0 | metrics->pixels_per_em.x=face->size->metrics.x_ppem; |
1243 | 0 | metrics->pixels_per_em.y=face->size->metrics.y_ppem; |
1244 | 0 | metrics->ascent=(double) face->size->metrics.ascender/64.0; |
1245 | 0 | metrics->descent=(double) face->size->metrics.descender/64.0; |
1246 | 0 | metrics->width=0; |
1247 | 0 | metrics->height=(double) face->size->metrics.height/64.0; |
1248 | 0 | metrics->max_advance=(double) face->size->metrics.max_advance/64.0; |
1249 | 0 | metrics->bounds.x1=0.0; |
1250 | 0 | metrics->bounds.y1=metrics->descent; |
1251 | 0 | metrics->bounds.x2=metrics->ascent+metrics->descent; |
1252 | 0 | metrics->bounds.y2=metrics->ascent+metrics->descent; |
1253 | 0 | metrics->underline_position=face->underline_position/64.0; |
1254 | 0 | metrics->underline_thickness=face->underline_thickness/64.0; |
1255 | | |
1256 | | /* |
1257 | | If the user-provided text string is NULL or empty, then nothing |
1258 | | more to do. |
1259 | | */ |
1260 | 0 | if ((draw_info->text == NULL) || (draw_info->text[0] == '\0')) |
1261 | 0 | { |
1262 | 0 | (void) FT_Done_Face(face); |
1263 | 0 | (void) FT_Done_FreeType(library); |
1264 | 0 | return status; |
1265 | 0 | } |
1266 | | |
1267 | | /* |
1268 | | Convert text to 4-byte format (supporting up to 21 code point |
1269 | | bits) as prescribed by the encoding. |
1270 | | */ |
1271 | 0 | switch (encoding_type) |
1272 | 0 | { |
1273 | 0 | case ft_encoding_sjis: |
1274 | 0 | { |
1275 | 0 | text=EncodeSJIS(draw_info->text,&length); |
1276 | 0 | break; |
1277 | 0 | } |
1278 | 0 | case ft_encoding_unicode: |
1279 | 0 | { |
1280 | 0 | text=EncodeUnicode(draw_info->text,&length); |
1281 | 0 | break; |
1282 | 0 | } |
1283 | 0 | default: |
1284 | 0 | { |
1285 | 0 | if (draw_info->encoding != (char *) NULL) |
1286 | 0 | { |
1287 | 0 | if (LocaleCompare(draw_info->encoding,"SJIS") == 0) |
1288 | 0 | { |
1289 | 0 | text=EncodeSJIS(draw_info->text,&length); |
1290 | 0 | break; |
1291 | 0 | } |
1292 | 0 | if ((LocaleCompare(draw_info->encoding,"UTF-8") == 0) || |
1293 | 0 | (encoding_type != ft_encoding_none)) |
1294 | 0 | { |
1295 | 0 | text=EncodeUnicode(draw_info->text,&length); |
1296 | 0 | break; |
1297 | 0 | } |
1298 | 0 | } |
1299 | 0 | text=EncodeText(draw_info->text,&length); |
1300 | 0 | break; |
1301 | 0 | } |
1302 | 0 | } |
1303 | 0 | if (text == (magick_code_point_t *) NULL) |
1304 | 0 | { |
1305 | 0 | (void) FT_Done_Face(face); |
1306 | 0 | (void) FT_Done_FreeType(library); |
1307 | 0 | (void) LogMagickEvent(AnnotateEvent,GetMagickModule(), |
1308 | 0 | "Text encoding failed: encoding_type=%ld " |
1309 | 0 | "draw_info->encoding=\"%s\" draw_info->text=\"%s\" length=%ld", |
1310 | 0 | (long) encoding_type, |
1311 | 0 | (draw_info->encoding ? draw_info->encoding : "(null)"), |
1312 | 0 | (draw_info->text ? draw_info->text : "(null)"), |
1313 | 0 | (long) length); |
1314 | 0 | ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed, |
1315 | 0 | draw_info->font) |
1316 | 0 | } |
1317 | | /* |
1318 | | Compute bounding box. |
1319 | | */ |
1320 | 0 | (void) LogMagickEvent(AnnotateEvent,GetMagickModule(), |
1321 | 0 | "Font %.1024s; font-encoding %.1024s; text-encoding %.1024s; pointsize %g", |
1322 | 0 | draw_info->font != (char *) NULL ? draw_info->font : "none", |
1323 | 0 | encoding != (char *) NULL ? encoding : "none", |
1324 | 0 | draw_info->encoding != (char *) NULL ? draw_info->encoding : "none", |
1325 | 0 | draw_info->pointsize); |
1326 | 0 | glyph.id=0; |
1327 | 0 | last_glyph.id=0; |
1328 | 0 | origin.x=0; |
1329 | 0 | origin.y=0; |
1330 | 0 | affine.xx=(FT_Fixed) (65536L*draw_info->affine.sx+0.5); |
1331 | 0 | affine.yx=(FT_Fixed) (-65536L*draw_info->affine.rx+0.5); |
1332 | 0 | affine.xy=(FT_Fixed) (-65536L*draw_info->affine.ry+0.5); |
1333 | 0 | affine.yy=(FT_Fixed) (65536L*draw_info->affine.sy+0.5); |
1334 | 0 | clone_info=CloneDrawInfo((ImageInfo *) NULL,draw_info); |
1335 | 0 | (void) QueryColorDatabase("#000000ff",&clone_info->fill,&image->exception); |
1336 | 0 | (void) CloneString(&clone_info->primitive,"path '"); |
1337 | 0 | pattern=draw_info->fill_pattern; |
1338 | 0 | for (i=0; i < (long) length; i++) |
1339 | 0 | { |
1340 | 0 | glyph.id=FT_Get_Char_Index(face,text[i]); |
1341 | 0 | if ((glyph.id != 0) && (last_glyph.id != 0) && FT_HAS_KERNING(face)) |
1342 | 0 | { |
1343 | 0 | FT_Vector |
1344 | 0 | kerning; |
1345 | |
|
1346 | 0 | (void) FT_Get_Kerning(face,last_glyph.id,glyph.id,ft_kerning_default, |
1347 | 0 | &kerning); |
1348 | 0 | origin.x+=kerning.x; |
1349 | 0 | } |
1350 | 0 | glyph.origin=origin; |
1351 | 0 | glyph.image=0; |
1352 | 0 | ft_status=FT_Load_Glyph(face,glyph.id,FT_LOAD_DEFAULT); |
1353 | 0 | if (ft_status != False) /* 0 means success */ |
1354 | 0 | continue; |
1355 | 0 | ft_status=FT_Get_Glyph(face->glyph,&glyph.image); |
1356 | 0 | if (ft_status != False) /* 0 means success */ |
1357 | 0 | continue; |
1358 | | #if 0 |
1359 | | /* |
1360 | | Obtain glyph's control box. Usually faster than computing the |
1361 | | exact bounding box but may be slightly larger in some |
1362 | | situations. |
1363 | | */ |
1364 | | (void) FT_Glyph_Get_CBox(glyph.image,FT_GLYPH_BBOX_SUBPIXELS,&bounds); |
1365 | | #else |
1366 | | /* |
1367 | | Compute exact bounding box for scaled outline. If necessary, the |
1368 | | outline Bezier arcs are walked over to extract their extrema. |
1369 | | */ |
1370 | 0 | (void) FT_Outline_Get_BBox(&((FT_OutlineGlyph) glyph.image)->outline,&bounds); |
1371 | 0 | #endif |
1372 | 0 | if ((i == 0) || (bounds.xMin < metrics->bounds.x1)) |
1373 | 0 | metrics->bounds.x1=bounds.xMin; |
1374 | 0 | if ((i == 0) || (bounds.yMin < metrics->bounds.y1)) |
1375 | 0 | metrics->bounds.y1=bounds.yMin; |
1376 | 0 | if ((i == 0) || (bounds.xMax > metrics->bounds.x2)) |
1377 | 0 | metrics->bounds.x2=bounds.xMax; |
1378 | 0 | if ((i == 0) || (bounds.yMax > metrics->bounds.y2)) |
1379 | 0 | metrics->bounds.y2=bounds.yMax; |
1380 | 0 | if (draw_info->render) |
1381 | 0 | if ((draw_info->stroke.opacity != TransparentOpacity) || |
1382 | 0 | (draw_info->stroke_pattern != (Image *) NULL)) |
1383 | 0 | { |
1384 | | /* |
1385 | | Trace the glyph. |
1386 | | */ |
1387 | 0 | clone_info->affine.tx=glyph.origin.x/64.0; |
1388 | 0 | clone_info->affine.ty=glyph.origin.y/64.0; |
1389 | 0 | (void) FT_Outline_Decompose(&((FT_OutlineGlyph) glyph.image)->outline, |
1390 | 0 | &OutlineMethods,clone_info); |
1391 | 0 | } |
1392 | 0 | FT_Vector_Transform(&glyph.origin,&affine); |
1393 | 0 | (void) FT_Glyph_Transform(glyph.image,&affine,&glyph.origin); |
1394 | 0 | if (draw_info->render) |
1395 | 0 | { |
1396 | 0 | status &= ModifyCache(image,&image->exception); |
1397 | 0 | if ((draw_info->fill.opacity != TransparentOpacity) || |
1398 | 0 | (pattern != (Image *) NULL)) |
1399 | 0 | { |
1400 | | /* |
1401 | | Rasterize the glyph. |
1402 | | */ |
1403 | 0 | ft_status=FT_Glyph_To_Bitmap(&glyph.image,ft_render_mode_normal, |
1404 | 0 | (FT_Vector *) NULL,True); |
1405 | 0 | if (ft_status != False) |
1406 | 0 | continue; |
1407 | 0 | bitmap=(FT_BitmapGlyph) glyph.image; |
1408 | 0 | image->storage_class=DirectClass; |
1409 | 0 | if (bitmap->bitmap.pixel_mode == ft_pixel_mode_mono) |
1410 | 0 | { |
1411 | 0 | point.x=offset->x+(origin.x >> 6); |
1412 | 0 | } |
1413 | 0 | else |
1414 | 0 | { |
1415 | 0 | point.x=offset->x+bitmap->left; |
1416 | 0 | } |
1417 | 0 | point.y=offset->y-bitmap->top; |
1418 | 0 | p=bitmap->bitmap.buffer; |
1419 | | /* FIXME: OpenMP */ |
1420 | 0 | for (y=0; y < (long) bitmap->bitmap.rows; y++) |
1421 | 0 | { |
1422 | 0 | int pc = y * bitmap->bitmap.pitch; |
1423 | 0 | int pcr = pc; |
1424 | 0 | if ((ceil(point.y+y-0.5) < 0) || |
1425 | 0 | (ceil(point.y+y-0.5) >= image->rows)) |
1426 | 0 | { |
1427 | 0 | continue; |
1428 | 0 | } |
1429 | | /* |
1430 | | Try to get whole span. May fail. |
1431 | | */ |
1432 | 0 | q=GetImagePixels(image,(long) ceil(point.x-0.5), |
1433 | 0 | (long) ceil(point.y+y-0.5),bitmap->bitmap.width,1); |
1434 | 0 | active=q != (PixelPacket *) NULL; |
1435 | 0 | for (x=0; x < (long) bitmap->bitmap.width; x++, pc++) |
1436 | 0 | { |
1437 | 0 | if (((long) ceil(point.x+x-0.5) < 0) || |
1438 | 0 | ((unsigned long) ceil(point.x+x-0.5) >= image->columns)) |
1439 | 0 | { |
1440 | 0 | if (active) |
1441 | 0 | q++; |
1442 | 0 | continue; |
1443 | 0 | } |
1444 | | /* 8-bit gray-level pixmap */ |
1445 | 0 | if (bitmap->bitmap.pixel_mode == ft_pixel_mode_grays) |
1446 | 0 | { |
1447 | 0 | if (draw_info->text_antialias) |
1448 | 0 | opacity=ScaleCharToQuantum((double) p[pc]); |
1449 | 0 | else |
1450 | 0 | opacity=(p[pc] < 127 ? OpaqueOpacity : TransparentOpacity); |
1451 | 0 | } |
1452 | | /* 1-bit monochrome bitmap */ |
1453 | 0 | else if (bitmap->bitmap.pixel_mode == ft_pixel_mode_mono) |
1454 | 0 | { |
1455 | 0 | opacity=((p[(x >> 3) + pcr] & (1 << (~x & 0x07))) ? |
1456 | 0 | TransparentOpacity : OpaqueOpacity); |
1457 | 0 | } |
1458 | 0 | else |
1459 | 0 | { |
1460 | 0 | continue; /* ignore it? */ |
1461 | 0 | } |
1462 | 0 | fill_color=draw_info->fill; |
1463 | 0 | if (pattern != (Image *) NULL) |
1464 | 0 | { |
1465 | 0 | if (AcquireOnePixelByReference |
1466 | 0 | (pattern,&fill_color, |
1467 | 0 | (long) (point.x+x-pattern->tile_info.x) % pattern->columns, |
1468 | 0 | (long) (point.y+y-pattern->tile_info.y) % pattern->rows, |
1469 | 0 | &image->exception) == MagickFail) |
1470 | 0 | { |
1471 | 0 | status=MagickFail; |
1472 | 0 | } |
1473 | 0 | } |
1474 | | /* |
1475 | | If not full span, then get one pixel. |
1476 | | */ |
1477 | 0 | if (!active) |
1478 | 0 | q=GetImagePixels(image,(long) ceil(point.x+x-0.5), |
1479 | 0 | (long) ceil(point.y+y-0.5),1,1); |
1480 | 0 | if (q == (PixelPacket *) NULL) |
1481 | 0 | { |
1482 | 0 | continue; |
1483 | 0 | } |
1484 | | /* |
1485 | | At this point, opacity is 0==transparent to MaxRGB==opaque, and represents an |
1486 | | anti-aliasing edge blending value. The computation below integrates in the |
1487 | | fill color opacity, and converts the result to 0=opaque to MaxRGB=transparent. |
1488 | | */ |
1489 | 0 | opacity=MaxRGB-(opacity*(MaxRGB-fill_color.opacity)+/*round*/(MaxRGB>>1))/MaxRGB; |
1490 | 0 | AlphaCompositePixel(q,&fill_color,opacity,q, |
1491 | 0 | image->matte ? q->opacity : OpaqueOpacity); |
1492 | 0 | if (!active) |
1493 | 0 | { |
1494 | | /* |
1495 | | Sync the one pixel |
1496 | | */ |
1497 | 0 | if (SyncImagePixels(image) != MagickPass) |
1498 | 0 | status=MagickFail; |
1499 | 0 | } |
1500 | 0 | else |
1501 | 0 | { |
1502 | 0 | q++; |
1503 | 0 | } |
1504 | 0 | if (status == MagickFail) |
1505 | 0 | break; |
1506 | 0 | } |
1507 | | /* |
1508 | | Sync the full span |
1509 | | */ |
1510 | 0 | if (active) |
1511 | 0 | if (SyncImagePixels(image) != MagickPass) |
1512 | 0 | status=MagickFail; |
1513 | 0 | if (status == MagickFail) |
1514 | 0 | break; |
1515 | 0 | } |
1516 | 0 | } |
1517 | 0 | } |
1518 | 0 | origin.x+=face->glyph->advance.x; |
1519 | 0 | if (origin.x > metrics->width) |
1520 | 0 | metrics->width=origin.x; |
1521 | 0 | if (last_glyph.image != 0) |
1522 | 0 | { |
1523 | 0 | FT_Done_Glyph(last_glyph.image); |
1524 | 0 | last_glyph.image=0; |
1525 | 0 | } |
1526 | 0 | last_glyph=glyph; |
1527 | 0 | } |
1528 | 0 | metrics->width/=64.0; |
1529 | 0 | metrics->bounds.x1/=64.0; |
1530 | 0 | metrics->bounds.y1/=64.0; |
1531 | 0 | metrics->bounds.x2/=64.0; |
1532 | 0 | metrics->bounds.y2/=64.0; |
1533 | 0 | if ((status != MagickFail)&& (draw_info->render)) |
1534 | 0 | if ((draw_info->stroke.opacity != TransparentOpacity) || |
1535 | 0 | (draw_info->stroke_pattern != (Image *) NULL)) |
1536 | 0 | { |
1537 | | /* |
1538 | | Draw text stroke. |
1539 | | */ |
1540 | 0 | clone_info->affine.tx=offset->x; |
1541 | 0 | clone_info->affine.ty=offset->y; |
1542 | 0 | (void) ConcatenateString(&clone_info->primitive,"'"); |
1543 | 0 | (void) DrawImage(image,clone_info); |
1544 | 0 | } |
1545 | 0 | if (glyph.image != 0) |
1546 | 0 | { |
1547 | 0 | FT_Done_Glyph(glyph.image); |
1548 | 0 | glyph.image=0; |
1549 | 0 | } |
1550 | | /* |
1551 | | Free resources. |
1552 | | */ |
1553 | 0 | MagickFreeMemory(text); |
1554 | 0 | DestroyDrawInfo(clone_info); |
1555 | 0 | (void) FT_Done_Face(face); |
1556 | 0 | (void) FT_Done_FreeType(library); |
1557 | 0 | return(status); |
1558 | 0 | } |
1559 | | #else |
1560 | | static unsigned int RenderFreetype(Image *image,const DrawInfo *draw_info, |
1561 | | const char *encoding,const PointInfo *offset, |
1562 | | TypeMetric *metrics) |
1563 | | { |
1564 | | ThrowBinaryException(MissingDelegateError,FreeTypeLibraryIsNotAvailable, |
1565 | | draw_info->font); |
1566 | | ARG_NOT_USED(encoding); |
1567 | | ARG_NOT_USED(offset); |
1568 | | ARG_NOT_USED(metrics); |
1569 | | } |
1570 | | #endif |
1571 | | |
1572 | | /* |
1573 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1574 | | % % |
1575 | | % % |
1576 | | % % |
1577 | | + R e n d e r P o s t s c r i p t % |
1578 | | % % |
1579 | | % % |
1580 | | % % |
1581 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1582 | | % |
1583 | | % Method RenderPostscript renders text on the image with a Postscript font. |
1584 | | % It also returns the bounding box of the text relative to the image. |
1585 | | % |
1586 | | % The format of the RenderPostscript method is: |
1587 | | % |
1588 | | % unsigned int RenderPostscript(Image *image,DrawInfo *draw_info, |
1589 | | % const PointInfo *offset,TypeMetric *metrics) |
1590 | | % |
1591 | | % A description of each parameter follows: |
1592 | | % |
1593 | | % o status: Method RenderPostscript returns True if the text is rendered on |
1594 | | % the image, otherwise False. |
1595 | | % |
1596 | | % o image: The image. |
1597 | | % |
1598 | | % o draw_info: The draw info. |
1599 | | % |
1600 | | % o offset: (x,y) location of text relative to image. |
1601 | | % |
1602 | | % o metrics: bounding box of text. |
1603 | | % |
1604 | | % |
1605 | | */ |
1606 | | |
1607 | | static char *EscapeParenthesis(const char *source) |
1608 | 26.6k | { |
1609 | 26.6k | char |
1610 | 26.6k | *destination; |
1611 | | |
1612 | 26.6k | register char |
1613 | 26.6k | *q; |
1614 | | |
1615 | 26.6k | register const char |
1616 | 26.6k | *p; |
1617 | | |
1618 | 26.6k | size_t |
1619 | 26.6k | length; |
1620 | | |
1621 | 26.6k | assert(source != (const char *) NULL); |
1622 | | |
1623 | | /* |
1624 | | Use dry-run method to compute required string length. |
1625 | | */ |
1626 | 26.6k | length=0; |
1627 | 4.92M | for (p=source; *p; p++) |
1628 | 4.89M | { |
1629 | 4.89M | if ((*p == '(') || (*p == ')')) |
1630 | 10.2k | length++; |
1631 | 4.89M | length++; |
1632 | 4.89M | } |
1633 | 26.6k | destination=MagickAllocateMemory(char *,length+1); |
1634 | 26.6k | if (destination == (char *) NULL) |
1635 | 0 | MagickFatalError3(ResourceLimitFatalError,MemoryAllocationFailed, |
1636 | 26.6k | UnableToEscapeString); |
1637 | 26.6k | *destination='\0'; |
1638 | 26.6k | q=destination; |
1639 | 4.92M | for (p=source; *p; p++) |
1640 | 4.89M | { |
1641 | 4.89M | if ((*p == '(') || (*p == ')')) |
1642 | 10.2k | *q++= '\\'; |
1643 | 4.89M | *q++=(*p); |
1644 | 4.89M | } |
1645 | 26.6k | *q=0; |
1646 | 26.6k | return(destination); |
1647 | 26.6k | } |
1648 | | |
1649 | | static MagickPassFail RenderPostscript(Image *image,const DrawInfo *draw_info, |
1650 | | const PointInfo *offset,TypeMetric *metrics) |
1651 | 26.6k | { |
1652 | 26.6k | char |
1653 | 26.6k | filename[MaxTextExtent], |
1654 | 26.6k | geometry[MaxTextExtent], |
1655 | 26.6k | *text; |
1656 | | |
1657 | 26.6k | FILE |
1658 | 26.6k | *file; |
1659 | | |
1660 | 26.6k | Image |
1661 | 26.6k | *annotate_image, |
1662 | 26.6k | *pattern; |
1663 | | |
1664 | 26.6k | ImageInfo |
1665 | 26.6k | *clone_info; |
1666 | | |
1667 | 26.6k | long |
1668 | 26.6k | y; |
1669 | | |
1670 | 26.6k | PointInfo |
1671 | 26.6k | extent, |
1672 | 26.6k | point, |
1673 | 26.6k | resolution; |
1674 | | |
1675 | 26.6k | register long |
1676 | 26.6k | i, |
1677 | 26.6k | x; |
1678 | | |
1679 | 26.6k | register PixelPacket |
1680 | 26.6k | *q; |
1681 | | |
1682 | 26.6k | unsigned int |
1683 | 26.6k | identity; |
1684 | | |
1685 | | /* |
1686 | | Render label with a Postscript font. |
1687 | | */ |
1688 | 26.6k | (void) LogMagickEvent(AnnotateEvent,GetMagickModule(), |
1689 | 26.6k | "Font %.1024s; pointsize %g",draw_info->font != (char *) NULL ? |
1690 | 14.8k | draw_info->font : "none",draw_info->pointsize); |
1691 | 26.6k | file=AcquireTemporaryFileStream(filename,BinaryFileIOMode); |
1692 | 26.6k | if (file == (FILE *) NULL) |
1693 | 26.6k | ThrowBinaryException(FileOpenError,UnableToCreateTemporaryFile,filename); |
1694 | 26.6k | (void) fprintf(file,"%%!PS-Adobe-3.0\n"); |
1695 | 26.6k | (void) fprintf(file,"/ReencodeType\n"); |
1696 | 26.6k | (void) fprintf(file,"{\n"); |
1697 | 26.6k | (void) fprintf(file," findfont dup length\n"); |
1698 | 26.6k | (void) fprintf(file, |
1699 | 26.6k | " dict begin { 1 index /FID ne {def} {pop pop} ifelse } forall\n"); |
1700 | 26.6k | (void) fprintf(file, |
1701 | 26.6k | " /Encoding ISOLatin1Encoding def currentdict end definefont pop\n"); |
1702 | 26.6k | (void) fprintf(file,"} bind def\n"); |
1703 | | /* |
1704 | | Sample to compute bounding box. |
1705 | | */ |
1706 | 26.6k | identity=(draw_info->affine.sx == draw_info->affine.sy) && |
1707 | 22.3k | (draw_info->affine.rx == 0.0) && (draw_info->affine.ry == 0.0); |
1708 | 26.6k | extent.x=0.0; |
1709 | 26.6k | extent.y=0.0; |
1710 | 5.00M | for (i=0; i <= (long) (strlen(draw_info->text)+2); i++) |
1711 | 4.97M | { |
1712 | 4.97M | point.x=fabs(draw_info->affine.sx*i*draw_info->pointsize+ |
1713 | 4.97M | draw_info->affine.ry*2.0*draw_info->pointsize); |
1714 | 4.97M | point.y=fabs(draw_info->affine.rx*i*draw_info->pointsize+ |
1715 | 4.97M | draw_info->affine.sy*2.0*draw_info->pointsize); |
1716 | 4.97M | if (point.x > extent.x) |
1717 | 4.93M | extent.x=point.x; |
1718 | 4.97M | if (point.y > extent.y) |
1719 | 535k | extent.y=point.y; |
1720 | 4.97M | } |
1721 | 26.6k | (void) fprintf(file,"%g %g moveto\n",identity ? 0.0 : extent.x/2.0, |
1722 | 26.6k | extent.y/2.0); |
1723 | 26.6k | (void) fprintf(file,"%g %g scale\n",draw_info->pointsize, |
1724 | 26.6k | draw_info->pointsize); |
1725 | 26.6k | if ((draw_info->font == (char *) NULL) || (*draw_info->font == '\0')) |
1726 | 16.7k | (void) fprintf(file, |
1727 | 16.7k | "/Times-Roman-ISO dup /Times-Roman ReencodeType findfont setfont\n"); |
1728 | 9.92k | else |
1729 | 9.92k | (void) fprintf(file, |
1730 | 9.92k | "/%.1024s-ISO dup /%.1024s ReencodeType findfont setfont\n", |
1731 | 9.92k | draw_info->font,draw_info->font); |
1732 | 26.6k | (void) fprintf(file,"[%g %g %g %g 0 0] concat\n",draw_info->affine.sx, |
1733 | 26.6k | -draw_info->affine.rx,-draw_info->affine.ry,draw_info->affine.sy); |
1734 | 26.6k | text=EscapeParenthesis(draw_info->text); |
1735 | 26.6k | if (!identity) |
1736 | 14.0k | (void) fprintf(file,"(%.1024s) stringwidth pop -0.5 mul -0.5 rmoveto\n", |
1737 | 14.0k | text); |
1738 | 26.6k | (void) fprintf(file,"(%.1024s) show\n",text); |
1739 | 26.6k | MagickFreeMemory(text); |
1740 | 26.6k | (void) fprintf(file,"showpage\n"); |
1741 | 26.6k | (void) fclose(file); |
1742 | 26.6k | FormatString(geometry,"%ldx%ld+0+0!",(long) ceil(extent.x-0.5), |
1743 | 26.6k | (long) ceil(extent.y-0.5)); |
1744 | 26.6k | clone_info=CloneImageInfo((ImageInfo *) NULL); |
1745 | 26.6k | (void) FormatString(clone_info->filename,"ps:%.1024s",filename); |
1746 | 26.6k | (void) CloneString(&clone_info->page,geometry); |
1747 | 26.6k | if (draw_info->density != (char *) NULL) |
1748 | 0 | (void) CloneString(&clone_info->density,draw_info->density); |
1749 | 26.6k | clone_info->antialias=draw_info->text_antialias; |
1750 | 26.6k | annotate_image=ReadImage(clone_info,&image->exception); |
1751 | 26.6k | if (image->exception.severity != UndefinedException) |
1752 | 26.6k | MagickError2(image->exception.severity,image->exception.reason, |
1753 | 26.6k | image->exception.description); |
1754 | 26.6k | DestroyImageInfo(clone_info); |
1755 | 26.6k | (void) LiberateTemporaryFile(filename); |
1756 | 26.6k | if (annotate_image == (Image *) NULL) |
1757 | 26.6k | return(False); |
1758 | 0 | resolution.x=72.0; |
1759 | 0 | resolution.y=72.0; |
1760 | 0 | if (draw_info->density != (char *) NULL) |
1761 | 0 | { |
1762 | 0 | int |
1763 | 0 | count; |
1764 | |
|
1765 | 0 | count=GetMagickDimension(draw_info->density,&resolution.x,&resolution.y,NULL,NULL); |
1766 | 0 | if (count != 2) |
1767 | 0 | resolution.y=resolution.x; |
1768 | 0 | } |
1769 | 0 | if (!identity) |
1770 | 0 | TransformImage(&annotate_image,"0x0",(char *) NULL); |
1771 | 0 | else |
1772 | 0 | { |
1773 | 0 | RectangleInfo |
1774 | 0 | crop_info; |
1775 | |
|
1776 | 0 | crop_info=GetImageBoundingBox(annotate_image,&annotate_image->exception); |
1777 | 0 | crop_info.height=(unsigned long) ceil((resolution.y/72.0)* |
1778 | 0 | ExpandAffine(&draw_info->affine)*draw_info->pointsize-0.5); |
1779 | 0 | crop_info.y=(long) ceil((resolution.y/72.0)*extent.y/8.0-0.5); |
1780 | 0 | (void) FormatString(geometry,"%lux%lu%+ld%+ld",crop_info.width, |
1781 | 0 | crop_info.height,crop_info.x,crop_info.y); |
1782 | 0 | TransformImage(&annotate_image,geometry,(char *) NULL); |
1783 | 0 | } |
1784 | 0 | metrics->pixels_per_em.x=(resolution.y/72.0)* |
1785 | 0 | ExpandAffine(&draw_info->affine)*draw_info->pointsize; |
1786 | 0 | metrics->pixels_per_em.y=metrics->pixels_per_em.x; |
1787 | 0 | metrics->ascent=metrics->pixels_per_em.x; |
1788 | 0 | metrics->descent=metrics->pixels_per_em.y/-5.0; |
1789 | 0 | metrics->width=annotate_image->columns/ExpandAffine(&draw_info->affine); |
1790 | 0 | metrics->height=1.152*metrics->pixels_per_em.x; |
1791 | 0 | metrics->max_advance=metrics->pixels_per_em.x; |
1792 | 0 | metrics->bounds.x1=0.0; |
1793 | 0 | metrics->bounds.y1=metrics->descent; |
1794 | 0 | metrics->bounds.x2=metrics->ascent+metrics->descent; |
1795 | 0 | metrics->bounds.y2=metrics->ascent+metrics->descent; |
1796 | 0 | metrics->underline_position=(-2.0); |
1797 | 0 | metrics->underline_thickness=1.0; |
1798 | 0 | if (!draw_info->render) |
1799 | 0 | { |
1800 | 0 | DestroyImage(annotate_image); |
1801 | 0 | return(True); |
1802 | 0 | } |
1803 | 0 | if (draw_info->fill.opacity != TransparentOpacity) |
1804 | 0 | { |
1805 | 0 | PixelPacket |
1806 | 0 | fill_color; |
1807 | | |
1808 | | /* |
1809 | | Render fill color. |
1810 | | */ |
1811 | 0 | (void) SetImageType(annotate_image,TrueColorMatteType); |
1812 | 0 | fill_color=draw_info->fill; |
1813 | 0 | pattern=draw_info->fill_pattern; |
1814 | 0 | for (y=0; y < (long) annotate_image->rows; y++) |
1815 | 0 | { |
1816 | 0 | q=GetImagePixels(annotate_image,0,y,annotate_image->columns,1); |
1817 | 0 | if (q == (PixelPacket *) NULL) |
1818 | 0 | break; |
1819 | 0 | for (x=0; x < (long) annotate_image->columns; x++) |
1820 | 0 | { |
1821 | 0 | if (pattern != (Image *) NULL) |
1822 | 0 | (void) AcquireOnePixelByReference(pattern,&fill_color, |
1823 | 0 | (long) (x-pattern->tile_info.x) % pattern->columns, |
1824 | 0 | (long) (y-pattern->tile_info.y) % pattern->rows, |
1825 | 0 | &image->exception); |
1826 | 0 | q->opacity=(Quantum) (MaxRGB-(((MaxRGB-(double) |
1827 | 0 | PixelIntensityToQuantum(q))*(MaxRGB-fill_color.opacity))/ |
1828 | 0 | MaxRGB)+0.5); |
1829 | 0 | q->red=fill_color.red; |
1830 | 0 | q->green=fill_color.green; |
1831 | 0 | q->blue=fill_color.blue; |
1832 | 0 | q++; |
1833 | 0 | } |
1834 | 0 | if (!SyncImagePixels(annotate_image)) |
1835 | 0 | break; |
1836 | 0 | } |
1837 | 0 | (void) CompositeImage(image,OverCompositeOp,annotate_image,(long) |
1838 | 0 | ceil(offset->x-0.5),(long) ceil(offset->y-(metrics->ascent+ |
1839 | 0 | metrics->descent)-0.5)); |
1840 | 0 | } |
1841 | 0 | DestroyImage(annotate_image); |
1842 | 0 | return(MagickPass); |
1843 | 0 | } |
1844 | | |
1845 | | /* |
1846 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1847 | | % % |
1848 | | % % |
1849 | | % % |
1850 | | + R e n d e r X 1 1 % |
1851 | | % % |
1852 | | % % |
1853 | | % % |
1854 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1855 | | % |
1856 | | % Method RenderX11 renders text on the image with an X11 font. It also |
1857 | | % returns the bounding box of the text relative to the image. |
1858 | | % |
1859 | | % The format of the RenderX11 method is: |
1860 | | % |
1861 | | % unsigned int RenderX11(Image *image,DrawInfo *draw_info, |
1862 | | % const PointInfo *offset,TypeMetric *metrics) |
1863 | | % |
1864 | | % A description of each parameter follows: |
1865 | | % |
1866 | | % o status: Method RenderX11 returns True if the text is rendered on the |
1867 | | % image, otherwise False. |
1868 | | % |
1869 | | % o image: The image. |
1870 | | % |
1871 | | % o draw_info: The draw info. |
1872 | | % |
1873 | | % o offset: (x,y) location of text relative to image. |
1874 | | % |
1875 | | % o metrics: bounding box of text. |
1876 | | % |
1877 | | % |
1878 | | */ |
1879 | | #if defined(HasX11) |
1880 | | static MagickPassFail RenderX11(Image *image,const DrawInfo *draw_info, |
1881 | | const PointInfo *offset,TypeMetric *metrics) |
1882 | | { |
1883 | | static DrawInfo |
1884 | | cache_info; |
1885 | | |
1886 | | static Display |
1887 | | *display = (Display *) NULL; |
1888 | | |
1889 | | static MagickXAnnotateInfo |
1890 | | annotate_info; |
1891 | | |
1892 | | static XFontStruct |
1893 | | *font_info; |
1894 | | |
1895 | | static MagickXPixelInfo |
1896 | | pixel; |
1897 | | |
1898 | | static MagickXResourceInfo |
1899 | | resource_info; |
1900 | | |
1901 | | static XrmDatabase |
1902 | | resource_database; |
1903 | | |
1904 | | static XStandardColormap |
1905 | | *map_info; |
1906 | | |
1907 | | static XVisualInfo |
1908 | | *visual_info; |
1909 | | |
1910 | | MagickPassFail |
1911 | | status; |
1912 | | |
1913 | | unsigned long |
1914 | | height, |
1915 | | width; |
1916 | | |
1917 | | if (display == (Display *) NULL) |
1918 | | { |
1919 | | const char |
1920 | | *client_name; |
1921 | | |
1922 | | /* |
1923 | | Open X server connection. |
1924 | | */ |
1925 | | display=XOpenDisplay(draw_info->server_name); |
1926 | | if (display == (Display *) NULL) |
1927 | | ThrowBinaryException(XServerError,UnableToOpenXServer, |
1928 | | draw_info->server_name); |
1929 | | /* |
1930 | | Get user defaults from X resource database. |
1931 | | */ |
1932 | | (void) XSetErrorHandler(MagickXError); |
1933 | | client_name=GetClientName(); |
1934 | | resource_database=MagickXGetResourceDatabase(display,client_name); |
1935 | | MagickXGetResourceInfo(resource_database,client_name,&resource_info); |
1936 | | resource_info.close_server=False; |
1937 | | resource_info.colormap=PrivateColormap; |
1938 | | resource_info.font=AllocateString(draw_info->font); |
1939 | | resource_info.background_color=AllocateString("#ffffffffffff"); |
1940 | | resource_info.foreground_color=AllocateString("#000000000000"); |
1941 | | map_info=XAllocStandardColormap(); |
1942 | | if (map_info == (XStandardColormap *) NULL) |
1943 | | ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed, |
1944 | | UnableToAllocateColormap); |
1945 | | /* |
1946 | | Initialize visual info. |
1947 | | */ |
1948 | | visual_info=MagickXBestVisualInfo(display,map_info,&resource_info); |
1949 | | if (visual_info == (XVisualInfo *) NULL) |
1950 | | ThrowBinaryException(XServerError,UnableToGetVisual, |
1951 | | draw_info->server_name); |
1952 | | map_info->colormap=(Colormap) NULL; |
1953 | | pixel.pixels=(unsigned long *) NULL; |
1954 | | /* |
1955 | | Initialize Standard Colormap info. |
1956 | | */ |
1957 | | MagickXGetMapInfo(visual_info,XDefaultColormap(display,visual_info->screen), |
1958 | | map_info); |
1959 | | MagickXGetPixelPacket(display,visual_info,map_info,&resource_info, |
1960 | | (Image *) NULL,&pixel); |
1961 | | pixel.annotate_context=XDefaultGC(display,visual_info->screen); |
1962 | | /* |
1963 | | Initialize font info. |
1964 | | */ |
1965 | | font_info=MagickXBestFont(display,&resource_info,False); |
1966 | | if (font_info == (XFontStruct *) NULL) |
1967 | | ThrowBinaryException(XServerError,UnableToLoadFont,draw_info->font); |
1968 | | cache_info=(*draw_info); |
1969 | | } |
1970 | | /* |
1971 | | Initialize annotate info. |
1972 | | */ |
1973 | | MagickXGetAnnotateInfo(&annotate_info); |
1974 | | annotate_info.stencil=ForegroundStencil; |
1975 | | if (cache_info.font != draw_info->font) |
1976 | | { |
1977 | | /* |
1978 | | Type name has changed. |
1979 | | */ |
1980 | | (void) XFreeFont(display,font_info); |
1981 | | (void) CloneString(&resource_info.font,draw_info->font); |
1982 | | font_info=MagickXBestFont(display,&resource_info,False); |
1983 | | if (font_info == (XFontStruct *) NULL) |
1984 | | ThrowBinaryException(XServerError,UnableToLoadFont,draw_info->font); |
1985 | | } |
1986 | | (void) LogMagickEvent(AnnotateEvent,GetMagickModule(), |
1987 | | "Font %.1024s; pointsize %g",draw_info->font != (char *) NULL ? |
1988 | | draw_info->font : "none",draw_info->pointsize); |
1989 | | cache_info=(*draw_info); |
1990 | | annotate_info.font_info=font_info; |
1991 | | annotate_info.text=(char *) draw_info->text; |
1992 | | annotate_info.width=XTextWidth(font_info,draw_info->text, |
1993 | | (int) strlen(draw_info->text)); |
1994 | | annotate_info.height=font_info->ascent+font_info->descent; |
1995 | | metrics->pixels_per_em.x=font_info->max_bounds.width; |
1996 | | metrics->pixels_per_em.y=font_info->max_bounds.width; |
1997 | | metrics->ascent=font_info->ascent; |
1998 | | metrics->descent=(-font_info->descent); |
1999 | | metrics->width=annotate_info.width/ExpandAffine(&draw_info->affine); |
2000 | | metrics->height=metrics->pixels_per_em.x+4; |
2001 | | metrics->max_advance=font_info->max_bounds.width; |
2002 | | metrics->bounds.x1=0.0; |
2003 | | metrics->bounds.y1=metrics->descent; |
2004 | | metrics->bounds.x2=metrics->ascent+metrics->descent; |
2005 | | metrics->bounds.y2=metrics->ascent+metrics->descent; |
2006 | | metrics->underline_position=(-2.0); |
2007 | | metrics->underline_thickness=1.0; |
2008 | | if (!draw_info->render) |
2009 | | return(MagickPass); |
2010 | | if (draw_info->fill.opacity == TransparentOpacity) |
2011 | | return(MagickPass); |
2012 | | /* |
2013 | | Render fill color. |
2014 | | */ |
2015 | | width=annotate_info.width; |
2016 | | height=annotate_info.height; |
2017 | | if ((draw_info->affine.rx != 0.0) || (draw_info->affine.ry != 0.0)) |
2018 | | { |
2019 | | if (((draw_info->affine.sx-draw_info->affine.sy) == 0.0) && |
2020 | | ((draw_info->affine.rx+draw_info->affine.ry) == 0.0)) |
2021 | | annotate_info.degrees=(180.0/MagickPI)* |
2022 | | atan2(draw_info->affine.rx,draw_info->affine.sx); |
2023 | | } |
2024 | | FormatString(annotate_info.geometry,"%lux%lu+%ld+%ld",width,height, |
2025 | | (long) ceil(offset->x-0.5), |
2026 | | (long) ceil(offset->y-metrics->ascent-metrics->descent-0.5)); |
2027 | | pixel.pen_color.red=ScaleQuantumToShort(draw_info->fill.red); |
2028 | | pixel.pen_color.green=ScaleQuantumToShort(draw_info->fill.green); |
2029 | | pixel.pen_color.blue=ScaleQuantumToShort(draw_info->fill.blue); |
2030 | | status=MagickXAnnotateImage(display,&pixel,&annotate_info,image); |
2031 | | if (status == 0) |
2032 | | ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed, |
2033 | | UnableToAnnotateImage); |
2034 | | return(MagickPass); |
2035 | | } |
2036 | | #else |
2037 | | static MagickPassFail RenderX11(Image *image,const DrawInfo *draw_info, |
2038 | | const PointInfo *offset,TypeMetric *metrics) |
2039 | 276 | { |
2040 | 276 | ARG_NOT_USED(offset); |
2041 | 276 | ARG_NOT_USED(metrics); |
2042 | 276 | ThrowBinaryException(MissingDelegateError,XWindowLibraryIsNotAvailable, |
2043 | 276 | draw_info->font); |
2044 | 0 | } |
2045 | | #endif |