2009-10-09 Chris Toshok <toshok@ximian.com>
[moon.git] / src / glyphs.cpp
blobb7717ba2aa03967878fdea55b9573eb73fa2b2d2
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * glyphs.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <cairo.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 #include <errno.h>
21 #include <math.h>
23 #include "file-downloader.h"
24 #include "runtime.h"
25 #include "glyphs.h"
26 #include "utils.h"
27 #include "debug.h"
28 #include "uri.h"
29 #include "geometry.h"
31 #if DEBUG
32 #define d(x) x
33 #else
34 #define d(x)
35 #endif
37 #define ORIGIN_IS_SET(x) ((x) > -HUGE)
40 // Glyphs
43 enum GlyphAttrMask {
44 Cluster = 1 << 1,
45 Index = 1 << 2,
46 Advance = 1 << 3,
47 uOffset = 1 << 4,
48 vOffset = 1 << 5,
51 class GlyphAttr : public List::Node {
52 public:
53 guint32 glyph_count;
54 guint32 code_units;
55 guint32 index;
56 double advance;
57 double uoffset;
58 double voffset;
59 guint8 set;
61 GlyphAttr ();
64 GlyphAttr::GlyphAttr ()
66 glyph_count = 1;
67 code_units = 1;
68 set = 0;
71 Glyphs::Glyphs ()
73 SetObjectType (Type::GLYPHS);
75 downloader = NULL;
77 fill = NULL;
78 path = NULL;
80 attrs = new List ();
81 text = NULL;
82 font = NULL;
84 height = 0.0;
85 width = 0.0;
86 left = 0.0;
87 top = 0.0;
89 uri_changed = false;
90 invalid = false;
91 dirty = false;
94 Glyphs::~Glyphs ()
96 CleanupDownloader ();
98 if (path)
99 moon_path_destroy (path);
101 attrs->Clear (true);
102 delete attrs;
104 g_free (text);
106 delete font;
109 void
110 Glyphs::CleanupDownloader ()
112 if (downloader) {
113 downloader->RemoveHandler (Downloader::CompletedEvent, downloader_complete, this);
114 downloader->Abort ();
115 downloader->unref ();
116 downloader = NULL;
120 void
121 Glyphs::Layout ()
123 double size = GetFontRenderingEmSize ();
124 guint32 code_units, glyph_count, i;
125 bool first_char = true;
126 double x0, x1, y0, y1;
127 double bottom, right;
128 double bottom0, top0;
129 GlyphInfo *glyph;
130 GlyphAttr *attr;
131 int nglyphs = 0;
132 double offset;
133 bool cluster;
134 double scale;
135 int n = 0;
137 invalid = false;
138 dirty = false;
140 height = 0.0;
141 width = 0.0;
142 left = 0.0;
143 top = 0.0;
145 if (path) {
146 moon_path_destroy (path);
147 path = NULL;
150 if (!font) {
151 // required font fields have not been set
152 return;
155 if (((!text || !text[0]) && attrs->IsEmpty ())) {
156 // no glyphs to render
157 return;
160 if (fill == NULL) {
161 // no fill specified (unlike TextBlock, there is no default brush)
162 return;
165 // scale Advance, uOffset and vOffset units to pixels
166 scale = round (size) / 100.0;
168 x0 = GetOriginX ();
169 if (!ORIGIN_IS_SET (x0))
170 x0 = 0.0;
172 right = x0;
173 left = x0;
175 // OriginY is the baseline if specified
176 y0 = GetOriginY ();
177 if (ORIGIN_IS_SET (y0)) {
178 top0 = y0 - font->Ascender ();
179 } else {
180 y0 = font->Ascender ();
181 top0 = 0.0;
184 bottom0 = top0 + font->Height ();
186 bottom = bottom0;
187 top = top0;
189 path = moon_path_new (16);
191 attr = (GlyphAttr *) attrs->First ();
193 if (text && text[0]) {
194 gunichar *c = text;
196 while (*c != 0) {
197 if (attr && (attr->set & Cluster)) {
198 // get the cluster's GlyphCount and CodeUnitCount
199 glyph_count = attr->glyph_count;
200 code_units = attr->code_units;
201 } else {
202 glyph_count = 1;
203 code_units = 1;
206 if (glyph_count == 1 && code_units == 1)
207 cluster = false;
208 else
209 cluster = true;
211 // render the glyph cluster
212 i = 0;
213 do {
214 if (attr && (attr->set & Index)) {
215 if (!(glyph = font->GetGlyphInfoByIndex (attr->index)))
216 goto next1;
217 } else if (cluster) {
218 // indexes MUST be specified for each glyph in a cluster
219 moon_path_destroy (path);
220 invalid = true;
221 path = NULL;
222 return;
223 } else {
224 if (!(glyph = font->GetGlyphInfo (*c)))
225 goto next1;
228 y1 = y0;
229 if (attr && (attr->set & vOffset)) {
230 offset = -(attr->voffset * scale);
231 bottom = MAX (bottom, bottom0 + offset);
232 top = MIN (top, top0 + offset);
233 y1 += offset;
236 if (attr && (attr->set & uOffset)) {
237 offset = (attr->uoffset * scale);
238 left = MIN (left, x0 + offset);
239 x1 = x0 + offset;
240 } else if (first_char) {
241 if (glyph->metrics.horiBearingX < 0)
242 x0 -= glyph->metrics.horiBearingX;
244 first_char = false;
245 x1 = x0;
246 } else {
247 x1 = x0;
250 right = MAX (right, x1 + glyph->metrics.horiAdvance);
252 font->AppendPath (path, glyph, x1, y1);
253 nglyphs++;
255 if (attr && (attr->set & Advance))
256 x0 += attr->advance * scale;
257 else
258 x0 += glyph->metrics.horiAdvance;
260 next1:
262 attr = attr ? (GlyphAttr *) attr->next : NULL;
263 i++;
265 if (i == glyph_count)
266 break;
268 if (!attr) {
269 // there MUST be an attr for each glyph in a cluster
270 moon_path_destroy (path);
271 invalid = true;
272 path = NULL;
273 return;
276 if ((attr->set & Cluster)) {
277 // only the first glyph in a cluster may specify a cluster mapping
278 moon_path_destroy (path);
279 invalid = true;
280 path = NULL;
281 return;
283 } while (true);
285 // consume the code units
286 for (i = 0; i < code_units && *c != 0; i++)
287 c++;
289 n++;
293 while (attr) {
294 if (attr->set & Cluster) {
295 LOG_TEXT (stderr, "Can't use clusters past the end of the UnicodeString\n");
296 moon_path_destroy (path);
297 invalid = true;
298 path = NULL;
299 return;
302 if (!(attr->set & Index)) {
303 LOG_TEXT (stderr, "No index specified for glyph %d\n", n + 1);
304 moon_path_destroy (path);
305 invalid = true;
306 path = NULL;
307 return;
310 if (!(glyph = font->GetGlyphInfoByIndex (attr->index)))
311 goto next;
313 y1 = y0;
314 if ((attr->set & vOffset)) {
315 offset = -(attr->voffset * scale);
316 bottom = MAX (bottom, bottom0 + offset);
317 top = MIN (top, top0 + offset);
318 y1 += offset;
321 if ((attr->set & uOffset)) {
322 offset = (attr->uoffset * scale);
323 left = MIN (left, x0 + offset);
324 x1 = x0 + offset;
325 } else if (first_char) {
326 if (glyph->metrics.horiBearingX < 0)
327 x0 -= glyph->metrics.horiBearingX;
329 first_char = false;
330 x1 = x0;
331 } else {
332 x1 = x0;
335 right = MAX (right, x1 + glyph->metrics.horiAdvance);
337 font->AppendPath (path, glyph, x1, y1);
338 nglyphs++;
340 if ((attr->set & Advance))
341 x0 += attr->advance * scale;
342 else
343 x0 += glyph->metrics.horiAdvance;
345 next:
347 attr = (GlyphAttr *) attr->next;
348 n++;
351 if (nglyphs > 0) {
352 height = bottom - top;
353 width = right - left;
354 } else {
355 moon_path_destroy (path);
356 path = NULL;
360 void
361 Glyphs::GetSizeForBrush (cairo_t *cr, double *width, double *height)
363 if (dirty)
364 Layout ();
366 *height = this->height;
367 *width = this->width;
370 Point
371 Glyphs::GetOriginPoint ()
373 double x0 = GetOriginX ();
374 double y0 = GetOriginY ();
376 if (!ORIGIN_IS_SET (x0))
377 x0 = 0.0;
379 if (ORIGIN_IS_SET (y0)) {
380 double ascend = font ? font->Ascender () : 0.0;
382 return Point (x0, y0 - ascend);
383 } else {
384 return Point (x0, 0);
388 void
389 Glyphs::Render (cairo_t *cr, Region *region, bool path_only)
391 if (width == 0.0 && height == 0.0)
392 return;
394 if (invalid) {
395 // do not render anything if our state is invalid to keep with Silverlight's behavior.
396 // (Note: rendering code also assumes everything is kosher)
397 return;
400 if (path == NULL || path->cairo.num_data == 0) {
401 // No glyphs to render
402 return;
405 cairo_save (cr);
406 cairo_set_matrix (cr, &absolute_xform);
408 if (!path_only)
409 RenderLayoutClip (cr);
411 Rect area = Rect (left, top, width, height);
412 fill->SetupBrush (cr, area);
414 cairo_append_path (cr, &path->cairo);
415 fill->Fill (cr);
417 cairo_restore (cr);
420 Size
421 Glyphs::ComputeActualSize ()
423 if (dirty)
424 Layout ();
426 return Size (left + width, top + height);
429 Size
430 Glyphs::MeasureOverride (Size availableSize)
432 if (dirty)
433 Layout ();
435 return Size (left + width, top + height).Min (availableSize);
438 Size
439 Glyphs::ArrangeOverride (Size finalSize)
441 if (dirty)
442 Layout ();
444 finalSize = ApplySizeConstraints (finalSize);
445 return Size (left + width, top + height).Max (finalSize);
448 void
449 Glyphs::ComputeBounds ()
451 if (dirty)
452 Layout ();
454 bounds = IntersectBoundsWithClipPath (Rect (left, top, width, height), false).Transform (&absolute_xform);
457 bool
458 Glyphs::InsideObject (cairo_t *cr, double x, double y)
460 double nx = x;
461 double ny = y;
463 TransformPoint (&nx, &ny);
465 return (nx >= left && ny >= top && nx < left + width && ny < top + height);
468 Point
469 Glyphs::GetTransformOrigin ()
471 // Glyphs seems to always use 0,0 no matter what is specified in the RenderTransformOrigin nor the OriginX/Y points
472 return Point (0,0);
475 void
476 Glyphs::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
478 if (prop && prop->GetId () == Glyphs::FillProperty) {
479 Invalidate ();
480 } else {
481 FrameworkElement::OnSubPropertyChanged (prop, obj, subobj_args);
485 void
486 Glyphs::LoadFont (const Uri *uri, const char *path)
488 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
489 StyleSimulations simulate = GetStyleSimulations ();
490 double size = GetFontRenderingEmSize ();
491 char *resource;
492 int index;
494 if (uri->GetFragment ()) {
495 if ((index = strtol (uri->GetFragment (), NULL, 10)) < 0 || index == G_MAXINT)
496 index = 0;
497 } else {
498 index = 0;
501 resource = uri->ToString ((UriToStringFlags) (UriHidePasswd | UriHideFragment | UriHideQuery));
502 manager->AddResource (resource, path);
503 font = TextFont::Load (resource, index, size, simulate);
504 g_free (resource);
507 void
508 Glyphs::downloader_complete (EventObject *sender, EventArgs *calldata, gpointer closure)
510 ((Glyphs *) closure)->DownloaderComplete ();
513 void
514 Glyphs::DownloaderComplete ()
516 Uri *uri = GetFontUri ();
517 char *filename;
519 delete font;
520 font = NULL;
522 // get the downloaded file path
523 if (!(filename = downloader->GetDownloadedFilename (NULL))) {
524 UpdateBounds (true);
525 Invalidate ();
526 dirty = true;
527 return;
530 LoadFont (uri, filename);
531 g_free (filename);
533 UpdateBounds (true);
534 Invalidate ();
535 dirty = true;
538 #if DEBUG
539 static void
540 print_parse_error (const char *in, const char *where, const char *reason)
542 if (debug_flags & RUNTIME_DEBUG_TEXT) {
543 fprintf (stderr, "Glyph Indices parse error: \"%s\": %s\n", in, reason);
544 fprintf (stderr, " ");
545 for (int i = 0; i < (where - in); i++)
546 fputc (' ', stderr);
547 fprintf (stderr, "^\n");
550 #endif
552 void
553 Glyphs::SetIndicesInternal (const char *in)
555 register const char *inptr = in;
556 GlyphAttr *glyph;
557 double value;
558 char *end;
559 uint bit;
560 int n;
562 attrs->Clear (true);
564 if (in == NULL)
565 return;
567 while (g_ascii_isspace (*inptr))
568 inptr++;
570 while (*inptr) {
571 glyph = new GlyphAttr ();
573 while (g_ascii_isspace (*inptr))
574 inptr++;
576 // check for a cluster
577 if (*inptr == '(') {
578 inptr++;
579 while (g_ascii_isspace (*inptr))
580 inptr++;
582 errno = 0;
583 glyph->code_units = strtoul (inptr, &end, 10);
584 if (glyph->code_units == 0 || (glyph->code_units == LONG_MAX && errno != 0)) {
585 // invalid cluster
586 d(print_parse_error (in, inptr, errno ? strerror (errno) : "invalid cluster mapping; CodeUnitCount cannot be 0"));
587 delete glyph;
588 return;
591 inptr = end;
592 while (g_ascii_isspace (*inptr))
593 inptr++;
595 if (*inptr != ':') {
596 // invalid cluster
597 d(print_parse_error (in, inptr, "expected ':'"));
598 delete glyph;
599 return;
602 inptr++;
603 while (g_ascii_isspace (*inptr))
604 inptr++;
606 errno = 0;
607 glyph->glyph_count = strtoul (inptr, &end, 10);
608 if (glyph->glyph_count == 0 || (glyph->glyph_count == LONG_MAX && errno != 0)) {
609 // invalid cluster
610 d(print_parse_error (in, inptr, errno ? strerror (errno) : "invalid cluster mapping; GlyphCount cannot be 0"));
611 delete glyph;
612 return;
615 inptr = end;
616 while (g_ascii_isspace (*inptr))
617 inptr++;
619 if (*inptr != ')') {
620 // invalid cluster
621 d(print_parse_error (in, inptr, "expected ')'"));
622 delete glyph;
623 return;
626 glyph->set |= Cluster;
627 inptr++;
629 while (g_ascii_isspace (*inptr))
630 inptr++;
633 if (*inptr >= '0' && *inptr <= '9') {
634 errno = 0;
635 glyph->index = strtoul (inptr, &end, 10);
636 if ((glyph->index == 0 || glyph->index == LONG_MAX) && errno != 0) {
637 // invalid glyph index
638 d(print_parse_error (in, inptr, strerror (errno)));
639 delete glyph;
640 return;
643 glyph->set |= Index;
645 inptr = end;
646 while (g_ascii_isspace (*inptr))
647 inptr++;
650 bit = (uint) Advance;
651 n = 0;
653 while (*inptr == ',' && n < 3) {
654 inptr++;
655 while (g_ascii_isspace (*inptr))
656 inptr++;
658 if (*inptr != ',') {
659 value = g_ascii_strtod (inptr, &end);
660 if ((value == 0.0 || value == HUGE_VAL || value == -HUGE_VAL) && errno != 0) {
661 // invalid advance or offset
662 d(print_parse_error (in, inptr, strerror (errno)));
663 delete glyph;
664 return;
666 } else {
667 end = (char *) inptr;
670 if (end > inptr) {
671 switch ((GlyphAttrMask) bit) {
672 case Advance:
673 glyph->advance = value;
674 glyph->set |= Advance;
675 break;
676 case uOffset:
677 glyph->uoffset = value;
678 glyph->set |= uOffset;
679 break;
680 case vOffset:
681 glyph->voffset = value;
682 glyph->set |= vOffset;
683 break;
684 default:
685 break;
689 inptr = end;
690 while (g_ascii_isspace (*inptr))
691 inptr++;
693 bit <<= 1;
694 n++;
697 attrs->Append (glyph);
699 while (g_ascii_isspace (*inptr))
700 inptr++;
702 if (*inptr && *inptr != ';') {
703 d(print_parse_error (in, inptr, "expected ';'"));
704 return;
707 if (*inptr == '\0')
708 break;
710 inptr++;
714 void
715 Glyphs::DownloadFont (Surface *surface, Uri *uri)
717 if ((downloader = surface->CreateDownloader ())) {
718 char *str = uri->ToString (UriHideFragment);
719 downloader->Open ("GET", str, FontPolicy);
720 g_free (str);
722 downloader->AddHandler (downloader->CompletedEvent, downloader_complete, this);
723 if (downloader->Started () || downloader->Completed ()) {
724 if (downloader->Completed ())
725 DownloaderComplete ();
726 } else {
727 // This is what actually triggers the download
728 downloader->Send ();
730 } else {
731 // we're shutting down
735 bool
736 Glyphs::SetFontResource (const Uri *uri)
738 Application *application = Application::GetCurrent ();
739 char *path;
741 if (!application || !(path = application->GetResourceAsPath (GetResourceBase(), uri)))
742 return false;
744 LoadFont (uri, path);
745 g_free (path);
747 return true;
750 void
751 Glyphs::SetSurface (Surface *surface)
753 Uri *uri;
755 if (GetSurface () == surface)
756 return;
758 FrameworkElement::SetSurface (surface);
760 if (!uri_changed || !surface)
761 return;
763 if ((uri = GetFontUri ()))
764 DownloadFont (surface, uri);
766 uri_changed = false;
769 void
770 Glyphs::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
772 bool invalidate = true;
774 if (args->GetProperty ()->GetOwnerType() != Type::GLYPHS) {
775 FrameworkElement::OnPropertyChanged (args, error);
776 return;
779 if (args->GetId () == Glyphs::FontUriProperty) {
780 Uri *uri = args->GetNewValue() ? args->GetNewValue()->AsUri () : NULL;
781 Surface *surface = GetSurface ();
783 CleanupDownloader ();
784 dirty = true;
785 delete font;
786 font = NULL;
788 if (!Uri::IsNullOrEmpty (uri)) {
789 if (!SetFontResource (uri)) {
790 // need to create a downloader for this font...
791 if (surface) {
792 DownloadFont (surface, uri);
793 uri_changed = false;
794 } else {
795 // queue a font download
796 uri_changed = true;
798 } else {
799 uri_changed = false;
801 } else {
802 uri_changed = false;
804 } else if (args->GetId () == Glyphs::FillProperty) {
805 fill = args->GetNewValue() ? args->GetNewValue()->AsBrush() : NULL;
806 } else if (args->GetId () == Glyphs::UnicodeStringProperty) {
807 const char *str = args->GetNewValue() ? args->GetNewValue()->AsString () : NULL;
808 g_free (text);
810 if (str != NULL)
811 text = g_utf8_to_ucs4_fast (str, -1, NULL);
812 else
813 text = NULL;
815 dirty = true;
816 } else if (args->GetId () == Glyphs::IndicesProperty) {
817 const char *str = args->GetNewValue() ? args->GetNewValue()->AsString () : NULL;
818 SetIndicesInternal (str);
819 dirty = true;
820 } else if (args->GetId () == Glyphs::FontRenderingEmSizeProperty) {
821 if (font != NULL)
822 dirty = font->SetSize (args->GetNewValue ()->AsDouble ());
823 else
824 dirty = true;
825 } else if (args->GetId () == Glyphs::OriginXProperty) {
826 dirty = true;
827 } else if (args->GetId () == Glyphs::OriginYProperty) {
828 dirty = true;
829 } else if (args->GetId () == Glyphs::StyleSimulationsProperty) {
830 StyleSimulations simulate = (StyleSimulations) args->GetNewValue ()->AsInt32 ();
832 // clear any unsupported flags
833 simulate = (StyleSimulations) (simulate & StyleSimulationsBoldItalic);
835 if (font != NULL)
836 dirty = font->SetStyleSimulations (simulate);
837 else
838 dirty = true;
841 if (invalidate)
842 Invalidate ();
844 if (dirty)
845 UpdateBounds (true);
847 NotifyListenersOfPropertyChange (args, error);