Graphics_text.cpp 79 KB

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