1 /*
2  * tkMacOSXFont.c --
3  *
4  *	Contains the Macintosh implementation of the platform-independant
5  *	font package interface.
6  *
7  * Copyright 2002-2004 Benjamin Riefenstahl, Benjamin.Riefenstahl@epost.de
8  * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
9  * Copyright 2008-2009, Apple Inc.
10  *
11  * See the file "license.terms" for information on usage and redistribution
12  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13  */
14 
15 #include "tkMacOSXPrivate.h"
16 #include "tkMacOSXFont.h"
17 
18 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1080
19 #define defaultOrientation kCTFontDefaultOrientation
20 #define verticalOrientation kCTFontVerticalOrientation
21 #else
22 #define defaultOrientation kCTFontOrientationDefault
23 #define verticalOrientation kCTFontOrientationVertical
24 #endif
25 #if MAC_OS_X_VERSION_MIN_REQUIRED < 101100
26 #define fixedPitch kCTFontUserFixedPitchFontType
27 #else
28 #define fixedPitch kCTFontUIFontUserFixedPitch
29 #endif
30 
31 /*
32 #ifdef TK_MAC_DEBUG
33 #define TK_MAC_DEBUG_FONTS
34 #endif
35 */
36 
37 /*
38  * The following structure represents our Macintosh-specific implementation
39  * of a font object.
40  */
41 
42 typedef struct {
43     TkFont font;		/* Stuff used by generic font package. Must
44 				 * be first in structure. */
45 
46     NSFont *nsFont;
47     NSDictionary *nsAttributes;
48 } MacFont;
49 
50 /*
51  * The names for our "native" fonts.
52  */
53 
54 #define SYSTEMFONT_NAME		"system"
55 #define APPLFONT_NAME		"application"
56 #define MENUITEMFONT_NAME	"menu"
57 
58 struct SystemFontMapEntry {
59     const ThemeFontID id;
60     const char *systemName;
61     const char *tkName;
62     const char *tkName1;
63 };
64 
65 #define ThemeFont(n, ...) { kTheme##n##Font, "system" #n "Font", ##__VA_ARGS__ }
66 static const struct SystemFontMapEntry systemFontMap[] = {
67     ThemeFont(System, 			"TkDefaultFont", "TkIconFont"),
68     ThemeFont(EmphasizedSystem,		"TkCaptionFont"),
69     ThemeFont(SmallSystem,		"TkHeadingFont", "TkTooltipFont"),
70     ThemeFont(SmallEmphasizedSystem),
71     ThemeFont(Application,		"TkTextFont"),
72     ThemeFont(Label,			"TkSmallCaptionFont"),
73     ThemeFont(Views),
74     ThemeFont(MenuTitle),
75     ThemeFont(MenuItem,			"TkMenuFont"),
76     ThemeFont(MenuItemMark),
77     ThemeFont(MenuItemCmdKey),
78     ThemeFont(WindowTitle),
79     ThemeFont(PushButton),
80     ThemeFont(UtilityWindowTitle),
81     ThemeFont(AlertHeader),
82     ThemeFont(Toolbar),
83     ThemeFont(MiniSystem),
84     { kThemeSystemFontDetail,		"systemDetailSystemFont" },
85     { kThemeSystemFontDetailEmphasized,	"systemDetailEmphasizedSystemFont" },
86     { -1, NULL }
87 };
88 #undef ThemeFont
89 
90 static int antialiasedTextEnabled = -1;
91 static NSCharacterSet *whitespaceCharacterSet = nil;
92 static NSCharacterSet *lineendingCharacterSet = nil;
93 
94 static void GetTkFontAttributesForNSFont(NSFont *nsFont,
95 	TkFontAttributes *faPtr);
96 static NSFont *FindNSFont(const char *familyName, NSFontTraitMask traits,
97 	NSInteger weight, CGFloat size, int fallbackToDefault);
98 static void InitFont(NSFont *nsFont, const TkFontAttributes *reqFaPtr,
99 	MacFont * fontPtr);
100 static int CreateNamedSystemFont(Tcl_Interp *interp, Tk_Window tkwin,
101 	const char* name, TkFontAttributes *faPtr);
102 static void DrawCharsInContext(Display *display, Drawable drawable, GC gc,
103 	Tk_Font tkfont, const char *source, int numBytes, int rangeStart,
104 	int rangeLength, int x, int y, double angle);
105 
106 @interface NSFont(TKFont)
107 - (NSFont *)bestMatchingFontForCharacters:(const UTF16Char *)characters
108 	length:(NSUInteger)length attributes:(NSDictionary *)attributes
109 	actualCoveredLength:(NSUInteger *)coveredLength;
110 @end
111 
112 #pragma mark -
113 #pragma mark Font Helpers:
114 
115 #define GetNSFontTraitsFromTkFontAttributes(faPtr) \
116 	((faPtr)->weight == TK_FW_BOLD ? NSBoldFontMask : NSUnboldFontMask) | \
117 	((faPtr)->slant == TK_FS_ITALIC ? NSItalicFontMask : NSUnitalicFontMask)
118 
119 /*
120  *---------------------------------------------------------------------------
121  *
122  * GetTkFontAttributesForNSFont --
123  *
124  *	Fill in TkFontAttributes for given NSFont.
125  *
126  * Results:
127  *	None.
128  *
129  * Side effects:
130  *	None.
131  *
132  *---------------------------------------------------------------------------
133  */
134 
135 static void
GetTkFontAttributesForNSFont(NSFont * nsFont,TkFontAttributes * faPtr)136 GetTkFontAttributesForNSFont(
137     NSFont *nsFont,
138     TkFontAttributes *faPtr)
139 {
140     NSFontTraitMask traits = [[NSFontManager sharedFontManager]
141 	    traitsOfFont:nsFont];
142 
143     faPtr->family = Tk_GetUid([[nsFont familyName] UTF8String]);
144     faPtr->size = [nsFont pointSize];
145     faPtr->weight = (traits & NSBoldFontMask ? TK_FW_BOLD : TK_FW_NORMAL);
146     faPtr->slant = (traits & NSItalicFontMask ? TK_FS_ITALIC : TK_FS_ROMAN);
147 }
148 
149 /*
150  *---------------------------------------------------------------------------
151  *
152  * FindNSFont --
153  *
154  *	Find NSFont for given attributes. Use default values for missing
155  *	attributes, and do a case-insensitive search for font family names
156  *	if necessary. If fallbackToDefault flag is set, use the system font
157  *	as a last resort.
158  *
159  * Results:
160  *	None.
161  *
162  * Side effects:
163  *	None.
164  *
165  *---------------------------------------------------------------------------
166  */
167 
168 static NSFont *
FindNSFont(const char * familyName,NSFontTraitMask traits,NSInteger weight,CGFloat size,int fallbackToDefault)169 FindNSFont(
170     const char *familyName,
171     NSFontTraitMask traits,
172     NSInteger weight,
173     CGFloat size,
174     int fallbackToDefault)
175 {
176     NSFontManager *fm = [NSFontManager sharedFontManager];
177     NSFont *nsFont, *dflt = nil;
178     #define defaultFont (dflt ? dflt : (dflt = [NSFont systemFontOfSize:0]))
179     NSString *family;
180 
181     if (familyName) {
182 	family = [[[NSString alloc] initWithUTF8String:familyName] autorelease];
183     } else {
184 	family = [defaultFont familyName];
185     }
186     if (size == 0.0) {
187 	size = [defaultFont pointSize];
188     }
189     nsFont = [fm fontWithFamily:family traits:traits weight:weight size:size];
190     if (!nsFont) {
191 	NSArray *availableFamilies = [fm availableFontFamilies];
192 	NSString *caseFamily = nil;
193 
194 	for (NSString *f in availableFamilies) {
195 	    if ([family caseInsensitiveCompare:f] == NSOrderedSame) {
196 		caseFamily = f;
197 		break;
198 	    }
199 	}
200 	if (caseFamily) {
201 	    nsFont = [fm fontWithFamily:caseFamily traits:traits weight:weight
202 		    size:size];
203 	}
204     }
205     if (!nsFont) {
206 	nsFont = [NSFont fontWithName:family size:size];
207     }
208     if (!nsFont && fallbackToDefault) {
209 	nsFont = [fm convertFont:defaultFont toFamily:family];
210 	nsFont = [fm convertFont:nsFont toSize:size];
211 	nsFont = [fm convertFont:nsFont toHaveTrait:traits];
212     }
213     [nsFont retain];
214     #undef defaultFont
215     return nsFont;
216 }
217 
218 /*
219  *---------------------------------------------------------------------------
220  *
221  * InitFont --
222  *
223  *	Helper for TkpGetNativeFont() and TkpGetFontFromAttributes().
224  *
225  * Results:
226  *	Fills the MacFont structure.
227  *
228  * Side effects:
229  *	Memory allocated.
230  *
231  *---------------------------------------------------------------------------
232  */
233 
234 static void
InitFont(NSFont * nsFont,const TkFontAttributes * reqFaPtr,MacFont * fontPtr)235 InitFont(
236     NSFont *nsFont,
237     const TkFontAttributes *reqFaPtr,	/* Can be NULL */
238     MacFont *fontPtr)
239 {
240     TkFontAttributes *faPtr;
241     TkFontMetrics *fmPtr;
242     NSDictionary *nsAttributes;
243     NSRect bounds;
244     CGFloat kern = 0.0;
245     NSFontRenderingMode renderingMode = NSFontDefaultRenderingMode;
246     int ascent, descent/*, dontAA*/;
247     static const UniChar ch[] = {'.', 'W', ' ', 0xc4, 0xc1, 0xc2, 0xc3, 0xc7};
248 			/* ., W, Space, Auml, Aacute, Acirc, Atilde, Ccedilla */
249     #define nCh (sizeof(ch) / sizeof(UniChar))
250     CGGlyph glyphs[nCh];
251     CGRect boundingRects[nCh];
252 
253     fontPtr->font.fid = (Font) fontPtr;
254     faPtr = &fontPtr->font.fa;
255     if (reqFaPtr) {
256 	*faPtr = *reqFaPtr;
257     } else {
258 	TkInitFontAttributes(faPtr);
259     }
260     fontPtr->nsFont = nsFont;
261     // some don't like antialiasing on fixed-width even if bigger than limit
262 //    dontAA = [nsFont isFixedPitch] && fontPtr->font.fa.size <= 10;
263     if (antialiasedTextEnabled >= 0/* || dontAA*/) {
264 	renderingMode = (antialiasedTextEnabled == 0/* || dontAA*/) ?
265 		NSFontIntegerAdvancementsRenderingMode :
266 		NSFontAntialiasedRenderingMode;
267     }
268     nsFont = [nsFont screenFontWithRenderingMode:renderingMode];
269     GetTkFontAttributesForNSFont(nsFont, faPtr);
270     fmPtr = &fontPtr->font.fm;
271     fmPtr->ascent = floor([nsFont ascender] + [nsFont leading] + 0.5);
272     fmPtr->descent = floor(-[nsFont descender] + 0.5);
273     fmPtr->maxWidth = [nsFont maximumAdvancement].width;
274     fmPtr->fixed = [nsFont isFixedPitch];   /* Does not work for all fonts */
275 
276     /*
277      * The ascent, descent and fixed fields are not correct for all fonts, as
278      * a workaround deduce that info from the metrics of some typical glyphs,
279      * along with screenfont kerning (space advance difference to printer font)
280      */
281 
282     bounds = [nsFont boundingRectForFont];
283     if (CTFontGetGlyphsForCharacters((CTFontRef) nsFont, ch, glyphs, nCh)) {
284 	fmPtr->fixed = [nsFont advancementForGlyph:glyphs[0]].width ==
285 		[nsFont advancementForGlyph:glyphs[1]].width;
286 	bounds = NSRectFromCGRect(CTFontGetBoundingRectsForGlyphs((CTFontRef)
287 		nsFont, defaultOrientation, ch, boundingRects, nCh));
288 	kern = [nsFont advancementForGlyph:glyphs[2]].width -
289 		[fontPtr->nsFont advancementForGlyph:glyphs[2]].width;
290     }
291     descent = floor(-bounds.origin.y + 0.5);
292     ascent = floor(bounds.size.height + bounds.origin.y + 0.5);
293     if (ascent > fmPtr->ascent) {
294 	fmPtr->ascent = ascent;
295     }
296     if (descent > fmPtr->descent) {
297 	fmPtr->descent = descent;
298     }
299     nsAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
300 	    nsFont, NSFontAttributeName,
301 	    [NSNumber numberWithInt:faPtr->underline ?
302 		NSUnderlineStyleSingle|NSUnderlinePatternSolid :
303 		NSUnderlineStyleNone], NSUnderlineStyleAttributeName,
304 	    [NSNumber numberWithInt:faPtr->overstrike ?
305 		NSUnderlineStyleSingle|NSUnderlinePatternSolid :
306 		NSUnderlineStyleNone], NSStrikethroughStyleAttributeName,
307 	    [NSNumber numberWithInt:fmPtr->fixed ? 0 : 1],
308 		NSLigatureAttributeName,
309 	    [NSNumber numberWithDouble:kern], NSKernAttributeName, nil];
310     fontPtr->nsAttributes = [nsAttributes retain];
311     #undef nCh
312 }
313 
314 /*
315  *-------------------------------------------------------------------------
316  *
317  * CreateNamedSystemFont --
318  *
319  *	Register a system font with the Tk named font mechanism.
320  *
321  * Results:
322  *
323  *	Result from TkCreateNamedFont().
324  *
325  * Side effects:
326  *
327  *	A new named font is added to the Tk font registry.
328  *
329  *-------------------------------------------------------------------------
330  */
331 
332 static int
CreateNamedSystemFont(Tcl_Interp * interp,Tk_Window tkwin,const char * name,TkFontAttributes * faPtr)333 CreateNamedSystemFont(
334     Tcl_Interp *interp,
335     Tk_Window tkwin,
336     const char* name,
337     TkFontAttributes *faPtr)
338 {
339     TkDeleteNamedFont(NULL, tkwin, name);
340     return TkCreateNamedFont(interp, tkwin, name, faPtr);
341 }
342 
343 #pragma mark -
344 #pragma mark Font handling:
345 
346 /*
347  *-------------------------------------------------------------------------
348  *
349  * TkpFontPkgInit --
350  *
351  *	This procedure is called when an application is created. It
352  *	initializes all the structures that are used by the
353  *	platform-dependant code on a per application basis.
354  *	Note that this is called before TkpInit() !
355  *
356  * Results:
357  *	None.
358  *
359  * Side effects:
360  *	Initialize named system fonts.
361  *
362  *-------------------------------------------------------------------------
363  */
364 
365 void
TkpFontPkgInit(TkMainInfo * mainPtr)366 TkpFontPkgInit(
367     TkMainInfo *mainPtr)	/* The application being created. */
368 {
369     Tcl_Interp *interp = mainPtr->interp;
370     Tk_Window tkwin = (Tk_Window) mainPtr->winPtr;
371     const struct SystemFontMapEntry *systemFont = systemFontMap;
372     NSFont *nsFont;
373     TkFontAttributes fa;
374     NSMutableCharacterSet *cs;
375     /* Since we called before TkpInit, we need our own autorelease pool. */
376     NSAutoreleasePool *pool = [NSAutoreleasePool new];
377 
378     /* force this for now */
379     if (!mainPtr->winPtr->mainPtr) {
380 	mainPtr->winPtr->mainPtr = mainPtr;
381     }
382     while (systemFont->systemName) {
383 	nsFont = (NSFont*) CTFontCreateUIFontForLanguage(
384 		HIThemeGetUIFontType(systemFont->id), 0, NULL);
385 	if (nsFont) {
386 	    TkInitFontAttributes(&fa);
387 	    GetTkFontAttributesForNSFont(nsFont, &fa);
388 	    CreateNamedSystemFont(interp, tkwin, systemFont->systemName, &fa);
389 	    if (systemFont->tkName) {
390 		CreateNamedSystemFont(interp, tkwin, systemFont->tkName, &fa);
391 	    }
392 	    if (systemFont->tkName1) {
393 		CreateNamedSystemFont(interp, tkwin, systemFont->tkName1, &fa);
394 	    }
395 	    CFRelease(nsFont);
396 	}
397 	systemFont++;
398     }
399     TkInitFontAttributes(&fa);
400     nsFont = (NSFont*) CTFontCreateUIFontForLanguage(fixedPitch, 11, NULL);
401     if (nsFont) {
402 	GetTkFontAttributesForNSFont(nsFont, &fa);
403 	CFRelease(nsFont);
404     } else {
405 	fa.family = Tk_GetUid("Monaco");
406 	fa.size = 11;
407 	fa.weight = TK_FW_NORMAL;
408 	fa.slant = TK_FS_ROMAN;
409     }
410     CreateNamedSystemFont(interp, tkwin, "TkFixedFont", &fa);
411     if (!whitespaceCharacterSet) {
412 	whitespaceCharacterSet = [[NSCharacterSet
413 		whitespaceAndNewlineCharacterSet] retain];
414 	cs = [whitespaceCharacterSet mutableCopy];
415 	[cs removeCharactersInString:@" "];
416 	lineendingCharacterSet = [cs copy];
417 	[cs release];
418     }
419     [pool drain];
420 }
421 
422 /*
423  *---------------------------------------------------------------------------
424  *
425  * TkpGetNativeFont --
426  *
427  *	Map a platform-specific native font name to a TkFont.
428  *
429  * Results:
430  *	The return value is a pointer to a TkFont that represents the
431  *	native font. If a native font by the given name could not be
432  *	found, the return value is NULL.
433  *
434  *	Every call to this procedure returns a new TkFont structure, even
435  *	if the name has already been seen before. The caller should call
436  *	TkpDeleteFont() when the font is no longer needed.
437  *
438  *	The caller is responsible for initializing the memory associated
439  *	with the generic TkFont when this function returns and releasing
440  *	the contents of the generics TkFont before calling TkpDeleteFont().
441  *
442  * Side effects:
443  *	None.
444  *
445  *---------------------------------------------------------------------------
446  */
447 
448 TkFont *
TkpGetNativeFont(Tk_Window tkwin,const char * name)449 TkpGetNativeFont(
450     Tk_Window tkwin,		/* For display where font will be used. */
451     const char *name)		/* Platform-specific font name. */
452 {
453     MacFont *fontPtr = NULL;
454     ThemeFontID themeFontId;
455     CTFontRef ctFont;
456 
457     if (strcmp(name, SYSTEMFONT_NAME) == 0) {
458 	themeFontId = kThemeSystemFont;
459     } else if (strcmp(name, APPLFONT_NAME) == 0) {
460 	themeFontId = kThemeApplicationFont;
461     } else if (strcmp(name, MENUITEMFONT_NAME) == 0) {
462 	themeFontId = kThemeMenuItemFont;
463     } else {
464 	return NULL;
465     }
466     ctFont = CTFontCreateUIFontForLanguage(HIThemeGetUIFontType(
467 	    themeFontId), 0, NULL);
468     if (ctFont) {
469 	fontPtr = (MacFont *) ckalloc(sizeof(MacFont));
470 	InitFont((NSFont*) ctFont, NULL, fontPtr);
471     }
472 
473     return (TkFont *) fontPtr;
474 }
475 
476 /*
477  *---------------------------------------------------------------------------
478  *
479  * TkpGetFontFromAttributes --
480  *
481  *	Given a desired set of attributes for a font, find a font with the
482  *	closest matching attributes.
483  *
484  * Results:
485  *	The return value is a pointer to a TkFont that represents the font
486  *	with the desired attributes. If a font with the desired attributes
487  *	could not be constructed, some other font will be substituted
488  *	automatically.
489  *
490  *	Every call to this procedure returns a new TkFont structure, even
491  *	if the specified attributes have already been seen before. The
492  *	caller should call TkpDeleteFont() to free the platform- specific
493  *	data when the font is no longer needed.
494  *
495  *	The caller is responsible for initializing the memory associated
496  *	with the generic TkFont when this function returns and releasing
497  *	the contents of the generic TkFont before calling TkpDeleteFont().
498  *
499  * Side effects:
500  *	None.
501  *
502  *---------------------------------------------------------------------------
503  */
504 
505 TkFont *
TkpGetFontFromAttributes(TkFont * tkFontPtr,Tk_Window tkwin,const TkFontAttributes * faPtr)506 TkpGetFontFromAttributes(
507     TkFont *tkFontPtr,		/* If non-NULL, store the information in this
508 				 * existing TkFont structure, rather than
509 				 * allocating a new structure to hold the
510 				 * font; the existing contents of the font
511 				 * will be released. If NULL, a new TkFont
512 				 * structure is allocated. */
513     Tk_Window tkwin,		/* For display where font will be used. */
514     const TkFontAttributes *faPtr)
515 				/* Set of attributes to match. */
516 {
517     MacFont *fontPtr;
518     int points = TkFontGetPoints(tkwin, faPtr->size);
519     NSFontTraitMask traits = GetNSFontTraitsFromTkFontAttributes(faPtr);
520     NSInteger weight = (faPtr->weight == TK_FW_BOLD ? 9 : 5);
521     NSFont *nsFont;
522 
523     nsFont = FindNSFont(faPtr->family, traits, weight, points, 0);
524     if (!nsFont) {
525 	char *const *aliases = TkFontGetAliasList(faPtr->family);
526 
527 	while (aliases && !nsFont) {
528 	    nsFont = FindNSFont(*aliases++, traits, weight, points, 0);
529 	}
530     }
531     if (!nsFont) {
532 	nsFont = FindNSFont(faPtr->family, traits, weight, points, 1);
533     }
534     if (!nsFont) {
535 	Tcl_Panic("Could not determine NSFont from TkFontAttributes");
536     }
537     if (tkFontPtr == NULL) {
538 	fontPtr = (MacFont *) ckalloc(sizeof(MacFont));
539     } else {
540 	fontPtr = (MacFont *) tkFontPtr;
541 	TkpDeleteFont(tkFontPtr);
542     }
543     CFRetain(nsFont); /* Always needed to allow unconditional CFRelease below */
544     InitFont(nsFont, faPtr, fontPtr);
545 
546     return (TkFont *) fontPtr;
547 }
548 
549 /*
550  *---------------------------------------------------------------------------
551  *
552  * TkpDeleteFont --
553  *
554  *	Called to release a font allocated by TkpGetNativeFont() or
555  *	TkpGetFontFromAttributes(). The caller should have already
556  *	released the fields of the TkFont that are used exclusively by the
557  *	generic TkFont code.
558  *
559  * Results:
560  *	TkFont is deallocated.
561  *
562  * Side effects:
563  *	None.
564  *
565  *---------------------------------------------------------------------------
566  */
567 
568 void
TkpDeleteFont(TkFont * tkFontPtr)569 TkpDeleteFont(
570     TkFont *tkFontPtr)		/* Token of font to be deleted. */
571 {
572     MacFont *fontPtr = (MacFont *) tkFontPtr;
573 
574     [fontPtr->nsAttributes release];
575     fontPtr->nsAttributes = NULL;
576     CFRelease(fontPtr->nsFont); /* Either a CTFontRef or a CFRetained NSFont */
577 }
578 
579 /*
580  *---------------------------------------------------------------------------
581  *
582  * TkpGetFontFamilies --
583  *
584  *	Return information about the font families that are available on
585  *	the display of the given window.
586  *
587  * Results:
588  *	Modifies interp's result object to hold a list of all the available
589  *	font families.
590  *
591  * Side effects:
592  *	None.
593  *
594  *---------------------------------------------------------------------------
595  */
596 
597 void
TkpGetFontFamilies(Tcl_Interp * interp,Tk_Window tkwin)598 TkpGetFontFamilies(
599     Tcl_Interp *interp,		/* Interp to hold result. */
600     Tk_Window tkwin)		/* For display to query. */
601 {
602     Tcl_Obj *resultPtr = Tcl_NewListObj(0, NULL);
603     NSArray *list = [[NSFontManager sharedFontManager] availableFontFamilies];
604 
605     for (NSString *family in list) {
606 	Tcl_ListObjAppendElement(NULL, resultPtr,
607 		Tcl_NewStringObj([family UTF8String], -1));
608     }
609     Tcl_SetObjResult(interp, resultPtr);
610 }
611 
612 /*
613  *-------------------------------------------------------------------------
614  *
615  * TkpGetSubFonts --
616  *
617  *	A function used by the testing package for querying the actual
618  *	screen fonts that make up a font object.
619  *
620  * Results:
621  *	Modifies interp's result object to hold a list containing the names
622  *	of the screen fonts that make up the given font object.
623  *
624  * Side effects:
625  *	None.
626  *
627  *-------------------------------------------------------------------------
628  */
629 
630 void
TkpGetSubFonts(Tcl_Interp * interp,Tk_Font tkfont)631 TkpGetSubFonts(
632     Tcl_Interp *interp,		/* Interp to hold result. */
633     Tk_Font tkfont)		/* Font object to query. */
634 {
635     MacFont *fontPtr = (MacFont *) tkfont;
636     Tcl_Obj *resultPtr = Tcl_NewListObj(0, NULL);
637 
638     if (fontPtr->nsFont) {
639 	NSArray *list = [[fontPtr->nsFont fontDescriptor]
640 		objectForKey:NSFontCascadeListAttribute];
641 
642 	for (NSFontDescriptor *subFontDesc in list) {
643 	    NSString *family = [subFontDesc objectForKey:NSFontFamilyAttribute];
644 
645 	    if (family) {
646 		Tcl_ListObjAppendElement(NULL, resultPtr,
647 			Tcl_NewStringObj([family UTF8String], -1));
648 	    }
649 	}
650     }
651     Tcl_SetObjResult(interp, resultPtr);
652 }
653 
654 /*
655  *----------------------------------------------------------------------
656  *
657  * TkpGetFontAttrsForChar --
658  *
659  *	Retrieve the font attributes of the actual font used to render a
660  *	given character.
661  *
662  * Results:
663  *	None.
664  *
665  * Side effects:
666  *	The font attributes are stored in *faPtr.
667  *
668  *----------------------------------------------------------------------
669  */
670 
671 void
TkpGetFontAttrsForChar(Tk_Window tkwin,Tk_Font tkfont,Tcl_UniChar c,TkFontAttributes * faPtr)672 TkpGetFontAttrsForChar(
673     Tk_Window tkwin,		/* Window on the font's display */
674     Tk_Font tkfont,		/* Font to query */
675     Tcl_UniChar c,		/* Character of interest */
676     TkFontAttributes* faPtr)	/* Output: Font attributes */
677 {
678     MacFont *fontPtr = (MacFont *) tkfont;
679     NSFont *nsFont = fontPtr->nsFont;
680     *faPtr = fontPtr->font.fa;
681     if (nsFont && ![[nsFont coveredCharacterSet] characterIsMember:c]) {
682 	UTF16Char ch = c;
683 
684 	nsFont = [nsFont bestMatchingFontForCharacters:&ch
685 		length:1 attributes:nil actualCoveredLength:NULL];
686 	if (nsFont) {
687 	    GetTkFontAttributesForNSFont(nsFont, faPtr);
688 	}
689     }
690 }
691 
692 #pragma mark -
693 #pragma mark Measuring and drawing:
694 
695 /*
696  *---------------------------------------------------------------------------
697  *
698  * Tk_MeasureChars --
699  *
700  *	Determine the number of characters from the string that will fit in
701  *	the given horizontal span. The measurement is done under the
702  *	assumption that Tk_DrawChars() will be used to actually display the
703  *	characters.
704  *
705  *	With ATSUI we need the line context to do this right, so we have the
706  *	actual implementation in TkpMeasureCharsInContext().
707  *
708  * Results:
709  *	The return value is the number of bytes from source that fit into the
710  *	span that extends from 0 to maxLength. *lengthPtr is filled with the
711  *	x-coordinate of the right edge of the last character that did fit.
712  *
713  * Side effects:
714  *	None.
715  *
716  * Todo:
717  *	Effects of the "flags" parameter are untested.
718  *
719  *---------------------------------------------------------------------------
720  */
721 
722 int
Tk_MeasureChars(Tk_Font tkfont,const char * source,int numBytes,int maxLength,int flags,int * lengthPtr)723 Tk_MeasureChars(
724     Tk_Font tkfont,		/* Font in which characters will be drawn. */
725     const char *source,		/* UTF-8 string to be displayed. Need not be
726 				 * '\0' terminated. */
727     int numBytes,		/* Maximum number of bytes to consider from
728 				 * source string. */
729     int maxLength,		/* If >= 0, maxLength specifies the longest
730 				 * permissible line length; don't consider any
731 				 * character that would cross this x-position.
732 				 * If < 0, then line length is unbounded and
733 				 * the flags argument is ignored. */
734     int flags,			/* Various flag bits OR-ed together:
735 				 * TK_PARTIAL_OK means include the last char
736 				 * which only partially fit on this line.
737 				 * TK_WHOLE_WORDS means stop on a word
738 				 * boundary, if possible. TK_AT_LEAST_ONE
739 				 * means return at least one character even if
740 				 * no characters fit. */
741     int *lengthPtr)		/* Filled with x-location just after the
742 				 * terminating character. */
743 {
744     return TkpMeasureCharsInContext(tkfont, source, numBytes, 0, numBytes,
745 	    maxLength, flags, lengthPtr);
746 }
747 
748 /*
749  *---------------------------------------------------------------------------
750  *
751  * TkpMeasureCharsInContext --
752  *
753  *	Determine the number of bytes from the string that will fit in the
754  *	given horizontal span. The measurement is done under the assumption
755  *	that TkpDrawCharsInContext() will be used to actually display the
756  *	characters.
757  *
758  *	This one is almost the same as Tk_MeasureChars(), but with access to
759  *	all the characters on the line for context.
760  *
761  * Results:
762  *	The return value is the number of bytes from source that
763  *	fit into the span that extends from 0 to maxLength. *lengthPtr is
764  *	filled with the x-coordinate of the right edge of the last
765  *	character that did fit.
766  *
767  * Side effects:
768  *	None.
769  *
770  *---------------------------------------------------------------------------
771  */
772 
773 int
TkpMeasureCharsInContext(Tk_Font tkfont,const char * source,int numBytes,int rangeStart,int rangeLength,int maxLength,int flags,int * lengthPtr)774 TkpMeasureCharsInContext(
775     Tk_Font tkfont,		/* Font in which characters will be drawn. */
776     const char * source,	/* UTF-8 string to be displayed. Need not be
777 				 * '\0' terminated. */
778     int numBytes,		/* Maximum number of bytes to consider from
779 				 * source string in all. */
780     int rangeStart,		/* Index of first byte to measure. */
781     int rangeLength,		/* Length of range to measure in bytes. */
782     int maxLength,		/* If >= 0, maxLength specifies the longest
783 				 * permissible line length; don't consider any
784 				 * character that would cross this x-position.
785 				 * If < 0, then line length is unbounded and
786 				 * the flags argument is ignored. */
787     int flags,			/* Various flag bits OR-ed together:
788 				 * TK_PARTIAL_OK means include the last char
789 				 * which only partially fits on this line.
790 				 * TK_WHOLE_WORDS means stop on a word
791 				 * boundary, if possible. TK_AT_LEAST_ONE
792 				 * means return at least one character even
793 				 * if no characters fit.  If TK_WHOLE_WORDS
794 				 * and TK_AT_LEAST_ONE are set and the first
795 				 * word doesn't fit, we return at least one
796 				 * character or whatever characters fit into
797 				 * maxLength.  TK_ISOLATE_END means that the
798 				 * last character should not be considered in
799 				 * context with the rest of the string (used
800 				 * for breaking lines). */
801     int *lengthPtr)		/* Filled with x-location just after the
802 				 * terminating character. */
803 {
804     const MacFont *fontPtr = (const MacFont *) tkfont;
805     NSString *string;
806     NSAttributedString *attributedString;
807     CTTypesetterRef typesetter;
808     CFIndex start, len;
809     CFRange range = {0, 0};
810     CTLineRef line;
811     CGFloat offset = 0;
812     CFIndex index;
813     double width;
814     int length, fit;
815 
816     if (rangeStart < 0 || rangeLength <= 0 ||
817 	    rangeStart + rangeLength > numBytes ||
818 	    (maxLength == 0 && !(flags & TK_AT_LEAST_ONE))) {
819 	*lengthPtr = 0;
820 	return 0;
821     }
822 #if 0
823     /* Back-compatibility with ATSUI renderer, appears not to be needed */
824     if (rangeStart == 0 && maxLength == 1 && (flags & TK_ISOLATE_END) &&
825 	    !(flags & TK_AT_LEAST_ONE)) {
826 	length = 0;
827 	fit = 0;
828 	goto done;
829     }
830 #endif
831     if (maxLength > 32767) {
832 	maxLength = 32767;
833     }
834     string = [[NSString alloc] initWithBytesNoCopy:(void*)source
835 		length:numBytes encoding:NSUTF8StringEncoding freeWhenDone:NO];
836     if (!string) {
837 	length = 0;
838 	fit = rangeLength;
839 	goto done;
840     }
841     attributedString = [[NSAttributedString alloc] initWithString:string
842 	    attributes:fontPtr->nsAttributes];
843     typesetter = CTTypesetterCreateWithAttributedString(
844 	    (CFAttributedStringRef)attributedString);
845     start = Tcl_NumUtfChars(source, rangeStart);
846     len = Tcl_NumUtfChars(source + rangeStart, rangeLength);
847     if (start > 0) {
848 	range.length = start;
849 	line = CTTypesetterCreateLine(typesetter, range);
850 	offset = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
851 	CFRelease(line);
852     }
853     if (maxLength < 0) {
854 	index = len;
855 	range.length = len;
856 	line = CTTypesetterCreateLine(typesetter, range);
857 	width = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
858 	CFRelease(line);
859     } else {
860 	double maxWidth = maxLength + offset;
861 	NSCharacterSet *cs;
862 
863 	index = start;
864 	if (flags & TK_WHOLE_WORDS) {
865 	    index = CTTypesetterSuggestLineBreak(typesetter, start, maxWidth);
866 	    if (index <= start && (flags & TK_AT_LEAST_ONE)) {
867 		flags &= ~TK_WHOLE_WORDS;
868 	    }
869 	}
870 	if (index <= start && !(flags & TK_WHOLE_WORDS)) {
871 	    index = CTTypesetterSuggestClusterBreak(typesetter, start, maxWidth);
872 	}
873 	cs = (index < len || (flags & TK_WHOLE_WORDS)) ?
874 		whitespaceCharacterSet : lineendingCharacterSet;
875 	while (index > start &&
876 		[cs characterIsMember:[string characterAtIndex:(index - 1)]]) {
877 	    index--;
878 	}
879 	if (index <= start && (flags & TK_AT_LEAST_ONE)) {
880 	    index = start + 1;
881 	}
882 	if (index > 0) {
883 	    range.length = index;
884 	    line = CTTypesetterCreateLine(typesetter, range);
885 	    width = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
886 	    CFRelease(line);
887 	} else {
888 	    width = 0;
889 	}
890 	if (width < maxWidth && (flags & TK_PARTIAL_OK) && index < len) {
891 	    range.length = ++index;
892 	    line = CTTypesetterCreateLine(typesetter, range);
893 	    width = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
894 	    CFRelease(line);
895 	}
896 
897         /* The call to CTTypesetterSuggestClusterBreak above will always
898            return at least one character regardless of whether it exceeded
899            it or not.  Clean that up now. */
900 	  while (width > maxWidth && !(flags & TK_PARTIAL_OK) && index > start+(flags & TK_AT_LEAST_ONE)) {
901 	    range.length = --index;
902 	    line = CTTypesetterCreateLine(typesetter, range);
903 	    width = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
904 	    CFRelease(line);
905 	}
906 
907     }
908     CFRelease(typesetter);
909     [attributedString release];
910     [string release];
911     length = ceil(width - offset);
912     fit = (Tcl_UtfAtIndex(source, index) - source) - rangeStart;
913 done:
914 #ifdef TK_MAC_DEBUG_FONTS
915     TkMacOSXDbgMsg("measure: source=\"%s\" range=\"%.*s\" maxLength=%d "
916 	    "flags='%s%s%s%s' -> width=%d bytesFit=%d\n", source, rangeLength,
917 	    source+rangeStart, maxLength,
918 	    flags & TK_PARTIAL_OK   ? "partialOk "  : "",
919 	    flags & TK_WHOLE_WORDS  ? "wholeWords " : "",
920 	    flags & TK_AT_LEAST_ONE ? "atLeastOne " : "",
921 	    flags & TK_ISOLATE_END  ? "isolateEnd " : "",
922 	    length, fit);
923 //if (!(rangeLength==1 && rangeStart == 0)) fprintf(stderr, "   measure len=%d (max=%d, w=%.0f) from %d (nb=%d): source=\"%s\": index=%d return %d\n",rangeLength,maxLength,width,rangeStart,numBytes, source+rangeStart, index, fit);
924 #endif
925     *lengthPtr = length;
926     return fit;
927 }
928 
929 /*
930  *---------------------------------------------------------------------------
931  *
932  * Tk_DrawChars --
933  *
934  *	Draw a string of characters on the screen.
935  *
936  *	With ATSUI we need the line context to do this right, so we have the
937  *	actual implementation in TkpDrawCharsInContext().
938  *
939  * Results:
940   *	None.
941  *
942  * Side effects:
943  *	Information gets drawn on the screen.
944  *
945  *---------------------------------------------------------------------------
946  */
947 
948 void
Tk_DrawChars(Display * display,Drawable drawable,GC gc,Tk_Font tkfont,const char * source,int numBytes,int x,int y)949 Tk_DrawChars(
950     Display *display,		/* Display on which to draw. */
951     Drawable drawable,		/* Window or pixmap in which to draw. */
952     GC gc,			/* Graphics context for drawing characters. */
953     Tk_Font tkfont,		/* Font in which characters will be drawn; must
954 				 * be the same as font used in GC. */
955     const char *source,		/* UTF-8 string to be displayed. Need not be
956 				 * '\0' terminated. All Tk meta-characters
957 				 * (tabs, control characters, and newlines)
958 				 * should be stripped out of the string that
959 				 * is passed to this function. If they are not
960 				 * stripped out, they will be displayed as
961 				 * regular printing characters. */
962     int numBytes,		/* Number of bytes in string. */
963     int x, int y)		/* Coordinates at which to place origin of the
964 				 * string when drawing. */
965 {
966     DrawCharsInContext(display, drawable, gc, tkfont, source, numBytes,
967 	    0, numBytes, x, y, 0.0);
968 }
969 
970 /*
971  *---------------------------------------------------------------------------
972  *
973  * TkpDrawCharsInContext --
974  *
975  *	Draw a string of characters on the screen like Tk_DrawChars(), with
976  *	access to all the characters on the line for context.
977  *
978  * Results:
979  *	None.
980  *
981  * Side effects:
982  *	Information gets drawn on the screen.
983  *
984  * Todo:
985  *	Stippled text drawing.
986  *
987  *---------------------------------------------------------------------------
988  */
989 
990 void
TkpDrawCharsInContext(Display * display,Drawable drawable,GC gc,Tk_Font tkfont,const char * source,int numBytes,int rangeStart,int rangeLength,int x,int y)991 TkpDrawCharsInContext(
992     Display *display,		/* Display on which to draw. */
993     Drawable drawable,		/* Window or pixmap in which to draw. */
994     GC gc,			/* Graphics context for drawing characters. */
995     Tk_Font tkfont,		/* Font in which characters will be drawn; must
996 				 * be the same as font used in GC. */
997     const char * source,	/* UTF-8 string to be displayed. Need not be
998 				 * '\0' terminated. All Tk meta-characters
999 				 * (tabs, control characters, and newlines)
1000 				 * should be stripped out of the string that
1001 				 * is passed to this function. If they are not
1002 				 * stripped out, they will be displayed as
1003 				 * regular printing characters. */
1004     int numBytes,		/* Number of bytes in string. */
1005     int rangeStart,		/* Index of first byte to draw. */
1006     int rangeLength,		/* Length of range to draw in bytes. */
1007     int x, int y)		/* Coordinates at which to place origin of the
1008 				 * whole (not just the range) string when
1009 				 * drawing. */
1010 {
1011     DrawCharsInContext(display, drawable, gc, tkfont, source, numBytes,
1012 	    rangeStart, rangeLength, x, y, 0.0);
1013 }
1014 
1015 static void
DrawCharsInContext(Display * display,Drawable drawable,GC gc,Tk_Font tkfont,const char * source,int numBytes,int rangeStart,int rangeLength,int x,int y,double angle)1016 DrawCharsInContext(
1017     Display *display,		/* Display on which to draw. */
1018     Drawable drawable,		/* Window or pixmap in which to draw. */
1019     GC gc,			/* Graphics context for drawing characters. */
1020     Tk_Font tkfont,		/* Font in which characters will be drawn; must
1021 				 * be the same as font used in GC. */
1022     const char * source,	/* UTF-8 string to be displayed. Need not be
1023 				 * '\0' terminated. All Tk meta-characters
1024 				 * (tabs, control characters, and newlines)
1025 				 * should be stripped out of the string that
1026 				 * is passed to this function. If they are not
1027 				 * stripped out, they will be displayed as
1028 				 * regular printing characters. */
1029     int numBytes,		/* Number of bytes in string. */
1030     int rangeStart,		/* Index of first byte to draw. */
1031     int rangeLength,		/* Length of range to draw in bytes. */
1032     int x, int y,		/* Coordinates at which to place origin of the
1033 				 * whole (not just the range) string when
1034 				 * drawing. */
1035     double angle)
1036 {
1037     const MacFont *fontPtr = (const MacFont *) tkfont;
1038     NSString *string;
1039     NSMutableDictionary *attributes;
1040     NSAttributedString *attributedString;
1041     CTTypesetterRef typesetter;
1042     CFIndex start, len;
1043     CTLineRef line;
1044     MacDrawable *macWin = (MacDrawable *) drawable;
1045     TkMacOSXDrawingContext drawingContext;
1046     CGContextRef context;
1047     CGColorRef fg;
1048     NSFont *nsFont;
1049     CGAffineTransform t;
1050     int h;
1051 
1052     if (rangeStart < 0 || rangeLength <= 0 ||
1053 	    rangeStart + rangeLength > numBytes ||
1054 	    !TkMacOSXSetupDrawingContext(drawable, gc, 1, &drawingContext)) {
1055 	return;
1056     }
1057     string = [[NSString alloc] initWithBytesNoCopy:(void*)source
1058 		length:numBytes encoding:NSUTF8StringEncoding freeWhenDone:NO];
1059     if (!string) {
1060 	return;
1061     }
1062     context = drawingContext.context;
1063     fg = TkMacOSXCreateCGColor(gc, gc->foreground);
1064     attributes = [fontPtr->nsAttributes mutableCopy];
1065     [attributes setObject:(id)fg forKey:(id)kCTForegroundColorAttributeName];
1066     CFRelease(fg);
1067     nsFont = [attributes objectForKey:NSFontAttributeName];
1068     [nsFont setInContext:[NSGraphicsContext graphicsContextWithGraphicsPort:
1069 	    context flipped:NO]];
1070     CGContextSetTextMatrix(context, CGAffineTransformIdentity);
1071     attributedString = [[NSAttributedString alloc] initWithString:string
1072 	    attributes:attributes];
1073     typesetter = CTTypesetterCreateWithAttributedString(
1074 	    (CFAttributedStringRef)attributedString);
1075     x += macWin->xOff;
1076     y += macWin->yOff;
1077     h = drawingContext.portBounds.size.height;
1078     y = h - y;
1079     t = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, h);
1080     if (angle != 0.0) {
1081 	t = CGAffineTransformTranslate(CGAffineTransformRotate(
1082 		CGAffineTransformTranslate(t, x, y), angle*M_PI/180.0), -x, -y);
1083     }
1084     CGContextConcatCTM(context, t);
1085     CGContextSetTextPosition(context, x, y);
1086     start = Tcl_NumUtfChars(source, rangeStart);
1087     len = Tcl_NumUtfChars(source, rangeStart + rangeLength);
1088     if (start > 0) {
1089 	CGRect clipRect = CGRectInfinite, startBounds;
1090 	line = CTTypesetterCreateLine(typesetter, CFRangeMake(0, start));
1091 	startBounds = CTLineGetImageBounds(line, context);
1092 	CFRelease(line);
1093 	clipRect.origin.x = startBounds.origin.x + startBounds.size.width;
1094 	CGContextClipToRect(context, clipRect);
1095     }
1096     line = CTTypesetterCreateLine(typesetter, CFRangeMake(0, len));
1097     CTLineDraw(line, context);
1098     CFRelease(line);
1099     CFRelease(typesetter);
1100     [attributedString release];
1101     [string release];
1102     [attributes release];
1103     TkMacOSXRestoreDrawingContext(&drawingContext);
1104 }
1105 
1106 #pragma mark -
1107 #pragma mark Accessors:
1108 
1109 /*
1110  *---------------------------------------------------------------------------
1111  *
1112  * TkMacOSXNSFontForFont --
1113  *
1114  *	Return an NSFont for the given Tk_Font.
1115  *
1116  * Results:
1117  *	NSFont*.
1118  *
1119  * Side effects:
1120  *	None.
1121  *
1122  *---------------------------------------------------------------------------
1123  */
1124 
1125 MODULE_SCOPE NSFont*
TkMacOSXNSFontForFont(Tk_Font tkfont)1126 TkMacOSXNSFontForFont(
1127     Tk_Font tkfont)
1128 {
1129     return tkfont ? ((MacFont *)tkfont)->nsFont : nil;
1130 }
1131 
1132 /*
1133  *---------------------------------------------------------------------------
1134  *
1135  * TkMacOSXNSFontAttributesForFont --
1136  *
1137  *	Return an NSDictionary of font attributes for the given Tk_Font.
1138  *
1139  * Results:
1140  *	NSFont*.
1141  *
1142  * Side effects:
1143  *	None.
1144  *
1145  *---------------------------------------------------------------------------
1146  */
1147 
1148 MODULE_SCOPE NSDictionary*
TkMacOSXNSFontAttributesForFont(Tk_Font tkfont)1149 TkMacOSXNSFontAttributesForFont(
1150     Tk_Font tkfont)
1151 {
1152     return tkfont ? ((MacFont *)tkfont)->nsAttributes : nil;
1153 }
1154 
1155 /*
1156  *---------------------------------------------------------------------------
1157  *
1158  * TkMacOSXIsCharacterMissing --
1159  *
1160  *	Given a tkFont and a character determine whether the character has
1161  *	a glyph defined in the font or not.
1162  *
1163  * Results:
1164  *	Returns a 1 if the character is missing, a 0 if it is not.
1165  *
1166  * Side effects:
1167  *	None.
1168  *
1169  *---------------------------------------------------------------------------
1170  */
1171 
1172 int
TkMacOSXIsCharacterMissing(Tk_Font tkfont,unsigned int searchChar)1173 TkMacOSXIsCharacterMissing(
1174     Tk_Font tkfont,		/* The font we are looking in. */
1175     unsigned int searchChar)	/* The character we are looking for. */
1176 {
1177     return 0;
1178 }
1179 
1180 /*
1181  *----------------------------------------------------------------------
1182  *
1183  * TkMacOSXUseAntialiasedText --
1184  *
1185  *	Enables or disables application-wide use of antialiased text (where
1186  *	available). Sets up a linked Tcl global variable to allow
1187  *	disabling of antialiased text from tcl.
1188  *	The possible values for this variable are:
1189  *
1190  *	-1 - Use system default as configurable in "System Prefs" -> "General".
1191  *	 0 - Unconditionally disable antialiasing.
1192  *	 1 - Unconditionally enable antialiasing.
1193  *
1194  * Results:
1195  *
1196  *	TCL_OK.
1197  *
1198  * Side effects:
1199  *
1200  *	None.
1201  *
1202  *----------------------------------------------------------------------
1203  */
1204 
1205 MODULE_SCOPE int
TkMacOSXUseAntialiasedText(Tcl_Interp * interp,int enable)1206 TkMacOSXUseAntialiasedText(
1207     Tcl_Interp * interp,	/* The Tcl interpreter to receive the
1208 				 * variable.*/
1209     int enable)			/* Initial value. */
1210 {
1211     static Boolean initialized = FALSE;
1212 
1213     if (!initialized) {
1214 	initialized = TRUE;
1215 
1216 	if (Tcl_CreateNamespace(interp, "::tk::mac", NULL, NULL) == NULL) {
1217 	    Tcl_ResetResult(interp);
1218 	}
1219 	if (Tcl_LinkVar(interp, "::tk::mac::antialiasedtext",
1220 		(char *) &antialiasedTextEnabled,
1221 		TCL_LINK_INT) != TCL_OK) {
1222 	    Tcl_ResetResult(interp);
1223 	}
1224     }
1225     antialiasedTextEnabled = enable;
1226     return TCL_OK;
1227 }
1228 
1229 /*
1230  * Local Variables:
1231  * mode: objc
1232  * c-basic-offset: 4
1233  * fill-column: 79
1234  * coding: utf-8
1235  * End:
1236  */
1237