1 /* Graphics_text.cpp
2  *
3  * Copyright (C) 1992-2021 Paul Boersma, 2013 Tom Naughton, 2017 David Weenink
4  *
5  * This code is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or (at
8  * your option) any later version.
9  *
10  * This code is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  * See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this work. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "../kar/UnicodeData.h"
20 #include "GraphicsP.h"
21 #include "../kar/longchar.h"
22 #include "Printer.h"
23 
24 extern const char * ipaSerifRegularPS [];
25 
26 /*
27  * When computing the width of a text by adding the widths of the separate characters,
28  * we will make a correction for systems that make slanted characters overlap the character box to their right.
29  * The effect is especially strong on Mac (older versions).
30  * The slant correction is taken relative to the font size.
31  */
32 #define POSTSCRIPT_SLANT_CORRECTION  0.1
33 #define SCREEN_SLANT_CORRECTION  0.05
34 
35 #define HAS_FI_AND_FL_LIGATURES  ( my postScript == true )
36 
37 #if cairo
38 	PangoFontMap *thePangoFontMap;
39 	PangoContext *thePangoContext;
40 	static bool hasTimes, hasHelvetica, hasCourier, hasPalatino, hasDoulos, hasCharis, hasIpaSerif;
41 #elif gdi
42 	#define win_MAXIMUM_FONT_SIZE  500
43 	static HFONT fonts [1 + (int) kGraphics_resolution::MAX] [1 + kGraphics_font_JAPANESE] [1+win_MAXIMUM_FONT_SIZE] [1 + Graphics_BOLD_ITALIC];
win_size2isize(int size)44 	static int win_size2isize (int size) { return size > win_MAXIMUM_FONT_SIZE ? win_MAXIMUM_FONT_SIZE : size; }
win_isize2size(int isize)45 	static int win_isize2size (int isize) { return isize; }
46 #elif quartz
47 	static bool hasTimes, hasHelvetica, hasCourier, hasPalatino, hasDoulos, hasCharis, hasIpaSerif;
48 	#define mac_MAXIMUM_FONT_SIZE  500
49 	static CTFontRef theScreenFonts [1 + kGraphics_font_DINGBATS] [1+mac_MAXIMUM_FONT_SIZE] [1 + Graphics_BOLD_ITALIC];
50 #endif
51 
52 #if gdi
53 	#ifdef __CYGWIN__
54 		#define FONT_TYPE_TYPE  unsigned int
55 	#else
56 		#define FONT_TYPE_TYPE  unsigned long int
57 	#endif
58 	static bool charisAvailable = false, doulosAvailable = false;
fontFuncEx_charis(const LOGFONTW * oldLogFont,const TEXTMETRICW * oldTextMetric,FONT_TYPE_TYPE fontType,LPARAM lparam)59 	static int CALLBACK fontFuncEx_charis (const LOGFONTW *oldLogFont, const TEXTMETRICW *oldTextMetric, FONT_TYPE_TYPE fontType, LPARAM lparam) {
60 		const LPENUMLOGFONTW logFont = (LPENUMLOGFONTW) oldLogFont; (void) oldTextMetric; (void) fontType; (void) lparam;
61 		charisAvailable = true;
62 		return 1;
63 	}
fontFuncEx_doulos(const LOGFONTW * oldLogFont,const TEXTMETRICW * oldTextMetric,FONT_TYPE_TYPE fontType,LPARAM lparam)64 	static int CALLBACK fontFuncEx_doulos (const LOGFONTW *oldLogFont, const TEXTMETRICW *oldTextMetric, FONT_TYPE_TYPE fontType, LPARAM lparam) {
65 		const LPENUMLOGFONTW logFont = (LPENUMLOGFONTW) oldLogFont; (void) oldTextMetric; (void) fontType; (void) lparam;
66 		doulosAvailable = true;
67 		return 1;
68 	}
loadFont(GraphicsScreen me,int font,int size,int style)69 	static HFONT loadFont (GraphicsScreen me, int font, int size, int style) {
70 		LOGFONTW spec;
71 		static int ipaInited;
72 		if (my printer || my metafile) {
73 			spec. lfHeight = - win_isize2size (size) * my resolution / 72.0;
74 		} else {
75 			spec. lfHeight = - win_isize2size (size) * my resolution / 72.0;
76 		}
77 		spec. lfWidth = 0;
78 		spec. lfEscapement = spec. lfOrientation = 0;
79 		spec. lfWeight = style & Graphics_BOLD ? FW_BOLD : 0;
80 		spec. lfItalic = style & Graphics_ITALIC ? 1 : 0;
81 		spec. lfUnderline = spec. lfStrikeOut = 0;
82 		spec. lfCharSet =
83 			font == kGraphics_font_SYMBOL ? SYMBOL_CHARSET :
84 			font == kGraphics_font_CHINESE ? DEFAULT_CHARSET :
85 			font == kGraphics_font_JAPANESE ? DEFAULT_CHARSET :
86 			font >= kGraphics_font_IPATIMES ? DEFAULT_CHARSET :
87 			ANSI_CHARSET;
88 		spec. lfOutPrecision = spec. lfClipPrecision = spec. lfQuality = 0;
89 		spec. lfPitchAndFamily =
90 			( font == (int) kGraphics_font::COURIER ? FIXED_PITCH : font == kGraphics_font_IPATIMES ? DEFAULT_PITCH : VARIABLE_PITCH ) |
91 			( font == (int) kGraphics_font::HELVETICA ? FF_SWISS : font == (int) kGraphics_font::COURIER ? FF_MODERN :
92 			  font == kGraphics_font_CHINESE ? FF_DONTCARE :
93 			  font == kGraphics_font_JAPANESE ? FF_DONTCARE :
94 			  font >= kGraphics_font_IPATIMES ? FF_DONTCARE : FF_ROMAN );
95 		if (font == kGraphics_font_IPATIMES && ! ipaInited && Melder_debug != 15) {
96 			LOGFONTW logFont;
97 			logFont. lfCharSet = DEFAULT_CHARSET;
98 			logFont. lfPitchAndFamily = 0;
99 			wcscpy (logFont. lfFaceName, L"Charis SIL");
100 			EnumFontFamiliesExW (my d_gdiGraphicsContext, & logFont, fontFuncEx_charis, 0, 0);
101 			wcscpy (logFont. lfFaceName, L"Doulos SIL");
102 			EnumFontFamiliesExW (my d_gdiGraphicsContext, & logFont, fontFuncEx_doulos, 0, 0);
103 			ipaInited = true;
104 			if (! charisAvailable && ! doulosAvailable) {
105 				/* BUG: The next warning may cause reentry of drawing (on window exposure) and lead to crash. Some code must be non-reentrant !! */
106 				Melder_warning (U"The phonetic font is not available.\nSeveral characters may not look correct.\nSee www.praat.org");
107 			}
108 		}
109 		wcscpy (spec. lfFaceName,
110 			font == (int) kGraphics_font::HELVETICA ? L"Arial" :
111 			font == (int) kGraphics_font::TIMES     ? L"Times New Roman" :
112 			font == (int) kGraphics_font::COURIER   ? L"Courier New" :
113 			font == (int) kGraphics_font::PALATINO  ? L"Book Antiqua" :
114 			font == kGraphics_font_SYMBOL    ? L"Symbol" :
115 			font == kGraphics_font_IPATIMES  ? ( doulosAvailable && style == 0 ? L"Doulos SIL" : charisAvailable ? L"Charis SIL" : L"Times New Roman" ) :
116 			font == kGraphics_font_DINGBATS  ? L"Wingdings" :
117 			font == kGraphics_font_CHINESE   ? L"SimSun" :
118 			font == kGraphics_font_JAPANESE  ? L"MS UI Gothic" :
119 			L"");
120 		return CreateFontIndirectW (& spec);
121 	}
122 #endif
123 
124 #if cairo
PangoFontDescription_create(int font,_Graphics_widechar * lc)125 	static PangoFontDescription *PangoFontDescription_create (int font, _Graphics_widechar *lc) {
126 		static PangoFontDescription *fontDescriptions [1 + kGraphics_font_DINGBATS];
127 		Melder_assert (font >= 0 && font <= kGraphics_font_DINGBATS);
128 		if (! fontDescriptions [font]) {
129 			const char *fontFace =
130 				font == (int) kGraphics_font::HELVETICA ? "Helvetica" :
131 				font == (int) kGraphics_font::TIMES ? "Times" :
132 				font == (int) kGraphics_font::COURIER ? "Courier New" :
133 				font == (int) kGraphics_font::PALATINO ? "Palatino" :
134 				font == kGraphics_font_IPATIMES ? "Doulos SIL" :
135 				font == kGraphics_font_IPAPALATINO ? "Charis SIL" :
136 				font == kGraphics_font_DINGBATS ? "Dingbats" : "Serif";
137 			fontDescriptions [font] = pango_font_description_from_string (fontFace);
138 		}
139 
140 		PangoStyle slant = (lc -> style & Graphics_ITALIC ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
141 		pango_font_description_set_style (fontDescriptions [font], slant);
142 
143 		PangoWeight weight = (lc -> style & Graphics_BOLD ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
144 		pango_font_description_set_weight (fontDescriptions [font], weight);
145 		pango_font_description_set_absolute_size (fontDescriptions [font], (int) (lc -> size * PANGO_SCALE));
146 		return fontDescriptions [font];
147 	}
148 #endif
149 
isDiacritic(Longchar_Info info,int font)150 inline static bool isDiacritic (Longchar_Info info, int font) {
151 	if (info -> isDiacritic == 0)
152 		return false;
153 	if (info -> isDiacritic == 1)
154 		return true;
155 	Melder_assert (info -> isDiacritic == 2);   // corner
156 	if (font == kGraphics_font_IPATIMES || font == kGraphics_font_IPAPALATINO)
157 		return false;   // Doulos or Charis
158 	return true;   // e.g. Times substitutes a zero-width corner
159 }
160 
161 /*
162 	Most operating systems provide automatic font substitution nowadays,
163 	so that e.g. phonetic characters will be drawn recognizably even if the
164 	user-preferred font is e.g. Times, Helvetica, Courier or Palatino
165 	and the phonetic characters are not available in that user font.
166 
167 	However, on some systems the substituted font might really be a last resort
168 	font that does not look at all similar to the user font. An example is
169 	the use of the sans-serif font Lucida Grande for phonetic characters
170 	within a stretch of a serif font such as Times or Palatino.
171 
172 	This is not good enough for Praat. We need more control over the shape
173 	of phonetic characters. We therefore advise the use of Doulos SIL,
174 	which is Times-like, or Charis SIL, which is Palatino-like.
175 	For true continuity between non-phonetic and phonetic characters it is
176 	mandatory that the exact same font is used for both types of characters,
177 	so we use Doulos SIL to replace Times even for non-phonetic characters,
178 	and Charis SIL to replace Palatino even for non-phonetic characters.
179 	A technical issue that makes this even more important is that diacritics
180 	can look really weird if at the beginning of a Praat font stretch:
181 	a "b" followed by a ring below will not be aligned correctly if they
182 	are part of different Praat font stretches.
183 
184 	Beside Praat-enforced visual font continuity, there are some more issues,
185 	such as that the "/" (slash) character should extend below the baseline
186 	whenever it is used in an equation or to demarcate a phonological
187 	representation.
188 */
189 #if cairo || quartz
chooseFont(Graphics me,_Graphics_widechar * lc)190 inline static int chooseFont (Graphics me, _Graphics_widechar *lc) {
191 	/*
192 		When we arrive here, the character's font is the "user-preferred font",
193 		which is Courier if the user asked for code style (e.g. between "$$" and "$"),
194 		or otherwise Times, Helvetica, Courier or Palatino, as chosen from a font menu.
195 
196 		Exception: if the character is a slash, its font has already been converted to Courier.
197 	*/
198 	int font = lc -> font.integer_;
199 	Longchar_Info info = lc -> karInfo;
200 	int alphabet = info -> alphabet;
201 
202 	if (font == (int) kGraphics_font::COURIER) {
203 		constexpr bool systemSubstitutesMonospacedSerifFontForIpaCourier = (quartz);
204 		if (systemSubstitutesMonospacedSerifFontForIpaCourier) {
205 			/*
206 				No need to check whether the character is phonetic or not.
207 			*/
208 			return (int) kGraphics_font::COURIER;
209 		}
210 		if (alphabet == Longchar_SYMBOL ||
211 			alphabet == Longchar_PHONETIC ||
212 			lc [1]. kar > U'\t' && lc [1]. karInfo -> isDiacritic)   // inspect next character to ensure diacritic continuity
213 		{
214 			/*
215 				Serif is more important than monospaced,
216 				and Charis looks slightly better within Courier than Doulos does.
217 			*/
218 			if (hasCharis)
219 				return kGraphics_font_IPAPALATINO;
220 			if (hasDoulos)
221 				return kGraphics_font_IPATIMES;
222 		}
223 		return (int) kGraphics_font::COURIER;
224 	}
225 	font =
226 		alphabet == Longchar_SYMBOL || // ? kGraphics_font_SYMBOL :
227 		alphabet == Longchar_PHONETIC ?
228 			( my font == kGraphics_font::TIMES ?
229 				( hasDoulos ?
230 					( lc -> style == 0 ?
231 						kGraphics_font_IPATIMES :
232 					  hasCharis ?
233 						kGraphics_font_IPAPALATINO :   // other styles in Charis, because Doulos has no bold or italic
234 						(int) kGraphics_font::TIMES
235 					) :
236 				  hasCharis ?
237 					kGraphics_font_IPAPALATINO :
238 					(int) kGraphics_font::TIMES   // on newer systems, Times and Times New Roman have a lot of phonetic characters
239 				) :
240 			  my font == kGraphics_font::HELVETICA ?
241 				(int) kGraphics_font::HELVETICA :   // sans serif, so fall back on Lucida Grande or so for phonetic characters
242 			  /* my font must be kGraphics_font_PALATINO */
243 			  hasCharis && Melder_debug != 900 ?
244 				kGraphics_font_IPAPALATINO :
245 			  hasDoulos && Melder_debug != 900 ?
246 				( lc -> style == 0 ?
247 					kGraphics_font_IPATIMES :
248 					(int) kGraphics_font::TIMES
249 				) :
250 				(int) kGraphics_font::PALATINO
251 			) :
252 		alphabet == Longchar_DINGBATS ?
253 			kGraphics_font_DINGBATS :
254 		my font == kGraphics_font::TIMES ?
255 			( hasDoulos ?
256 				( lc -> style == 0 ?
257 					kGraphics_font_IPATIMES :
258 				  lc -> style == Graphics_ITALIC ?
259 					( lc [1]. kar > U'\t' && lc [1]. karInfo -> isDiacritic && hasCharis ?
260 						kGraphics_font_IPAPALATINO : (int) kGraphics_font::TIMES ) :   // correct placement of diacritics
261 				  hasCharis ?
262 					kGraphics_font_IPAPALATINO :
263 					(int) kGraphics_font::TIMES
264 				) :
265 				(int) kGraphics_font::TIMES
266 			) :
267 		my font == kGraphics_font::HELVETICA ?
268 			(int) kGraphics_font::HELVETICA :
269 		my font == kGraphics_font::PALATINO ?
270 			( hasCharis && Melder_debug != 900 ?
271 				kGraphics_font_IPAPALATINO :
272 				(int) kGraphics_font::PALATINO
273 			) :
274 		(int) my font;   // why not lc -> font.integer_?
275 	Melder_assert (font >= 0 && font <= kGraphics_font_DINGBATS);
276 	return font;
277 }
278 #endif
279 
charSize(Graphics anyGraphics,_Graphics_widechar * lc)280 static void charSize (Graphics anyGraphics, _Graphics_widechar *lc) {
281 	if (anyGraphics -> screen) {
282 		#if cairo
283 			GraphicsScreen me = static_cast <GraphicsScreen> (anyGraphics);
284 			Melder_assert (my duringXor);
285 			const int normalSize = my fontSize * my resolution / 72.0;
286 			const int smallSize = (3 * normalSize + 2) / 4;
287 			const int size = ( lc -> size < 100 ? smallSize : normalSize );
288 			lc -> width = 7;
289 			lc -> baseline *= my fontSize * 0.01;
290 			lc -> code = lc -> kar;
291 			lc -> font.string = nullptr;
292 			lc -> font.integer_ = 0;
293 			lc -> size = size;
294 		#elif gdi
295 			GraphicsScreen me = static_cast <GraphicsScreen> (anyGraphics);
296 			Longchar_Info info = lc -> karInfo;
297 			const int normalSize = win_size2isize (my fontSize);
298 			const int smallSize = (3 * normalSize + 2) / 4;
299 			int font = info -> alphabet == Longchar_SYMBOL ? kGraphics_font_SYMBOL :
300 			       info -> alphabet == Longchar_PHONETIC ? kGraphics_font_IPATIMES :
301 			       info -> alphabet == Longchar_DINGBATS ? kGraphics_font_DINGBATS : lc -> font.integer_;
302 			if ((unsigned int) lc -> kar >= 0x2E80 && (unsigned int) lc -> kar <= 0x9FFF)
303 				font = ( theGraphicsCjkFontStyle == kGraphics_cjkFontStyle::CHINESE ? kGraphics_font_CHINESE : kGraphics_font_JAPANESE );
304 			const int size = ( lc -> size < 100 ? smallSize : normalSize );
305 			const int style = lc -> style & (Graphics_ITALIC | Graphics_BOLD);   // take out Graphics_CODE
306 			HFONT fontInfo = fonts [(int) my resolutionNumber] [font] [size] [style];
307 			if (! fontInfo) {
308 				fontInfo = loadFont (me, font, size, style);
309 				if (! fontInfo)
310 					return;
311 				fonts [(int) my resolutionNumber] [font] [size] [style] = fontInfo;
312 			}
313 			SIZE extent;
314 			lc -> code =
315 				font == kGraphics_font_IPATIMES ||
316 				font == (int) kGraphics_font::TIMES ||
317 				font == (int) kGraphics_font::HELVETICA ||
318 				font == kGraphics_font_CHINESE ||
319 				font == kGraphics_font_JAPANESE ||
320 				font == (int) kGraphics_font::COURIER ? lc -> kar :
321 				info -> winEncoding;
322 			if (lc -> code == 0) {
323 				_Graphics_widechar *lc2;
324 				if (lc -> kar == UNICODE_LATIN_SMALL_LETTER_SCHWA_WITH_HOOK) {
325 					info = Longchar_getInfo (U's', U'w');
326 					lc -> kar = info -> unicode;
327 					lc -> code = info -> winEncoding;
328 					for (lc2 = lc + 1; lc2 -> kar != U'\0'; lc2 ++) { }
329 					lc2 [1]. kar = U'\0';
330 					while (lc2 - lc > 0) {
331 						lc2 [0] = lc2 [-1];
332 						lc2 --;
333 					}
334 					lc [1]. kar = UNICODE_MODIFIER_LETTER_RHOTIC_HOOK;
335 				} else if (lc -> kar == UNICODE_LATIN_SMALL_LETTER_L_WITH_MIDDLE_TILDE) {
336 					info = Longchar_getInfo (U'l', U' ');
337 					lc -> kar = info -> unicode;
338 					lc -> code = info -> winEncoding;
339 					for (lc2 = lc + 1; lc2 -> kar != U'\0'; lc2 ++) { }
340 					lc2 [1]. kar = U'\0';
341 					while (lc2 - lc > 0) {
342 						lc2 [0] = lc2 [-1];
343 						lc2 --;
344 					}
345 					lc [1]. kar = UNICODE_COMBINING_TILDE_OVERLAY;
346 				}
347 			}
348 			SelectFont (my d_gdiGraphicsContext, fontInfo);
349 			if (lc -> code <= 0x00FFFF) {
350 				char16 code = (char16) lc -> code;
351 				GetTextExtentPoint32W (my d_gdiGraphicsContext, (WCHAR *) & code, 1, & extent);
352 			} else {
353 				char32 code [2] { lc -> code, U'\0' };
354 				GetTextExtentPoint32W (my d_gdiGraphicsContext, Melder_peek32toW (code), 2, & extent);
355 			}
356 			lc -> width = extent. cx;
357 			lc -> baseline *= my fontSize * 0.01 * my resolution / 72.0;
358 			lc -> font.string = nullptr;
359 			lc -> font.integer_ = font;   // kGraphics_font_HELVETICA .. kGraphics_font_DINGBATS
360 			lc -> size = size;   // 0..4 instead of 10..24
361 			lc -> style = style;   // without Graphics_CODE
362 		#elif quartz
363 		#endif
364 	} else if (anyGraphics -> postScript) {
365 		GraphicsPostscript me = static_cast <GraphicsPostscript> (anyGraphics);
366 		const int normalSize = (int) ((double) my fontSize * (double) my resolution / 72.0);
367 		Longchar_Info info = lc -> karInfo;
368 		const int font = info -> alphabet == Longchar_SYMBOL ? kGraphics_font_SYMBOL :
369 				info -> alphabet == Longchar_PHONETIC ? kGraphics_font_IPATIMES :
370 				info -> alphabet == Longchar_DINGBATS ? kGraphics_font_DINGBATS : lc -> font.integer_;
371 		const int style = lc -> style == Graphics_ITALIC ? Graphics_ITALIC :
372 			lc -> style == Graphics_BOLD || lc -> link ? Graphics_BOLD :
373 			lc -> style == Graphics_BOLD_ITALIC ? Graphics_BOLD_ITALIC : 0;
374 		if (! my fontInfos [font] [style]) {
375 			const char *fontInfo, *secondaryFontInfo = nullptr, *tertiaryFontInfo = nullptr;
376 			if (font == (int) kGraphics_font::COURIER) {
377 				fontInfo = style == Graphics_BOLD ? "Courier-Bold" :
378 					style == Graphics_ITALIC ? "Courier-Oblique" :
379 					style == Graphics_BOLD_ITALIC ? "Courier-BoldOblique" : "Courier";
380 				secondaryFontInfo = style == Graphics_BOLD ? "CourierNewPS-BoldMT" :
381 					style == Graphics_ITALIC ? "CourierNewPS-ItalicMT" :
382 					style == Graphics_BOLD_ITALIC ? "CourierNewPS-BoldItalicMT" : "CourierNewPSMT";
383 				tertiaryFontInfo = style == Graphics_BOLD ? "CourierNew-Bold" :
384 					style == Graphics_ITALIC ? "CourierNew-Italic" :
385 					style == Graphics_BOLD_ITALIC ? "CourierNew-BoldItalic" : "CourierNew";
386 			} else if (font == (int) kGraphics_font::TIMES) {
387 				fontInfo = style == Graphics_BOLD ? "Times-Bold" :
388 					style == Graphics_ITALIC ? "Times-Italic" :
389 					style == Graphics_BOLD_ITALIC ? "Times-BoldItalic" : "Times-Roman";
390 				secondaryFontInfo = style == Graphics_BOLD ? "TimesNewRomanPS-BoldMT" :
391 					style == Graphics_ITALIC ? "TimesNewRomanPS-ItalicMT" :
392 					style == Graphics_BOLD_ITALIC ? "TimesNewRomanPS-BoldItalicMT" : "TimesNewRomanPSMT";
393 				tertiaryFontInfo = style == Graphics_BOLD ? "TimesNewRoman-Bold" :
394 					style == Graphics_ITALIC ? "TimesNewRoman-Italic" :
395 					style == Graphics_BOLD_ITALIC ? "TimesNewRoman-BoldItalic" : "TimesNewRoman";
396 			} else if (font == (int) kGraphics_font::PALATINO) {
397 				fontInfo = style == Graphics_BOLD ? "Palatino-Bold" :
398 					style == Graphics_ITALIC ? "Palatino-Italic" :
399 					style == Graphics_BOLD_ITALIC ? "Palatino-BoldItalic" : "Palatino-Roman";
400 				secondaryFontInfo = style == Graphics_BOLD ? "BookAntiquaPS-BoldMT" :
401 					style == Graphics_ITALIC ? "BookAntiquaPS-ItalicMT" :
402 					style == Graphics_BOLD_ITALIC ? "BookAntiquaPS-BoldItalicMT" : "BookAntiquaPSMT";
403 				tertiaryFontInfo = style == Graphics_BOLD ? "BookAntiqua-Bold" :
404 					style == Graphics_ITALIC ? "BookAntiqua-Italic" :
405 					style == Graphics_BOLD_ITALIC ? "BookAntiqua-BoldItalic" : "BookAntiqua";
406 			} else if (font == kGraphics_font_IPATIMES) {
407 				if (my includeFonts && ! my loadedXipa) {
408 					const char **p;
409 					for (p = & ipaSerifRegularPS [0]; *p; p ++)
410 						my d_printf (my d_file, "%s", *p);
411 					my loadedXipa = true;
412 				}
413 				fontInfo = my useSilipaPS ?
414 					(style == Graphics_BOLD || style == Graphics_BOLD_ITALIC ? "SILDoulosIPA93Bold" : "SILDoulosIPA93Regular") :
415 					"TeX-xipa10-Praat-Regular";
416 			} else if (font == kGraphics_font_SYMBOL) {
417 				fontInfo = "Symbol";
418 			} else if (font == kGraphics_font_DINGBATS) {
419 				fontInfo = "ZapfDingbats";
420 			} else {
421 				fontInfo = style == Graphics_BOLD ? "Helvetica-Bold" :
422 					style == Graphics_ITALIC ? "Helvetica-Oblique" :
423 					style == Graphics_BOLD_ITALIC ? "Helvetica-BoldOblique" : "Helvetica";
424 				secondaryFontInfo = style == Graphics_BOLD ? "Arial-BoldMT" :
425 					style == Graphics_ITALIC ? "Arial-ItalicMT" :
426 					style == Graphics_BOLD_ITALIC ? "Arial-BoldItalicMT" : "ArialMT";
427 				tertiaryFontInfo = style == Graphics_BOLD ? "Arial-Bold" :
428 					style == Graphics_ITALIC ? "Arial-Italic" :
429 					style == Graphics_BOLD_ITALIC ? "Arial-BoldItalic" : "Arial";
430 			}
431 			my fontInfos [font] [style] = Melder_malloc_f (char, 100);
432 			if (font == kGraphics_font_IPATIMES || font == kGraphics_font_SYMBOL || font == kGraphics_font_DINGBATS) {
433 				strcpy (my fontInfos [font] [style], fontInfo);
434 			} else {
435 				sprintf (my fontInfos [font] [style], "%s-Praat", fontInfo);
436 				if (thePrinter. fontChoiceStrategy == kGraphicsPostscript_fontChoiceStrategy::LINOTYPE) {
437 					my d_printf (my d_file, "/%s /%s-Praat PraatEncode\n", fontInfo, fontInfo);
438 				} else if (thePrinter. fontChoiceStrategy == kGraphicsPostscript_fontChoiceStrategy::MONOTYPE) {
439 					my d_printf (my d_file, "/%s /%s-Praat PraatEncode\n", tertiaryFontInfo, fontInfo);
440 				} else if (thePrinter. fontChoiceStrategy == kGraphicsPostscript_fontChoiceStrategy::PS_MONOTYPE) {
441 					my d_printf (my d_file, "/%s /%s-Praat PraatEncode\n", secondaryFontInfo, fontInfo);
442 				} else {
443 					/* Automatic font choice strategy. */
444 					if (secondaryFontInfo) {
445 						my d_printf (my d_file,
446 							"/%s /Font resourcestatus\n"
447 							"{ pop pop /%s /%s-Praat PraatEncode }\n"
448 							"{ /%s /%s-Praat PraatEncode }\n"
449 							"ifelse\n",
450 							secondaryFontInfo, secondaryFontInfo, fontInfo, fontInfo, fontInfo);
451 					} else {
452 						my d_printf (my d_file, "/%s /%s-Praat PraatEncode\n", fontInfo, fontInfo);
453 					}
454 				}
455 			}
456 		}
457 		lc -> font.integer_ = 0;
458 		lc -> font.string = my fontInfos [font] [style];
459 
460 		/*
461 		 * Convert size and baseline information to device coordinates.
462 		 */
463 		lc -> size *= normalSize * 0.01;
464 		lc -> baseline *= normalSize * 0.01;
465 
466 		if (font == (int) kGraphics_font::COURIER) {
467 			lc -> width = 600;   // Courier
468 		} else if (style == 0) {
469 			if (font == (int) kGraphics_font::TIMES)
470 				lc -> width = info -> ps.times;
471 			else if (font == (int) kGraphics_font::HELVETICA)
472 				lc -> width = info -> ps.helvetica;
473 			else if (font == (int) kGraphics_font::PALATINO)
474 				lc -> width = info -> ps.palatino;
475 			else if (font == kGraphics_font_SYMBOL)
476 				lc -> width = info -> ps.times;
477 			else if (my useSilipaPS)
478 				lc -> width = info -> ps.timesItalic;   // ?
479 			else
480 				lc -> width = info -> ps.times;   // XIPA
481 		} else if (style == Graphics_BOLD) {
482 			if (font == (int) kGraphics_font::TIMES)
483 				lc -> width = info -> ps.timesBold;
484 			else if (font == (int) kGraphics_font::HELVETICA)
485 				lc -> width = info -> ps.helveticaBold;
486 			else if (font == (int) kGraphics_font::PALATINO)
487 				lc -> width = info -> ps.palatinoBold;
488 			else if (font == kGraphics_font_SYMBOL)
489 				lc -> width = info -> ps.times;
490 			else if (my useSilipaPS)
491 				lc -> width = info -> ps.timesBoldItalic;   // ?
492 			else
493 				lc -> width = info -> ps.times;   // Symbol, IPA
494 		} else if (style == Graphics_ITALIC) {
495 			if (font == (int) kGraphics_font::TIMES)
496 				lc -> width = info -> ps.timesItalic;
497 			else if (font == (int) kGraphics_font::HELVETICA)
498 				lc -> width = info -> ps.helvetica;
499 			else if (font == (int) kGraphics_font::PALATINO)
500 				lc -> width = info -> ps.palatinoItalic;
501 			else if (font == kGraphics_font_SYMBOL)
502 				lc -> width = info -> ps.times;
503 			else if (my useSilipaPS)
504 				lc -> width = info -> ps.timesItalic;
505 			else
506 				lc -> width = info -> ps.times;   // Symbol, IPA
507 		} else if (style == Graphics_BOLD_ITALIC) {
508 			if (font == (int) kGraphics_font::TIMES)
509 				lc -> width = info -> ps.timesBoldItalic;
510 			else if (font == (int) kGraphics_font::HELVETICA)
511 				lc -> width = info -> ps.helveticaBold;
512 			else if (font == (int) kGraphics_font::PALATINO)
513 				lc -> width = info -> ps.palatinoBoldItalic;
514 			else if (font == kGraphics_font_SYMBOL)
515 				lc -> width = info -> ps.times;
516 			else if (my useSilipaPS)
517 				lc -> width = info -> ps.timesBoldItalic;
518 			else
519 				lc -> width = info -> ps.times;   // Symbol, IPA
520 		}
521 		lc -> width *= lc -> size / 1000.0;
522 		lc -> code = ( font == kGraphics_font_IPATIMES && my useSilipaPS ? info -> macEncoding : info -> psEncoding );
523 		if (lc -> code == 0) {
524 			_Graphics_widechar *lc2;
525 			if (lc -> kar == UNICODE_LATIN_SMALL_LETTER_SCHWA_WITH_HOOK) {
526 				info = Longchar_getInfo (U's', U'w');
527 				lc -> kar = info -> unicode;
528 				lc -> code = info -> macEncoding;
529 				lc -> width = info -> ps.timesItalic * lc -> size / 1000.0;
530 				for (lc2 = lc + 1; lc2 -> kar != U'\0'; lc2 ++) { }
531 				lc2 [1]. kar = U'\0';
532 				while (lc2 - lc > 0) {
533 					lc2 [0] = lc2 [-1];
534 					lc2 --;
535 				}
536 				lc [1]. kar = UNICODE_MODIFIER_LETTER_RHOTIC_HOOK;
537 			} else if (lc -> kar == UNICODE_LATIN_SMALL_LETTER_L_WITH_MIDDLE_TILDE) {
538 				info = Longchar_getInfo (U'l', U' ');
539 				lc -> code = info -> macEncoding;
540 				lc -> kar = info -> unicode;
541 				lc -> width = info -> ps.timesItalic * lc -> size / 1000.0;
542 				for (lc2 = lc + 1; lc2 -> kar != U'\0'; lc2 ++) { }
543 				lc2 [1]. kar = U'\0';
544 				while (lc2 - lc > 0) {
545 					lc2 [0] = lc2 [-1];
546 					lc2 --;
547 				}
548 				lc [1]. kar = UNICODE_COMBINING_TILDE_OVERLAY;
549 			}
550 		}
551 	}
552 }
553 
554 #if quartz
quartz_getFontName(int font,int style)555 static conststring32 quartz_getFontName (int font, int style) {
556 	if (Melder_systemVersion < 110000)
557 		style = 0;   // style extension in the name was not needed on macOS X
558 	switch (font) {
559 		case (int) kGraphics_font::TIMES:
560 			return
561 				style == 0 ? U"Times New Roman"
562 				: style == Graphics_BOLD ? U"Times New Roman Bold"
563 				: style == Graphics_ITALIC ? U"Times New Roman Italic"
564 				: U"Times New Roman Bold Italic";
565 		case (int) kGraphics_font::HELVETICA:
566 			return
567 				style == 0 ? U"Arial"
568 				: style == Graphics_BOLD ? U"Arial Bold"
569 				: style == Graphics_ITALIC ? U"Arial Italic"
570 				: U"Arial Bold Italic";
571 		case (int) kGraphics_font::COURIER:
572 			return
573 				style == 0 ? U"Courier New"
574 				: style == Graphics_BOLD ? U"Courier New Bold"
575 				: style == Graphics_ITALIC ? U"Courier New Italic"
576 				: U"Courier New Bold Italic";
577 		case (int) kGraphics_font::PALATINO:
578 			if (Melder_debug == 900)
579 				return U"DG Meta Serif Science";
580 			else
581 				return style == 0 ? U"Palatino"
582 				: style == Graphics_BOLD ? U"Palatino Bold"
583 				: style == Graphics_ITALIC ? U"Palatino Italic"
584 				: U"Palatino Bold Italic";
585 		case kGraphics_font_SYMBOL:
586 			return U"Symbol";
587 		case kGraphics_font_IPATIMES:
588 			return U"Doulos SIL";
589 		case kGraphics_font_IPAPALATINO:
590 			return
591 				style == 0 ? U"Charis SIL"
592 				: style == Graphics_BOLD ? U"Charis SIL Bold"
593 				: style == Graphics_ITALIC ? U"Charis SIL Italic"
594 				: U"Charis SIL Bold Italic";
595 		case kGraphics_font_DINGBATS:
596 			return U"Zapf Dingbats";
597 		default:
598 			return nullptr;
599 	}
600 }
quartz_getFontRef(int font,int size,int style)601 static CTFontRef quartz_getFontRef (int font, int size, int style) {
602 	CTFontSymbolicTraits ctStyle = ( style & Graphics_BOLD ? kCTFontBoldTrait : 0 ) | ( style & Graphics_ITALIC ? kCTFontItalicTrait : 0 );
603 #if 1
604 	CFStringRef key = kCTFontSymbolicTrait;
605 	CFNumberRef value = CFNumberCreate (nullptr, kCFNumberIntType, & ctStyle);
606 	CFIndex numberOfValues = 1;
607 	CFDictionaryRef styleDict = CFDictionaryCreate (nullptr, (const void **) & key, (const void **) & value, numberOfValues,
608 		& kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
609 	CFRelease (value);
610 	CFStringRef keys [2];
611 	keys [0] = kCTFontTraitsAttribute;
612 	keys [1] = kCTFontNameAttribute;
613 	conststring32 fontName = quartz_getFontName (font, style);
614 	CFStringRef cfFont = (CFStringRef) Melder_peek32toCfstring (fontName);
615 	void *values [2] = { (void *) styleDict, (void *) cfFont };
616 	CFDictionaryRef attributes = CFDictionaryCreate (nullptr, (const void **) & keys, (const void **) & values, 2,
617 		& kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
618 	CFRelease (styleDict);
619 	CTFontDescriptorRef ctFontDescriptor = CTFontDescriptorCreateWithAttributes (attributes);
620 	CFRelease (attributes);
621 #else
622 	NSMutableDictionary *styleDict = [[NSMutableDictionary alloc] initWithCapacity: 1];
623 	[styleDict   setObject: [NSNumber numberWithUnsignedInt: ctStyle]   forKey: (id) kCTFontSymbolicTrait];
624 	NSMutableDictionary *attributes = [[NSMutableDictionary alloc] initWithCapacity: 2];
625 	[attributes   setObject: styleDict   forKey: (id) kCTFontTraitsAttribute];
626 	switch (font) {
627 		case (int) kGraphics_font::TIMES:       { [attributes   setObject: @"Times New Roman" forKey: (id) kCTFontNameAttribute]; } break;
628 		case (int) kGraphics_font::HELVETICA:   { [attributes   setObject: @"Arial"           forKey: (id) kCTFontNameAttribute]; } break;
629 		case (int) kGraphics_font::COURIER:     { [attributes   setObject: @"Courier New"     forKey: (id) kCTFontNameAttribute]; } break;
630 		case (int) kGraphics_font::PALATINO:    { if (Melder_debug == 900)
631 												[attributes   setObject: @"DG Meta Serif Science" forKey: (id) kCTFontNameAttribute];
632 										   else
633 												[attributes   setObject: @"Palatino"              forKey: (id) kCTFontNameAttribute];
634 										 } break;
635 		case kGraphics_font_SYMBOL:      { [attributes   setObject: @"Symbol"          forKey: (id) kCTFontNameAttribute]; } break;
636 		case kGraphics_font_IPATIMES:    { [attributes   setObject: @"Doulos SIL"      forKey: (id) kCTFontNameAttribute]; } break;
637 		case kGraphics_font_IPAPALATINO: { [attributes   setObject: @"Charis SIL"      forKey: (id) kCTFontNameAttribute]; } break;
638 		case kGraphics_font_DINGBATS:    { [attributes   setObject: @"Zapf Dingbats"   forKey: (id) kCTFontNameAttribute]; } break;
639 	}
640 	CTFontDescriptorRef ctFontDescriptor = CTFontDescriptorCreateWithAttributes ((CFMutableDictionaryRef) attributes);
641 	[styleDict release];
642 	[attributes release];
643 #endif
644 	CTFontRef ctFont = CTFontCreateWithFontDescriptor (ctFontDescriptor, size, nullptr);
645 	CFRelease (ctFontDescriptor);
646 	return ctFont;
647 }
648 #endif
649 
charDraw(Graphics anyGraphics,int xDC,int yDC,_Graphics_widechar * lc,const char32 codes[],int nchars,int width)650 static void charDraw (Graphics anyGraphics, int xDC, int yDC, _Graphics_widechar *lc,
651 	const char32 codes [], int nchars, int width)
652 {
653 	trace (U"nchars ", nchars, U" first ", (int) lc->kar, U" ", (char32) lc -> kar, U" rightToLeft ", lc->rightToLeft);
654 	if (anyGraphics -> postScript) {
655 		GraphicsPostscript me = static_cast <GraphicsPostscript> (anyGraphics);
656 		const bool onlyRegular = lc -> font.string [0] == 'S' ||
657 			(lc -> font.string [0] == 'T' && lc -> font.string [1] == 'e');   // Symbol & SILDoulos !
658 		const bool slant = (lc -> style & Graphics_ITALIC) && onlyRegular;
659 		const bool thick = (lc -> style & Graphics_BOLD) && onlyRegular;
660 		if (lc -> font.string != my lastFid || lc -> size != my lastSize)
661 			my d_printf (my d_file, my languageLevel == 1 ? "/%s %d FONT\n" : "/%s %d selectfont\n",
662 					my lastFid = lc -> font.string, my lastSize = lc -> size);
663 		if (lc -> link)
664 			my d_printf (my d_file, "0 0 1 setrgbcolor\n");
665 		for (int i = -3; i <= 3; i ++) {
666 			if (i != 0 && ! thick)
667 				continue;
668 			my d_printf (my d_file, "%d %d M ", xDC + i, yDC);
669 			if (my textRotation != 0.0 || slant) {
670 				my d_printf (my d_file, "gsave currentpoint translate ");
671 				if (my textRotation != 0.0)
672 					my d_printf (my d_file, "%.6g rotate 0 0 M\n", (double) my textRotation);
673 				if (slant)
674 					my d_printf (my d_file, "[1 0 0.25 1 0 0] concat 0 0 M\n");
675 			}
676 			my d_printf (my d_file, "(");
677 			const char32 *p = & codes [0];
678 			while (*p) {
679 				if (*p == U'(' || *p == U')' || *p == U'\\') {
680 					my d_printf (my d_file, "\\%c", (unsigned char) *p);
681 				} else if (*p >= 32 && *p <= 126) {
682 					my d_printf (my d_file, "%c", (unsigned char) *p);
683 				} else {
684 					my d_printf (my d_file, "\\%d%d%d", (unsigned char) *p / 64,
685 						((unsigned char) *p % 64) / 8, (unsigned char) *p % 8);
686 				}
687 				p ++;
688 			}
689 			my d_printf (my d_file, ") show\n");
690 			if (my textRotation != 0.0 || slant)
691 				my d_printf (my d_file, "grestore\n");
692 		}
693 		if (lc -> link)
694 			my d_printf (my d_file, "0 0 0 setrgbcolor\n");
695 	} else if (anyGraphics -> screen) {
696 		GraphicsScreen me = static_cast <GraphicsScreen> (anyGraphics);
697 		#if cairo
698 			if (! my d_cairoGraphicsContext)
699 				return;
700 			// TODO!
701 			if (lc -> link)
702 				_Graphics_setColour (me, Melder_BLUE);
703 			int font = lc -> font.integer_;
704 			cairo_save (my d_cairoGraphicsContext);
705 			cairo_translate (my d_cairoGraphicsContext, xDC, yDC);
706 			//cairo_scale (my d_cairoGraphicsContext, 1, -1);
707 			cairo_rotate (my d_cairoGraphicsContext, - my textRotation * NUMpi / 180.0);
708 			const char *codes8 = Melder_peek32to8 (codes);
709 			PangoFontDescription *font_description = PangoFontDescription_create (font, lc);
710 			PangoLayout *layout = pango_cairo_create_layout (my d_cairoGraphicsContext);
711 			pango_layout_set_font_description (layout, font_description);
712 			pango_layout_set_text (layout, codes8, -1);
713 			cairo_move_to (my d_cairoGraphicsContext, 0 /*xDC*/, 0 /*yDC*/);
714 			// instead of pango_cairo_show_layout we use pango_cairo_show_layout_line to
715 			// get the same text origin as cairo_show_text, i.e. baseline left, instead of Pango's top left!
716 			pango_cairo_show_layout_line (my d_cairoGraphicsContext, pango_layout_get_line_readonly (layout, 0));
717 			g_object_unref (layout);
718 			cairo_restore (my d_cairoGraphicsContext);
719 			if (lc -> link)
720 				_Graphics_setColour (me, my colour);
721 			return;
722 		#elif gdi
723 			const int font = lc -> font.integer_;
724 			conststringW codesW = Melder_peek32toW (codes);
725 			if (my duringXor) {
726 				/*
727 					On GDI, SetROP2 does not influence text drawing,
728 					so we have to create a bitmap in the background
729 					and use BitBlt with SRCINVERT as its ROP.
730 				*/
731 				const int descent = (1.0/216) * my fontSize * my resolution;
732 				const int ascent = (1.0/72) * my fontSize * my resolution;
733 				const int maxWidth = 800, maxHeight = 200;
734 				const int baseline = 100, top = baseline - ascent - 1, bottom = baseline + descent + 1;
735 				static bool inited = false;
736 				static HDC dc;
737 				static HBITMAP bitmap;
738 				if (! inited) {
739 					dc = CreateCompatibleDC (my d_gdiGraphicsContext);
740 					bitmap = CreateCompatibleBitmap (my d_gdiGraphicsContext, maxWidth, maxHeight);
741 					SelectBitmap (dc, bitmap);
742 					SetBkMode (dc, TRANSPARENT);   // not the default!
743 					SelectPen (dc, GetStockPen (BLACK_PEN));
744 					SelectBrush (dc, GetStockBrush (BLACK_BRUSH));
745 					SetTextAlign (dc, TA_LEFT | TA_BASELINE | TA_NOUPDATECP);   // baseline is not the default!
746 					inited = true;
747 				}
748 				width += 4;   // for slant
749 				Rectangle (dc, 0, top, width, bottom);
750 				SelectFont (dc, fonts [(int) my resolutionNumber] [font] [lc -> size] [lc -> style]);
751 				SetTextColor (dc, my d_winForegroundColour);
752 				TextOutW (dc, 0, baseline, codesW, str16len ((conststring16) codesW));
753 				BitBlt (my d_gdiGraphicsContext, xDC, yDC - ascent, width, bottom - top, dc, 0, top, SRCINVERT);
754 				return;
755 			}
756 			SelectPen (my d_gdiGraphicsContext, my d_winPen);
757 			SelectBrush (my d_gdiGraphicsContext, my d_winBrush);
758 			if (lc -> link)
759 				SetTextColor (my d_gdiGraphicsContext, RGB (0, 0, 255));
760 			else
761 				SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
762 			SelectFont (my d_gdiGraphicsContext, fonts [(int) my resolutionNumber] [font] [lc -> size] [lc -> style]);
763 			if (my textRotation == 0.0) {
764 				TextOutW (my d_gdiGraphicsContext, xDC, yDC, codesW, str16len ((const char16 *) codesW));
765 			} else {
766 				int restore = SaveDC (my d_gdiGraphicsContext);
767 				SetGraphicsMode (my d_gdiGraphicsContext, GM_ADVANCED);
768 				double a = my textRotation * NUMpi / 180.0, cosa = cos (a), sina = sin (a);
769 				XFORM rotate = { (float) cosa, (float) - sina, (float) sina, (float) cosa, 0.0, 0.0 };
770 				ModifyWorldTransform (my d_gdiGraphicsContext, & rotate, MWT_RIGHTMULTIPLY);
771 				XFORM translate = { 1.0, 0.0, 0.0, 1.0, (float) xDC, (float) yDC };
772 				ModifyWorldTransform (my d_gdiGraphicsContext, & translate, MWT_RIGHTMULTIPLY);
773 				TextOutW (my d_gdiGraphicsContext, 0, 0, codesW, str16len ((const char16 *) codesW));
774 				RestoreDC (my d_gdiGraphicsContext, restore);
775 			}
776 			if (lc -> link)
777 				SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
778 			SelectPen (my d_gdiGraphicsContext, GetStockPen (BLACK_PEN)), SelectBrush (my d_gdiGraphicsContext, GetStockBrush (NULL_BRUSH));
779 			return;
780 		#elif quartz
781 			/*
782 				Determine the font family.
783 			*/
784 			const int font = lc -> font.integer_;   // the font of the first character
785 
786 			/*
787 				Determine the style.
788 			*/
789 			const int style = lc -> style;   // the style of the first character
790 
791 			/*
792 				Determine the font-style combination.
793 			*/
794 			CTFontRef ctFont = theScreenFonts [font] [lc -> size] [style];
795 			if (! ctFont)
796 				theScreenFonts [font] [lc -> size] [style] = ctFont =
797 					quartz_getFontRef (font, lc -> size, style);
798 
799 			const char16 *codes16 = Melder_peek32to16 (codes);
800 			#if 1
801 				CFStringRef s = CFStringCreateWithBytes (nullptr,
802 					(const UInt8 *) codes16, str16len (codes16) * 2,
803 					kCFStringEncodingUTF16LE, false);
804 				integer length = CFStringGetLength (s);
805 			#else
806 				NSString *s = [[NSString alloc]   initWithBytes: codes16   length: str16len (codes16) * 2   encoding: NSUTF16LittleEndianStringEncoding];
807 				integer length = [s length];
808 			#endif
809 
810 			CGFloat descent = CTFontGetDescent (ctFont);
811 
812             CFMutableAttributedStringRef string = CFAttributedStringCreateMutable (kCFAllocatorDefault, length);
813             CFAttributedStringReplaceString (string, CFRangeMake (0, 0), (CFStringRef) s);
814             CFRange textRange = CFRangeMake (0, length);
815             CFAttributedStringSetAttribute (string, textRange, kCTFontAttributeName, ctFont);
816 
817 			/*
818 				We don't set kerning explicitly, so that Praat will use standard kerning.
819 			*/
820 
821 			static CTParagraphStyleRef paragraphStyle;
822 			if (! paragraphStyle) {
823 				CTTextAlignment textAlignment = kCTLeftTextAlignment;
824 				CTParagraphStyleSetting paragraphSettings [1] = { { kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), & textAlignment } };
825 				paragraphStyle = CTParagraphStyleCreate (paragraphSettings, 1);
826 				Melder_assert (paragraphStyle != nullptr);
827 			}
828             CFAttributedStringSetAttribute (string, textRange, kCTParagraphStyleAttributeName, paragraphStyle);
829 
830             MelderColour colour = lc -> link ? Melder_BLUE : my colour;
831             CGColorRef color = CGColorCreateGenericRGB (colour.red, colour.green, colour.blue, 1.0);
832 			Melder_assert (color != nullptr);
833             CFAttributedStringSetAttribute (string, textRange, kCTForegroundColorAttributeName, color);
834 
835             /*
836             	Draw.
837 			*/
838 
839             CGContextSetTextMatrix (my d_macGraphicsContext, CGAffineTransformIdentity);   // this could set the "current context" for CoreText
840             CFRelease (color);
841 
842             CGContextSaveGState (my d_macGraphicsContext);
843             CGContextTranslateCTM (my d_macGraphicsContext, xDC, yDC);
844             if (my yIsZeroAtTheTop)
845             	CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
846             CGContextRotateCTM (my d_macGraphicsContext, my textRotation * NUMpi / 180.0);
847 
848 			CTLineRef line = CTLineCreateWithAttributedString (string);
849 			CTLineDraw (line, my d_macGraphicsContext);
850 			CFRelease (line);
851             CGContextRestoreGState (my d_macGraphicsContext);
852 
853             // Clean up
854             CFRelease (string);
855 			CFRelease (s);
856 			//CFRelease (ctFont);
857 			return;
858 		#endif
859 	}
860 }
861 
862 #define MAX_LINK_LENGTH  300
863 
864 static integer bufferSize;
865 static _Graphics_widechar *theWidechar;
866 static char32 *charCodes;
initBuffer(conststring32 txt)867 static int initBuffer (conststring32 txt) {
868 	try {
869 		constexpr integer maximumNumberOfReplacementCharactersPerCharacter = 2;
870 		integer sizeNeeded = maximumNumberOfReplacementCharactersPerCharacter * str32len (txt) + 1;
871 		if (sizeNeeded > bufferSize) {
872 			sizeNeeded += sizeNeeded / 2 + 100;
873 			Melder_free (theWidechar);
874 			Melder_free (charCodes);
875 			theWidechar = Melder_calloc (_Graphics_widechar, sizeNeeded);
876 			charCodes = Melder_calloc (char32, sizeNeeded);
877 			bufferSize = sizeNeeded;
878 		}
879 		return 1;
880 	} catch (MelderError) {
881 		bufferSize = 0;
882 		Melder_flushError ();
883 		return 0;
884 	}
885 }
886 
887 static int numberOfLinks = 0;
888 static Graphics_Link links [100];    // a maximum of 100 links per string
889 
charSizes(Graphics me,_Graphics_widechar string[],bool measureEachCharacterSeparately)890 static void charSizes (Graphics me, _Graphics_widechar string [], bool measureEachCharacterSeparately) {
891 	/*
892 		Ideally, this function should work even in cases where there is no screen.
893 		Example: a Praat script wants to draw a text inside a rectangle
894 		and determines the width of the rectangle by means of the "Text width (mm)" command.
895 		Ideally, this script should run correctly from the command line.
896 		On the Mac, `CTFramesetterSuggestFrameSizeWithConstraints` works correctly from the command line,
897 		but on Linux, `pango_layout_get_extents` does not work if there is no d_cairoGraphicsContext.
898 		(last checked 2020-07-17)
899 	*/
900 	if (my postScript || (cairo && ! my screen)) {   // TODO: use Pango measurements even without Cairo context (if no screen)
901 		for (_Graphics_widechar *character = string; character -> kar > U'\t'; character ++)
902 			charSize (me, character);
903 	} else {
904 	/*
905 		Measure the size of each character.
906 	*/
907 	_Graphics_widechar *character;
908 	#if quartz || cairo
909 		#if cairo
910 			if (! ((GraphicsScreen) me) -> d_cairoGraphicsContext)
911 				return;
912 		#endif
913 		int numberOfDiacritics = 0;
914 		for (_Graphics_widechar *lc = string; lc -> kar > U'\t'; lc ++) {
915 			/*
916 				Determine the font family.
917 			*/
918 			Longchar_Info info = lc -> karInfo;
919 			Melder_assert (info);
920 			int font = chooseFont (me, lc);
921 			lc -> font.string = nullptr;   // this erases font.integer_!
922 
923 			/*
924 				Determine the style.
925 			*/
926 			int style = lc -> style;
927 			Melder_assert (style >= 0 && style <= Graphics_BOLD_ITALIC);
928 
929 			int normalSize = my fontSize * my resolution / 72.0;
930 			int smallSize = (3 * normalSize + 2) / 4;
931 			int size = ( lc -> size < 100 ? smallSize : normalSize );
932 			lc -> size = size;
933 			lc -> baseline *= 0.01 * normalSize;
934 			lc -> code = lc -> kar;
935 			lc -> font.integer_ = font;
936 			if (Longchar_Info_isDiacritic (info))
937 				numberOfDiacritics ++;
938 
939 			#if quartz
940 				/*
941 					Determine and store the font-style combination.
942 				*/
943 				CTFontRef ctFont = theScreenFonts [font] [size] [style];
944 				if (! ctFont)
945 					theScreenFonts [font] [size] [style] = ctFont =
946 						quartz_getFontRef (font, size, style);
947 			#endif
948 		}
949 		int nchars = 0;
950 		for (_Graphics_widechar *lc = string; lc -> kar > U'\t'; lc ++) {
951 			charCodes [nchars ++] = lc -> code;
952 			_Graphics_widechar *next = lc + 1;
953 			lc -> width = 0;
954 			if (measureEachCharacterSeparately ||
955 				next->kar <= U' ' || next->style != lc->style ||
956 				next->baseline != lc->baseline || next->size != lc->size || next->link != lc->link ||
957 				next->font.integer_ != lc->font.integer_ || next->font.string != lc->font.string ||
958 				next->rightToLeft != lc->rightToLeft ||
959 				(my textRotation != 0.0 && my screen && my resolution > 150))
960 			{
961 				charCodes [nchars] = U'\0';
962 				#if cairo
963 					const char *codes8 = Melder_peek32to8 (charCodes);
964 					int length = strlen (codes8);
965 					PangoFontDescription *fontDescription = PangoFontDescription_create (lc -> font.integer_, lc);
966 
967 					/*
968 						Measuring the width of a text with a homogeneous Praat font
969 						should still allow for Pango's font substitution.
970 						Low-level sequences such as `pango_itemize--pango_shape--pango_glyph_string_get_width`
971 						or `pango_itemize--pango_shape--pango_font_map_load_font--pango_glyph_string_extents`
972 						don't accomplish this: they seem to compute the width solely on the
973 						basis of the (perhaps substituted) font of the *first* glyph. By contrast, a PangoLayout
974 						performs font substitution when drawing with `pango_cairo_show_layout_line`,
975 						and also when measuring the width with `pango_layout_get_extents`.
976 						Fortunately, a PangoLayout is 1.5 to 2 times faster than the two low-level methods
977 						(measured 20170527).
978 					*/
979 					Melder_assert (my screen);
980 					PangoLayout *layout = pango_cairo_create_layout (((GraphicsScreen) me) -> d_cairoGraphicsContext);
981 					pango_layout_set_font_description (layout, fontDescription);
982 					pango_layout_set_text (layout, codes8, -1);
983 					PangoRectangle inkRect, logicalRect;
984 					pango_layout_get_extents (layout, & inkRect, & logicalRect);
985 					lc -> width = logicalRect. width / PANGO_SCALE;
986 					Melder_assert (logicalRect.x == 0);
987 					g_object_unref (layout);
988 				#elif quartz
989 					const char16 *codes16 = Melder_peek32to16 (charCodes);
990 					int64 length = str16len (codes16);
991 
992 					NSString *s = [[NSString alloc]
993 						initWithBytes: codes16
994 						length: (NSUInteger) (length * 2)
995 						encoding: NSUTF16LittleEndianStringEncoding   // BUG: should be NSUTF16NativeStringEncoding, except that that doesn't exist
996 						];
997 
998 					CFRange textRange = CFRangeMake (0, (CFIndex) [s length]);
999 
1000 					CFMutableAttributedStringRef cfstring =
1001 						CFAttributedStringCreateMutable (kCFAllocatorDefault, (CFIndex) [s length]);
1002 					CFAttributedStringReplaceString (cfstring, CFRangeMake (0, 0), (CFStringRef) s);
1003 					CFAttributedStringSetAttribute (cfstring, textRange, kCTFontAttributeName, theScreenFonts [lc -> font.integer_] [lc -> size] [lc -> style]);
1004 
1005 					/*
1006 					 * Measure.
1007 					 */
1008 
1009 					// Create a path to render text in
1010 					CGMutablePathRef path = CGPathCreateMutable ();
1011 					NSRect measureRect = NSMakeRect (0, 0, CGFLOAT_MAX, CGFLOAT_MAX);
1012 					CGPathAddRect (path, nullptr, (CGRect) measureRect);
1013 
1014 					CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString ((CFAttributedStringRef) cfstring);
1015 					CFRange fitRange;
1016 					CGSize targetSize = CGSizeMake (lc -> width, CGFLOAT_MAX);
1017 					CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints (framesetter, textRange, nullptr, targetSize, & fitRange);
1018 					CFRelease (framesetter);
1019 					CFRelease (cfstring);
1020 					[s release];
1021 					CFRelease (path);
1022 					//Longchar_Info info = lc -> karInfo;
1023 					//bool isDiacritic = ( info -> ps.times == 0 );
1024 					//lc -> width = ( isDiacritic ? 0.0 : frameSize.width * lc -> size / 100.0 );
1025 					lc -> width = frameSize.width /* * lc -> size / 100.0 */;
1026 					if (Melder_systemVersion >= 101100) {
1027 						/*
1028 						 * If the text ends in a space, CTFramesetterSuggestFrameSizeWithConstraints() ignores the space.
1029 						 * we correct for this.
1030 						 */
1031 						if (codes16 [length - 1] == u' ')
1032 							lc -> width += 25.0 * lc -> size / 100.0;
1033 					}
1034 				#endif
1035 				nchars = 0;
1036 			}
1037 		}
1038 	#else
1039 		for (character = string; character -> kar > U'\t'; character ++)
1040 			charSize (me, character);
1041 	#endif
1042 	}
1043 	/*
1044 	 * Each character has been garnished with information about the character's width.
1045 	 * Make a correction for systems that make slanted characters overlap the character box to their right.
1046 	 * We must do this after the previous loop, because we query the size of the *next* character.
1047 	 *
1048 	 * Keep this in SYNC with psTextWidth.
1049 	 */
1050 	for (_Graphics_widechar *character = string; character -> kar > U'\t'; character ++) {
1051 		if ((character -> style & Graphics_ITALIC) != 0) {
1052 			_Graphics_widechar *nextCharacter = character + 1;
1053 			if (nextCharacter -> kar <= U'\t') {
1054 				character -> width += SCREEN_SLANT_CORRECTION / 72 * my fontSize * my resolution;
1055 			} else if (((nextCharacter -> style & Graphics_ITALIC) == 0 && nextCharacter -> baseline >= character -> baseline)
1056 				|| (character -> baseline == 0 && nextCharacter -> baseline > 0))
1057 			{
1058 				if (nextCharacter -> kar == U'.' || nextCharacter -> kar == U',')
1059 					character -> width += SCREEN_SLANT_CORRECTION / 144 * my fontSize * my resolution;
1060 				else
1061 					character -> width += SCREEN_SLANT_CORRECTION / 72 * my fontSize * my resolution;
1062 			}
1063 		}
1064 	}
1065 }
1066 
1067 /*
1068  * The routine textWidth determines the fractional width of a text, in device coordinates.
1069  */
textWidth(_Graphics_widechar string[])1070 static double textWidth (_Graphics_widechar string []) {
1071 	double width = 0.0;
1072 	for (_Graphics_widechar *character = string; character -> kar > U'\t'; character ++)
1073 		width += character -> width;
1074 	return width;
1075 }
1076 
drawOneCell(Graphics me,int xDC,int yDC,_Graphics_widechar lc[])1077 static void drawOneCell (Graphics me, int xDC, int yDC, _Graphics_widechar lc []) {
1078 	int nchars = 0;
1079 	const double width = textWidth (lc);
1080 	/*
1081 	 * We must continue even if width is zero (for adjusting textY).
1082 	 */
1083 	_Graphics_widechar *plc, *lastlc;
1084 	bool inLink = false;
1085 	double dx, dy;
1086 	switch (my horizontalTextAlignment) {
1087 		case (int) Graphics_LEFT:      dx = 1.0 + (0.1/72.0) * my fontSize * my resolution; break;
1088 		case (int) Graphics_CENTRE:    dx = - width / 2.0; break;
1089 		case (int) Graphics_RIGHT:     dx = ( width != 0.0 ? - width - (0.1/72.0) * my fontSize * my resolution : 0 ); break;   // if width is zero, do not step left
1090 		default:                 dx = 1 + (0.1/72.0) * my fontSize * my resolution; break;
1091 	}
1092 	switch (my verticalTextAlignment) {
1093 		case Graphics_BOTTOM:    dy = (0.4/72.0) * my fontSize * my resolution; break;
1094 		case Graphics_HALF:      dy = (-0.3/72.0) * my fontSize * my resolution; break;
1095 		case Graphics_TOP:       dy = (-1.0/72.0) * my fontSize * my resolution; break;
1096 		case Graphics_BASELINE:  dy = 0.0; break;
1097 		default:                 dy = 0.0; break;
1098 	}
1099 	if (my textRotation != 0.0) {
1100 		double xbegin = dx, x = xbegin, cosa, sina;
1101 		if (my textRotation == 90.0f) {
1102 			cosa = 0.0;
1103 			sina = 1.0;
1104 		} else if (my textRotation == 270.0f) {
1105 			cosa = 0.0;
1106 			sina = -1.0;
1107 		} else {
1108 			const double angle = my textRotation * NUMpi / 180.0;
1109 			cosa = cos (angle);
1110 			sina = sin (angle);
1111 		}
1112 		for (plc = lc; plc -> kar > U'\t'; plc ++) {
1113 			_Graphics_widechar *next = plc + 1;
1114 			charCodes [nchars ++] = plc -> code;   // buffer...
1115 			x += plc -> width;
1116 			/*
1117 			 * We can draw stretches of characters:
1118 			 * they have different styles, baselines, sizes, or fonts,
1119 			 * or if there is a break between them,
1120 			 * or if we cannot rotate multiple characters,
1121 			 * which is the case on bitmap printers.
1122 			 */
1123 			if (next->kar < U' ' || next->style != plc->style ||
1124 				next->baseline != plc->baseline || next->size != plc->size ||
1125 				next->font.integer_ != plc->font.integer_ || next->font.string != plc->font.string ||
1126 				next->rightToLeft != plc->rightToLeft ||
1127 				(my screen && my resolution > 150))
1128 			{
1129 				const double dy2 = dy + plc -> baseline;
1130 				const double xr = cosa * xbegin - sina * dy2;
1131 				const double yr = sina * xbegin + cosa * dy2;
1132 				charCodes [nchars] = U'\0';   // ...and flush
1133 				charDraw (me, xDC + xr, my yIsZeroAtTheTop ? yDC - yr : yDC + yr,
1134 					plc, charCodes, nchars, x - xbegin);
1135 				nchars = 0;
1136 				xbegin = x;
1137 			}
1138 		}
1139 	} else {
1140 		double xbegin = xDC + dx, x = xbegin, y = ( my yIsZeroAtTheTop ? yDC - dy : yDC + dy );   // all mutable
1141 		lastlc = lc;
1142 		if (my wrapWidth != 0.0) {
1143 			/*
1144 			 * Replace some spaces with new-line symbols.
1145 			 */
1146 			int xmax = xDC + my wrapWidth * my scaleX;
1147 			for (plc = lc; plc -> kar >= U' '; plc ++) {
1148 				x += plc -> width;
1149 				if (x > xmax) {   // wrap (if wrapWidth is too small, each word will be on a separate line)
1150 					while (plc >= lastlc) {
1151 						if (plc -> kar == U' ' && ! plc -> link)   // keep links contiguous
1152 							break;
1153 						plc --;
1154 					}
1155 					if (plc <= lastlc)
1156 						break;   // hopeless situation: no spaces; get over it
1157 					lastlc = plc;
1158 					plc -> kar = U'\n';   // replace space with newline
1159 					#if quartz || cairo
1160 						if (my screen) {
1161 							/*
1162 								This part is needed when using the non-`charSize()` variant of `charSizes()`,
1163 								because otherwise you'll see extra spaces
1164 								before the first font switch on each non-initial line.
1165 							*/
1166 							_Graphics_widechar *next = plc + 1;
1167 							if (next->style != plc->style ||
1168 								next->baseline != plc->baseline || next->size != plc->size || next->link != plc->link ||
1169 								next->font.integer_ != plc->font.integer_ || next->font.string != plc->font.string ||
1170 								next->rightToLeft != plc->rightToLeft)
1171 							{
1172 								// nothing
1173 							} else {
1174 								next -> width -= 0.25 * my fontSize * my resolution / 72.0;   // subtract the width of one space
1175 							}
1176 						}
1177 					#endif
1178 					x = xDC + dx + my secondIndent * my scaleX;
1179 				}
1180 			}
1181 			xbegin = x = xDC + dx;   // re-initialize for second pass
1182 		}
1183 		for (plc = lc; plc -> kar > U'\t'; plc ++) {
1184 			_Graphics_widechar *next = plc + 1;
1185 			if (plc -> link) {
1186 				if (! inLink) {
1187 					const double descent = ( my yIsZeroAtTheTop ? -(0.3/72) : (0.3/72) ) * my fontSize * my resolution;
1188 					links [++ numberOfLinks]. x1 = x;
1189 					links [numberOfLinks]. y1 = y - descent;
1190 					links [numberOfLinks]. y2 = y + 3 * descent;
1191 					inLink = true;
1192 				}
1193 			} else if (inLink) {
1194 				links [numberOfLinks]. x2 = x;
1195 				inLink = false;
1196 			}
1197 			if (plc -> kar == U'\n') {
1198 				xbegin = x = xDC + dx + my secondIndent * my scaleX;
1199 				y = ( my yIsZeroAtTheTop ? y + (1.2/72) * my fontSize * my resolution : y - (1.2/72) * my fontSize * my resolution );
1200 			} else {
1201 				charCodes [nchars ++] = plc -> code;   // buffer...
1202 				x += plc -> width;
1203 				if (next->kar < U' ' || next->style != plc->style ||
1204 					next->baseline != plc->baseline || next->size != plc->size || next->link != plc->link ||
1205 					next->font.integer_ != plc->font.integer_ || next->font.string != plc->font.string ||
1206 					next->rightToLeft != plc->rightToLeft)
1207 				{
1208 					charCodes [nchars] = U'\0';   // ...and flush
1209 					charDraw (me, xbegin, my yIsZeroAtTheTop ? y - plc -> baseline : y + plc -> baseline,
1210 						plc, charCodes, nchars, x - xbegin);
1211 					nchars = 0;
1212 					xbegin = x;
1213 				}
1214 			}
1215 		}
1216 		if (inLink) {
1217 			links [numberOfLinks]. x2 = x;
1218 			inLink = false;
1219 		}
1220 		my textX = (x - my deltaX) / my scaleX;
1221 		my textY = (( my yIsZeroAtTheTop ? y + dy : y - dy ) - my deltaY) / my scaleY;
1222 	}
1223 }
1224 
1225 static struct { double width; kGraphics_horizontalAlignment alignment; } tabs [1 + 20] = { { 0, Graphics_CENTRE },
1226 	{ 1, Graphics_CENTRE }, { 1, Graphics_CENTRE }, { 1, Graphics_CENTRE }, { 1, Graphics_CENTRE },
1227 	{ 1, Graphics_CENTRE }, { 1, Graphics_CENTRE }, { 1, Graphics_CENTRE }, { 1, Graphics_CENTRE } };
1228 
1229 /*
1230  * The routine 'drawCells' handles table and layout.
1231  */
drawCells(Graphics me,double xWC,double yWC,_Graphics_widechar lc[])1232 static void drawCells (Graphics me, double xWC, double yWC, _Graphics_widechar lc []) {
1233 	_Graphics_widechar *plc;
1234 	int itab = 0, saveTextAlignment = my horizontalTextAlignment;
1235 	double saveWrapWidth = my wrapWidth;
1236 	numberOfLinks = 0;
1237 	for (plc = lc; /* No stop condition. */ ; plc ++) {
1238 		charSizes (me, plc, false);
1239 		drawOneCell (me, xWC * my scaleX + my deltaX, yWC * my scaleY + my deltaY, plc);
1240 		while (plc -> kar != U'\0' && plc -> kar != U'\t') plc ++;   // find end of cell
1241 		if (plc -> kar == U'\0')   // end of text?
1242 			break;
1243 		if (plc -> kar == U'\t') {   // go to next cell
1244 			xWC += ( tabs [itab]. alignment == Graphics_LEFT ? tabs [itab]. width :
1245 				tabs [itab]. alignment == Graphics_CENTRE ? 0.5 * tabs [itab]. width : 0 ) * my fontSize / 12.0;
1246 			itab ++;
1247 			xWC += ( tabs [itab]. alignment == Graphics_LEFT ? 0 :
1248 				tabs [itab]. alignment == Graphics_CENTRE ? 0.5 * tabs [itab]. width : tabs [itab]. width ) * my fontSize / 12.0;
1249 			my horizontalTextAlignment = (int) tabs [itab]. alignment;
1250 			my wrapWidth = tabs [itab]. width * my fontSize / 12.0;
1251 		}
1252 	}
1253 	my horizontalTextAlignment = saveTextAlignment;
1254 	my wrapWidth = saveWrapWidth;
1255 }
1256 
parseTextIntoCellsLinesRuns(Graphics me,conststring32 txt,_Graphics_widechar a_widechar[])1257 static void parseTextIntoCellsLinesRuns (Graphics me, conststring32 txt /* cattable */, _Graphics_widechar a_widechar []) {
1258 	char32 kar;
1259 	const char32 *in = & txt [0];
1260 	int nquote = 0;
1261 	_Graphics_widechar *out = & a_widechar [0];
1262 	bool charSuperscript = false, charSubscript = false, charItalic = false, charBold = false;
1263 	bool wordItalic = false, wordBold = false, wordCode = false, wordLink = false;
1264 	bool globalSuperscript = false, globalSubscript = false, globalItalic = false, globalBold = false, globalCode = false, globalLink = false;
1265 	bool globalSmall = 0;
1266 	numberOfLinks = 0;
1267 	while ((kar = *in++) != U'\0') {
1268 		if (kar == U'^' && my circumflexIsSuperscript) {
1269 			if (globalSuperscript) globalSuperscript = 0;
1270 			else if (in [0] == U'^') { globalSuperscript = 1; in ++; }
1271 			else charSuperscript = 1;
1272 			wordItalic = wordBold = wordCode = false;
1273 			continue;
1274 		} else if (kar == U'_' && my underscoreIsSubscript) {
1275 			if (globalSubscript) { globalSubscript = false; wordItalic = wordBold = wordCode = false; continue; }
1276 			else if (in [0] == U'_') { globalSubscript = true; in ++; wordItalic = wordBold = wordCode = false; continue; }
1277 			else if (! my dollarSignIsCode) { charSubscript = true; wordItalic = wordBold = wordCode = false; continue; }   // not in manuals
1278 			else
1279 				;   // a normal underscore in manuals
1280 		} else if (kar == U'%' && my percentSignIsItalic) {
1281 			if (globalItalic) globalItalic = false;
1282 			else if (in [0] == U'%') { globalItalic = true; in ++; }
1283 			else if (my dollarSignIsCode) wordItalic = true;   // in manuals
1284 			else charItalic = true;
1285 			continue;
1286 		} else if (kar == U'#' && my numberSignIsBold) {
1287 			if (globalBold) globalBold = false;
1288 			else if (in [0] == U'#') { globalBold = true; in ++; }
1289 			else if (my dollarSignIsCode) wordBold = true;   // in manuals
1290 			else charBold = true;
1291 			continue;
1292 		} else if (kar == U'$' && my dollarSignIsCode) {
1293 			if (globalCode) globalCode = false;
1294 			else if (in [0] == U'$') { globalCode = true; in ++; }
1295 			else wordCode = true;
1296 			continue;
1297 		} else if (kar == U'@' && my atSignIsLink   // recognize links
1298 		           && my textRotation == 0.0)   // no links allowed in rotated text, because links are identified by 2-point rectangles
1299 		{
1300 			char32 *to, *max;
1301 			/*
1302 			 * We will distinguish:
1303 			 * 1. The link text: the text shown to the user, drawn in blue.
1304 			 * 2. The link info: the information saved in the Graphics object when the user clicks the link;
1305 			 *    this may be a page title in a manual or any other information.
1306 			 * The link info is equal to the link text in the following cases:
1307 			 * 1. A single-word link: "this is a @Link that consists of one word".
1308 			 * 2. Longer links without '|' in them: "@@Link with spaces@".
1309 			 * The link info is unequal to the link text in the following case:
1310 			 * 3. Longer links with '|' in them: "@@Page linked to|Text shown in blue@"
1311 			 */
1312 			if (globalLink) {
1313 				/*
1314 				 * Detected the third '@' in strings like "@@Link with spaces@".
1315 				 * This closes the link text (which will be shown in blue).
1316 				 */
1317 				globalLink = false;   // close the drawn link text (the normal colour will take over)
1318 				continue;   // the '@' must not be drawn
1319 			} else if (in [0] == U'@') {
1320 				/*
1321 				 * Detected the second '@' in strings like "@@Link with spaces@".
1322 				 * A format like "@@Page linked to|Text shown in blue@" is permitted.
1323 				 * First step: collect the page text (the link information);
1324 				 * it is everything between "@@" and "|" or "@" or end of string.
1325 				 */
1326 				const char32 *from = in + 1;   // start with first character after "@@"
1327 				if (! links [++ numberOfLinks]. name)   // make room for saving link info
1328 					links [numberOfLinks]. name = Melder_calloc_f (char32, MAX_LINK_LENGTH + 1);
1329 				to = links [numberOfLinks]. name, max = to + MAX_LINK_LENGTH;
1330 				while (*from && *from != U'@' && *from != U'|' && to < max)   // until end-of-string or '@' or '|'...
1331 					* to ++ = * from ++;   // ... copy one character
1332 				*to = U'\0';   // close saved link info
1333 				/*
1334 				 * Second step: collect the link text that is to be drawn.
1335 				 * Its characters will be collected during the normal cycles of the loop.
1336 				 * If the link info is equal to the link text, no action is needed.
1337 				 * If, on the other hand, there is a separate link info, this will have to be skipped.
1338 				 */
1339 				if (*from == U'|')
1340 					in += to - links [numberOfLinks]. name + 1;   // skip link info + '|'
1341 				/*
1342 				 * We are entering the link-text-collection mode.
1343 				 */
1344 				globalLink = true;
1345 				/*
1346 				 * Both '@' must be skipped and must not be drawn.
1347 				 */
1348 				in ++;   // skip second '@'
1349 				continue;   // do not draw
1350 			} else {
1351 				/*
1352 				 * Detected a single-word link, like in "this is a @Link that consists of one word".
1353 				 * First step: collect the page text: letters, digits, and underscores.
1354 				 */
1355 				const char32 *from = in;   // start with first character after "@"
1356 				if (! links [++ numberOfLinks]. name)   // make room for saving link info
1357 					links [numberOfLinks]. name = Melder_calloc_f (char32, MAX_LINK_LENGTH + 1);
1358 				to = links [numberOfLinks]. name;
1359 				max = to + MAX_LINK_LENGTH;
1360 				while (*from && (Melder_isWordCharacter (*from) || *from == U'_') && to < max)   // until end-of-word...
1361 					*to ++ = *from++;   // ... copy one character
1362 				*to = U'\0';   // close saved link info
1363 				/*
1364 				 * Second step: collect the link text that is to be drawn.
1365 				 * Its characters will be collected during the normal cycles of the loop.
1366 				 * The link info is equal to the link text, so no skipping is needed.
1367 				 */
1368 				wordLink = true;   // enter the single-word link-text-collection mode
1369 			}
1370 			continue;
1371 		} else if (kar == U'\\') {
1372 			/*
1373 			 * Detected backslash sequence: backslash + kar1 + kar2...
1374 			 */
1375 			char32 kar1, kar2;
1376 			/*
1377 			 * ... except if kar1 or kar2 is null: in that case, draw the backslash.
1378 			 */
1379 			if (! (kar1 = in [0]) || ! (kar2 = in [1])) {
1380 				;   // normal backslash symbol
1381 			/*
1382 			 * Catch "\s{", which means: small characters until corresponding '}'.
1383 			 */
1384 			} else if (kar2 == U'{') {
1385 				if (kar1 == U's')
1386 					globalSmall = true;
1387 				in += 2;
1388 				continue;
1389 			/*
1390 			 * Default action: translate the backslash sequence into the long character 'kar1,kar2'.
1391 			 */
1392 			} else {
1393 				kar = Longchar_getInfo (kar1, kar2) -> unicode;
1394 				in += 2;
1395 			}
1396 		} else if (kar == U'\"') {
1397 			if (! (my font == kGraphics_font::COURIER || my fontStyle == Graphics_CODE || wordCode || globalCode))
1398 				kar = ++nquote & 1 ? UNICODE_LEFT_DOUBLE_QUOTATION_MARK : UNICODE_RIGHT_DOUBLE_QUOTATION_MARK;
1399 		} else if (kar == U'\'') {
1400 			kar = UNICODE_RIGHT_SINGLE_QUOTATION_MARK;
1401 		} else if (kar == U'`') {
1402 			kar = UNICODE_LEFT_SINGLE_QUOTATION_MARK;
1403 		} else if (kar >= 32 && kar <= 126) {
1404 			if (kar == U'f') {
1405 				if (in [0] == U'i' && HAS_FI_AND_FL_LIGATURES && ! (my font == kGraphics_font::COURIER || my fontStyle == Graphics_CODE || wordCode || globalCode)) {
1406 					kar = UNICODE_LATIN_SMALL_LIGATURE_FI;
1407 					in ++;
1408 				} else if (in [0] == U'l' && HAS_FI_AND_FL_LIGATURES && ! (my font == kGraphics_font::COURIER || my fontStyle == Graphics_CODE || wordCode || globalCode)) {
1409 					kar = UNICODE_LATIN_SMALL_LIGATURE_FL;
1410 					in ++;
1411 				}
1412 			} else if (kar == U'}') {
1413 				if (globalSmall) { globalSmall = 0; continue; }
1414 			}
1415 		} else if (kar == U'\t') {
1416 			out -> kar = U'\t';
1417 			out -> rightToLeft = false;
1418 			wordItalic = wordBold = wordCode = wordLink = false;
1419 			globalSubscript = globalSuperscript = globalItalic = globalBold = globalCode = globalLink = globalSmall = false;
1420 			charItalic = charBold = charSuperscript = charSubscript = false;
1421 			out ++;
1422 			continue;   // do not draw
1423 		} else if (kar == U'\n') {
1424 			kar = U' ';
1425 		}
1426 		if (wordItalic | wordBold | wordCode | wordLink) {
1427 			if (! Melder_isWordCharacter (kar) && kar != U'_')
1428 				wordItalic = wordBold = wordCode = wordLink = false;
1429 		}
1430 		out -> style =
1431 			(wordLink | globalLink) && my fontStyle != Graphics_CODE ? Graphics_BOLD :
1432 			((my fontStyle & Graphics_ITALIC) | charItalic | wordItalic | globalItalic ? Graphics_ITALIC : 0) +
1433 			((my fontStyle & Graphics_BOLD) | charBold | wordBold | globalBold ? Graphics_BOLD : 0);
1434 		out -> font.string = nullptr;
1435 		out -> font.integer_ = my fontStyle == Graphics_CODE || wordCode || globalCode ||
1436 			(kar == U'/' || kar == U'|') && my font != kGraphics_font::PALATINO ? (int) kGraphics_font::COURIER : (int) my font;
1437 		out -> link = wordLink | globalLink;
1438 		out -> baseline = charSuperscript | globalSuperscript ? 34 : charSubscript | globalSubscript ? -25 : 0;
1439 		out -> size = globalSmall || out -> baseline != 0 ? 80 : 100;
1440 		if (kar == U'/' && my font != kGraphics_font::PALATINO) {
1441 			out -> baseline -= out -> size / 12;
1442 			out -> size += out -> size / 10;
1443 			if (my screen)
1444 				out -> font.integer_ = (int) kGraphics_font::PALATINO;
1445 		}
1446 		out -> code = U'?';   // does this have any meaning?
1447 		Melder_assert (kar != U'\0');
1448 		if (my postScript) {
1449 			if (kar == UNICODE_LATIN_SMALL_LETTER_TS_DIGRAPH) {
1450 				kar = U't';
1451 				out -> kar = kar;
1452 				out -> karInfo = Longchar_getInfoFromNative (kar);
1453 				Melder_assert (out -> karInfo);
1454 				out -> rightToLeft = false;
1455 				kar = U's';
1456 				out ++;
1457 				* out = out [-1];
1458 			} else if (kar == UNICODE_LATIN_SMALL_LETTER_TESH_DIGRAPH) {
1459 				kar = U't';
1460 				out -> kar = kar;
1461 				out -> karInfo = Longchar_getInfoFromNative (kar);
1462 				Melder_assert (out -> karInfo);
1463 				out -> rightToLeft = false;
1464 				kar = UNICODE_LATIN_SMALL_LETTER_ESH;
1465 				out ++;
1466 				* out = out [-1];
1467 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_H) {
1468 				kar = U'h';
1469 				out -> baseline = 34;
1470 				out -> size = 80;
1471 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_H_WITH_HOOK) {
1472 				kar = UNICODE_LATIN_SMALL_LETTER_H_WITH_HOOK;
1473 				out -> baseline = 34;
1474 				out -> size = 80;
1475 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_GAMMA) {
1476 				kar = UNICODE_LATIN_SMALL_LETTER_GAMMA;
1477 				out -> baseline = 34;
1478 				out -> size = 80;
1479 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_W) {
1480 				kar = U'w';
1481 				out -> baseline = 34;
1482 				out -> size = 80;
1483 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_TURNED_H) {
1484 				kar = UNICODE_LATIN_SMALL_LETTER_TURNED_H;
1485 				out -> baseline = 34;
1486 				out -> size = 80;
1487 			} else if (kar == UNICODE_MODIFIER_LETTER_GLOTTAL_STOP) {
1488 				kar = UNICODE_LATIN_LETTER_GLOTTAL_STOP;
1489 				out -> baseline = 34;
1490 				out -> size = 80;
1491 			} else if (kar == UNICODE_MODIFIER_LETTER_REVERSED_GLOTTAL_STOP) {
1492 				kar = UNICODE_LATIN_LETTER_PHARYNGEAL_VOICED_FRICATIVE;
1493 				out -> baseline = 34;
1494 				out -> size = 80;
1495 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_L) {
1496 				kar = U'l';
1497 				out -> baseline = 34;
1498 				out -> size = 80;
1499 			} else if (kar == UNICODE_SUPERSCRIPT_LATIN_SMALL_LETTER_N) {
1500 				kar = U'n';
1501 				out -> baseline = 34;
1502 				out -> size = 80;
1503 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_M) {
1504 				kar = U'm';
1505 				out -> baseline = 34;
1506 				out -> size = 80;
1507 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_ENG) {
1508 				kar = UNICODE_LATIN_SMALL_LETTER_ENG;
1509 				out -> baseline = 34;
1510 				out -> size = 80;
1511 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_S) {
1512 				kar = U's';
1513 				out -> baseline = 34;
1514 				out -> size = 80;
1515 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_X) {
1516 				kar = U'x';
1517 				out -> baseline = 34;
1518 				out -> size = 80;
1519 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_F) {
1520 				kar = U'f';
1521 				out -> baseline = 34;
1522 				out -> size = 80;
1523 			} else if (kar == UNICODE_MODIFIER_LETTER_SMALL_Y) {
1524 				kar = U'y';
1525 				out -> baseline = 34;
1526 				out -> size = 80;
1527 			}
1528 		}
1529 		out -> kar = kar;
1530 		out -> karInfo = Longchar_getInfoFromNative (kar);
1531 		Melder_assert (out -> karInfo);
1532 		out -> rightToLeft =
1533 			(kar >= 0x0590 && kar <= 0x06FF) ||
1534 			(kar >= 0xFE70 && kar <= 0xFEFF) ||
1535 			(kar >= 0xFB1E && kar <= 0xFDFF);
1536 		charItalic = charBold = charSuperscript = charSubscript = false;
1537 		out ++;
1538 	}
1539 	out -> kar = U'\0';   // end of text
1540 	out -> karInfo = Longchar_getInfoFromNative (kar);
1541 	Melder_assert (out -> karInfo);
1542 	out -> rightToLeft = false;
1543 }
1544 
Graphics_textWidth(Graphics me,conststring32 txt)1545 double Graphics_textWidth (Graphics me, conststring32 txt) {
1546 	if (! initBuffer (txt))
1547 		return 0.0;
1548 	//Melder_casual (U"Graphics_textWidth: workstation viewport ", my d_x1DC, U" ", my d_x2DC, U" ", my d_y1DC, U" ", my d_y2DC);
1549 	//Melder_casual (U"Graphics_textWidth: workstation window ", my d_x1wNDC, U" ", my d_x2wNDC, U" ", my d_y1wNDC, U" ", my d_y2wNDC);
1550 	//Melder_casual (U"Graphics_textWidth: viewport ", my d_x1NDC, U" ", my d_x2NDC, U" ", my d_y1NDC, U" ", my d_y2NDC);
1551 	//Melder_casual (U"Graphics_textWidth: window ", my d_x1WC, U" ", my d_x2WC, U" ", my d_y1WC, U" ", my d_y2WC);
1552 	#if cairo
1553 		Melder_assert (Thing_isa (me, classGraphicsScreen));
1554 		cairo_t *oldCairoGraphicsContext = ((GraphicsScreen) me) -> d_cairoGraphicsContext;
1555 		cairo_surface_t *cairoSurface;
1556 		if (! oldCairoGraphicsContext)
1557 			#ifndef NO_GUI
1558 			if (((GraphicsScreen) me) -> d_window) {
1559 				//Melder_casual (U"Graphics_textWidth: creating a graphics context in an existing widget.");
1560 				((GraphicsScreen) me) -> d_cairoGraphicsContext = gdk_cairo_create (((GraphicsScreen) me) -> d_window);
1561 			} else
1562 			#endif
1563 			{
1564 				//Melder_casual (U"Graphics_textWidth: creating a graphics context outside any existing widget.");
1565 				cairoSurface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 1/*my d_x2DC - my d_x1DC*/, 1/*my d_y2DC - my d_y1DC*/);
1566 				((GraphicsScreen) me) -> d_cairoGraphicsContext = cairo_create (cairoSurface);
1567 			}
1568 	#endif
1569 	parseTextIntoCellsLinesRuns (me, txt, theWidechar);
1570 	charSizes (me, theWidechar, false);
1571 	const double width = textWidth (theWidechar);
1572 	//Melder_casual (U"Graphics_textWidth: width ", width, U", scale ", my scaleX);
1573 	#if cairo
1574 		if (! oldCairoGraphicsContext) {
1575 			#ifndef NO_GUI
1576 			if (((GraphicsScreen) me) -> d_window) {
1577 				cairo_destroy (((GraphicsScreen) me) -> d_cairoGraphicsContext);
1578 			} else
1579 			#endif
1580 			{
1581 				cairo_destroy (((GraphicsScreen) me) -> d_cairoGraphicsContext);
1582 				cairo_surface_destroy (cairoSurface);
1583 			}
1584 			((GraphicsScreen) me) -> d_cairoGraphicsContext = nullptr;
1585 		}
1586 	#endif
1587 	return width / my scaleX;
1588 }
1589 
Graphics_textRect(Graphics me,double x1,double x2,double y1,double y2,conststring32 txt)1590 void Graphics_textRect (Graphics me, double x1, double x2, double y1, double y2, conststring32 txt) {
1591 	_Graphics_widechar *plc, *startOfLine;
1592 	double width = 0.0, lineHeight = (1.1 / 72) * my fontSize * my resolution;
1593 	const integer x1DC = x1 * my scaleX + my deltaX + 2, x2DC = x2 * my scaleX + my deltaX - 2;
1594 	const integer y1DC = y1 * my scaleY + my deltaY, y2DC = y2 * my scaleY + my deltaY;
1595 	const int availableHeight = ( my yIsZeroAtTheTop ? y1DC - y2DC : y2DC - y1DC ), availableWidth = x2DC - x1DC;
1596 	const int linesAvailable = Melder_clippedLeft (1, int (availableHeight / lineHeight));
1597 	if (availableWidth <= 0)
1598 		return;
1599 	if (! initBuffer (txt))
1600 		return;
1601 	parseTextIntoCellsLinesRuns (me, txt, theWidechar);
1602 	charSizes (me, theWidechar, true);
1603 	int linesNeeded = 1;
1604 	for (plc = theWidechar; plc -> kar > U'\t'; plc ++) {
1605 		width += plc -> width;
1606 		if (width > availableWidth) {
1607 			if (++ linesNeeded > linesAvailable)
1608 				break;
1609 			width = 0.0;
1610 		}
1611 	}
1612 	const int lines = Melder_clippedRight (linesNeeded, linesAvailable);
1613 	startOfLine = theWidechar;
1614 	for (int iline = 1; iline <= lines; iline ++) {
1615 		width = 0.0;
1616 		for (plc = startOfLine; plc -> kar > U'\t'; plc ++) {
1617 			bool flush = false;
1618 			width += plc -> width;
1619 			if (width > availableWidth)
1620 				flush = true;
1621 			/*
1622 				Trick for incorporating end-of-text.
1623 			*/
1624 			if (! flush && plc [1]. kar <= U'\t') {
1625 				Melder_assert (iline == lines);
1626 				plc ++;   // brr
1627 				flush = true;
1628 			}
1629 			if (flush) {
1630 				const char32 saveKar = plc -> kar;
1631 				const int direction = ( my yIsZeroAtTheTop ? -1 : 1 );
1632 				const int x = (
1633 					my horizontalTextAlignment == (int) Graphics_LEFT ?
1634 						x1DC
1635 					: my horizontalTextAlignment == (int) Graphics_RIGHT ?
1636 						x2DC
1637 					:
1638 						0.5 * (x1 + x2) * my scaleX + my deltaX
1639 				);
1640 				const int y = (
1641 					my verticalTextAlignment == Graphics_BOTTOM ?
1642 						y1DC + direction * (lines - iline) * lineHeight
1643 					: my verticalTextAlignment == Graphics_TOP ?
1644 						y2DC - direction * (iline - 1) * lineHeight
1645 					:
1646 						0.5 * (y1 + y2) * my scaleY + my deltaY + 0.5 * direction * (lines - iline*2 + 1) * lineHeight
1647 				);
1648 				plc -> kar = U'\0';
1649 				drawOneCell (me, x, y, startOfLine);
1650 				plc -> kar = saveKar;
1651 				startOfLine = plc;
1652 				break;
1653 			}
1654 		}
1655 	}
1656 }
1657 
Graphics_text(Graphics me,double xWC,double yWC,conststring32 txt)1658 void Graphics_text (Graphics me, double xWC, double yWC, conststring32 txt) {
1659 	if (my recording) {
1660 		const conststring8 txt_utf8 = Melder_peek32to8 (txt);
1661 		const int length = strlen (txt_utf8) / sizeof (double) + 1;
1662 		op (TEXT, 3 + length); put (xWC); put (yWC); sput (txt_utf8, length)
1663 	} else {
1664 		if (my wrapWidth == 0.0 && str32chr (txt, U'\n') && my textRotation == 0.0) {
1665 			const double lineSpacingWC = (1.2/72.0) * my fontSize * my resolution / fabs (my scaleY);
1666 			integer numberOfLines = 1;
1667 			for (const char32 *p = & txt [0]; *p != U'\0'; p ++) {
1668 				if (*p == U'\n')
1669 					numberOfLines ++;
1670 			}
1671 			yWC += (
1672 				my verticalTextAlignment == Graphics_TOP ?
1673 					0.0
1674 				: my verticalTextAlignment == Graphics_HALF ?
1675 					0.5 * (numberOfLines - 1) * lineSpacingWC
1676 				:
1677 					(numberOfLines - 1) * lineSpacingWC
1678 			);
1679 			autostring32 linesToDraw = Melder_dup_f (txt);
1680 			const char32 *p = & linesToDraw [0];
1681 			for (;;) {
1682 				char32 * const newline = str32chr (p, U'\n');
1683 				if (newline)
1684 					*newline = U'\0';
1685 				Graphics_text (me, xWC, yWC, p);
1686 				yWC -= lineSpacingWC;
1687 				if (newline)
1688 					p = newline + 1;
1689 				else
1690 					break;
1691 			}
1692 			return;
1693 		}
1694 		if (! initBuffer (txt))
1695 			return;
1696 		parseTextIntoCellsLinesRuns (me, txt, theWidechar);
1697 		drawCells (me, xWC, yWC, theWidechar);
1698 	}
1699 }
1700 
Graphics_inqTextX(Graphics me)1701 double Graphics_inqTextX (Graphics me) { return my textX; }
Graphics_inqTextY(Graphics me)1702 double Graphics_inqTextY (Graphics me) { return my textY; }
1703 
Graphics_getLinks(Graphics_Link ** plinks)1704 int Graphics_getLinks (Graphics_Link **plinks) { *plinks = & links [0]; return numberOfLinks; }
1705 
psTextWidth(_Graphics_widechar string[],bool useSilipaPS)1706 static double psTextWidth (_Graphics_widechar string [], bool useSilipaPS) {
1707 	/*
1708 		The following has to be kept IN SYNC with GraphicsPostscript::charSize.
1709 	*/
1710 	double textWidth = 0.0;
1711 	for (_Graphics_widechar *character = & string [0]; character -> kar > U'\t'; character ++) {
1712 		Longchar_Info info = character -> karInfo;
1713 		const int font = (
1714 			info -> alphabet == Longchar_SYMBOL ?
1715 				kGraphics_font_SYMBOL
1716 			: info -> alphabet == Longchar_PHONETIC ?
1717 				kGraphics_font_IPATIMES
1718 			: info -> alphabet == Longchar_DINGBATS ?
1719 				kGraphics_font_DINGBATS
1720 			:
1721 				character -> font.integer_
1722 		);
1723 		const int style = (
1724 			character -> style == Graphics_ITALIC ?
1725 				Graphics_ITALIC
1726 			: character -> style == Graphics_BOLD || character -> link ?
1727 				Graphics_BOLD
1728 			: character -> style == Graphics_BOLD_ITALIC ?
1729 				Graphics_BOLD_ITALIC
1730 			:
1731 				0
1732 		);
1733 		const double size = character -> size * 0.01;
1734 		double charWidth = 600;   // Courier
1735 		if (font == (int) kGraphics_font::COURIER) {
1736 			charWidth = 600;
1737 		} else if (style == 0) {
1738 			if (font == (int) kGraphics_font::TIMES)
1739 				charWidth = info -> ps.times;
1740 			else if (font == (int) kGraphics_font::HELVETICA)
1741 				charWidth = info -> ps.helvetica;
1742 			else if (font == (int) kGraphics_font::PALATINO)
1743 				charWidth = info -> ps.palatino;
1744 			else if (font == kGraphics_font_IPATIMES && useSilipaPS)
1745 				charWidth = info -> ps.timesItalic;
1746 			else
1747 				charWidth = info -> ps.times;   // Symbol, IPA
1748 		} else if (style == Graphics_BOLD) {
1749 			if (font == (int) kGraphics_font::TIMES)
1750 				charWidth = info -> ps.timesBold;
1751 			else if (font == (int) kGraphics_font::HELVETICA)
1752 				charWidth = info -> ps.helveticaBold;
1753 			else if (font == (int) kGraphics_font::PALATINO)
1754 				charWidth = info -> ps.palatinoBold;
1755 			else if (font == kGraphics_font_IPATIMES && useSilipaPS)
1756 				charWidth = info -> ps.timesBoldItalic;
1757 			else
1758 				charWidth = info -> ps.times;
1759 		} else if (style == Graphics_ITALIC) {
1760 			if (font == (int) kGraphics_font::TIMES)
1761 				charWidth = info -> ps.timesItalic;
1762 			else if (font == (int) kGraphics_font::HELVETICA)
1763 				charWidth = info -> ps.helvetica;
1764 			else if (font == (int) kGraphics_font::PALATINO)
1765 				charWidth = info -> ps.palatinoItalic;
1766 			else if (font == kGraphics_font_IPATIMES && useSilipaPS)
1767 				charWidth = info -> ps.timesItalic;
1768 			else
1769 				charWidth = info -> ps.times;
1770 		} else if (style == Graphics_BOLD_ITALIC) {
1771 			if (font == (int) kGraphics_font::TIMES)
1772 				charWidth = info -> ps.timesBoldItalic;
1773 			else if (font == (int) kGraphics_font::HELVETICA)
1774 				charWidth = info -> ps.helveticaBold;
1775 			else if (font == (int) kGraphics_font::PALATINO)
1776 				charWidth = info -> ps.palatinoBoldItalic;
1777 			else if (font == kGraphics_font_IPATIMES && useSilipaPS)
1778 				charWidth = info -> ps.timesBoldItalic;
1779 			else
1780 				charWidth = info -> ps.times;
1781 		}
1782 		charWidth *= size / 1000.0;
1783 		textWidth += charWidth;
1784 	}
1785 	/*
1786 	 * The following has to be kept IN SYNC with charSizes ().
1787 	 */
1788 	for (_Graphics_widechar *character = & string [0]; character -> kar > U'\t'; character ++) {
1789 		if ((character -> style & Graphics_ITALIC) != 0) {
1790 			_Graphics_widechar *nextCharacter = character + 1;
1791 			if (nextCharacter -> kar <= U'\t') {
1792 				textWidth += POSTSCRIPT_SLANT_CORRECTION;
1793 			} else if (((nextCharacter -> style & Graphics_ITALIC) == 0 && nextCharacter -> baseline >= character -> baseline)
1794 				|| (character -> baseline == 0 && nextCharacter -> baseline > 0))
1795 			{
1796 				if (nextCharacter -> kar == U'.' || nextCharacter -> kar == U',')
1797 					textWidth += 0.5 * POSTSCRIPT_SLANT_CORRECTION;
1798 				else
1799 					textWidth += POSTSCRIPT_SLANT_CORRECTION;
1800 			}
1801 		}
1802 	}
1803 	return textWidth;
1804 }
1805 
Graphics_textWidth_ps_mm(Graphics me,conststring32 txt,bool useSilipaPS)1806 double Graphics_textWidth_ps_mm (Graphics me, conststring32 txt, bool useSilipaPS) {
1807 	if (! initBuffer (txt))
1808 		return 0.0;
1809 	parseTextIntoCellsLinesRuns (me, txt, theWidechar);
1810 	return psTextWidth (theWidechar, useSilipaPS) * (double) my fontSize * (25.4 / 72.0);
1811 }
1812 
Graphics_textWidth_ps(Graphics me,conststring32 txt,bool useSilipaPS)1813 double Graphics_textWidth_ps (Graphics me, conststring32 txt, bool useSilipaPS) {
1814 	return Graphics_dxMMtoWC (me, Graphics_textWidth_ps_mm (me, txt, useSilipaPS));
1815 }
1816 
1817 #if quartz
_GraphicsMac_tryToInitializeFonts()1818 	bool _GraphicsMac_tryToInitializeFonts () {
1819 		static bool inited = false;
1820 		if (inited)
1821 			return true;
1822 		NSArray *fontNames = [[NSFontManager sharedFontManager] availableFontFamilies];
1823 		hasTimes = [fontNames containsObject: @"Times"];
1824 		if (! hasTimes)
1825 			hasTimes = [fontNames containsObject: @"Times New Roman"];
1826 		hasHelvetica = [fontNames containsObject: @"Helvetica"];
1827 		if (! hasHelvetica)
1828 			hasHelvetica = [fontNames containsObject: @"Arial"];
1829 		hasCourier = [fontNames containsObject: @"Courier"];
1830 		if (! hasCourier)
1831 			hasCourier = [fontNames containsObject: @"Courier New"];
1832 		hasPalatino = [fontNames containsObject: @"Palatino"];
1833 		if (! hasPalatino)
1834 			hasPalatino = [fontNames containsObject: @"Book Antiqua"];
1835 		hasDoulos = [fontNames containsObject: @"Doulos SIL"];
1836 		hasCharis = [fontNames containsObject: @"Charis SIL"];
1837 		hasIpaSerif = hasDoulos || hasCharis;
1838 		inited = true;
1839 		return true;
1840 	}
1841 #endif
1842 
1843 #if cairo
testFont(const char * fontName)1844 	static const char *testFont (const char *fontName) {
1845 		PangoFontDescription *pangoFontDescription = pango_font_description_from_string (fontName);
1846 		PangoFont *pangoFont = pango_font_map_load_font (thePangoFontMap, thePangoContext, pangoFontDescription);
1847 		PangoFontDescription *pangoFontDescription2 = pango_font_describe (pangoFont);
1848 		return pango_font_description_get_family (pangoFontDescription2);
1849 	}
_GraphicsLin_tryToInitializeFonts()1850 	bool _GraphicsLin_tryToInitializeFonts () {
1851 		static bool inited = false;
1852 		if (inited)
1853 			return true;
1854 		thePangoFontMap = pango_cairo_font_map_get_default ();
1855 		thePangoContext = pango_font_map_create_context (thePangoFontMap);
1856 		#if 0   /* For debugging: list all fonts. */
1857 			PangoFontFamily **families;
1858 			int numberOfFamilies;
1859 			pango_font_map_list_families (thePangoFontMap, & families, & numberOfFamilies);
1860 			for (int i = 0; i < numberOfFamilies; i ++)
1861 				fprintf (stderr, "%d %s\n", i, pango_font_family_get_name (families [i]));
1862 			g_free (families);
1863 		#endif
1864 		const char *trueName;
1865 		trueName = testFont ("Times");
1866 		hasTimes = !! strstr (trueName, "Times") || !! strstr (trueName, "Roman") || !! strstr (trueName, "Serif");
1867 		trueName = testFont ("Helvetica");
1868 		hasHelvetica = !! strstr (trueName, "Helvetica") || !! strstr (trueName, "Arial") || !! strstr (trueName, "Sans");
1869 		trueName = testFont ("Courier");
1870 		hasCourier = !! strstr (trueName, "Courier") || !! strstr (trueName, "Mono");
1871 		trueName = testFont ("Palatino");
1872 		hasPalatino = !! strstr (trueName, "Palatino") || !! strstr (trueName, "Palladio");
1873 		trueName = testFont ("Doulos SIL");
1874 		hasDoulos = !! strstr (trueName, "Doulos");
1875 		trueName = testFont ("Charis SIL");
1876 		hasCharis = !! strstr (trueName, "Charis");
1877 		hasIpaSerif = hasDoulos || hasCharis;
1878 		testFont ("Symbol");
1879 		testFont ("Dingbats");
1880 		#if 0   /* For debugging: list font availability. */
1881 			fprintf (stderr, "times %d helvetica %d courier %d palatino %d doulos %d charis %d\n",
1882 				hasTimes, hasHelvetica, hasCourier, hasPalatino, hasDoulos, hasCharis);
1883 		#endif
1884 		inited = true;
1885 		return true;
1886 	}
1887 #endif
1888 
_GraphicsScreen_text_init(GraphicsScreen me)1889 void _GraphicsScreen_text_init (GraphicsScreen me) {   // BUG: should be done as late as possible
1890 	#if cairo
1891         (void) me;
1892 		Melder_assert (_GraphicsLin_tryToInitializeFonts ());
1893 	#elif gdi
1894 		int font, size, style;
1895 		if (my printer || my metafile)
1896 			for (font = (int) kGraphics_font::MIN; font <= kGraphics_font_DINGBATS; font ++)
1897 				for (size = 0; size <= 4; size ++)
1898 					for (style = 0; style <= Graphics_BOLD_ITALIC; style ++)
1899 						if (fonts [(int) my resolutionNumber] [font] [size] [style]) {
1900 							//DeleteObject (fonts [my resolutionNumber] [font] [size] [style]);
1901 							//fonts [my resolutionNumber] [font] [size] [style] = 0;
1902 						}
1903 	#elif quartz
1904         (void) me;
1905         Melder_assert (_GraphicsMac_tryToInitializeFonts ());   // should have been handled when setting my useQuartz to true
1906 	#endif
1907 }
1908 
1909 /* Output attributes. */
1910 
Graphics_setTextAlignment(Graphics me,kGraphics_horizontalAlignment hor,int vert)1911 void Graphics_setTextAlignment (Graphics me, kGraphics_horizontalAlignment hor, int vert) {
1912 	if ((int) hor != Graphics_NOCHANGE)
1913 		my horizontalTextAlignment = (int) hor;
1914 	if (vert != Graphics_NOCHANGE)
1915 		my verticalTextAlignment = vert;
1916 	if (my recording) { op (SET_TEXT_ALIGNMENT, 2); put (hor); put (vert); }
1917 }
1918 
Graphics_setFont(Graphics me,kGraphics_font font)1919 void Graphics_setFont (Graphics me, kGraphics_font font) {
1920 	my font = font;
1921 	if (my recording) { op (SET_FONT, 1); put (font); }
1922 }
1923 
Graphics_setFontSize(Graphics me,double size)1924 void Graphics_setFontSize (Graphics me, double size) {
1925 	my fontSize = size;
1926 	if (my recording) { op (SET_FONT_SIZE, 1); put (size); }
1927 }
1928 
Graphics_setFontStyle(Graphics me,int style)1929 void Graphics_setFontStyle (Graphics me, int style) {
1930 	my fontStyle = style;
1931 	if (my recording) { op (SET_FONT_STYLE, 1); put (style); }
1932 }
1933 
Graphics_setItalic(Graphics me,bool onoff)1934 void Graphics_setItalic (Graphics me, bool onoff) {
1935 	Graphics_setFontStyle (me, ( onoff ? my fontStyle | Graphics_ITALIC : my fontStyle & ~ Graphics_ITALIC ));
1936 }
1937 
Graphics_setBold(Graphics me,bool onoff)1938 void Graphics_setBold (Graphics me, bool onoff) {
1939 	Graphics_setFontStyle (me, ( onoff ? my fontStyle | Graphics_BOLD : my fontStyle & ~ Graphics_BOLD ));
1940 }
1941 
Graphics_setCode(Graphics me,bool onoff)1942 void Graphics_setCode (Graphics me, bool onoff) {
1943 	Graphics_setFontStyle (me, ( onoff ? my fontStyle | Graphics_CODE : my fontStyle & ~ Graphics_CODE ));
1944 }
1945 
Graphics_setTextRotation(Graphics me,double angle)1946 void Graphics_setTextRotation (Graphics me, double angle) {
1947 	my textRotation = angle;
1948 	if (my recording) { op (SET_TEXT_ROTATION, 1); put (angle); }
1949 }
1950 
Graphics_setWrapWidth(Graphics me,double wrapWidth)1951 void Graphics_setWrapWidth (Graphics me, double wrapWidth) {
1952 	my wrapWidth = wrapWidth;
1953 	if (my recording) { op (SET_WRAP_WIDTH, 1); put (wrapWidth); }
1954 }
1955 
Graphics_setSecondIndent(Graphics me,double indent)1956 void Graphics_setSecondIndent (Graphics me, double indent) {
1957 	my secondIndent = indent;
1958 	if (my recording) { op (SET_SECOND_INDENT, 1); put (indent); }
1959 }
1960 
Graphics_setPercentSignIsItalic(Graphics me,bool isItalic)1961 void Graphics_setPercentSignIsItalic (Graphics me, bool isItalic) {
1962 	my percentSignIsItalic = isItalic;
1963 	if (my recording) { op (SET_PERCENT_SIGN_IS_ITALIC, 1); put (isItalic); }
1964 }
1965 
Graphics_setNumberSignIsBold(Graphics me,bool isBold)1966 void Graphics_setNumberSignIsBold (Graphics me, bool isBold) {
1967 	my numberSignIsBold = isBold;
1968 	if (my recording) { op (SET_NUMBER_SIGN_IS_BOLD, 1); put (isBold); }
1969 }
1970 
Graphics_setCircumflexIsSuperscript(Graphics me,bool isSuperscript)1971 void Graphics_setCircumflexIsSuperscript (Graphics me, bool isSuperscript) {
1972 	my circumflexIsSuperscript = isSuperscript;
1973 	if (my recording) { op (SET_CIRCUMFLEX_IS_SUPERSCRIPT, 1); put (isSuperscript); }
1974 }
1975 
Graphics_setUnderscoreIsSubscript(Graphics me,bool isSubscript)1976 void Graphics_setUnderscoreIsSubscript (Graphics me, bool isSubscript) {
1977 	my underscoreIsSubscript = isSubscript;
1978 	if (my recording) { op (SET_UNDERSCORE_IS_SUBSCRIPT, 1); put (isSubscript); }
1979 }
1980 
Graphics_setDollarSignIsCode(Graphics me,bool isCode)1981 void Graphics_setDollarSignIsCode (Graphics me, bool isCode) {
1982 	my dollarSignIsCode = isCode;
1983 	if (my recording) { op (SET_DOLLAR_SIGN_IS_CODE, 1); put (isCode); }
1984 }
1985 
Graphics_setAtSignIsLink(Graphics me,bool isLink)1986 void Graphics_setAtSignIsLink (Graphics me, bool isLink) {
1987 	my atSignIsLink = isLink;
1988 	if (my recording) { op (SET_AT_SIGN_IS_LINK, 1); put (isLink); }
1989 }
1990 
1991 /* Inquiries. */
1992 
Graphics_inqFont(Graphics me)1993 enum kGraphics_font Graphics_inqFont (Graphics me) { return my font; }
Graphics_inqFontSize(Graphics me)1994 double Graphics_inqFontSize (Graphics me) { return my fontSize; }
Graphics_inqFontStyle(Graphics me)1995 int Graphics_inqFontStyle (Graphics me) { return my fontStyle; }
1996 
1997 /* End of file Graphics_text.cpp */
1998