X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=jwxyz%2Fjwxyz-cocoa.m;h=4585e6584ce231202379e5ef32a529bf0ea6d9f9;hp=10da646f92a81e3b0b0856480b8a1a2b8c338242;hb=4361b69d3178d7fc98d0388f9a223af6c2651aba;hpb=d6b0217f2417bd19187f0ebc389d6c5c2233b11c diff --git a/jwxyz/jwxyz-cocoa.m b/jwxyz/jwxyz-cocoa.m index 10da646f..4585e658 100644 --- a/jwxyz/jwxyz-cocoa.m +++ b/jwxyz/jwxyz-cocoa.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 1991-2016 Jamie Zawinski +/* xscreensaver, Copyright (c) 1991-2017 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -20,6 +20,7 @@ #import "jwxyzI.h" #import "jwxyz-cocoa.h" +#import "utf8wc.h" #include @@ -27,8 +28,19 @@ # import # import # define NSOpenGLContext EAGLContext +# define NSFont UIFont + +# define NSFontTraitMask UIFontDescriptorSymbolicTraits +// The values for the flags for NSFontTraitMask and +// UIFontDescriptorSymbolicTraits match up, not that it really matters here. +# define NSBoldFontMask UIFontDescriptorTraitBold +# define NSFixedPitchFontMask UIFontDescriptorTraitMonoSpace +# define NSItalicFontMask UIFontDescriptorTraitItalic #endif +#import +#import + /* OS X/iOS-specific JWXYZ implementation. */ void @@ -59,6 +71,7 @@ jwxyz_abort (const char *fmt, ...) encoding:NSUTF8StringEncoding] userInfo: nil] raise]; +# undef abort abort(); // not reached } @@ -79,6 +92,452 @@ jwxyz_drawable_depth (Drawable d) } +float +jwxyz_scale (Window main_window) +{ + float scale = 1; + +# ifdef USE_IPHONE + /* Since iOS screens are physically smaller than desktop screens, scale up + the fonts to make them more readable. + + Note that X11 apps on iOS also have the backbuffer sized in points + instead of pixels, resulting in an effective X11 screen size of 768x1024 + or so, even if the display has significantly higher resolution. That is + unrelated to this hack, which is really about DPI. + */ + scale = main_window->window.view.hackedContentScaleFactor; + if (scale < 1) // iPad Pro magnifies the backbuffer by 3x, which makes text + scale = 1; // excessively blurry in BSOD. +# endif + + return scale; +} + + +/* Font metric terminology, as used by X11: + + "lbearing" is the distance from the logical origin to the leftmost pixel. + If a character's ink extends to the left of the origin, it is negative. + + "rbearing" is the distance from the logical origin to the rightmost pixel. + + "descent" is the distance from the logical origin to the bottommost pixel. + For characters with descenders, it is positive. For superscripts, it + is negative. + + "ascent" is the distance from the logical origin to the topmost pixel. + It is the number of pixels above the baseline. + + "width" is the distance from the logical origin to the position where + the logical origin of the next character should be placed. + + If "rbearing" is greater than "width", then this character overlaps the + following character. If smaller, then there is trailing blank space. + */ +static void +utf8_metrics (Display *dpy, NSFont *nsfont, NSString *nsstr, XCharStruct *cs) +{ + // Returns the metrics of the multi-character, single-line UTF8 string. + + Drawable d = XRootWindow (dpy, 0); + + CGContextRef cgc = d->cgc; + NSDictionary *attr = + [NSDictionary dictionaryWithObjectsAndKeys: + nsfont, NSFontAttributeName, + nil]; + NSAttributedString *astr = [[NSAttributedString alloc] + initWithString:nsstr + attributes:attr]; + CTLineRef ctline = CTLineCreateWithAttributedString ( + (__bridge CFAttributedStringRef) astr); + CGContextSetTextPosition (cgc, 0, 0); + CGContextSetShouldAntialias (cgc, True); // #### Guess? + + memset (cs, 0, sizeof(*cs)); + + // "CTRun represents set of consecutive glyphs sharing the same + // attributes and direction". + // + // We also get multiple runs any time font subsitution happens: + // E.g., if the current font is Verdana-Bold, a ← character + // in the NSString will actually be rendered in LucidaGrande-Bold. + // + int count = 0; + for (id runid in (NSArray *)CTLineGetGlyphRuns(ctline)) { + CTRunRef run = (CTRunRef) runid; + CFRange r = { 0, }; + CGRect bbox = CTRunGetImageBounds (run, cgc, r); + CGFloat ascent, descent, leading; + CGFloat advancement = + CTRunGetTypographicBounds (run, r, &ascent, &descent, &leading); + +# ifndef USE_IPHONE + // Only necessary for when LCD smoothing is enabled, which iOS doesn't do. + bbox.origin.x -= 2.0/3.0; + bbox.size.width += 4.0/3.0; + bbox.size.height += 1.0/2.0; +# endif + + // Create the metrics for this run: + XCharStruct cc; + cc.ascent = ceil (bbox.origin.y + bbox.size.height); + cc.descent = ceil (-bbox.origin.y); + cc.lbearing = floor (bbox.origin.x); + cc.rbearing = ceil (bbox.origin.x + bbox.size.width); + cc.width = floor (advancement + 0.5); + + // Add those metrics into the cumulative metrics: + if (count == 0) + *cs = cc; + else + { + cs->ascent = MAX (cs->ascent, cc.ascent); + cs->descent = MAX (cs->descent, cc.descent); + cs->lbearing = MIN (cs->lbearing, cs->width + cc.lbearing); + cs->rbearing = MAX (cs->rbearing, cs->width + cc.rbearing); + cs->width = MAX (cs->width, cs->width + cc.width); + } + + // Why no y? What about vertical text? + // XCharStruct doesn't encapsulate that but XGlyphInfo does. + + count++; + } + + [astr release]; + CFRelease (ctline); +} + + +static NSArray * +font_family_members (NSString *family_name) +{ +# ifndef USE_IPHONE + return [[NSFontManager sharedFontManager] + availableMembersOfFontFamily:family_name]; +# else + return [UIFont fontNamesForFamilyName:family_name]; +# endif +} + + +const char * +jwxyz_default_font_family (int require) +{ + return require & JWXYZ_STYLE_MONOSPACE ? "Courier" : "Verdana"; +} + + +static NSFontTraitMask +nsfonttraitmask_for (int font_style) +{ + NSFontTraitMask result = 0; + if (font_style & JWXYZ_STYLE_BOLD) + result |= NSBoldFontMask; + if (font_style & JWXYZ_STYLE_ITALIC) + result |= NSItalicFontMask; + if (font_style & JWXYZ_STYLE_MONOSPACE) + result |= NSFixedPitchFontMask; + return result; +} + + +static NSFont * +try_font (NSFontTraitMask traits, NSFontTraitMask mask, + NSString *family_name, float size) +{ + NSArray *family_members = font_family_members (family_name); + if (!family_members.count) { + family_members = font_family_members ( + [NSString stringWithUTF8String:jwxyz_default_font_family ( + traits & NSFixedPitchFontMask ? JWXYZ_STYLE_MONOSPACE : 0)]); + } + +# ifndef USE_IPHONE + for (unsigned k = 0; k != family_members.count; ++k) { + + NSArray *member = [family_members objectAtIndex:k]; + NSFontTraitMask font_mask = + [(NSNumber *)[member objectAtIndex:3] unsignedIntValue]; + + if ((font_mask & mask) == traits) { + + NSString *name = [member objectAtIndex:0]; + NSFont *f = [NSFont fontWithName:name size:size]; + if (!f) + break; + + /* Don't use this font if it (probably) doesn't include ASCII characters. + */ + NSStringEncoding enc = [f mostCompatibleStringEncoding]; + if (! (enc == NSUTF8StringEncoding || + enc == NSISOLatin1StringEncoding || + enc == NSNonLossyASCIIStringEncoding || + enc == NSISOLatin2StringEncoding || + enc == NSUnicodeStringEncoding || + enc == NSWindowsCP1250StringEncoding || + enc == NSWindowsCP1252StringEncoding || + enc == NSMacOSRomanStringEncoding)) { + // NSLog(@"skipping \"%@\": encoding = %d", name, enc); + break; + } + // NSLog(@"using \"%@\": %d", name, enc); + + return f; + } + } +# else // USE_IPHONE + + // This trick needs iOS 3.1, see "Using SDK-Based Development". + Class has_font_descriptor = [UIFontDescriptor class]; + + for (NSString *fn in family_members) { +# define MATCH(X) \ + ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \ + != NSNotFound) + + NSFontTraitMask font_mask; + if (has_font_descriptor) { + // This only works on iOS 7 and later. + font_mask = [[UIFontDescriptor + fontDescriptorWithFontAttributes: + @{UIFontDescriptorNameAttribute:fn}] + symbolicTraits]; + } else { + font_mask = 0; + if (MATCH(@"Bold")) + font_mask |= NSBoldFontMask; + if (MATCH(@"Italic") || MATCH(@"Oblique")) + font_mask |= NSItalicFontMask; + if (MATCH(@"Courier")) + font_mask |= NSFixedPitchFontMask; + } + + if ((font_mask & mask) == traits) { + + /* Check if it can do ASCII. No good way to accomplish this! + These are fonts present in iPhone Simulator as of June 2012 + that don't include ASCII. + */ + if (MATCH(@"AppleGothic") || // Korean + MATCH(@"Dingbats") || // Dingbats + MATCH(@"Emoji") || // Emoticons + MATCH(@"Geeza") || // Arabic + MATCH(@"Hebrew") || // Hebrew + MATCH(@"HiraKaku") || // Japanese + MATCH(@"HiraMin") || // Japanese + MATCH(@"Kailasa") || // Tibetan + MATCH(@"Ornaments") || // Dingbats + MATCH(@"STHeiti") // Chinese + ) + break; + + return [UIFont fontWithName:fn size:size]; + } +# undef MATCH + } +# endif + + return NULL; +} + + +/* Returns a random font in the given size and face. + */ +static NSFont * +random_font (NSFontTraitMask traits, NSFontTraitMask mask, float size) +{ + +# ifndef USE_IPHONE + // Providing Unbold or Unitalic in the mask for availableFontNamesWithTraits + // returns an empty list, at least on a system with default fonts only. + NSArray *families = [[NSFontManager sharedFontManager] + availableFontFamilies]; + if (!families) return 0; +# else + NSArray *families = [UIFont familyNames]; + + // There are many dups in the families array -- uniquify it. + { + NSArray *sorted_families = + [families sortedArrayUsingSelector:@selector(compare:)]; + NSMutableArray *new_families = + [NSMutableArray arrayWithCapacity:sorted_families.count]; + + NSString *prev_family = @""; + for (NSString *family in sorted_families) { + if ([family compare:prev_family]) + [new_families addObject:family]; + prev_family = family; + } + + families = new_families; + } +# endif // USE_IPHONE + + long n = [families count]; + if (n <= 0) return 0; + + int j; + for (j = 0; j < n; j++) { + int i = random() % n; + NSString *family_name = [families objectAtIndex:i]; + + NSFont *result = try_font (traits, mask, family_name, size); + if (result) + return result; + } + + // None of the fonts support ASCII? + return 0; +} + +void * +jwxyz_load_native_font (Window main_window, int traits_jwxyz, int mask_jwxyz, + const char *font_name_ptr, size_t font_name_length, + int font_name_type, float size, + char **family_name_ret, + int *ascent_ret, int *descent_ret) +{ + NSFont *nsfont = NULL; + + NSFontTraitMask + traits = nsfonttraitmask_for (traits_jwxyz), + mask = nsfonttraitmask_for (mask_jwxyz); + + NSString *font_name = font_name_type != JWXYZ_FONT_RANDOM ? + [[NSString alloc] initWithBytes:font_name_ptr + length:font_name_length + encoding:NSUTF8StringEncoding] : + nil; + + size *= jwxyz_scale (main_window); + + if (font_name_type == JWXYZ_FONT_RANDOM) { + + nsfont = random_font (traits, mask, size); + [font_name release]; + + } else if (font_name_type == JWXYZ_FONT_FACE) { + + nsfont = [NSFont fontWithName:font_name size:size]; + + } else if (font_name_type == JWXYZ_FONT_FAMILY) { + + Assert (size > 0, "zero font size"); + + if (!nsfont) + nsfont = try_font (traits, mask, font_name, size); + + // if that didn't work, turn off attibutes until it does + // (e.g., there is no "Monaco-Bold".) + // + if (!nsfont && (mask & NSItalicFontMask)) { + traits &= ~NSItalicFontMask; + mask &= ~NSItalicFontMask; + nsfont = try_font (traits, mask, font_name, size); + } + if (!nsfont && (mask & NSBoldFontMask)) { + traits &= ~NSBoldFontMask; + mask &= ~NSBoldFontMask; + nsfont = try_font (traits, mask, font_name, size); + } + if (!nsfont && (mask & NSFixedPitchFontMask)) { + traits &= ~NSFixedPitchFontMask; + mask &= ~NSFixedPitchFontMask; + nsfont = try_font (traits, mask, font_name, size); + } + } + + if (nsfont) + { + if (family_name_ret) + *family_name_ret = strdup (nsfont.familyName.UTF8String); + + CFRetain (nsfont); // needed for garbage collection? + + *ascent_ret = ceil ([nsfont ascender]); + *descent_ret = -floor ([nsfont descender]); + + Assert([nsfont fontName], "broken NSFont in fid"); + } + + return nsfont; +} + + +void +jwxyz_release_native_font (Display *dpy, void *native_font) +{ + // #### DAMMIT! I can't tell what's going wrong here, but I keep getting + // crashes in [NSFont ascender] <- query_font, and it seems to go away + // if I never release the nsfont. So, fuck it, we'll just leak fonts. + // They're probably not very big... + // + // [fid->nsfont release]; + // CFRelease (fid->nsfont); +} + + +// Given a UTF8 string, return an NSString. Bogus UTF8 characters are ignored. +// We have to do this because stringWithCString returns NULL if there are +// any invalid characters at all. +// +static NSString * +sanitize_utf8 (const char *in, size_t in_len, Bool *latin1_pP) +{ + size_t out_len = in_len * 4; // length of string might increase + char *s2 = (char *) malloc (out_len); + char *out = s2; + const char *in_end = in + in_len; + const char *out_end = out + out_len; + Bool latin1_p = True; + + while (in < in_end) + { + unsigned long uc; + long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc); + long L2 = utf8_encode (uc, out, out_end - out); + in += L1; + out += L2; + if (uc > 255) latin1_p = False; + } + *out = 0; + NSString *nsstr = + [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding]; + free (s2); + if (latin1_pP) *latin1_pP = latin1_p; + return (nsstr ? nsstr : @""); +} + + +NSString * +nsstring_from(const char *str, size_t len, int utf8_p) +{ + Bool latin1_p; + NSString *nsstr = utf8_p ? + sanitize_utf8 (str, len, &latin1_p) : + [[[NSString alloc] initWithBytes:str + length:len + encoding:NSISOLatin1StringEncoding] + autorelease]; + return nsstr; +} + +void +jwxyz_render_text (Display *dpy, void *native_font, + const char *str, size_t len, int utf8_p, + XCharStruct *cs_ret, char **pixmap_ret) +{ + utf8_metrics (dpy, (NSFont *)native_font, nsstring_from (str, len, utf8_p), + cs_ret); + + Assert (!pixmap_ret, "TODO"); +} + + void jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp) { @@ -377,8 +836,14 @@ jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc, OS X. (Early 2009 Mac mini, OS X 10.10) */ # ifdef USE_IPHONE - jwxyz_gl_copy_area_copy_tex_image (dpy, src, dst, gc, src_x, src_y, - width, height, dst_x, dst_y); + + /* TODO: This might not still work. */ + jwxyz_bind_drawable (dpy, dpy->main_window, src); + jwxyz_gl_copy_area_read_tex_image (dpy, jwxyz_frame (src)->height, + src_x, src_y, width, height, dst_x, dst_y); + jwxyz_bind_drawable (dpy, dpy->main_window, dst); + jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y, + width, height, dst_x, dst_y); # else // !USE_IPHONE jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y, width, height, dst_x, dst_y); @@ -423,7 +888,7 @@ jwxyz_assert_drawable (Window main_window, Drawable d) } @catch (NSException *exception) { perror([[exception reason] UTF8String]); - abort(); + jwxyz_abort(); } #endif // !USE_IPHONE && !__OPTIMIZE__ }