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