blob: 50c2110db5dfcfb146a484daeae9ce3b05f6fb67 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% AAA N N N N OOO TTTTT AAA TTTTT EEEEE %
7% A A NN N NN N O O T A A T E %
8% AAAAA N N N N N N O O T AAAAA T EEE %
9% A A N NN N NN O O T A A T E %
10% A A N N N N OOO T A A T EEEEE %
11% %
12% %
13% MagickCore Image Annotation Methods %
14% %
15% Software Design %
cristyde984cd2013-12-01 14:49:27 +000016% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000017% July 1992 %
18% %
19% %
Cristyf6ff9ea2016-12-05 09:53:35 -050020% Copyright 1999-2017 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36% Digital Applications (www.digapp.com) contributed the stroked text algorithm.
37% It was written by Leonard Rosenthol.
38%
39%
40*/
41
42/*
43 Include declarations.
44*/
cristy4c08aed2011-07-01 19:47:50 +000045#include "MagickCore/studio.h"
46#include "MagickCore/annotate.h"
cristy5ff4eaf2011-09-03 01:38:02 +000047#include "MagickCore/annotate-private.h"
cristy4c08aed2011-07-01 19:47:50 +000048#include "MagickCore/attribute.h"
Cristyba4ad2d2016-06-07 09:15:26 -040049#include "MagickCore/cache-private.h"
cristy4c08aed2011-07-01 19:47:50 +000050#include "MagickCore/cache-view.h"
cristyc9afe162013-05-28 16:07:26 +000051#include "MagickCore/channel.h"
cristy4c08aed2011-07-01 19:47:50 +000052#include "MagickCore/client.h"
53#include "MagickCore/color.h"
54#include "MagickCore/color-private.h"
cristy54b92622012-04-09 18:42:06 +000055#include "MagickCore/colorspace-private.h"
cristy4c08aed2011-07-01 19:47:50 +000056#include "MagickCore/composite.h"
57#include "MagickCore/composite-private.h"
58#include "MagickCore/constitute.h"
59#include "MagickCore/draw.h"
60#include "MagickCore/draw-private.h"
cristy3644bdf2012-08-23 09:07:08 +000061#include "MagickCore/enhance.h"
cristy4c08aed2011-07-01 19:47:50 +000062#include "MagickCore/exception.h"
63#include "MagickCore/exception-private.h"
64#include "MagickCore/gem.h"
65#include "MagickCore/geometry.h"
66#include "MagickCore/image-private.h"
67#include "MagickCore/log.h"
68#include "MagickCore/quantum.h"
69#include "MagickCore/quantum-private.h"
70#include "MagickCore/pixel-accessor.h"
71#include "MagickCore/property.h"
72#include "MagickCore/resource_.h"
73#include "MagickCore/semaphore.h"
74#include "MagickCore/statistic.h"
75#include "MagickCore/string_.h"
Cristy17f8c722016-01-31 09:42:56 -050076#include "MagickCore/token.h"
cristy4c08aed2011-07-01 19:47:50 +000077#include "MagickCore/token-private.h"
dirk06f59012016-03-13 20:51:16 +010078#include "MagickCore/transform-private.h"
cristy4c08aed2011-07-01 19:47:50 +000079#include "MagickCore/type.h"
80#include "MagickCore/utility.h"
cristyd1dd6e42011-09-04 01:46:08 +000081#include "MagickCore/utility-private.h"
cristybcbda3f2011-09-03 13:01:22 +000082#include "MagickCore/xwindow.h"
cristy4c08aed2011-07-01 19:47:50 +000083#include "MagickCore/xwindow-private.h"
cristy3ed852e2009-09-05 21:47:34 +000084#if defined(MAGICKCORE_FREETYPE_DELEGATE)
cristy07a3cca2012-12-10 13:09:10 +000085#if defined(__MINGW32__) || defined(__MINGW64__)
cristy3ed852e2009-09-05 21:47:34 +000086# undef interface
87#endif
cristy03f187e2013-01-24 00:22:19 +000088#include <ft2build.h>
cristy3ed852e2009-09-05 21:47:34 +000089#if defined(FT_FREETYPE_H)
90# include FT_FREETYPE_H
91#else
92# include <freetype/freetype.h>
93#endif
94#if defined(FT_GLYPH_H)
95# include FT_GLYPH_H
96#else
97# include <freetype/ftglyph.h>
98#endif
99#if defined(FT_OUTLINE_H)
100# include FT_OUTLINE_H
101#else
102# include <freetype/ftoutln.h>
103#endif
104#if defined(FT_BBOX_H)
105# include FT_BBOX_H
106#else
107# include <freetype/ftbbox.h>
108#endif /* defined(FT_BBOX_H) */
109#endif
Cristy17f8c722016-01-31 09:42:56 -0500110#if defined(MAGICKCORE_RAQM_DELEGATE)
Cristy69a0d3b2015-12-22 20:40:36 -0500111#include <raqm.h>
Cristyea866ca2016-02-05 15:47:42 -0500112#endif
Cristy17f8c722016-01-31 09:42:56 -0500113typedef struct _GraphemeInfo
Cristy69a0d3b2015-12-22 20:40:36 -0500114{
Cristy87dc5e42016-02-05 18:52:57 -0500115 size_t
Cristy69a0d3b2015-12-22 20:40:36 -0500116 index,
117 x_offset,
118 x_advance,
119 y_offset;
120
Cristy87dc5e42016-02-05 18:52:57 -0500121 size_t
Cristy69a0d3b2015-12-22 20:40:36 -0500122 cluster;
Cristy17f8c722016-01-31 09:42:56 -0500123} GraphemeInfo;
Cristy5eb36082016-02-05 15:37:09 -0500124
cristy3ed852e2009-09-05 21:47:34 +0000125/*
cristyd7ecaca2011-01-24 14:14:53 +0000126 Annotate semaphores.
127*/
128static SemaphoreInfo
129 *annotate_semaphore = (SemaphoreInfo *) NULL;
130
131/*
cristy3ed852e2009-09-05 21:47:34 +0000132 Forward declarations.
133*/
134static MagickBooleanType
cristy5cbc0162011-08-29 00:36:28 +0000135 RenderType(Image *,const DrawInfo *,const PointInfo *,TypeMetric *,
136 ExceptionInfo *),
137 RenderPostscript(Image *,const DrawInfo *,const PointInfo *,TypeMetric *,
138 ExceptionInfo *),
cristy3ed852e2009-09-05 21:47:34 +0000139 RenderFreetype(Image *,const DrawInfo *,const char *,const PointInfo *,
cristy5cbc0162011-08-29 00:36:28 +0000140 TypeMetric *,ExceptionInfo *),
141 RenderX11(Image *,const DrawInfo *,const PointInfo *,TypeMetric *,
142 ExceptionInfo *);
cristy3ed852e2009-09-05 21:47:34 +0000143
144/*
145%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
146% %
147% %
148% %
cristyd7ecaca2011-01-24 14:14:53 +0000149+ A n n o t a t e C o m p o n e n t G e n e s i s %
150% %
151% %
152% %
153%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
154%
155% AnnotateComponentGenesis() instantiates the annotate component.
156%
157% The format of the AnnotateComponentGenesis method is:
158%
159% MagickBooleanType AnnotateComponentGenesis(void)
160%
161*/
cristy5ff4eaf2011-09-03 01:38:02 +0000162MagickPrivate MagickBooleanType AnnotateComponentGenesis(void)
cristyd7ecaca2011-01-24 14:14:53 +0000163{
cristy7c977062014-04-04 14:05:53 +0000164 if (annotate_semaphore == (SemaphoreInfo *) NULL)
165 annotate_semaphore=AcquireSemaphoreInfo();
cristyd7ecaca2011-01-24 14:14:53 +0000166 return(MagickTrue);
167}
168
169/*
170%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
171% %
172% %
173% %
174+ A n n o t a t e C o m p o n e n t T e r m i n u s %
175% %
176% %
177% %
178%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
179%
180% AnnotateComponentTerminus() destroys the annotate component.
181%
182% The format of the AnnotateComponentTerminus method is:
183%
184% AnnotateComponentTerminus(void)
185%
186*/
cristy5ff4eaf2011-09-03 01:38:02 +0000187MagickPrivate void AnnotateComponentTerminus(void)
cristyd7ecaca2011-01-24 14:14:53 +0000188{
189 if (annotate_semaphore == (SemaphoreInfo *) NULL)
cristy04b11db2014-02-16 15:10:39 +0000190 ActivateSemaphoreInfo(&annotate_semaphore);
cristy3d162a92014-02-16 14:05:06 +0000191 RelinquishSemaphoreInfo(&annotate_semaphore);
cristyd7ecaca2011-01-24 14:14:53 +0000192}
193
194/*
195%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
196% %
197% %
198% %
cristy3ed852e2009-09-05 21:47:34 +0000199% A n n o t a t e I m a g e %
200% %
201% %
202% %
203%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
204%
205% AnnotateImage() annotates an image with text. Optionally you can include
206% any of the following bits of information about the image by embedding
207% the appropriate special characters:
208%
cristy528127a2014-12-14 20:10:00 +0000209% \n newline
210% \r carriage return
211% < less-than character.
212% > greater-than character.
213% & ampersand character.
214% %% a percent sign
215% %b file size of image read in
216% %c comment meta-data property
217% %d directory component of path
218% %e filename extension or suffix
219% %f filename (including suffix)
220% %g layer canvas page geometry (equivalent to "%Wx%H%X%Y")
221% %h current image height in pixels
222% %i image filename (note: becomes output filename for "info:")
223% %k CALCULATED: number of unique colors
224% %l label meta-data property
225% %m image file format (file magic)
226% %n number of images in current image sequence
227% %o output filename (used for delegates)
228% %p index of image in current image list
229% %q quantum depth (compile-time constant)
230% %r image class and colorspace
231% %s scene number (from input unless re-assigned)
232% %t filename without directory or extension (suffix)
233% %u unique temporary filename (used for delegates)
234% %w current width in pixels
235% %x x resolution (density)
236% %y y resolution (density)
237% %z image depth (as read in unless modified, image save depth)
238% %A image transparency channel enabled (true/false)
239% %C image compression type
240% %D image GIF dispose method
241% %G original image size (%wx%h; before any resizes)
242% %H page (canvas) height
243% %M Magick filename (original file exactly as given, including read mods)
244% %O page (canvas) offset ( = %X%Y )
245% %P page (canvas) size ( = %Wx%H )
246% %Q image compression quality ( 0 = default )
247% %S ?? scenes ??
248% %T image time delay (in centi-seconds)
249% %U image resolution units
250% %W page (canvas) width
251% %X page (canvas) x offset (including sign)
252% %Y page (canvas) y offset (including sign)
253% %Z unique filename (used for delegates)
254% %@ CALCULATED: trim bounding box (without actually trimming)
255% %# CALCULATED: 'signature' hash of image values
cristy3ed852e2009-09-05 21:47:34 +0000256%
257% The format of the AnnotateImage method is:
258%
cristy5cbc0162011-08-29 00:36:28 +0000259% MagickBooleanType AnnotateImage(Image *image,DrawInfo *draw_info,
260% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000261%
262% A description of each parameter follows:
263%
264% o image: the image.
265%
266% o draw_info: the draw info.
267%
cristy5cbc0162011-08-29 00:36:28 +0000268% o exception: return any errors or warnings in this structure.
269%
cristy3ed852e2009-09-05 21:47:34 +0000270*/
271MagickExport MagickBooleanType AnnotateImage(Image *image,
cristy5cbc0162011-08-29 00:36:28 +0000272 const DrawInfo *draw_info,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000273{
274 char
cristy151b66d2015-04-15 10:50:31 +0000275 primitive[MagickPathExtent],
cristy3ed852e2009-09-05 21:47:34 +0000276 **textlist;
277
278 DrawInfo
279 *annotate,
280 *annotate_info;
281
282 GeometryInfo
283 geometry_info;
284
285 MagickBooleanType
286 status;
287
288 PointInfo
289 offset;
290
291 RectangleInfo
292 geometry;
293
cristybb503372010-05-27 20:51:26 +0000294 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000295 i;
296
297 size_t
298 length;
299
300 TypeMetric
301 metrics;
302
cristybb503372010-05-27 20:51:26 +0000303 size_t
cristy58460392009-09-09 01:52:06 +0000304 height,
cristy3ed852e2009-09-05 21:47:34 +0000305 number_lines;
306
307 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000308 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000309 if (image->debug != MagickFalse)
310 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
311 assert(draw_info != (DrawInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000312 assert(draw_info->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000313 if (draw_info->text == (char *) NULL)
314 return(MagickFalse);
315 if (*draw_info->text == '\0')
316 return(MagickTrue);
317 textlist=StringToList(draw_info->text);
318 if (textlist == (char **) NULL)
319 return(MagickFalse);
320 length=strlen(textlist[0]);
321 for (i=1; textlist[i] != (char *) NULL; i++)
322 if (strlen(textlist[i]) > length)
323 length=strlen(textlist[i]);
cristybb503372010-05-27 20:51:26 +0000324 number_lines=(size_t) i;
cristy3ed852e2009-09-05 21:47:34 +0000325 annotate=CloneDrawInfo((ImageInfo *) NULL,draw_info);
326 annotate_info=CloneDrawInfo((ImageInfo *) NULL,draw_info);
327 SetGeometry(image,&geometry);
328 SetGeometryInfo(&geometry_info);
329 if (annotate_info->geometry != (char *) NULL)
330 {
331 (void) ParsePageGeometry(image,annotate_info->geometry,&geometry,
cristy5cbc0162011-08-29 00:36:28 +0000332 exception);
cristy3ed852e2009-09-05 21:47:34 +0000333 (void) ParseGeometry(annotate_info->geometry,&geometry_info);
334 }
cristy5cbc0162011-08-29 00:36:28 +0000335 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000336 return(MagickFalse);
cristya6400b12013-03-15 12:20:18 +0000337 if (IsGrayColorspace(image->colorspace) != MagickFalse)
cristy0c81d062013-04-21 15:22:02 +0000338 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +0000339 status=MagickTrue;
340 for (i=0; textlist[i] != (char *) NULL; i++)
341 {
342 /*
343 Position text relative to image.
344 */
345 annotate_info->affine.tx=geometry_info.xi-image->page.x;
346 annotate_info->affine.ty=geometry_info.psi-image->page.y;
347 (void) CloneString(&annotate->text,textlist[i]);
cristy5cbc0162011-08-29 00:36:28 +0000348 (void) GetTypeMetrics(image,annotate,&metrics,exception);
cristybb503372010-05-27 20:51:26 +0000349 height=(ssize_t) (metrics.ascent-metrics.descent+
cristy58460392009-09-09 01:52:06 +0000350 draw_info->interline_spacing+0.5);
cristy3ed852e2009-09-05 21:47:34 +0000351 switch (annotate->gravity)
352 {
353 case UndefinedGravity:
354 default:
355 {
356 offset.x=annotate_info->affine.tx+i*annotate_info->affine.ry*height;
357 offset.y=annotate_info->affine.ty+i*annotate_info->affine.sy*height;
358 break;
359 }
360 case NorthWestGravity:
361 {
362 offset.x=(geometry.width == 0 ? -1.0 : 1.0)*annotate_info->affine.tx+i*
363 annotate_info->affine.ry*height+annotate_info->affine.ry*
364 (metrics.ascent+metrics.descent);
365 offset.y=(geometry.height == 0 ? -1.0 : 1.0)*annotate_info->affine.ty+i*
366 annotate_info->affine.sy*height+annotate_info->affine.sy*
367 metrics.ascent;
368 break;
369 }
370 case NorthGravity:
371 {
372 offset.x=(geometry.width == 0 ? -1.0 : 1.0)*annotate_info->affine.tx+
373 geometry.width/2.0+i*annotate_info->affine.ry*height-
Cristydd8b9292016-12-10 10:45:35 -0500374 annotate_info->affine.sx*metrics.width/2.0+annotate_info->affine.ry*
375 (metrics.ascent+metrics.descent);
cristy3ed852e2009-09-05 21:47:34 +0000376 offset.y=(geometry.height == 0 ? -1.0 : 1.0)*annotate_info->affine.ty+i*
377 annotate_info->affine.sy*height+annotate_info->affine.sy*
Cristydd8b9292016-12-10 10:45:35 -0500378 metrics.ascent-annotate_info->affine.rx*metrics.width/2.0;
cristy3ed852e2009-09-05 21:47:34 +0000379 break;
380 }
381 case NorthEastGravity:
382 {
383 offset.x=(geometry.width == 0 ? 1.0 : -1.0)*annotate_info->affine.tx+
384 geometry.width+i*annotate_info->affine.ry*height-
Cristydd8b9292016-12-10 10:45:35 -0500385 annotate_info->affine.sx*metrics.width+annotate_info->affine.ry*
386 (metrics.ascent+metrics.descent)-1.0;
cristy3ed852e2009-09-05 21:47:34 +0000387 offset.y=(geometry.height == 0 ? -1.0 : 1.0)*annotate_info->affine.ty+i*
388 annotate_info->affine.sy*height+annotate_info->affine.sy*
Cristydd8b9292016-12-10 10:45:35 -0500389 metrics.ascent-annotate_info->affine.rx*metrics.width;
cristy3ed852e2009-09-05 21:47:34 +0000390 break;
391 }
392 case WestGravity:
393 {
394 offset.x=(geometry.width == 0 ? -1.0 : 1.0)*annotate_info->affine.tx+i*
395 annotate_info->affine.ry*height+annotate_info->affine.ry*
396 (metrics.ascent+metrics.descent-(number_lines-1.0)*height)/2.0;
397 offset.y=(geometry.height == 0 ? -1.0 : 1.0)*annotate_info->affine.ty+
398 geometry.height/2.0+i*annotate_info->affine.sy*height+
399 annotate_info->affine.sy*(metrics.ascent+metrics.descent-
Cristyd4f277c2016-05-20 12:07:30 -0400400 (number_lines-1.0)*height)/2.0;
cristy3ed852e2009-09-05 21:47:34 +0000401 break;
402 }
cristy3ed852e2009-09-05 21:47:34 +0000403 case CenterGravity:
404 {
405 offset.x=(geometry.width == 0 ? -1.0 : 1.0)*annotate_info->affine.tx+
406 geometry.width/2.0+i*annotate_info->affine.ry*height-
Cristydd8b9292016-12-10 10:45:35 -0500407 annotate_info->affine.sx*metrics.width/2.0+annotate_info->affine.ry*
408 (metrics.ascent+metrics.descent-(number_lines-1.0)*height)/2.0;
cristy3ed852e2009-09-05 21:47:34 +0000409 offset.y=(geometry.height == 0 ? -1.0 : 1.0)*annotate_info->affine.ty+
410 geometry.height/2.0+i*annotate_info->affine.sy*height-
Cristydd8b9292016-12-10 10:45:35 -0500411 annotate_info->affine.rx*metrics.width/2.0+annotate_info->affine.sy*
412 (metrics.ascent+metrics.descent-(number_lines-1.0)*height)/2.0;
cristy3ed852e2009-09-05 21:47:34 +0000413 break;
414 }
415 case EastGravity:
416 {
417 offset.x=(geometry.width == 0 ? 1.0 : -1.0)*annotate_info->affine.tx+
418 geometry.width+i*annotate_info->affine.ry*height-
Cristydd8b9292016-12-10 10:45:35 -0500419 annotate_info->affine.sx*metrics.width+
cristy3ed852e2009-09-05 21:47:34 +0000420 annotate_info->affine.ry*(metrics.ascent+metrics.descent-
421 (number_lines-1.0)*height)/2.0-1.0;
422 offset.y=(geometry.height == 0 ? -1.0 : 1.0)*annotate_info->affine.ty+
423 geometry.height/2.0+i*annotate_info->affine.sy*height-
Cristydd8b9292016-12-10 10:45:35 -0500424 annotate_info->affine.rx*metrics.width+
cristy3ed852e2009-09-05 21:47:34 +0000425 annotate_info->affine.sy*(metrics.ascent+metrics.descent-
Cristyd4f277c2016-05-20 12:07:30 -0400426 (number_lines-1.0)*height)/2.0;
cristy3ed852e2009-09-05 21:47:34 +0000427 break;
428 }
429 case SouthWestGravity:
430 {
431 offset.x=(geometry.width == 0 ? -1.0 : 1.0)*annotate_info->affine.tx+i*
432 annotate_info->affine.ry*height-annotate_info->affine.ry*
433 (number_lines-1.0)*height;
434 offset.y=(geometry.height == 0 ? 1.0 : -1.0)*annotate_info->affine.ty+
435 geometry.height+i*annotate_info->affine.sy*height-
436 annotate_info->affine.sy*(number_lines-1.0)*height+metrics.descent;
437 break;
438 }
439 case SouthGravity:
440 {
441 offset.x=(geometry.width == 0 ? -1.0 : 1.0)*annotate_info->affine.tx+
442 geometry.width/2.0+i*annotate_info->affine.ry*height-
Cristydd8b9292016-12-10 10:45:35 -0500443 annotate_info->affine.sx*metrics.width/2.0-
cristy3ed852e2009-09-05 21:47:34 +0000444 annotate_info->affine.ry*(number_lines-1.0)*height/2.0;
445 offset.y=(geometry.height == 0 ? 1.0 : -1.0)*annotate_info->affine.ty+
446 geometry.height+i*annotate_info->affine.sy*height-
Cristydd8b9292016-12-10 10:45:35 -0500447 annotate_info->affine.rx*metrics.width/2.0-
cristy3ed852e2009-09-05 21:47:34 +0000448 annotate_info->affine.sy*(number_lines-1.0)*height+metrics.descent;
449 break;
450 }
451 case SouthEastGravity:
452 {
453 offset.x=(geometry.width == 0 ? 1.0 : -1.0)*annotate_info->affine.tx+
454 geometry.width+i*annotate_info->affine.ry*height-
Cristydd8b9292016-12-10 10:45:35 -0500455 annotate_info->affine.sx*metrics.width-
cristy3ed852e2009-09-05 21:47:34 +0000456 annotate_info->affine.ry*(number_lines-1.0)*height-1.0;
457 offset.y=(geometry.height == 0 ? 1.0 : -1.0)*annotate_info->affine.ty+
458 geometry.height+i*annotate_info->affine.sy*height-
Cristydd8b9292016-12-10 10:45:35 -0500459 annotate_info->affine.rx*metrics.width-
cristy3ed852e2009-09-05 21:47:34 +0000460 annotate_info->affine.sy*(number_lines-1.0)*height+metrics.descent;
461 break;
462 }
463 }
464 switch (annotate->align)
465 {
466 case LeftAlign:
467 {
468 offset.x=annotate_info->affine.tx+i*annotate_info->affine.ry*height;
469 offset.y=annotate_info->affine.ty+i*annotate_info->affine.sy*height;
470 break;
471 }
472 case CenterAlign:
473 {
474 offset.x=annotate_info->affine.tx+i*annotate_info->affine.ry*height-
Cristydd8b9292016-12-10 10:45:35 -0500475 annotate_info->affine.sx*metrics.width/2.0;
cristy3ed852e2009-09-05 21:47:34 +0000476 offset.y=annotate_info->affine.ty+i*annotate_info->affine.sy*height-
Cristydd8b9292016-12-10 10:45:35 -0500477 annotate_info->affine.rx*metrics.width/2.0;
cristy3ed852e2009-09-05 21:47:34 +0000478 break;
479 }
480 case RightAlign:
481 {
482 offset.x=annotate_info->affine.tx+i*annotate_info->affine.ry*height-
Cristydd8b9292016-12-10 10:45:35 -0500483 annotate_info->affine.sx*metrics.width;
cristy3ed852e2009-09-05 21:47:34 +0000484 offset.y=annotate_info->affine.ty+i*annotate_info->affine.sy*height-
Cristydd8b9292016-12-10 10:45:35 -0500485 annotate_info->affine.rx*metrics.width;
cristy3ed852e2009-09-05 21:47:34 +0000486 break;
487 }
488 default:
489 break;
490 }
cristy4c08aed2011-07-01 19:47:50 +0000491 if (draw_info->undercolor.alpha != TransparentAlpha)
cristy3ed852e2009-09-05 21:47:34 +0000492 {
493 DrawInfo
494 *undercolor_info;
495
496 /*
497 Text box.
498 */
499 undercolor_info=CloneDrawInfo((ImageInfo *) NULL,(DrawInfo *) NULL);
500 undercolor_info->fill=draw_info->undercolor;
501 undercolor_info->affine=draw_info->affine;
502 undercolor_info->affine.tx=offset.x-draw_info->affine.ry*metrics.ascent;
503 undercolor_info->affine.ty=offset.y-draw_info->affine.sy*metrics.ascent;
cristy151b66d2015-04-15 10:50:31 +0000504 (void) FormatLocaleString(primitive,MagickPathExtent,
cristydcc02b02015-06-08 23:08:35 +0000505 "rectangle 0.0,0.0 %g,%g",metrics.origin.x,(double) height);
cristy3ed852e2009-09-05 21:47:34 +0000506 (void) CloneString(&undercolor_info->primitive,primitive);
cristy018f07f2011-09-04 21:15:19 +0000507 (void) DrawImage(image,undercolor_info,exception);
cristy3ed852e2009-09-05 21:47:34 +0000508 (void) DestroyDrawInfo(undercolor_info);
509 }
510 annotate_info->affine.tx=offset.x;
511 annotate_info->affine.ty=offset.y;
cristy151b66d2015-04-15 10:50:31 +0000512 (void) FormatLocaleString(primitive,MagickPathExtent,"stroke-width %g "
cristya8549b12011-05-18 19:05:08 +0000513 "line 0,0 %g,0",metrics.underline_thickness,metrics.width);
cristy3ed852e2009-09-05 21:47:34 +0000514 if (annotate->decorate == OverlineDecoration)
515 {
516 annotate_info->affine.ty-=(draw_info->affine.sy*(metrics.ascent+
517 metrics.descent-metrics.underline_position));
518 (void) CloneString(&annotate_info->primitive,primitive);
cristy018f07f2011-09-04 21:15:19 +0000519 (void) DrawImage(image,annotate_info,exception);
cristy3ed852e2009-09-05 21:47:34 +0000520 }
521 else
522 if (annotate->decorate == UnderlineDecoration)
523 {
524 annotate_info->affine.ty-=(draw_info->affine.sy*
525 metrics.underline_position);
526 (void) CloneString(&annotate_info->primitive,primitive);
cristy018f07f2011-09-04 21:15:19 +0000527 (void) DrawImage(image,annotate_info,exception);
cristy3ed852e2009-09-05 21:47:34 +0000528 }
529 /*
530 Annotate image with text.
531 */
cristy5cbc0162011-08-29 00:36:28 +0000532 status=RenderType(image,annotate,&offset,&metrics,exception);
cristy3ed852e2009-09-05 21:47:34 +0000533 if (status == MagickFalse)
534 break;
535 if (annotate->decorate == LineThroughDecoration)
536 {
537 annotate_info->affine.ty-=(draw_info->affine.sy*(height+
538 metrics.underline_position+metrics.descent)/2.0);
539 (void) CloneString(&annotate_info->primitive,primitive);
cristy018f07f2011-09-04 21:15:19 +0000540 (void) DrawImage(image,annotate_info,exception);
cristy3ed852e2009-09-05 21:47:34 +0000541 }
542 }
543 /*
544 Relinquish resources.
545 */
546 annotate_info=DestroyDrawInfo(annotate_info);
547 annotate=DestroyDrawInfo(annotate);
548 for (i=0; textlist[i] != (char *) NULL; i++)
549 textlist[i]=DestroyString(textlist[i]);
550 textlist=(char **) RelinquishMagickMemory(textlist);
551 return(status);
552}
553
554/*
555%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
556% %
557% %
558% %
559% F o r m a t M a g i c k C a p t i o n %
560% %
561% %
562% %
563%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
564%
565% FormatMagickCaption() formats a caption so that it fits within the image
566% width. It returns the number of lines in the formatted caption.
567%
568% The format of the FormatMagickCaption method is:
569%
cristybb503372010-05-27 20:51:26 +0000570% ssize_t FormatMagickCaption(Image *image,DrawInfo *draw_info,
cristy5cbc0162011-08-29 00:36:28 +0000571% const MagickBooleanType split,TypeMetric *metrics,char **caption,
572% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000573%
574% A description of each parameter follows.
575%
576% o image: The image.
577%
cristy3ed852e2009-09-05 21:47:34 +0000578% o draw_info: the draw info.
579%
cristy6b1d05e2010-09-22 19:17:27 +0000580% o split: when no convenient line breaks-- insert newline.
581%
cristy3ed852e2009-09-05 21:47:34 +0000582% o metrics: Return the font metrics in this structure.
583%
cristy6b1d05e2010-09-22 19:17:27 +0000584% o caption: the caption.
585%
cristy5cbc0162011-08-29 00:36:28 +0000586% o exception: return any errors or warnings in this structure.
587%
cristy3ed852e2009-09-05 21:47:34 +0000588*/
cristybb503372010-05-27 20:51:26 +0000589MagickExport ssize_t FormatMagickCaption(Image *image,DrawInfo *draw_info,
cristy5cbc0162011-08-29 00:36:28 +0000590 const MagickBooleanType split,TypeMetric *metrics,char **caption,
591 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000592{
593 MagickBooleanType
Cristy20aa2992015-12-31 19:09:27 -0500594 digit,
cristy3ed852e2009-09-05 21:47:34 +0000595 status;
596
597 register char
598 *p,
599 *q,
600 *s;
601
cristybb503372010-05-27 20:51:26 +0000602 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000603 i;
604
cristybb503372010-05-27 20:51:26 +0000605 size_t
cristy3ed852e2009-09-05 21:47:34 +0000606 width;
607
cristy6193b862011-08-06 16:33:59 +0000608 ssize_t
609 n;
610
Cristy20aa2992015-12-31 19:09:27 -0500611 digit=MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +0000612 q=draw_info->text;
613 s=(char *) NULL;
Cristydfa2f8c2017-01-25 07:25:04 -0500614 width=0;
cristy3ed852e2009-09-05 21:47:34 +0000615 for (p=(*caption); GetUTFCode(p) != 0; p+=GetUTFOctets(p))
616 {
Cristy20aa2992015-12-31 19:09:27 -0500617 if ((digit == MagickFalse) && (IsUTFSpace(GetUTFCode(p)) != MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000618 s=p;
Cristy20aa2992015-12-31 19:09:27 -0500619 digit=((GetUTFCode(p) >= 0x0030) && (GetUTFCode(p) <= 0x0039)) ?
620 MagickTrue : MagickFalse;
cristy2911f2d2011-08-20 23:04:43 +0000621 if (GetUTFCode(p) == '\n')
cristye2c81ad2011-08-20 22:54:14 +0000622 q=draw_info->text;
cristybb503372010-05-27 20:51:26 +0000623 for (i=0; i < (ssize_t) GetUTFOctets(p); i++)
cristy3ed852e2009-09-05 21:47:34 +0000624 *q++=(*(p+i));
625 *q='\0';
cristy5cbc0162011-08-29 00:36:28 +0000626 status=GetTypeMetrics(image,draw_info,metrics,exception);
cristy3ed852e2009-09-05 21:47:34 +0000627 if (status == MagickFalse)
628 break;
cristy78fed5e2013-06-02 17:24:16 +0000629 width=(size_t) floor(metrics->width+draw_info->stroke_width+0.5);
Arthur Darcet6f55f532016-08-30 15:23:32 +0200630 if ((width <= image->columns) || (s == (char *) NULL))
cristy6193b862011-08-06 16:33:59 +0000631 continue;
cristy4aa314e2011-08-19 13:59:45 +0000632 if ((s != (char *) NULL) && (GetUTFOctets(s) == 1))
cristy3ed852e2009-09-05 21:47:34 +0000633 {
634 *s='\n';
635 p=s;
636 }
637 else
Cristy9d12f1e2016-12-24 13:27:00 -0500638 if (split != MagickFalse)
cristy6b1d05e2010-09-22 19:17:27 +0000639 {
640 char
641 *target;
cristy3ed852e2009-09-05 21:47:34 +0000642
cristy6b1d05e2010-09-22 19:17:27 +0000643 /*
644 No convenient line breaks-- insert newline.
645 */
646 target=AcquireString(*caption);
647 n=p-(*caption);
648 CopyMagickString(target,*caption,n+1);
649 ConcatenateMagickString(target,"\n",strlen(*caption)+1);
650 ConcatenateMagickString(target,p,strlen(*caption)+2);
651 (void) DestroyString(*caption);
652 *caption=target;
653 p=(*caption)+n;
654 }
cristy3ed852e2009-09-05 21:47:34 +0000655 q=draw_info->text;
cristy6193b862011-08-06 16:33:59 +0000656 s=(char *) NULL;
cristy3ed852e2009-09-05 21:47:34 +0000657 }
Cristydfa2f8c2017-01-25 07:25:04 -0500658 if (width > image->columns)
659 {
660 char
661 *text;
662
663 /*
664 No convenient break point, force one.
665 */
666 text=AcquireString(draw_info->text);
667 q=draw_info->text;
668 s=(char *) NULL;
669 for (p=(*caption); GetUTFCode(p) != 0; p+=GetUTFOctets(p))
670 {
671 if (IsUTFSpace(GetUTFCode(p)) != MagickFalse)
672 s=p;
673 if (GetUTFCode(p) == '\n')
674 q=draw_info->text;
675 for (i=0; i < (ssize_t) GetUTFOctets(p); i++)
676 *q++=(*(p+i));
677 *q='\0';
678 status=GetTypeMetrics(image,draw_info,metrics,exception);
679 if (status == MagickFalse)
680 break;
681 width=(size_t) floor(metrics->width+draw_info->stroke_width+0.5);
682 if ((width <= image->columns) || (strcmp(text,draw_info->text) == 0))
683 continue;
684 (void) strcpy(text,draw_info->text);
685 if ((s != (char *) NULL) && (GetUTFOctets(s) == 1))
686 {
687 *s='\n';
688 p=s;
689 }
690 else
691 if ((s != (char *) NULL) || (split != MagickFalse))
692 {
693 char
694 *target;
695
696 /*
697 No convenient line breaks-- insert newline.
698 */
699 target=AcquireString(*caption);
700 n=p-(*caption);
701 CopyMagickString(target,*caption,n+1);
702 ConcatenateMagickString(target,"\n",strlen(*caption)+1);
703 ConcatenateMagickString(target,p,strlen(*caption)+2);
704 (void) DestroyString(*caption);
705 *caption=target;
706 p=(*caption)+n;
707 }
708 q=draw_info->text;
709 s=(char *) NULL;
710 }
711 text=DestroyString(text);
712 }
cristy6193b862011-08-06 16:33:59 +0000713 n=0;
cristy3ed852e2009-09-05 21:47:34 +0000714 for (p=(*caption); GetUTFCode(p) != 0; p+=GetUTFOctets(p))
715 if (GetUTFCode(p) == '\n')
cristy6193b862011-08-06 16:33:59 +0000716 n++;
717 return(n);
cristy3ed852e2009-09-05 21:47:34 +0000718}
719
720/*
721%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
722% %
723% %
724% %
725% G e t M u l t i l i n e T y p e M e t r i c s %
726% %
727% %
728% %
729%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
730%
731% GetMultilineTypeMetrics() returns the following information for the
732% specified font and text:
733%
734% character width
735% character height
736% ascender
737% descender
738% text width
739% text height
740% maximum horizontal advance
741% bounds: x1
742% bounds: y1
743% bounds: x2
744% bounds: y2
745% origin: x
746% origin: y
747% underline position
748% underline thickness
749%
750% This method is like GetTypeMetrics() but it returns the maximum text width
751% and height for multiple lines of text.
752%
753% The format of the GetMultilineTypeMetrics method is:
754%
755% MagickBooleanType GetMultilineTypeMetrics(Image *image,
cristy5cbc0162011-08-29 00:36:28 +0000756% const DrawInfo *draw_info,TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000757%
758% A description of each parameter follows:
759%
760% o image: the image.
761%
762% o draw_info: the draw info.
763%
764% o metrics: Return the font metrics in this structure.
765%
cristy5cbc0162011-08-29 00:36:28 +0000766% o exception: return any errors or warnings in this structure.
767%
cristy3ed852e2009-09-05 21:47:34 +0000768*/
769MagickExport MagickBooleanType GetMultilineTypeMetrics(Image *image,
cristy5cbc0162011-08-29 00:36:28 +0000770 const DrawInfo *draw_info,TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000771{
772 char
773 **textlist;
774
775 DrawInfo
776 *annotate_info;
777
778 MagickBooleanType
779 status;
780
cristybb503372010-05-27 20:51:26 +0000781 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000782 i;
783
784 TypeMetric
785 extent;
786
cristy3ed852e2009-09-05 21:47:34 +0000787 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000788 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000789 if (image->debug != MagickFalse)
790 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
791 assert(draw_info != (DrawInfo *) NULL);
792 assert(draw_info->text != (char *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000793 assert(draw_info->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000794 if (*draw_info->text == '\0')
795 return(MagickFalse);
796 annotate_info=CloneDrawInfo((ImageInfo *) NULL,draw_info);
797 annotate_info->text=DestroyString(annotate_info->text);
798 /*
799 Convert newlines to multiple lines of text.
800 */
801 textlist=StringToList(draw_info->text);
802 if (textlist == (char **) NULL)
803 return(MagickFalse);
804 annotate_info->render=MagickFalse;
cristy024843f2011-06-03 17:52:52 +0000805 annotate_info->direction=UndefinedDirection;
cristy3ed852e2009-09-05 21:47:34 +0000806 (void) ResetMagickMemory(metrics,0,sizeof(*metrics));
807 (void) ResetMagickMemory(&extent,0,sizeof(extent));
808 /*
809 Find the widest of the text lines.
810 */
811 annotate_info->text=textlist[0];
cristy5cbc0162011-08-29 00:36:28 +0000812 status=GetTypeMetrics(image,annotate_info,&extent,exception);
cristy3ed852e2009-09-05 21:47:34 +0000813 *metrics=extent;
814 for (i=1; textlist[i] != (char *) NULL; i++)
815 {
816 annotate_info->text=textlist[i];
cristy5cbc0162011-08-29 00:36:28 +0000817 status=GetTypeMetrics(image,annotate_info,&extent,exception);
cristy3ed852e2009-09-05 21:47:34 +0000818 if (extent.width > metrics->width)
819 *metrics=extent;
820 }
cristye2c81ad2011-08-20 22:54:14 +0000821 metrics->height=(double) (i*(size_t) (metrics->ascent-metrics->descent+0.5)+
822 (i-1)*draw_info->interline_spacing);
cristy3ed852e2009-09-05 21:47:34 +0000823 /*
824 Relinquish resources.
825 */
826 annotate_info->text=(char *) NULL;
827 annotate_info=DestroyDrawInfo(annotate_info);
828 for (i=0; textlist[i] != (char *) NULL; i++)
829 textlist[i]=DestroyString(textlist[i]);
830 textlist=(char **) RelinquishMagickMemory(textlist);
831 return(status);
832}
833
834/*
835%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
836% %
837% %
838% %
839% G e t T y p e M e t r i c s %
840% %
841% %
842% %
843%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
844%
845% GetTypeMetrics() returns the following information for the specified font
846% and text:
847%
848% character width
849% character height
850% ascender
851% descender
852% text width
853% text height
854% maximum horizontal advance
855% bounds: x1
856% bounds: y1
857% bounds: x2
858% bounds: y2
859% origin: x
860% origin: y
861% underline position
862% underline thickness
863%
864% The format of the GetTypeMetrics method is:
865%
866% MagickBooleanType GetTypeMetrics(Image *image,const DrawInfo *draw_info,
cristy5cbc0162011-08-29 00:36:28 +0000867% TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000868%
869% A description of each parameter follows:
870%
871% o image: the image.
872%
873% o draw_info: the draw info.
874%
875% o metrics: Return the font metrics in this structure.
876%
cristy5cbc0162011-08-29 00:36:28 +0000877% o exception: return any errors or warnings in this structure.
878%
cristy3ed852e2009-09-05 21:47:34 +0000879*/
880MagickExport MagickBooleanType GetTypeMetrics(Image *image,
cristy5cbc0162011-08-29 00:36:28 +0000881 const DrawInfo *draw_info,TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000882{
883 DrawInfo
884 *annotate_info;
885
886 MagickBooleanType
887 status;
888
889 PointInfo
890 offset;
891
892 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000893 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000894 if (image->debug != MagickFalse)
895 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
896 assert(draw_info != (DrawInfo *) NULL);
897 assert(draw_info->text != (char *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000898 assert(draw_info->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000899 annotate_info=CloneDrawInfo((ImageInfo *) NULL,draw_info);
900 annotate_info->render=MagickFalse;
cristy024843f2011-06-03 17:52:52 +0000901 annotate_info->direction=UndefinedDirection;
cristy3ed852e2009-09-05 21:47:34 +0000902 (void) ResetMagickMemory(metrics,0,sizeof(*metrics));
903 offset.x=0.0;
904 offset.y=0.0;
cristy5cbc0162011-08-29 00:36:28 +0000905 status=RenderType(image,annotate_info,&offset,metrics,exception);
cristy3ed852e2009-09-05 21:47:34 +0000906 if (image->debug != MagickFalse)
907 (void) LogMagickEvent(AnnotateEvent,GetMagickModule(),"Metrics: text: %s; "
cristye7f51092010-01-17 00:39:37 +0000908 "width: %g; height: %g; ascent: %g; descent: %g; max advance: %g; "
cristya8549b12011-05-18 19:05:08 +0000909 "bounds: %g,%g %g,%g; origin: %g,%g; pixels per em: %g,%g; "
cristye7f51092010-01-17 00:39:37 +0000910 "underline position: %g; underline thickness: %g",annotate_info->text,
cristy3ed852e2009-09-05 21:47:34 +0000911 metrics->width,metrics->height,metrics->ascent,metrics->descent,
912 metrics->max_advance,metrics->bounds.x1,metrics->bounds.y1,
cristy4c08aed2011-07-01 19:47:50 +0000913 metrics->bounds.x2,metrics->bounds.y2,metrics->origin.x,metrics->origin.y,
914 metrics->pixels_per_em.x,metrics->pixels_per_em.y,
cristy3ed852e2009-09-05 21:47:34 +0000915 metrics->underline_position,metrics->underline_thickness);
916 annotate_info=DestroyDrawInfo(annotate_info);
917 return(status);
918}
919
920/*
921%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
922% %
923% %
924% %
925+ R e n d e r T y p e %
926% %
927% %
928% %
929%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
930%
931% RenderType() renders text on the image. It also returns the bounding box of
932% the text relative to the image.
933%
934% The format of the RenderType method is:
935%
936% MagickBooleanType RenderType(Image *image,DrawInfo *draw_info,
cristy5cbc0162011-08-29 00:36:28 +0000937% const PointInfo *offset,TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000938%
939% A description of each parameter follows:
940%
941% o image: the image.
942%
943% o draw_info: the draw info.
944%
945% o offset: (x,y) location of text relative to image.
946%
947% o metrics: bounding box of text.
948%
cristy5cbc0162011-08-29 00:36:28 +0000949% o exception: return any errors or warnings in this structure.
950%
cristy3ed852e2009-09-05 21:47:34 +0000951*/
952static MagickBooleanType RenderType(Image *image,const DrawInfo *draw_info,
cristy5cbc0162011-08-29 00:36:28 +0000953 const PointInfo *offset,TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000954{
955 const TypeInfo
956 *type_info;
957
958 DrawInfo
959 *annotate_info;
960
961 MagickBooleanType
962 status;
963
964 type_info=(const TypeInfo *) NULL;
965 if (draw_info->font != (char *) NULL)
966 {
967 if (*draw_info->font == '@')
968 {
969 status=RenderFreetype(image,draw_info,draw_info->encoding,offset,
cristy5cbc0162011-08-29 00:36:28 +0000970 metrics,exception);
cristy3ed852e2009-09-05 21:47:34 +0000971 return(status);
972 }
973 if (*draw_info->font == '-')
cristy5cbc0162011-08-29 00:36:28 +0000974 return(RenderX11(image,draw_info,offset,metrics,exception));
Cristy23f956b2016-10-01 12:21:15 -0400975 if (*draw_info->font == '^')
976 return(RenderPostscript(image,draw_info,offset,metrics,exception));
cristy3ed852e2009-09-05 21:47:34 +0000977 if (IsPathAccessible(draw_info->font) != MagickFalse)
978 {
979 status=RenderFreetype(image,draw_info,draw_info->encoding,offset,
cristy5cbc0162011-08-29 00:36:28 +0000980 metrics,exception);
cristy3ed852e2009-09-05 21:47:34 +0000981 return(status);
982 }
cristy5cbc0162011-08-29 00:36:28 +0000983 type_info=GetTypeInfo(draw_info->font,exception);
cristy3ed852e2009-09-05 21:47:34 +0000984 if (type_info == (const TypeInfo *) NULL)
cristy5cbc0162011-08-29 00:36:28 +0000985 (void) ThrowMagickException(exception,GetMagickModule(),TypeWarning,
cristyefe601c2013-01-05 17:51:12 +0000986 "UnableToReadFont","`%s'",draw_info->font);
cristy3ed852e2009-09-05 21:47:34 +0000987 }
988 if ((type_info == (const TypeInfo *) NULL) &&
989 (draw_info->family != (const char *) NULL))
990 {
991 type_info=GetTypeInfoByFamily(draw_info->family,draw_info->style,
cristy5cbc0162011-08-29 00:36:28 +0000992 draw_info->stretch,draw_info->weight,exception);
cristy3ed852e2009-09-05 21:47:34 +0000993 if (type_info == (const TypeInfo *) NULL)
cristy5cbc0162011-08-29 00:36:28 +0000994 (void) ThrowMagickException(exception,GetMagickModule(),TypeWarning,
cristyefe601c2013-01-05 17:51:12 +0000995 "UnableToReadFont","`%s'",draw_info->family);
cristy3ed852e2009-09-05 21:47:34 +0000996 }
997 if (type_info == (const TypeInfo *) NULL)
cristyd984f272010-03-16 01:46:12 +0000998 type_info=GetTypeInfoByFamily("Arial",draw_info->style,
cristy5cbc0162011-08-29 00:36:28 +0000999 draw_info->stretch,draw_info->weight,exception);
cristyd984f272010-03-16 01:46:12 +00001000 if (type_info == (const TypeInfo *) NULL)
cristycbb34a42010-03-16 01:45:18 +00001001 type_info=GetTypeInfoByFamily("Helvetica",draw_info->style,
cristy5cbc0162011-08-29 00:36:28 +00001002 draw_info->stretch,draw_info->weight,exception);
cristy3ed852e2009-09-05 21:47:34 +00001003 if (type_info == (const TypeInfo *) NULL)
cristy704acaa2010-03-23 13:08:21 +00001004 type_info=GetTypeInfoByFamily("Century Schoolbook",draw_info->style,
cristy5cbc0162011-08-29 00:36:28 +00001005 draw_info->stretch,draw_info->weight,exception);
cristy704acaa2010-03-23 13:08:21 +00001006 if (type_info == (const TypeInfo *) NULL)
cristy6193b862011-08-06 16:33:59 +00001007 type_info=GetTypeInfoByFamily("Sans",draw_info->style,
cristy5cbc0162011-08-29 00:36:28 +00001008 draw_info->stretch,draw_info->weight,exception);
cristy6193b862011-08-06 16:33:59 +00001009 if (type_info == (const TypeInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001010 type_info=GetTypeInfoByFamily((const char *) NULL,draw_info->style,
cristy5cbc0162011-08-29 00:36:28 +00001011 draw_info->stretch,draw_info->weight,exception);
cristy3ed852e2009-09-05 21:47:34 +00001012 if (type_info == (const TypeInfo *) NULL)
cristy5cbc0162011-08-29 00:36:28 +00001013 type_info=GetTypeInfo("*",exception);
cristy704acaa2010-03-23 13:08:21 +00001014 if (type_info == (const TypeInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001015 {
cristy5cbc0162011-08-29 00:36:28 +00001016 status=RenderFreetype(image,draw_info,draw_info->encoding,offset,metrics,
1017 exception);
cristy3ed852e2009-09-05 21:47:34 +00001018 return(status);
1019 }
1020 annotate_info=CloneDrawInfo((ImageInfo *) NULL,draw_info);
1021 annotate_info->face=type_info->face;
1022 if (type_info->metrics != (char *) NULL)
1023 (void) CloneString(&annotate_info->metrics,type_info->metrics);
1024 if (type_info->glyphs != (char *) NULL)
1025 (void) CloneString(&annotate_info->font,type_info->glyphs);
cristy5cbc0162011-08-29 00:36:28 +00001026 status=RenderFreetype(image,annotate_info,type_info->encoding,offset,metrics,
1027 exception);
cristy3ed852e2009-09-05 21:47:34 +00001028 annotate_info=DestroyDrawInfo(annotate_info);
1029 return(status);
1030}
1031
1032/*
1033%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1034% %
1035% %
1036% %
1037+ R e n d e r F r e e t y p e %
1038% %
1039% %
1040% %
1041%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1042%
1043% RenderFreetype() renders text on the image with a Truetype font. It also
1044% returns the bounding box of the text relative to the image.
1045%
1046% The format of the RenderFreetype method is:
1047%
1048% MagickBooleanType RenderFreetype(Image *image,DrawInfo *draw_info,
cristy5cbc0162011-08-29 00:36:28 +00001049% const char *encoding,const PointInfo *offset,TypeMetric *metrics,
1050% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001051%
1052% A description of each parameter follows:
1053%
1054% o image: the image.
1055%
1056% o draw_info: the draw info.
1057%
1058% o encoding: the font encoding.
1059%
1060% o offset: (x,y) location of text relative to image.
1061%
1062% o metrics: bounding box of text.
1063%
cristy5cbc0162011-08-29 00:36:28 +00001064% o exception: return any errors or warnings in this structure.
1065%
cristy3ed852e2009-09-05 21:47:34 +00001066*/
1067
1068#if defined(MAGICKCORE_FREETYPE_DELEGATE)
cristy2857ffc2010-03-27 23:44:00 +00001069
Cristy87dc5e42016-02-05 18:52:57 -05001070static size_t ComplexTextLayout(const Image *image,const DrawInfo *draw_info,
1071 const char *text,const size_t length,const FT_Face face,const FT_Int32 flags,
1072 GraphemeInfo **grapheme,ExceptionInfo *exception)
Cristy69a0d3b2015-12-22 20:40:36 -05001073{
Cristy17f8c722016-01-31 09:42:56 -05001074#if defined(MAGICKCORE_RAQM_DELEGATE)
Cristy0d88ea32016-02-05 15:20:00 -05001075 const char
1076 *features;
1077
Cristy17f8c722016-01-31 09:42:56 -05001078 raqm_t
1079 *rq;
1080
1081 raqm_glyph_t
1082 *glyphs;
1083
Cristy87dc5e42016-02-05 18:52:57 -05001084 register size_t
1085 i;
1086
Cristy6720ed72016-02-09 09:27:35 -05001087 size_t
1088 extent;
Cristy87dc5e42016-02-05 18:52:57 -05001089
Cristy6720ed72016-02-09 09:27:35 -05001090 extent=0;
Cristy0d88ea32016-02-05 15:20:00 -05001091 rq=raqm_create();
Cristy17f8c722016-01-31 09:42:56 -05001092 if (rq == (raqm_t *) NULL)
1093 goto cleanup;
Cristy0d88ea32016-02-05 15:20:00 -05001094 if (raqm_set_text_utf8(rq,text,length) == 0)
Cristy17f8c722016-01-31 09:42:56 -05001095 goto cleanup;
Cristy87dc5e42016-02-05 18:52:57 -05001096 if (raqm_set_par_direction(rq,(raqm_direction_t) draw_info->direction) == 0)
Cristy17f8c722016-01-31 09:42:56 -05001097 goto cleanup;
Cristy0d88ea32016-02-05 15:20:00 -05001098 if (raqm_set_freetype_face(rq,face) == 0)
Cristy17f8c722016-01-31 09:42:56 -05001099 goto cleanup;
Cristy0d88ea32016-02-05 15:20:00 -05001100 features=GetImageProperty(image,"type:features",exception);
1101 if (features != (const char *) NULL)
Cristy17f8c722016-01-31 09:42:56 -05001102 {
1103 char
1104 breaker,
1105 quote,
1106 *token;
1107
Cristy17f8c722016-01-31 09:42:56 -05001108 int
1109 next,
1110 status_token;
1111
Cristy87dc5e42016-02-05 18:52:57 -05001112 TokenInfo
1113 *token_info;
1114
Cristy17f8c722016-01-31 09:42:56 -05001115 next=0;
1116 token_info=AcquireTokenInfo();
Cristy87dc5e42016-02-05 18:52:57 -05001117 token=AcquireString("");
Cristy0d88ea32016-02-05 15:20:00 -05001118 status_token=Tokenizer(token_info,0,token,50,features,"",",","",'\0',
1119 &breaker,&next,&quote);
Cristy17f8c722016-01-31 09:42:56 -05001120 while (status_token == 0)
Cristy0d88ea32016-02-05 15:20:00 -05001121 {
1122 raqm_add_font_feature(rq,token,strlen(token));
1123 status_token=Tokenizer(token_info,0,token,50,features,"",",","",'\0',
1124 &breaker,&next,&quote);
1125 }
Cristy17f8c722016-01-31 09:42:56 -05001126 token_info=DestroyTokenInfo(token_info);
1127 token=DestroyString(token);
1128 }
Cristy0d88ea32016-02-05 15:20:00 -05001129 if (raqm_layout(rq) == 0)
Cristy17f8c722016-01-31 09:42:56 -05001130 goto cleanup;
Cristy6720ed72016-02-09 09:27:35 -05001131 glyphs=raqm_get_glyphs(rq,&extent);
Cristy17f8c722016-01-31 09:42:56 -05001132 if (glyphs == (raqm_glyph_t *) NULL)
1133 {
Cristy6720ed72016-02-09 09:27:35 -05001134 extent=0;
Cristy17f8c722016-01-31 09:42:56 -05001135 goto cleanup;
1136 }
Cristy6720ed72016-02-09 09:27:35 -05001137 *grapheme=(GraphemeInfo *) AcquireQuantumMemory(extent,sizeof(**grapheme));
Cristy17f8c722016-01-31 09:42:56 -05001138 if (*grapheme == (GraphemeInfo *) NULL)
1139 {
Cristy6720ed72016-02-09 09:27:35 -05001140 extent=0;
Cristy17f8c722016-01-31 09:42:56 -05001141 goto cleanup;
1142 }
Cristy6720ed72016-02-09 09:27:35 -05001143 for (i=0; i < (ssize_t) extent; i++)
Cristy0d88ea32016-02-05 15:20:00 -05001144 {
1145 (*grapheme)[i].index=glyphs[i].index;
1146 (*grapheme)[i].x_offset=glyphs[i].x_offset;
1147 (*grapheme)[i].x_advance=glyphs[i].x_advance;
1148 (*grapheme)[i].y_offset=glyphs[i].y_offset;
1149 (*grapheme)[i].cluster=glyphs[i].cluster;
1150 }
Cristy17f8c722016-01-31 09:42:56 -05001151
1152cleanup:
Cristy0d88ea32016-02-05 15:20:00 -05001153 raqm_destroy(rq);
Cristy6720ed72016-02-09 09:27:35 -05001154 return(extent);
Cristy14c8ae42015-12-23 15:51:51 -05001155#else
Cristy87dc5e42016-02-05 18:52:57 -05001156 const char
1157 *p;
1158
Cristy69a0d3b2015-12-22 20:40:36 -05001159 FT_Error
1160 ft_status;
1161
1162 register ssize_t
1163 i;
1164
1165 ssize_t
1166 last_glyph;
1167
dirk9d7cf6b2016-09-18 19:36:36 +02001168 magick_unreferenced(image);
dirk751a6672016-03-19 11:09:53 +01001169 magick_unreferenced(exception);
1170
Cristy91c27412015-12-23 15:37:08 -05001171 /*
Cristy9da9d9c2015-12-28 11:53:40 -05001172 Simple layout for bi-directional text (right-to-left or left-to-right).
Cristy91c27412015-12-23 15:37:08 -05001173 */
Cristy0d88ea32016-02-05 15:20:00 -05001174 *grapheme=(GraphemeInfo *) AcquireQuantumMemory(length+1,sizeof(**grapheme));
Cristy17f8c722016-01-31 09:42:56 -05001175 if (*grapheme == (GraphemeInfo *) NULL)
Cristy69a0d3b2015-12-22 20:40:36 -05001176 return(0);
1177 last_glyph=0;
shamsad45bfbb2016-02-01 09:26:39 +04001178 p=text;
1179 for (i=0; GetUTFCode(p) != 0; p+=GetUTFOctets(p), i++)
Cristy69a0d3b2015-12-22 20:40:36 -05001180 {
Cristy87dc5e42016-02-05 18:52:57 -05001181 (*grapheme)[i].index=(ssize_t) FT_Get_Char_Index(face,GetUTFCode(p));
Cristy91c27412015-12-23 15:37:08 -05001182 (*grapheme)[i].x_offset=0;
1183 (*grapheme)[i].y_offset=0;
1184 if (((*grapheme)[i].index != 0) && (last_glyph != 0))
Cristy69a0d3b2015-12-22 20:40:36 -05001185 {
1186 if (FT_HAS_KERNING(face))
1187 {
1188 FT_Vector
Cristy50d31562015-12-28 07:36:33 -05001189 kerning;
Cristy69a0d3b2015-12-22 20:40:36 -05001190
Cristy06d4a5a2016-03-03 11:47:02 -05001191 ft_status=FT_Get_Kerning(face,(FT_UInt) last_glyph,(FT_UInt)
1192 (*grapheme)[i].index,ft_kerning_default,&kerning);
Cristy69a0d3b2015-12-22 20:40:36 -05001193 if (ft_status == 0)
Cristy87dc5e42016-02-05 18:52:57 -05001194 (*grapheme)[i-1].x_advance+=(FT_Pos) ((draw_info->direction ==
1195 RightToLeftDirection ? -1.0 : 1.0)*kerning.x);
Cristy69a0d3b2015-12-22 20:40:36 -05001196 }
Cristy91c27412015-12-23 15:37:08 -05001197 }
dirk86bacde2016-03-17 22:04:25 +01001198 ft_status=FT_Load_Glyph(face,(FT_UInt) (*grapheme)[i].index,flags);
Cristy91c27412015-12-23 15:37:08 -05001199 (*grapheme)[i].x_advance=face->glyph->advance.x;
shamsad45bfbb2016-02-01 09:26:39 +04001200 (*grapheme)[i].cluster=p-text;
Cristy91c27412015-12-23 15:37:08 -05001201 last_glyph=(*grapheme)[i].index;
Cristy69a0d3b2015-12-22 20:40:36 -05001202 }
1203 return((size_t) i);
Cristy14c8ae42015-12-23 15:51:51 -05001204#endif
Cristy69a0d3b2015-12-22 20:40:36 -05001205}
1206
cristy3ed852e2009-09-05 21:47:34 +00001207static int TraceCubicBezier(FT_Vector *p,FT_Vector *q,FT_Vector *to,
1208 DrawInfo *draw_info)
1209{
1210 AffineMatrix
1211 affine;
1212
1213 char
cristy151b66d2015-04-15 10:50:31 +00001214 path[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00001215
1216 affine=draw_info->affine;
Cristy9c282cc2015-08-29 17:37:22 -04001217 (void) FormatLocaleString(path,MagickPathExtent,"C%g,%g %g,%g %g,%g",
1218 affine.tx+p->x/64.0,affine.ty-p->y/64.0,affine.tx+q->x/64.0,affine.ty-
1219 q->y/64.0,affine.tx+to->x/64.0,affine.ty-to->y/64.0);
cristy3ed852e2009-09-05 21:47:34 +00001220 (void) ConcatenateString(&draw_info->primitive,path);
1221 return(0);
1222}
1223
1224static int TraceLineTo(FT_Vector *to,DrawInfo *draw_info)
1225{
1226 AffineMatrix
1227 affine;
1228
1229 char
cristy151b66d2015-04-15 10:50:31 +00001230 path[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00001231
1232 affine=draw_info->affine;
Cristy9c282cc2015-08-29 17:37:22 -04001233 (void) FormatLocaleString(path,MagickPathExtent,"L%g,%g",affine.tx+to->x/64.0,
1234 affine.ty-to->y/64.0);
cristy3ed852e2009-09-05 21:47:34 +00001235 (void) ConcatenateString(&draw_info->primitive,path);
1236 return(0);
1237}
1238
1239static int TraceMoveTo(FT_Vector *to,DrawInfo *draw_info)
1240{
1241 AffineMatrix
1242 affine;
1243
1244 char
cristy151b66d2015-04-15 10:50:31 +00001245 path[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00001246
1247 affine=draw_info->affine;
Cristy9c282cc2015-08-29 17:37:22 -04001248 (void) FormatLocaleString(path,MagickPathExtent,"M%g,%g",affine.tx+to->x/64.0,
1249 affine.ty-to->y/64.0);
cristy3ed852e2009-09-05 21:47:34 +00001250 (void) ConcatenateString(&draw_info->primitive,path);
1251 return(0);
1252}
1253
1254static int TraceQuadraticBezier(FT_Vector *control,FT_Vector *to,
1255 DrawInfo *draw_info)
1256{
1257 AffineMatrix
1258 affine;
1259
1260 char
cristy151b66d2015-04-15 10:50:31 +00001261 path[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00001262
1263 affine=draw_info->affine;
Cristy9c282cc2015-08-29 17:37:22 -04001264 (void) FormatLocaleString(path,MagickPathExtent,"Q%g,%g %g,%g",affine.tx+
1265 control->x/64.0,affine.ty-control->y/64.0,affine.tx+to->x/64.0,affine.ty-
1266 to->y/64.0);
cristy3ed852e2009-09-05 21:47:34 +00001267 (void) ConcatenateString(&draw_info->primitive,path);
1268 return(0);
1269}
1270
1271static MagickBooleanType RenderFreetype(Image *image,const DrawInfo *draw_info,
cristy5cbc0162011-08-29 00:36:28 +00001272 const char *encoding,const PointInfo *offset,TypeMetric *metrics,
1273 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001274{
1275#if !defined(FT_OPEN_PATHNAME)
1276#define FT_OPEN_PATHNAME ft_open_pathname
1277#endif
1278
1279 typedef struct _GlyphInfo
1280 {
1281 FT_UInt
1282 id;
1283
1284 FT_Vector
1285 origin;
1286
1287 FT_Glyph
1288 image;
1289 } GlyphInfo;
1290
1291 const char
1292 *value;
1293
1294 DrawInfo
1295 *annotate_info;
1296
1297 FT_BBox
1298 bounds;
1299
1300 FT_BitmapGlyph
1301 bitmap;
1302
1303 FT_Encoding
1304 encoding_type;
1305
1306 FT_Error
cristyf8f295f2013-08-11 17:26:49 +00001307 ft_status;
cristy3ed852e2009-09-05 21:47:34 +00001308
1309 FT_Face
1310 face;
1311
1312 FT_Int32
1313 flags;
1314
1315 FT_Library
1316 library;
1317
1318 FT_Matrix
1319 affine;
1320
1321 FT_Open_Args
1322 args;
1323
1324 FT_Vector
1325 origin;
1326
1327 GlyphInfo
1328 glyph,
1329 last_glyph;
1330
Cristy17f8c722016-01-31 09:42:56 -05001331 GraphemeInfo
1332 *grapheme;
1333
cristyf8f295f2013-08-11 17:26:49 +00001334 MagickBooleanType
1335 status;
1336
cristy3ed852e2009-09-05 21:47:34 +00001337 PointInfo
1338 point,
1339 resolution;
1340
1341 register char
1342 *p;
1343
Cristy69a0d3b2015-12-22 20:40:36 -05001344 register ssize_t
1345 i;
1346
1347 size_t
1348 length;
1349
cristy9d314ff2011-03-09 01:30:28 +00001350 ssize_t
1351 code,
1352 y;
1353
cristy3ed852e2009-09-05 21:47:34 +00001354 static FT_Outline_Funcs
1355 OutlineMethods =
1356 {
1357 (FT_Outline_MoveTo_Func) TraceMoveTo,
1358 (FT_Outline_LineTo_Func) TraceLineTo,
1359 (FT_Outline_ConicTo_Func) TraceQuadraticBezier,
1360 (FT_Outline_CubicTo_Func) TraceCubicBezier,
1361 0, 0
1362 };
1363
cristy2857ffc2010-03-27 23:44:00 +00001364 unsigned char
1365 *utf8;
1366
cristy3ed852e2009-09-05 21:47:34 +00001367 /*
1368 Initialize Truetype library.
1369 */
cristyf8f295f2013-08-11 17:26:49 +00001370 ft_status=FT_Init_FreeType(&library);
1371 if (ft_status != 0)
cristy3ed852e2009-09-05 21:47:34 +00001372 ThrowBinaryException(TypeError,"UnableToInitializeFreetypeLibrary",
1373 image->filename);
1374 args.flags=FT_OPEN_PATHNAME;
1375 if (draw_info->font == (char *) NULL)
1376 args.pathname=ConstantString("helvetica");
1377 else
1378 if (*draw_info->font != '@')
1379 args.pathname=ConstantString(draw_info->font);
1380 else
1381 args.pathname=ConstantString(draw_info->font+1);
1382 face=(FT_Face) NULL;
cristyf8f295f2013-08-11 17:26:49 +00001383 ft_status=FT_Open_Face(library,&args,(long) draw_info->face,&face);
cristy3ed852e2009-09-05 21:47:34 +00001384 args.pathname=DestroyString(args.pathname);
cristyf8f295f2013-08-11 17:26:49 +00001385 if (ft_status != 0)
cristy3ed852e2009-09-05 21:47:34 +00001386 {
1387 (void) FT_Done_FreeType(library);
cristy5cbc0162011-08-29 00:36:28 +00001388 (void) ThrowMagickException(exception,GetMagickModule(),TypeError,
cristyefe601c2013-01-05 17:51:12 +00001389 "UnableToReadFont","`%s'",draw_info->font);
Cristy23f956b2016-10-01 12:21:15 -04001390 return(MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +00001391 }
1392 if ((draw_info->metrics != (char *) NULL) &&
1393 (IsPathAccessible(draw_info->metrics) != MagickFalse))
1394 (void) FT_Attach_File(face,draw_info->metrics);
1395 encoding_type=ft_encoding_unicode;
cristyf8f295f2013-08-11 17:26:49 +00001396 ft_status=FT_Select_Charmap(face,encoding_type);
1397 if ((ft_status != 0) && (face->num_charmaps != 0))
1398 ft_status=FT_Set_Charmap(face,face->charmaps[0]);
cristy3ed852e2009-09-05 21:47:34 +00001399 if (encoding != (const char *) NULL)
1400 {
1401 if (LocaleCompare(encoding,"AdobeCustom") == 0)
1402 encoding_type=ft_encoding_adobe_custom;
1403 if (LocaleCompare(encoding,"AdobeExpert") == 0)
1404 encoding_type=ft_encoding_adobe_expert;
1405 if (LocaleCompare(encoding,"AdobeStandard") == 0)
1406 encoding_type=ft_encoding_adobe_standard;
1407 if (LocaleCompare(encoding,"AppleRoman") == 0)
1408 encoding_type=ft_encoding_apple_roman;
1409 if (LocaleCompare(encoding,"BIG5") == 0)
1410 encoding_type=ft_encoding_big5;
1411 if (LocaleCompare(encoding,"GB2312") == 0)
1412 encoding_type=ft_encoding_gb2312;
1413 if (LocaleCompare(encoding,"Johab") == 0)
1414 encoding_type=ft_encoding_johab;
1415#if defined(ft_encoding_latin_1)
1416 if (LocaleCompare(encoding,"Latin-1") == 0)
1417 encoding_type=ft_encoding_latin_1;
1418#endif
1419 if (LocaleCompare(encoding,"Latin-2") == 0)
1420 encoding_type=ft_encoding_latin_2;
1421 if (LocaleCompare(encoding,"None") == 0)
1422 encoding_type=ft_encoding_none;
1423 if (LocaleCompare(encoding,"SJIScode") == 0)
1424 encoding_type=ft_encoding_sjis;
1425 if (LocaleCompare(encoding,"Symbol") == 0)
1426 encoding_type=ft_encoding_symbol;
1427 if (LocaleCompare(encoding,"Unicode") == 0)
1428 encoding_type=ft_encoding_unicode;
1429 if (LocaleCompare(encoding,"Wansung") == 0)
1430 encoding_type=ft_encoding_wansung;
cristyf8f295f2013-08-11 17:26:49 +00001431 ft_status=FT_Select_Charmap(face,encoding_type);
1432 if (ft_status != 0)
cristye3b2bb82014-02-15 13:20:08 +00001433 {
1434 (void) FT_Done_Face(face);
1435 (void) FT_Done_FreeType(library);
1436 ThrowBinaryException(TypeError,"UnrecognizedFontEncoding",encoding);
1437 }
cristy3ed852e2009-09-05 21:47:34 +00001438 }
1439 /*
1440 Set text size.
1441 */
1442 resolution.x=DefaultResolution;
1443 resolution.y=DefaultResolution;
1444 if (draw_info->density != (char *) NULL)
1445 {
1446 GeometryInfo
1447 geometry_info;
1448
1449 MagickStatusType
dirk2ed7f132016-09-19 22:49:38 +02001450 geometry_flags;
cristy3ed852e2009-09-05 21:47:34 +00001451
dirk2ed7f132016-09-19 22:49:38 +02001452 geometry_flags=ParseGeometry(draw_info->density,&geometry_info);
cristy3ed852e2009-09-05 21:47:34 +00001453 resolution.x=geometry_info.rho;
1454 resolution.y=geometry_info.sigma;
dirk2ed7f132016-09-19 22:49:38 +02001455 if ((geometry_flags & SigmaValue) == 0)
cristy3ed852e2009-09-05 21:47:34 +00001456 resolution.y=resolution.x;
1457 }
cristyf8f295f2013-08-11 17:26:49 +00001458 ft_status=FT_Set_Char_Size(face,(FT_F26Dot6) (64.0*draw_info->pointsize),
cristy3ed852e2009-09-05 21:47:34 +00001459 (FT_F26Dot6) (64.0*draw_info->pointsize),(FT_UInt) resolution.x,
1460 (FT_UInt) resolution.y);
Cristy2245bab2016-01-06 08:58:05 -05001461 if (ft_status != 0)
1462 {
1463 (void) FT_Done_Face(face);
1464 (void) FT_Done_FreeType(library);
1465 ThrowBinaryException(TypeError,"UnableToReadFont",draw_info->font);
1466 }
cristy3ed852e2009-09-05 21:47:34 +00001467 metrics->pixels_per_em.x=face->size->metrics.x_ppem;
1468 metrics->pixels_per_em.y=face->size->metrics.y_ppem;
1469 metrics->ascent=(double) face->size->metrics.ascender/64.0;
1470 metrics->descent=(double) face->size->metrics.descender/64.0;
1471 metrics->width=0;
1472 metrics->origin.x=0;
1473 metrics->origin.y=0;
1474 metrics->height=(double) face->size->metrics.height/64.0;
1475 metrics->max_advance=0.0;
1476 if (face->size->metrics.max_advance > MagickEpsilon)
1477 metrics->max_advance=(double) face->size->metrics.max_advance/64.0;
1478 metrics->bounds.x1=0.0;
1479 metrics->bounds.y1=metrics->descent;
1480 metrics->bounds.x2=metrics->ascent+metrics->descent;
1481 metrics->bounds.y2=metrics->ascent+metrics->descent;
1482 metrics->underline_position=face->underline_position/64.0;
1483 metrics->underline_thickness=face->underline_thickness/64.0;
cristy71709bf2011-09-25 13:10:29 +00001484 if ((draw_info->text == (char *) NULL) || (*draw_info->text == '\0'))
cristy3ed852e2009-09-05 21:47:34 +00001485 {
1486 (void) FT_Done_Face(face);
1487 (void) FT_Done_FreeType(library);
1488 return(MagickTrue);
1489 }
1490 /*
1491 Compute bounding box.
1492 */
1493 if (image->debug != MagickFalse)
1494 (void) LogMagickEvent(AnnotateEvent,GetMagickModule(),"Font %s; "
cristye7f51092010-01-17 00:39:37 +00001495 "font-encoding %s; text-encoding %s; pointsize %g",
cristy3ed852e2009-09-05 21:47:34 +00001496 draw_info->font != (char *) NULL ? draw_info->font : "none",
1497 encoding != (char *) NULL ? encoding : "none",
1498 draw_info->encoding != (char *) NULL ? draw_info->encoding : "none",
1499 draw_info->pointsize);
1500 flags=FT_LOAD_NO_BITMAP;
cristy55a804e2012-01-13 17:46:11 +00001501 if (draw_info->text_antialias == MagickFalse)
cristy4f9709e2012-06-10 01:54:51 +00001502 flags|=FT_LOAD_TARGET_MONO;
cristy55a804e2012-01-13 17:46:11 +00001503 else
1504 {
cristya89189d2012-01-13 17:54:57 +00001505#if defined(FT_LOAD_TARGET_LIGHT)
cristy55a804e2012-01-13 17:46:11 +00001506 flags|=FT_LOAD_TARGET_LIGHT;
cristya89189d2012-01-13 17:54:57 +00001507#elif defined(FT_LOAD_TARGET_LCD)
1508 flags|=FT_LOAD_TARGET_LCD;
cristy55a804e2012-01-13 17:46:11 +00001509#endif
1510 }
cristyd15e6592011-10-15 00:13:06 +00001511 value=GetImageProperty(image,"type:hinting",exception);
cristy33204242010-10-07 23:17:12 +00001512 if ((value != (const char *) NULL) && (LocaleCompare(value,"off") == 0))
cristy3ed852e2009-09-05 21:47:34 +00001513 flags|=FT_LOAD_NO_HINTING;
1514 glyph.id=0;
1515 glyph.image=NULL;
1516 last_glyph.id=0;
1517 last_glyph.image=NULL;
1518 origin.x=0;
1519 origin.y=0;
1520 affine.xx=65536L;
1521 affine.yx=0L;
1522 affine.xy=0L;
1523 affine.yy=65536L;
1524 if (draw_info->render != MagickFalse)
1525 {
1526 affine.xx=(FT_Fixed) (65536L*draw_info->affine.sx+0.5);
1527 affine.yx=(FT_Fixed) (-65536L*draw_info->affine.rx+0.5);
1528 affine.xy=(FT_Fixed) (-65536L*draw_info->affine.ry+0.5);
1529 affine.yy=(FT_Fixed) (65536L*draw_info->affine.sy+0.5);
1530 }
1531 annotate_info=CloneDrawInfo((ImageInfo *) NULL,draw_info);
Cristy9c282cc2015-08-29 17:37:22 -04001532 if (annotate_info->dash_pattern != (double *) NULL)
1533 annotate_info->dash_pattern[0]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001534 (void) CloneString(&annotate_info->primitive,"path '");
Cristyba4ad2d2016-06-07 09:15:26 -04001535 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +00001536 if (draw_info->render != MagickFalse)
1537 {
1538 if (image->storage_class != DirectClass)
cristy5cbc0162011-08-29 00:36:28 +00001539 (void) SetImageStorageClass(image,DirectClass,exception);
cristy17f11b02014-12-20 19:37:04 +00001540 if (image->alpha_trait == UndefinedPixelTrait)
cristya64d8002014-11-16 12:12:12 +00001541 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001542 }
1543 point.x=0.0;
1544 point.y=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001545 for (p=draw_info->text; GetUTFCode(p) != 0; p+=GetUTFOctets(p))
cristybd512462010-02-13 02:13:14 +00001546 if (GetUTFCode(p) < 0)
1547 break;
cristy2857ffc2010-03-27 23:44:00 +00001548 utf8=(unsigned char *) NULL;
cristyd913ea12010-02-13 02:32:57 +00001549 if (GetUTFCode(p) == 0)
cristy2857ffc2010-03-27 23:44:00 +00001550 p=draw_info->text;
cristybd512462010-02-13 02:13:14 +00001551 else
cristybd512462010-02-13 02:13:14 +00001552 {
cristy2857ffc2010-03-27 23:44:00 +00001553 utf8=ConvertLatin1ToUTF8((unsigned char *) draw_info->text);
1554 if (utf8 != (unsigned char *) NULL)
cristyc9b12952010-03-28 01:12:28 +00001555 p=(char *) utf8;
cristy2857ffc2010-03-27 23:44:00 +00001556 }
Cristy17f8c722016-01-31 09:42:56 -05001557 grapheme=(GraphemeInfo *) NULL;
Cristy87dc5e42016-02-05 18:52:57 -05001558 length=ComplexTextLayout(image,draw_info,p,strlen(p),face,flags,&grapheme,
Cristy5eb36082016-02-05 15:37:09 -05001559 exception);
Cristy69a0d3b2015-12-22 20:40:36 -05001560 code=0;
1561 for (i=0; i < (ssize_t) length; i++)
cristy2857ffc2010-03-27 23:44:00 +00001562 {
1563 /*
1564 Render UTF-8 sequence.
1565 */
dirk86bacde2016-03-17 22:04:25 +01001566 glyph.id=(FT_UInt) grapheme[i].index;
cristy2857ffc2010-03-27 23:44:00 +00001567 if (glyph.id == 0)
1568 glyph.id=FT_Get_Char_Index(face,'?');
1569 if ((glyph.id != 0) && (last_glyph.id != 0))
Cristy69a0d3b2015-12-22 20:40:36 -05001570 origin.x+=(FT_Pos) (64.0*draw_info->kerning);
cristy2857ffc2010-03-27 23:44:00 +00001571 glyph.origin=origin;
dirk86bacde2016-03-17 22:04:25 +01001572 glyph.origin.x+=(FT_Pos) grapheme[i].x_offset;
1573 glyph.origin.y+=(FT_Pos) grapheme[i].y_offset;
cristyf8f295f2013-08-11 17:26:49 +00001574 ft_status=FT_Load_Glyph(face,glyph.id,flags);
1575 if (ft_status != 0)
cristy2857ffc2010-03-27 23:44:00 +00001576 continue;
cristyf8f295f2013-08-11 17:26:49 +00001577 ft_status=FT_Get_Glyph(face->glyph,&glyph.image);
1578 if (ft_status != 0)
cristy2857ffc2010-03-27 23:44:00 +00001579 continue;
cristyf8f295f2013-08-11 17:26:49 +00001580 ft_status=FT_Outline_Get_BBox(&((FT_OutlineGlyph) glyph.image)->outline,
cristy2857ffc2010-03-27 23:44:00 +00001581 &bounds);
cristyf8f295f2013-08-11 17:26:49 +00001582 if (ft_status != 0)
cristy2857ffc2010-03-27 23:44:00 +00001583 continue;
1584 if ((p == draw_info->text) || (bounds.xMin < metrics->bounds.x1))
Cristy8e1dd682016-06-19 10:01:37 -04001585 if (bounds.xMin != 0)
1586 metrics->bounds.x1=(double) bounds.xMin;
cristy2857ffc2010-03-27 23:44:00 +00001587 if ((p == draw_info->text) || (bounds.yMin < metrics->bounds.y1))
Cristy8e1dd682016-06-19 10:01:37 -04001588 if (bounds.yMin != 0)
1589 metrics->bounds.y1=(double) bounds.yMin;
cristy2857ffc2010-03-27 23:44:00 +00001590 if ((p == draw_info->text) || (bounds.xMax > metrics->bounds.x2))
Cristy8e1dd682016-06-19 10:01:37 -04001591 if (bounds.xMax != 0)
1592 metrics->bounds.x2=(double) bounds.xMax;
cristy2857ffc2010-03-27 23:44:00 +00001593 if ((p == draw_info->text) || (bounds.yMax > metrics->bounds.y2))
Cristy8e1dd682016-06-19 10:01:37 -04001594 if (bounds.yMax != 0)
1595 metrics->bounds.y2=(double) bounds.yMax;
Cristy9c282cc2015-08-29 17:37:22 -04001596 if (((draw_info->stroke.alpha != TransparentAlpha) ||
1597 (draw_info->stroke_pattern != (Image *) NULL)) &&
1598 ((status != MagickFalse) && (draw_info->render != MagickFalse)))
cristyf8f295f2013-08-11 17:26:49 +00001599 {
Cristy9c282cc2015-08-29 17:37:22 -04001600 /*
1601 Trace the glyph.
1602 */
1603 annotate_info->affine.tx=glyph.origin.x/64.0;
Cristy69a0d3b2015-12-22 20:40:36 -05001604 annotate_info->affine.ty=(-glyph.origin.y/64.0);
Cristy9c282cc2015-08-29 17:37:22 -04001605 (void) FT_Outline_Decompose(&((FT_OutlineGlyph) glyph.image)->outline,
1606 &OutlineMethods,annotate_info);
1607 }
cristy2857ffc2010-03-27 23:44:00 +00001608 FT_Vector_Transform(&glyph.origin,&affine);
1609 (void) FT_Glyph_Transform(glyph.image,&affine,&glyph.origin);
cristyf8f295f2013-08-11 17:26:49 +00001610 ft_status=FT_Glyph_To_Bitmap(&glyph.image,ft_render_mode_normal,
cristy2857ffc2010-03-27 23:44:00 +00001611 (FT_Vector *) NULL,MagickTrue);
cristyf8f295f2013-08-11 17:26:49 +00001612 if (ft_status != 0)
cristy2857ffc2010-03-27 23:44:00 +00001613 continue;
1614 bitmap=(FT_BitmapGlyph) glyph.image;
1615 point.x=offset->x+bitmap->left;
cristy2f5b5c72011-09-16 16:52:00 +00001616 if (bitmap->bitmap.pixel_mode == ft_pixel_mode_mono)
1617 point.x=offset->x+(origin.x >> 6);
cristy2857ffc2010-03-27 23:44:00 +00001618 point.y=offset->y-bitmap->top;
1619 if (draw_info->render != MagickFalse)
1620 {
1621 CacheView
1622 *image_view;
cristybd512462010-02-13 02:13:14 +00001623
cristy2f5b5c72011-09-16 16:52:00 +00001624 register unsigned char
dirkfa589d62015-09-20 16:59:31 +02001625 *r;
cristy2f5b5c72011-09-16 16:52:00 +00001626
cristy2857ffc2010-03-27 23:44:00 +00001627 /*
1628 Rasterize the glyph.
1629 */
cristy46ff2672012-12-14 15:32:26 +00001630 image_view=AcquireAuthenticCacheView(image,exception);
dirkfa589d62015-09-20 16:59:31 +02001631 r=bitmap->bitmap.buffer;
cristybb503372010-05-27 20:51:26 +00001632 for (y=0; y < (ssize_t) bitmap->bitmap.rows; y++)
cristy2857ffc2010-03-27 23:44:00 +00001633 {
Cristy87dc5e42016-02-05 18:52:57 -05001634 double
1635 fill_opacity;
1636
cristybd512462010-02-13 02:13:14 +00001637 MagickBooleanType
cristy2857ffc2010-03-27 23:44:00 +00001638 active,
1639 sync;
cristybd512462010-02-13 02:13:14 +00001640
cristy101ab702011-10-13 13:06:32 +00001641 PixelInfo
cristy2857ffc2010-03-27 23:44:00 +00001642 fill_color;
cristybd512462010-02-13 02:13:14 +00001643
cristy4c08aed2011-07-01 19:47:50 +00001644 register Quantum
dirk05d2ff72015-11-18 23:13:43 +01001645 *magick_restrict q;
cristybd512462010-02-13 02:13:14 +00001646
cristy88158062011-06-09 00:31:43 +00001647 register ssize_t
1648 x;
1649
cristy73ce31b2010-06-13 23:17:39 +00001650 ssize_t
cristy2f5b5c72011-09-16 16:52:00 +00001651 n,
cristy73ce31b2010-06-13 23:17:39 +00001652 x_offset,
1653 y_offset;
1654
cristy2857ffc2010-03-27 23:44:00 +00001655 if (status == MagickFalse)
1656 continue;
cristybb503372010-05-27 20:51:26 +00001657 x_offset=(ssize_t) ceil(point.x-0.5);
1658 y_offset=(ssize_t) ceil(point.y+y-0.5);
1659 if ((y_offset < 0) || (y_offset >= (ssize_t) image->rows))
cristy2857ffc2010-03-27 23:44:00 +00001660 continue;
cristy4c08aed2011-07-01 19:47:50 +00001661 q=(Quantum *) NULL;
cristybb503372010-05-27 20:51:26 +00001662 if ((x_offset < 0) || (x_offset >= (ssize_t) image->columns))
cristy2857ffc2010-03-27 23:44:00 +00001663 active=MagickFalse;
1664 else
cristybd512462010-02-13 02:13:14 +00001665 {
cristy2857ffc2010-03-27 23:44:00 +00001666 q=GetCacheViewAuthenticPixels(image_view,x_offset,y_offset,
1667 bitmap->bitmap.width,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001668 active=q != (Quantum *) NULL ? MagickTrue : MagickFalse;
cristybd512462010-02-13 02:13:14 +00001669 }
cristy854209a2011-09-19 17:02:57 +00001670 n=y*bitmap->bitmap.pitch-1;
1671 for (x=0; x < (ssize_t) bitmap->bitmap.width; x++)
cristy2857ffc2010-03-27 23:44:00 +00001672 {
cristy854209a2011-09-19 17:02:57 +00001673 n++;
cristy2857ffc2010-03-27 23:44:00 +00001674 x_offset++;
cristy2f5b5c72011-09-16 16:52:00 +00001675 if ((x_offset < 0) || (x_offset >= (ssize_t) image->columns))
cristy2857ffc2010-03-27 23:44:00 +00001676 {
cristydcf8f732015-01-24 20:54:48 +00001677 if (q != (Quantum *) NULL)
1678 q+=GetPixelChannels(image);
cristy2857ffc2010-03-27 23:44:00 +00001679 continue;
1680 }
cristy2f5b5c72011-09-16 16:52:00 +00001681 if (bitmap->bitmap.pixel_mode != ft_pixel_mode_mono)
dirkfa589d62015-09-20 16:59:31 +02001682 fill_opacity=(double) (r[n])/(bitmap->bitmap.num_grays-1);
cristy2f5b5c72011-09-16 16:52:00 +00001683 else
dirkfa589d62015-09-20 16:59:31 +02001684 fill_opacity=((r[(x >> 3)+y*bitmap->bitmap.pitch] &
cristy2f5b5c72011-09-16 16:52:00 +00001685 (1 << (~x & 0x07)))) == 0 ? 0.0 : 1.0;
cristy2857ffc2010-03-27 23:44:00 +00001686 if (draw_info->text_antialias == MagickFalse)
1687 fill_opacity=fill_opacity >= 0.5 ? 1.0 : 0.0;
1688 if (active == MagickFalse)
1689 q=GetCacheViewAuthenticPixels(image_view,x_offset,y_offset,1,1,
1690 exception);
cristy6193b862011-08-06 16:33:59 +00001691 if (q == (Quantum *) NULL)
cristy9c5171c2014-05-25 21:26:39 +00001692 continue;
cristyec061ec2011-10-21 17:41:17 +00001693 GetPixelInfo(image,&fill_color);
dirk50be5382016-03-14 12:42:50 +01001694 GetFillColor(draw_info,x_offset,y_offset,&fill_color,exception);
cristy4c08aed2011-07-01 19:47:50 +00001695 fill_opacity=fill_opacity*fill_color.alpha;
1696 CompositePixelOver(image,&fill_color,fill_opacity,q,
1697 GetPixelAlpha(image,q),q);
cristy2857ffc2010-03-27 23:44:00 +00001698 if (active == MagickFalse)
1699 {
1700 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1701 if (sync == MagickFalse)
1702 status=MagickFalse;
1703 }
cristyed231572011-07-14 02:18:59 +00001704 q+=GetPixelChannels(image);
cristybd512462010-02-13 02:13:14 +00001705 }
cristy2857ffc2010-03-27 23:44:00 +00001706 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1707 if (sync == MagickFalse)
1708 status=MagickFalse;
cristybd512462010-02-13 02:13:14 +00001709 }
cristy2857ffc2010-03-27 23:44:00 +00001710 image_view=DestroyCacheView(image_view);
Cristy9c282cc2015-08-29 17:37:22 -04001711 if (((draw_info->stroke.alpha != TransparentAlpha) ||
1712 (draw_info->stroke_pattern != (Image *) NULL)) &&
1713 (status != MagickFalse))
1714 {
1715 /*
1716 Draw text stroke.
1717 */
1718 annotate_info->linejoin=RoundJoin;
1719 annotate_info->affine.tx=offset->x;
1720 annotate_info->affine.ty=offset->y;
1721 (void) ConcatenateString(&annotate_info->primitive,"'");
1722 (void) DrawImage(image,annotate_info,exception);
1723 (void) CloneString(&annotate_info->primitive,"path '");
1724 }
cristy2857ffc2010-03-27 23:44:00 +00001725 }
1726 if ((bitmap->left+bitmap->bitmap.width) > metrics->width)
1727 metrics->width=bitmap->left+bitmap->bitmap.width;
cristyaa83c2c2011-09-21 13:36:25 +00001728 if ((fabs(draw_info->interword_spacing) >= MagickEpsilon) &&
Cristy91c27412015-12-23 15:37:08 -05001729 (IsUTFSpace(GetUTFCode(p+grapheme[i].cluster)) != MagickFalse) &&
cristy2857ffc2010-03-27 23:44:00 +00001730 (IsUTFSpace(code) == MagickFalse))
Cristy69a0d3b2015-12-22 20:40:36 -05001731 origin.x+=(FT_Pos) (64.0*draw_info->interword_spacing);
cristy2857ffc2010-03-27 23:44:00 +00001732 else
Cristy91c27412015-12-23 15:37:08 -05001733 origin.x+=(FT_Pos) grapheme[i].x_advance;
cristyaa83c2c2011-09-21 13:36:25 +00001734 metrics->origin.x=(double) origin.x;
1735 metrics->origin.y=(double) origin.y;
cristybd5a96c2011-08-21 00:04:26 +00001736 if (last_glyph.id != 0)
1737 FT_Done_Glyph(last_glyph.image);
cristy2857ffc2010-03-27 23:44:00 +00001738 last_glyph=glyph;
Cristy91c27412015-12-23 15:37:08 -05001739 code=GetUTFCode(p+grapheme[i].cluster);
cristy2857ffc2010-03-27 23:44:00 +00001740 }
Cristy17f8c722016-01-31 09:42:56 -05001741 if (grapheme != (GraphemeInfo *) NULL)
1742 grapheme=(GraphemeInfo *) RelinquishMagickMemory(grapheme);
cristy2857ffc2010-03-27 23:44:00 +00001743 if (utf8 != (unsigned char *) NULL)
1744 utf8=(unsigned char *) RelinquishMagickMemory(utf8);
cristybd5a96c2011-08-21 00:04:26 +00001745 if (last_glyph.id != 0)
1746 FT_Done_Glyph(last_glyph.image);
cristy3ed852e2009-09-05 21:47:34 +00001747 /*
1748 Determine font metrics.
1749 */
1750 glyph.id=FT_Get_Char_Index(face,'_');
1751 glyph.origin=origin;
cristyf8f295f2013-08-11 17:26:49 +00001752 ft_status=FT_Load_Glyph(face,glyph.id,flags);
1753 if (ft_status == 0)
cristy3ed852e2009-09-05 21:47:34 +00001754 {
cristyf8f295f2013-08-11 17:26:49 +00001755 ft_status=FT_Get_Glyph(face->glyph,&glyph.image);
1756 if (ft_status == 0)
cristy3ed852e2009-09-05 21:47:34 +00001757 {
cristyf8f295f2013-08-11 17:26:49 +00001758 ft_status=FT_Outline_Get_BBox(&((FT_OutlineGlyph) glyph.image)->
1759 outline,&bounds);
1760 if (ft_status == 0)
cristy3ed852e2009-09-05 21:47:34 +00001761 {
1762 FT_Vector_Transform(&glyph.origin,&affine);
1763 (void) FT_Glyph_Transform(glyph.image,&affine,&glyph.origin);
cristyf8f295f2013-08-11 17:26:49 +00001764 ft_status=FT_Glyph_To_Bitmap(&glyph.image,ft_render_mode_normal,
cristy3ed852e2009-09-05 21:47:34 +00001765 (FT_Vector *) NULL,MagickTrue);
1766 bitmap=(FT_BitmapGlyph) glyph.image;
1767 if (bitmap->left > metrics->width)
1768 metrics->width=bitmap->left;
1769 }
1770 }
cristye2c81ad2011-08-20 22:54:14 +00001771 FT_Done_Glyph(glyph.image);
cristy3ed852e2009-09-05 21:47:34 +00001772 }
cristy3ed852e2009-09-05 21:47:34 +00001773 metrics->bounds.x1/=64.0;
1774 metrics->bounds.y1/=64.0;
1775 metrics->bounds.x2/=64.0;
1776 metrics->bounds.y2/=64.0;
1777 metrics->origin.x/=64.0;
1778 metrics->origin.y/=64.0;
1779 /*
1780 Relinquish resources.
1781 */
1782 annotate_info=DestroyDrawInfo(annotate_info);
1783 (void) FT_Done_Face(face);
1784 (void) FT_Done_FreeType(library);
cristyf8f295f2013-08-11 17:26:49 +00001785 return(status);
cristy3ed852e2009-09-05 21:47:34 +00001786}
1787#else
1788static MagickBooleanType RenderFreetype(Image *image,const DrawInfo *draw_info,
1789 const char *magick_unused(encoding),const PointInfo *offset,
cristy5cbc0162011-08-29 00:36:28 +00001790 TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001791{
cristy5cbc0162011-08-29 00:36:28 +00001792 (void) ThrowMagickException(exception,GetMagickModule(),
anthonye5b39652012-04-21 05:37:29 +00001793 MissingDelegateWarning,"DelegateLibrarySupportNotBuiltIn","'%s' (Freetype)",
cristyee081822011-02-02 19:07:30 +00001794 draw_info->font != (char *) NULL ? draw_info->font : "none");
cristye3b062a2011-12-17 18:34:52 +00001795 return(RenderPostscript(image,draw_info,offset,metrics,exception));
cristy3ed852e2009-09-05 21:47:34 +00001796}
1797#endif
1798
1799/*
1800%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1801% %
1802% %
1803% %
1804+ R e n d e r P o s t s c r i p t %
1805% %
1806% %
1807% %
1808%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1809%
1810% RenderPostscript() renders text on the image with a Postscript font. It
1811% also returns the bounding box of the text relative to the image.
1812%
1813% The format of the RenderPostscript method is:
1814%
1815% MagickBooleanType RenderPostscript(Image *image,DrawInfo *draw_info,
cristy5cbc0162011-08-29 00:36:28 +00001816% const PointInfo *offset,TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001817%
1818% A description of each parameter follows:
1819%
1820% o image: the image.
1821%
1822% o draw_info: the draw info.
1823%
1824% o offset: (x,y) location of text relative to image.
1825%
1826% o metrics: bounding box of text.
1827%
cristy5cbc0162011-08-29 00:36:28 +00001828% o exception: return any errors or warnings in this structure.
1829%
cristy3ed852e2009-09-05 21:47:34 +00001830*/
1831
Cristy34838f52016-07-09 20:54:46 -04001832static char *EscapeParenthesis(const char *source)
cristy3ed852e2009-09-05 21:47:34 +00001833{
1834 char
Cristy34838f52016-07-09 20:54:46 -04001835 *destination;
cristy3ed852e2009-09-05 21:47:34 +00001836
1837 register char
Cristy34838f52016-07-09 20:54:46 -04001838 *q;
1839
1840 register const char
cristy3ed852e2009-09-05 21:47:34 +00001841 *p;
1842
cristy3ed852e2009-09-05 21:47:34 +00001843 size_t
Cristy34838f52016-07-09 20:54:46 -04001844 length;
cristy3ed852e2009-09-05 21:47:34 +00001845
Cristy34838f52016-07-09 20:54:46 -04001846 assert(source != (const char *) NULL);
1847 length=0;
1848 for (p=source; *p != '\0'; p++)
cristy3ed852e2009-09-05 21:47:34 +00001849 {
Cristy34838f52016-07-09 20:54:46 -04001850 if ((*p == '\\') || (*p == '(') || (*p == ')'))
cristy3ed852e2009-09-05 21:47:34 +00001851 {
Cristy34838f52016-07-09 20:54:46 -04001852 if (~length < 1)
1853 ThrowFatalException(ResourceLimitFatalError,"UnableToEscapeString");
1854 length++;
cristy3ed852e2009-09-05 21:47:34 +00001855 }
Cristy34838f52016-07-09 20:54:46 -04001856 length++;
cristy3ed852e2009-09-05 21:47:34 +00001857 }
Cristy34838f52016-07-09 20:54:46 -04001858 destination=(char *) NULL;
1859 if (~length >= (MagickPathExtent-1))
1860 destination=(char *) AcquireQuantumMemory(length+MagickPathExtent,
1861 sizeof(*destination));
1862 if (destination == (char *) NULL)
1863 ThrowFatalException(ResourceLimitFatalError,"UnableToEscapeString");
1864 *destination='\0';
1865 q=destination;
1866 for (p=source; *p != '\0'; p++)
1867 {
1868 if ((*p == '\\') || (*p == '(') || (*p == ')'))
1869 *q++='\\';
1870 *q++=(*p);
1871 }
1872 *q='\0';
1873 return(destination);
cristy3ed852e2009-09-05 21:47:34 +00001874}
1875
1876static MagickBooleanType RenderPostscript(Image *image,
cristy5cbc0162011-08-29 00:36:28 +00001877 const DrawInfo *draw_info,const PointInfo *offset,TypeMetric *metrics,
1878 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001879{
1880 char
cristy151b66d2015-04-15 10:50:31 +00001881 filename[MagickPathExtent],
1882 geometry[MagickPathExtent],
cristy3ed852e2009-09-05 21:47:34 +00001883 *text;
1884
1885 FILE
1886 *file;
1887
1888 Image
1889 *annotate_image;
1890
1891 ImageInfo
1892 *annotate_info;
1893
1894 int
1895 unique_file;
1896
cristy3ed852e2009-09-05 21:47:34 +00001897 MagickBooleanType
1898 identity;
1899
1900 PointInfo
1901 extent,
1902 point,
1903 resolution;
1904
cristybb503372010-05-27 20:51:26 +00001905 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001906 i;
1907
cristy14d71292012-05-20 16:48:13 +00001908 size_t
1909 length;
1910
cristy9d314ff2011-03-09 01:30:28 +00001911 ssize_t
1912 y;
1913
cristy3ed852e2009-09-05 21:47:34 +00001914 /*
1915 Render label with a Postscript font.
1916 */
1917 if (image->debug != MagickFalse)
1918 (void) LogMagickEvent(AnnotateEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +00001919 "Font %s; pointsize %g",draw_info->font != (char *) NULL ?
cristy3ed852e2009-09-05 21:47:34 +00001920 draw_info->font : "none",draw_info->pointsize);
1921 file=(FILE *) NULL;
1922 unique_file=AcquireUniqueFileResource(filename);
1923 if (unique_file != -1)
1924 file=fdopen(unique_file,"wb");
1925 if ((unique_file == -1) || (file == (FILE *) NULL))
1926 {
cristy5cbc0162011-08-29 00:36:28 +00001927 ThrowFileException(exception,FileOpenError,"UnableToOpenFile",filename);
cristy3ed852e2009-09-05 21:47:34 +00001928 return(MagickFalse);
1929 }
cristyb51dff52011-05-19 16:55:47 +00001930 (void) FormatLocaleFile(file,"%%!PS-Adobe-3.0\n");
1931 (void) FormatLocaleFile(file,"/ReencodeType\n");
1932 (void) FormatLocaleFile(file,"{\n");
1933 (void) FormatLocaleFile(file," findfont dup length\n");
1934 (void) FormatLocaleFile(file,
cristy3ed852e2009-09-05 21:47:34 +00001935 " dict begin { 1 index /FID ne {def} {pop pop} ifelse } forall\n");
cristyb51dff52011-05-19 16:55:47 +00001936 (void) FormatLocaleFile(file,
cristy3ed852e2009-09-05 21:47:34 +00001937 " /Encoding ISOLatin1Encoding def currentdict end definefont pop\n");
cristyb51dff52011-05-19 16:55:47 +00001938 (void) FormatLocaleFile(file,"} bind def\n");
cristy3ed852e2009-09-05 21:47:34 +00001939 /*
1940 Sample to compute bounding box.
1941 */
cristyaa83c2c2011-09-21 13:36:25 +00001942 identity=(fabs(draw_info->affine.sx-draw_info->affine.sy) < MagickEpsilon) &&
1943 (fabs(draw_info->affine.rx) < MagickEpsilon) &&
1944 (fabs(draw_info->affine.ry) < MagickEpsilon) ? MagickTrue : MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +00001945 extent.x=0.0;
1946 extent.y=0.0;
cristy14d71292012-05-20 16:48:13 +00001947 length=strlen(draw_info->text);
1948 for (i=0; i <= (ssize_t) (length+2); i++)
cristy3ed852e2009-09-05 21:47:34 +00001949 {
1950 point.x=fabs(draw_info->affine.sx*i*draw_info->pointsize+
1951 draw_info->affine.ry*2.0*draw_info->pointsize);
1952 point.y=fabs(draw_info->affine.rx*i*draw_info->pointsize+
1953 draw_info->affine.sy*2.0*draw_info->pointsize);
1954 if (point.x > extent.x)
1955 extent.x=point.x;
1956 if (point.y > extent.y)
1957 extent.y=point.y;
1958 }
cristyb51dff52011-05-19 16:55:47 +00001959 (void) FormatLocaleFile(file,"%g %g moveto\n",identity != MagickFalse ? 0.0 :
cristy3ed852e2009-09-05 21:47:34 +00001960 extent.x/2.0,extent.y/2.0);
cristyb51dff52011-05-19 16:55:47 +00001961 (void) FormatLocaleFile(file,"%g %g scale\n",draw_info->pointsize,
cristy3ed852e2009-09-05 21:47:34 +00001962 draw_info->pointsize);
1963 if ((draw_info->font == (char *) NULL) || (*draw_info->font == '\0') ||
1964 (strchr(draw_info->font,'/') != (char *) NULL))
cristyb51dff52011-05-19 16:55:47 +00001965 (void) FormatLocaleFile(file,
cristy3ed852e2009-09-05 21:47:34 +00001966 "/Times-Roman-ISO dup /Times-Roman ReencodeType findfont setfont\n");
1967 else
cristy1e604812011-05-19 18:07:50 +00001968 (void) FormatLocaleFile(file,
1969 "/%s-ISO dup /%s ReencodeType findfont setfont\n",draw_info->font,
1970 draw_info->font);
cristyb51dff52011-05-19 16:55:47 +00001971 (void) FormatLocaleFile(file,"[%g %g %g %g 0 0] concat\n",
cristy8cd5b312010-01-07 01:10:24 +00001972 draw_info->affine.sx,-draw_info->affine.rx,-draw_info->affine.ry,
1973 draw_info->affine.sy);
cristy3ed852e2009-09-05 21:47:34 +00001974 text=EscapeParenthesis(draw_info->text);
1975 if (identity == MagickFalse)
cristy1e604812011-05-19 18:07:50 +00001976 (void) FormatLocaleFile(file,"(%s) stringwidth pop -0.5 mul -0.5 rmoveto\n",
1977 text);
cristyb51dff52011-05-19 16:55:47 +00001978 (void) FormatLocaleFile(file,"(%s) show\n",text);
cristy3ed852e2009-09-05 21:47:34 +00001979 text=DestroyString(text);
cristyb51dff52011-05-19 16:55:47 +00001980 (void) FormatLocaleFile(file,"showpage\n");
cristy3ed852e2009-09-05 21:47:34 +00001981 (void) fclose(file);
cristy151b66d2015-04-15 10:50:31 +00001982 (void) FormatLocaleString(geometry,MagickPathExtent,"%.20gx%.20g+0+0!",
cristye8c25f92010-06-03 00:53:06 +00001983 floor(extent.x+0.5),floor(extent.y+0.5));
cristy3ed852e2009-09-05 21:47:34 +00001984 annotate_info=AcquireImageInfo();
cristy151b66d2015-04-15 10:50:31 +00001985 (void) FormatLocaleString(annotate_info->filename,MagickPathExtent,"ps:%s",
cristy3ed852e2009-09-05 21:47:34 +00001986 filename);
1987 (void) CloneString(&annotate_info->page,geometry);
1988 if (draw_info->density != (char *) NULL)
1989 (void) CloneString(&annotate_info->density,draw_info->density);
1990 annotate_info->antialias=draw_info->text_antialias;
cristy5cbc0162011-08-29 00:36:28 +00001991 annotate_image=ReadImage(annotate_info,exception);
1992 CatchException(exception);
cristy3ed852e2009-09-05 21:47:34 +00001993 annotate_info=DestroyImageInfo(annotate_info);
1994 (void) RelinquishUniqueFileResource(filename);
1995 if (annotate_image == (Image *) NULL)
1996 return(MagickFalse);
cristy55cb9f12012-08-23 09:02:28 +00001997 (void) NegateImage(annotate_image,MagickFalse,exception);
cristy3ed852e2009-09-05 21:47:34 +00001998 resolution.x=DefaultResolution;
1999 resolution.y=DefaultResolution;
2000 if (draw_info->density != (char *) NULL)
2001 {
2002 GeometryInfo
2003 geometry_info;
2004
2005 MagickStatusType
2006 flags;
2007
2008 flags=ParseGeometry(draw_info->density,&geometry_info);
2009 resolution.x=geometry_info.rho;
2010 resolution.y=geometry_info.sigma;
2011 if ((flags & SigmaValue) == 0)
2012 resolution.y=resolution.x;
2013 }
2014 if (identity == MagickFalse)
cristye941a752011-10-15 01:52:48 +00002015 (void) TransformImage(&annotate_image,"0x0",(char *) NULL,exception);
cristy3ed852e2009-09-05 21:47:34 +00002016 else
2017 {
2018 RectangleInfo
2019 crop_info;
2020
cristy5cbc0162011-08-29 00:36:28 +00002021 crop_info=GetImageBoundingBox(annotate_image,exception);
cristybb503372010-05-27 20:51:26 +00002022 crop_info.height=(size_t) ((resolution.y/DefaultResolution)*
cristy3ed852e2009-09-05 21:47:34 +00002023 ExpandAffine(&draw_info->affine)*draw_info->pointsize+0.5);
cristybb503372010-05-27 20:51:26 +00002024 crop_info.y=(ssize_t) ceil((resolution.y/DefaultResolution)*extent.y/8.0-
cristy06609ee2010-03-17 20:21:27 +00002025 0.5);
cristy151b66d2015-04-15 10:50:31 +00002026 (void) FormatLocaleString(geometry,MagickPathExtent,
cristy6d8abba2010-06-03 01:10:47 +00002027 "%.20gx%.20g%+.20g%+.20g",(double) crop_info.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002028 crop_info.height,(double) crop_info.x,(double) crop_info.y);
cristye941a752011-10-15 01:52:48 +00002029 (void) TransformImage(&annotate_image,geometry,(char *) NULL,exception);
cristy3ed852e2009-09-05 21:47:34 +00002030 }
2031 metrics->pixels_per_em.x=(resolution.y/DefaultResolution)*
2032 ExpandAffine(&draw_info->affine)*draw_info->pointsize;
2033 metrics->pixels_per_em.y=metrics->pixels_per_em.x;
2034 metrics->ascent=metrics->pixels_per_em.x;
2035 metrics->descent=metrics->pixels_per_em.y/-5.0;
2036 metrics->width=(double) annotate_image->columns/
2037 ExpandAffine(&draw_info->affine);
2038 metrics->height=1.152*metrics->pixels_per_em.x;
2039 metrics->max_advance=metrics->pixels_per_em.x;
2040 metrics->bounds.x1=0.0;
2041 metrics->bounds.y1=metrics->descent;
2042 metrics->bounds.x2=metrics->ascent+metrics->descent;
2043 metrics->bounds.y2=metrics->ascent+metrics->descent;
2044 metrics->underline_position=(-2.0);
2045 metrics->underline_thickness=1.0;
2046 if (draw_info->render == MagickFalse)
2047 {
2048 annotate_image=DestroyImage(annotate_image);
2049 return(MagickTrue);
2050 }
cristy4c08aed2011-07-01 19:47:50 +00002051 if (draw_info->fill.alpha != TransparentAlpha)
cristy3ed852e2009-09-05 21:47:34 +00002052 {
cristybd659482011-10-09 23:19:32 +00002053 CacheView
2054 *annotate_view;
2055
cristy3ed852e2009-09-05 21:47:34 +00002056 MagickBooleanType
2057 sync;
2058
cristy101ab702011-10-13 13:06:32 +00002059 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002060 fill_color;
2061
cristy3ed852e2009-09-05 21:47:34 +00002062 /*
2063 Render fill color.
2064 */
cristy17f11b02014-12-20 19:37:04 +00002065 if (image->alpha_trait == UndefinedPixelTrait)
cristy63240882011-08-05 19:05:27 +00002066 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy17f11b02014-12-20 19:37:04 +00002067 if (annotate_image->alpha_trait == UndefinedPixelTrait)
cristy63240882011-08-05 19:05:27 +00002068 (void) SetImageAlphaChannel(annotate_image,OpaqueAlphaChannel,
2069 exception);
2070 fill_color=draw_info->fill;
cristy46ff2672012-12-14 15:32:26 +00002071 annotate_view=AcquireAuthenticCacheView(annotate_image,exception);
cristybb503372010-05-27 20:51:26 +00002072 for (y=0; y < (ssize_t) annotate_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002073 {
cristybb503372010-05-27 20:51:26 +00002074 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002075 x;
2076
cristy4c08aed2011-07-01 19:47:50 +00002077 register Quantum
dirk05d2ff72015-11-18 23:13:43 +01002078 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002079
2080 q=GetCacheViewAuthenticPixels(annotate_view,0,y,annotate_image->columns,
2081 1,exception);
cristy6193b862011-08-06 16:33:59 +00002082 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002083 break;
cristybb503372010-05-27 20:51:26 +00002084 for (x=0; x < (ssize_t) annotate_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002085 {
dirk50be5382016-03-14 12:42:50 +01002086 GetFillColor(draw_info,x,y,&fill_color,exception);
cristy55cb9f12012-08-23 09:02:28 +00002087 SetPixelAlpha(annotate_image,ClampToQuantum((((double) QuantumScale*
2088 GetPixelIntensity(annotate_image,q)*fill_color.alpha))),q);
cristy4c08aed2011-07-01 19:47:50 +00002089 SetPixelRed(annotate_image,fill_color.red,q);
2090 SetPixelGreen(annotate_image,fill_color.green,q);
2091 SetPixelBlue(annotate_image,fill_color.blue,q);
cristyed231572011-07-14 02:18:59 +00002092 q+=GetPixelChannels(annotate_image);
cristy3ed852e2009-09-05 21:47:34 +00002093 }
2094 sync=SyncCacheViewAuthenticPixels(annotate_view,exception);
2095 if (sync == MagickFalse)
2096 break;
2097 }
2098 annotate_view=DestroyCacheView(annotate_view);
cristy39172402012-03-30 13:04:39 +00002099 (void) CompositeImage(image,annotate_image,OverCompositeOp,MagickTrue,
cristybb503372010-05-27 20:51:26 +00002100 (ssize_t) ceil(offset->x-0.5),(ssize_t) ceil(offset->y-(metrics->ascent+
cristye941a752011-10-15 01:52:48 +00002101 metrics->descent)-0.5),exception);
cristy3ed852e2009-09-05 21:47:34 +00002102 }
2103 annotate_image=DestroyImage(annotate_image);
2104 return(MagickTrue);
2105}
2106
2107/*
2108%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2109% %
2110% %
2111% %
2112+ R e n d e r X 1 1 %
2113% %
2114% %
2115% %
2116%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2117%
2118% RenderX11() renders text on the image with an X11 font. It also returns the
2119% bounding box of the text relative to the image.
2120%
2121% The format of the RenderX11 method is:
2122%
2123% MagickBooleanType RenderX11(Image *image,DrawInfo *draw_info,
cristy5cbc0162011-08-29 00:36:28 +00002124% const PointInfo *offset,TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002125%
2126% A description of each parameter follows:
2127%
2128% o image: the image.
2129%
2130% o draw_info: the draw info.
2131%
2132% o offset: (x,y) location of text relative to image.
2133%
2134% o metrics: bounding box of text.
2135%
cristy5cbc0162011-08-29 00:36:28 +00002136% o exception: return any errors or warnings in this structure.
2137%
cristy3ed852e2009-09-05 21:47:34 +00002138*/
cristycc168c22013-04-18 14:42:31 +00002139static MagickBooleanType RenderX11(Image *image,const DrawInfo *draw_info,
2140 const PointInfo *offset,TypeMetric *metrics,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002141{
2142 MagickBooleanType
2143 status;
2144
cristyd7ecaca2011-01-24 14:14:53 +00002145 if (annotate_semaphore == (SemaphoreInfo *) NULL)
cristy04b11db2014-02-16 15:10:39 +00002146 ActivateSemaphoreInfo(&annotate_semaphore);
cristyd7ecaca2011-01-24 14:14:53 +00002147 LockSemaphoreInfo(annotate_semaphore);
cristy534d0a32013-04-13 16:27:24 +00002148 status=XRenderImage(image,draw_info,offset,metrics,exception);
cristyd7ecaca2011-01-24 14:14:53 +00002149 UnlockSemaphoreInfo(annotate_semaphore);
cristy534d0a32013-04-13 16:27:24 +00002150 return(status);
cristy3ed852e2009-09-05 21:47:34 +00002151}