/* Graphics_text.cpp
 *
 * Copyright (C) 1992-2019 Paul Boersma, 2013 Tom Naughton, 2017 David Weenink
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This code is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this work. If not, see <http://www.gnu.org/licenses/>.
 */

#include <ctype.h>
#include "../kar/UnicodeData.h"
#include "GraphicsP.h"
#include "../kar/longchar.h"
#include "Printer.h"

extern const char * ipaSerifRegularPS [];

/*
 * When computing the width of a text by adding the widths of the separate characters,
 * we will make a correction for systems that make slanted characters overlap the character box to their right.
 * The effect is especially strong on Mac (older versions).
 * The slant correction is taken relative to the font size.
 */
#define POSTSCRIPT_SLANT_CORRECTION  0.1
#define SCREEN_SLANT_CORRECTION  0.05

#define HAS_FI_AND_FL_LIGATURES  ( my postScript == true )

#if cairo
	PangoFontMap *thePangoFontMap;
	PangoContext *thePangoContext;
	static bool hasTimes, hasHelvetica, hasCourier, hasSymbol, hasPalatino, hasDoulos, hasCharis, hasIpaSerif;
#elif gdi
	#define win_MAXIMUM_FONT_SIZE  500
	static HFONT fonts [1 + (int) kGraphics_resolution::MAX] [1 + kGraphics_font_JAPANESE] [1+win_MAXIMUM_FONT_SIZE] [1 + Graphics_BOLD_ITALIC];
	static int win_size2isize (int size) { return size > win_MAXIMUM_FONT_SIZE ? win_MAXIMUM_FONT_SIZE : size; }
	static int win_isize2size (int isize) { return isize; }
#elif quartz
	static bool hasTimes, hasHelvetica, hasCourier, hasSymbol, hasPalatino, hasDoulos, hasCharis, hasIpaSerif;
	#define mac_MAXIMUM_FONT_SIZE  500
	static CTFontRef theScreenFonts [1 + kGraphics_font_DINGBATS] [1+mac_MAXIMUM_FONT_SIZE] [1 + Graphics_BOLD_ITALIC];
	static RGBColor theWhiteColour = { 0xFFFF, 0xFFFF, 0xFFFF }, theBlueColour = { 0, 0, 0xFFFF };
#endif

#if gdi
	#ifdef __CYGWIN__
		#define FONT_TYPE_TYPE  unsigned int
	#else
		#define FONT_TYPE_TYPE  unsigned long int
	#endif
	static bool charisAvailable = false, doulosAvailable = false;
	static int CALLBACK fontFuncEx_charis (const LOGFONTW *oldLogFont, const TEXTMETRICW *oldTextMetric, FONT_TYPE_TYPE fontType, LPARAM lparam) {
		const LPENUMLOGFONTW logFont = (LPENUMLOGFONTW) oldLogFont; (void) oldTextMetric; (void) fontType; (void) lparam;
		charisAvailable = true;
		return 1;
	}
	static int CALLBACK fontFuncEx_doulos (const LOGFONTW *oldLogFont, const TEXTMETRICW *oldTextMetric, FONT_TYPE_TYPE fontType, LPARAM lparam) {
		const LPENUMLOGFONTW logFont = (LPENUMLOGFONTW) oldLogFont; (void) oldTextMetric; (void) fontType; (void) lparam;
		doulosAvailable = true;
		return 1;
	}
	static HFONT loadFont (GraphicsScreen me, int font, int size, int style) {
		LOGFONTW spec;
		static int ipaInited;
		if (my printer || my metafile) {
			spec. lfHeight = - win_isize2size (size) * my resolution / 72.0;
		} else {
			spec. lfHeight = - win_isize2size (size) * my resolution / 72.0;
		}
		spec. lfWidth = 0;
		spec. lfEscapement = spec. lfOrientation = 0;
		spec. lfWeight = style & Graphics_BOLD ? FW_BOLD : 0;
		spec. lfItalic = style & Graphics_ITALIC ? 1 : 0;
		spec. lfUnderline = spec. lfStrikeOut = 0;
		spec. lfCharSet =
			font == kGraphics_font_SYMBOL ? SYMBOL_CHARSET :
			font == kGraphics_font_CHINESE ? DEFAULT_CHARSET :
			font == kGraphics_font_JAPANESE ? DEFAULT_CHARSET :
			font >= kGraphics_font_IPATIMES ? DEFAULT_CHARSET :
			ANSI_CHARSET;
		spec. lfOutPrecision = spec. lfClipPrecision = spec. lfQuality = 0;
		spec. lfPitchAndFamily =
			( font == (int) kGraphics_font::COURIER ? FIXED_PITCH : font == kGraphics_font_IPATIMES ? DEFAULT_PITCH : VARIABLE_PITCH ) |
			( font == (int) kGraphics_font::HELVETICA ? FF_SWISS : font == (int) kGraphics_font::COURIER ? FF_MODERN :
			  font == kGraphics_font_CHINESE ? FF_DONTCARE :
			  font == kGraphics_font_JAPANESE ? FF_DONTCARE :
			  font >= kGraphics_font_IPATIMES ? FF_DONTCARE : FF_ROMAN );
		if (font == kGraphics_font_IPATIMES && ! ipaInited && Melder_debug != 15) {
			LOGFONTW logFont;
			logFont. lfCharSet = DEFAULT_CHARSET;
			logFont. lfPitchAndFamily = 0;
			wcscpy (logFont. lfFaceName, L"Charis SIL");
			EnumFontFamiliesExW (my d_gdiGraphicsContext, & logFont, fontFuncEx_charis, 0, 0);
			wcscpy (logFont. lfFaceName, L"Doulos SIL");
			EnumFontFamiliesExW (my d_gdiGraphicsContext, & logFont, fontFuncEx_doulos, 0, 0);
			ipaInited = true;
			if (! charisAvailable && ! doulosAvailable) {
				/* BUG: The next warning may cause reentry of drawing (on window exposure) and lead to crash. Some code must be non-reentrant !! */
				Melder_warning (U"The phonetic font is not available.\nSeveral characters may not look correct.\nSee www.praat.org");
			}
		}
		wcscpy (spec. lfFaceName,
			font == (int) kGraphics_font::HELVETICA ? L"Arial" :
			font == (int) kGraphics_font::TIMES     ? L"Times New Roman" :
			font == (int) kGraphics_font::COURIER   ? L"Courier New" :
			font == (int) kGraphics_font::PALATINO  ? L"Book Antiqua" :
			font == kGraphics_font_SYMBOL    ? L"Symbol" :
			font == kGraphics_font_IPATIMES  ? ( doulosAvailable && style == 0 ? L"Doulos SIL" : charisAvailable ? L"Charis SIL" : L"Times New Roman" ) :
			font == kGraphics_font_DINGBATS  ? L"Wingdings" :
			font == kGraphics_font_CHINESE   ? L"SimSun" :
			font == kGraphics_font_JAPANESE  ? L"MS UI Gothic" :
			L"");
		return CreateFontIndirectW (& spec);
	}
#endif

#if cairo
	static PangoFontDescription *PangoFontDescription_create (int font, _Graphics_widechar *lc) {
		static PangoFontDescription *fontDescriptions [1 + kGraphics_font_DINGBATS];
		Melder_assert (font >= 0 && font <= kGraphics_font_DINGBATS);
		if (! fontDescriptions [font]) {
			const char *fontFace =
				font == (int) kGraphics_font::HELVETICA ? "Helvetica" :
				font == (int) kGraphics_font::TIMES ? "Times" :
				font == (int) kGraphics_font::COURIER ? "Courier" :
				font == (int) kGraphics_font::PALATINO ? "Palatino" :
				font == kGraphics_font_IPATIMES ? "Doulos SIL" :
				font == kGraphics_font_IPAPALATINO ? "Charis SIL" :
				font == kGraphics_font_DINGBATS ? "Dingbats" : "Serif";
			fontDescriptions [font] = pango_font_description_from_string (fontFace);
		}

		PangoStyle slant = (lc -> style & Graphics_ITALIC ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
		pango_font_description_set_style (fontDescriptions [font], slant);
						
		PangoWeight weight = (lc -> style & Graphics_BOLD ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
		pango_font_description_set_weight (fontDescriptions [font], weight);
		pango_font_description_set_absolute_size (fontDescriptions [font], (int) (lc -> size * PANGO_SCALE));
		return fontDescriptions [font];
	}
#endif

inline static bool isDiacritic (Longchar_Info info, int font) {
	if (info -> isDiacritic == 0) return false;
	if (info -> isDiacritic == 1) return true;
	Melder_assert (info -> isDiacritic == 2);   // corner
	if (font == kGraphics_font_IPATIMES || font == kGraphics_font_IPAPALATINO) return false;   // Doulos or Charis
	return true;   // e.g. Times substitutes a zero-width corner
}

/*
	Most operating systems provide automatic font substitution nowadays,
	so that e.g. phonetic characters will be drawn recognizably even if the
	user-preferred font is e.g. Times, Helvetica, Courier or Palatino
	and the phonetic characters are not available in that user font.
	
	However, on some systems the substituted font might really be a last resort
	font that does not look at all similar to the user font. An example is
	the use of the sans-serif font Lucida Grande for phonetic characters
	within a stretch of a serif font such as Times or Palatino.
	
	This is not good enough for Praat. We need more control over the shape
	of phonetic characters. We therefore advise the use of Doulos SIL,
	which is Times-like, or Charis SIL, which is Palatino-like.
	For true continuity between non-phonetic and phonetic characters it is
	mandatory that the exact same font is used for both types of characters,
	so we use Doulos SIL to replace Times even for non-phonetic characters,
	and Charis SIL to replace Palatino even for non-phonetic characters.
	A technical issue that makes this even more important is that diacritics
	can look really weird if at the beginning of a Praat font stretch:
	a "b" followed by a ring below will not be aligned correctly if they
	are part of different Praat font stretches.
	
	Beside Praat-enforced visual font continuity, there are some more issues,
	such as that the "/" (slash) character should extend below the baseline
	whenever it is used in an equation or to demarcate a phonological
	representation.
*/
#if cairo || quartz
inline static int chooseFont (Graphics me, _Graphics_widechar *lc) {
	/*
		When we arrive here, the character's font is the "user-preferred font",
		which is Courier if the user asked for code style (e.g. between "$$" and "$"),
		or otherwise Times, Helvetica, Courier or Palatino, as chosen from a font menu.
		
		Exception: if the character is a slash, its font has already been converted to Courier.
	*/
	int font = lc -> font.integer_;
	Longchar_Info info = lc -> karInfo;
	int alphabet = info -> alphabet;

	if (font == (int) kGraphics_font::COURIER) {
		constexpr bool systemSubstitutesMonospacedSerifFontForIpaCourier = (quartz);
		if (systemSubstitutesMonospacedSerifFontForIpaCourier) {
			/*
				No need to check whether the character is phonetic or not.
			*/
			return (int) kGraphics_font::COURIER;
		}
		if (alphabet == Longchar_SYMBOL ||
			alphabet == Longchar_PHONETIC ||
			lc [1]. kar > U'\t' && lc [1]. karInfo -> isDiacritic)   // inspect next character to ensure diacritic continuity
		{
			/*
				Serif is more important than monospaced,
				and Charis looks slightly better within Courier than Doulos does.
			*/
			if (hasCharis) return kGraphics_font_IPAPALATINO;
			if (hasDoulos) return kGraphics_font_IPATIMES;
		}
		return (int) kGraphics_font::COURIER;
	}
	font =
		alphabet == Longchar_SYMBOL || // ? kGraphics_font_SYMBOL :
		alphabet == Longchar_PHONETIC ?
			( my font == kGraphics_font::TIMES ?
				( hasDoulos ?
					( lc -> style == 0 ?
						kGraphics_font_IPATIMES :
					  hasCharis ?
						kGraphics_font_IPAPALATINO :   // other styles in Charis, because Doulos has no bold or italic
						(int) kGraphics_font::TIMES
					) :
				  hasCharis ?
					kGraphics_font_IPAPALATINO :
					(int) kGraphics_font::TIMES   // on newer systems, Times and Times New Roman have a lot of phonetic characters
				) :
			  my font == kGraphics_font::HELVETICA ?
				(int) kGraphics_font::HELVETICA :   // sans serif, so fall back on Lucida Grande or so for phonetic characters
			  /* my font must be kGraphics_font_PALATINO */
			  hasCharis && Melder_debug != 900 ?
				kGraphics_font_IPAPALATINO :
			  hasDoulos && Melder_debug != 900 ?
				( lc -> style == 0 ?
					kGraphics_font_IPATIMES :
					(int) kGraphics_font::TIMES
				) :
				(int) kGraphics_font::PALATINO
			) :
		alphabet == Longchar_DINGBATS ?
			kGraphics_font_DINGBATS :
		my font == kGraphics_font::TIMES ?
			( hasDoulos ?
				( lc -> style == 0 ?
					kGraphics_font_IPATIMES :
				  lc -> style == Graphics_ITALIC ?
					( lc [1]. kar > U'\t' && lc [1]. karInfo -> isDiacritic && hasCharis ?
						kGraphics_font_IPAPALATINO : (int) kGraphics_font::TIMES ) :   // correct placement of diacritics
				  hasCharis ?
					kGraphics_font_IPAPALATINO :
					(int) kGraphics_font::TIMES
				) :
				(int) kGraphics_font::TIMES
			) :
		my font == kGraphics_font::HELVETICA ?
			(int) kGraphics_font::HELVETICA :
		my font == kGraphics_font::PALATINO ?
			( hasCharis && Melder_debug != 900 ?
				kGraphics_font_IPAPALATINO :
				(int) kGraphics_font::PALATINO
			) :
		(int) my font;   // why not lc -> font.integer_?
	Melder_assert (font >= 0 && font <= kGraphics_font_DINGBATS);
	return font;
}
#endif

static void charSize (void *void_me, _Graphics_widechar *lc) {
	iam (Graphics);
	if (my screen) {
		iam (GraphicsScreen);
		#if cairo
			Melder_assert (my duringXor);
			Longchar_Info info = lc -> karInfo;
			int normalSize = my fontSize * my resolution / 72.0;
			int smallSize = (3 * normalSize + 2) / 4;
			int size = lc -> size < 100 ? smallSize : normalSize;
			lc -> width = 7;
			lc -> baseline *= my fontSize * 0.01;
			lc -> code = lc -> kar;
			lc -> font.string = nullptr;
			lc -> font.integer_ = 0;
			lc -> size = size;
		#elif gdi
			Longchar_Info info = lc -> karInfo;
			int font, size, style;
			HFONT fontInfo;
			int normalSize = win_size2isize (my fontSize);
			int smallSize = (3 * normalSize + 2) / 4;
			font = info -> alphabet == Longchar_SYMBOL ? kGraphics_font_SYMBOL :
			       info -> alphabet == Longchar_PHONETIC ? kGraphics_font_IPATIMES :
			       info -> alphabet == Longchar_DINGBATS ? kGraphics_font_DINGBATS : lc -> font.integer_;
			if ((unsigned int) lc -> kar >= 0x2E80 && (unsigned int) lc -> kar <= 0x9FFF)
				font = ( theGraphicsCjkFontStyle == kGraphics_cjkFontStyle::CHINESE ? kGraphics_font_CHINESE : kGraphics_font_JAPANESE );
			size = lc -> size < 100 ? smallSize : normalSize;
			style = lc -> style & (Graphics_ITALIC | Graphics_BOLD);   // take out Graphics_CODE
			fontInfo = fonts [(int) my resolutionNumber] [font] [size] [style];
			if (! fontInfo) {
				fontInfo = loadFont (me, font, size, style);
				if (! fontInfo) return;
				fonts [(int) my resolutionNumber] [font] [size] [style] = fontInfo;
			}
			SIZE extent;
			lc -> code =
				font == kGraphics_font_IPATIMES ||
				font == (int) kGraphics_font::TIMES ||
				font == (int) kGraphics_font::HELVETICA ||
				font == kGraphics_font_CHINESE ||
				font == kGraphics_font_JAPANESE ||
				font == (int) kGraphics_font::COURIER ? lc -> kar :
				info -> winEncoding;
			if (lc -> code == 0) {
				_Graphics_widechar *lc2;
				if (lc -> kar == UNICODE_LATIN_SMALL_LETTER_SCHWA_WITH_HOOK) {
					info = Longchar_getInfo ('s', 'w');
					lc -> kar = info -> unicode;
					lc -> code = info -> winEncoding;
					for (lc2 = lc + 1; lc2 -> kar != U'\0'; lc2 ++) { }
					lc2 [1]. kar = U'\0';
					while (lc2 - lc > 0) { lc2 [0] = lc2 [-1]; lc2 --; }
					lc [1]. kar = UNICODE_MODIFIER_LETTER_RHOTIC_HOOK;
				} else if (lc -> kar == UNICODE_LATIN_SMALL_LETTER_L_WITH_MIDDLE_TILDE) {
					info = Longchar_getInfo ('l', ' ');
					lc -> kar = info -> unicode;
					lc -> code = info -> winEncoding;
					for (lc2 = lc + 1; lc2 -> kar != U'\0'; lc2 ++) { }
					lc2 [1]. kar = U'\0';
					while (lc2 - lc > 0) { lc2 [0] = lc2 [-1]; lc2 --; }
					lc [1]. kar = UNICODE_COMBINING_TILDE_OVERLAY;
				}
			}
			SelectFont (my d_gdiGraphicsContext, fontInfo);
			if (lc -> code <= 0x00FFFF) {
				char16 code = (char16) lc -> code;
				GetTextExtentPoint32W (my d_gdiGraphicsContext, (WCHAR *) & code, 1, & extent);
			} else {
				char32 code [2] { lc -> code, U'\0' };
				GetTextExtentPoint32W (my d_gdiGraphicsContext, Melder_peek32toW (code), 2, & extent);
			}
			lc -> width = extent. cx;
			lc -> baseline *= my fontSize * 0.01 * my resolution / 72.0;
			lc -> font.string = nullptr;
			lc -> font.integer_ = font;   // kGraphics_font_HELVETICA .. kGraphics_font_DINGBATS
			lc -> size = size;   // 0..4 instead of 10..24
			lc -> style = style;   // without Graphics_CODE
		#elif quartz
		#endif
	} else if (my postScript) {
		iam (GraphicsPostscript);
		int normalSize = (int) ((double) my fontSize * (double) my resolution / 72.0);
		Longchar_Info info = lc -> karInfo;
		int font = info -> alphabet == Longchar_SYMBOL ? kGraphics_font_SYMBOL :
				info -> alphabet == Longchar_PHONETIC ? kGraphics_font_IPATIMES :
				info -> alphabet == Longchar_DINGBATS ? kGraphics_font_DINGBATS : lc -> font.integer_;
		int style = lc -> style == Graphics_ITALIC ? Graphics_ITALIC :
			lc -> style == Graphics_BOLD || lc -> link ? Graphics_BOLD :
			lc -> style == Graphics_BOLD_ITALIC ? Graphics_BOLD_ITALIC : 0;
		if (! my fontInfos [font] [style]) {
			const char *fontInfo, *secondaryFontInfo = nullptr, *tertiaryFontInfo = nullptr;
			if (font == (int) kGraphics_font::COURIER) {
				fontInfo = style == Graphics_BOLD ? "Courier-Bold" :
					style == Graphics_ITALIC ? "Courier-Oblique" :
					style == Graphics_BOLD_ITALIC ? "Courier-BoldOblique" : "Courier";
				secondaryFontInfo = style == Graphics_BOLD ? "CourierNewPS-BoldMT" :
					style == Graphics_ITALIC ? "CourierNewPS-ItalicMT" :
					style == Graphics_BOLD_ITALIC ? "CourierNewPS-BoldItalicMT" : "CourierNewPSMT";
				tertiaryFontInfo = style == Graphics_BOLD ? "CourierNew-Bold" :
					style == Graphics_ITALIC ? "CourierNew-Italic" :
					style == Graphics_BOLD_ITALIC ? "CourierNew-BoldItalic" : "CourierNew";
			} else if (font == (int) kGraphics_font::TIMES) {
				fontInfo = style == Graphics_BOLD ? "Times-Bold" :
					style == Graphics_ITALIC ? "Times-Italic" :
					style == Graphics_BOLD_ITALIC ? "Times-BoldItalic" : "Times-Roman";
				secondaryFontInfo = style == Graphics_BOLD ? "TimesNewRomanPS-BoldMT" :
					style == Graphics_ITALIC ? "TimesNewRomanPS-ItalicMT" :
					style == Graphics_BOLD_ITALIC ? "TimesNewRomanPS-BoldItalicMT" : "TimesNewRomanPSMT";
				tertiaryFontInfo = style == Graphics_BOLD ? "TimesNewRoman-Bold" :
					style == Graphics_ITALIC ? "TimesNewRoman-Italic" :
					style == Graphics_BOLD_ITALIC ? "TimesNewRoman-BoldItalic" : "TimesNewRoman";
			} else if (font == (int) kGraphics_font::PALATINO) {
				fontInfo = style == Graphics_BOLD ? "Palatino-Bold" :
					style == Graphics_ITALIC ? "Palatino-Italic" :
					style == Graphics_BOLD_ITALIC ? "Palatino-BoldItalic" : "Palatino-Roman";
				secondaryFontInfo = style == Graphics_BOLD ? "BookAntiquaPS-BoldMT" :
					style == Graphics_ITALIC ? "BookAntiquaPS-ItalicMT" :
					style == Graphics_BOLD_ITALIC ? "BookAntiquaPS-BoldItalicMT" : "BookAntiquaPSMT";
				tertiaryFontInfo = style == Graphics_BOLD ? "BookAntiqua-Bold" :
					style == Graphics_ITALIC ? "BookAntiqua-Italic" :
					style == Graphics_BOLD_ITALIC ? "BookAntiqua-BoldItalic" : "BookAntiqua";
			} else if (font == kGraphics_font_IPATIMES) {
				if (my includeFonts && ! my loadedXipa) {
					const char **p;
					for (p = & ipaSerifRegularPS [0]; *p; p ++)
						my d_printf (my d_file, "%s", *p);
					my loadedXipa = true;
				}
				fontInfo = my useSilipaPS ?
					(style == Graphics_BOLD || style == Graphics_BOLD_ITALIC ? "SILDoulosIPA93Bold" : "SILDoulosIPA93Regular") :
					"TeX-xipa10-Praat-Regular";
			} else if (font == kGraphics_font_SYMBOL) {
				fontInfo = "Symbol";
			} else if (font == kGraphics_font_DINGBATS) {
				fontInfo = "ZapfDingbats";
			} else {
				fontInfo = style == Graphics_BOLD ? "Helvetica-Bold" :
					style == Graphics_ITALIC ? "Helvetica-Oblique" :
					style == Graphics_BOLD_ITALIC ? "Helvetica-BoldOblique" : "Helvetica";
				secondaryFontInfo = style == Graphics_BOLD ? "Arial-BoldMT" :
					style == Graphics_ITALIC ? "Arial-ItalicMT" :
					style == Graphics_BOLD_ITALIC ? "Arial-BoldItalicMT" : "ArialMT";
				tertiaryFontInfo = style == Graphics_BOLD ? "Arial-Bold" :
					style == Graphics_ITALIC ? "Arial-Italic" :
					style == Graphics_BOLD_ITALIC ? "Arial-BoldItalic" : "Arial";
			}
			my fontInfos [font] [style] = Melder_malloc_f (char, 100);
			if (font == kGraphics_font_IPATIMES || font == kGraphics_font_SYMBOL || font == kGraphics_font_DINGBATS) {
				strcpy (my fontInfos [font] [style], fontInfo);
			} else {
				sprintf (my fontInfos [font] [style], "%s-Praat", fontInfo);
				if (thePrinter. fontChoiceStrategy == kGraphicsPostscript_fontChoiceStrategy::LINOTYPE) {
					my d_printf (my d_file, "/%s /%s-Praat PraatEncode\n", fontInfo, fontInfo);
				} else if (thePrinter. fontChoiceStrategy == kGraphicsPostscript_fontChoiceStrategy::MONOTYPE) {
					my d_printf (my d_file, "/%s /%s-Praat PraatEncode\n", tertiaryFontInfo, fontInfo);
				} else if (thePrinter. fontChoiceStrategy == kGraphicsPostscript_fontChoiceStrategy::PS_MONOTYPE) {
					my d_printf (my d_file, "/%s /%s-Praat PraatEncode\n", secondaryFontInfo, fontInfo);
				} else {
					/* Automatic font choice strategy. */
					if (secondaryFontInfo) {
						my d_printf (my d_file,
							"/%s /Font resourcestatus\n"
							"{ pop pop /%s /%s-Praat PraatEncode }\n"
							"{ /%s /%s-Praat PraatEncode }\n"
							"ifelse\n",
							secondaryFontInfo, secondaryFontInfo, fontInfo, fontInfo, fontInfo);
					} else {
						my d_printf (my d_file, "/%s /%s-Praat PraatEncode\n", fontInfo, fontInfo);
					}
				}
			}
		}
		lc -> font.integer_ = 0;
		lc -> font.string = my fontInfos [font] [style];

		/*
		 * Convert size and baseline information to device coordinates.
		 */
		lc -> size *= normalSize * 0.01;
		lc -> baseline *= normalSize * 0.01;

		if (font == (int) kGraphics_font::COURIER) {
			lc -> width = 600;   // Courier
		} else if (style == 0) {
			if (font == (int) kGraphics_font::TIMES) lc -> width = info -> ps.times;
			else if (font == (int) kGraphics_font::HELVETICA) lc -> width = info -> ps.helvetica;
			else if (font == (int) kGraphics_font::PALATINO) lc -> width = info -> ps.palatino;
			else if (font == kGraphics_font_SYMBOL) lc -> width = info -> ps.times;
			else if (my useSilipaPS) lc -> width = info -> ps.timesItalic;
			else lc -> width = info -> ps.times;   // XIPA
		} else if (style == Graphics_BOLD) {
			if (font == (int) kGraphics_font::TIMES) lc -> width = info -> ps.timesBold;
			else if (font == (int) kGraphics_font::HELVETICA) lc -> width = info -> ps.helveticaBold;
			else if (font == (int) kGraphics_font::PALATINO) lc -> width = info -> ps.palatinoBold;
			else if (font == kGraphics_font_SYMBOL) lc -> width = info -> ps.times;
			else if (my useSilipaPS) lc -> width = info -> ps.timesBoldItalic;
			else lc -> width = info -> ps.times;   // Symbol, IPA
		} else if (style == Graphics_ITALIC) {
			if (font == (int) kGraphics_font::TIMES) lc -> width = info -> ps.timesItalic;
			else if (font == (int) kGraphics_font::HELVETICA) lc -> width = info -> ps.helvetica;
			else if (font == (int) kGraphics_font::PALATINO) lc -> width = info -> ps.palatinoItalic;
			else if (font == kGraphics_font_SYMBOL) lc -> width = info -> ps.times;
			else if (my useSilipaPS) lc -> width = info -> ps.timesItalic;
			else lc -> width = info -> ps.times;   // Symbol, IPA
		} else if (style == Graphics_BOLD_ITALIC) {
			if (font == (int) kGraphics_font::TIMES) lc -> width = info -> ps.timesBoldItalic;
			else if (font == (int) kGraphics_font::HELVETICA) lc -> width = info -> ps.helveticaBold;
			else if (font == (int) kGraphics_font::PALATINO) lc -> width = info -> ps.palatinoBoldItalic;
			else if (font == kGraphics_font_SYMBOL) lc -> width = info -> ps.times;
			else if (my useSilipaPS) lc -> width = info -> ps.timesBoldItalic;
			else lc -> width = info -> ps.times;   // Symbol, IPA
		}
		lc -> width *= lc -> size / 1000.0;
		lc -> code = font == kGraphics_font_IPATIMES && my useSilipaPS ? info -> macEncoding : info -> psEncoding;
		if (lc -> code == 0) {
			_Graphics_widechar *lc2;
			if (lc -> kar == UNICODE_LATIN_SMALL_LETTER_SCHWA_WITH_HOOK) {
				info = Longchar_getInfo ('s', 'w');
				lc -> kar = info -> unicode;
				lc -> code = info -> macEncoding;
				lc -> width = info -> ps.timesItalic * lc -> size / 1000.0;
				for (lc2 = lc + 1; lc2 -> kar != U'\0'; lc2 ++) { }
				lc2 [1]. kar = U'\0';
				while (lc2 - lc > 0) { lc2 [0] = lc2 [-1]; lc2 --; }
				lc [1]. kar = UNICODE_MODIFIER_LETTER_RHOTIC_HOOK;
			} else if (lc -> kar == UNICODE_LATIN_SMALL_LETTER_L_WITH_MIDDLE_TILDE) {
				info = Longchar_getInfo ('l', ' ');
				lc -> code = info -> macEncoding;
				lc -> kar = info -> unicode;
				lc -> width = info -> ps.timesItalic * lc -> size / 1000.0;
				for (lc2 = lc + 1; lc2 -> kar != U'\0'; lc2 ++) { }
				lc2 [1]. kar = U'\0';
				while (lc2 - lc > 0) { lc2 [0] = lc2 [-1]; lc2 --; }
				lc [1]. kar = UNICODE_COMBINING_TILDE_OVERLAY;
			}
		}
	}
}

static void charDraw (void *void_me, int xDC, int yDC, _Graphics_widechar *lc,
	const char32 codes [], int nchars, int width)
{
	iam (Graphics);
	//Melder_casual (U"nchars ", nchars, U" first ", (int) lc->kar, U" ", (char32) lc -> kar, U" rightToLeft ", lc->rightToLeft);
	if (my postScript) {
		iam (GraphicsPostscript);
		bool onlyRegular = lc -> font.string [0] == 'S' ||
			(lc -> font.string [0] == 'T' && lc -> font.string [1] == 'e');   // Symbol & SILDoulos !
		int slant = (lc -> style & Graphics_ITALIC) && onlyRegular;
		int thick = (lc -> style & Graphics_BOLD) && onlyRegular;
		if (lc -> font.string != my lastFid || lc -> size != my lastSize)
			my d_printf (my d_file, my languageLevel == 1 ? "/%s %d FONT\n" : "/%s %d selectfont\n",
				my lastFid = lc -> font.string, my lastSize = lc -> size);
		if (lc -> link) my d_printf (my d_file, "0 0 1 setrgbcolor\n");
		for (int i = -3; i <= 3; i ++) {
			if (i != 0 && ! thick) continue;
			my d_printf (my d_file, "%d %d M ", xDC + i, yDC);
			if (my textRotation != 0.0 || slant) {
				my d_printf (my d_file, "gsave currentpoint translate ");
				if (my textRotation != 0.0)
					my d_printf (my d_file, "%.6g rotate 0 0 M\n", (double) my textRotation);
				if (slant)
					my d_printf (my d_file, "[1 0 0.25 1 0 0] concat 0 0 M\n");
			}
			my d_printf (my d_file, "(");
			const char32 *p = & codes [0];
			while (*p) {
				if (*p == U'(' || *p == U')' || *p == U'\\') {
					my d_printf (my d_file, "\\%c", (unsigned char) *p);
				} else if (*p >= 32 && *p <= 126) {
					my d_printf (my d_file, "%c", (unsigned char) *p);
				} else {
					my d_printf (my d_file, "\\%d%d%d", (unsigned char) *p / 64,
						((unsigned char) *p % 64) / 8, (unsigned char) *p % 8);
				}
				p ++;
			}
			my d_printf (my d_file, ") show\n");
			if (my textRotation != 0.0 || slant)
				my d_printf (my d_file, "grestore\n");
		}
		if (lc -> link) my d_printf (my d_file, "0 0 0 setrgbcolor\n");
	} else if (my screen) {
		iam (GraphicsScreen);
		#if cairo
			if (my duringXor) {
				#if ALLOW_GDK_DRAWING
					static GdkFont *font = nullptr;
					if (! font) {
						font = gdk_font_load ("-*-courier-medium-r-normal--*-120-*-*-*-*-iso8859-1");
						if (! font) {
							font = gdk_font_load ("-*-courier 10 pitch-medium-r-normal--*-120-*-*-*-*-iso8859-1");
						}
					}
					if (font) {
						gdk_draw_text_wc (my d_window, font, my d_gdkGraphicsContext, xDC, yDC, (const GdkWChar *) codes, nchars);
					}
					gdk_flush ();
				#endif
				return;
			}
			if (! my d_cairoGraphicsContext) return;
			// TODO!
			if (lc -> link) _Graphics_setColour (me, Graphics_BLUE);
			int font = lc -> font.integer_;
			cairo_save (my d_cairoGraphicsContext);
			cairo_translate (my d_cairoGraphicsContext, xDC, yDC);
			//cairo_scale (my d_cairoGraphicsContext, 1, -1);
			cairo_rotate (my d_cairoGraphicsContext, - my textRotation * NUMpi / 180.0);
			const char *codes8 = Melder_peek32to8 (codes);
			PangoFontDescription *font_description = PangoFontDescription_create (font, lc);
			PangoLayout *layout = pango_cairo_create_layout (my d_cairoGraphicsContext);
			pango_layout_set_font_description (layout, font_description);
			pango_layout_set_text (layout, codes8, -1);
			cairo_move_to (my d_cairoGraphicsContext, 0 /*xDC*/, 0 /*yDC*/);
			// instead of pango_cairo_show_layout we use pango_cairo_show_layout_line to
			// get the same text origin as cairo_show_text, i.e. baseline left, instead of Pango's top left!
			pango_cairo_show_layout_line (my d_cairoGraphicsContext, pango_layout_get_line_readonly (layout, 0));
			g_object_unref (layout);
			cairo_restore (my d_cairoGraphicsContext);
			if (lc -> link) _Graphics_setColour (me, my colour);
			return;
		#elif gdi
			int font = lc -> font.integer_;
			conststringW codesW = Melder_peek32toW (codes);
			if (my duringXor) {
				int descent = (1.0/216) * my fontSize * my resolution;
				int ascent = (1.0/72) * my fontSize * my resolution;
				int maxWidth = 800, maxHeight = 200;
				int baseline = 100, top = baseline - ascent - 1, bottom = baseline + descent + 1;
				static int inited = 0;
				static HDC dc;
				static HBITMAP bitmap;
				if (! inited) {
					dc = CreateCompatibleDC (my d_gdiGraphicsContext);
					bitmap = CreateCompatibleBitmap (my d_gdiGraphicsContext, maxWidth, maxHeight);
					SelectBitmap (dc, bitmap);
					SetBkMode (dc, TRANSPARENT);   // not the default!
					SelectPen (dc, GetStockPen (BLACK_PEN));
					SelectBrush (dc, GetStockBrush (BLACK_BRUSH));
					SetTextAlign (dc, TA_LEFT | TA_BASELINE | TA_NOUPDATECP);   // baseline is not the default!
					inited = 1;
				}
				width += 4;   // for slant
				Rectangle (dc, 0, top, width, bottom);
				SelectFont (dc, fonts [(int) my resolutionNumber] [font] [lc -> size] [lc -> style]);
				SetTextColor (dc, my d_winForegroundColour);
				TextOutW (dc, 0, baseline, codesW, str16len ((conststring16) codesW));
				BitBlt (my d_gdiGraphicsContext, xDC, yDC - ascent, width, bottom - top, dc, 0, top, SRCINVERT);
				return;
			}
			SelectPen (my d_gdiGraphicsContext, my d_winPen), SelectBrush (my d_gdiGraphicsContext, my d_winBrush);
			if (lc -> link) SetTextColor (my d_gdiGraphicsContext, RGB (0, 0, 255)); else SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
			SelectFont (my d_gdiGraphicsContext, fonts [(int) my resolutionNumber] [font] [lc -> size] [lc -> style]);
			if (my textRotation == 0.0) {
				TextOutW (my d_gdiGraphicsContext, xDC, yDC, codesW, str16len ((const char16 *) codesW));
			} else {
				int restore = SaveDC (my d_gdiGraphicsContext);
				SetGraphicsMode (my d_gdiGraphicsContext, GM_ADVANCED);
				double a = my textRotation * NUMpi / 180.0, cosa = cos (a), sina = sin (a);
				XFORM rotate = { (float) cosa, (float) - sina, (float) sina, (float) cosa, 0.0, 0.0 };
				ModifyWorldTransform (my d_gdiGraphicsContext, & rotate, MWT_RIGHTMULTIPLY);
				XFORM translate = { 1.0, 0.0, 0.0, 1.0, (float) xDC, (float) yDC };
				ModifyWorldTransform (my d_gdiGraphicsContext, & translate, MWT_RIGHTMULTIPLY);
				TextOutW (my d_gdiGraphicsContext, 0, 0, codesW, str16len ((const char16 *) codesW));
				RestoreDC (my d_gdiGraphicsContext, restore);
			}
			if (lc -> link) SetTextColor (my d_gdiGraphicsContext, my d_winForegroundColour);
			SelectPen (my d_gdiGraphicsContext, GetStockPen (BLACK_PEN)), SelectBrush (my d_gdiGraphicsContext, GetStockBrush (NULL_BRUSH));
			return;
		#elif quartz
			/*
			 * Determine the font family.
			 */
			int font = lc -> font.integer_;   // the font of the first character

			/*
			 * Determine the style.
			 */
			int style = lc -> style;   // the style of the first character

			/*
			 * Determine the font-style combination.
			 */
			CTFontRef ctFont = theScreenFonts [font] [lc -> size] [style];
			if (! ctFont) {
				CTFontSymbolicTraits ctStyle = ( style & Graphics_BOLD ? kCTFontBoldTrait : 0 ) | ( lc -> style & Graphics_ITALIC ? kCTFontItalicTrait : 0 );
			#if 1
				CFStringRef key = kCTFontSymbolicTrait;
				CFNumberRef value = CFNumberCreate (nullptr, kCFNumberIntType, & ctStyle);
				CFIndex numberOfValues = 1;
				CFDictionaryRef styleDict = CFDictionaryCreate (nullptr, (const void **) & key, (const void **) & value, numberOfValues,
					& kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
				CFRelease (value);
				CFStringRef keys [2];
				keys [0] = kCTFontTraitsAttribute;
				keys [1] = kCTFontNameAttribute;
				CFStringRef cfFont;
				switch (font) {
					case (int) kGraphics_font::TIMES:       { cfFont = (CFStringRef) Melder_peek32toCfstring (U"Times New Roman"); } break;
					case (int) kGraphics_font::HELVETICA:   { cfFont = (CFStringRef) Melder_peek32toCfstring (U"Arial"          ); } break;
					case (int) kGraphics_font::COURIER:     { cfFont = (CFStringRef) Melder_peek32toCfstring (U"Courier New"    ); } break;
					case (int) kGraphics_font::PALATINO:    { if (Melder_debug == 900)
															cfFont = (CFStringRef) Melder_peek32toCfstring (U"DG Meta Serif Science");
													   else
														    cfFont = (CFStringRef) Melder_peek32toCfstring (U"Palatino");
													 } break;
					case kGraphics_font_SYMBOL:      { cfFont = (CFStringRef) Melder_peek32toCfstring (U"Symbol"         ); } break;
					case kGraphics_font_IPATIMES:    { cfFont = (CFStringRef) Melder_peek32toCfstring (U"Doulos SIL"     ); } break;
					case kGraphics_font_IPAPALATINO: { cfFont = (CFStringRef) Melder_peek32toCfstring (U"Charis SIL"     ); } break;
					case kGraphics_font_DINGBATS:    { cfFont = (CFStringRef) Melder_peek32toCfstring (U"Zapf Dingbats"  ); } break;
				}
				void *values [2] = { (void *) styleDict, (void *) cfFont };
				CFDictionaryRef attributes = CFDictionaryCreate (nullptr, (const void **) & keys, (const void **) & values, 2,
					& kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
				CFRelease (styleDict);
				CTFontDescriptorRef ctFontDescriptor = CTFontDescriptorCreateWithAttributes (attributes);
				CFRelease (attributes);
			#else   /* Preparing for the time to come when Apple deprecates Core Foundation. */
				NSMutableDictionary *styleDict = [[NSMutableDictionary alloc] initWithCapacity: 1];
				[styleDict   setObject: [NSNumber numberWithUnsignedInt: ctStyle]   forKey: (id) kCTFontSymbolicTrait];
				NSMutableDictionary *attributes = [[NSMutableDictionary alloc] initWithCapacity: 2];
				[attributes   setObject: styleDict   forKey: (id) kCTFontTraitsAttribute];
				switch (font) {
					case (int) kGraphics_font::TIMES:       { [attributes   setObject: @"Times New Roman"   forKey: (id) kCTFontNameAttribute]; } break;
					case (int) kGraphics_font::HELVETICA:   { [attributes   setObject: @"Arial"             forKey: (id) kCTFontNameAttribute]; } break;
					case (int) kGraphics_font::COURIER:     { [attributes   setObject: @"Courier New"       forKey: (id) kCTFontNameAttribute]; } break;
					case (int) kGraphics_font::PALATINO:    { if (Melder_debug == 900)
															[attributes   setObject: @"DG Meta Serif Science" forKey: (id) kCTFontNameAttribute];
													   else
														    [attributes   setObject: @"Palatino"              forKey: (id) kCTFontNameAttribute];
													 } break;
					case kGraphics_font_SYMBOL:      { [attributes   setObject: @"Symbol"            forKey: (id) kCTFontNameAttribute]; } break;
					case kGraphics_font_IPATIMES:    { [attributes   setObject: @"Doulos SIL"        forKey: (id) kCTFontNameAttribute]; } break;
					case kGraphics_font_IPAPALATINO: { [attributes   setObject: @"Charis SIL"        forKey: (id) kCTFontNameAttribute]; } break;
					case kGraphics_font_DINGBATS:    { [attributes   setObject: @"Zapf Dingbats"     forKey: (id) kCTFontNameAttribute]; } break;
				}
				CTFontDescriptorRef ctFontDescriptor = CTFontDescriptorCreateWithAttributes ((CFMutableDictionaryRef) attributes);
				[styleDict release];
				[attributes release];
			#endif
				ctFont = CTFontCreateWithFontDescriptor (ctFontDescriptor, lc -> size, nullptr);
				CFRelease (ctFontDescriptor);
				theScreenFonts [font] [lc -> size] [style] = ctFont;
			}

			const char16 *codes16 = Melder_peek32to16 (codes);
			#if 1
				CFStringRef s = CFStringCreateWithBytes (nullptr,
					(const UInt8 *) codes16, str16len (codes16) * 2,
					kCFStringEncodingUTF16LE, false);
				int length = CFStringGetLength (s);
			#else
				NSString *s = [[NSString alloc]   initWithBytes: codes16   length: str16len (codes16) * 2   encoding: NSUTF16LittleEndianStringEncoding];
				int length = [s length];
			#endif

			CGFloat descent = CTFontGetDescent (ctFont);

            CFMutableAttributedStringRef string = CFAttributedStringCreateMutable (kCFAllocatorDefault, length);
            CFAttributedStringReplaceString (string, CFRangeMake (0, 0), (CFStringRef) s);
            CFRange textRange = CFRangeMake (0, length);
            CFAttributedStringSetAttribute (string, textRange, kCTFontAttributeName, ctFont);

			static CFNumberRef cfKerning;
			if (! cfKerning) {
				double kerning = 0.0;
				cfKerning = CFNumberCreate (kCFAllocatorDefault, kCFNumberDoubleType, & kerning);
			}
            CFAttributedStringSetAttribute (string, textRange, kCTKernAttributeName, cfKerning);

			static CTParagraphStyleRef paragraphStyle;
			if (! paragraphStyle) {
				CTTextAlignment textAlignment = kCTLeftTextAlignment;
				CTParagraphStyleSetting paragraphSettings [1] = { { kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), & textAlignment } };
				paragraphStyle = CTParagraphStyleCreate (paragraphSettings, 1);
				Melder_assert (paragraphStyle != nullptr);
			}
            CFAttributedStringSetAttribute (string, textRange, kCTParagraphStyleAttributeName, paragraphStyle);

            RGBColor *macColor = lc -> link ? & theBlueColour : my duringXor ? & theWhiteColour : & my d_macColour;
            CGColorRef color = CGColorCreateGenericRGB (macColor->red / 65536.0, macColor->green / 65536.0, macColor->blue / 65536.0, 1.0);
			Melder_assert (color != nullptr);
            CFAttributedStringSetAttribute (string, textRange, kCTForegroundColorAttributeName, color);

            /*
             * Draw.
             */
    
            CGContextSetTextMatrix (my d_macGraphicsContext, CGAffineTransformIdentity);   // this could set the "current context" for CoreText
            CFRelease (color);

			if (my d_macView) {
				if (SUPPORT_DIRECT_DRAWING) {
					[my d_macView   lockFocus];
					my d_macGraphicsContext = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
				}
			}
            CGContextSaveGState (my d_macGraphicsContext);
            CGContextTranslateCTM (my d_macGraphicsContext, xDC, yDC);
            if (my yIsZeroAtTheTop) CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
            CGContextRotateCTM (my d_macGraphicsContext, my textRotation * NUMpi / 180.0);

			CTLineRef line = CTLineCreateWithAttributedString (string);
            if (my duringXor) {
                CGContextSetBlendMode (my d_macGraphicsContext, kCGBlendModeDifference);
                CGContextSetAllowsAntialiasing (my d_macGraphicsContext, false);
				CTLineDraw (line, my d_macGraphicsContext);
                CGContextSetBlendMode (my d_macGraphicsContext, kCGBlendModeNormal);
                CGContextSetAllowsAntialiasing (my d_macGraphicsContext, true);
            } else {
				CTLineDraw (line, my d_macGraphicsContext);
            }
			//CGContextFlush (my d_macGraphicsContext);
			CFRelease (line);
            CGContextRestoreGState (my d_macGraphicsContext);

            // Clean up
            CFRelease (string);
			CFRelease (s);
			//CFRelease (ctFont);
			if (my d_macView) {
				if (SUPPORT_DIRECT_DRAWING)
					[my d_macView   unlockFocus];
				if (! my duringXor) {
					//[my d_macView   setNeedsDisplay: YES];   // otherwise, CoreText text may not be drawn
				}
			}
			return;
		#endif
	}
}

static void initText (void *void_me) {
	iam (Graphics);
	if (my screen) {
		iam (GraphicsScreen);
		(void) me;
	}
}

static void exitText (void *void_me) {
	iam (Graphics);
	if (my screen) {
		iam (GraphicsScreen);
		(void) me;
	}
}

#define MAX_LINK_LENGTH  300

static integer bufferSize;
static _Graphics_widechar *theWidechar;
static char32 *charCodes;
static int initBuffer (conststring32 txt) {
	try {
		integer sizeNeeded = str32len (txt) + 1;
		if (sizeNeeded > bufferSize) {
			sizeNeeded += sizeNeeded / 2 + 100;
			Melder_free (theWidechar);
			Melder_free (charCodes);
			theWidechar = Melder_calloc (_Graphics_widechar, sizeNeeded);
			charCodes = Melder_calloc (char32, sizeNeeded);
			bufferSize = sizeNeeded;
		}
		return 1;
	} catch (MelderError) {
		bufferSize = 0;
		Melder_flushError ();
		return 0;
	}
}

static int numberOfLinks = 0;
static Graphics_Link links [100];    // a maximum of 100 links per string

static void charSizes (Graphics me, _Graphics_widechar string [], bool measureEachCharacterSeparately) {
	if (my postScript || (cairo && my duringXor)) {
		for (_Graphics_widechar *character = string; character -> kar > U'\t'; character ++)
			charSize (me, character);
	} else {
	/*
	 * Measure the size of each character.
	 */
	_Graphics_widechar *character;
	#if quartz || cairo
		#if cairo
			if (! ((GraphicsScreen) me) -> d_cairoGraphicsContext) return;
		#endif
		int numberOfDiacritics = 0;
		for (_Graphics_widechar *lc = string; lc -> kar > U'\t'; lc ++) {
			/*
			 * Determine the font family.
			 */
			Longchar_Info info = lc -> karInfo;
			Melder_assert (info);
			int font = chooseFont (me, lc);
			lc -> font.string = nullptr;   // this erases font.integer_!

			/*
			 * Determine the style.
			 */
			int style = lc -> style;
			Melder_assert (style >= 0 && style <= Graphics_BOLD_ITALIC);

			#if quartz
				/*
				 * Determine and store the font-style combination.
				 */
				CTFontRef ctFont = theScreenFonts [font] [100] [style];
				if (! ctFont) {
					CTFontSymbolicTraits ctStyle = ( style & Graphics_BOLD ? kCTFontBoldTrait : 0 ) | ( lc -> style & Graphics_ITALIC ? kCTFontItalicTrait : 0 );
					NSMutableDictionary *styleDict = [[NSMutableDictionary alloc] initWithCapacity: 1];
					[styleDict   setObject: [NSNumber numberWithUnsignedInt: ctStyle]   forKey: (id) kCTFontSymbolicTrait];
					NSMutableDictionary *attributes = [[NSMutableDictionary alloc] initWithCapacity: 2];
					[attributes   setObject: styleDict   forKey: (id) kCTFontTraitsAttribute];
					switch (font) {
						case (int) kGraphics_font::TIMES:       { [attributes   setObject: @"Times"           forKey: (id) kCTFontNameAttribute]; } break;
						case (int) kGraphics_font::HELVETICA:   { [attributes   setObject: @"Arial"           forKey: (id) kCTFontNameAttribute]; } break;
						case (int) kGraphics_font::COURIER:     { [attributes   setObject: @"Courier New"     forKey: (id) kCTFontNameAttribute]; } break;
						case (int) kGraphics_font::PALATINO:    { if (Melder_debug == 900)
																[attributes   setObject: @"DG Meta Serif Science" forKey: (id) kCTFontNameAttribute];
														   else
																[attributes   setObject: @"Palatino"              forKey: (id) kCTFontNameAttribute];
														 } break;
						case kGraphics_font_SYMBOL:      { [attributes   setObject: @"Symbol"          forKey: (id) kCTFontNameAttribute]; } break;
						case kGraphics_font_IPATIMES:    { [attributes   setObject: @"Doulos SIL"      forKey: (id) kCTFontNameAttribute]; } break;
						case kGraphics_font_IPAPALATINO: { [attributes   setObject: @"Charis SIL"      forKey: (id) kCTFontNameAttribute]; } break;
						case kGraphics_font_DINGBATS:    { [attributes   setObject: @"Zapf Dingbats"   forKey: (id) kCTFontNameAttribute]; } break;
					}
					CTFontDescriptorRef ctFontDescriptor = CTFontDescriptorCreateWithAttributes ((CFMutableDictionaryRef) attributes);
					[styleDict release];
					[attributes release];
					ctFont = CTFontCreateWithFontDescriptor (ctFontDescriptor, 100.0, nullptr);
					CFRelease (ctFontDescriptor);
					theScreenFonts [font] [100] [style] = ctFont;
				}
			#endif

			int normalSize = my fontSize * my resolution / 72.0;
			int smallSize = (3 * normalSize + 2) / 4;
			int size = lc -> size < 100 ? smallSize : normalSize;
			lc -> size = size;
			lc -> baseline *= 0.01 * normalSize;
			lc -> code = lc -> kar;
			lc -> font.integer_ = font;
			if (Longchar_Info_isDiacritic (info)) {
				numberOfDiacritics ++;
			}
		}
		int nchars = 0;
		for (_Graphics_widechar *lc = string; lc -> kar > U'\t'; lc ++) {
			charCodes [nchars ++] = lc -> code;
			_Graphics_widechar *next = lc + 1;
			lc -> width = 0;
			if (measureEachCharacterSeparately ||
				next->kar <= U' ' || next->style != lc->style ||
				next->baseline != lc->baseline || next->size != lc->size || next->link != lc->link ||
				next->font.integer_ != lc->font.integer_ || next->font.string != lc->font.string ||
				next->rightToLeft != lc->rightToLeft ||
				(my textRotation != 0.0 && my screen && my resolution > 150))
			{
				charCodes [nchars] = U'\0';
				#if cairo
					const char *codes8 = Melder_peek32to8 (charCodes);
					int length = strlen (codes8);
					PangoFontDescription *fontDescription = PangoFontDescription_create (lc -> font.integer_, lc);

					/*
						Measuring the width of a text with a homogeneous Praat font
						should still allow for Pango's font substitution.
						Low-level sequences such as `pango_itemize--pango_shape--pango_glyph_string_get_width`
						or `pango_itemize--pango_shape--pango_font_map_load_font--pango_glyph_string_extents`
						don't accomplish this: they seem to compute the width solely on the
						basis of the (perhaps substituted) font of the *first* glyph. By contrast, a PangoLayout
						performs font substitution when drawing with `pango_cairo_show_layout_line`,
						and also when measuring the width with `pango_layout_get_extents`.
						Fortunately, a PangoLayout is 1.5 to 2 times faster than the two low-level methods
						(measured 20170527).
					*/
					PangoLayout *layout = pango_cairo_create_layout (((GraphicsScreen) me) -> d_cairoGraphicsContext);
					pango_layout_set_font_description (layout, fontDescription);
					pango_layout_set_text (layout, codes8, -1);
					PangoRectangle inkRect, logicalRect;
					pango_layout_get_extents (layout, & inkRect, & logicalRect);
					lc -> width = logicalRect. width / PANGO_SCALE;
					Melder_assert (logicalRect.x == 0);
					g_object_unref (layout);
				#elif quartz
					const char16 *codes16 = Melder_peek32to16 (charCodes);
					int64 length = str16len (codes16);

					NSString *s = [[NSString alloc]
						initWithBytes: codes16
						length: (NSUInteger) (length * 2)
						encoding: NSUTF16LittleEndianStringEncoding   // BUG: should be NSUTF16NativeStringEncoding, except that that doesn't exist
						];

					CFRange textRange = CFRangeMake (0, (CFIndex) [s length]);

					CFMutableAttributedStringRef cfstring =
						CFAttributedStringCreateMutable (kCFAllocatorDefault, (CFIndex) [s length]);
					CFAttributedStringReplaceString (cfstring, CFRangeMake (0, 0), (CFStringRef) s);
					CFAttributedStringSetAttribute (cfstring, textRange, kCTFontAttributeName, theScreenFonts [lc -> font.integer_] [100] [lc -> style]);

					/*
					 * Measure.
					 */

					// Create a path to render text in
					CGMutablePathRef path = CGPathCreateMutable ();
					NSRect measureRect = NSMakeRect (0, 0, CGFLOAT_MAX, CGFLOAT_MAX);
					CGPathAddRect (path, nullptr, (CGRect) measureRect);
				
					CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString ((CFAttributedStringRef) cfstring);
					CFRange fitRange;
					CGSize targetSize = CGSizeMake (lc -> width, CGFLOAT_MAX);
					CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints (framesetter, textRange, nullptr, targetSize, & fitRange);
					CFRelease (framesetter);
					CFRelease (cfstring);
					[s release];
					CFRelease (path);
					//Longchar_Info info = lc -> karInfo;
					//bool isDiacritic = info -> ps.times == 0;
					//lc -> width = isDiacritic ? 0.0 : frameSize.width * lc -> size / 100.0;
					lc -> width = frameSize.width * lc -> size / 100.0;
					if (Melder_systemVersion >= 101100) {
						/*
						 * If the text ends in a space, CTFramesetterSuggestFrameSizeWithConstraints() ignores the space.
						 * we correct for this.
						 */
						if (codes16 [length - 1] == u' ') {
							lc -> width += 25.0 * lc -> size / 100.0;
						}
					}
				#endif
				nchars = 0;
			}
		}
	#else
		for (character = string; character -> kar > U'\t'; character ++)
			charSize (me, character);
	#endif
	}
	/*
	 * Each character has been garnished with information about the character's width.
	 * Make a correction for systems that make slanted characters overlap the character box to their right.
	 * We must do this after the previous loop, because we query the size of the *next* character.
	 *
	 * Keep this in SYNC with psTextWidth.
	 */
	for (_Graphics_widechar *character = string; character -> kar > U'\t'; character ++) {
		if ((character -> style & Graphics_ITALIC) != 0) {
			_Graphics_widechar *nextCharacter = character + 1;
			if (nextCharacter -> kar <= U'\t') {
				character -> width += SCREEN_SLANT_CORRECTION / 72 * my fontSize * my resolution;
			} else if (((nextCharacter -> style & Graphics_ITALIC) == 0 && nextCharacter -> baseline >= character -> baseline)
				|| (character -> baseline == 0 && nextCharacter -> baseline > 0))
			{
				if (nextCharacter -> kar == U'.' || nextCharacter -> kar == U',')
					character -> width += SCREEN_SLANT_CORRECTION / 144 * my fontSize * my resolution;
				else
					character -> width += SCREEN_SLANT_CORRECTION / 72 * my fontSize * my resolution;
			}
		}
	}
}

/*
 * The routine textWidth determines the fractional width of a text, in device coordinates.
 */
static double textWidth (_Graphics_widechar string []) {
	_Graphics_widechar *character;
	double width = 0;
	for (character = string; character -> kar > U'\t'; character ++)
		width += character -> width;
	return width;
}

static void drawOneCell (Graphics me, int xDC, int yDC, _Graphics_widechar lc []) {
	int nchars = 0;
	double width = textWidth (lc), dx, dy;
	/*
	 * We must continue even if width is zero (for adjusting textY).
	 */
	_Graphics_widechar *plc, *lastlc;
	bool inLink = false;
	switch (my horizontalTextAlignment) {
		case (int) Graphics_LEFT:      dx = 1 + (0.1/72.0) * my fontSize * my resolution; break;
		case (int) Graphics_CENTRE:    dx = - width / 2; break;
		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
		default:                 dx = 1 + (0.1/72.0) * my fontSize * my resolution; break;
	}
	switch (my verticalTextAlignment) {
		case Graphics_BOTTOM:    dy = (0.4/72.0) * my fontSize * my resolution; break;
		case Graphics_HALF:      dy = (-0.3/72.0) * my fontSize * my resolution; break;
		case Graphics_TOP:       dy = (-1.0/72.0) * my fontSize * my resolution; break;
		case Graphics_BASELINE:  dy = 0; break;
		default:                 dy = 0; break;
	}
	#if quartz
		if (my screen) {
			GraphicsQuartz_initDraw ((GraphicsScreen) me);
		}
	#endif
	if (my textRotation != 0.0) {
		double xbegin = dx, x = xbegin, cosa, sina;
		if (my textRotation == 90.0f) { cosa = 0.0; sina = 1.0; }
		else if (my textRotation == 270.0f) { cosa = 0.0; sina = -1.0; }
		else { double a = my textRotation * NUMpi / 180.0; cosa = cos (a); sina = sin (a); }
		for (plc = lc; plc -> kar > U'\t'; plc ++) {
			_Graphics_widechar *next = plc + 1;
			charCodes [nchars ++] = plc -> code;   // buffer...
			x += plc -> width;
			/*
			 * We can draw stretches of characters:
			 * they have different styles, baselines, sizes, or fonts,
			 * or if there is a break between them,
			 * or if we cannot rotate multiple characters,
			 * which is the case on bitmap printers.
			 */
			if (next->kar < U' ' || next->style != plc->style ||
				next->baseline != plc->baseline || next->size != plc->size ||
				next->font.integer_ != plc->font.integer_ || next->font.string != plc->font.string ||
				next->rightToLeft != plc->rightToLeft ||
				(my screen && my resolution > 150))
			{
				double dy2 = dy + plc -> baseline;
				double xr = cosa * xbegin - sina * dy2;
				double yr = sina * xbegin + cosa * dy2;
				charCodes [nchars] = U'\0';   // ...and flush
				charDraw (me, xDC + xr, my yIsZeroAtTheTop ? yDC - yr : yDC + yr,
					plc, charCodes, nchars, x - xbegin);
				nchars = 0;
				xbegin = x;
			}
		}
	} else {
		double xbegin = xDC + dx, x = xbegin, y = my yIsZeroAtTheTop ? yDC - dy : yDC + dy;
		lastlc = lc;
		if (my wrapWidth != 0.0) {
			/*
			 * Replace some spaces with new-line symbols.
			 */
			int xmax = xDC + my wrapWidth * my scaleX;
			for (plc = lc; plc -> kar >= U' '; plc ++) {
				x += plc -> width;
				if (x > xmax) {   // wrap (if wrapWidth is too small, each word will be on a separate line)
					while (plc >= lastlc) {
						if (plc -> kar == U' ' && ! plc -> link)   // keep links contiguous
							break;
						plc --;
					}
					if (plc <= lastlc) break;   // hopeless situation: no spaces; get over it
					lastlc = plc;
					plc -> kar = U'\n';   // replace space with newline
					#if quartz || cairo
						if (my screen) {
							/*
								This part is needed when using the non-`charSize()` variant of `charSizes()`,
								because otherwise you'll see extra spaces
								before the first font switch on each non-initial line.
							*/
							_Graphics_widechar *next = plc + 1;
							if (next->style != plc->style ||
								next->baseline != plc->baseline || next->size != plc->size || next->link != plc->link ||
								next->font.integer_ != plc->font.integer_ || next->font.string != plc->font.string ||
								next->rightToLeft != plc->rightToLeft)
							{
								// nothing
							} else {
								next -> width -= 0.25 * my fontSize * my resolution / 72.0;   // subtract the width of one space
							}
						}
					#endif
					x = xDC + dx + my secondIndent * my scaleX;
				}
			}
			xbegin = x = xDC + dx;   // re-initialize for second pass
		}
		for (plc = lc; plc -> kar > U'\t'; plc ++) {
			_Graphics_widechar *next = plc + 1;
			if (plc -> link) {
				if (! inLink) {
					double descent = ( my yIsZeroAtTheTop ? -(0.3/72) : (0.3/72) ) * my fontSize * my resolution;
					links [++ numberOfLinks]. x1 = x;
					links [numberOfLinks]. y1 = y - descent;
					links [numberOfLinks]. y2 = y + 3 * descent;
					inLink = true;
				}
			} else if (inLink) {
				links [numberOfLinks]. x2 = x;
				inLink = false;
			}
			if (plc -> kar == U'\n') {
				xbegin = x = xDC + dx + my secondIndent * my scaleX;
				y = my yIsZeroAtTheTop ? y + (1.2/72) * my fontSize * my resolution : y - (1.2/72) * my fontSize * my resolution;
			} else {
				charCodes [nchars ++] = plc -> code;   // buffer...
				x += plc -> width;
				if (next->kar < U' ' || next->style != plc->style ||
					next->baseline != plc->baseline || next->size != plc->size || next->link != plc->link ||
					next->font.integer_ != plc->font.integer_ || next->font.string != plc->font.string ||
					next->rightToLeft != plc->rightToLeft)
				{
					charCodes [nchars] = U'\0';   // ...and flush
					charDraw (me, xbegin, my yIsZeroAtTheTop ? y - plc -> baseline : y + plc -> baseline,
						plc, charCodes, nchars, x - xbegin);
					nchars = 0;
					xbegin = x;
				}
			}
		}
		if (inLink) {
			links [numberOfLinks]. x2 = x;
			inLink = false;
		}
		my textX = (x - my deltaX) / my scaleX;
		my textY = (( my yIsZeroAtTheTop ? y + dy : y - dy ) - my deltaY) / my scaleY;
	}
	#if quartz
		if (my screen) {
			GraphicsQuartz_exitDraw ((GraphicsScreen) me);
		}
	#endif
}

static struct { double width; kGraphics_horizontalAlignment alignment; } tabs [1 + 20] = { { 0, Graphics_CENTRE },
	{ 1, Graphics_CENTRE }, { 1, Graphics_CENTRE }, { 1, Graphics_CENTRE }, { 1, Graphics_CENTRE },
	{ 1, Graphics_CENTRE }, { 1, Graphics_CENTRE }, { 1, Graphics_CENTRE }, { 1, Graphics_CENTRE } };

/*
 * The routine 'drawCells' handles table and layout.
 */
static void drawCells (Graphics me, double xWC, double yWC, _Graphics_widechar lc []) {
	_Graphics_widechar *plc;
	int itab = 0, saveTextAlignment = my horizontalTextAlignment;
	double saveWrapWidth = my wrapWidth;
	numberOfLinks = 0;
	for (plc = lc; /* No stop condition. */ ; plc ++) {
		charSizes (me, plc, false);
		drawOneCell (me, xWC * my scaleX + my deltaX, yWC * my scaleY + my deltaY, plc);
		while (plc -> kar != U'\0' && plc -> kar != U'\t') plc ++;   // find end of cell
		if (plc -> kar == U'\0')   // end of text?
			break;
		if (plc -> kar == U'\t') {   // go to next cell
			xWC += ( tabs [itab]. alignment == Graphics_LEFT ? tabs [itab]. width :
				tabs [itab]. alignment == Graphics_CENTRE ? 0.5 * tabs [itab]. width : 0 ) * my fontSize / 12.0;
			itab ++;
			xWC += ( tabs [itab]. alignment == Graphics_LEFT ? 0 :
				tabs [itab]. alignment == Graphics_CENTRE ? 0.5 * tabs [itab]. width : tabs [itab]. width ) * my fontSize / 12.0;
			my horizontalTextAlignment = (int) tabs [itab]. alignment;
			my wrapWidth = tabs [itab]. width * my fontSize / 12.0;
		}
	}
	my horizontalTextAlignment = saveTextAlignment;
	my wrapWidth = saveWrapWidth;
}

static void parseTextIntoCellsLinesRuns (Graphics me, conststring32 txt /* cattable */, _Graphics_widechar a_widechar []) {
	char32 kar;
	const char32 *in = & txt [0];
	int nquote = 0;
	_Graphics_widechar *out = & a_widechar [0];
	bool charSuperscript = false, charSubscript = false, charItalic = false, charBold = false;
	bool wordItalic = false, wordBold = false, wordCode = false, wordLink = false;
	bool globalSuperscript = false, globalSubscript = false, globalItalic = false, globalBold = false, globalCode = false, globalLink = false;
	bool globalSmall = 0;
	numberOfLinks = 0;
	while ((kar = *in++) != U'\0') {
		if (kar == U'^' && my circumflexIsSuperscript) {
			if (globalSuperscript) globalSuperscript = 0;
			else if (in [0] == '^') { globalSuperscript = 1; in ++; }
			else charSuperscript = 1;
			wordItalic = wordBold = wordCode = false;
			continue;
		} else if (kar == U'_' && my underscoreIsSubscript) {
			if (globalSubscript) { globalSubscript = false; wordItalic = wordBold = wordCode = false; continue; }
			else if (in [0] == U'_') { globalSubscript = true; in ++; wordItalic = wordBold = wordCode = false; continue; }
			else if (! my dollarSignIsCode) { charSubscript = true; wordItalic = wordBold = wordCode = false; continue; }   // not in manuals
			else
				;   // a normal underscore in manuals
		} else if (kar == U'%' && my percentSignIsItalic) {
			if (globalItalic) globalItalic = false;
			else if (in [0] == U'%') { globalItalic = true; in ++; }
			else if (my dollarSignIsCode) wordItalic = true;   // in manuals
			else charItalic = true;
			continue;
		} else if (kar == U'#' && my numberSignIsBold) {
			if (globalBold) globalBold = false;
			else if (in [0] == U'#') { globalBold = true; in ++; }
			else if (my dollarSignIsCode) wordBold = true;   // in manuals
			else charBold = true;
			continue;
		} else if (kar == U'$' && my dollarSignIsCode) {
			if (globalCode) globalCode = false;
			else if (in [0] == U'$') { globalCode = true; in ++; }
			else wordCode = true;
			continue;
		} else if (kar == U'@' && my atSignIsLink   // recognize links
		           && my textRotation == 0.0)   // no links allowed in rotated text, because links are identified by 2-point rectangles
		{
			char32 *to, *max;
			/*
			 * We will distinguish:
			 * 1. The link text: the text shown to the user, drawn in blue.
			 * 2. The link info: the information saved in the Graphics object when the user clicks the link;
			 *    this may be a page title in a manual or any other information.
			 * The link info is equal to the link text in the following cases:
			 * 1. A single-word link: "this is a @Link that consists of one word".
			 * 2. Longer links without '|' in them: "@@Link with spaces@".
			 * The link info is unequal to the link text in the following case:
			 * 3. Longer links with '|' in them: "@@Page linked to|Text shown in blue@"
			 */
			if (globalLink) {
				/*
				 * Detected the third '@' in strings like "@@Link with spaces@".
				 * This closes the link text (which will be shown in blue).
				 */
				globalLink = false;   // close the drawn link text (the normal colour will take over)
				continue;   // the '@' must not be drawn
			} else if (in [0] == U'@') {
				/*
				 * Detected the second '@' in strings like "@@Link with spaces@".
				 * A format like "@@Page linked to|Text shown in blue@" is permitted.
				 * First step: collect the page text (the link information);
				 * it is everything between "@@" and "|" or "@" or end of string.
				 */
				const char32 *from = in + 1;   // start with first character after "@@"
				if (! links [++ numberOfLinks]. name)   // make room for saving link info
					links [numberOfLinks]. name = Melder_calloc_f (char32, MAX_LINK_LENGTH + 1);
				to = links [numberOfLinks]. name, max = to + MAX_LINK_LENGTH;
				while (*from && *from != U'@' && *from != U'|' && to < max)   // until end-of-string or '@' or '|'...
					* to ++ = * from ++;   // ... copy one character
				*to = U'\0';   // close saved link info
				/*
				 * Second step: collect the link text that is to be drawn.
				 * Its characters will be collected during the normal cycles of the loop.
				 * If the link info is equal to the link text, no action is needed.
				 * If, on the other hand, there is a separate link info, this will have to be skipped.
				 */
				if (*from == U'|')
					in += to - links [numberOfLinks]. name + 1;   // skip link info + '|'
				/*
				 * We are entering the link-text-collection mode.
				 */
				globalLink = true;
				/*
				 * Both '@' must be skipped and must not be drawn.
				 */
				in ++;   // skip second '@'
				continue;   // do not draw
			} else {
				/*
				 * Detected a single-word link, like in "this is a @Link that consists of one word".
				 * First step: collect the page text: letters, digits, and underscores.
				 */
				const char32 *from = in;   // start with first character after "@"
				if (! links [++ numberOfLinks]. name)   // make room for saving link info
					links [numberOfLinks]. name = Melder_calloc_f (char32, MAX_LINK_LENGTH + 1);
				to = links [numberOfLinks]. name;
				max = to + MAX_LINK_LENGTH;
				while (*from && (Melder_isWordCharacter (*from) || *from == U'_') && to < max)   // until end-of-word...
					*to ++ = *from++;   // ... copy one character
				*to = U'\0';   // close saved link info
				/*
				 * Second step: collect the link text that is to be drawn.
				 * Its characters will be collected during the normal cycles of the loop.
				 * The link info is equal to the link text, so no skipping is needed.
				 */
				wordLink = true;   // enter the single-word link-text-collection mode
			}
			continue;
		} else if (kar == U'\\') {
			/*
			 * Detected backslash sequence: backslash + kar1 + kar2...
			 */
			char32 kar1, kar2;
			/*
			 * ... except if kar1 or kar2 is null: in that case, draw the backslash.
			 */
			if (! (kar1 = in [0]) || ! (kar2 = in [1])) {
				;   // normal backslash symbol
			/*
			 * Catch "\s{", which means: small characters until corresponding '}'.
			 */
			} else if (kar2 == U'{') {
				if (kar1 == U's') globalSmall = true;
				in += 2;
				continue;
			/*
			 * Default action: translate the backslash sequence into the long character 'kar1,kar2'.
			 */
			} else {
				kar = Longchar_getInfo (kar1, kar2) -> unicode;
				in += 2;
			}
		} else if (kar == U'\"') {
			if (! (my font == kGraphics_font::COURIER || my fontStyle == Graphics_CODE || wordCode || globalCode))
				kar = ++nquote & 1 ? UNICODE_LEFT_DOUBLE_QUOTATION_MARK : UNICODE_RIGHT_DOUBLE_QUOTATION_MARK;
		} else if (kar == U'\'') {
			kar = UNICODE_RIGHT_SINGLE_QUOTATION_MARK;
		} else if (kar == U'`') {
			kar = UNICODE_LEFT_SINGLE_QUOTATION_MARK;
		} else if (kar >= 32 && kar <= 126) {
			if (kar == U'f') {
				if (in [0] == U'i' && HAS_FI_AND_FL_LIGATURES && ! (my font == kGraphics_font::COURIER || my fontStyle == Graphics_CODE || wordCode || globalCode)) {
					kar = UNICODE_LATIN_SMALL_LIGATURE_FI;
					in ++;
				} else if (in [0] == U'l' && HAS_FI_AND_FL_LIGATURES && ! (my font == kGraphics_font::COURIER || my fontStyle == Graphics_CODE || wordCode || globalCode)) {
					kar = UNICODE_LATIN_SMALL_LIGATURE_FL;
					in ++;
				}
			} else if (kar == U'}') {
				if (globalSmall) { globalSmall = 0; continue; }
			}
		} else if (kar == U'\t') {
			out -> kar = U'\t';
			out -> rightToLeft = false;
			wordItalic = wordBold = wordCode = wordLink = false;
			globalSubscript = globalSuperscript = globalItalic = globalBold = globalCode = globalLink = globalSmall = false;
			charItalic = charBold = charSuperscript = charSubscript = false;
			out ++;
			continue;   // do not draw
		} else if (kar == U'\n') {
			kar = U' ';
		}
		if (wordItalic | wordBold | wordCode | wordLink) {
			if (! Melder_isWordCharacter (kar) && kar != U'_')
				wordItalic = wordBold = wordCode = wordLink = false;
		}
		out -> style =
			(wordLink | globalLink) && my fontStyle != Graphics_CODE ? Graphics_BOLD :
			((my fontStyle & Graphics_ITALIC) | charItalic | wordItalic | globalItalic ? Graphics_ITALIC : 0) +
			((my fontStyle & Graphics_BOLD) | charBold | wordBold | globalBold ? Graphics_BOLD : 0);
		out -> font.string = nullptr;
		out -> font.integer_ = my fontStyle == Graphics_CODE || wordCode || globalCode ||
			kar == U'/' || kar == U'|' ? (int) kGraphics_font::COURIER : (int) my font;
		out -> link = wordLink | globalLink;
		out -> baseline = charSuperscript | globalSuperscript ? 34 : charSubscript | globalSubscript ? -25 : 0;
		out -> size = globalSmall || out -> baseline != 0 ? 80 : 100;
		if (kar == U'/') {
			out -> baseline -= out -> size / 12;
			out -> size += out -> size / 10;
			if (my screen) out -> font.integer_ = (int) kGraphics_font::PALATINO;
		}
		out -> code = U'?';   // does this have any meaning?
		Melder_assert (kar != U'\0');
		out -> kar = kar;
		out -> karInfo = Longchar_getInfoFromNative (kar);
		Melder_assert (out -> karInfo);
		out -> rightToLeft =
			(kar >= 0x0590 && kar <= 0x06FF) ||
			(kar >= 0xFE70 && kar <= 0xFEFF) ||
			(kar >= 0xFB1E && kar <= 0xFDFF);
		charItalic = charBold = charSuperscript = charSubscript = false;
		out ++;
	}
	out -> kar = U'\0';   // end of text
	out -> karInfo = Longchar_getInfoFromNative (kar);
	Melder_assert (out -> karInfo);
	out -> rightToLeft = false;
}

double Graphics_textWidth (Graphics me, conststring32 txt) {
	if (! initBuffer (txt)) return 0.0;
	initText (me);
	parseTextIntoCellsLinesRuns (me, txt, theWidechar);
	charSizes (me, theWidechar, false);
	double width = textWidth (theWidechar);
	exitText (me);
	return width / my scaleX;
}

void Graphics_textRect (Graphics me, double x1, double x2, double y1, double y2, conststring32 txt) {
	_Graphics_widechar *plc, *startOfLine;
	double width = 0.0, lineHeight = (1.1 / 72) * my fontSize * my resolution;
	integer x1DC = x1 * my scaleX + my deltaX + 2, x2DC = x2 * my scaleX + my deltaX - 2;
	integer y1DC = y1 * my scaleY + my deltaY, y2DC = y2 * my scaleY + my deltaY;
	int availableHeight = my yIsZeroAtTheTop ? y1DC - y2DC : y2DC - y1DC, availableWidth = x2DC - x1DC;
	int linesAvailable = availableHeight / lineHeight, linesNeeded = 1, lines, iline;
	if (linesAvailable <= 0) linesAvailable = 1;
	if (availableWidth <= 0) return;
	if (! initBuffer (txt)) return;
	initText (me);
	parseTextIntoCellsLinesRuns (me, txt, theWidechar);
	charSizes (me, theWidechar, true);
	for (plc = theWidechar; plc -> kar > U'\t'; plc ++) {
		width += plc -> width;
		if (width > availableWidth) {
			if (++ linesNeeded > linesAvailable) break;
			width = 0.0;
		}	
	}
	lines = linesNeeded > linesAvailable ? linesAvailable : linesNeeded;
	startOfLine = theWidechar;
	for (iline = 1; iline <= lines; iline ++) {
		width = 0.0;
		for (plc = startOfLine; plc -> kar > U'\t'; plc ++) {
			bool flush = false;
			width += plc -> width;
			if (width > availableWidth) flush = true;
			/*
			 * Trick for incorporating end-of-text.
			 */
			if (! flush && plc [1]. kar <= U'\t') {
				Melder_assert (iline == lines);
				plc ++;   // brr
				flush = true;
			}
			if (flush) {
				char32 saveKar = plc -> kar;
				int direction = my yIsZeroAtTheTop ? -1 : 1;
				int x = my horizontalTextAlignment == (int) Graphics_LEFT ? x1DC :
					my horizontalTextAlignment == (int) Graphics_RIGHT ? x2DC :
					0.5 * (x1 + x2) * my scaleX + my deltaX;
				int y = my verticalTextAlignment == Graphics_BOTTOM ?
					y1DC + direction * (lines - iline) * lineHeight :
					my verticalTextAlignment == Graphics_TOP ?
					y2DC - direction * (iline - 1) * lineHeight :
					0.5 * (y1 + y2) * my scaleY + my deltaY + 0.5 * direction * (lines - iline*2 + 1) * lineHeight;
				plc -> kar = U'\0';
				drawOneCell (me, x, y, startOfLine);
				plc -> kar = saveKar;
				startOfLine = plc;
				break;
			}
		}
	}
	exitText (me);
}

void Graphics_text (Graphics me, double xWC, double yWC, conststring32 txt) {
	if (my wrapWidth == 0.0 && str32chr (txt, U'\n') && my textRotation == 0.0) {
		double lineSpacingWC = (1.2/72.0) * my fontSize * my resolution / fabs (my scaleY);
		integer numberOfLines = 1;
		for (const char32 *p = & txt [0]; *p != U'\0'; p ++) {
			if (*p == U'\n') {
				numberOfLines ++;
			}
		}
		yWC +=
			my verticalTextAlignment == Graphics_TOP ? 0.0 :
			my verticalTextAlignment == Graphics_HALF ? 0.5 * (numberOfLines - 1) * lineSpacingWC:
			(numberOfLines - 1) * lineSpacingWC;
		autostring32 linesToDraw = Melder_dup_f (txt);
		char32 *p = & linesToDraw [0];
		for (;;) {
			char32 *newline = str32chr (p, U'\n');
			if (newline) *newline = U'\0';
			Graphics_text (me, xWC, yWC, p);
			yWC -= lineSpacingWC;
			if (newline) {
				p = newline + 1;
			} else {
				break;
			}
		}
		return;
	}
	if (! initBuffer (txt)) return;
	initText (me);
	parseTextIntoCellsLinesRuns (me, txt, theWidechar);
	drawCells (me, xWC, yWC, theWidechar);
	exitText (me);
	if (my recording) {
		conststring8 txt_utf8 = Melder_peek32to8 (txt);
		int length = strlen (txt_utf8) / sizeof (double) + 1;
		op (TEXT, 3 + length); put (xWC); put (yWC); sput (txt_utf8, length)
	}
}

double Graphics_inqTextX (Graphics me) { return my textX; }
double Graphics_inqTextY (Graphics me) { return my textY; }

int Graphics_getLinks (Graphics_Link **plinks) { *plinks = & links [0]; return numberOfLinks; }

static double psTextWidth (_Graphics_widechar string [], bool useSilipaPS) {
	/*
	 * The following has to be kept IN SYNC with GraphicsPostscript::charSize.
	 */
	double textWidth = 0;
	for (_Graphics_widechar *character = & string [0]; character -> kar > U'\t'; character ++) {
		Longchar_Info info = character -> karInfo;
		int font = info -> alphabet == Longchar_SYMBOL ? kGraphics_font_SYMBOL :
				info -> alphabet == Longchar_PHONETIC ? kGraphics_font_IPATIMES :
				info -> alphabet == Longchar_DINGBATS ? kGraphics_font_DINGBATS : character -> font.integer_;
		int style = character -> style == Graphics_ITALIC ? Graphics_ITALIC :
			character -> style == Graphics_BOLD || character -> link ? Graphics_BOLD :
			character -> style == Graphics_BOLD_ITALIC ? Graphics_BOLD_ITALIC : 0;
		double size = character -> size * 0.01;
		double charWidth = 600;   // Courier
		if (font == (int) kGraphics_font::COURIER) {
			charWidth = 600;
		} else if (style == 0) {
			if (font == (int) kGraphics_font::TIMES) charWidth = info -> ps.times;
			else if (font == (int) kGraphics_font::HELVETICA) charWidth = info -> ps.helvetica;
			else if (font == (int) kGraphics_font::PALATINO) charWidth = info -> ps.palatino;
			else if (font == kGraphics_font_IPATIMES && useSilipaPS) charWidth = info -> ps.timesItalic;
			else charWidth = info -> ps.times;   // Symbol, IPA
		} else if (style == Graphics_BOLD) {
			if (font == (int) kGraphics_font::TIMES) charWidth = info -> ps.timesBold;
			else if (font == (int) kGraphics_font::HELVETICA) charWidth = info -> ps.helveticaBold;
			else if (font == (int) kGraphics_font::PALATINO) charWidth = info -> ps.palatinoBold;
			else if (font == kGraphics_font_IPATIMES && useSilipaPS) charWidth = info -> ps.timesBoldItalic;
			else charWidth = info -> ps.times;
		} else if (style == Graphics_ITALIC) {
			if (font == (int) kGraphics_font::TIMES) charWidth = info -> ps.timesItalic;
			else if (font == (int) kGraphics_font::HELVETICA) charWidth = info -> ps.helvetica;
			else if (font == (int) kGraphics_font::PALATINO) charWidth = info -> ps.palatinoItalic;
			else if (font == kGraphics_font_IPATIMES && useSilipaPS) charWidth = info -> ps.timesItalic;
			else charWidth = info -> ps.times;
		} else if (style == Graphics_BOLD_ITALIC) {
			if (font == (int) kGraphics_font::TIMES) charWidth = info -> ps.timesBoldItalic;
			else if (font == (int) kGraphics_font::HELVETICA) charWidth = info -> ps.helveticaBold;
			else if (font == (int) kGraphics_font::PALATINO) charWidth = info -> ps.palatinoBoldItalic;
			else if (font == kGraphics_font_IPATIMES && useSilipaPS) charWidth = info -> ps.timesBoldItalic;
			else charWidth = info -> ps.times;
		}
		charWidth *= size / 1000.0;
		textWidth += charWidth;
	}
	/*
	 * The following has to be kept IN SYNC with charSizes ().
	 */
	for (_Graphics_widechar *character = & string [0]; character -> kar > U'\t'; character ++) {
		if ((character -> style & Graphics_ITALIC) != 0) {
			_Graphics_widechar *nextCharacter = character + 1;
			if (nextCharacter -> kar <= '\t') {
				textWidth += POSTSCRIPT_SLANT_CORRECTION;
			} else if (((nextCharacter -> style & Graphics_ITALIC) == 0 && nextCharacter -> baseline >= character -> baseline)
				|| (character -> baseline == 0 && nextCharacter -> baseline > 0))
			{
				if (nextCharacter -> kar == U'.' || nextCharacter -> kar == U',')
					textWidth += 0.5 * POSTSCRIPT_SLANT_CORRECTION;
				else
					textWidth += POSTSCRIPT_SLANT_CORRECTION;
			}
		}
	}
	return textWidth;
}

double Graphics_textWidth_ps_mm (Graphics me, conststring32 txt, bool useSilipaPS) {
	if (! initBuffer (txt)) return 0.0;
	parseTextIntoCellsLinesRuns (me, txt, theWidechar);
	return psTextWidth (theWidechar, useSilipaPS) * (double) my fontSize * (25.4 / 72.0);
}

double Graphics_textWidth_ps (Graphics me, conststring32 txt, bool useSilipaPS) {
	return Graphics_dxMMtoWC (me, Graphics_textWidth_ps_mm (me, txt, useSilipaPS));
}

#if quartz
	bool _GraphicsMac_tryToInitializeFonts () {
		static bool inited = false;
		if (inited) return true;
		NSArray *fontNames = [[NSFontManager sharedFontManager] availableFontFamilies];
		hasTimes = [fontNames containsObject: @"Times"];
		if (! hasTimes) hasTimes = [fontNames containsObject: @"Times New Roman"];
		hasHelvetica = [fontNames containsObject: @"Helvetica"];
		if (! hasHelvetica) hasHelvetica = [fontNames containsObject: @"Arial"];
		hasCourier = [fontNames containsObject: @"Courier"];
		if (! hasCourier) hasCourier = [fontNames containsObject: @"Courier New"];
		hasSymbol = [fontNames containsObject: @"Symbol"];
		hasPalatino = [fontNames containsObject: @"Palatino"];
		if (! hasPalatino) hasPalatino = [fontNames containsObject: @"Book Antiqua"];
		hasDoulos = [fontNames containsObject: @"Doulos SIL"];
		hasCharis = [fontNames containsObject: @"Charis SIL"];
		hasIpaSerif = hasDoulos || hasCharis;
		inited = true;
		return true;
	}
#endif

#if cairo
	static const char *testFont (const char *fontName) {
		PangoFontDescription *pangoFontDescription, *pangoFontDescription2;
		PangoFont *pangoFont;
		pangoFontDescription = pango_font_description_from_string (fontName);
		pangoFont = pango_font_map_load_font (thePangoFontMap, thePangoContext, pangoFontDescription);
		pangoFontDescription2 = pango_font_describe (pangoFont);
		return pango_font_description_get_family (pangoFontDescription2);
	}
	bool _GraphicsLin_tryToInitializeFonts () {
		static bool inited = false;
		if (inited) return true;
		thePangoFontMap = pango_cairo_font_map_get_default ();
		thePangoContext = pango_font_map_create_context (thePangoFontMap);
		#if 0   /* For debugging: list all fonts. */
			PangoFontFamily **families;
			int numberOfFamilies;
			pango_font_map_list_families (thePangoFontMap, & families, & numberOfFamilies);
			for (int i = 0; i < numberOfFamilies; i ++) {
				fprintf (stderr, "%d %s\n", i, pango_font_family_get_name (families [i]));
			}
			g_free (families);
		#endif
		const char *trueName;
		trueName = testFont ("Times");
		hasTimes = !! strstr (trueName, "Times") || !! strstr (trueName, "Roman") || !! strstr (trueName, "Serif");
		trueName = testFont ("Helvetica");
		hasHelvetica = !! strstr (trueName, "Helvetica") || !! strstr (trueName, "Arial") || !! strstr (trueName, "Sans");
		trueName = testFont ("Courier");
		hasCourier = !! strstr (trueName, "Courier") || !! strstr (trueName, "Mono");
		trueName = testFont ("Palatino");
		hasPalatino = !! strstr (trueName, "Palatino") || !! strstr (trueName, "Palladio");
		trueName = testFont ("Doulos SIL");
		hasDoulos = !! strstr (trueName, "Doulos");
		trueName = testFont ("Charis SIL");
		hasCharis = !! strstr (trueName, "Charis");
		hasIpaSerif = hasDoulos || hasCharis;
		testFont ("Symbol");
		testFont ("Dingbats");
		#if 0   /* For debugging: list font availability. */
			fprintf (stderr, "times %d helvetica %d courier %d palatino %d doulos %d charis %d\n",
				hasTimes, hasHelvetica, hasCourier, hasPalatino, hasDoulos, hasCharis);
		#endif
		inited = true;
		return true;
	}
#endif

void _GraphicsScreen_text_init (GraphicsScreen me) {   // BUG: should be done as late as possible
	#if cairo
        (void) me;
		Melder_assert (_GraphicsLin_tryToInitializeFonts ());
	#elif gdi
		int font, size, style;
		if (my printer || my metafile)
			for (font = (int) kGraphics_font::MIN; font <= kGraphics_font_DINGBATS; font ++)
				for (size = 0; size <= 4; size ++)
					for (style = 0; style <= Graphics_BOLD_ITALIC; style ++)
						if (fonts [(int) my resolutionNumber] [font] [size] [style]) {
							//DeleteObject (fonts [my resolutionNumber] [font] [size] [style]);
							//fonts [my resolutionNumber] [font] [size] [style] = 0;
						}
	#elif quartz
        (void) me;
        Melder_assert (_GraphicsMac_tryToInitializeFonts ());   // should have been handled when setting my useQuartz to true
	#endif
}

/* Output attributes. */

void Graphics_setTextAlignment (Graphics me, kGraphics_horizontalAlignment hor, int vert) {
	if ((int) hor != Graphics_NOCHANGE) my horizontalTextAlignment = (int) hor;
	if (vert != Graphics_NOCHANGE) my verticalTextAlignment = vert;
	if (my recording) { op (SET_TEXT_ALIGNMENT, 2); put (hor); put (vert); }
}

void Graphics_setFont (Graphics me, enum kGraphics_font font) {
	my font = font;
	if (my recording) { op (SET_FONT, 1); put (font); }
}

void Graphics_setFontSize (Graphics me, double size) {
	my fontSize = size;
	if (my recording) { op (SET_FONT_SIZE, 1); put (size); }
}

void Graphics_setFontStyle (Graphics me, int style) {
	my fontStyle = style;
	if (my recording) { op (SET_FONT_STYLE, 1); put (style); }
}

void Graphics_setItalic (Graphics me, bool onoff) {
	if (onoff) my fontStyle |= Graphics_ITALIC; else my fontStyle &= ~ Graphics_ITALIC;
}

void Graphics_setBold (Graphics me, bool onoff) {
	if (onoff) my fontStyle |= Graphics_BOLD; else my fontStyle &= ~ Graphics_BOLD;
}

void Graphics_setCode (Graphics me, bool onoff) {
	if (onoff) my fontStyle |= Graphics_CODE; else my fontStyle &= ~ Graphics_CODE;
}

void Graphics_setTextRotation (Graphics me, double angle) {
	my textRotation = angle;
	if (my recording) { op (SET_TEXT_ROTATION, 1); put (angle); }
}

void Graphics_setWrapWidth (Graphics me, double wrapWidth) {
	my wrapWidth = wrapWidth;
	if (my recording) { op (SET_WRAP_WIDTH, 1); put (wrapWidth); }
}

void Graphics_setSecondIndent (Graphics me, double indent) {
	my secondIndent = indent;
	if (my recording) { op (SET_SECOND_INDENT, 1); put (indent); }
}

void Graphics_setPercentSignIsItalic (Graphics me, bool isItalic) {
	my percentSignIsItalic = isItalic;
	if (my recording) { op (SET_PERCENT_SIGN_IS_ITALIC, 1); put (isItalic); }
}

void Graphics_setNumberSignIsBold (Graphics me, bool isBold) {
	my numberSignIsBold = isBold;
	if (my recording) { op (SET_NUMBER_SIGN_IS_BOLD, 1); put (isBold); }
}

void Graphics_setCircumflexIsSuperscript (Graphics me, bool isSuperscript) {
	my circumflexIsSuperscript = isSuperscript;
	if (my recording) { op (SET_CIRCUMFLEX_IS_SUPERSCRIPT, 1); put (isSuperscript); }
}

void Graphics_setUnderscoreIsSubscript (Graphics me, bool isSubscript) {
	my underscoreIsSubscript = isSubscript;
	if (my recording) { op (SET_UNDERSCORE_IS_SUBSCRIPT, 1); put (isSubscript); }
}

void Graphics_setDollarSignIsCode (Graphics me, bool isCode) {
	my dollarSignIsCode = isCode;
	if (my recording) { op (SET_DOLLAR_SIGN_IS_CODE, 1); put (isCode); }
}

void Graphics_setAtSignIsLink (Graphics me, bool isLink) {
	my atSignIsLink = isLink;
	if (my recording) { op (SET_AT_SIGN_IS_LINK, 1); put (isLink); }
}

/* Inquiries. */

enum kGraphics_font Graphics_inqFont (Graphics me) { return my font; }
double Graphics_inqFontSize (Graphics me) { return my fontSize; }
int Graphics_inqFontStyle (Graphics me) { return my fontStyle; }

/* End of file Graphics_text.cpp */
