2009-12-03 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / glyphs.cpp
blob4272d2af617747499fb20d856c97f58b7b5c47bb
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"
30 #include "deployment.h"
32 #if DEBUG
33 #define d(x) x
34 #else
35 #define d(x)
36 #endif
38 #define ORIGIN_IS_SET(x) ((x) > -HUGE)
41 // Glyphs
44 enum GlyphAttrMask {
45 Cluster = 1 << 1,
46 Index = 1 << 2,
47 Advance = 1 << 3,
48 uOffset = 1 << 4,
49 vOffset = 1 << 5,
52 class GlyphAttr : public List::Node {
53 public:
54 guint32 glyph_count;
55 guint32 code_units;
56 guint32 index;
57 double advance;
58 double uoffset;
59 double voffset;
60 guint8 set;
62 GlyphAttr ();
65 GlyphAttr::GlyphAttr ()
67 glyph_count = 1;
68 code_units = 1;
69 set = 0;
72 Glyphs::Glyphs ()
74 SetObjectType (Type::GLYPHS);
76 downloader = NULL;
78 fill = NULL;
79 path = NULL;
81 attrs = new List ();
82 text = NULL;
83 font = NULL;
85 height = 0.0;
86 width = 0.0;
87 left = 0.0;
88 top = 0.0;
90 uri_changed = false;
91 invalid = false;
92 dirty = false;
95 Glyphs::~Glyphs ()
97 CleanupDownloader ();
99 if (path)
100 moon_path_destroy (path);
102 attrs->Clear (true);
103 delete attrs;
105 g_free (text);
107 delete font;
110 void
111 Glyphs::CleanupDownloader ()
113 if (downloader) {
114 downloader->RemoveHandler (Downloader::CompletedEvent, downloader_complete, this);
115 downloader->Abort ();
116 downloader->unref ();
117 downloader = NULL;
121 void
122 Glyphs::Layout ()
124 double size = GetFontRenderingEmSize ();
125 guint32 code_units, glyph_count, i;
126 bool first_char = true;
127 double x0, x1, y0, y1;
128 double bottom, right;
129 double bottom0, top0;
130 GlyphInfo *glyph;
131 GlyphAttr *attr;
132 int nglyphs = 0;
133 double offset;
134 bool cluster;
135 double scale;
136 int n = 0;
138 invalid = false;
139 dirty = false;
141 height = 0.0;
142 width = 0.0;
143 left = 0.0;
144 top = 0.0;
146 if (path) {
147 moon_path_destroy (path);
148 path = NULL;
151 if (!font) {
152 // required font fields have not been set
153 return;
156 if (((!text || !text[0]) && attrs->IsEmpty ())) {
157 // no glyphs to render
158 return;
161 if (fill == NULL) {
162 // no fill specified (unlike TextBlock, there is no default brush)
163 return;
166 // scale Advance, uOffset and vOffset units to pixels
167 scale = round (size) / 100.0;
169 x0 = GetOriginX ();
170 if (!ORIGIN_IS_SET (x0))
171 x0 = 0.0;
173 right = x0;
174 left = x0;
176 // OriginY is the baseline if specified
177 y0 = GetOriginY ();
178 if (ORIGIN_IS_SET (y0)) {
179 top0 = y0 - font->Ascender ();
180 } else {
181 y0 = font->Ascender ();
182 top0 = 0.0;
185 bottom0 = top0 + font->Height ();
187 bottom = bottom0;
188 top = top0;
190 path = moon_path_new (16);
192 attr = (GlyphAttr *) attrs->First ();
194 if (text && text[0]) {
195 gunichar *c = text;
197 while (*c != 0) {
198 if (attr && (attr->set & Cluster)) {
199 // get the cluster's GlyphCount and CodeUnitCount
200 glyph_count = attr->glyph_count;
201 code_units = attr->code_units;
202 } else {
203 glyph_count = 1;
204 code_units = 1;
207 if (glyph_count == 1 && code_units == 1)
208 cluster = false;
209 else
210 cluster = true;
212 // render the glyph cluster
213 i = 0;
214 do {
215 if (attr && (attr->set & Index)) {
216 if (!(glyph = font->GetGlyphInfoByIndex (attr->index)))
217 goto next1;
218 } else if (cluster) {
219 // indexes MUST be specified for each glyph in a cluster
220 moon_path_destroy (path);
221 invalid = true;
222 path = NULL;
223 return;
224 } else {
225 if (!(glyph = font->GetGlyphInfo (*c)))
226 goto next1;
229 y1 = y0;
230 if (attr && (attr->set & vOffset)) {
231 offset = -(attr->voffset * scale);
232 bottom = MAX (bottom, bottom0 + offset);
233 top = MIN (top, top0 + offset);
234 y1 += offset;
237 if (attr && (attr->set & uOffset)) {
238 offset = (attr->uoffset * scale);
239 left = MIN (left, x0 + offset);
240 x1 = x0 + offset;
241 } else if (first_char) {
242 if (glyph->metrics.horiBearingX < 0)
243 x0 -= glyph->metrics.horiBearingX;
245 first_char = false;
246 x1 = x0;
247 } else {
248 x1 = x0;
251 right = MAX (right, x1 + glyph->metrics.horiAdvance);
253 font->AppendPath (path, glyph, x1, y1);
254 nglyphs++;
256 if (attr && (attr->set & Advance))
257 x0 += attr->advance * scale;
258 else
259 x0 += glyph->metrics.horiAdvance;
261 next1:
263 attr = attr ? (GlyphAttr *) attr->next : NULL;
264 i++;
266 if (i == glyph_count)
267 break;
269 if (!attr) {
270 // there MUST be an attr for each glyph in a cluster
271 moon_path_destroy (path);
272 invalid = true;
273 path = NULL;
274 return;
277 if ((attr->set & Cluster)) {
278 // only the first glyph in a cluster may specify a cluster mapping
279 moon_path_destroy (path);
280 invalid = true;
281 path = NULL;
282 return;
284 } while (true);
286 // consume the code units
287 for (i = 0; i < code_units && *c != 0; i++)
288 c++;
290 n++;
294 while (attr) {
295 if (attr->set & Cluster) {
296 LOG_TEXT (stderr, "Can't use clusters past the end of the UnicodeString\n");
297 moon_path_destroy (path);
298 invalid = true;
299 path = NULL;
300 return;
303 if (!(attr->set & Index)) {
304 LOG_TEXT (stderr, "No index specified for glyph %d\n", n + 1);
305 moon_path_destroy (path);
306 invalid = true;
307 path = NULL;
308 return;
311 if (!(glyph = font->GetGlyphInfoByIndex (attr->index)))
312 goto next;
314 y1 = y0;
315 if ((attr->set & vOffset)) {
316 offset = -(attr->voffset * scale);
317 bottom = MAX (bottom, bottom0 + offset);
318 top = MIN (top, top0 + offset);
319 y1 += offset;
322 if ((attr->set & uOffset)) {
323 offset = (attr->uoffset * scale);
324 left = MIN (left, x0 + offset);
325 x1 = x0 + offset;
326 } else if (first_char) {
327 if (glyph->metrics.horiBearingX < 0)
328 x0 -= glyph->metrics.horiBearingX;
330 first_char = false;
331 x1 = x0;
332 } else {
333 x1 = x0;
336 right = MAX (right, x1 + glyph->metrics.horiAdvance);
338 font->AppendPath (path, glyph, x1, y1);
339 nglyphs++;
341 if ((attr->set & Advance))
342 x0 += attr->advance * scale;
343 else
344 x0 += glyph->metrics.horiAdvance;
346 next:
348 attr = (GlyphAttr *) attr->next;
349 n++;
352 if (nglyphs > 0) {
353 height = bottom - top;
354 width = right - left;
355 } else {
356 moon_path_destroy (path);
357 path = NULL;
361 void
362 Glyphs::GetSizeForBrush (cairo_t *cr, double *width, double *height)
364 if (dirty)
365 Layout ();
367 *height = this->height;
368 *width = this->width;
371 Point
372 Glyphs::GetOriginPoint ()
374 double x0 = GetOriginX ();
375 double y0 = GetOriginY ();
377 if (!ORIGIN_IS_SET (x0))
378 x0 = 0.0;
380 if (ORIGIN_IS_SET (y0)) {
381 double ascend = font ? font->Ascender () : 0.0;
383 return Point (x0, y0 - ascend);
384 } else {
385 return Point (x0, 0);
389 void
390 Glyphs::Render (cairo_t *cr, Region *region, bool path_only)
392 if (width == 0.0 && height == 0.0)
393 return;
395 if (invalid) {
396 // do not render anything if our state is invalid to keep with Silverlight's behavior.
397 // (Note: rendering code also assumes everything is kosher)
398 return;
401 if (path == NULL || path->cairo.num_data == 0) {
402 // No glyphs to render
403 return;
406 cairo_save (cr);
407 cairo_set_matrix (cr, &absolute_xform);
409 if (!path_only)
410 RenderLayoutClip (cr);
412 Rect area = Rect (left, top, width, height);
413 fill->SetupBrush (cr, area);
415 cairo_append_path (cr, &path->cairo);
416 fill->Fill (cr);
418 cairo_restore (cr);
421 Size
422 Glyphs::ComputeActualSize ()
424 if (dirty)
425 Layout ();
427 return Size (left + width, top + height);
430 Size
431 Glyphs::MeasureOverride (Size availableSize)
433 if (dirty)
434 Layout ();
436 return Size (left + width, top + height).Min (availableSize);
439 Size
440 Glyphs::ArrangeOverride (Size finalSize)
442 if (dirty)
443 Layout ();
445 finalSize = ApplySizeConstraints (finalSize);
446 return Size (left + width, top + height).Max (finalSize);
449 void
450 Glyphs::ComputeBounds ()
452 if (dirty)
453 Layout ();
455 bounds = IntersectBoundsWithClipPath (Rect (left, top, width, height), false).Transform (&absolute_xform);
458 Point
459 Glyphs::GetTransformOrigin ()
461 // Glyphs seems to always use 0,0 no matter what is specified in the RenderTransformOrigin nor the OriginX/Y points
462 return Point (0,0);
465 void
466 Glyphs::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
468 if (prop && prop->GetId () == Glyphs::FillProperty) {
469 Invalidate ();
470 } else {
471 FrameworkElement::OnSubPropertyChanged (prop, obj, subobj_args);
475 void
476 Glyphs::LoadFont (const Uri *uri, const char *path)
478 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
479 StyleSimulations simulate = GetStyleSimulations ();
480 double size = GetFontRenderingEmSize ();
481 char *resource;
482 int index;
484 if (uri->GetFragment ()) {
485 if ((index = strtol (uri->GetFragment (), NULL, 10)) < 0 || index == G_MAXINT)
486 index = 0;
487 } else {
488 index = 0;
491 resource = uri->ToString ((UriToStringFlags) (UriHidePasswd | UriHideFragment | UriHideQuery));
492 manager->AddResource (resource, path);
493 font = TextFont::Load (resource, index, size, simulate);
494 g_free (resource);
497 void
498 Glyphs::downloader_complete (EventObject *sender, EventArgs *calldata, gpointer closure)
500 ((Glyphs *) closure)->DownloaderComplete ();
503 void
504 Glyphs::DownloaderComplete ()
506 Uri *uri = GetFontUri ();
507 char *filename;
509 delete font;
510 font = NULL;
512 // get the downloaded file path
513 if (!(filename = downloader->GetDownloadedFilename (NULL))) {
514 UpdateBounds (true);
515 Invalidate ();
516 dirty = true;
517 return;
520 LoadFont (uri, filename);
521 g_free (filename);
523 UpdateBounds (true);
524 Invalidate ();
525 dirty = true;
528 #if DEBUG
529 static void
530 print_parse_error (const char *in, const char *where, const char *reason)
532 if (debug_flags & RUNTIME_DEBUG_TEXT) {
533 fprintf (stderr, "Glyph Indices parse error: \"%s\": %s\n", in, reason);
534 fprintf (stderr, " ");
535 for (int i = 0; i < (where - in); i++)
536 fputc (' ', stderr);
537 fprintf (stderr, "^\n");
540 #endif
542 void
543 Glyphs::SetIndicesInternal (const char *in)
545 register const char *inptr = in;
546 GlyphAttr *glyph;
547 double value = 0;
548 char *end;
549 uint bit;
550 int n;
552 attrs->Clear (true);
554 if (in == NULL)
555 return;
557 while (g_ascii_isspace (*inptr))
558 inptr++;
560 while (*inptr) {
561 glyph = new GlyphAttr ();
563 while (g_ascii_isspace (*inptr))
564 inptr++;
566 // check for a cluster
567 if (*inptr == '(') {
568 inptr++;
569 while (g_ascii_isspace (*inptr))
570 inptr++;
572 errno = 0;
573 glyph->code_units = strtoul (inptr, &end, 10);
574 if (glyph->code_units == 0 || (glyph->code_units == LONG_MAX && errno != 0)) {
575 // invalid cluster
576 d(print_parse_error (in, inptr, errno ? strerror (errno) : "invalid cluster mapping; CodeUnitCount cannot be 0"));
577 delete glyph;
578 return;
581 inptr = end;
582 while (g_ascii_isspace (*inptr))
583 inptr++;
585 if (*inptr != ':') {
586 // invalid cluster
587 d(print_parse_error (in, inptr, "expected ':'"));
588 delete glyph;
589 return;
592 inptr++;
593 while (g_ascii_isspace (*inptr))
594 inptr++;
596 errno = 0;
597 glyph->glyph_count = strtoul (inptr, &end, 10);
598 if (glyph->glyph_count == 0 || (glyph->glyph_count == LONG_MAX && errno != 0)) {
599 // invalid cluster
600 d(print_parse_error (in, inptr, errno ? strerror (errno) : "invalid cluster mapping; GlyphCount cannot be 0"));
601 delete glyph;
602 return;
605 inptr = end;
606 while (g_ascii_isspace (*inptr))
607 inptr++;
609 if (*inptr != ')') {
610 // invalid cluster
611 d(print_parse_error (in, inptr, "expected ')'"));
612 delete glyph;
613 return;
616 glyph->set |= Cluster;
617 inptr++;
619 while (g_ascii_isspace (*inptr))
620 inptr++;
623 if (*inptr >= '0' && *inptr <= '9') {
624 errno = 0;
625 glyph->index = strtoul (inptr, &end, 10);
626 if ((glyph->index == 0 || glyph->index == LONG_MAX) && errno != 0) {
627 // invalid glyph index
628 d(print_parse_error (in, inptr, strerror (errno)));
629 delete glyph;
630 return;
633 glyph->set |= Index;
635 inptr = end;
636 while (g_ascii_isspace (*inptr))
637 inptr++;
640 bit = (uint) Advance;
641 n = 0;
643 while (*inptr == ',' && n < 3) {
644 inptr++;
645 while (g_ascii_isspace (*inptr))
646 inptr++;
648 if (*inptr != ',') {
649 value = g_ascii_strtod (inptr, &end);
650 if ((value == 0.0 || value == HUGE_VAL || value == -HUGE_VAL) && errno != 0) {
651 // invalid advance or offset
652 d(print_parse_error (in, inptr, strerror (errno)));
653 delete glyph;
654 return;
656 } else {
657 end = (char *) inptr;
660 if (end > inptr) {
661 switch ((GlyphAttrMask) bit) {
662 case Advance:
663 glyph->advance = value;
664 glyph->set |= Advance;
665 break;
666 case uOffset:
667 glyph->uoffset = value;
668 glyph->set |= uOffset;
669 break;
670 case vOffset:
671 glyph->voffset = value;
672 glyph->set |= vOffset;
673 break;
674 default:
675 break;
679 inptr = end;
680 while (g_ascii_isspace (*inptr))
681 inptr++;
683 bit <<= 1;
684 n++;
687 attrs->Append (glyph);
689 while (g_ascii_isspace (*inptr))
690 inptr++;
692 if (*inptr && *inptr != ';') {
693 d(print_parse_error (in, inptr, "expected ';'"));
694 return;
697 if (*inptr == '\0')
698 break;
700 inptr++;
704 void
705 Glyphs::DownloadFont (Uri *uri, MoonError *error)
707 Surface *surface = GetDeployment ()->GetSurface ();
709 if ((downloader = surface->CreateDownloader ())) {
710 char *str = uri->ToString (UriHideFragment);
711 downloader->Open ("GET", str, FontPolicy);
712 g_free (str);
714 if (downloader->GetFailedMessage () != NULL) {
715 MoonError::FillIn (error, MoonError::ARGUMENT_OUT_OF_RANGE, 1000, downloader->GetFailedMessage ());
716 downloader->unref ();
717 downloader = NULL;
718 return;
721 downloader->AddHandler (downloader->CompletedEvent, downloader_complete, this);
722 if (downloader->Started () || downloader->Completed ()) {
723 if (downloader->Completed ())
724 DownloaderComplete ();
725 } else {
726 // This is what actually triggers the download
727 downloader->Send ();
729 } else {
730 // we're shutting down
734 bool
735 Glyphs::SetFontResource (const Uri *uri)
737 Application *application = Application::GetCurrent ();
738 char *path;
740 if (!application || !(path = application->GetResourceAsPath (GetResourceBase(), uri)))
741 return false;
743 LoadFont (uri, path);
744 g_free (path);
746 return true;
749 void
750 Glyphs::SetParent (DependencyObject *parent, MoonError *error)
752 if (parent && IsAttached () && uri_changed) {
753 // we've been added to the tree, kick off any pending
754 // download we may have
755 Uri *uri;
757 if ((uri = GetFontUri ()))
758 DownloadFont (uri, error);
760 uri_changed = false;
762 if (error && error->number)
763 return;
766 FrameworkElement::SetParent (parent, error);
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;
782 CleanupDownloader ();
783 dirty = true;
784 delete font;
785 font = NULL;
787 if (!Uri::IsNullOrEmpty (uri)) {
788 if (!SetFontResource (uri)) {
789 if (uri->IsInvalidPath ()) {
790 if (IsBeingParsed ()) {
791 MoonError::FillIn (error, MoonError::XAML_PARSE_EXCEPTION, 0, "invalid path found in uri");
793 // FIXME: I'm guessing, based on moon-unit tests, that this event should only be emitted
794 // when being parsed from javascript as opposed to managed land...
795 if (IsAttached () && uri->IsUncPath ())
796 GetDeployment ()->GetSurface ()->EmitError (new ParserErrorEventArgs ("invalid uri", NULL, 0, 0, 0, NULL, NULL));
798 } else {
799 // need to create a downloader for this font...
800 if (IsAttached ()) {
801 DownloadFont (uri, IsBeingParsed () ? error : NULL);
802 uri_changed = false;
803 } else {
804 // queue a font download
805 uri_changed = true;
808 } else {
809 uri_changed = false;
811 } else {
812 uri_changed = false;
814 } else if (args->GetId () == Glyphs::FillProperty) {
815 fill = args->GetNewValue() ? args->GetNewValue()->AsBrush() : NULL;
816 } else if (args->GetId () == Glyphs::UnicodeStringProperty) {
817 const char *str = args->GetNewValue() ? args->GetNewValue()->AsString () : NULL;
818 g_free (text);
820 if (str != NULL)
821 text = g_utf8_to_ucs4_fast (str, -1, NULL);
822 else
823 text = NULL;
825 dirty = true;
826 } else if (args->GetId () == Glyphs::IndicesProperty) {
827 const char *str = args->GetNewValue() ? args->GetNewValue()->AsString () : NULL;
828 SetIndicesInternal (str);
829 dirty = true;
830 } else if (args->GetId () == Glyphs::FontRenderingEmSizeProperty) {
831 if (font != NULL)
832 dirty = font->SetSize (args->GetNewValue ()->AsDouble ());
833 else
834 dirty = true;
835 } else if (args->GetId () == Glyphs::OriginXProperty) {
836 dirty = true;
837 } else if (args->GetId () == Glyphs::OriginYProperty) {
838 dirty = true;
839 } else if (args->GetId () == Glyphs::StyleSimulationsProperty) {
840 StyleSimulations simulate = (StyleSimulations) args->GetNewValue ()->AsInt32 ();
842 // clear any unsupported flags
843 simulate = (StyleSimulations) (simulate & StyleSimulationsBoldItalic);
845 if (font != NULL)
846 dirty = font->SetStyleSimulations (simulate);
847 else
848 dirty = true;
851 if (invalidate)
852 Invalidate ();
854 if (dirty)
855 UpdateBounds (true);
857 NotifyListenersOfPropertyChange (args, error);