1 /* Dia -- an diagram creation/manipulation program
2  * Copyright (C) 1998 Alexander Larsson
3  * Font code completely reworked for the Pango conversion
4  * Copyright (C) 2002 Cyrille Chepelov
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h> /* strlen */
28 #include <time.h>
29 
30 #include <pango/pango.h>
31 #ifdef HAVE_FREETYPE
32 #include <pango/pangoft2.h>
33 #endif
34 #include <gdk/gdk.h>
35 #include <gtk/gtk.h> /* just for gtk_get_default_language() */
36 #ifdef GDK_WINDOWING_WIN32
37 /* avoid namespace clashes caused by inclusion of windows.h */
38 #define Rectangle Win32Rectangle
39 #define WIN32_LEAN_AND_MEAN
40 #include <pango/pangowin32.h>
41 #undef Rectangle
42 #endif
43 #include "font.h"
44 #include "message.h"
45 #include "intl.h"
46 #include "textline.h"
47 
48 static PangoContext* pango_context = NULL;
49 
50 struct _DiaFont
51 {
52   GObject parent_instance;
53 
54   PangoFontDescription* pfd;
55   /* mutable */ char* legacy_name;
56 
57   /* there is a difference between Pango's font size and Dia's font height */
58   /* Calculated  font_size is to make 'font_height = ascent + descent */
59   /* The font_height is used as default line height, there used to be a hard-coded size = 0.7 * height  */
60   /* before using pango_set_absolute_size() to overcome font size differences between renderers */
61   real height;
62   /* Need to load a font to query it's metrics */
63   PangoFont *loaded;
64   PangoFontMetrics *metrics;
65 };
66 
67 
68 /* This is the global factor that says what zoom factor is 100%.  It's
69  * normally 20.0 (and likely to stay that way).  It is defined by how many
70  * pixels one cm is represented as.
71  */
72 static real global_zoom_factor = 20.0;
73 
74 static void
dia_font_check_for_font(int font)75 dia_font_check_for_font(int font)
76 {
77   DiaFont *check;
78   PangoFont *loaded;
79   static real height = 1.0;
80 
81   check = dia_font_new_from_style(font, height);
82   loaded = pango_context_load_font(dia_font_get_context(), check->pfd);
83   if (!loaded) {
84     message_error(_("Can't load font %s.\n"), dia_font_get_family(check));
85   } else {
86     g_object_unref(loaded);
87   }
88   dia_font_unref(check);
89 }
90 
91 void
dia_font_init(PangoContext * pcontext)92 dia_font_init(PangoContext* pcontext)
93 {
94   pango_context = pcontext;
95   /* We must have these three fonts! */
96   dia_font_check_for_font(DIA_FONT_SANS);
97   dia_font_check_for_font(DIA_FONT_SERIF);
98   dia_font_check_for_font(DIA_FONT_MONOSPACE);
99 }
100 
101 /* We might not need these anymore, when using FT2/Win32 fonts only */
102 static GList *pango_contexts = NULL;
103 
104 /*! DEPRECATED: there should be only one "measure context" */
105 void
dia_font_push_context(PangoContext * pcontext)106 dia_font_push_context(PangoContext *pcontext)
107 {
108   pango_contexts = g_list_prepend(pango_contexts, pango_context);
109   pango_context = pcontext;
110   pango_context_set_language (pango_context, gtk_get_default_language ());
111   g_object_ref(pcontext);
112 }
113 
114 /*! DEPRECATED: there should be only one "measure context" */
115 void
dia_font_pop_context()116 dia_font_pop_context() {
117   g_object_unref(pango_context);
118   pango_context = (PangoContext*)pango_contexts->data;
119   pango_context_set_language (pango_context, gtk_get_default_language ());
120   pango_contexts = g_list_next(pango_contexts);
121 }
122 
123 PangoContext *
dia_font_get_context()124 dia_font_get_context()
125 {
126   if (pango_context == NULL) {
127 /* Maybe this one with pangocairo
128      dia_font_push_context (pango_cairo_font_map_create_context (pango_cairo_font_map_get_default()));
129  * but it gives:
130      (lt-dia:30476): Pango-CRITICAL **: pango_renderer_draw_layout: assertion `PANGO_IS_RENDERER (renderer)' failed
131  */
132 #ifdef HAVE_FREETYPE
133     /* This is suggested by new Pango (1.2.4+), but doesn't get us the
134      * right resolution:(
135      dia_font_push_context(pango_ft2_font_map_create_context(pango_ft2_font_map_new()));
136      */
137     /* with 96x96 it gives consistent - too big - sizes with the cairo renderer, it was 75x75 with 0.96.x
138     dia_font_push_context(pango_ft2_get_context(96,96));
139     */
140     dia_font_push_context(pango_ft2_get_context(75,75));
141 #else
142     if (gdk_display_get_default ())
143       dia_font_push_context(gdk_pango_context_get());
144     else {
145 #  ifdef GDK_WINDOWING_WIN32
146       dia_font_push_context(pango_win32_get_context ());
147 #  else
148       g_warning ("dia_font_get_context() : not font context w/o display. Crashing soon.");
149 #  endif
150     }
151 #endif
152   }
153   return pango_context;
154 }
155 
156     /* dia centimetres to pango device units */
157 static gint
dcm_to_pdu(real dcm)158 dcm_to_pdu(real dcm) { return dcm * global_zoom_factor * PANGO_SCALE; }
159     /* pango device units to dia centimetres */
160 static real
pdu_to_dcm(gint pdu)161 pdu_to_dcm(gint pdu) { return (real)pdu / (global_zoom_factor * PANGO_SCALE); }
162 
163 static void dia_font_class_init(DiaFontClass* class);
164 static void dia_font_finalize(GObject* object);
165 static void dia_font_init_instance(DiaFont*);
166 
167 GType
dia_font_get_type(void)168 dia_font_get_type (void)
169 {
170     static GType object_type = 0;
171 
172     if (!object_type) {
173         static const GTypeInfo object_info =
174             {
175                 sizeof (DiaFontClass),
176                 (GBaseInitFunc) NULL,
177                 (GBaseFinalizeFunc) NULL,
178                 (GClassInitFunc) dia_font_class_init, /* class_init */
179                 NULL,           /* class_finalize */
180                 NULL,           /* class_data */
181                 sizeof (DiaFont),
182                 0,              /* n_preallocs */
183                 (GInstanceInitFunc)dia_font_init_instance
184             };
185         object_type = g_type_register_static (G_TYPE_OBJECT,
186                                               "DiaFont",
187                                               &object_info, 0);
188     }
189     return object_type;
190 }
191 static gpointer parent_class;
192 
193 static void
dia_font_class_init(DiaFontClass * klass)194 dia_font_class_init(DiaFontClass* klass)
195 {
196   GObjectClass* object_class = G_OBJECT_CLASS(klass);
197   parent_class = g_type_class_peek_parent(klass);
198   object_class->finalize = dia_font_finalize;
199 }
200 
201 static void
dia_font_init_instance(DiaFont * font)202 dia_font_init_instance(DiaFont* font)
203 {
204         /*GObject *gobject = G_OBJECT(font);  */
205 }
206 
207 static void
dia_pfd_set_height(PangoFontDescription * pfd,real height)208 dia_pfd_set_height(PangoFontDescription* pfd, real height)
209 {
210   /* ONLY place for the magic factor! */
211   pango_font_description_set_absolute_size(pfd, dcm_to_pdu(height) * 0.8);
212 }
213 
214 /*!
215  * In Dia a font is usually referred to by it's (line-) height, not it's size.
216  *
217  * This methods "calculates" the latter from the former. This used to be some magic factor of 0.7 which did not
218  * solve the resolution dependencance of the former calculation. In fact there is new magic factor now because
219  * really calculating the font size from the height would involve two font loads which seem to be two expensive.
220  */
221 static void
_dia_font_adjust_size(DiaFont * font,real height,gboolean recalc_alwways)222 _dia_font_adjust_size (DiaFont *font, real height, gboolean recalc_alwways)
223 {
224 
225   if (font->height != height || !font->metrics || recalc_alwways) {
226     PangoFont *loaded;
227 
228     dia_pfd_set_height (font->pfd, height);
229     /* need to load a font to get it's metrics */
230     loaded = font->loaded;
231     font->loaded = pango_context_load_font(dia_font_get_context(), font->pfd);
232     if (loaded) /* drop old reference */
233       g_object_unref (loaded);
234     if (font->metrics)
235       pango_font_metrics_unref (font->metrics);
236 
237     /* caching metrics */
238     font->metrics = pango_font_get_metrics (font->loaded, NULL);
239     font->height = height;
240   }
241 }
242 DiaFont*
dia_font_new(const char * family,DiaFontStyle style,real height)243 dia_font_new(const char *family, DiaFontStyle style, real height)
244 {
245   DiaFont* font = dia_font_new_from_style(style, height);
246   gboolean changed;
247 
248   changed = family != NULL && strcmp (pango_font_description_get_family(font->pfd), family) != 0;
249   pango_font_description_set_family(font->pfd, family);
250 
251   if (changed)
252     _dia_font_adjust_size (font, font->height, TRUE);
253 
254   return font;
255 }
256 
257 static void
dia_pfd_set_family(PangoFontDescription * pfd,DiaFontFamily fam)258 dia_pfd_set_family(PangoFontDescription* pfd, DiaFontFamily fam)
259 {
260   switch (fam) {
261   case DIA_FONT_SANS :
262     pango_font_description_set_family(pfd, "sans");
263     break;
264   case DIA_FONT_SERIF :
265     pango_font_description_set_family(pfd, "serif");
266     break;
267   case DIA_FONT_MONOSPACE :
268     pango_font_description_set_family(pfd, "monospace");
269     break;
270   default :
271     /* Pango does _not_ allow fonts without a name (or at least they are not useful) */
272     pango_font_description_set_family(pfd, "sans");
273     break;
274   }
275 }
276 
277 static void
dia_pfd_set_weight(PangoFontDescription * pfd,DiaFontWeight fw)278 dia_pfd_set_weight(PangoFontDescription* pfd, DiaFontWeight fw)
279 {
280   switch (fw) {
281   case DIA_FONT_ULTRALIGHT :
282     pango_font_description_set_weight(pfd, PANGO_WEIGHT_ULTRALIGHT);
283     break;
284   case DIA_FONT_LIGHT :
285     pango_font_description_set_weight(pfd, PANGO_WEIGHT_LIGHT);
286     break;
287   case DIA_FONT_WEIGHT_NORMAL :
288     pango_font_description_set_weight(pfd, PANGO_WEIGHT_NORMAL);
289     break;
290   case DIA_FONT_MEDIUM : /* Pango doesn't have this, but
291                             'intermediate values are possible' */
292     pango_font_description_set_weight(pfd, 500);
293     break;
294   case DIA_FONT_DEMIBOLD : /* Pango doesn't have this, ... */
295     pango_font_description_set_weight(pfd, 600);
296     break;
297   case DIA_FONT_BOLD :
298     pango_font_description_set_weight(pfd, PANGO_WEIGHT_BOLD);
299     break;
300   case DIA_FONT_ULTRABOLD :
301     pango_font_description_set_weight(pfd, PANGO_WEIGHT_ULTRABOLD);
302     break;
303   case DIA_FONT_HEAVY :
304     pango_font_description_set_weight(pfd, PANGO_WEIGHT_HEAVY);
305     break;
306   default :
307     g_assert_not_reached();
308   }
309 }
310 
311 static void
dia_pfd_set_slant(PangoFontDescription * pfd,DiaFontSlant fo)312 dia_pfd_set_slant(PangoFontDescription* pfd, DiaFontSlant fo)
313 {
314   switch (fo) {
315   case DIA_FONT_NORMAL :
316     pango_font_description_set_style(pfd,PANGO_STYLE_NORMAL);
317     break;
318   case DIA_FONT_OBLIQUE :
319     pango_font_description_set_style(pfd,PANGO_STYLE_OBLIQUE);
320     break;
321   case DIA_FONT_ITALIC :
322     pango_font_description_set_style(pfd,PANGO_STYLE_ITALIC);
323     break;
324   default :
325     g_assert_not_reached();
326   }
327 }
328 
329 DiaFont*
dia_font_new_from_style(DiaFontStyle style,real height)330 dia_font_new_from_style(DiaFontStyle style, real height)
331 {
332   DiaFont* retval;
333   /* in the future we could establish Dia's own default font
334    * matching to be as (font-)system independent as possible.
335    * For now fall back to Pangos configuration --hb
336    */
337   PangoFontDescription* pfd = pango_font_description_new();
338   dia_pfd_set_family(pfd,DIA_FONT_STYLE_GET_FAMILY(style));
339   dia_pfd_set_weight(pfd,DIA_FONT_STYLE_GET_WEIGHT(style));
340   dia_pfd_set_slant(pfd,DIA_FONT_STYLE_GET_SLANT(style));
341   dia_pfd_set_height(pfd,height);
342 
343   retval = DIA_FONT(g_object_new(DIA_TYPE_FONT, NULL));
344   retval->pfd = pfd;
345   _dia_font_adjust_size (retval, height, FALSE);
346   retval->legacy_name = NULL;
347   return retval;
348 }
349 
dia_font_copy(const DiaFont * font)350 DiaFont* dia_font_copy(const DiaFont* font)
351 {
352   if (!font)
353     return NULL;
354   return dia_font_new(dia_font_get_family(font),
355                       dia_font_get_style(font),
356                       dia_font_get_height(font));
357 }
358 
359 void
dia_font_finalize(GObject * object)360 dia_font_finalize(GObject* object)
361 {
362   DiaFont* font = DIA_FONT(object);
363 
364   if (font->pfd)
365     pango_font_description_free(font->pfd);
366   font->pfd = NULL;
367   if (font->metrics)
368     pango_font_metrics_unref(font->metrics);
369   font->metrics = NULL;
370   if (font->loaded)
371     g_object_unref(font->loaded);
372   font->loaded = NULL;
373   G_OBJECT_CLASS(parent_class)->finalize(object);
374 }
375 
376 DiaFont *
dia_font_ref(DiaFont * font)377 dia_font_ref(DiaFont* font)
378 {
379   g_object_ref(G_OBJECT(font));
380   return font;
381 }
382 
383 void
dia_font_unref(DiaFont * font)384 dia_font_unref(DiaFont* font)
385 {
386   g_object_unref(G_OBJECT(font));
387 }
388 
389 DiaFontStyle
dia_font_get_style(const DiaFont * font)390 dia_font_get_style(const DiaFont* font)
391 {
392   guint style;
393 
394   static int weight_map[] = {
395     DIA_FONT_ULTRALIGHT, DIA_FONT_LIGHT,
396     DIA_FONT_WEIGHT_NORMAL, /* intentionaly ==0 */
397     DIA_FONT_MEDIUM, DIA_FONT_DEMIBOLD, /* not yet in Pango */
398     DIA_FONT_BOLD, DIA_FONT_ULTRABOLD, DIA_FONT_HEAVY
399   };
400 
401   PangoStyle pango_style = pango_font_description_get_style(font->pfd);
402   PangoWeight pango_weight = pango_font_description_get_weight(font->pfd);
403 
404   g_assert(PANGO_WEIGHT_ULTRALIGHT <= pango_weight && pango_weight <= PANGO_WEIGHT_HEAVY);
405   g_assert(PANGO_WEIGHT_ULTRALIGHT == 200);
406   g_assert(PANGO_WEIGHT_NORMAL == 400);
407   g_assert(PANGO_WEIGHT_BOLD == 700);
408 
409   style  = weight_map[(pango_weight - PANGO_WEIGHT_ULTRALIGHT) / 100];
410   style |= (pango_style << 2);
411 
412   return style;
413 }
414 
415 G_CONST_RETURN char*
dia_font_get_family(const DiaFont * font)416 dia_font_get_family(const DiaFont* font)
417 {
418   return pango_font_description_get_family(font->pfd);
419 }
420 
421 G_CONST_RETURN PangoFontDescription *
dia_font_get_description(const DiaFont * font)422 dia_font_get_description(const DiaFont* font)
423 {
424   return font->pfd;
425 }
426 
427 real
dia_font_get_height(const DiaFont * font)428 dia_font_get_height(const DiaFont* font)
429 {
430   return font->height;
431 }
432 
433 real
dia_font_get_size(const DiaFont * font)434 dia_font_get_size(const DiaFont* font)
435 {
436   if (!pango_font_description_get_size_is_absolute (font->pfd))
437     g_warning ("dia_font_get_size() : no absolute size");
438   return pdu_to_dcm(pango_font_description_get_size(font->pfd));
439 }
440 
441 void
dia_font_set_height(DiaFont * font,real height)442 dia_font_set_height(DiaFont* font, real height)
443 {
444   _dia_font_adjust_size (font, height, FALSE);
445 }
446 
447 
448 G_CONST_RETURN char*
dia_font_get_psfontname(const DiaFont * font)449 dia_font_get_psfontname(const DiaFont *font)
450 {
451   /* This hack corrects a couple fonts that were misnamed in
452    * earlier versions of Dia.   See bug #477079.
453    */
454   const char *fontname = dia_font_get_legacy_name(font);
455 
456   if (!fontname)
457     return NULL;
458 
459   if (strcmp(fontname, "NewCenturySchoolbook-Roman") == 0)
460     return "NewCenturySchlbk-Roman";
461   else if (strcmp(fontname, "NewCenturySchoolbook-Italic") == 0)
462     return "NewCenturySchlbk-Italic";
463   else if (strcmp(fontname, "NewCenturySchoolbook-Bold") == 0)
464     return "NewCenturySchlbk-Bold";
465   else if (strcmp(fontname, "NewCenturySchoolbook-BoldItalic") == 0)
466     return "NewCenturySchlbk-BoldItalic";
467 
468   return fontname;
469 }
470 
471 void
dia_font_set_any_family(DiaFont * font,const char * family)472 dia_font_set_any_family(DiaFont* font, const char* family)
473 {
474   gboolean changed;
475 
476   g_return_if_fail(font != NULL);
477 
478   changed = strcmp (pango_font_description_get_family(font->pfd), family) != 0;
479   pango_font_description_set_family(font->pfd, family);
480   if (changed) /* force recalculation on name change */
481     _dia_font_adjust_size (font, font->height, TRUE);
482   if (font->legacy_name) {
483     g_free(font->legacy_name);
484     font->legacy_name = NULL;
485   }
486 }
487 
488 void
dia_font_set_family(DiaFont * font,DiaFontFamily family)489 dia_font_set_family(DiaFont* font, DiaFontFamily family)
490 {
491   g_return_if_fail(font != NULL);
492 
493   dia_pfd_set_family(font->pfd,family);
494   if (font->legacy_name) {
495     g_free(font->legacy_name);
496     font->legacy_name = NULL;
497   }
498 }
499 
500 void
dia_font_set_weight(DiaFont * font,DiaFontWeight weight)501 dia_font_set_weight(DiaFont* font, DiaFontWeight weight)
502 {
503   DiaFontWeight old_weight = DIA_FONT_STYLE_GET_WEIGHT(dia_font_get_style(font));
504   g_return_if_fail(font != NULL);
505   dia_pfd_set_weight(font->pfd,weight);
506   if (old_weight != weight)
507     _dia_font_adjust_size (font, font->height, TRUE);
508 }
509 
510 void
dia_font_set_slant(DiaFont * font,DiaFontSlant slant)511 dia_font_set_slant(DiaFont* font, DiaFontSlant slant)
512 {
513   DiaFontSlant old_slant = DIA_FONT_STYLE_GET_SLANT(dia_font_get_style(font));
514   g_return_if_fail(font != NULL);
515   dia_pfd_set_slant(font->pfd,slant);
516   if (slant != old_slant)
517     _dia_font_adjust_size (font, font->height, TRUE);
518 }
519 
520 
521 typedef struct _WeightName {
522   DiaFontWeight fw;
523   const char *name;
524 } WeightName;
525 static const WeightName weight_names[] = {
526   {DIA_FONT_ULTRALIGHT, "200"},
527   {DIA_FONT_LIGHT,"300"},
528   {DIA_FONT_WEIGHT_NORMAL,"normal"},
529   {DIA_FONT_WEIGHT_NORMAL,"400"},
530   {DIA_FONT_MEDIUM, "500"},
531   {DIA_FONT_DEMIBOLD, "600"},
532   {DIA_FONT_BOLD, "700"},
533   {DIA_FONT_ULTRABOLD, "800"},
534   {DIA_FONT_HEAVY, "900"},
535   {0,NULL}
536 };
537 
538 G_CONST_RETURN char *
dia_font_get_weight_string(const DiaFont * font)539 dia_font_get_weight_string(const DiaFont* font)
540 {
541     const WeightName* p;
542     DiaFontWeight fw = DIA_FONT_STYLE_GET_WEIGHT(dia_font_get_style(font));
543 
544     for (p = weight_names; p->name != NULL; ++p) {
545         if (p->fw == fw) return p->name;
546     }
547     return "normal";
548 }
549 
550 void
dia_font_set_weight_from_string(DiaFont * font,const char * weight)551 dia_font_set_weight_from_string(DiaFont* font, const char* weight)
552 {
553     DiaFontWeight fw = DIA_FONT_WEIGHT_NORMAL;
554     const WeightName* p;
555 
556     for (p = weight_names; p->name != NULL; ++p) {
557         if (0 == strncmp(weight,p->name,8)) {
558             fw = p->fw;
559             break;
560         }
561     }
562 
563     dia_font_set_weight(font,fw);
564 }
565 
566 
567 typedef struct _SlantName {
568   DiaFontSlant fo;
569   const char *name;
570 } SlantName;
571 static const SlantName slant_names[] = {
572   { DIA_FONT_NORMAL, "normal" },
573   { DIA_FONT_OBLIQUE, "oblique" },
574   { DIA_FONT_ITALIC, "italic" },
575   { 0, NULL}
576 };
577 
578 G_CONST_RETURN char *
dia_font_get_slant_string(const DiaFont * font)579 dia_font_get_slant_string(const DiaFont* font)
580 {
581   const SlantName* p;
582   DiaFontSlant fo =
583     DIA_FONT_STYLE_GET_SLANT(dia_font_get_style(font));
584 
585   for (p = slant_names; p->name != NULL; ++p) {
586     if (p->fo == fo)
587       return p->name;
588   }
589   return "normal";
590 }
591 
592 void
dia_font_set_slant_from_string(DiaFont * font,const char * obli)593 dia_font_set_slant_from_string(DiaFont* font, const char* obli)
594 {
595   DiaFontSlant fo = DIA_FONT_NORMAL;
596   const SlantName* p;
597 
598   DiaFontStyle old_style;
599   DiaFontSlant old_fo;
600   old_style = dia_font_get_style(font);
601   old_fo = DIA_FONT_STYLE_GET_SLANT(old_style);
602 
603   for (p = slant_names; p->name != NULL; ++p) {
604     if (0 == strncmp(obli,p->name,8)) {
605       fo = p->fo;
606       break;
607     }
608   }
609   dia_font_set_slant(font,fo);
610 }
611 
612 
613 /* ************************************************************************ */
614 /* Non-scaled versions of the utility routines                              */
615 /* ************************************************************************ */
616 
617 real
dia_font_string_width(const char * string,DiaFont * font,real height)618 dia_font_string_width(const char* string, DiaFont *font, real height)
619 {
620   real result = 0;
621   if (string && *string) {
622     TextLine *text_line = text_line_new(string, font, height);
623     result = text_line_get_width(text_line);
624     text_line_destroy(text_line);
625   }
626   return result;
627 }
628 
629 real
dia_font_ascent(const char * string,DiaFont * font,real height)630 dia_font_ascent(const char* string, DiaFont* font, real height)
631 {
632   if (font->metrics) {
633     real ascent = pdu_to_dcm(pango_font_metrics_get_ascent (font->metrics));
634     return ascent * (height / font->height);
635   } else {
636     /* previous, _expensive_ but string specific way */
637     TextLine *text_line = text_line_new(string, font, height);
638     real result = text_line_get_ascent(text_line);
639     text_line_destroy(text_line);
640     return result;
641   }
642 }
643 
644 real
dia_font_descent(const char * string,DiaFont * font,real height)645 dia_font_descent(const char* string, DiaFont* font, real height)
646 {
647   if (font->metrics) {
648     real descent = pdu_to_dcm(pango_font_metrics_get_descent (font->metrics));
649     return descent * (height / font->height);
650   } else {
651     /* previous, _expensive_ but string specific way */
652     TextLine *text_line = text_line_new(string, font, height);
653     real result = text_line_get_descent(text_line);
654     text_line_destroy(text_line);
655     return result;
656   }
657 }
658 
659 
660 PangoLayout*
dia_font_build_layout(const char * string,DiaFont * font,real height)661 dia_font_build_layout(const char* string, DiaFont* font, real height)
662 {
663   PangoLayout* layout;
664   PangoAttrList* list;
665   PangoAttribute* attr;
666   guint length;
667   PangoFontDescription *pfd;
668   real factor;
669 
670   layout = pango_layout_new(dia_font_get_context());
671 
672   length = string ? strlen(string) : 0;
673   pango_layout_set_text(layout, string, length);
674 
675   list = pango_attr_list_new();
676 
677   pfd = pango_font_description_copy (font->pfd);
678   /* account for difference between size and height as well as between font height and given one */
679   factor = dia_font_get_size(font) / dia_font_get_height (font);
680   pango_font_description_set_absolute_size (pfd, dcm_to_pdu (height) * factor);
681   attr = pango_attr_font_desc_new(pfd);
682   pango_font_description_free (pfd);
683 
684   attr->start_index = 0;
685   attr->end_index = length;
686   pango_attr_list_insert(list,attr); /* eats attr */
687 
688   pango_layout_set_attributes(layout,list);
689   pango_attr_list_unref(list);
690 
691   pango_layout_set_indent(layout,0);
692   pango_layout_set_justify(layout,FALSE);
693   pango_layout_set_alignment(layout,PANGO_ALIGN_LEFT);
694 
695   return layout;
696 }
697 
698 /** Find the offsets of the individual letters in the iter and place them
699  * in an array.
700  * This currently assumes only one run per iter, which is all we can input.
701  * @param iter The PangoLayoutIter to count characters in.
702  * @param offsets The place to return the offsets
703  * @param n_offsets The place to return the number of offsets
704  */
705 static void
get_string_offsets(PangoLayoutIter * iter,real ** offsets,int * n_offsets)706 get_string_offsets(PangoLayoutIter *iter, real** offsets, int* n_offsets)
707 {
708   int i;
709   PangoLayoutLine*   line = pango_layout_iter_get_line(iter);
710   PangoGlyphItem* item;
711   PangoGlyphString* string;
712 
713   if(0 == line->length)
714   {
715     *n_offsets = 0;
716     return;
717   }
718   item = (PangoGlyphItem*)line->runs->data;
719   string = item->glyphs;
720 
721   *n_offsets = string->num_glyphs;
722   *offsets = g_new(real, *n_offsets);
723 
724   for (i = 0; i < string->num_glyphs; i++) {
725     PangoGlyphGeometry geom = string->glyphs[i].geometry;
726 
727     (*offsets)[i] = pdu_to_dcm(geom.width) / global_zoom_factor;
728   }
729 }
730 
731 /** Copy the offsets into a storage object, adjusting for exaggerated size.
732  */
733 static void
get_layout_offsets(PangoLayoutLine * line,PangoLayoutLine ** layout_line)734 get_layout_offsets(PangoLayoutLine *line, PangoLayoutLine **layout_line)
735 {
736   /* Some info not copied. */
737   GSList *layout_runs = NULL;
738   GSList *runs = line->runs;
739 
740   *layout_line = g_new0(PangoLayoutLine, 1);
741 
742   /* A LayoutLine contains a GSList runs of PangoGlyphItems.
743    * Each PangoGlyphItem contains an array glyphs of PangoGlyphString.
744    * This array is run->item->num_chars long?
745    * Each PangoGlyphString contains an array glyphs of PangoGlyphInfo.
746    * This array is run->glyphs[i].num_glyphs long.
747    */
748   for (; runs != NULL; runs = g_slist_next(runs)) {
749     PangoGlyphItem *run = (PangoGlyphItem *) runs->data;
750     PangoGlyphItem *layout_run = g_new0(PangoGlyphItem, 1);
751     int j;
752     /* Make single pointer */
753     PangoGlyphString *glyph_string = run->glyphs;
754     PangoGlyphString *layout_glyph_string;
755 
756     layout_run->glyphs = g_new0(PangoGlyphString, 1);
757     layout_glyph_string = layout_run->glyphs;
758 
759     layout_glyph_string->num_glyphs = glyph_string->num_glyphs;
760     layout_glyph_string->glyphs =
761       g_new0(PangoGlyphInfo, glyph_string->num_glyphs);
762     for (j = 0; j < layout_glyph_string->num_glyphs; j++) {
763       PangoGlyphInfo *info = &glyph_string->glyphs[j];
764       PangoGlyphInfo *layout_info = &layout_glyph_string->glyphs[j];
765       layout_info->geometry.width = info->geometry.width;
766       layout_info->geometry.x_offset = info->geometry.x_offset;
767       layout_info->geometry.y_offset = info->geometry.y_offset;
768     }
769     layout_runs = g_slist_append(layout_runs, layout_run);
770   }
771   (*layout_line)->runs = layout_runs;
772 }
773 
774 /** Get size information for the given string, font and height.
775  *
776  * @returns an array of offsets of the individual glyphs in the layout.
777  */
778 real*
dia_font_get_sizes(const char * string,DiaFont * font,real height,real * width,real * ascent,real * descent,int * n_offsets,PangoLayoutLine ** layout_offsets)779 dia_font_get_sizes(const char* string, DiaFont *font, real height,
780 		   real *width, real *ascent, real *descent, int *n_offsets,
781 		   PangoLayoutLine **layout_offsets)
782 {
783   PangoLayout* layout;
784   PangoLayoutIter* iter;
785   real top, bline, bottom;
786   const gchar* non_empty_string;
787   PangoRectangle ink_rect,logical_rect;
788   real* offsets = NULL; /* avoid: 'offsets' may be used uninitialized in this function */
789 
790   /* We need some reasonable ascent/descent values even for empty strings. */
791   if (string == NULL || string[0] == '\0') {
792     non_empty_string = "XjgM149";
793   } else {
794     non_empty_string = string;
795   }
796   layout = dia_font_build_layout(non_empty_string, font, height * global_zoom_factor);
797 
798   /* Only one line here ? */
799   iter = pango_layout_get_iter(layout);
800 
801   pango_layout_iter_get_line_extents(iter, &ink_rect, &logical_rect);
802 
803   top = pdu_to_dcm(logical_rect.y) / global_zoom_factor;
804   bottom = pdu_to_dcm(logical_rect.y + logical_rect.height) / global_zoom_factor;
805   bline = pdu_to_dcm(pango_layout_iter_get_baseline(iter)) / global_zoom_factor;
806 
807   get_string_offsets(iter, &offsets, n_offsets);
808   get_layout_offsets(pango_layout_get_line(layout, 0), layout_offsets);
809 
810   /* FIXME: the above assumption of 'one line' is wrong. At least calculate the overall width correctly
811    * to avoid text overflowing its box, like in bug #482585 */
812   while (pango_layout_iter_next_line (iter)) {
813     PangoRectangle more_ink_rect, more_logical_rect;
814 
815     pango_layout_iter_get_line_extents(iter, &more_ink_rect, &more_logical_rect);
816     if (more_logical_rect.width > logical_rect.width)
817       logical_rect.width = more_logical_rect.width;
818     /* also calculate for the ink rect (true space needed for drawing the glyphs) */
819     if (more_ink_rect.width > ink_rect.width)
820       ink_rect.width = more_ink_rect.width;
821   }
822 
823   pango_layout_iter_free(iter);
824   g_object_unref(G_OBJECT(layout));
825 
826   *ascent = bline-top;
827   *descent = bottom-bline;
828   if (non_empty_string != string) {
829     *width = 0.0;
830   } else {
831     /* take the bigger rectangle to avoid cutting of any part of the string */
832     *width = pdu_to_dcm(logical_rect.width > ink_rect.width ? logical_rect.width : ink_rect.width) / global_zoom_factor;
833   }
834   return offsets;
835 }
836 
837 
838 /**
839  * Compatibility with older files out of pre Pango Time.
840  * Make old files look as similar as possible
841  * List should be kept alphabetically sorted by oldname, in case of
842  * duplicates the one with the preferred newname comes first.
843  *
844  * FIXME: DIA_FONT_FAMILY_ANY in the list below does mean noone knows better
845  *
846  * The PostScript names can be found on page 139 of
847  * http://partners.adobe.com/public/developer/en/ps/PS3010and3011.Supplement.pdf
848  *
849  * Note that these are not strictly the Adobe names, as a few were used
850  * incorrectly and are kept for backwards compatibility.  The latin-1
851  * postscript renderer mangles these.
852  *
853  * Some additional fairly standard fonts for asian scripts are also added.
854  */
855 static struct _legacy_font {
856   gchar*       oldname;
857   gchar*       newname;
858   DiaFontStyle style;   /* the DIA_FONT_FAMILY() is used as falback only */
859 } legacy_fonts[] = {
860   { "AvantGarde-Book",        "AvantGarde", DIA_FONT_SERIF },
861   { "AvantGarde-BookOblique", "AvantGarde", DIA_FONT_SERIF | DIA_FONT_OBLIQUE },
862   { "AvantGarde-Demi",        "AvantGarde", DIA_FONT_SERIF | DIA_FONT_DEMIBOLD },
863   { "AvantGarde-DemiOblique", "AvantGarde", DIA_FONT_SERIF | DIA_FONT_OBLIQUE | DIA_FONT_DEMIBOLD },
864   { "Batang", "Batang", DIA_FONT_FAMILY_ANY },
865   { "Bookman-Demi",        "Bookman Old Style", DIA_FONT_SERIF | DIA_FONT_DEMIBOLD },
866   { "Bookman-DemiItalic",  "Bookman Old Style", DIA_FONT_SERIF | DIA_FONT_DEMIBOLD | DIA_FONT_ITALIC },
867   { "Bookman-Light",       "Bookman Old Style", DIA_FONT_SERIF | DIA_FONT_LIGHT },
868   { "Bookman-LightItalic", "Bookman Old Style", DIA_FONT_SERIF | DIA_FONT_LIGHT | DIA_FONT_ITALIC },
869   { "BousungEG-Light-GB", "BousungEG-Light-GB", DIA_FONT_FAMILY_ANY },
870   { "Courier",             "monospace", DIA_FONT_MONOSPACE },
871   { "Courier",             "Courier New", DIA_FONT_MONOSPACE },
872   { "Courier-Bold",        "monospace", DIA_FONT_MONOSPACE | DIA_FONT_BOLD },
873   { "Courier-Bold",        "Courier New", DIA_FONT_MONOSPACE | DIA_FONT_BOLD },
874   { "Courier-BoldOblique", "monospace", DIA_FONT_MONOSPACE | DIA_FONT_ITALIC | DIA_FONT_BOLD },
875   { "Courier-BoldOblique", "Courier New", DIA_FONT_MONOSPACE | DIA_FONT_ITALIC | DIA_FONT_BOLD },
876   { "Courier-Oblique",     "monospace", DIA_FONT_MONOSPACE | DIA_FONT_ITALIC},
877   { "Courier-Oblique",     "Courier New", DIA_FONT_MONOSPACE | DIA_FONT_ITALIC },
878   { "Dotum", "Dotum", DIA_FONT_FAMILY_ANY },
879   { "GBZenKai-Medium", "GBZenKai-Medium", DIA_FONT_FAMILY_ANY },
880   { "GothicBBB-Medium", "GothicBBB-Medium", DIA_FONT_FAMILY_ANY },
881   { "Gulim", "Gulim", DIA_FONT_FAMILY_ANY },
882   { "Headline", "Headline", DIA_FONT_FAMILY_ANY },
883   { "Helvetica",             "sans", DIA_FONT_SANS },
884   { "Helvetica",             "Arial", DIA_FONT_SANS },
885   { "Helvetica-Bold",        "sans", DIA_FONT_SANS | DIA_FONT_BOLD },
886   { "Helvetica-Bold",        "Arial", DIA_FONT_SANS | DIA_FONT_BOLD },
887   { "Helvetica-BoldOblique", "sans", DIA_FONT_SANS | DIA_FONT_BOLD | DIA_FONT_ITALIC },
888   { "Helvetica-BoldOblique", "Arial", DIA_FONT_SANS | DIA_FONT_BOLD | DIA_FONT_ITALIC },
889   { "Helvetica-Narrow",             "Arial Narrow", DIA_FONT_SANS | DIA_FONT_MEDIUM },
890   { "Helvetica-Narrow-Bold",        "Arial Narrow", DIA_FONT_SANS | DIA_FONT_DEMIBOLD },
891   { "Helvetica-Narrow-BoldOblique", "Arial Narrow", DIA_FONT_SANS | DIA_FONT_MEDIUM | DIA_FONT_OBLIQUE },
892   { "Helvetica-Narrow-Oblique",     "Arial Narrow", DIA_FONT_SANS | DIA_FONT_MEDIUM | DIA_FONT_OBLIQUE },
893   { "Helvetica-Oblique",     "sans", DIA_FONT_SANS | DIA_FONT_ITALIC },
894   { "Helvetica-Oblique",     "Arial", DIA_FONT_SANS | DIA_FONT_ITALIC },
895   { "MOESung-Medium", "MOESung-Medium", DIA_FONT_FAMILY_ANY },
896   { "NewCenturySchoolbook-Bold",       "Century Schoolbook SWA", DIA_FONT_SERIF | DIA_FONT_BOLD },
897   { "NewCenturySchoolbook-BoldItalic", "Century Schoolbook SWA", DIA_FONT_SERIF | DIA_FONT_BOLD | DIA_FONT_ITALIC },
898   { "NewCenturySchoolbook-Italic",     "Century Schoolbook SWA", DIA_FONT_SERIF | DIA_FONT_ITALIC },
899   { "NewCenturySchoolbook-Roman",      "Century Schoolbook SWA", DIA_FONT_SERIF },
900   { "Palatino-Bold",       "Palatino", DIA_FONT_FAMILY_ANY | DIA_FONT_BOLD },
901   { "Palatino-BoldItalic", "Palatino", DIA_FONT_FAMILY_ANY | DIA_FONT_BOLD | DIA_FONT_ITALIC },
902   { "Palatino-Italic",     "Palatino", DIA_FONT_FAMILY_ANY | DIA_FONT_ITALIC },
903   { "Palatino-Roman",      "Palatino", DIA_FONT_FAMILY_ANY },
904   { "Ryumin-Light",      "Ryumin", DIA_FONT_FAMILY_ANY | DIA_FONT_LIGHT },
905   { "ShanHeiSun-Light",  "ShanHeiSun", DIA_FONT_FAMILY_ANY | DIA_FONT_LIGHT },
906   { "Song-Medium",       "Song-Medium", DIA_FONT_FAMILY_ANY | DIA_FONT_MEDIUM },
907   { "Symbol",           "Symbol", DIA_FONT_SANS | DIA_FONT_MEDIUM },
908   { "Times-Bold",       "serif", DIA_FONT_SERIF | DIA_FONT_BOLD },
909   { "Times-Bold",       "Times New Roman", DIA_FONT_SERIF | DIA_FONT_BOLD },
910   { "Times-BoldItalic", "serif", DIA_FONT_SERIF | DIA_FONT_ITALIC | DIA_FONT_BOLD },
911   { "Times-BoldItalic", "Times New Roman", DIA_FONT_SERIF | DIA_FONT_ITALIC | DIA_FONT_BOLD },
912   { "Times-Italic",     "serif", DIA_FONT_SERIF | DIA_FONT_ITALIC },
913   { "Times-Italic",     "Times New Roman", DIA_FONT_SERIF | DIA_FONT_ITALIC },
914   { "Times-Roman",      "serif", DIA_FONT_SERIF },
915   { "Times-Roman",      "Times New Roman", DIA_FONT_SERIF },
916   { "ZapfChancery-MediumItalic", "Zapf Calligraphic 801 SWA", DIA_FONT_SERIF | DIA_FONT_MEDIUM },
917   { "ZapfDingbats", "Zapf Calligraphic 801 SWA", DIA_FONT_SERIF },
918   { "ZenKai-Medium", "ZenKai", DIA_FONT_FAMILY_ANY | DIA_FONT_MEDIUM },
919 };
920 
921 
922 /**
923  * Given a legacy name as stored until Dia-0.90 construct
924  * a new DiaFont which is as similar as possible
925  */
926 DiaFont*
dia_font_new_from_legacy_name(const char * name)927 dia_font_new_from_legacy_name(const char* name)
928 {
929   /* do NOT translate anything here !!! */
930   DiaFont* retval;
931   struct _legacy_font* found = NULL;
932   real height = 1.0;
933   int i;
934 
935   for (i = 0; i < G_N_ELEMENTS(legacy_fonts); i++) {
936     if (!strcmp(name, legacy_fonts[i].oldname)) {
937       found = &legacy_fonts[i];
938       break;
939     }
940   }
941   if (found) {
942     retval = dia_font_new (found->newname, found->style, height);
943     retval->legacy_name = found->oldname;
944   } else {
945     /* We tried our best, let Pango complain */
946     retval = dia_font_new (name, DIA_FONT_WEIGHT_NORMAL, height);
947     retval->legacy_name = NULL;
948   }
949 
950   return retval;
951 }
952 
953 G_CONST_RETURN char*
dia_font_get_legacy_name(const DiaFont * font)954 dia_font_get_legacy_name(const DiaFont *font)
955 {
956   const char* matched_name = NULL;
957   const char* family;
958   DiaFontStyle style;
959   int i;
960 
961   /* if we have loaded it from an old file, use the old name */
962   if (font->legacy_name)
963     return font->legacy_name;
964 
965   family = dia_font_get_family (font);
966   style = dia_font_get_style (font);
967   for (i = 0; i < G_N_ELEMENTS(legacy_fonts); i++) {
968     if (0 == g_ascii_strcasecmp (legacy_fonts[i].newname, family)) {
969       /* match weight and slant */
970       DiaFontStyle st = legacy_fonts[i].style;
971       if ((DIA_FONT_STYLE_GET_SLANT(style) | DIA_FONT_STYLE_GET_WEIGHT(style))
972           == (DIA_FONT_STYLE_GET_SLANT(st) | DIA_FONT_STYLE_GET_WEIGHT(st))) {
973         return legacy_fonts[i].oldname; /* exact match */
974       } else if (0 == (DIA_FONT_STYLE_GET_SLANT(st) | DIA_FONT_STYLE_GET_WEIGHT(st))) {
975         matched_name = legacy_fonts[i].oldname;
976         /* 'unmodified' font, continue matching */
977       }
978     }
979   }
980   return matched_name ? matched_name : "Courier";
981 }
982 
983