Dr0ptp4kt has submitted this change and it was merged.

Change subject: Move lead image inflation/face detection to background thread.
......................................................................


Move lead image inflation/face detection to background thread.

Listens in on URLCache notification that the web view retrieved
an image and sees if this image is a variant of the lead image.
Shows this image if the variant is larger than the one already
being shown.

Fixed performance issue in data layer related to seeing if an
image binary exists.

Improved fileSizePrefix error handling.

Fixed bug in imageSizeVariants sorting.

Added c methods for unit rectangle conversion.

Face detection more efficient - since face rect is stored as
unit rectangle we don't have to re-detect the face if we
intercept a higher res variant of lead image.

Change-Id: I979f3a07c7d37febd5b7d9f99a8105223f2525b3
---
M MediaWikiKit/MediaWikiKit/MWKDataStore.m
M MediaWikiKit/MediaWikiKit/MWKImage.h
M MediaWikiKit/MediaWikiKit/MWKImage.m
M MediaWikiKit/MediaWikiKit/MWKImageList.m
M Wikipedia.xcodeproj/project.pbxproj
A wikipedia/C Methods/WMFGeometry.c
A wikipedia/C Methods/WMFGeometry.h
A wikipedia/Categories/UIImage+WMFFocalImageDrawing.h
A wikipedia/Categories/UIImage+WMFFocalImageDrawing.m
A wikipedia/Custom Objects/WMFFaceDetector.h
A wikipedia/Custom Objects/WMFFaceDetector.m
R wikipedia/Custom Views/MenuButton.h
R wikipedia/Custom Views/MenuButton.m
R wikipedia/Custom Views/MenuLabel.h
R wikipedia/Custom Views/MenuLabel.m
R wikipedia/Custom Views/PaddedLabel.h
R wikipedia/Custom Views/PaddedLabel.m
R wikipedia/Custom Views/TabularScrollView.h
R wikipedia/Custom Views/TabularScrollView.m
R wikipedia/Custom Views/WMFCenteredPathView.h
R wikipedia/Custom Views/WMFCenteredPathView.m
R wikipedia/Custom Views/WikiGlyphButton.h
R wikipedia/Custom Views/WikiGlyphButton.m
R wikipedia/Custom Views/WikiGlyphLabel.h
R wikipedia/Custom Views/WikiGlyphLabel.m
M wikipedia/Networking/Fetchers/SavedArticlesFetcher.h
M wikipedia/Networking/Fetchers/SavedArticlesFetcher.m
M wikipedia/View Controllers/Image Gallery/WMFImageGalleryViewController.m
D wikipedia/View Controllers/LeadImage/FocalImage.h
D wikipedia/View Controllers/LeadImage/FocalImage.m
M wikipedia/View Controllers/LeadImage/LeadImageContainer.m
M wikipedia/View Controllers/Preview/PreviewAndSaveViewController.m
M wikipedia/View Controllers/SavedPages/SavedPagesViewController.m
M wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.h
M wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.m
M wikipedia/View Controllers/ShareCard/WMFShareCardViewController.m
M wikipedia/Web Image Interception/URLCache.h
M wikipedia/Web Image Interception/URLCache.m
M wikipedia/en.lproj/Main_iPhone.strings
39 files changed, 722 insertions(+), 412 deletions(-)

Approvals:
  Dr0ptp4kt: Looks good to me, approved
  Fjalapeno: Looks good to me, but someone else must approve



diff --git a/MediaWikiKit/MediaWikiKit/MWKDataStore.m 
b/MediaWikiKit/MediaWikiKit/MWKDataStore.m
index 86b7f15..2bf60cc 100644
--- a/MediaWikiKit/MediaWikiKit/MWKDataStore.m
+++ b/MediaWikiKit/MediaWikiKit/MWKDataStore.m
@@ -312,9 +312,7 @@
         NSLog(@"nil image passed to imageDataWithImage");
         return nil;
     }
-    NSString* path     = [self pathForImage:image];
-    NSString* fileName = [@"Image" 
stringByAppendingPathExtension:image.extension];
-    NSString* filePath = [path stringByAppendingPathComponent:fileName];
+    NSString* filePath = [image fullImageBinaryPath];
 
     NSError* err;
     NSData* data = [NSData dataWithContentsOfFile:filePath options:0 
error:&err];
diff --git a/MediaWikiKit/MediaWikiKit/MWKImage.h 
b/MediaWikiKit/MediaWikiKit/MWKImage.h
index 0f6603b..c31ae02 100644
--- a/MediaWikiKit/MediaWikiKit/MWKImage.h
+++ b/MediaWikiKit/MediaWikiKit/MWKImage.h
@@ -48,8 +48,10 @@
 - (void)save;
 
 - (UIImage*)asUIImage;
+- (NSData*) asNSData;
 
 - (MWKImage*)largestVariant;
+- (MWKImage*)largestCachedVariant;
 
 /// Return the folder containing the image file from receiver's @c sourceURL.
 - (NSString*)basename;
@@ -77,7 +79,7 @@
 /// The name of the image "file" associatd with the receiver, with percent 
encodings replaced.
 + (NSString*)canonicalFilenameFromSourceURL:(NSString*)sourceURL;
 
-+ (int)fileSizePrefix:(NSString*)sourceURL;
++ (NSInteger)fileSizePrefix:(NSString*)sourceURL;
 
 /**
  * Checks if two images are variants of each other <b>but not exactly the same 
image</b>.
@@ -93,4 +95,6 @@
  */
 - (BOOL)isVariantOfImage:(MWKImage*)otherImage;
 
+- (NSString*)fullImageBinaryPath;
+
 @end
diff --git a/MediaWikiKit/MediaWikiKit/MWKImage.m 
b/MediaWikiKit/MediaWikiKit/MWKImage.m
index ca55ba8..251bbfb 100644
--- a/MediaWikiKit/MediaWikiKit/MWKImage.m
+++ b/MediaWikiKit/MediaWikiKit/MWKImage.m
@@ -74,11 +74,15 @@
     return [[self fileNameNoSizePrefix:sourceURL] 
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
 }
 
-+ (int)fileSizePrefix:(NSString*)sourceURL {
-    NSString* fileName      = [sourceURL lastPathComponent];
-    NSRegularExpression* re = [NSRegularExpression 
regularExpressionWithPattern:@"^(\\d+)px-" options:0 error:nil];
++ (NSInteger)fileSizePrefix:(NSString*)sourceURL {
+    NSString* fileName = [sourceURL lastPathComponent];
+    if (!fileName) {
+        return -1;
+    }
+    NSError* error          = nil;
+    NSRegularExpression* re = [NSRegularExpression 
regularExpressionWithPattern:@"^(\\d+)px-" options:0 error:&error];
     NSArray* matches        = [re matchesInString:fileName options:0 
range:NSMakeRange(0, [fileName length])];
-    if ([matches count]) {
+    if (!error && [matches count]) {
         return [[fileName substringWithRange:[matches[0] rangeAtIndex:0]] 
intValue];
     } else {
         return -1;
@@ -159,15 +163,23 @@
     return [UIImage imageWithData:imageData scale:1.0];
 }
 
+- (NSData*)asNSData {
+    return [self.article.dataStore imageDataWithImage:self];
+}
+
 - (MWKImage*)largestVariant {
     NSString* largestURL = [self.article.images 
largestImageVariant:self.sourceURL];
     return [self.article imageWithURL:largestURL];
 }
 
+- (MWKImage*)largestCachedVariant {
+    return [self.article.images largestImageVariantForURL:self.sourceURL 
cachedOnly:YES];
+}
+
 - (BOOL)isCached {
-    // @fixme maybe make this more efficient
-    NSData* data = [self.article.dataStore imageDataWithImage:self];
-    return (data != nil);
+    NSString* fullPath = [self fullImageBinaryPath];
+    BOOL fileExists    = [[NSFileManager defaultManager] 
fileExistsAtPath:fullPath];
+    return fileExists;
 }
 
 - (BOOL)isEqual:(id)object {
@@ -199,4 +211,11 @@
             [super description], self.article.title, self.sourceURL];
 }
 
+- (NSString*)fullImageBinaryPath {
+    NSString* path     = [self.article.dataStore pathForImage:self];
+    NSString* fileName = [@"Image" 
stringByAppendingPathExtension:self.extension];
+    NSString* filePath = [path stringByAppendingPathComponent:fileName];
+    return filePath;
+}
+
 @end
diff --git a/MediaWikiKit/MediaWikiKit/MWKImageList.m 
b/MediaWikiKit/MediaWikiKit/MWKImageList.m
index fda21cd..3d27b02 100644
--- a/MediaWikiKit/MediaWikiKit/MWKImageList.m
+++ b/MediaWikiKit/MediaWikiKit/MWKImageList.m
@@ -94,8 +94,19 @@
     if (arr) {
         NSMutableArray* arr2 = [NSMutableArray arrayWithArray:arr];
         [arr2 sortUsingComparator:^NSComparisonResult (NSString* url1, 
NSString* url2) {
-            int width1 = [MWKImage fileSizePrefix:[url1 lastPathComponent]];
-            int width2 = [MWKImage fileSizePrefix:[url2 lastPathComponent]];
+            NSInteger width1 = [MWKImage fileSizePrefix:[url1 
lastPathComponent]];
+            NSInteger width2 = [MWKImage fileSizePrefix:[url2 
lastPathComponent]];
+
+            // Canonical image won't have "fileSizePrefix" at beginning of 
file name.
+            // ie: "cat.jpg" is larger than "200px-cat.jpg". Set to 
NSIntegerMax in
+            // these cases so canonical will correctly sort as largest.
+            if (width1 == -1) {
+                width1 = NSIntegerMax;
+            }
+            if (width2 == -1) {
+                width2 = NSIntegerMax;
+            }
+
             if (width1 > width2) {
                 return NSOrderedDescending;
             } else if (width1 < width2) {
diff --git a/Wikipedia.xcodeproj/project.pbxproj 
b/Wikipedia.xcodeproj/project.pbxproj
index e869cbb..e35b6ba 100644
--- a/Wikipedia.xcodeproj/project.pbxproj
+++ b/Wikipedia.xcodeproj/project.pbxproj
@@ -14,6 +14,10 @@
                040892641935ABBD004CF254 /* UIViewController+StatusBarHeight.m 
in Sources */ = {isa = PBXBuildFile; fileRef = 040892631935ABBD004CF254 /* 
UIViewController+StatusBarHeight.m */; };
                04090A33187F53E400577EDF /* clear.png in Resources */ = {isa = 
PBXBuildFile; fileRef = 04090A32187F53E400577EDF /* clear.png */; };
                04090A3B187FB7D000577EDF /* UIView+Debugging.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04090A3A187FB7D000577EDF /* UIView+Debugging.m 
*/; };
+               040D83591AB0ECFD000896D5 /* WMFCenteredPathView.m in Sources */ 
= {isa = PBXBuildFile; fileRef = 040D83581AB0ECFD000896D5 /* 
WMFCenteredPathView.m */; };
+               040D835A1AB0ECFD000896D5 /* WMFCenteredPathView.m in Sources */ 
= {isa = PBXBuildFile; fileRef = 040D83581AB0ECFD000896D5 /* 
WMFCenteredPathView.m */; };
+               040D835E1AB0EE45000896D5 /* WMFGeometry.c in Sources */ = {isa 
= PBXBuildFile; fileRef = 040D835C1AB0EE45000896D5 /* WMFGeometry.c */; };
+               040D835F1AB0EE45000896D5 /* WMFGeometry.c in Sources */ = {isa 
= PBXBuildFile; fileRef = 040D835C1AB0EE45000896D5 /* WMFGeometry.c */; };
                0412362E189C29EA00E0CF8E /* abuse-filter-disallowed.png in 
Resources */ = {isa = PBXBuildFile; fileRef = 04123624189C29EA00E0CF8E /* 
abuse-filter-disallowed.png */; };
                04123630189C29EA00E0CF8E /* abuse-filter-disallo...@2x.png in 
Resources */ = {isa = PBXBuildFile; fileRef = 04123625189C29EA00E0CF8E /* 
abuse-filter-disallo...@2x.png */; };
                04123636189C29EA00E0CF8E /* abuse-filter-flag-white.png in 
Resources */ = {isa = PBXBuildFile; fileRef = 04123628189C29EA00E0CF8E /* 
abuse-filter-flag-white.png */; };
@@ -75,12 +79,10 @@
                04530AF81935C07500022512 /* ModalContentViewController.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04530AF71935C07500022512 /* 
ModalContentViewController.m */; };
                04530AFB1935C2B500022512 /* EmptySegue.m in Sources */ = {isa = 
PBXBuildFile; fileRef = 04530AFA1935C2B500022512 /* EmptySegue.m */; };
                045374881A35834D00CE1A56 /* LeadImageTitleAttributedString.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 045374871A35834D00CE1A56 /* 
LeadImageTitleAttributedString.m */; };
-               045424461A429E8800F1DF2F /* FocalImage.m in Sources */ = {isa = 
PBXBuildFile; fileRef = 045424451A429E8800F1DF2F /* FocalImage.m */; };
                045A9F0D18F6090E0057EA85 /* assets in Resources */ = {isa = 
PBXBuildFile; fileRef = 045A9F0C18F6090E0057EA85 /* assets */; };
                045D872119FAD2FA0035C1F9 /* AboutViewController.m in Sources */ 
= {isa = PBXBuildFile; fileRef = 045D872019FAD2FA0035C1F9 /* 
AboutViewController.m */; };
                045EFF1A19A25FEB00D0EDBB /* logo-placeholder-search.png in 
Resources */ = {isa = PBXBuildFile; fileRef = 045EFF1819A25FEB00D0EDBB /* 
logo-placeholder-search.png */; };
                045EFF1B19A25FEB00D0EDBB /* logo-placeholder-sea...@2x.png in 
Resources */ = {isa = PBXBuildFile; fileRef = 045EFF1919A25FEB00D0EDBB /* 
logo-placeholder-sea...@2x.png */; };
-               0460F8DC19B0F932001BC59B /* CenteredPathView.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 0460F8DB19B0F932001BC59B /* CenteredPathView.m 
*/; };
                0462A6D11A1FE016009412D4 /* SearchResultAttributedString.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 0462A6D01A1FE016009412D4 /* 
SearchResultAttributedString.m */; };
                0463639818A844570049EE4F /* KeychainCredentials.m in Sources */ 
= {isa = PBXBuildFile; fileRef = 0463639718A844570049EE4F /* 
KeychainCredentials.m */; };
                0472BC18193AD88C00C40BDA /* MWKSection+DisplayHtml.m in Sources 
*/ = {isa = PBXBuildFile; fileRef = 0472BC17193AD88C00C40BDA /* 
MWKSection+DisplayHtml.m */; };
@@ -125,7 +127,6 @@
                0487048E19F8262600B7D307 /* WikipediaZeroMessageFetcher.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 0487047919F8262600B7D307 /* 
WikipediaZeroMessageFetcher.m */; };
                0487048F19F8262600B7D307 /* WikiTextSectionFetcher.m in Sources 
*/ = {isa = PBXBuildFile; fileRef = 0487047B19F8262600B7D307 /* 
WikiTextSectionFetcher.m */; };
                0487049019F8262600B7D307 /* WikiTextSectionUploader.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 0487047D19F8262600B7D307 /* 
WikiTextSectionUploader.m */; };
-               048A26771906268100395F53 /* PaddedLabel.m in Sources */ = {isa 
= PBXBuildFile; fileRef = 048A26761906268100395F53 /* PaddedLabel.m */; };
                0493C2CC1952373100EBB973 /* DataHousekeeping.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 0493C2CB1952373100EBB973 /* DataHousekeeping.m 
*/; };
                0493C2D419526A0100EBB973 /* WikiFont-Glyphs.ttf in Resources */ 
= {isa = PBXBuildFile; fileRef = 0493C2D319526A0100EBB973 /* 
WikiFont-Glyphs.ttf */; };
                049566C218F5F4CB0058EA12 /* ZeroConfigState.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 049566C118F5F4CB0058EA12 /* ZeroConfigState.m 
*/; };
@@ -138,13 +139,10 @@
                04B0EA47190B2319007458AF /* PreviewLicenseView.xib in Resources 
*/ = {isa = PBXBuildFile; fileRef = 04B0EA46190B2319007458AF /* 
PreviewLicenseView.xib */; };
                04B0EA4A190B2348007458AF /* PreviewLicenseView.m in Sources */ 
= {isa = PBXBuildFile; fileRef = 04B0EA49190B2348007458AF /* 
PreviewLicenseView.m */; };
                04B162F119284A6F00B1ABC2 /* BottomMenuContainerView.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04B162F019284A6F00B1ABC2 /* 
BottomMenuContainerView.m */; };
-               04B6050C193522650007185A /* WikiGlyphButton.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04B6050B193522650007185A /* WikiGlyphButton.m 
*/; };
-               04B605101935236C0007185A /* WikiGlyphLabel.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04B6050F1935236C0007185A /* WikiGlyphLabel.m */; 
};
                04B6925018E77B2A00F88D8A /* UIWebView+HideScrollGradient.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04B6924F18E77B2A00F88D8A /* 
UIWebView+HideScrollGradient.m */; };
                04B7B9BD18B5570E00A63551 /* CaptchaViewController.m in Sources 
*/ = {isa = PBXBuildFile; fileRef = 04B7B9BC18B5570E00A63551 /* 
CaptchaViewController.m */; };
                04B91AA718E34BBC00FFAA1C /* UIView+TemporaryAnimatedXF.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04B91AA618E34BBC00FFAA1C /* 
UIView+TemporaryAnimatedXF.m */; };
                04B91AAB18E3D9E200FFAA1C /* 
NSString+FormattedAttributedString.m in Sources */ = {isa = PBXBuildFile; 
fileRef = 04B91AAA18E3D9E200FFAA1C /* NSString+FormattedAttributedString.m */; 
};
-               04B91AB718E4D5B200FFAA1C /* TabularScrollView.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04B91AB618E4D5B200FFAA1C /* TabularScrollView.m 
*/; };
                04BA48A11A80062F00CB5CAE /* UIFont+WMFStyle.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04BA48A01A80062E00CB5CAE /* UIFont+WMFStyle.m 
*/; };
                04C0A0781936786000D55325 /* UIViewController+ModalPresent.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04C0A0771936786000D55325 /* 
UIViewController+ModalPresent.m */; };
                04C43AA4183440C1006C643B /* MWNetworkActivityIndicatorManager.m 
in Sources */ = {isa = PBXBuildFile; fileRef = 04C43AA1183440C1006C643B /* 
MWNetworkActivityIndicatorManager.m */; };
@@ -171,8 +169,6 @@
                04CCCFF61935094000E3F60C /* PrimaryMenuViewController.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04CCCFF31935094000E3F60C /* 
PrimaryMenuViewController.m */; };
                04CCCFF71935094000E3F60C /* PrimaryMenuTableViewCell.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04CCCFF51935094000E3F60C /* 
PrimaryMenuTableViewCell.m */; };
                04CFA120194900D50088269A /* TopMenuTextFieldContainer.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04CFA11F194900D50088269A /* 
TopMenuTextFieldContainer.m */; };
-               04CFA123194B94980088269A /* MenuButton.m in Sources */ = {isa = 
PBXBuildFile; fileRef = 04CFA122194B94980088269A /* MenuButton.m */; };
-               04CFA126194B94A10088269A /* MenuLabel.m in Sources */ = {isa = 
PBXBuildFile; fileRef = 04CFA125194B94A10088269A /* MenuLabel.m */; };
                04D122321899B8AC006B9A30 /* AlertWebView.m in Sources */ = {isa 
= PBXBuildFile; fileRef = 04D122311899B8AC006B9A30 /* AlertWebView.m */; };
                04D149DD18877343006B4104 /* AlertLabel.m in Sources */ = {isa = 
PBXBuildFile; fileRef = 04D149DA18877343006B4104 /* AlertLabel.m */; };
                04D149DF18877343006B4104 /* UIViewController+Alert.m in Sources 
*/ = {isa = PBXBuildFile; fileRef = 04D149DC18877343006B4104 /* 
UIViewController+Alert.m */; };
@@ -180,6 +176,22 @@
                04D3082B19991CB60034F106 /* logo-placeholder-nearby.png in 
Resources */ = {isa = PBXBuildFile; fileRef = 04D3082919991CB60034F106 /* 
logo-placeholder-nearby.png */; };
                04D3082C19991CB60034F106 /* logo-placeholder-nea...@2x.png in 
Resources */ = {isa = PBXBuildFile; fileRef = 04D3082A19991CB60034F106 /* 
logo-placeholder-nea...@2x.png */; };
                04D34DB21863D39000610A87 /* libxml2.dylib in Frameworks */ = 
{isa = PBXBuildFile; fileRef = 04D34DB11863D39000610A87 /* libxml2.dylib */; };
+               04D686C91AB28FE40009B44A /* UIImage+WMFFocalImageDrawing.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04D686C81AB28FE40009B44A /* 
UIImage+WMFFocalImageDrawing.m */; };
+               04D686CA1AB28FE40009B44A /* UIImage+WMFFocalImageDrawing.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04D686C81AB28FE40009B44A /* 
UIImage+WMFFocalImageDrawing.m */; };
+               04D686CE1AB292160009B44A /* WMFFaceDetector.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04D686CD1AB292160009B44A /* WMFFaceDetector.m 
*/; };
+               04D686CF1AB292160009B44A /* WMFFaceDetector.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04D686CD1AB292160009B44A /* WMFFaceDetector.m 
*/; };
+               04D686F41AB2949C0009B44A /* MenuButton.m in Sources */ = {isa = 
PBXBuildFile; fileRef = 04D686E91AB2949C0009B44A /* MenuButton.m */; };
+               04D686F51AB2949C0009B44A /* MenuButton.m in Sources */ = {isa = 
PBXBuildFile; fileRef = 04D686E91AB2949C0009B44A /* MenuButton.m */; };
+               04D686F61AB2949C0009B44A /* MenuLabel.m in Sources */ = {isa = 
PBXBuildFile; fileRef = 04D686EB1AB2949C0009B44A /* MenuLabel.m */; };
+               04D686F71AB2949C0009B44A /* MenuLabel.m in Sources */ = {isa = 
PBXBuildFile; fileRef = 04D686EB1AB2949C0009B44A /* MenuLabel.m */; };
+               04D686F81AB2949C0009B44A /* PaddedLabel.m in Sources */ = {isa 
= PBXBuildFile; fileRef = 04D686ED1AB2949C0009B44A /* PaddedLabel.m */; };
+               04D686F91AB2949C0009B44A /* PaddedLabel.m in Sources */ = {isa 
= PBXBuildFile; fileRef = 04D686ED1AB2949C0009B44A /* PaddedLabel.m */; };
+               04D686FA1AB2949C0009B44A /* TabularScrollView.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04D686EF1AB2949C0009B44A /* TabularScrollView.m 
*/; };
+               04D686FB1AB2949C0009B44A /* TabularScrollView.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04D686EF1AB2949C0009B44A /* TabularScrollView.m 
*/; };
+               04D686FC1AB2949C0009B44A /* WikiGlyphButton.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04D686F11AB2949C0009B44A /* WikiGlyphButton.m 
*/; };
+               04D686FD1AB2949C0009B44A /* WikiGlyphButton.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04D686F11AB2949C0009B44A /* WikiGlyphButton.m 
*/; };
+               04D686FE1AB2949C0009B44A /* WikiGlyphLabel.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04D686F31AB2949C0009B44A /* WikiGlyphLabel.m */; 
};
+               04D686FF1AB2949C0009B44A /* WikiGlyphLabel.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04D686F31AB2949C0009B44A /* WikiGlyphLabel.m */; 
};
                04DB0BEA18BD37F900B4BCF3 /* 
UIScrollView+ScrollSubviewToLocation.m in Sources */ = {isa = PBXBuildFile; 
fileRef = 04DB0BE918BD37F900B4BCF3 /* UIScrollView+ScrollSubviewToLocation.m 
*/; };
                04DD89B118BFE63A00DD5DAD /* PreviewAndSaveViewController.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04DD89B018BFE63A00DD5DAD /* 
PreviewAndSaveViewController.m */; };
                04E106341A3560030046DC27 /* LeadImageContainer.m in Sources */ 
= {isa = PBXBuildFile; fileRef = 04E106321A3560030046DC27 /* 
LeadImageContainer.m */; };
@@ -372,6 +384,10 @@
                04090A32187F53E400577EDF /* clear.png */ = {isa = 
PBXFileReference; lastKnownFileType = image.png; path = clear.png; sourceTree = 
"<group>"; };
                04090A39187FB7D000577EDF /* UIView+Debugging.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
"UIView+Debugging.h"; sourceTree = "<group>"; };
                04090A3A187FB7D000577EDF /* UIView+Debugging.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= "UIView+Debugging.m"; sourceTree = "<group>"; };
+               040D83571AB0ECFD000896D5 /* WMFCenteredPathView.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WMFCenteredPathView.h; sourceTree = "<group>"; };
+               040D83581AB0ECFD000896D5 /* WMFCenteredPathView.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WMFCenteredPathView.m; sourceTree = "<group>"; };
+               040D835C1AB0EE45000896D5 /* WMFGeometry.c */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 
WMFGeometry.c; sourceTree = "<group>"; };
+               040D835D1AB0EE45000896D5 /* WMFGeometry.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WMFGeometry.h; sourceTree = "<group>"; };
                040E5C4E184566F4007AFE6F /* CoreData.framework */ = {isa = 
PBXFileReference; lastKnownFileType = wrapper.framework; name = 
CoreData.framework; path = System/Library/Frameworks/CoreData.framework; 
sourceTree = SDKROOT; };
                04123624189C29EA00E0CF8E /* abuse-filter-disallowed.png */ = 
{isa = PBXFileReference; lastKnownFileType = image.png; path = 
"abuse-filter-disallowed.png"; sourceTree = "<group>"; };
                04123625189C29EA00E0CF8E /* abuse-filter-disallo...@2x.png */ = 
{isa = PBXFileReference; lastKnownFileType = image.png; path = 
"abuse-filter-disallo...@2x.png"; sourceTree = "<group>"; };
@@ -486,15 +502,11 @@
                04530AFA1935C2B500022512 /* EmptySegue.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= EmptySegue.m; sourceTree = "<group>"; };
                045374861A35834D00CE1A56 /* LeadImageTitleAttributedString.h */ 
= {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.h; path = LeadImageTitleAttributedString.h; sourceTree = 
"<group>"; };
                045374871A35834D00CE1A56 /* LeadImageTitleAttributedString.m */ 
= {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = LeadImageTitleAttributedString.m; sourceTree = 
"<group>"; };
-               045424441A429E8800F1DF2F /* FocalImage.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
FocalImage.h; sourceTree = "<group>"; };
-               045424451A429E8800F1DF2F /* FocalImage.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= FocalImage.m; sourceTree = "<group>"; };
                045A9F0C18F6090E0057EA85 /* assets */ = {isa = 
PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = 
"<group>"; };
                045D871F19FAD2FA0035C1F9 /* AboutViewController.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
AboutViewController.h; sourceTree = "<group>"; };
                045D872019FAD2FA0035C1F9 /* AboutViewController.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= AboutViewController.m; sourceTree = "<group>"; };
                045EFF1819A25FEB00D0EDBB /* logo-placeholder-search.png */ = 
{isa = PBXFileReference; lastKnownFileType = image.png; path = 
"logo-placeholder-search.png"; sourceTree = "<group>"; };
                045EFF1919A25FEB00D0EDBB /* logo-placeholder-sea...@2x.png */ = 
{isa = PBXFileReference; lastKnownFileType = image.png; path = 
"logo-placeholder-sea...@2x.png"; sourceTree = "<group>"; };
-               0460F8DA19B0F932001BC59B /* CenteredPathView.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
CenteredPathView.h; sourceTree = "<group>"; };
-               0460F8DB19B0F932001BC59B /* CenteredPathView.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= CenteredPathView.m; sourceTree = "<group>"; };
                0462A6CF1A1FE016009412D4 /* SearchResultAttributedString.h */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
path = SearchResultAttributedString.h; sourceTree = "<group>"; };
                0462A6D01A1FE016009412D4 /* SearchResultAttributedString.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = SearchResultAttributedString.m; sourceTree = 
"<group>"; };
                0463639618A844570049EE4F /* KeychainCredentials.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
KeychainCredentials.h; sourceTree = "<group>"; };
@@ -571,8 +583,6 @@
                0487047B19F8262600B7D307 /* WikiTextSectionFetcher.m */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; 
path = WikiTextSectionFetcher.m; sourceTree = "<group>"; };
                0487047C19F8262600B7D307 /* WikiTextSectionUploader.h */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path 
= WikiTextSectionUploader.h; sourceTree = "<group>"; };
                0487047D19F8262600B7D307 /* WikiTextSectionUploader.m */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; 
path = WikiTextSectionUploader.m; sourceTree = "<group>"; };
-               048A26751906268100395F53 /* PaddedLabel.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
PaddedLabel.h; sourceTree = "<group>"; };
-               048A26761906268100395F53 /* PaddedLabel.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; 
lineEnding = 0; path = PaddedLabel.m; sourceTree = "<group>"; 
xcLanguageSpecificationIdentifier = xcode.lang.objc; };
                0493C2CA1952373100EBB973 /* DataHousekeeping.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
DataHousekeeping.h; sourceTree = "<group>"; };
                0493C2CB1952373100EBB973 /* DataHousekeeping.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= DataHousekeeping.m; sourceTree = "<group>"; };
                0493C2D319526A0100EBB973 /* WikiFont-Glyphs.ttf */ = {isa = 
PBXFileReference; lastKnownFileType = file; path = "WikiFont-Glyphs.ttf"; 
sourceTree = "<group>"; };
@@ -595,10 +605,6 @@
                04B0EA49190B2348007458AF /* PreviewLicenseView.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= PreviewLicenseView.m; sourceTree = "<group>"; };
                04B162EF19284A6F00B1ABC2 /* BottomMenuContainerView.h */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path 
= BottomMenuContainerView.h; sourceTree = "<group>"; };
                04B162F019284A6F00B1ABC2 /* BottomMenuContainerView.m */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; 
path = BottomMenuContainerView.m; sourceTree = "<group>"; };
-               04B6050A193522650007185A /* WikiGlyphButton.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WikiGlyphButton.h; sourceTree = "<group>"; };
-               04B6050B193522650007185A /* WikiGlyphButton.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WikiGlyphButton.m; sourceTree = "<group>"; };
-               04B6050E1935236C0007185A /* WikiGlyphLabel.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WikiGlyphLabel.h; sourceTree = "<group>"; };
-               04B6050F1935236C0007185A /* WikiGlyphLabel.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WikiGlyphLabel.m; sourceTree = "<group>"; };
                04B6924E18E77B2A00F88D8A /* UIWebView+HideScrollGradient.h */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
path = "UIWebView+HideScrollGradient.h"; sourceTree = "<group>"; };
                04B6924F18E77B2A00F88D8A /* UIWebView+HideScrollGradient.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = "UIWebView+HideScrollGradient.m"; sourceTree = 
"<group>"; };
                04B7B9BB18B5570E00A63551 /* CaptchaViewController.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
CaptchaViewController.h; sourceTree = "<group>"; };
@@ -607,8 +613,6 @@
                04B91AA618E34BBC00FFAA1C /* UIView+TemporaryAnimatedXF.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = "UIView+TemporaryAnimatedXF.m"; sourceTree = 
"<group>"; };
                04B91AA918E3D9E200FFAA1C /* 
NSString+FormattedAttributedString.h */ = {isa = PBXFileReference; fileEncoding 
= 4; lastKnownFileType = sourcecode.c.h; path = 
"NSString+FormattedAttributedString.h"; sourceTree = "<group>"; };
                04B91AAA18E3D9E200FFAA1C /* 
NSString+FormattedAttributedString.m */ = {isa = PBXFileReference; fileEncoding 
= 4; lastKnownFileType = sourcecode.c.objc; path = 
"NSString+FormattedAttributedString.m"; sourceTree = "<group>"; };
-               04B91AB518E4D5B200FFAA1C /* TabularScrollView.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
TabularScrollView.h; sourceTree = "<group>"; };
-               04B91AB618E4D5B200FFAA1C /* TabularScrollView.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= TabularScrollView.m; sourceTree = "<group>"; };
                04BA489F1A80062E00CB5CAE /* UIFont+WMFStyle.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
"UIFont+WMFStyle.h"; sourceTree = "<group>"; };
                04BA48A01A80062E00CB5CAE /* UIFont+WMFStyle.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= "UIFont+WMFStyle.m"; sourceTree = "<group>"; };
                04C0A0761936786000D55325 /* UIViewController+ModalPresent.h */ 
= {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.h; path = "UIViewController+ModalPresent.h"; sourceTree = 
"<group>"; };
@@ -655,10 +659,6 @@
                04CCCFF51935094000E3F60C /* PrimaryMenuTableViewCell.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = PrimaryMenuTableViewCell.m; sourceTree = "<group>"; };
                04CFA11E194900D50088269A /* TopMenuTextFieldContainer.h */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
path = TopMenuTextFieldContainer.h; sourceTree = "<group>"; };
                04CFA11F194900D50088269A /* TopMenuTextFieldContainer.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = TopMenuTextFieldContainer.m; sourceTree = "<group>"; 
};
-               04CFA121194B94980088269A /* MenuButton.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
MenuButton.h; sourceTree = "<group>"; };
-               04CFA122194B94980088269A /* MenuButton.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= MenuButton.m; sourceTree = "<group>"; };
-               04CFA124194B94A10088269A /* MenuLabel.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
MenuLabel.h; sourceTree = "<group>"; };
-               04CFA125194B94A10088269A /* MenuLabel.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= MenuLabel.m; sourceTree = "<group>"; };
                04D122301899B8AC006B9A30 /* AlertWebView.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
AlertWebView.h; sourceTree = "<group>"; };
                04D122311899B8AC006B9A30 /* AlertWebView.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= AlertWebView.m; sourceTree = "<group>"; };
                04D149D918877343006B4104 /* AlertLabel.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
AlertLabel.h; sourceTree = "<group>"; };
@@ -670,6 +670,22 @@
                04D3082919991CB60034F106 /* logo-placeholder-nearby.png */ = 
{isa = PBXFileReference; lastKnownFileType = image.png; path = 
"logo-placeholder-nearby.png"; sourceTree = "<group>"; };
                04D3082A19991CB60034F106 /* logo-placeholder-nea...@2x.png */ = 
{isa = PBXFileReference; lastKnownFileType = image.png; path = 
"logo-placeholder-nea...@2x.png"; sourceTree = "<group>"; };
                04D34DB11863D39000610A87 /* libxml2.dylib */ = {isa = 
PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = 
libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; };
+               04D686C71AB28FE40009B44A /* UIImage+WMFFocalImageDrawing.h */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
path = "UIImage+WMFFocalImageDrawing.h"; sourceTree = "<group>"; };
+               04D686C81AB28FE40009B44A /* UIImage+WMFFocalImageDrawing.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = "UIImage+WMFFocalImageDrawing.m"; sourceTree = 
"<group>"; };
+               04D686CC1AB292160009B44A /* WMFFaceDetector.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WMFFaceDetector.h; sourceTree = "<group>"; };
+               04D686CD1AB292160009B44A /* WMFFaceDetector.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WMFFaceDetector.m; sourceTree = "<group>"; };
+               04D686E81AB2949C0009B44A /* MenuButton.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
MenuButton.h; sourceTree = "<group>"; };
+               04D686E91AB2949C0009B44A /* MenuButton.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= MenuButton.m; sourceTree = "<group>"; };
+               04D686EA1AB2949C0009B44A /* MenuLabel.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
MenuLabel.h; sourceTree = "<group>"; };
+               04D686EB1AB2949C0009B44A /* MenuLabel.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= MenuLabel.m; sourceTree = "<group>"; };
+               04D686EC1AB2949C0009B44A /* PaddedLabel.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
PaddedLabel.h; sourceTree = "<group>"; };
+               04D686ED1AB2949C0009B44A /* PaddedLabel.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= PaddedLabel.m; sourceTree = "<group>"; };
+               04D686EE1AB2949C0009B44A /* TabularScrollView.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
TabularScrollView.h; sourceTree = "<group>"; };
+               04D686EF1AB2949C0009B44A /* TabularScrollView.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= TabularScrollView.m; sourceTree = "<group>"; };
+               04D686F01AB2949C0009B44A /* WikiGlyphButton.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WikiGlyphButton.h; sourceTree = "<group>"; };
+               04D686F11AB2949C0009B44A /* WikiGlyphButton.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WikiGlyphButton.m; sourceTree = "<group>"; };
+               04D686F21AB2949C0009B44A /* WikiGlyphLabel.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WikiGlyphLabel.h; sourceTree = "<group>"; };
+               04D686F31AB2949C0009B44A /* WikiGlyphLabel.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WikiGlyphLabel.m; sourceTree = "<group>"; };
                04DB0BE818BD37F900B4BCF3 /* 
UIScrollView+ScrollSubviewToLocation.h */ = {isa = PBXFileReference; 
fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
"UIScrollView+ScrollSubviewToLocation.h"; sourceTree = "<group>"; };
                04DB0BE918BD37F900B4BCF3 /* 
UIScrollView+ScrollSubviewToLocation.m */ = {isa = PBXFileReference; 
fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = 
"UIScrollView+ScrollSubviewToLocation.m"; sourceTree = "<group>"; };
                04DD89AF18BFE63A00DD5DAD /* PreviewAndSaveViewController.h */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
path = PreviewAndSaveViewController.h; sourceTree = "<group>"; };
@@ -1128,6 +1144,15 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+               040D835B1AB0EE14000896D5 /* C Methods */ = {
+                       isa = PBXGroup;
+                       children = (
+                               040D835D1AB0EE45000896D5 /* WMFGeometry.h */,
+                               040D835C1AB0EE45000896D5 /* WMFGeometry.c */,
+                       );
+                       path = "C Methods";
+                       sourceTree = "<group>";
+               };
                040E5C50184673F2007AFE6F /* Data */ = {
                        isa = PBXGroup;
                        children = (
@@ -1381,15 +1406,6 @@
                        path = About;
                        sourceTree = "<group>";
                };
-               0460F8D919B0F90E001BC59B /* CenteredPathView */ = {
-                       isa = PBXGroup;
-                       children = (
-                               0460F8DA19B0F932001BC59B /* CenteredPathView.h 
*/,
-                               0460F8DB19B0F932001BC59B /* CenteredPathView.m 
*/,
-                       );
-                       path = CenteredPathView;
-                       sourceTree = "<group>";
-               };
                0463639518A844380049EE4F /* Keychain */ = {
                        isa = PBXGroup;
                        children = (
@@ -1577,15 +1593,6 @@
                        path = BaseFetcher;
                        sourceTree = "<group>";
                };
-               048A26741906268100395F53 /* PaddedLabel */ = {
-                       isa = PBXGroup;
-                       children = (
-                               048A26751906268100395F53 /* PaddedLabel.h */,
-                               048A26761906268100395F53 /* PaddedLabel.m */,
-                       );
-                       path = PaddedLabel;
-                       sourceTree = "<group>";
-               };
                0493C2C91952373100EBB973 /* Housekeeping */ = {
                        isa = PBXGroup;
                        children = (
@@ -1632,28 +1639,6 @@
                        path = ../Importer;
                        sourceTree = SOURCE_ROOT;
                };
-               04B60509193522650007185A /* MenuButton */ = {
-                       isa = PBXGroup;
-                       children = (
-                               04B6050A193522650007185A /* WikiGlyphButton.h 
*/,
-                               04B6050B193522650007185A /* WikiGlyphButton.m 
*/,
-                               04CFA121194B94980088269A /* MenuButton.h */,
-                               04CFA122194B94980088269A /* MenuButton.m */,
-                       );
-                       path = MenuButton;
-                       sourceTree = "<group>";
-               };
-               04B6050D1935236C0007185A /* MenuLabel */ = {
-                       isa = PBXGroup;
-                       children = (
-                               04B6050E1935236C0007185A /* WikiGlyphLabel.h */,
-                               04B6050F1935236C0007185A /* WikiGlyphLabel.m */,
-                               04CFA124194B94A10088269A /* MenuLabel.h */,
-                               04CFA125194B94A10088269A /* MenuLabel.m */,
-                       );
-                       path = MenuLabel;
-                       sourceTree = "<group>";
-               };
                04B7B9BA18B5569600A63551 /* Captcha */ = {
                        isa = PBXGroup;
                        children = (
@@ -1661,15 +1646,6 @@
                                04B7B9BC18B5570E00A63551 /* 
CaptchaViewController.m */,
                        );
                        path = Captcha;
-                       sourceTree = "<group>";
-               };
-               04B91AB418E4D58D00FFAA1C /* TabularScrollView */ = {
-                       isa = PBXGroup;
-                       children = (
-                               04B91AB518E4D5B200FFAA1C /* TabularScrollView.h 
*/,
-                               04B91AB618E4D5B200FFAA1C /* TabularScrollView.m 
*/,
-                       );
-                       path = TabularScrollView;
                        sourceTree = "<group>";
                };
                04C43A9F183440C1006C643B /* mw-network */ = {
@@ -1780,6 +1756,8 @@
                                04B91AA618E34BBC00FFAA1C /* 
UIView+TemporaryAnimatedXF.m */,
                                043F8BF01A11699A00D1AE44 /* 
UIView+WMFRoundCorners.h */,
                                043F8BF11A11699A00D1AE44 /* 
UIView+WMFRoundCorners.m */,
+                               04D686C71AB28FE40009B44A /* 
UIImage+WMFFocalImageDrawing.h */,
+                               04D686C81AB28FE40009B44A /* 
UIImage+WMFFocalImageDrawing.m */,
                                044396211A3D33030081557D /* 
UICollectionViewCell+DynamicCellHeight.h */,
                                044396221A3D33030081557D /* 
UICollectionViewCell+DynamicCellHeight.m */,
                                0433542018A023FE009305F0 /* 
UIViewController+HideKeyboard.h */,
@@ -1911,6 +1889,15 @@
                        path = Alerts;
                        sourceTree = "<group>";
                };
+               04D686CB1AB291DE0009B44A /* Custom Objects */ = {
+                       isa = PBXGroup;
+                       children = (
+                               04D686CC1AB292160009B44A /* WMFFaceDetector.h 
*/,
+                               04D686CD1AB292160009B44A /* WMFFaceDetector.m 
*/,
+                       );
+                       path = "Custom Objects";
+                       sourceTree = "<group>";
+               };
                04DD89AE18BFE63A00DD5DAD /* Preview */ = {
                        isa = PBXGroup;
                        children = (
@@ -1929,11 +1916,9 @@
                04E106301A3560030046DC27 /* LeadImage */ = {
                        isa = PBXGroup;
                        children = (
-                               04E106331A3560030046DC27 /* 
LeadImageContainer.xib */,
                                04E106311A3560030046DC27 /* 
LeadImageContainer.h */,
                                04E106321A3560030046DC27 /* 
LeadImageContainer.m */,
-                               045424441A429E8800F1DF2F /* FocalImage.h */,
-                               045424451A429E8800F1DF2F /* FocalImage.m */,
+                               04E106331A3560030046DC27 /* 
LeadImageContainer.xib */,
                                04E106361A3560A90046DC27 /* 
LeadImageTitleLabel.h */,
                                04E106371A3560A90046DC27 /* 
LeadImageTitleLabel.m */,
                                045374861A35834D00CE1A56 /* 
LeadImageTitleAttributedString.h */,
@@ -2156,8 +2141,22 @@
                C42D94811A937DE000A4871A /* Custom Views */ = {
                        isa = PBXGroup;
                        children = (
+                               04D686E81AB2949C0009B44A /* MenuButton.h */,
+                               04D686E91AB2949C0009B44A /* MenuButton.m */,
+                               04D686EA1AB2949C0009B44A /* MenuLabel.h */,
+                               04D686EB1AB2949C0009B44A /* MenuLabel.m */,
+                               04D686EC1AB2949C0009B44A /* PaddedLabel.h */,
+                               04D686ED1AB2949C0009B44A /* PaddedLabel.m */,
+                               04D686EE1AB2949C0009B44A /* TabularScrollView.h 
*/,
+                               04D686EF1AB2949C0009B44A /* TabularScrollView.m 
*/,
+                               04D686F01AB2949C0009B44A /* WikiGlyphButton.h 
*/,
+                               04D686F11AB2949C0009B44A /* WikiGlyphButton.m 
*/,
+                               04D686F21AB2949C0009B44A /* WikiGlyphLabel.h */,
+                               04D686F31AB2949C0009B44A /* WikiGlyphLabel.m */,
                                C42D94821A937DE000A4871A /* WMFBorderButton.h 
*/,
                                C42D94831A937DE000A4871A /* WMFBorderButton.m 
*/,
+                               040D83571AB0ECFD000896D5 /* 
WMFCenteredPathView.h */,
+                               040D83581AB0ECFD000896D5 /* 
WMFCenteredPathView.m */,
                                C963358F1AA92AAC00A1EB2C /* WMFCrashAlertView.h 
*/,
                                C96335901AA92AAC00A1EB2C /* WMFCrashAlertView.m 
*/,
                                C42D94841A937DE000A4871A /* 
WMFProgressLineView.h */,
@@ -2187,6 +2186,7 @@
                        isa = PBXGroup;
                        children = (
                                C98990321A699DE000AF44FC /* 
WMFShareCardViewController.h */,
+                               C98990331A699DE000AF44FC /* 
WMFShareCardViewController.m */,
                                C91A86F21A8BCB680088A801 /* 
WMFShareCardImageContainer.h */,
                                C91A86F31A8BCB680088A801 /* 
WMFShareCardImageContainer.m */,
                                C90799B81A8564C60044E13C /* 
WMFShareOptionsViewController.h */,
@@ -2196,7 +2196,6 @@
                                C97972791A731EAA00C6ED7A /* ShareOptions.xib */,
                                C979727B1A731F2D00C6ED7A /* 
WMFShareOptionsView.h */,
                                C979727C1A731F2D00C6ED7A /* 
WMFShareOptionsView.m */,
-                               C98990331A699DE000AF44FC /* 
WMFShareCardViewController.m */,
                                C98990351A699DFB00AF44FC /* ShareCard.xib */,
                        );
                        name = ShareCard;
@@ -2265,8 +2264,10 @@
                                D46CD8C218A1AC4F0042959E /* Localizable.strings 
*/,
                                045A9F0C18F6090E0057EA85 /* assets */,
                                04272E771940EEBC00CC682F /* AssetsFile */,
+                               040D835B1AB0EE14000896D5 /* C Methods */,
+                               C42D94811A937DE000A4871A /* Custom Views */,
+                               04D686CB1AB291DE0009B44A /* Custom Objects */,
                                04C43AB7183442FC006C643B /* Categories */,
-                               0460F8D919B0F90E001BC59B /* CenteredPathView */,
                                040E5C50184673F2007AFE6F /* Data */,
                                04292FFB185FC026002A13FC /* Defines */,
                                D4B0ADFF19365F4600F0AC90 /* EventLogging */,
@@ -2274,13 +2275,8 @@
                                0493C2C91952373100EBB973 /* Housekeeping */,
                                0466F44C183A30CC00EA1FD7 /* Images */,
                                0463639518A844380049EE4F /* Keychain */,
-                               04B60509193522650007185A /* MenuButton */,
-                               04B6050D1935236C0007185A /* MenuLabel */,
-                               C42D94811A937DE000A4871A /* Custom Views */,
                                0487041519F824D700B7D307 /* Networking */,
-                               048A26741906268100395F53 /* PaddedLabel */,
                                0447866C1852B5010050563B /* Session */,
-                               04B91AB418E4D58D00FFAA1C /* TabularScrollView 
*/,
                                04C43AB0183441A4006C643B /* View Controllers */,
                                04A70FD4185BB6C300E24515 /* Web Image 
Interception */,
                                D499143F181D51DE00E6073C /* Supporting Files */,
@@ -2808,11 +2804,14 @@
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               04D686FD1AB2949C0009B44A /* WikiGlyphButton.m 
in Sources */,
                                BCDB75C41AB0E8300005593F /* 
WMFSubstringUtilsTests.m in Sources */,
                                BC0FED6D1AAA0268002488D7 /* 
MWKHistoryListTests.m in Sources */,
                                BC0FED731AAA026C002488D7 /* 
NSMutableDictionary+MaybeSetTests.m in Sources */,
                                BC0FED6E1AAA0268002488D7 /* MWKImageListTests.m 
in Sources */,
+                               04D686F51AB2949C0009B44A /* MenuButton.m in 
Sources */,
                                BC0FED701AAA026C002488D7 /* 
NSArray+PredicateTests.m in Sources */,
+                               04D686F91AB2949C0009B44A /* PaddedLabel.m in 
Sources */,
                                BC0FED771AAA026C002488D7 /* 
WMFImageURLParsingTests.m in Sources */,
                                BC0FED6B1AAA0268002488D7 /* 
MWKDataStoreStorageTests.m in Sources */,
                                BC0FED751AAA026C002488D7 /* 
NSArray+BKIndexTests.m in Sources */,
@@ -2822,13 +2821,20 @@
                                BC0FED621AAA0263002488D7 /* WMFCodingStyle.m in 
Sources */,
                                BC0FED741AAA026C002488D7 /* 
CircularBitwiseRotationTests.m in Sources */,
                                BC0FED681AAA0268002488D7 /* MWKUserTests.m in 
Sources */,
+                               04D686F71AB2949C0009B44A /* MenuLabel.m in 
Sources */,
                                BC0FED661AAA0268002488D7 /* MWKSiteTests.m in 
Sources */,
+                               040D835F1AB0EE45000896D5 /* WMFGeometry.c in 
Sources */,
                                BC0FED691AAA0268002488D7 /* 
MWKProtectionStatusTests.m in Sources */,
+                               040D835A1AB0ECFD000896D5 /* 
WMFCenteredPathView.m in Sources */,
                                BC0FED6C1AAA0268002488D7 /* 
MWKImageStorageTests.m in Sources */,
                                BC0FED721AAA026C002488D7 /* 
WMFErrorForApiErrorObjectTests.m in Sources */,
+                               04D686CA1AB28FE40009B44A /* 
UIImage+WMFFocalImageDrawing.m in Sources */,
+                               04D686FF1AB2949C0009B44A /* WikiGlyphLabel.m in 
Sources */,
                                BC0FED711AAA026C002488D7 /* 
WMFJoinedPropertyParametersTests.m in Sources */,
                                BC0FED6A1AAA0268002488D7 /* 
MWKDataStorePathTests.m in Sources */,
                                BC0FED761AAA026C002488D7 /* 
NSString+WMFHTMLParsingTests.m in Sources */,
+                               04D686CF1AB292160009B44A /* WMFFaceDetector.m 
in Sources */,
+                               04D686FB1AB2949C0009B44A /* TabularScrollView.m 
in Sources */,
                                BC0FED671AAA0268002488D7 /* MWKTitleTests.m in 
Sources */,
                                BC0FED631AAA0263002488D7 /* MWKTestCase.m in 
Sources */,
                        );
@@ -2857,7 +2863,6 @@
                                BCB669A91A83F6C400C7B1FE /* MWKSection.m in 
Sources */,
                                BCB669B61A83F6C400C7B1FE /* MWKImageList.m in 
Sources */,
                                BCB848831AAE0C5C0077EC24 /* 
WMFImageGalleryCollectionViewCell.m in Sources */,
-                               04CFA126194B94A10088269A /* MenuLabel.m in 
Sources */,
                                04B0EA4A190B2348007458AF /* 
PreviewLicenseView.m in Sources */,
                                0462A6D11A1FE016009412D4 /* 
SearchResultAttributedString.m in Sources */,
                                0487047F19F8262600B7D307 /* AccountCreator.m in 
Sources */,
@@ -2876,19 +2881,19 @@
                                0463639818A844570049EE4F /* 
KeychainCredentials.m in Sources */,
                                BCA96E771AAA35EE009A61FA /* 
UIView+WMFDefaultNib.m in Sources */,
                                04414DDB1A140FAF00A41B4E /* 
SearchDidYouMeanButton.m in Sources */,
+                               040D83591AB0ECFD000896D5 /* 
WMFCenteredPathView.m in Sources */,
                                04530AFB1935C2B500022512 /* EmptySegue.m in 
Sources */,
                                BCB669B41A83F6C400C7B1FE /* MWKArticle.m in 
Sources */,
                                044BD6B618849AD000FFE4BE /* 
SectionEditorViewController.m in Sources */,
                                042B3996192EAEEA0066B270 /* 
ShareMenuSavePageActivity.m in Sources */,
                                0429301018604898002A13FC /* 
SavedPagesViewController.m in Sources */,
                                04D149DF18877343006B4104 /* 
UIViewController+Alert.m in Sources */,
-                               04B605101935236C0007185A /* WikiGlyphLabel.m in 
Sources */,
                                BCA96E731AAA354D009A61FA /* WMFGradientView.m 
in Sources */,
-                               048A26771906268100395F53 /* PaddedLabel.m in 
Sources */,
                                04B91AAB18E3D9E200FFAA1C /* 
NSString+FormattedAttributedString.m in Sources */,
                                043F18E518D9691D00D8489A /* 
UINavigationController+TopActionSheet.m in Sources */,
                                044396231A3D33030081557D /* 
UICollectionViewCell+DynamicCellHeight.m in Sources */,
                                04414DDF1A1420EB00A41B4E /* 
WikiDataShortDescriptionFetcher.m in Sources */,
+                               04D686F61AB2949C0009B44A /* MenuLabel.m in 
Sources */,
                                04D308281998A8AA0034F106 /* 
NearbyThumbnailView.m in Sources */,
                                042E3B931AA16D6700BF8D66 /* 
UIViewController+WMFChildViewController.m in Sources */,
                                045374881A35834D00CE1A56 /* 
LeadImageTitleAttributedString.m in Sources */,
@@ -2898,6 +2903,7 @@
                                0447866F1852B5010050563B /* SessionSingleton.m 
in Sources */,
                                BCB669AA1A83F6C400C7B1FE /* MWKImage.m in 
Sources */,
                                D4E8A8A4190835C100DA4765 /* DataMigrator.m in 
Sources */,
+                               04D686F41AB2949C0009B44A /* MenuButton.m in 
Sources */,
                                0443961B1A3C11A30081557D /* 
NearbyResultCollectionCell.m in Sources */,
                                0487048519F8262600B7D307 /* EditTokenFetcher.m 
in Sources */,
                                04CCCFF71935094000E3F60C /* 
PrimaryMenuTableViewCell.m in Sources */,
@@ -2921,6 +2927,7 @@
                                04A70FD7185BB6C300E24515 /* URLCache.m in 
Sources */,
                                C90799BA1A8564C60044E13C /* 
WMFShareOptionsViewController.m in Sources */,
                                04C91CEB195517250035ED1B /* 
OnboardingViewController.m in Sources */,
+                               04D686FA1AB2949C0009B44A /* TabularScrollView.m 
in Sources */,
                                041A3B5E18E11ED90079FF1C /* LanguagesCell.m in 
Sources */,
                                042487521A54BECD00A5C905 /* 
MWKArticle+Convenience.m in Sources */,
                                043DAC4B1901C3EE001CD17C /* 
CreditsViewController.m in Sources */,
@@ -2931,17 +2938,18 @@
                                04D149DD18877343006B4104 /* AlertLabel.m in 
Sources */,
                                BCB669AE1A83F6C400C7B1FE /* MWKHistoryEntry.m 
in Sources */,
                                0433542618A093C5009305F0 /* 
UIView+RemoveConstraints.m in Sources */,
+                               04D686FC1AB2949C0009B44A /* WikiGlyphButton.m 
in Sources */,
                                C98990341A699DE000AF44FC /* 
WMFShareCardViewController.m in Sources */,
                                C42D94861A937DE000A4871A /* WMFBorderButton.m 
in Sources */,
                                047801BE18AE987900DBB747 /* 
UIButton+ColorMask.m in Sources */,
                                BC86B93D1A929CC500B4C039 /* 
UICollectionViewFlowLayout+NSCopying.m in Sources */,
+                               04D686C91AB28FE40009B44A /* 
UIImage+WMFFocalImageDrawing.m in Sources */,
                                0429300A18604898002A13FC /* 
SavedPagesResultCell.m in Sources */,
                                0487048419F8262600B7D307 /* CaptchaResetter.m 
in Sources */,
                                D407E6411A51DBDA00CCC8B1 /* SchemaConverter.m 
in Sources */,
                                BCB669A71A83F6C400C7B1FE /* MWKSiteDataObject.m 
in Sources */,
                                BC7DFCD61AA4E5FE000035C3 /* 
WMFImageURLParsing.m in Sources */,
                                04821CD119895EDC007558F6 /* 
ReferenceGradientView.m in Sources */,
-                               0460F8DC19B0F932001BC59B /* CenteredPathView.m 
in Sources */,
                                0472BC18193AD88C00C40BDA /* 
MWKSection+DisplayHtml.m in Sources */,
                                047ED63918C13E4900442BE3 /* PreviewWebView.m in 
Sources */,
                                0480AEA01AA4F4DA00A9950C /* 
WMFIntrinsicContentSizeAwareTableView.m in Sources */,
@@ -2971,7 +2979,6 @@
                                04224501197F5E09005DD0BF /* BulletedLabel.m in 
Sources */,
                                C979727D1A731F2D00C6ED7A /* 
WMFShareOptionsView.m in Sources */,
                                C91A86F41A8BCB680088A801 /* 
WMFShareCardImageContainer.m in Sources */,
-                               04B6050C193522650007185A /* WikiGlyphButton.m 
in Sources */,
                                BCB58F781A8D081E00465627 /* NSArray+BKIndex.m 
in Sources */,
                                042A5B2C19253E690095E172 /* 
BottomMenuViewController.m in Sources */,
                                0433542218A023FE009305F0 /* 
UIViewController+HideKeyboard.m in Sources */,
@@ -2985,11 +2992,12 @@
                                BCB58F441A890D9700465627 /* 
MWKImageInfo+MWKImageComparison.m in Sources */,
                                BCB669AD1A83F6C400C7B1FE /* MWKSavedPageList.m 
in Sources */,
                                BCC185E81A9FA498005378F8 /* 
UICollectionViewFlowLayout+AttributeUtils.m in Sources */,
-                               04B91AB718E4D5B200FFAA1C /* TabularScrollView.m 
in Sources */,
+                               04D686F81AB2949C0009B44A /* PaddedLabel.m in 
Sources */,
                                04C695D218ED213000D9F2DA /* 
UIScrollView+NoHorizontalScrolling.m in Sources */,
                                04E106381A3560A90046DC27 /* 
LeadImageTitleLabel.m in Sources */,
                                04F0E2EE186FB2D100468738 /* 
TOCSectionCellView.m in Sources */,
                                04D122321899B8AC006B9A30 /* AlertWebView.m in 
Sources */,
+                               040D835E1AB0EE45000896D5 /* WMFGeometry.c in 
Sources */,
                                04C0A0781936786000D55325 /* 
UIViewController+ModalPresent.m in Sources */,
                                BC86B9361A92966B00B4C039 /* 
AFHTTPRequestOperationManager+UniqueRequests.m in Sources */,
                                D4991449181D51DE00E6073C /* AppDelegate.m in 
Sources */,
@@ -3021,7 +3029,6 @@
                                BC955BC71A82BEFD000EF9E4 /* 
MWKImageInfoFetcher.m in Sources */,
                                04C7576E1A1AA2D00084AC39 /* RecentSearchCell.m 
in Sources */,
                                BCB58F541A894D3E00465627 /* 
WMFImageGalleryDetailOverlayView.m in Sources */,
-                               045424461A429E8800F1DF2F /* FocalImage.m in 
Sources */,
                                04292FF8185FBB0B002A13FC /* 
SearchResultsController.m in Sources */,
                                04478633185145090050563B /* 
HistoryViewController.m in Sources */,
                                BCB669B21A83F6C400C7B1FE /* MWKImageInfo.m in 
Sources */,
@@ -3045,11 +3052,12 @@
                                04272E7B1940EEBC00CC682F /* WMFAssetsFile.m in 
Sources */,
                                042A5B2919253E570095E172 /* 
TopMenuViewController.m in Sources */,
                                D4991445181D51DE00E6073C /* main.m in Sources 
*/,
-                               04CFA123194B94980088269A /* MenuButton.m in 
Sources */,
                                0480AE9C1AA4F01600A9950C /* 
WMFWebViewFooterContainerView.m in Sources */,
                                D47BF5D4197870390067C3BC /* SavedPagesFunnel.m 
in Sources */,
                                04C43AC0183442FC006C643B /* NSString+Extras.m 
in Sources */,
+                               04D686FE1AB2949C0009B44A /* WikiGlyphLabel.m in 
Sources */,
                                04CCCFEE1935093A00E3F60C /* 
SecondaryMenuRowView.m in Sources */,
+                               04D686CE1AB292160009B44A /* WMFFaceDetector.m 
in Sources */,
                                0442F57B19006DCC00F55DF9 /* PageHistoryLabel.m 
in Sources */,
                                041C6206199ED2A20061516F /* MWKSection+TOC.m in 
Sources */,
                                BC2CBB8E1AA10F400079A313 /* 
UIView+WMFFrameUtils.m in Sources */,
diff --git a/wikipedia/C Methods/WMFGeometry.c b/wikipedia/C 
Methods/WMFGeometry.c
new file mode 100644
index 0000000..d33f69d
--- /dev/null
+++ b/wikipedia/C Methods/WMFGeometry.c
@@ -0,0 +1,25 @@
+//  Created by Monte Hurd on 3/11/15.
+//  Copyright (c) 2015 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
+
+#include "WMFGeometry.h"
+
+CGRect WMFUnitRectFromRectForReferenceSize(CGRect rect, CGSize refSize){
+    if (CGSizeEqualToSize(refSize, CGSizeZero) || CGRectIsEmpty(rect)) {
+        return CGRectZero;
+    }
+    return CGRectMake(
+        CGRectGetMinX(rect) / refSize.width,
+        CGRectGetMinY(rect) / refSize.height,
+        CGRectGetWidth(rect) / refSize.width,
+        CGRectGetHeight(rect) / refSize.height
+        );
+}
+
+CGRect WMFRectFromUnitRectForReferenceSize(CGRect unitRect, CGSize refSize){
+    return CGRectMake(
+        unitRect.origin.x * refSize.width,
+        unitRect.origin.y * refSize.height,
+        unitRect.size.width * refSize.width,
+        unitRect.size.height * refSize.height
+        );
+}
\ No newline at end of file
diff --git a/wikipedia/C Methods/WMFGeometry.h b/wikipedia/C 
Methods/WMFGeometry.h
new file mode 100644
index 0000000..225e8c4
--- /dev/null
+++ b/wikipedia/C Methods/WMFGeometry.h
@@ -0,0 +1,17 @@
+//  Created by Monte Hurd on 3/11/15.
+//  Copyright (c) 2015 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
+
+#ifndef __Wikipedia__WMFGeometry__
+#define __Wikipedia__WMFGeometry__
+
+#include <stdio.h>
+
+#include <CoreGraphics/CGGeometry.h>
+
+// Convert rect to a unit rect for reference size.
+CG_EXTERN CGRect WMFUnitRectFromRectForReferenceSize(CGRect rect, CGSize 
referenceSize);
+
+// Convert unit rect back to rect for reference size.
+CG_EXTERN CGRect WMFRectFromUnitRectForReferenceSize(CGRect unitRect, CGSize 
referenceSize);
+
+#endif /* defined(__Wikipedia__WMFGeometry__) */
diff --git a/wikipedia/Categories/UIImage+WMFFocalImageDrawing.h 
b/wikipedia/Categories/UIImage+WMFFocalImageDrawing.h
new file mode 100644
index 0000000..5712753
--- /dev/null
+++ b/wikipedia/Categories/UIImage+WMFFocalImageDrawing.h
@@ -0,0 +1,22 @@
+//  Created by Monte Hurd on 3/12/15.
+//  Copyright (c) 2015 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
+
+#import <UIKit/UIKit.h>
+
+@interface UIImage (WMFFocalImageDrawing)
+
+/*
+   Draws image with:
+
+    - Aspect fill.
+    - Horizontal center if horizontal overlap.
+    - Align top if no focalBounds, else vertical centered on focalBounds.
+    - Optional focalBounds highlight.
+ */
+- (void)wmf_drawInRect:(CGRect)rect
+           focalBounds:(CGRect)focalBounds
+        focalHighlight:(BOOL)focalHighlight
+             blendMode:(CGBlendMode)blendMode
+                 alpha:(CGFloat)alpha;
+
+@end
diff --git a/wikipedia/Categories/UIImage+WMFFocalImageDrawing.m 
b/wikipedia/Categories/UIImage+WMFFocalImageDrawing.m
new file mode 100644
index 0000000..0363588
--- /dev/null
+++ b/wikipedia/Categories/UIImage+WMFFocalImageDrawing.m
@@ -0,0 +1,73 @@
+//  Created by Monte Hurd on 3/12/15.
+//  Copyright (c) 2015 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
+
+#import "UIImage+WMFFocalImageDrawing.h"
+
+@implementation UIImage (WMFFocalImageDrawing)
+
+- (void)wmf_drawInRect:(CGRect)rect
+           focalBounds:(CGRect)focalBounds
+        focalHighlight:(BOOL)focalHighlight
+             blendMode:(CGBlendMode)blendMode
+                 alpha:(CGFloat)alpha {
+    if ((self.size.width == 0) || (self.size.height == 0)) {
+        return;
+    }
+
+    // Aspect fill.
+    float xScale = rect.size.width / self.size.width;
+    float yScale = rect.size.height / self.size.height;
+    float scale  = MAX(xScale, yScale);
+    CGSize size  = CGSizeMake(self.size.width * scale, self.size.height * 
scale);
+
+    // Align top.
+    CGRect r = (CGRect){{0, 0}, size};
+
+    // Center horizontally.
+    CGFloat m1     = CGRectGetMidX(r);
+    CGFloat m2     = CGRectGetMidX(rect);
+    CGFloat offset = (m2 - m1);
+    r = CGRectOffset(r, offset, 0.0);
+
+    // Figure out bottom overlap so we can know how much we can move the image 
up.
+    CGFloat bottomOverlap = r.size.height - rect.size.height;
+    if (bottomOverlap > 0.0) {
+        if (!CGRectIsEmpty(focalBounds)) {
+            // Move image up to vertically center focal bounds (as much as 
possible).
+            CGFloat yMidSelf        = CGRectGetMidY(rect);
+            CGFloat yMidFocalBounds = CGRectGetMidY(focalBounds) * scale;
+            CGFloat yShift          = fminf(yMidFocalBounds - yMidSelf, 
bottomOverlap);
+            if (yShift > 0) {
+                r = CGRectOffset(r, 0.0, -yShift);
+            }
+        } else {
+            // If no focalBounds, move the image up a bit, if possible.
+            CGFloat quarterOverlap = (bottomOverlap * 0.25);
+            r = CGRectOffset(r, 0.0, -quarterOverlap);
+        }
+    }
+
+    [self drawInRect:r blendMode:blendMode alpha:alpha];
+
+    // Draw a box over the focal bounds.
+    if (focalHighlight) {
+        CGRect scaledfocalBounds =
+            CGRectMake(
+                (focalBounds.origin.x * scale) + r.origin.x,
+                (focalBounds.origin.y * scale) + r.origin.y,
+                focalBounds.size.width * scale,
+                focalBounds.size.height * scale
+                );
+        [self fillFocalBounds:scaledfocalBounds];
+    }
+}
+
+- (void)fillFocalBounds:(CGRect)bounds {
+    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+    CGContextRef context       = UIGraphicsGetCurrentContext();
+    CGContextSetRGBFillColor(context, 0.0, 1.0, 0.0, 0.3);
+    CGContextFillRect(context, bounds);
+    CGColorSpaceRelease(colorSpace);
+}
+
+@end
diff --git a/wikipedia/Custom Objects/WMFFaceDetector.h b/wikipedia/Custom 
Objects/WMFFaceDetector.h
new file mode 100644
index 0000000..fe3773a
--- /dev/null
+++ b/wikipedia/Custom Objects/WMFFaceDetector.h
@@ -0,0 +1,21 @@
+//  Created by Monte Hurd on 12/17/14.
+//  Copyright (c) 2014 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
+
+#import <UIKit/UIKit.h>
+
+@interface WMFFaceDetector : NSObject
+
+@property (nonatomic, strong) UIImage* image;
+
+/*
+   "detectFace" returns rect for largest face in "self.image".
+
+   Subsequent calls to "detectFace" return next largest face rect,
+   rolling back to first face after last face.
+
+   It only actually runs face detection on the first call.
+   Internally cached results are returned on subsequent calls.
+ */
+- (CGRect)detectFace;
+
+@end
diff --git a/wikipedia/Custom Objects/WMFFaceDetector.m b/wikipedia/Custom 
Objects/WMFFaceDetector.m
new file mode 100644
index 0000000..b0acd60
--- /dev/null
+++ b/wikipedia/Custom Objects/WMFFaceDetector.m
@@ -0,0 +1,80 @@
+//  Created by Monte Hurd on 12/17/14.
+//  Copyright (c) 2014 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
+
+#import "WMFFaceDetector.h"
+
+@interface WMFFaceDetector ()
+
+@property (strong, atomic) CIDetector* detector;
+@property (strong, atomic) NSArray* faces;
+@property (atomic) NSInteger nextFaceIndex;
+@property (atomic, readwrite) CGRect faceBounds;
+
+@end
+
+@implementation WMFFaceDetector
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        self.detector =
+            [CIDetector detectorOfType:CIDetectorTypeFace
+                               context:nil
+                               options:@{
+                 CIDetectorAccuracy: CIDetectorAccuracyLow,
+                 CIDetectorMinFeatureSize: @(0.15)
+             }];
+    }
+    return self;
+}
+
+- (CGRect)detectFace {
+    // Optimized for repeated calls (for easy cycle through all faces).
+    if (!self.image) {
+        return CGRectZero;
+    }
+
+    // No need to set faces more than once (for repeated call cycling).
+    if (!self.faces) {
+        NSAssert(self.image.CIImage, @"Attempted to use a UIImage w/o CIImage 
backing: Create the UIImage with 'imageWithCIImage' so face detection doesn't 
have to alloc/init a new CIImage to run detection. See: 
http://stackoverflow.com/a/15651358/135557";);
+        self.faces = [self.detector featuresInImage:self.image.CIImage];
+    }
+
+    CGRect widestFaceRect = CGRectZero;
+
+    // Index overrun protection.
+    if (self.nextFaceIndex >= self.faces.count) {
+        return CGRectZero;
+    }
+
+    // Get face for nextFaceIndex.
+    widestFaceRect = ((CIFaceFeature*)self.faces[self.nextFaceIndex]).bounds;
+
+    if (CGRectIsEmpty(widestFaceRect)) {
+        return CGRectZero;
+    }
+
+    // Increment so next call will return next face.
+    self.nextFaceIndex++;
+
+    // Reset if last face so next call shows first face.
+    if (self.nextFaceIndex == self.faces.count) {
+        self.nextFaceIndex = 0;
+    }
+
+    CGRect faceImageCoords =
+        (CGRect){
+        {widestFaceRect.origin.x, self.image.size.height - 
widestFaceRect.origin.y - widestFaceRect.size.height},
+        widestFaceRect.size
+    };
+
+    return faceImageCoords;
+}
+
+- (void)setImage:(UIImage*)image {
+    _image             = image;
+    self.nextFaceIndex = 0;
+    self.faces         = nil;
+}
+
+@end
diff --git a/wikipedia/MenuButton/MenuButton.h b/wikipedia/Custom 
Views/MenuButton.h
similarity index 100%
rename from wikipedia/MenuButton/MenuButton.h
rename to wikipedia/Custom Views/MenuButton.h
diff --git a/wikipedia/MenuButton/MenuButton.m b/wikipedia/Custom 
Views/MenuButton.m
similarity index 100%
rename from wikipedia/MenuButton/MenuButton.m
rename to wikipedia/Custom Views/MenuButton.m
diff --git a/wikipedia/MenuLabel/MenuLabel.h b/wikipedia/Custom 
Views/MenuLabel.h
similarity index 100%
rename from wikipedia/MenuLabel/MenuLabel.h
rename to wikipedia/Custom Views/MenuLabel.h
diff --git a/wikipedia/MenuLabel/MenuLabel.m b/wikipedia/Custom 
Views/MenuLabel.m
similarity index 100%
rename from wikipedia/MenuLabel/MenuLabel.m
rename to wikipedia/Custom Views/MenuLabel.m
diff --git a/wikipedia/PaddedLabel/PaddedLabel.h b/wikipedia/Custom 
Views/PaddedLabel.h
similarity index 100%
rename from wikipedia/PaddedLabel/PaddedLabel.h
rename to wikipedia/Custom Views/PaddedLabel.h
diff --git a/wikipedia/PaddedLabel/PaddedLabel.m b/wikipedia/Custom 
Views/PaddedLabel.m
similarity index 100%
rename from wikipedia/PaddedLabel/PaddedLabel.m
rename to wikipedia/Custom Views/PaddedLabel.m
diff --git a/wikipedia/TabularScrollView/TabularScrollView.h b/wikipedia/Custom 
Views/TabularScrollView.h
similarity index 100%
rename from wikipedia/TabularScrollView/TabularScrollView.h
rename to wikipedia/Custom Views/TabularScrollView.h
diff --git a/wikipedia/TabularScrollView/TabularScrollView.m b/wikipedia/Custom 
Views/TabularScrollView.m
similarity index 100%
rename from wikipedia/TabularScrollView/TabularScrollView.m
rename to wikipedia/Custom Views/TabularScrollView.m
diff --git a/wikipedia/CenteredPathView/CenteredPathView.h b/wikipedia/Custom 
Views/WMFCenteredPathView.h
similarity index 90%
rename from wikipedia/CenteredPathView/CenteredPathView.h
rename to wikipedia/Custom Views/WMFCenteredPathView.h
index fe13fd7..8e226fa 100644
--- a/wikipedia/CenteredPathView/CenteredPathView.h
+++ b/wikipedia/Custom Views/WMFCenteredPathView.h
@@ -5,7 +5,7 @@
 
 #import <UIKit/UIKit.h>
 
-@interface CenteredPathView : UIView
+@interface WMFCenteredPathView : UIView
 
 - (id)initWithPath:(CGPathRef)newPath
        strokeWidth:(CGFloat)strokeWidth
diff --git a/wikipedia/CenteredPathView/CenteredPathView.m b/wikipedia/Custom 
Views/WMFCenteredPathView.m
similarity index 96%
rename from wikipedia/CenteredPathView/CenteredPathView.m
rename to wikipedia/Custom Views/WMFCenteredPathView.m
index 529a25e..242ce8a 100644
--- a/wikipedia/CenteredPathView/CenteredPathView.m
+++ b/wikipedia/Custom Views/WMFCenteredPathView.m
@@ -1,8 +1,8 @@
 //  Created by Monte Hurd on 8/26/14.
 
-#import "CenteredPathView.h"
+#import "WMFCenteredPathView.h"
 
-@interface CenteredPathView ()
+@interface WMFCenteredPathView ()
 
 @property (nonatomic) CGPathRef path;
 @property (nonatomic) CGFloat strokeWidth;
@@ -11,7 +11,7 @@
 
 @end
 
-@implementation CenteredPathView
+@implementation WMFCenteredPathView
 
 - (id)initWithPath:(CGPathRef)newPath
        strokeWidth:(CGFloat)strokeWidth
diff --git a/wikipedia/MenuButton/WikiGlyphButton.h b/wikipedia/Custom 
Views/WikiGlyphButton.h
similarity index 100%
rename from wikipedia/MenuButton/WikiGlyphButton.h
rename to wikipedia/Custom Views/WikiGlyphButton.h
diff --git a/wikipedia/MenuButton/WikiGlyphButton.m b/wikipedia/Custom 
Views/WikiGlyphButton.m
similarity index 100%
rename from wikipedia/MenuButton/WikiGlyphButton.m
rename to wikipedia/Custom Views/WikiGlyphButton.m
diff --git a/wikipedia/MenuLabel/WikiGlyphLabel.h b/wikipedia/Custom 
Views/WikiGlyphLabel.h
similarity index 100%
rename from wikipedia/MenuLabel/WikiGlyphLabel.h
rename to wikipedia/Custom Views/WikiGlyphLabel.h
diff --git a/wikipedia/MenuLabel/WikiGlyphLabel.m b/wikipedia/Custom 
Views/WikiGlyphLabel.m
similarity index 100%
rename from wikipedia/MenuLabel/WikiGlyphLabel.m
rename to wikipedia/Custom Views/WikiGlyphLabel.m
diff --git a/wikipedia/Networking/Fetchers/SavedArticlesFetcher.h 
b/wikipedia/Networking/Fetchers/SavedArticlesFetcher.h
index 2c2b6c1..ba67495 100644
--- a/wikipedia/Networking/Fetchers/SavedArticlesFetcher.h
+++ b/wikipedia/Networking/Fetchers/SavedArticlesFetcher.h
@@ -4,7 +4,7 @@
 @class MWKArticle, MWKSavedPageList, AFHTTPRequestOperationManager;
 @class SavedArticlesFetcher;
 
-typedef void (^WMFSavedArticlesFetcherProgress)(CGFloat progress);
+typedef void (^ WMFSavedArticlesFetcherProgress)(CGFloat progress);
 
 @protocol SavedArticlesFetcherDelegate <FetchFinishedDelegate>
 
diff --git a/wikipedia/Networking/Fetchers/SavedArticlesFetcher.m 
b/wikipedia/Networking/Fetchers/SavedArticlesFetcher.m
index 42cadcb..14ff4ad 100644
--- a/wikipedia/Networking/Fetchers/SavedArticlesFetcher.m
+++ b/wikipedia/Networking/Fetchers/SavedArticlesFetcher.m
@@ -70,19 +70,15 @@
     });
 }
 
-- (void)getProgress:(WMFSavedArticlesFetcherProgress)progressBlock{
-
+- (void)getProgress:(WMFSavedArticlesFetcherProgress)progressBlock {
     dispatch_async(self.accessQueue, ^{
-        
         CGFloat progress = [self progress];
-        
+
         dispatch_async(dispatch_get_main_queue(), ^{
-            
             progressBlock(progress);
         });
     });
 }
-
 
 - (CGFloat)progress {
     if ([self.savedPageList length] == 0) {
@@ -117,7 +113,7 @@
 
         dispatch_async(dispatch_get_main_queue(), ^{
             [self.fetchFinishedDelegate savedArticlesFetcher:self 
didFetchArticle:article progress:progress status:status error:error];
-            
+
             dispatch_async(self.accessQueue, ^{
                 if ([self.fetchersByArticleTitle count] == 0) {
                     [self notifyDelegate];
diff --git a/wikipedia/View Controllers/Image 
Gallery/WMFImageGalleryViewController.m b/wikipedia/View Controllers/Image 
Gallery/WMFImageGalleryViewController.m
index fddcc3a..9825300 100644
--- a/wikipedia/View Controllers/Image Gallery/WMFImageGalleryViewController.m
+++ b/wikipedia/View Controllers/Image Gallery/WMFImageGalleryViewController.m
@@ -246,7 +246,7 @@
                                 duration:(NSTimeInterval)duration {
     [super willRotateToInterfaceOrientation:toInterfaceOrientation 
duration:duration];
     NSUInteger const currentImageIndex = [self mostVisibleItemIndex];
-    ImgGalleryLog(@"Will scroll to %u after rotation animation finishes.", 
currentImageIndex);
+    ImgGalleryLog(@"Will scroll to %lu after rotation animation finishes.", 
currentImageIndex);
     [UIView animateWithDuration:duration
                           delay:0
                         options:UIViewAnimationOptionBeginFromCurrentState | 
UIViewAnimationOptionAllowAnimatedContent
diff --git a/wikipedia/View Controllers/LeadImage/FocalImage.h b/wikipedia/View 
Controllers/LeadImage/FocalImage.h
deleted file mode 100644
index ee6b73f..0000000
--- a/wikipedia/View Controllers/LeadImage/FocalImage.h
+++ /dev/null
@@ -1,26 +0,0 @@
-//  Created by Monte Hurd on 12/17/14.
-//  Copyright (c) 2014 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
-
-#import <UIKit/UIKit.h>
-
-@interface FocalImage : UIImage
-
-/*
-   This draws image with:
-
-    - Aspect fill.
-    - Horizontal center if horizontal overlap.
-    - Align top if no focalBounds, else vertical centered on focalBounds.
-    - Optional focalBounds highlight.
- */
-- (void)drawInRect:(CGRect)rect
-       focalBounds:(CGRect)focalBounds
-    focalHighlight:(BOOL)focalHighlight
-         blendMode:(CGBlendMode)blendMode
-             alpha:(CGFloat)alpha;
-
-// Repeated calls to "getFaceBounds" will return the next face
-// rect each time (rolling back to first face after last face).
-- (CGRect)getFaceBounds;
-
-@end
diff --git a/wikipedia/View Controllers/LeadImage/FocalImage.m b/wikipedia/View 
Controllers/LeadImage/FocalImage.m
deleted file mode 100644
index ca05737..0000000
--- a/wikipedia/View Controllers/LeadImage/FocalImage.m
+++ /dev/null
@@ -1,133 +0,0 @@
-//  Created by Monte Hurd on 12/17/14.
-//  Copyright (c) 2014 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
-
-#import "FocalImage.h"
-
-@interface FocalImage ()
-
-@property (strong, nonatomic) CIDetector* detector;
-@property (strong, nonatomic) NSArray* faces;
-@property (nonatomic) NSInteger nextFaceIndex;
-
-@end
-
-
-@implementation FocalImage
-
-- (void)drawInRect:(CGRect)rect
-       focalBounds:(CGRect)focalBounds
-    focalHighlight:(BOOL)focalHighlight
-         blendMode:(CGBlendMode)blendMode
-             alpha:(CGFloat)alpha {
-    if ((self.size.width == 0) || (self.size.height == 0)) {
-        return;
-    }
-
-    // Aspect fill.
-    float xScale = rect.size.width / self.size.width;
-    float yScale = rect.size.height / self.size.height;
-    float scale  = MAX(xScale, yScale);
-    CGSize size  = CGSizeMake(self.size.width * scale, self.size.height * 
scale);
-
-    // Align top.
-    CGRect r = (CGRect){{0, 0}, size};
-
-    // Center horizontally.
-    CGFloat m1     = CGRectGetMidX(r);
-    CGFloat m2     = CGRectGetMidX(rect);
-    CGFloat offset = (m2 - m1);
-    r = CGRectOffset(r, offset, 0.0);
-
-    // Figure out bottom overlap so we can know how much we can move the image 
up.
-    CGFloat bottomOverlap = r.size.height - rect.size.height;
-    if (bottomOverlap > 0.0) {
-        if (!CGRectIsEmpty(focalBounds)) {
-            // Move image up to vertically center focal bounds (as much as 
possible).
-            CGFloat yMidSelf        = CGRectGetMidY(rect);
-            CGFloat yMidFocalBounds = CGRectGetMidY(focalBounds) * scale;
-            CGFloat yShift          = fminf(yMidFocalBounds - yMidSelf, 
bottomOverlap);
-            if (yShift > 0) {
-                r = CGRectOffset(r, 0.0, -yShift);
-            }
-        } else {
-            // If no focalBounds, move the image up a bit, if possible.
-            CGFloat quarterOverlap = (bottomOverlap * 0.25);
-            r = CGRectOffset(r, 0.0, -quarterOverlap);
-        }
-    }
-
-    [self drawInRect:r blendMode:blendMode alpha:alpha];
-
-    // Draw a box over the focal bounds.
-    if (focalHighlight) {
-        CGRect scaledfocalBounds =
-            CGRectMake(
-                (focalBounds.origin.x * scale) + r.origin.x,
-                (focalBounds.origin.y * scale) + r.origin.y,
-                focalBounds.size.width * scale,
-                focalBounds.size.height * scale
-                );
-        [self fillFocalBounds:scaledfocalBounds];
-    }
-}
-
-- (void)fillFocalBounds:(CGRect)bounds {
-    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
-    CGContextRef context       = UIGraphicsGetCurrentContext();
-    CGContextSetRGBFillColor(context, 0.0, 1.0, 0.0, 0.3);
-    CGContextFillRect(context, bounds);
-    CGColorSpaceRelease(colorSpace);
-}
-
-- (CGRect)getFaceBounds {
-    // Optimized for repeated calls (for easy cycle through all faces).
-
-    // No need to make the detector more than once.
-    if (!self.detector) {
-        self.detector =
-            [CIDetector detectorOfType:CIDetectorTypeFace
-                               context:nil
-                               options:@{
-                 CIDetectorAccuracy: CIDetectorAccuracyLow,
-                 CIDetectorMinFeatureSize: @(0.05)
-             }];
-    }
-
-    // No need to set faces more than once.
-    if (!self.faces) {
-        CIImage* cgImage = [CIImage imageWithCGImage:self.CGImage];
-        self.faces = [self.detector featuresInImage:cgImage];
-    }
-
-    CGRect widestFaceRect = CGRectZero;
-
-    // Index overrun protection.
-    if (self.nextFaceIndex >= self.faces.count) {
-        return CGRectZero;
-    }
-
-    // Get face for nextFaceIndex.
-    widestFaceRect = ((CIFaceFeature*)self.faces[self.nextFaceIndex]).bounds;
-
-    if (CGRectIsEmpty(widestFaceRect)) {
-        return CGRectZero;
-    }
-
-    // Increment so next call will return next face.
-    self.nextFaceIndex++;
-
-    // Reset if last face so next call shows first face.
-    if (self.nextFaceIndex == self.faces.count) {
-        self.nextFaceIndex = 0;
-    }
-
-    CGRect faceImageCoords =
-        (CGRect){
-        {widestFaceRect.origin.x, self.size.height - widestFaceRect.origin.y - 
widestFaceRect.size.height},
-        widestFaceRect.size
-    };
-
-    return faceImageCoords;
-}
-
-@end
diff --git a/wikipedia/View Controllers/LeadImage/LeadImageContainer.m 
b/wikipedia/View Controllers/LeadImage/LeadImageContainer.m
index c746ecb..2281414 100644
--- a/wikipedia/View Controllers/LeadImage/LeadImageContainer.m
+++ b/wikipedia/View Controllers/LeadImage/LeadImageContainer.m
@@ -10,11 +10,16 @@
 #import "LeadImageTitleLabel.h"
 #import "UIScreen+Extras.h"
 #import "QueuesSingleton.h"
-#import "FocalImage.h"
+#import "WMFFaceDetector.h"
 #import "MWKArticle+isMain.h"
 #import "UIView+Debugging.h"
+#import "WebViewController.h"
+#import "URLCache.h"
+#import "WMFGeometry.h"
+#import "UIImage+WMFFocalImageDrawing.h"
 
-#define PLACEHOLDER_IMAGE_ALPHA 0.3f
+static const CGFloat kPlaceHolderImageAlpha                   = 0.3f;
+static const CGFloat kMinimumAcceptableCachedVariantThreshold = 0.6f;
 
 /*
    When YES this causes lead image faces to be highlighted in green and
@@ -23,32 +28,49 @@
    Do *not* leave this set to YES for release.
  */
 #if DEBUG
-#define HIGHLIGHT_FOCAL_FACE 0
+#define ENABLE_FACE_DETECTION_DEBUGGING 0
 #else
 // disable in release builds
-#define HIGHLIGHT_FOCAL_FACE 0
+#define ENABLE_FACE_DETECTION_DEBUGGING 0
 #endif
 
 @interface LeadImageContainer ()
 
+#pragma mark Private properties
+
 @property (weak, nonatomic) IBOutlet UIView* titleDescriptionContainer;
 @property (weak, nonatomic) IBOutlet LeadImageTitleLabel* titleLabel;
-@property (strong, nonatomic) FocalImage* image;
-@property (nonatomic) CGRect focalFaceBounds;
+@property (strong, nonatomic) UIImage* image;
 @property(strong, nonatomic) MWKArticle* article;
 @property (nonatomic) BOOL isPlaceholder;
 @property(strong, nonatomic) id rotationObserver;
 @property (nonatomic) CGFloat height;
+@property (nonatomic) BOOL isFaceDetectionNeeded;
+@property (strong, nonatomic) WMFFaceDetector* faceDetector;
+@property (strong, nonatomic) NSData* placeholderImageData;
+@property (nonatomic, strong) dispatch_queue_t serialFaceDetectionQueue;
+@property (nonatomic) CGRect focalFaceBounds;
+@property (nonatomic) BOOL shouldHighlightFace;
 
 @end
 
 @implementation LeadImageContainer
 
+#pragma mark Setup
+
 - (void)awakeFromNib {
-    self.height          = LEAD_IMAGE_CONTAINER_HEIGHT;
-    self.isPlaceholder   = NO;
-    self.clipsToBounds   = YES;
-    self.backgroundColor = [UIColor clearColor];
+    [self setupSerialFaceDetectionQueue];
+
+    self.focalFaceBounds       = CGRectZero;
+    self.shouldHighlightFace   = ENABLE_FACE_DETECTION_DEBUGGING;
+    self.image                 = nil;
+    self.faceDetector          = [[WMFFaceDetector alloc] init];
+    self.isFaceDetectionNeeded = NO;
+    self.height                = LEAD_IMAGE_CONTAINER_HEIGHT;
+    self.isPlaceholder         = NO;
+    self.clipsToBounds         = YES;
+    self.backgroundColor       = [UIColor clearColor];
+    self.placeholderImageData  = UIImagePNGRepresentation([UIImage 
imageNamed:@"lead-default"]);
     [self adjustConstraintsScaleForViews:@[self.titleLabel]];
 
     self.rotationObserver =
@@ -58,27 +80,66 @@
                                                       
usingBlock:^(NSNotification* notification) {
         [self updateNonImageElements];
     }];
-    #if HIGHLIGHT_FOCAL_FACE
-    // Testing code so we can hit "Command-Shift-M" to toggle through focal 
images.
-    [[NSNotificationCenter defaultCenter] 
addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
-                                                      object:nil
-                                                       queue:[NSOperationQueue 
mainQueue]
-                                                  usingBlock:^(NSNotification* 
note) {
-        // Repeated calls to getFaceBounds returns next face bounds each time.
-        self.focalFaceBounds = [self.image getFaceBounds];
-        [self setNeedsDisplay];
-    }];
+
+    #if ENABLE_FACE_DETECTION_DEBUGGING
+    [self debugSetupToggle];
     #endif
 
     // Important! "clipsToBounds" must be "NO" so super long titles lay out 
properly!
     self.clipsToBounds = NO;
 
+    [[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(webViewRetrievedAnImage:) name:@"SectionImageRetrieved" 
object:nil];
+
     //[self randomlyColorSubviews];
 }
 
-- (void)dealloc {
-    [[NSNotificationCenter defaultCenter] 
removeObserver:self.rotationObserver];
+- (void)setupSerialFaceDetectionQueue {
+    // Low priority serial dispatch queue. From 
http://stackoverflow.com/a/17690878/135557
+    // Images intercepted from web view need to have face detection ran
+    // serially to avoid running face detection more than necessary.
+    self.serialFaceDetectionQueue = 
dispatch_queue_create("org.wikimedia.wikipedia.LeadImageContainer.faceDetector.queue",
 DISPATCH_QUEUE_SERIAL);
+    dispatch_queue_t low = 
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
+    dispatch_set_target_queue(self.serialFaceDetectionQueue, low);
 }
+
+#pragma mark WebView image retrieval interception
+
+- (void)webViewRetrievedAnImage:(NSNotification*)notification {
+    // Notification received each time the web view retrieves an image.
+
+    if (![NAV.topViewController isMemberOfClass:[WebViewController class]]) {
+        return;
+    }
+
+    BOOL (^ notificationContainsImage)(NSNotification*) = ^BOOL 
(NSNotification* n) {
+        return (
+            n.userInfo[kURLCacheKeyFileNameNoSizePrefix]
+            &&
+            n.userInfo[kURLCacheKeyWidth]
+            &&
+            n.userInfo[kURLCacheKeyData]
+            );
+    };
+
+    if (notificationContainsImage(notification)) {
+        if ([self 
isRetrievedImageVariantOfLeadImage:notification.userInfo[kURLCacheKeyFileNameNoSizePrefix]])
 {
+            if (self.isPlaceholder || [self 
isRetrievedImageWiderThanLeadImage:notification.userInfo[kURLCacheKeyWidth]]) {
+                NSLog(@"INTERCEPTED WEBVIEW IMAGE of width: %@", 
notification.userInfo[kURLCacheKeyWidth]);
+                [self showImage:notification.userInfo[kURLCacheKeyData] 
isPlaceHolder:NO];
+            }
+        }
+    }
+}
+
+- (BOOL)isRetrievedImageWiderThanLeadImage:(NSString*)retrievedImageWidth {
+    return (retrievedImageWidth.floatValue > self.image.size.width);
+}
+
+- 
(BOOL)isRetrievedImageVariantOfLeadImage:(NSString*)retrievedImageNameNoSizePrefix
 {
+    return ([self.article.image.fileNameNoSizePrefix 
isEqualToString:retrievedImageNameNoSizePrefix]);
+}
+
+#pragma mark Drawing
 
 - (void)drawRect:(CGRect)rect {
     [super drawRect:rect];
@@ -94,15 +155,15 @@
     // the gradient will look smooth.
     [self drawGradientBackground];
 
-    CGFloat alpha = self.isPlaceholder ? PLACEHOLDER_IMAGE_ALPHA : 1.0;
+    CGFloat alpha = self.isPlaceholder ? kPlaceHolderImageAlpha : 1.0;
 
     // Draw lead image, aspect fill, align top, vertically centering
     // focalFaceBounds face if necessary.
-    [self.image drawInRect:rect
-               focalBounds:self.focalFaceBounds
-            focalHighlight:HIGHLIGHT_FOCAL_FACE
-                 blendMode:kCGBlendModeMultiply
-                     alpha:alpha];
+    [self.image wmf_drawInRect:rect
+                   
focalBounds:WMFRectFromUnitRectForReferenceSize(self.focalFaceBounds, 
self.image.size)
+                focalHighlight:self.shouldHighlightFace
+                     blendMode:kCGBlendModeMultiply
+                         alpha:alpha];
 }
 
 - (void)drawGradientBackground {
@@ -164,6 +225,8 @@
     CGColorSpaceRelease(colorSpace);
 }
 
+#pragma mark Layout
+
 - (void)updateNonImageElements {
     // Updates title/description text color.
     [self updateTitleColors];
@@ -172,6 +235,14 @@
     [self updateHeights];
 
     [self setNeedsDisplay];
+}
+
+- (void)updateNonImageElementsIfNecessary {
+    if (!(self.isFaceDetectionNeeded && !self.isPlaceholder)) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [self updateNonImageElements];
+        });
+    }
 }
 
 - (void)updateHeights {
@@ -207,6 +278,8 @@
     self.titleLabel.shadowColor = shadowColor;
 }
 
+#pragma mark Flags
+
 - (BOOL)shouldHideImage {
     return
         UIInterfaceOrientationIsLandscape([[UIScreen mainScreen] 
interfaceOrientation])
@@ -222,79 +295,94 @@
     return (url.pathExtension && [url.pathExtension isEqualToString:@"gif"]) ? 
YES : NO;
 }
 
-- (void)showForArticle:(MWKArticle*)article {
-    self.article         = article;
-    self.focalFaceBounds = CGRectZero;
+#pragma mark Show
 
+- (void)showForArticle:(MWKArticle*)article {
+    self.article                = article;
+    self.focalFaceBounds        = CGRectZero;
     self.titleLabel.imageExists = [self imageExists];
+    self.image                  = nil;
+    self.isFaceDetectionNeeded  = YES;
 
     if (self.article.isMain) {
         [self.titleLabel setTitle:@"" description:@""];
+        [self updateNonImageElements];
+        return;
     } else {
         NSString* title = [self.article.displaytitle getStringWithoutHTML];
         [self.titleLabel setTitle:title description:[self 
getCurrentArticleDescription]];
     }
 
-    if (!self.article.image.asUIImage && [self imageExists]) {
-        [self loadPlaceholderImage];
+    // Show largest cached variant of lead image, or placeholder, immediately.
+    // This image is shown until the webview (potentially) retrieves higher 
resolution variants.
+    MWKImage* largestCachedVariant = self.article.image.largestCachedVariant;
+    if (largestCachedVariant) {
+        NSLog(@"SHOWING LARGEST CACHED VARIANT of width: %f", 
largestCachedVariant.width.floatValue);
+        [self showImage:[largestCachedVariant asNSData] isPlaceHolder:NO];
+    } else {
+        [self showImage:self.placeholderImageData isPlaceHolder:YES];
+    }
 
+    if (![self isLargestCachedVariantSufficient:largestCachedVariant]) {
         (void)[[ThumbnailFetcher alloc] initAndFetchThumbnailFromURL:[@"http:" 
stringByAppendingString:self.article.imageURL]
                                                          
withManager:[QueuesSingleton sharedInstance].articleFetchManager
                                                   thenNotifyDelegate:self];
-    } else {
-        [self showImage];
     }
 }
 
-- (void)loadPlaceholderImage {
-    FocalImage* placeholderImage =
-        [[FocalImage alloc] initWithCGImage:[UIImage 
imageNamed:@"lead-default"].CGImage];
-    self.isPlaceholder = YES;
-    self.image         = placeholderImage;
-    [self updateNonImageElements];
-}
-
-- (void)showImage {
-    UIImage* img = self.article.image.asUIImage;
-
-    FocalImage* image = [[FocalImage alloc] initWithCGImage:img.CGImage];
-
-    // Biggest face.
-    self.focalFaceBounds = [image getFaceBounds];
-
-    //NSLog(@"Requested lead image width = %d", LEAD_IMAGE_WIDTH);
-    //NSLog(@"Returned lead image width = %d", 
self.article.image.width.integerValue);
-    //NSLog(@"percent = %f", self.article.image.width.floatValue / 
LEAD_IMAGE_WIDTH);
-
-    /*
-       Note: this doesn't work as-is. Would need to listen for 
"SectionImageRetrieved"
-       notifications because article images are late-arriving.
-
-       Use first article image if no image at this point.
-
-       Should probably only do this if the image above is going to be greatly
-       cropped *and* no faces were detected. Then, instead of loading first 
image
-       of minimum size, as noted below, use first image of sufficient area 
which
-       would not need to be aggresively cropped.
-
-       Note: commented this out because I'm not sure it ever gets called.
-       Would also probably want to only use the first article image if it's
-       bigger than some minimum size.
-
-       if(!img){
-        MWKImage *firstArticleImage = self.article.sections[0].images[0];
-        UIImage *firstUIImage = firstArticleImage.asUIImage;
-        if(firstUIImage){
-            img = firstUIImage;
+- (BOOL)isLargestCachedVariantSufficient:(MWKImage*)largestCachedVariant {
+    if (![largestCachedVariant isEqualToImage:self.article.image]) {
+        CGFloat okMinimumWidth = LEAD_IMAGE_WIDTH * 
kMinimumAcceptableCachedVariantThreshold;
+        if (largestCachedVariant.width.floatValue < okMinimumWidth) {
+            if (self.article.imageURL) {
+                NSInteger widestExpectedImageWidth = [self 
widthOfWidestVariantWebViewWillDownload];
+                if (widestExpectedImageWidth < okMinimumWidth) {
+                    return NO;
+                }
+            }
         }
-       }
-     */
-
-    self.isPlaceholder = NO;
-    self.image         = image;
-
-    [self updateNonImageElements];
+    }
+    return YES;
 }
+
+- (void)showImage:(NSData*)retrievedImageData 
isPlaceHolder:(BOOL)isPlaceHolder {
+    self.isPlaceholder = isPlaceHolder;
+
+    // Face detection is faster if the UIImage has CIImage backing.
+    CIImage* ciImage = [[CIImage alloc] initWithData:retrievedImageData];
+    self.image = [UIImage imageWithCIImage:ciImage];
+
+    [self detectFaceIfNecessary];
+    [self updateNonImageElementsIfNecessary];
+}
+
+- (NSInteger)widthOfWidestVariantWebViewWillDownload {
+    MWKImage* widestUncachedVariant = nil;
+    NSArray* arr                    = [self.article.images 
imageSizeVariants:self.article.imageURL];
+    for (NSString* variantURL in [arr reverseObjectEnumerator]) {
+        MWKImage* image = [self.article imageWithURL:variantURL];
+        // Must exclude article.image because it is not retrieved by the web 
view
+        // (it's the thing we're deciding if we need to download!)
+        if (![image isEqualToImage:self.article.image]) {
+            if (!image.isCached) {
+                widestUncachedVariant = image;
+                break;
+            }
+        }
+    }
+    if (widestUncachedVariant) {
+        // Parse the width out of the url - necessary because the image 
probably hasn't been
+        // retrieved yet, so width and height properties won't be set yet.
+        // Note: occasionally images don't have size prefix in their file 
name, so for these
+        // images we won't be able to divine ahead of time whether among the 
images to be
+        // downloaded by the webview there will be one of sufficient 
resolution. In these
+        // cases it's ok because the higher res image will be fetched with the 
ThumbnailFetcher.
+        return [MWKImage fileSizePrefix:widestUncachedVariant.sourceURL];
+    }
+    return -1;
+}
+
+#pragma mark Fetch finished
 
 - (void)fetchFinished:(id)sender
           fetchedData:(id)fetchedData
@@ -304,14 +392,18 @@
         switch (status) {
             case FETCH_FINAL_STATUS_SUCCEEDED:
             {
-                // Associate the image retrieved with article.image.
-                ThumbnailFetcher* fetcher = (ThumbnailFetcher*)sender;
-                NSString* thumbnailURL    = [fetcher.url getUrlWithoutScheme];
+                
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
+                    // Associate the image retrieved with article.image.
+                    ThumbnailFetcher* fetcher = (ThumbnailFetcher*)sender;
+                    NSString* thumbnailURL = [fetcher.url getUrlWithoutScheme];
 
-                MWKImage* articleImage = [[MWKImage alloc] 
initWithArticle:self.article sourceURL:thumbnailURL];
-                [articleImage importImageData:fetchedData];
+                    MWKImage* articleImage = [[MWKImage alloc] 
initWithArticle:self.article sourceURL:thumbnailURL];
+                    [articleImage importImageData:fetchedData];
 
-                [self showImage];
+                    NSLog(@"FETCHED HIGHER RES VARIANT of width: %f", 
articleImage.width.floatValue);
+
+                    [self showImage:[articleImage asNSData] isPlaceHolder:NO];
+                });
             }
             break;
             case FETCH_FINAL_STATUS_FAILED:
@@ -326,6 +418,8 @@
     }
 }
 
+#pragma mark Description
+
 - (NSString*)getCurrentArticleDescription {
     NSString* description = self.article.entityDescription;
     if (description) {
@@ -335,4 +429,72 @@
     return description;
 }
 
+#pragma mark Face detection
+
+- (void)detectFaceIfNecessary {
+    if (self.isFaceDetectionNeeded && !self.isPlaceholder) {
+        UIImage* imageToDetect = self.image; // Ensure async block is working 
on this size variant.
+
+        dispatch_async(self.serialFaceDetectionQueue, ^{ // Important that 
this is a serial queue!
+            //NSLog(@"Face detection block ran for image of size = %@", 
NSStringFromCGSize(imageToDetect.size));
+
+            if (self.isFaceDetectionNeeded) { // Re-check in case it changed 
since block was dispatched.
+                self.faceDetector.image = imageToDetect;
+                CGRect faceBounds = [self.faceDetector detectFace];
+
+                //NSLog(@"FACE DETECTION ACTUALLY RAN FOR IMAGE SIZE = %@", 
NSStringFromCGSize(imageToDetect.size));
+
+                BOOL faceDetected = !CGRectIsEmpty(faceBounds);
+
+                // Store as unit rect so we don't have to re-run face 
detection on subsequent size
+                self.focalFaceBounds = 
WMFUnitRectFromRectForReferenceSize(faceBounds, imageToDetect.size);
+
+                if (faceDetected) {
+                    //NSLog(@"FACE FOUND FOR IMAGE SIZE = %@", 
NSStringFromCGSize(imageToDetect.size));
+
+                    self.isFaceDetectionNeeded = NO;
+                }
+            }
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [self updateNonImageElements];
+            });
+        });
+    }
+}
+
+#pragma mark Easy face detection debugging
+
+- (void)debugSetupToggle {
+    // Testing code so we can hit "Command-Shift-M" to toggle through focal 
images.
+    [[NSNotificationCenter defaultCenter] 
addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
+                                                      object:nil
+                                                       queue:[NSOperationQueue 
mainQueue]
+                                                  usingBlock:^(NSNotification* 
notification) {
+        [self debugDetectNextFace];
+    }];
+}
+
+- (void)debugDetectNextFace {
+    // Ensure detector is set to last image retrieved. Detector may have
+    // successfully detected large face in an earlier low res image, but
+    // current image may be higher res. See "Madonna del Granduca" enwiki
+    // article. Without this only the mother's face available in cycle as
+    // it is the only one detected when the first low-res variant is
+    // retrieved.
+    if (self.faceDetector.image != self.image) {
+        self.faceDetector.image = self.image;
+    }
+
+    // Repeated calls to detectNextFace returns next face bounds each time.
+    self.focalFaceBounds = 
WMFUnitRectFromRectForReferenceSize([self.faceDetector detectFace], 
self.faceDetector.image.size);
+    [self setNeedsDisplay];
+}
+
+#pragma mark Dealloc
+
+- (void)dealloc {
+    [[NSNotificationCenter defaultCenter] 
removeObserver:self.rotationObserver];
+    [[NSNotificationCenter defaultCenter] removeObserver:self 
name:@"SectionImageRetrieved" object:nil];
+}
+
 @end
diff --git a/wikipedia/View Controllers/Preview/PreviewAndSaveViewController.m 
b/wikipedia/View Controllers/Preview/PreviewAndSaveViewController.m
index 81e46be..6f16178 100644
--- a/wikipedia/View Controllers/Preview/PreviewAndSaveViewController.m
+++ b/wikipedia/View Controllers/Preview/PreviewAndSaveViewController.m
@@ -499,7 +499,7 @@
                 break;
         }
     } else if ([sender isKindOfClass:[WikiTextSectionUploader class]]) {
-        WikiTextSectionUploader* uploader = (WikiTextSectionUploader*)sender;
+        //WikiTextSectionUploader* uploader = (WikiTextSectionUploader*)sender;
 
         switch (status) {
             case FETCH_FINAL_STATUS_SUCCEEDED: {
diff --git a/wikipedia/View Controllers/SavedPages/SavedPagesViewController.m 
b/wikipedia/View Controllers/SavedPages/SavedPagesViewController.m
index ce4055d..82f1031 100644
--- a/wikipedia/View Controllers/SavedPages/SavedPagesViewController.m
+++ b/wikipedia/View Controllers/SavedPages/SavedPagesViewController.m
@@ -429,11 +429,8 @@
 }
 
 - (void)resumeRefresh {
-    
     [[SavedArticlesFetcher sharedInstance] getProgress:^(CGFloat progress) {
-
-        if(progress < 100){
-            
+        if (progress < 100) {
             self.progressView.progress = progress;
             [SavedArticlesFetcher sharedInstance].fetchFinishedDelegate = self;
 
diff --git a/wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.h 
b/wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.h
index 154f417..8745e0f 100644
--- a/wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.h
+++ b/wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.h
@@ -2,9 +2,7 @@
 
 #import <UIKit/UIKit.h>
 
-@class FocalImage;
-
 @interface WMFShareCardImageContainer : UIView
-@property (strong, nonatomic) FocalImage* image;
+@property (strong, nonatomic) UIImage* image;
 
 @end
diff --git a/wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.m 
b/wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.m
index 73cd08b..3dcf4f6 100644
--- a/wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.m
+++ b/wikipedia/View Controllers/ShareCard/WMFShareCardImageContainer.m
@@ -1,19 +1,41 @@
 
 
 #import "WMFShareCardImageContainer.h"
-#import "FocalImage.h"
+#import "UIImage+WMFFocalImageDrawing.h"
+#import "WMFFaceDetector.h"
 
+@interface WMFShareCardImageContainer ()
+
+@property(nonatomic, strong) WMFFaceDetector* faceDetector;
+@property(nonatomic) CGRect faceBounds;
+
+@end
 
 @implementation WMFShareCardImageContainer
+
+- (instancetype)initWithCoder:(NSCoder*)coder {
+    self = [super initWithCoder:coder];
+    if (self) {
+        self.faceDetector = [[WMFFaceDetector alloc] init];
+    }
+    return self;
+}
+
+- (void)setImage:(UIImage*)image {
+    _image                  = image;
+    self.faceDetector.image = image;
+    self.faceBounds         = [self.faceDetector detectFace];
+}
 
 - (void)drawRect:(CGRect)rect {
     [super drawRect:rect];
     [self drawGradientBackground];
-    [self.image drawInRect:rect
-               focalBounds:[self.image getFaceBounds]
-            focalHighlight:NO
-                 blendMode:kCGBlendModeMultiply
-                     alpha:1.0];
+
+    [self.image wmf_drawInRect:rect
+                   focalBounds:self.faceBounds
+                focalHighlight:NO
+                     blendMode:kCGBlendModeMultiply
+                         alpha:1.0];
 }
 
 // TODO: in follow-up patch, factor drawGradientBackground from
diff --git a/wikipedia/View Controllers/ShareCard/WMFShareCardViewController.m 
b/wikipedia/View Controllers/ShareCard/WMFShareCardViewController.m
index 481d89f..6d14137 100644
--- a/wikipedia/View Controllers/ShareCard/WMFShareCardViewController.m
+++ b/wikipedia/View Controllers/ShareCard/WMFShareCardViewController.m
@@ -7,7 +7,6 @@
 //
 
 #import "WMFShareCardViewController.h"
-#import "FocalImage.h"
 #import "NSString+Extras.h"
 #import "WMFShareCardImageContainer.h"
 #import "MWLanguageInfo.h"
@@ -59,11 +58,13 @@
     self.shareArticleDescription.text          = [[article.entityDescription 
getStringWithoutHTML] capitalizeFirstLetter];
     self.shareArticleDescription.textAlignment = subtextAlignment;
 
-    UIImage* leadImage = [article.image asUIImage];
-    if (leadImage) {
+    NSData* leadImageData = [article.image.largestCachedVariant asNSData];
+    if (leadImageData) {
         // in case the image has transparency, make its container white
         self.shareCardImageContainer.backgroundColor = [UIColor whiteColor];
-        self.shareCardImageContainer.image           = [[FocalImage alloc] 
initWithCGImage:leadImage.CGImage];
+        // Face detection is faster if the image has CIImage backing.
+        CIImage* ciImage = [[CIImage alloc] initWithData:leadImageData];
+        self.shareCardImageContainer.image = [UIImage 
imageWithCIImage:ciImage];
     }
 }
 
diff --git a/wikipedia/Web Image Interception/URLCache.h b/wikipedia/Web Image 
Interception/URLCache.h
index b9d33b0..148fdef 100644
--- a/wikipedia/Web Image Interception/URLCache.h
+++ b/wikipedia/Web Image Interception/URLCache.h
@@ -3,6 +3,13 @@
 
 #import <Foundation/Foundation.h>
 
+extern NSString* const kURLCacheKeyFileName;
+extern NSString* const kURLCacheKeyData;
+extern NSString* const kURLCacheKeyWidth;
+extern NSString* const kURLCacheKeyHeight;
+extern NSString* const kURLCacheKeyURL;
+extern NSString* const kURLCacheKeyFileNameNoSizePrefix;
+
 @interface URLCache : NSURLCache
 
 @end
diff --git a/wikipedia/Web Image Interception/URLCache.m b/wikipedia/Web Image 
Interception/URLCache.m
index acf7723..d64248f 100644
--- a/wikipedia/Web Image Interception/URLCache.m
+++ b/wikipedia/Web Image Interception/URLCache.m
@@ -2,11 +2,15 @@
 //  Copyright (c) 2013 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
 
 #import "URLCache.h"
-//#import "ArticleDataContextSingleton.h"
-//#import "ArticleCoreDataObjects.h"
-//#import "NSManagedObjectContext+SimpleFetch.h"
 #import "NSString+Extras.h"
 #import "SessionSingleton.h"
+
+NSString* const kURLCacheKeyFileName             = @"fileName";
+NSString* const kURLCacheKeyData                 = @"data";
+NSString* const kURLCacheKeyWidth                = @"width";
+NSString* const kURLCacheKeyHeight               = @"height";
+NSString* const kURLCacheKeyURL                  = @"url";
+NSString* const kURLCacheKeyFileNameNoSizePrefix = @"fileNameNoSizePrefix";
 
 #if 0
 #define URLCacheLog(...) NSLog(__VA_ARGS__)
@@ -133,8 +137,12 @@
     [[NSNotificationCenter defaultCenter] 
postNotificationName:@"SectionImageRetrieved"
                                                         object:nil
                                                       userInfo:@{
-         @"fileName": image.fileName,
-         @"data": imageDataToUse,
+         kURLCacheKeyFileName: image.fileName,
+         kURLCacheKeyData: imageDataToUse,
+         kURLCacheKeyWidth: image.width,
+         kURLCacheKeyHeight: image.height,
+         kURLCacheKeyURL: image.sourceURL,
+         kURLCacheKeyFileNameNoSizePrefix: image.fileNameNoSizePrefix
      }];
 }
 
diff --git a/wikipedia/en.lproj/Main_iPhone.strings 
b/wikipedia/en.lproj/Main_iPhone.strings
index 9349c6d..f670b7e 100644
--- a/wikipedia/en.lproj/Main_iPhone.strings
+++ b/wikipedia/en.lproj/Main_iPhone.strings
@@ -1,75 +1,75 @@
 
-/* Class = "IBUIButton"; normalTitle = "Show another captcha"; ObjectID = 
"21c-U6-yfo"; */
+/* Class = "UIButton"; normalTitle = "Show another captcha"; ObjectID = 
"21c-U6-yfo"; */
 "21c-U6-yfo.normalTitle" = "Show another captcha";
 
-/* Class = "IBUITextField"; placeholder = "User name"; ObjectID = 
"5cT-2Y-0Ie"; */
+/* Class = "UITextField"; placeholder = "User name"; ObjectID = "5cT-2Y-0Ie"; 
*/
 "5cT-2Y-0Ie.placeholder" = "User name";
 
-/* Class = "IBUILabel"; text = "Saved pages are pretty awesome. Think of them 
as bookmarks that you can read even when you are offline."; ObjectID = 
"GiD-Rj-wb7"; */
+/* Class = "UILabel"; text = "Saved pages are pretty awesome. Think of them as 
bookmarks that you can read even when you are offline."; ObjectID = 
"GiD-Rj-wb7"; */
 "GiD-Rj-wb7.text" = "Saved pages are pretty awesome. Think of them as 
bookmarks that you can read even when you are offline.";
 
-/* Class = "IBUILabel"; text = "Preview"; ObjectID = "LfM-01-aCF"; */
+/* Class = "UILabel"; text = "Preview"; ObjectID = "LfM-01-aCF"; */
 "LfM-01-aCF.text" = "Preview";
 
-/* Class = "IBUILabel"; text = "Skip"; ObjectID = "P6J-IE-CiO"; */
+/* Class = "UILabel"; text = "Skip"; ObjectID = "P6J-IE-CiO"; */
 "P6J-IE-CiO.text" = "Skip";
 
-/* Class = "IBUITextField"; placeholder = "Password"; ObjectID = "PCr-0J-fBj"; 
*/
+/* Class = "UITextField"; placeholder = "Password"; ObjectID = "PCr-0J-fBj"; */
 "PCr-0J-fBj.placeholder" = "Password";
 
-/* Class = "IBUILabel"; text = "Canonical Language"; ObjectID = "SER-n4-DZC"; 
*/
+/* Class = "UILabel"; text = "Canonical Language"; ObjectID = "SER-n4-DZC"; */
 "SER-n4-DZC.text" = "Canonical Language";
 
-/* Class = "IBUITextField"; placeholder = "Re-type password"; ObjectID = 
"UgH-77-lyp"; */
+/* Class = "UITextField"; placeholder = "Re-type password"; ObjectID = 
"UgH-77-lyp"; */
 "UgH-77-lyp.placeholder" = "Re-type password";
 
-/* Class = "IBUILabel"; text = "No saved pages yet."; ObjectID = "W1c-wQ-1pa"; 
*/
+/* Class = "UILabel"; text = "No saved pages yet."; ObjectID = "W1c-wQ-1pa"; */
 "W1c-wQ-1pa.text" = "No saved pages yet.";
 
-/* Class = "IBUILabel"; text = "Log in"; ObjectID = "Wff-o9-AdH"; */
+/* Class = "UILabel"; text = "Log in"; ObjectID = "Wff-o9-AdH"; */
 "Wff-o9-AdH.text" = "Log in";
 
-/* Class = "IBUILabel"; text = "Label"; ObjectID = "XkB-Xo-Xq0"; */
+/* Class = "UILabel"; text = "Label"; ObjectID = "XkB-Xo-Xq0"; */
 "XkB-Xo-Xq0.text" = "Label";
 
-/* Class = "IBUILabel"; text = "Test Zero Label Text"; ObjectID = 
"aCV-ih-PXn"; */
+/* Class = "UILabel"; text = "Test Zero Label Text"; ObjectID = "aCV-ih-PXn"; 
*/
 "aCV-ih-PXn.text" = "Test Zero Label Text";
 
-/* Class = "IBUILabel"; text = "No recent pages here."; ObjectID = 
"aUp-0e-F6i"; */
+/* Class = "UILabel"; text = "No recent pages here."; ObjectID = "aUp-0e-F6i"; 
*/
 "aUp-0e-F6i.text" = "No recent pages here.";
 
-/* Class = "IBUILabel"; text = "Create Account"; ObjectID = "c3c-PU-Exz"; */
+/* Class = "UILabel"; text = "Create Account"; ObjectID = "c3c-PU-Exz"; */
 "c3c-PU-Exz.text" = "Create Account";
 
-/* Class = "IBUILabel"; text = "Label"; ObjectID = "cbH-8H-z54"; */
+/* Class = "UILabel"; text = "Label"; ObjectID = "cbH-8H-z54"; */
 "cbH-8H-z54.text" = "Label";
 
-/* Class = "IBUITextField"; placeholder = "Enter CAPTCHA text from image 
above"; ObjectID = "gPg-cg-Yjy"; */
+/* Class = "UITextField"; placeholder = "Enter CAPTCHA text from image above"; 
ObjectID = "gPg-cg-Yjy"; */
 "gPg-cg-Yjy.placeholder" = "Enter CAPTCHA text from image above";
 
-/* Class = "IBUILabel"; text = "Already have an account? Log in"; ObjectID = 
"heA-3K-nhS"; */
+/* Class = "UILabel"; text = "Already have an account? Log in"; ObjectID = 
"heA-3K-nhS"; */
 "heA-3K-nhS.text" = "Already have an account? Log in";
 
-/* Class = "IBUILabel"; text = "You probably deleted all of them. Next time 
you go to a page you can get back to it from here."; ObjectID = "huU-kO-aYI"; */
+/* Class = "UILabel"; text = "You probably deleted all of them. Next time you 
go to a page you can get back to it from here."; ObjectID = "huU-kO-aYI"; */
 "huU-kO-aYI.text" = "You probably deleted all of them. Next time you go to a 
page you can get back to it from here.";
 
-/* Class = "IBUILabel"; text = "Create Account"; ObjectID = "jiW-Cg-oL3"; */
+/* Class = "UILabel"; text = "Create Account"; ObjectID = "jiW-Cg-oL3"; */
 "jiW-Cg-oL3.text" = "Create Account";
 
-/* Class = "IBUILabel"; text = "Language"; ObjectID = "jxY-ej-I9e"; */
+/* Class = "UILabel"; text = "Language"; ObjectID = "jxY-ej-I9e"; */
 "jxY-ej-I9e.text" = "Language";
 
-/* Class = "IBUITextField"; placeholder = "Password"; ObjectID = "kVb-lx-d6C"; 
*/
+/* Class = "UITextField"; placeholder = "Password"; ObjectID = "kVb-lx-d6C"; */
 "kVb-lx-d6C.placeholder" = "Password";
 
-/* Class = "IBUITextField"; placeholder = "User name"; ObjectID = 
"mAk-1N-jPC"; */
+/* Class = "UITextField"; placeholder = "User name"; ObjectID = "mAk-1N-jPC"; 
*/
 "mAk-1N-jPC.placeholder" = "User name";
 
-/* Class = "IBUILabel"; text = "Label"; ObjectID = "nI1-bn-0Ii"; */
+/* Class = "UILabel"; text = "Label"; ObjectID = "nI1-bn-0Ii"; */
 "nI1-bn-0Ii.text" = "Label";
 
-/* Class = "IBUITextField"; placeholder = "Email address (optional)"; ObjectID 
= "rKI-nq-3p7"; */
+/* Class = "UITextField"; placeholder = "Email address (optional)"; ObjectID = 
"rKI-nq-3p7"; */
 "rKI-nq-3p7.placeholder" = "Email address (optional)";
 
-/* Class = "IBUILabel"; text = "Create account"; ObjectID = "wkl-j8-wLX"; */
+/* Class = "UILabel"; text = "Create account"; ObjectID = "wkl-j8-wLX"; */
 "wkl-j8-wLX.text" = "Create account";

-- 
To view, visit https://gerrit.wikimedia.org/r/193323
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I979f3a07c7d37febd5b7d9f99a8105223f2525b3
Gerrit-PatchSet: 23
Gerrit-Project: apps/ios/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Mhurd <mh...@wikimedia.org>
Gerrit-Reviewer: Bgerstle <bgers...@wikimedia.org>
Gerrit-Reviewer: Dr0ptp4kt <ab...@wikimedia.org>
Gerrit-Reviewer: Fjalapeno <cfl...@wikimedia.org>
Gerrit-Reviewer: Mhurd <mh...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to