2009-12-07 Rolf Bjarne Kvinge <RKvinge@novell.com>
[moon.git] / src / fonts.cpp
blobfd3cd9f5fd93fc9bab8e15be1cca75cc83ccbf33
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * fonts.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2009 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <glib.h>
16 #include <glib/gstdio.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <math.h>
22 #include "timesource.h"
23 #include "moon-path.h"
24 #include "debug.h"
25 #include "fonts.h"
28 // TextFont
31 TextFont::TextFont (FontFace **faces, int n_faces, int master, bool gapless, double size)
33 this->simulate = StyleSimulationsNone;
34 this->n_faces = n_faces;
35 this->gapless = gapless;
36 this->master = master;
37 this->faces = faces;
38 this->n_glyphs = 0;
39 this->size = size;
40 this->desc = NULL;
42 UpdateFaceExtents ();
45 TextFont::~TextFont ()
47 ClearGlyphCache ();
49 for (int i = 0; i < n_faces; i++)
50 faces[i]->unref ();
51 g_free (faces);
54 void
55 TextFont::ClearGlyphCache ()
57 for (int i = 0; i < n_glyphs; i++) {
58 if (glyphs[i].path)
59 moon_path_destroy (glyphs[i].path);
62 n_glyphs = 0;
65 void
66 TextFont::UpdateFaceExtents ()
68 faces[master]->GetExtents (size, gapless, &extents);
71 TextFont *
72 TextFont::Load (const char *resource, int index, double size, StyleSimulations simulate)
74 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
75 FontFace **faces;
76 TextFont *font;
78 faces = g_new (FontFace *, 1);
79 if (!(faces[0] = manager->OpenFont (resource, index))) {
80 g_free (faces);
81 return NULL;
84 font = new TextFont (faces, 1, 0, false, size);
85 font->simulate = simulate;
87 return font;
90 #define lowercase(x) (((x) >= 'A' && (x) <= 'Z') ? (x) - 'A' + 'a' : (x))
92 static int
93 strcase_equal (gconstpointer v, gconstpointer v2)
95 return g_ascii_strcasecmp ((const char *) v, (const char *) v2) == 0;
99 static guint
100 strcase_hash (gconstpointer key)
102 const char *p = (const char *) key;
103 guint h = 0;
105 while (*p != '\0') {
106 h = (h << 5) - h + lowercase (*p);
107 p++;
110 return h;
113 static struct {
114 const char *lang;
115 const char *families[6];
116 } default_fonts[] = {
117 { "", { "Lucida Sans Unicode", "Liberation Sans", "Bitstream Vera Sans", "DejaVu Sans", "Luxi Sans", NULL } },
118 { "ja", { "MS Gothic", "Meiryo", "MS PMincho", "MS PGothic", "MS UI Gothic", NULL } },
119 { "ko", { "Gulim", "Malgun Gothic", "Dotum", "Arial Unicode MS", "Batang", NULL } },
120 { "zh", { "SimSun", "SimHei", "Microsoft YaHei", "Arial Unicode MS", NULL, NULL } },
123 static const char lang_table[256] = {
124 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
125 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
126 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0,
127 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0,
128 '-', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
129 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, '-',
130 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
131 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0
134 static char *
135 canon_lang (const char *lang)
137 const char *s = lang;
138 char *canon, *d;
140 d = canon = (char *) g_malloc (strlen (lang) + 1);
141 while (*s != '\0')
142 *d++ = lang_table[(unsigned char) *s++];
144 *d = '\0';
146 return canon;
149 #if 0
150 static bool
151 langs_equal (const char *lang0, const char *lang1)
153 const unsigned char *p0 = (const unsigned char *) lang0;
154 const unsigned char *p1 = (const unsigned char *) lang1;
156 while (lang_table[*p0] && lang_table[*p0] == lang_table[*p1])
157 p0++, p1++;
159 return lang_table[*p0] == lang_table[*p1];
161 #endif
163 static bool
164 langs_match (const char *pattern, const char *actual)
166 size_t n = strlen (pattern);
168 return strncmp (pattern, actual, n) == 0 &&
169 (actual[n] == '\0' || actual[n] == '-');
172 static int
173 LoadPortableUserInterface (FontManager *manager, GPtrArray *faces, const char *lang, FontStretches stretch, FontWeights weight, FontStyles style, bool *gapless)
175 guint preferred = G_N_ELEMENTS (default_fonts);
176 bool first_font = faces->len == 0;
177 bool silverlight_2_0 = false;
178 const char **families;
179 FontFace *face;
180 guint lucida;
181 guint i, j;
183 // Check for Silverlight >= 2.0
184 if (Deployment::GetCurrent ()->IsLoadedFromXap ()) {
185 // Verdana is the first fallback in Silverlight >= 2.0 applications
186 if ((face = manager->OpenFont ("Verdana", stretch, weight, style)))
187 g_ptr_array_add (faces, face);
189 silverlight_2_0 = true;
192 // Load Lucida Sans Unicode and save the index because we use it later for face extents
193 families = default_fonts[0].families;
194 lucida = faces->len;
196 for (j = 0; families[j]; j++) {
197 if ((face = manager->OpenFont (families[j], stretch, weight, style))) {
198 // Note: Silverlight >= 2.0 seems to use Lucida Sans Unicode's height
199 // metrics minus the sTypoLineGap to represent the height metrics for
200 // the Portable User Interface collection of fonts. Silverlight 1.0,
201 // however, uses the Lucida Sans Unicode height metrics as-is.
203 // Only subtract the sTypoLineGap value IFF we managed to load the real
204 // Lucida Sans Unicode font, not if we had to fallback to one of the
205 // others (because they have much smaller metrics).
206 *gapless = silverlight_2_0 && j == 0 && first_font;
208 g_ptr_array_add (faces, face);
209 break;
213 if (lang != NULL) {
214 // use the xml:lang tag to load the preferred font for the language as the next fallback
215 for (i = 1; i < G_N_ELEMENTS (default_fonts); i++) {
216 if (langs_match (default_fonts[i].lang, lang)) {
217 families = default_fonts[i].families;
219 for (j = 0; families[j]; j++) {
220 if ((face = manager->OpenFont (families[j], stretch, weight, style))) {
221 g_ptr_array_add (faces, face);
222 break;
226 preferred = i;
227 break;
232 // Now load the remaining default font faces...
233 for (i = 1; i < G_N_ELEMENTS (default_fonts); i++) {
234 // avoid re-loading the preferred font face
235 if (i == preferred)
236 continue;
238 families = default_fonts[i].families;
239 for (j = 0; families[j]; j++) {
240 if ((face = manager->OpenFont (families[j], stretch, weight, style))) {
241 g_ptr_array_add (faces, face);
242 break;
247 return (int) lucida;
250 TextFont *
251 TextFont::Load (const TextFontDescription *desc)
253 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
254 FontStretches stretch = desc->GetStretch ();
255 FontWeights weight = desc->GetWeight ();
256 const char *source = desc->GetSource ();
257 const char *lang = desc->GetLanguage ();
258 char **families = desc->GetFamilies ();
259 FontStyles style = desc->GetStyle ();
260 int lucida, master = -1;
261 bool gapless = false;
262 GHashTable *loaded;
263 GPtrArray *faces;
264 FontFace *face;
265 TextFont *font;
266 char *name;
267 int i;
269 loaded = g_hash_table_new (strcase_hash, strcase_equal);
271 faces = g_ptr_array_new ();
273 if (families) {
274 for (i = 0; families[i]; i++) {
275 if (g_hash_table_lookup (loaded, families[i]))
276 continue;
278 if (!g_ascii_strcasecmp (families[i], "Portable User Interface")) {
279 lucida = LoadPortableUserInterface (manager, faces, lang, stretch, weight, style, &gapless);
281 if (master == -1)
282 master = lucida;
283 } else {
284 face = NULL;
286 if (source && !strchr (families[i], '#')) {
287 // if there is a font source, try loading from the font source first
288 name = g_strdup_printf ("%s#%s", source, families[i]);
289 face = manager->OpenFont (name, stretch, weight, style);
290 g_free (name);
293 if (face == NULL)
294 face = manager->OpenFont (families[i], stretch, weight, style);
296 if (face != NULL) {
297 g_ptr_array_add (faces, face);
299 if (master == -1)
300 master = 0;
304 g_hash_table_insert (loaded, families[i], GINT_TO_POINTER (true));
306 } else if (source) {
307 if ((face = manager->OpenFont (source, 0))) {
308 g_ptr_array_add (faces, face);
309 master = 0;
313 // always add PUI as fallback unless already added
314 if (!g_hash_table_lookup (loaded, "Portable User Interface")) {
315 lucida = LoadPortableUserInterface (manager, faces, lang, stretch, weight, style, &gapless);
317 if (master == -1)
318 master = lucida;
321 g_hash_table_destroy (loaded);
322 g_strfreev (families);
324 if (faces->len == 0) {
325 g_ptr_array_free (faces, true);
326 return NULL;
329 font = new TextFont ((FontFace **) faces->pdata, faces->len, master, gapless, desc->GetSize ());
330 g_ptr_array_free (faces, false);
331 font->desc = desc;
333 return font;
336 bool
337 TextFont::SetSize (double size)
339 if (this->size == size)
340 return false;
342 this->size = size;
344 UpdateFaceExtents ();
345 ClearGlyphCache ();
347 return true;
350 double
351 TextFont::GetSize () const
353 return size;
356 bool
357 TextFont::SetStyleSimulations (StyleSimulations simulate)
359 if (this->simulate == simulate)
360 return false;
362 this->simulate = simulate;
364 ClearGlyphCache ();
366 return true;
369 StyleSimulations
370 TextFont::GetStyleSimulations () const
372 return simulate;
375 double
376 TextFont::Kerning (GlyphInfo *left, GlyphInfo *right)
378 #ifdef ENABLE_KERNING
379 if (left->face != right->face)
380 return 0.0;
382 return left->face->Kerning (size, left->index, right->index);
383 #else
384 return 0.0;
385 #endif
388 double
389 TextFont::Descender () const
391 return extents.descent;
394 double
395 TextFont::Ascender () const
397 return extents.ascent;
400 double
401 TextFont::Height () const
403 return extents.height;
406 static int
407 glyphsort (const void *v1, const void *v2)
409 GlyphInfo *g1 = (GlyphInfo *) v1;
410 GlyphInfo *g2 = (GlyphInfo *) v2;
411 gint64 cmp = g2->atime - g1->atime;
413 if ((cmp = g2->atime - g1->atime) < 0)
414 return -1;
416 return cmp > 0 ? 1 : 0;
419 GlyphInfo *
420 TextFont::GetGlyphInfo (FontFace *face, gunichar unichar, guint32 index)
422 gint64 now = get_now ();
423 GlyphInfo glyph, *slot;
424 int i;
426 for (i = 0; i < n_glyphs; i++) {
427 if (glyphs[i].unichar == unichar) {
428 slot = &glyphs[i];
429 slot->atime = now;
430 return slot;
434 glyph.unichar = unichar;
435 glyph.index = index;
436 glyph.face = face;
437 glyph.atime = now;
438 glyph.path = NULL;
440 if (desc != NULL) {
441 // figure out what to simulate
442 simulate = StyleSimulationsNone;
443 if (FontWeightIsBold (desc->GetWeight ()) && !face->IsBold ())
444 simulate = (StyleSimulations) (simulate | StyleSimulationsBold);
445 if (desc->GetStyle () == FontStylesItalic && !face->IsItalic ())
446 simulate = (StyleSimulations) (simulate | StyleSimulationsItalic);
449 if (!face->LoadGlyph (size, &glyph, simulate))
450 return NULL;
452 if (n_glyphs == GLYPH_CACHE_SIZE) {
453 // need to expire the least recently requested glyph (which will be the last element in the array after sorting)
454 qsort (glyphs, n_glyphs, sizeof (GlyphInfo), glyphsort);
456 for (i = 0; i < n_glyphs; i++)
457 fprintf (stderr, "glyphs[%d].atime = %" G_GINT64_FORMAT "\n", i, glyphs[i].atime);
459 slot = &glyphs[n_glyphs - 1];
461 if (slot->path)
462 moon_path_destroy (slot->path);
463 } else {
464 slot = &glyphs[n_glyphs++];
467 memcpy (slot, &glyph, sizeof (GlyphInfo));
469 return slot;
472 //static GlyphInfo ZeroWidthNoBreakSpace = {
473 // 0xFEFF, 0, { 0.0, 0.0, 0.0, 0.0, 0.0 }, NULL, 0, 0
474 //};
476 GlyphInfo *
477 TextFont::GetGlyphInfo (gunichar unichar)
479 FontFace *face = NULL;
480 guint32 index;
481 int i;
483 //if (unichar == 0xFEFF)
484 // return &ZeroWidthNoBreakSpace;
486 // find the face that contains this character
487 for (i = 0; i < n_faces; i++) {
488 if ((index = faces[i]->GetCharIndex (unichar)) != 0) {
489 face = faces[i];
490 break;
494 if (face == NULL) {
495 // draw the empty glyph from the primary face
496 face = faces[0];
497 index = 0;
500 return GetGlyphInfo (face, unichar, index);
503 GlyphInfo *
504 TextFont::GetGlyphInfoByIndex (guint32 index)
506 gunichar unichar;
508 unichar = faces[0]->GetCharFromIndex (index);
510 return GetGlyphInfo (faces[0], unichar, index);
513 double
514 TextFont::UnderlinePosition () const
516 return extents.underline_position;
519 double
520 TextFont::UnderlineThickness () const
522 return extents.underline_thickness;
525 void
526 TextFont::Path (cairo_t *cr, GlyphInfo *glyph, double x, double y)
528 if (!glyph->path || !(&glyph->path->cairo)->data)
529 return;
531 cairo_translate (cr, x, y);
532 cairo_append_path (cr, &glyph->path->cairo);
533 cairo_translate (cr, -x, -y);
536 void
537 TextFont::Path (cairo_t *cr, gunichar unichar, double x, double y)
539 GlyphInfo *glyph;
541 if (!(glyph = GetGlyphInfo (unichar)))
542 return;
544 Path (cr, glyph, x, y);
547 static void
548 moon_append_path_with_origin (moon_path *mpath, cairo_path_t *path, double x, double y)
550 cairo_path_data_t *data;
552 moon_move_to (mpath, x, y);
554 for (int i = 0; i < path->num_data; i += path->data[i].header.length) {
555 data = &path->data[i];
557 switch (data->header.type) {
558 case CAIRO_PATH_MOVE_TO:
559 moon_move_to (mpath, data[1].point.x + x, data[1].point.y + y);
560 break;
561 case CAIRO_PATH_LINE_TO:
562 moon_line_to (mpath, data[1].point.x + x, data[1].point.y + y);
563 break;
564 case CAIRO_PATH_CURVE_TO:
565 moon_curve_to (mpath, data[1].point.x + x, data[1].point.y + y,
566 data[2].point.x + x, data[2].point.y + y,
567 data[3].point.x + x, data[3].point.y + y);
568 break;
569 case CAIRO_PATH_CLOSE_PATH:
570 break;
575 void
576 TextFont::AppendPath (moon_path *path, GlyphInfo *glyph, double x, double y)
578 if (!glyph->path || !(&glyph->path->cairo)->data)
579 return;
581 moon_append_path_with_origin (path, &glyph->path->cairo, x, y);
584 void
585 TextFont::AppendPath (moon_path *path, gunichar unichar, double x, double y)
587 GlyphInfo *glyph;
589 if (!(glyph = GetGlyphInfo (unichar)))
590 return;
592 AppendPath (path, glyph, x, y);
596 TextFontDescription::TextFontDescription ()
598 changed = true;
599 font = NULL;
601 language = NULL;
602 family = NULL;
603 source = NULL;
605 style = FontStylesNormal;
606 weight = FontWeightsNormal;
607 stretch = FontStretchesNormal;
608 size = 14.666666984558105;
611 TextFontDescription::~TextFontDescription ()
613 g_free (language);
614 g_free (source);
615 g_free (family);
616 delete font;
619 void
620 TextFontDescription::Reload ()
622 // load the new font first - trick to hit the cache for old FontFaces
623 TextFont *ttf = TextFont::Load (this);
624 changed = false;
625 delete font;
626 font = ttf;
629 TextFont *
630 TextFontDescription::GetFont ()
632 if (changed)
633 Reload ();
635 return font;
638 const char *
639 TextFontDescription::GetSource () const
641 return source;
644 bool
645 TextFontDescription::SetSource (const char *source)
647 bool changed;
649 if (source) {
650 if (!this->source || g_ascii_strcasecmp (this->source, source) != 0) {
651 g_free (this->source);
652 this->source = g_strdup (source);
653 this->changed = true;
654 changed = true;
655 } else {
656 changed = false;
658 } else {
659 if (this->source) {
660 g_free (this->source);
661 this->source = NULL;
662 this->changed = true;
663 changed = true;
664 } else {
665 changed = false;
669 return changed;
672 char **
673 TextFontDescription::GetFamilies () const
675 char **families;
677 if (!family)
678 return NULL;
680 if ((families = g_strsplit (family, ",", -1))) {
681 for (int i = 0; families[i]; i++)
682 g_strstrip (families[i]);
685 return families;
688 const char *
689 TextFontDescription::GetFamily () const
691 return family;
694 bool
695 TextFontDescription::SetFamily (const char *family)
697 bool changed;
699 if (family) {
700 if (!this->family || g_ascii_strcasecmp (this->family, family) != 0) {
701 g_free (this->family);
702 this->family = g_strdup (family);
703 this->changed = true;
704 changed = true;
705 } else {
706 changed = false;
708 } else {
709 if (this->family) {
710 g_free (this->family);
711 this->family = NULL;
712 this->changed = true;
713 changed = true;
714 } else {
715 changed = false;
719 return changed;
722 const char *
723 TextFontDescription::GetLanguage () const
725 return language;
728 bool
729 TextFontDescription::SetLanguage (const char *lang)
731 bool changed;
732 char *canon;
734 if (lang) {
735 canon = canon_lang (lang);
737 if (!this->language || g_ascii_strcasecmp (this->language, canon) != 0) {
738 g_free (this->language);
739 this->language = canon;
740 this->changed = true;
741 changed = true;
742 } else {
743 g_free (canon);
744 changed = false;
746 } else {
747 if (this->language) {
748 g_free (this->language);
749 this->language = NULL;
750 this->changed = true;
751 changed = true;
752 } else {
753 changed = false;
757 return changed;
760 FontStyles
761 TextFontDescription::GetStyle () const
763 return style;
766 bool
767 TextFontDescription::SetStyle (FontStyles style)
769 bool changed = this->style != style;
771 if (changed) {
772 this->style = style;
773 this->changed = true;
776 return changed;
779 FontWeights
780 TextFontDescription::GetWeight () const
782 return weight;
785 bool
786 TextFontDescription::SetWeight (FontWeights weight)
788 bool changed = this->weight != weight;
790 if (changed) {
791 this->weight = weight;
792 this->changed = true;
795 return changed;
798 FontStretches
799 TextFontDescription::GetStretch () const
801 return stretch;
804 bool
805 TextFontDescription::SetStretch (FontStretches stretch)
807 bool changed = this->stretch != stretch;
809 if (changed) {
810 this->stretch = stretch;
811 this->changed = true;
814 return changed;
817 double
818 TextFontDescription::GetSize () const
820 return size;
823 bool
824 TextFontDescription::SetSize (double size)
826 bool changed = this->size != size;
828 if (font)
829 font->SetSize (size);
831 this->size = size;
833 return changed;