]> granicus.if.org Git - imagemagick/blob - MagickCore/nt-feature.c
(no commit message)
[imagemagick] / MagickCore / nt-feature.c
1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 %                                                                             %
4 %                                                                             %
5 %                                                                             %
6 %                                                                             %
7 %                                 N   N  TTTTT                                %
8 %                                 NN  N    T                                  %
9 %                                 N N N    T                                  %
10 %                                 N  NN    T                                  %
11 %                                 N   N    T                                  %
12 %                                                                             %
13 %                                                                             %
14 %                   Windows NT Feature Methods for MagickCore                 %
15 %                                                                             %
16 %                               Software Design                               %
17 %                                 John Cristy                                 %
18 %                                December 1996                                %
19 %                                                                             %
20 %                                                                             %
21 %  Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization      %
22 %  dedicated to making software imaging solutions freely available.           %
23 %                                                                             %
24 %  You may not use this file except in compliance with the License.  You may  %
25 %  obtain a copy of the License at                                            %
26 %                                                                             %
27 %    http://www.imagemagick.org/script/license.php                            %
28 %                                                                             %
29 %  Unless required by applicable law or agreed to in writing, software        %
30 %  distributed under the License is distributed on an "AS IS" BASIS,          %
31 %  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
32 %  See the License for the specific language governing permissions and        %
33 %  limitations under the License.                                             %
34 %                                                                             %
35 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36 %
37 %
38 */
39 \f
40 /*
41   Include declarations.
42 */
43 #include "MagickCore/studio.h"
44 #if defined(MAGICKCORE_WINDOWS_SUPPORT) || defined(__CYGWIN__)
45 #define WIN32_LEAN_AND_MEAN
46 #define VC_EXTRALEAN
47 #include <windows.h>
48 #include "MagickCore/cache.h"
49 #include "MagickCore/colorspace.h"
50 #include "MagickCore/colorspace-private.h"
51 #include "MagickCore/draw.h"
52 #include "MagickCore/exception.h"
53 #include "MagickCore/exception-private.h"
54 #include "MagickCore/image-private.h"
55 #include "MagickCore/memory_.h"
56 #include "MagickCore/monitor.h"
57 #include "MagickCore/monitor-private.h"
58 #include "MagickCore/pixel-accessor.h"
59 #include "MagickCore/quantum.h"
60 #include "MagickCore/string_.h"
61 #include "MagickCore/token.h"
62 #include "MagickCore/splay-tree.h"
63 #include "MagickCore/utility.h"
64 #include "MagickCore/nt-feature.h"
65 \f
66 /*
67 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
68 %                                                                             %
69 %                                                                             %
70 %                                                                             %
71 %   C r o p I m a g e T o H B i t m a p                                       %
72 %                                                                             %
73 %                                                                             %
74 %                                                                             %
75 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76 %
77 %  CropImageToHBITMAP() extracts a specified region of the image and returns
78 %  it as a Windows HBITMAP. While the same functionality can be accomplished by
79 %  invoking CropImage() followed by ImageToHBITMAP(), this method is more
80 %  efficient since it copies pixels directly to the HBITMAP.
81 %
82 %  The format of the CropImageToHBITMAP method is:
83 %
84 %      HBITMAP CropImageToHBITMAP(Image* image,const RectangleInfo *geometry,
85 %        ExceptionInfo *exception)
86 %
87 %  A description of each parameter follows:
88 %
89 %    o image: the image.
90 %
91 %    o geometry: Define the region of the image to crop with members
92 %      x, y, width, and height.
93 %
94 %    o exception: return any errors or warnings in this structure.
95 %
96 */
97 MagickExport void *CropImageToHBITMAP(Image *image,
98   const RectangleInfo *geometry,ExceptionInfo *exception)
99 {
100 #define CropImageTag  "Crop/Image"
101
102   BITMAP
103     bitmap;
104
105   HBITMAP
106     bitmapH;
107
108   HANDLE
109     bitmap_bitsH;
110
111   MagickBooleanType
112     proceed;
113
114   RectangleInfo
115     page;
116
117   register const Quantum
118     *p;
119
120   register RGBQUAD
121     *q;
122
123   RGBQUAD
124     *bitmap_bits;
125
126   ssize_t
127     y;
128
129   /*
130     Check crop geometry.
131   */
132   assert(image != (const Image *) NULL);
133   assert(image->signature == MagickSignature);
134   if (image->debug != MagickFalse)
135     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
136   assert(geometry != (const RectangleInfo *) NULL);
137   assert(exception != (ExceptionInfo *) NULL);
138   assert(exception->signature == MagickSignature);
139   if (((geometry->x+(ssize_t) geometry->width) < 0) ||
140       ((geometry->y+(ssize_t) geometry->height) < 0) ||
141       (geometry->x >= (ssize_t) image->columns) ||
142       (geometry->y >= (ssize_t) image->rows))
143     ThrowImageException(OptionError,"GeometryDoesNotContainImage");
144   page=(*geometry);
145   if ((page.x+(ssize_t) page.width) > (ssize_t) image->columns)
146     page.width=image->columns-page.x;
147   if ((page.y+(ssize_t) page.height) > (ssize_t) image->rows)
148     page.height=image->rows-page.y;
149   if (page.x < 0)
150     {
151       page.width+=page.x;
152       page.x=0;
153     }
154   if (page.y < 0)
155     {
156       page.height+=page.y;
157       page.y=0;
158     }
159
160   if ((page.width == 0) || (page.height == 0))
161     ThrowImageException(OptionError,"GeometryDimensionsAreZero");
162   /*
163     Initialize crop image attributes.
164   */
165   bitmap.bmType         = 0;
166   bitmap.bmWidth        = (LONG) page.width;
167   bitmap.bmHeight       = (LONG) page.height;
168   bitmap.bmWidthBytes   = bitmap.bmWidth * 4;
169   bitmap.bmPlanes       = 1;
170   bitmap.bmBitsPixel    = 32;
171   bitmap.bmBits         = NULL;
172
173   bitmap_bitsH=(HANDLE) GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,page.width*
174     page.height*bitmap.bmBitsPixel);
175   if (bitmap_bitsH == NULL)
176     return(NULL);
177   bitmap_bits=(RGBQUAD *) GlobalLock((HGLOBAL) bitmap_bitsH);
178   if ( bitmap.bmBits == NULL )
179     bitmap.bmBits = bitmap_bits;
180   if (IsRGBColorspace(image->colorspace) == MagickFalse)
181     TransformImageColorspace(image,RGBColorspace);
182   /*
183     Extract crop image.
184   */
185   q=bitmap_bits;
186   for (y=0; y < (ssize_t) page.height; y++)
187   {
188     p=GetVirtualPixels(image,page.x,page.y+y,page.width,1,exception);
189     if (p == (const Quantum *) NULL)
190       break;
191
192 #if MAGICKCORE_QUANTUM_DEPTH == 8
193       /* Form of PixelPacket is identical to RGBQUAD when MAGICKCORE_QUANTUM_DEPTH==8 */
194       CopyMagickMemory((void*)q,(const void*)p,page.width*sizeof(PixelPacket));
195       q += page.width;
196
197 #else  /* 16 or 32 bit Quantum */
198       {
199         ssize_t
200           x;
201
202         /* Transfer pixels, scaling to Quantum */
203         for( x=(ssize_t) page.width ; x> 0 ; x-- )
204           {
205             q->rgbRed = ScaleQuantumToChar(GetPixelRed(image,p));
206             q->rgbGreen = ScaleQuantumToChar(GetPixelGreen(image,p));
207             q->rgbBlue = ScaleQuantumToChar(GetPixelBlue(image,p));
208             q->rgbReserved = 0;
209             ++q;
210             ++p;
211           }
212       }
213 #endif
214     proceed=SetImageProgress(image,CropImageTag,y,page.height);
215     if (proceed == MagickFalse)
216       break;
217   }
218   if (y < (ssize_t) page.height)
219     {
220       GlobalUnlock((HGLOBAL) bitmap_bitsH);
221       GlobalFree((HGLOBAL) bitmap_bitsH);
222       return((void *) NULL);
223     }
224   bitmap.bmBits=bitmap_bits;
225   bitmapH=CreateBitmapIndirect(&bitmap);
226   GlobalUnlock((HGLOBAL) bitmap_bitsH);
227   GlobalFree((HGLOBAL) bitmap_bitsH);
228   return((void *) bitmapH);
229 }
230 \f
231 /*
232 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
233 %                                                                             %
234 %                                                                             %
235 %                                                                             %
236 %   I s M a g i c k C o n f l i c t                                           %
237 %                                                                             %
238 %                                                                             %
239 %                                                                             %
240 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
241 %
242 %  IsMagickConflict() returns true if the image format conflicts with a logical
243 %  drive (.e.g. X:).
244 %
245 %  The format of the IsMagickConflict method is:
246 %
247 %      MagickBooleanType IsMagickConflict(const char *magick)
248 %
249 %  A description of each parameter follows:
250 %
251 %    o magick: Specifies the image format.
252 %
253 */
254 MagickExport MagickBooleanType NTIsMagickConflict(const char *magick)
255 {
256   MagickBooleanType
257     status;
258
259   assert(magick != (char *) NULL);
260   if (strlen(magick) > 1)
261     return(MagickFalse);
262   status=(GetLogicalDrives() & (1 << ((toupper((int) (*magick)))-'A'))) != 0 ?
263     MagickTrue : MagickFalse;
264   return(status);
265 }
266 \f
267 /*
268 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
269 %                                                                             %
270 %                                                                             %
271 %                                                                             %
272 +   N T G e t T y pe L i s t                                                  %
273 %                                                                             %
274 %                                                                             %
275 %                                                                             %
276 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
277 %
278 %  NTLoadTypeLists() loads a Windows TrueType fonts.
279 %
280 %  The format of the NTLoadTypeLists method is:
281 %
282 %      MagickBooleanType NTLoadTypeLists(SplayTreeInfo *type_list)
283 %
284 %  A description of each parameter follows:
285 %
286 %    o type_list: A linked list of fonts.
287 %
288 */
289 MagickExport MagickBooleanType NTLoadTypeLists(SplayTreeInfo *type_list,
290   ExceptionInfo *exception)
291 {
292   HKEY
293     reg_key = (HKEY) INVALID_HANDLE_VALUE;
294
295   LONG
296     res;
297
298
299   int
300     list_entries = 0;
301
302   char
303     buffer[MaxTextExtent],
304     system_root[MaxTextExtent],
305     font_root[MaxTextExtent];
306
307   DWORD
308     type,
309     system_root_length;
310
311   MagickBooleanType
312     status;
313
314   /*
315     Try to find the right Windows*\CurrentVersion key, the SystemRoot and
316     then the Fonts key
317   */
318   res = RegOpenKeyExA (HKEY_LOCAL_MACHINE,
319     "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &reg_key);
320   if (res == ERROR_SUCCESS) {
321     system_root_length=sizeof(system_root)-1;
322     res = RegQueryValueExA(reg_key,"SystemRoot",NULL, &type,
323       (BYTE*) system_root, &system_root_length);
324   }
325   if (res != ERROR_SUCCESS) {
326     res = RegOpenKeyExA (HKEY_LOCAL_MACHINE,
327       "SOFTWARE\\Microsoft\\Windows\\CurrentVersion", 0, KEY_READ, &reg_key);
328     if (res == ERROR_SUCCESS) {
329       system_root_length=sizeof(system_root)-1;
330       res = RegQueryValueExA(reg_key,"SystemRoot",NULL, &type,
331         (BYTE*)system_root, &system_root_length);
332     }
333   }
334   if (res == ERROR_SUCCESS)
335     res = RegOpenKeyExA (reg_key, "Fonts",0, KEY_READ, &reg_key);
336   if (res != ERROR_SUCCESS)
337     return(MagickFalse);
338   *font_root='\0';
339   (void) CopyMagickString(buffer,system_root,MaxTextExtent);
340   (void) ConcatenateMagickString(buffer,"\\fonts\\arial.ttf",MaxTextExtent);
341   if (IsPathAccessible(buffer) != MagickFalse)
342     {
343       (void) CopyMagickString(font_root,system_root,MaxTextExtent);
344       (void) ConcatenateMagickString(font_root,"\\fonts\\",MaxTextExtent);
345     }
346   else
347     {
348       (void) CopyMagickString(font_root,system_root,MaxTextExtent);
349       (void) ConcatenateMagickString(font_root,"\\",MaxTextExtent);
350     }
351
352   {
353     TypeInfo
354       *type_info;
355
356     DWORD
357       registry_index = 0,
358       type,
359       value_data_size,
360       value_name_length;
361
362     char
363       value_data[MaxTextExtent],
364       value_name[MaxTextExtent];
365
366     res = ERROR_SUCCESS;
367
368     while (res != ERROR_NO_MORE_ITEMS)
369       {
370         char
371           *family_extent,
372           token[MaxTextExtent],
373           *pos,
374           *q;
375
376         value_name_length = sizeof(value_name) - 1;
377         value_data_size = sizeof(value_data) - 1;
378         res = RegEnumValueA ( reg_key, registry_index, value_name,
379           &value_name_length, 0, &type, (BYTE*)value_data, &value_data_size);
380         registry_index++;
381         if (res != ERROR_SUCCESS)
382           continue;
383         if ( (pos = strstr(value_name, " (TrueType)")) == (char*) NULL )
384           continue;
385         *pos='\0'; /* Remove (TrueType) from string */
386
387         type_info=(TypeInfo *) AcquireMagickMemory(sizeof(*type_info));
388         if (type_info == (TypeInfo *) NULL)
389           ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
390         (void) ResetMagickMemory(type_info,0,sizeof(TypeInfo));
391
392         type_info->path=ConstantString("Windows Fonts");
393         type_info->signature=MagickSignature;
394
395         /* Name */
396         (void) CopyMagickString(buffer,value_name,MaxTextExtent);
397         for(pos = buffer; *pos != 0 ; pos++)
398           if (*pos == ' ')
399             *pos = '-';
400         type_info->name=ConstantString(buffer);
401
402         /* Fullname */
403         type_info->description=ConstantString(value_name);
404
405         /* Format */
406         type_info->format=ConstantString("truetype");
407
408         /* Glyphs */
409         if (strchr(value_data,'\\') != (char *) NULL)
410           (void) CopyMagickString(buffer,value_data,MaxTextExtent);
411         else
412           {
413             (void) CopyMagickString(buffer,font_root,MaxTextExtent);
414             (void) ConcatenateMagickString(buffer,value_data,MaxTextExtent);
415           }
416
417         LocaleLower(buffer);
418         type_info->glyphs=ConstantString(buffer);
419
420         type_info->stretch=NormalStretch;
421         type_info->style=NormalStyle;
422         type_info->weight=400;
423
424         /* Some fonts are known to require special encodings */
425         if ( (LocaleCompare(type_info->name, "Symbol") == 0 ) ||
426              (LocaleCompare(type_info->name, "Wingdings") == 0 ) ||
427              (LocaleCompare(type_info->name, "Wingdings-2") == 0 ) ||
428              (LocaleCompare(type_info->name, "Wingdings-3") == 0 ) )
429           type_info->encoding=ConstantString("AppleRoman");
430
431         family_extent=value_name;
432
433         for (q=value_name; *q != '\0'; )
434           {
435             GetMagickToken(q,(const char **) &q,token);
436             if (*token == '\0')
437               break;
438
439             if (LocaleCompare(token,"Italic") == 0)
440               {
441                 type_info->style=ItalicStyle;
442               }
443
444             else if (LocaleCompare(token,"Oblique") == 0)
445               {
446                 type_info->style=ObliqueStyle;
447               }
448
449             else if (LocaleCompare(token,"Bold") == 0)
450               {
451                 type_info->weight=700;
452               }
453
454             else if (LocaleCompare(token,"Thin") == 0)
455               {
456                 type_info->weight=100;
457               }
458
459             else if ( (LocaleCompare(token,"ExtraLight") == 0) ||
460                       (LocaleCompare(token,"UltraLight") == 0) )
461               {
462                 type_info->weight=200;
463               }
464
465             else if (LocaleCompare(token,"Light") == 0)
466               {
467                 type_info->weight=300;
468               }
469
470             else if ( (LocaleCompare(token,"Normal") == 0) ||
471                       (LocaleCompare(token,"Regular") == 0) )
472               {
473                 type_info->weight=400;
474               }
475
476             else if (LocaleCompare(token,"Medium") == 0)
477               {
478                 type_info->weight=500;
479               }
480
481             else if ( (LocaleCompare(token,"SemiBold") == 0) ||
482                       (LocaleCompare(token,"DemiBold") == 0) )
483               {
484                 type_info->weight=600;
485               }
486
487             else if ( (LocaleCompare(token,"ExtraBold") == 0) ||
488                       (LocaleCompare(token,"UltraBold") == 0) )
489               {
490                 type_info->weight=800;
491               }
492
493             else if ( (LocaleCompare(token,"Heavy") == 0) ||
494                       (LocaleCompare(token,"Black") == 0) )
495               {
496                 type_info->weight=900;
497               }
498
499             else if (LocaleCompare(token,"Condensed") == 0)
500               {
501                 type_info->stretch = CondensedStretch;
502               }
503
504             else if (LocaleCompare(token,"Expanded") == 0)
505               {
506                 type_info->stretch = ExpandedStretch;
507               }
508
509             else if (LocaleCompare(token,"ExtraCondensed") == 0)
510               {
511                 type_info->stretch = ExtraCondensedStretch;
512               }
513
514             else if (LocaleCompare(token,"ExtraExpanded") == 0)
515               {
516                 type_info->stretch = ExtraExpandedStretch;
517               }
518
519             else if (LocaleCompare(token,"SemiCondensed") == 0)
520               {
521                 type_info->stretch = SemiCondensedStretch;
522               }
523
524             else if (LocaleCompare(token,"SemiExpanded") == 0)
525               {
526                 type_info->stretch = SemiExpandedStretch;
527               }
528
529             else if (LocaleCompare(token,"UltraCondensed") == 0)
530               {
531                 type_info->stretch = UltraCondensedStretch;
532               }
533
534             else if (LocaleCompare(token,"UltraExpanded") == 0)
535               {
536                 type_info->stretch = UltraExpandedStretch;
537               }
538
539             else
540               {
541                 family_extent=q;
542               }
543           }
544
545         (void) CopyMagickString(buffer,value_name,family_extent-value_name+1);
546         StripString(buffer);
547         type_info->family=ConstantString(buffer);
548
549         list_entries++;
550         status=AddValueToSplayTree(type_list,ConstantString(type_info->name),
551           type_info);
552         if (status == MagickFalse)
553           (void) ThrowMagickException(exception,GetMagickModule(),
554             ResourceLimitError,"MemoryAllocationFailed","`%s'",type_info->name);
555       }
556   }
557   RegCloseKey ( reg_key );
558   return(MagickTrue);
559 }
560 \f
561 /*
562 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
563 %                                                                             %
564 %                                                                             %
565 %                                                                             %
566 %   I m a g e T o H B i t m a p                                               %
567 %                                                                             %
568 %                                                                             %
569 %                                                                             %
570 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
571 %
572 %  ImageToHBITMAP() creates a Windows HBITMAP from an image.
573 %
574 %  The format of the ImageToHBITMAP method is:
575 %
576 %      HBITMAP ImageToHBITMAP(Image *image)
577 %
578 %  A description of each parameter follows:
579 %
580 %    o image: the image to convert.
581 %
582 */
583 MagickExport void *ImageToHBITMAP(Image *image)
584 {
585   BITMAP
586     bitmap;
587
588   ExceptionInfo
589     *exception;
590
591   HANDLE
592     bitmap_bitsH;
593
594   HBITMAP
595     bitmapH;
596
597   register ssize_t
598     x;
599
600   register const Quantum
601     *p;
602
603   register RGBQUAD
604     *q;
605
606   RGBQUAD
607     *bitmap_bits;
608
609   size_t
610     length;
611
612   ssize_t
613     y;
614
615   (void) ResetMagickMemory(&bitmap,0,sizeof(bitmap));
616   bitmap.bmType=0;
617   bitmap.bmWidth=(LONG) image->columns;
618   bitmap.bmHeight=(LONG) image->rows;
619   bitmap.bmWidthBytes=4*bitmap.bmWidth;
620   bitmap.bmPlanes=1;
621   bitmap.bmBitsPixel=32;
622   bitmap.bmBits=NULL;
623   length=bitmap.bmWidthBytes*bitmap.bmHeight;
624   bitmap_bitsH=(HANDLE) GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,length);
625   if (bitmap_bitsH == NULL)
626     {
627       char
628         *message;
629
630       message=GetExceptionMessage(errno);
631       (void) ThrowMagickException(&image->exception,GetMagickModule(),
632         ResourceLimitError,"MemoryAllocationFailed","`%s'",message);
633       message=DestroyString(message);
634       return(NULL);
635     }
636   bitmap_bits=(RGBQUAD *) GlobalLock((HGLOBAL) bitmap_bitsH);
637   q=bitmap_bits;
638   if (bitmap.bmBits == NULL)
639     bitmap.bmBits=bitmap_bits;
640   (void) TransformImageColorspace(image,RGBColorspace);
641   exception=(&image->exception);
642   for (y=0; y < (ssize_t) image->rows; y++)
643   {
644     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
645     if (p == (const Quantum *) NULL)
646       break;
647     for (x=0; x < (ssize_t) image->columns; x++)
648     {
649       q->rgbRed=ScaleQuantumToChar(GetPixelRed(image,p));
650       q->rgbGreen=ScaleQuantumToChar(GetPixelGreen(image,p));
651       q->rgbBlue=ScaleQuantumToChar(GetPixelBlue(image,p));
652       q->rgbReserved=0;
653       p+=GetPixelChannels(image);
654       q++;
655     }
656   }
657   bitmap.bmBits=bitmap_bits;
658   bitmapH=CreateBitmapIndirect(&bitmap);
659   if (bitmapH == NULL)
660     {
661       char
662         *message;
663
664       message=GetExceptionMessage(errno);
665       (void) ThrowMagickException(&image->exception,GetMagickModule(),
666         ResourceLimitError,"MemoryAllocationFailed","`%s'",message);
667       message=DestroyString(message);
668     }
669   GlobalUnlock((HGLOBAL) bitmap_bitsH);
670   GlobalFree((HGLOBAL) bitmap_bitsH);
671   return((void *) bitmapH);
672 }
673 \f
674 #endif