diff --git a/Evergreen.xcodeproj/project.pbxproj b/Evergreen.xcodeproj/project.pbxproj index a16ec029e..980d659ed 100644 --- a/Evergreen.xcodeproj/project.pbxproj +++ b/Evergreen.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ 84B06FEA1ED3803A00F0B54B /* RSFeedFinder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FE61ED3803200F0B54B /* RSFeedFinder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84B06FFD1ED3818D00F0B54B /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FFA1ED3818000F0B54B /* RSTree.framework */; }; 84B06FFE1ED3818D00F0B54B /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FFA1ED3818000F0B54B /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 84B0700A1ED3822600F0B54B /* RSTextDrawing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; }; + 84B0700B1ED3822600F0B54B /* RSTextDrawing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -211,6 +213,27 @@ remoteGlobalIDString = 842A0BE01CFCB9BC00BF746C; remoteInfo = RSTree; }; + 84B070061ED3821900F0B54B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8439D9FB1C8937C800E5E4B4; + remoteInfo = RSTextDrawing; + }; + 84B070081ED3821900F0B54B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8439DA051C8937C800E5E4B4; + remoteInfo = RSTextDrawingTests; + }; + 84B0700C1ED3822600F0B54B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 8439D9FA1C8937C800E5E4B4; + remoteInfo = RSTextDrawing; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -221,6 +244,7 @@ dstSubfolderSpec = 10; files = ( 84B06FB31ED37DBD00F0B54B /* RSDatabase.framework in Embed Frameworks */, + 84B0700B1ED3822600F0B54B /* RSTextDrawing.framework in Embed Frameworks */, 84B06FEA1ED3803A00F0B54B /* RSFeedFinder.framework in Embed Frameworks */, 84B06FFE1ED3818D00F0B54B /* RSTree.framework in Embed Frameworks */, 84B06FAF1ED37DBD00F0B54B /* RSCore.framework in Embed Frameworks */, @@ -250,6 +274,7 @@ 84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DB5.xcodeproj; path = Frameworks/DB5/DB5.xcodeproj; sourceTree = ""; }; 84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSFeedFinder.xcodeproj; path = Frameworks/RSFeedFinder/RSFeedFinder.xcodeproj; sourceTree = ""; }; 84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTree.xcodeproj; path = Frameworks/RSTree/RSTree.xcodeproj; sourceTree = ""; }; + 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTextDrawing.xcodeproj; path = Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -258,6 +283,7 @@ buildActionMask = 2147483647; files = ( 84B06FB21ED37DBD00F0B54B /* RSDatabase.framework in Frameworks */, + 84B0700A1ED3822600F0B54B /* RSTextDrawing.framework in Frameworks */, 84B06FE91ED3803A00F0B54B /* RSFeedFinder.framework in Frameworks */, 84B06FFD1ED3818D00F0B54B /* RSTree.framework in Frameworks */, 84B06FAE1ED37DBD00F0B54B /* RSCore.framework in Frameworks */, @@ -291,6 +317,7 @@ 84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */, 84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */, 84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */, + 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */, 84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */, 84B06FB61ED37E8B00F0B54B /* RSWeb.xcodeproj */, 84B06F761ED37BCA00F0B54B /* RSXML.xcodeproj */, @@ -382,6 +409,15 @@ name = Products; sourceTree = ""; }; + 84B070021ED3821800F0B54B /* Products */ = { + isa = PBXGroup; + children = ( + 84B070071ED3821900F0B54B /* RSTextDrawing.framework */, + 84B070091ED3821900F0B54B /* RSTextDrawingTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -404,6 +440,7 @@ 84B06FD21ED37F7D00F0B54B /* PBXTargetDependency */, 84B06FEC1ED3803A00F0B54B /* PBXTargetDependency */, 84B070001ED3818D00F0B54B /* PBXTargetDependency */, + 84B0700D1ED3822600F0B54B /* PBXTargetDependency */, ); name = Evergreen; productName = Evergreen; @@ -479,6 +516,10 @@ ProductGroup = 84B06FE11ED3803200F0B54B /* Products */; ProjectRef = 84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */; }, + { + ProductGroup = 84B070021ED3821800F0B54B /* Products */; + ProjectRef = 84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */; + }, { ProductGroup = 84B06FF51ED3818000F0B54B /* Products */; ProjectRef = 84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */; @@ -627,6 +668,20 @@ remoteRef = 84B06FFB1ED3818000F0B54B /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 84B070071ED3821900F0B54B /* RSTextDrawing.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RSTextDrawing.framework; + remoteRef = 84B070061ED3821900F0B54B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 84B070091ED3821900F0B54B /* RSTextDrawingTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RSTextDrawingTests.xctest; + remoteRef = 84B070081ED3821900F0B54B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ @@ -709,6 +764,11 @@ name = RSTree; targetProxy = 84B06FFF1ED3818D00F0B54B /* PBXContainerItemProxy */; }; + 84B0700D1ED3822600F0B54B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RSTextDrawing; + targetProxy = 84B0700C1ED3822600F0B54B /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ diff --git a/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj b/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj new file mode 100644 index 000000000..fcbbc7898 --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.pbxproj @@ -0,0 +1,444 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 84193AB31CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h in Headers */ = {isa = PBXBuildFile; fileRef = 84193AB11CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 84193AB41CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m in Sources */ = {isa = PBXBuildFile; fileRef = 84193AB21CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m */; }; + 8439D9FF1C8937C800E5E4B4 /* RSTextDrawing.h in Headers */ = {isa = PBXBuildFile; fileRef = 8439D9FE1C8937C800E5E4B4 /* RSTextDrawing.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8439DA061C8937C800E5E4B4 /* RSTextDrawing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8439D9FB1C8937C800E5E4B4 /* RSTextDrawing.framework */; }; + 8439DA0B1C8937C800E5E4B4 /* RSTextDrawingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8439DA0A1C8937C800E5E4B4 /* RSTextDrawingTests.m */; }; + 846416401C8938210064C661 /* RSSingleLineRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8464163E1C8938210064C661 /* RSSingleLineRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 846416411C8938210064C661 /* RSSingleLineRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8464163F1C8938210064C661 /* RSSingleLineRenderer.m */; }; + 84B717761CF9629000FF029D /* RSSingleLineView.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B717741CF9629000FF029D /* RSSingleLineView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 84B717771CF9629000FF029D /* RSSingleLineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B717751CF9629000FF029D /* RSSingleLineView.m */; }; + 84B7177B1CF9665100FF029D /* RSMultiLineView.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B717791CF9665100FF029D /* RSMultiLineView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 84B7177C1CF9665100FF029D /* RSMultiLineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B7177A1CF9665100FF029D /* RSMultiLineView.m */; }; + 84B7177D1CF9834700FF029D /* RSMultiLineRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 846416431C8938470064C661 /* RSMultiLineRenderer.m */; }; + 84B7177E1CF9834A00FF029D /* RSMultiLineRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 846416421C8938470064C661 /* RSMultiLineRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 84BA010F1C8D20C60029943B /* RSTextRendererProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 84BA010D1C8D20C60029943B /* RSTextRendererProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 84E697E41C8E6C01009C585A /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84E697E31C8E6C01009C585A /* RSCore.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 8439DA071C8937C800E5E4B4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8439D9F21C8937C800E5E4B4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8439D9FA1C8937C800E5E4B4; + remoteInfo = RSTextDrawing; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 84193AB11CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSMultiLineRendererMeasurements.h; path = RSTextDrawing/RSMultiLineRendererMeasurements.h; sourceTree = ""; }; + 84193AB21CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSMultiLineRendererMeasurements.m; path = RSTextDrawing/RSMultiLineRendererMeasurements.m; sourceTree = ""; }; + 8439D9FB1C8937C800E5E4B4 /* RSTextDrawing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RSTextDrawing.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8439D9FE1C8937C800E5E4B4 /* RSTextDrawing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RSTextDrawing.h; path = RSTextDrawing/RSTextDrawing.h; sourceTree = ""; }; + 8439DA001C8937C800E5E4B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = RSTextDrawing/Info.plist; sourceTree = ""; }; + 8439DA051C8937C800E5E4B4 /* RSTextDrawingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RSTextDrawingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 8439DA0A1C8937C800E5E4B4 /* RSTextDrawingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RSTextDrawingTests.m; sourceTree = ""; }; + 8439DA0C1C8937C800E5E4B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8464163E1C8938210064C661 /* RSSingleLineRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSSingleLineRenderer.h; path = RSTextDrawing/RSSingleLineRenderer.h; sourceTree = ""; }; + 8464163F1C8938210064C661 /* RSSingleLineRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSSingleLineRenderer.m; path = RSTextDrawing/RSSingleLineRenderer.m; sourceTree = ""; }; + 846416421C8938470064C661 /* RSMultiLineRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSMultiLineRenderer.h; path = RSTextDrawing/RSMultiLineRenderer.h; sourceTree = ""; }; + 846416431C8938470064C661 /* RSMultiLineRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSMultiLineRenderer.m; path = RSTextDrawing/RSMultiLineRenderer.m; sourceTree = ""; }; + 84B717741CF9629000FF029D /* RSSingleLineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSSingleLineView.h; path = RSTextDrawing/RSSingleLineView.h; sourceTree = ""; }; + 84B717751CF9629000FF029D /* RSSingleLineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSSingleLineView.m; path = RSTextDrawing/RSSingleLineView.m; sourceTree = ""; }; + 84B717791CF9665100FF029D /* RSMultiLineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSMultiLineView.h; path = RSTextDrawing/RSMultiLineView.h; sourceTree = ""; }; + 84B7177A1CF9665100FF029D /* RSMultiLineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSMultiLineView.m; path = RSTextDrawing/RSMultiLineView.m; sourceTree = ""; }; + 84BA010D1C8D20C60029943B /* RSTextRendererProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSTextRendererProtocol.h; path = RSTextDrawing/RSTextRendererProtocol.h; sourceTree = ""; }; + 84E697E31C8E6C01009C585A /* RSCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSCore.framework; path = ../RSCore/build/Debug/RSCore.framework; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8439D9F71C8937C800E5E4B4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 84E697E41C8E6C01009C585A /* RSCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8439DA021C8937C800E5E4B4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8439DA061C8937C800E5E4B4 /* RSTextDrawing.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8439D9F11C8937C800E5E4B4 = { + isa = PBXGroup; + children = ( + 8439D9FE1C8937C800E5E4B4 /* RSTextDrawing.h */, + 84B717741CF9629000FF029D /* RSSingleLineView.h */, + 84B717751CF9629000FF029D /* RSSingleLineView.m */, + 84B717791CF9665100FF029D /* RSMultiLineView.h */, + 84B7177A1CF9665100FF029D /* RSMultiLineView.m */, + 84BA010D1C8D20C60029943B /* RSTextRendererProtocol.h */, + 8464163E1C8938210064C661 /* RSSingleLineRenderer.h */, + 8464163F1C8938210064C661 /* RSSingleLineRenderer.m */, + 846416421C8938470064C661 /* RSMultiLineRenderer.h */, + 846416431C8938470064C661 /* RSMultiLineRenderer.m */, + 84193AB11CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h */, + 84193AB21CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m */, + 8439DA001C8937C800E5E4B4 /* Info.plist */, + 8439DA091C8937C800E5E4B4 /* RSTextDrawingTests */, + 8439D9FC1C8937C800E5E4B4 /* Products */, + 84E697E31C8E6C01009C585A /* RSCore.framework */, + ); + sourceTree = ""; + }; + 8439D9FC1C8937C800E5E4B4 /* Products */ = { + isa = PBXGroup; + children = ( + 8439D9FB1C8937C800E5E4B4 /* RSTextDrawing.framework */, + 8439DA051C8937C800E5E4B4 /* RSTextDrawingTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 8439DA091C8937C800E5E4B4 /* RSTextDrawingTests */ = { + isa = PBXGroup; + children = ( + 8439DA0A1C8937C800E5E4B4 /* RSTextDrawingTests.m */, + 8439DA0C1C8937C800E5E4B4 /* Info.plist */, + ); + path = RSTextDrawingTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 8439D9F81C8937C800E5E4B4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 8439D9FF1C8937C800E5E4B4 /* RSTextDrawing.h in Headers */, + 84B717761CF9629000FF029D /* RSSingleLineView.h in Headers */, + 84B7177E1CF9834A00FF029D /* RSMultiLineRenderer.h in Headers */, + 84BA010F1C8D20C60029943B /* RSTextRendererProtocol.h in Headers */, + 84193AB31CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.h in Headers */, + 84B7177B1CF9665100FF029D /* RSMultiLineView.h in Headers */, + 846416401C8938210064C661 /* RSSingleLineRenderer.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 8439D9FA1C8937C800E5E4B4 /* RSTextDrawing */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8439DA0F1C8937C800E5E4B4 /* Build configuration list for PBXNativeTarget "RSTextDrawing" */; + buildPhases = ( + 8439D9F61C8937C800E5E4B4 /* Sources */, + 8439D9F71C8937C800E5E4B4 /* Frameworks */, + 8439D9F81C8937C800E5E4B4 /* Headers */, + 8439D9F91C8937C800E5E4B4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RSTextDrawing; + productName = RSTextDrawing; + productReference = 8439D9FB1C8937C800E5E4B4 /* RSTextDrawing.framework */; + productType = "com.apple.product-type.framework"; + }; + 8439DA041C8937C800E5E4B4 /* RSTextDrawingTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8439DA121C8937C800E5E4B4 /* Build configuration list for PBXNativeTarget "RSTextDrawingTests" */; + buildPhases = ( + 8439DA011C8937C800E5E4B4 /* Sources */, + 8439DA021C8937C800E5E4B4 /* Frameworks */, + 8439DA031C8937C800E5E4B4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 8439DA081C8937C800E5E4B4 /* PBXTargetDependency */, + ); + name = RSTextDrawingTests; + productName = RSTextDrawingTests; + productReference = 8439DA051C8937C800E5E4B4 /* RSTextDrawingTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8439D9F21C8937C800E5E4B4 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0730; + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = "Ranchero Software"; + TargetAttributes = { + 8439D9FA1C8937C800E5E4B4 = { + CreatedOnToolsVersion = 7.2.1; + LastSwiftMigration = 0800; + }; + 8439DA041C8937C800E5E4B4 = { + CreatedOnToolsVersion = 7.2.1; + LastSwiftMigration = 0800; + }; + }; + }; + buildConfigurationList = 8439D9F51C8937C800E5E4B4 /* Build configuration list for PBXProject "RSTextDrawing" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 8439D9F11C8937C800E5E4B4; + productRefGroup = 8439D9FC1C8937C800E5E4B4 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8439D9FA1C8937C800E5E4B4 /* RSTextDrawing */, + 8439DA041C8937C800E5E4B4 /* RSTextDrawingTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8439D9F91C8937C800E5E4B4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8439DA031C8937C800E5E4B4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8439D9F61C8937C800E5E4B4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 84B717771CF9629000FF029D /* RSSingleLineView.m in Sources */, + 84B7177C1CF9665100FF029D /* RSMultiLineView.m in Sources */, + 846416411C8938210064C661 /* RSSingleLineRenderer.m in Sources */, + 84B7177D1CF9834700FF029D /* RSMultiLineRenderer.m in Sources */, + 84193AB41CF4EEEB00EAC812 /* RSMultiLineRendererMeasurements.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8439DA011C8937C800E5E4B4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8439DA0B1C8937C800E5E4B4 /* RSTextDrawingTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 8439DA081C8937C800E5E4B4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8439D9FA1C8937C800E5E4B4 /* RSTextDrawing */; + targetProxy = 8439DA071C8937C800E5E4B4 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 8439DA0D1C8937C800E5E4B4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 8439DA0E1C8937C800E5E4B4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 8439DA101C8937C800E5E4B4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = RSTextDrawing/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSTextDrawing; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 8439DA111C8937C800E5E4B4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = RSTextDrawing/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSTextDrawing; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + 8439DA131C8937C800E5E4B4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = RSTextDrawingTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSTextDrawingTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 8439DA141C8937C800E5E4B4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = RSTextDrawingTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSTextDrawingTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8439D9F51C8937C800E5E4B4 /* Build configuration list for PBXProject "RSTextDrawing" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8439DA0D1C8937C800E5E4B4 /* Debug */, + 8439DA0E1C8937C800E5E4B4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8439DA0F1C8937C800E5E4B4 /* Build configuration list for PBXNativeTarget "RSTextDrawing" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8439DA101C8937C800E5E4B4 /* Debug */, + 8439DA111C8937C800E5E4B4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8439DA121C8937C800E5E4B4 /* Build configuration list for PBXNativeTarget "RSTextDrawingTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8439DA131C8937C800E5E4B4 /* Debug */, + 8439DA141C8937C800E5E4B4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8439D9F21C8937C800E5E4B4 /* Project object */; +} diff --git a/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..a11ac5bda --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/Info.plist b/Frameworks/RSTextDrawing/RSTextDrawing/Info.plist new file mode 100644 index 000000000..df087ac1c --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2016 Ranchero Software. All rights reserved. + NSPrincipalClass + + + diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRenderer.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRenderer.h new file mode 100644 index 000000000..8dd51ded2 --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRenderer.h @@ -0,0 +1,28 @@ +// +// RSMultiLineRenderer.h +// RSTextDrawing +// +// Created by Brent Simmons on 3/3/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +@import Cocoa; +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RSMultiLineRendererMeasurements; + + +@interface RSMultiLineRenderer : NSObject + ++ (instancetype)rendererWithAttributedTitle:(NSAttributedString *)title; + +- (RSMultiLineRendererMeasurements *)measurementsForWidth:(CGFloat)width; + +@property (nonatomic, strong) NSColor *backgroundColor; // Default is white. + +@end + +NS_ASSUME_NONNULL_END + diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRenderer.m b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRenderer.m new file mode 100644 index 000000000..7f486bb46 --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRenderer.m @@ -0,0 +1,315 @@ +// +// RSMultiLineRenderer.m +// RSTextDrawing +// +// Created by Brent Simmons on 3/3/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +#import "RSMultiLineRenderer.h" +#import "RSMultiLineRendererMeasurements.h" + + +@interface RSMultiLineRenderer () + +@property (nonatomic, readonly) NSAttributedString *title; +@property (nonatomic) NSRect rect; +@property (nonatomic, readonly) CTFramesetterRef framesetter; +@property (nonatomic) CTFrameRef frameref; +@property (nonatomic, readonly) NSMutableDictionary *measurementCache; + +@end + + +static NSMutableDictionary *rendererCache = nil; +static NSUInteger kMaximumNumberOfLines = 2; + + +@implementation RSMultiLineRenderer + + +#pragma mark - Class Methods + ++ (instancetype)rendererWithAttributedTitle:(NSAttributedString *)title { + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + rendererCache = [NSMutableDictionary new]; + }); + + RSMultiLineRenderer *cachedRenderer = rendererCache[title]; + if (cachedRenderer != nil) { + return cachedRenderer; + } + + RSMultiLineRenderer *renderer = [[RSMultiLineRenderer alloc] initWithAttributedTitle:title]; + rendererCache[title] = renderer; + return renderer; +} + + ++ (void)emptyCache { + + for (RSMultiLineRenderer *oneRenderer in rendererCache.allValues) { + [oneRenderer emptyCache]; + [oneRenderer releaseFrameref]; + } + + rendererCache = [NSMutableDictionary new]; +} + + +#pragma mark Init + +- (instancetype)initWithAttributedTitle:(NSAttributedString *)title { + + self = [super init]; + if (self == nil) { + return nil; + } + + _title = title; + _measurementCache = [NSMutableDictionary new]; + _framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)title); + _backgroundColor = NSColor.whiteColor; + + return self; +} + + +#pragma mark Dealloc + +- (void)dealloc { + + if (_framesetter) { + CFRelease(_framesetter); + _framesetter = nil; + } + + if (_frameref) { + CFRelease(_frameref); + _frameref = nil; + } +} + + +#pragma mark Accessors + +- (void)setRect:(NSRect)r { + + r.origin.y = floor(r.origin.y); + r.origin.x = floor(r.origin.x); + r.size.height = floor(r.size.height); + r.size.width = floor(r.size.width); + + if (!NSEqualRects(r, _rect)) { + _rect = r; + [self releaseFrameref]; + } +} + +- (void)releaseFrameref { + + if (_frameref) { + CFRelease(_frameref); + _frameref = nil; + } +} + +#pragma mark - Measurements + +- (NSInteger)integerForWidth:(CGFloat)width { + + return (NSInteger)(floor(width)); +} + + +- (NSNumber *)keyForWidth:(CGFloat)width { + + return @([self integerForWidth:width]); +} + +- (RSMultiLineRendererMeasurements *)measurementsByRegardingNarrowerNeighborsInCache:(CGFloat)width { + + /*If width==30, and cached measurements for width==15 indicate that it's a single line of text, then return those measurements.*/ + + NSInteger w = [self integerForWidth:width]; + static const NSInteger kSingleLineHeightWithSlop = 20; + + for (NSNumber *oneKey in self.measurementCache.allKeys) { + + NSInteger oneWidth = oneKey.integerValue; + + if (oneWidth < w) { + RSMultiLineRendererMeasurements *oneMeasurements = self.measurementCache[oneKey]; + if (oneMeasurements.height <= kSingleLineHeightWithSlop) { + return oneMeasurements; + } + } + } + + return nil; +} + + +- (RSMultiLineRendererMeasurements *)measurementsByRegardingNeighborsInCache:(CGFloat)width { + + /*If width==30, and the cached measurements for width==15 and width==42 are equal, then we can use one of those for width==30.*/ + + if (self.measurementCache.count < 2) { + return nil; + } + + NSInteger w = [self integerForWidth:width]; + NSInteger lessThanNeighbor = NSNotFound; + NSInteger greaterThanNeighbor = NSNotFound; + + for (NSNumber *oneKey in self.measurementCache.allKeys) { + + NSInteger oneWidth = oneKey.integerValue; + if (oneWidth < w) { + if (lessThanNeighbor == NSNotFound) { + lessThanNeighbor = oneWidth; + } + else if (lessThanNeighbor < oneWidth) { + lessThanNeighbor = oneWidth; + } + } + if (oneWidth > w) { + if (greaterThanNeighbor == NSNotFound) { + greaterThanNeighbor = oneWidth; + } + else if (greaterThanNeighbor > oneWidth) { + greaterThanNeighbor = oneWidth; + } + } + } + + if (lessThanNeighbor == NSNotFound || greaterThanNeighbor == NSNotFound) { + return nil; + } + + RSMultiLineRendererMeasurements *lessThanMeasurements = self.measurementCache[@(lessThanNeighbor)]; + RSMultiLineRendererMeasurements *greaterThanMeasurements = self.measurementCache[@(greaterThanNeighbor)]; + + if ([lessThanMeasurements isEqual:greaterThanMeasurements]) { + return lessThanMeasurements; + } + + return nil; +} + +- (RSMultiLineRendererMeasurements *)measurementsForWidth:(CGFloat)width { + + NSNumber *key = [self keyForWidth:width]; + RSMultiLineRendererMeasurements *cachedMeasurements = self.measurementCache[key]; + if (cachedMeasurements) { + return cachedMeasurements; + } + + RSMultiLineRendererMeasurements *measurements = [self measurementsByRegardingNarrowerNeighborsInCache:width]; + if (measurements) { + return measurements; + } + measurements = [self measurementsByRegardingNeighborsInCache:width]; + if (measurements) { + return measurements; + } + + measurements = [self calculatedMeasurementsForWidth:width]; + self.measurementCache[key] = measurements; + return measurements; +} + + +#pragma mark - Cache + +- (void)emptyCache { + + [self.measurementCache removeAllObjects]; +} + + +#pragma mark Rendering + +static const CGFloat kMaxHeight = 10000.0; + +- (RSMultiLineRendererMeasurements *)calculatedMeasurementsForWidth:(CGFloat)width { + + NSInteger height = 0; + NSInteger heightOfFirstLine = 0; + + width = floor(width); + + @autoreleasepool { + + CGRect r = CGRectMake(0.0f, 0.0f, width, kMaxHeight); + CGPathRef path = CGPathCreateWithRect(r, NULL); + + CTFrameRef frameref = CTFramesetterCreateFrame(self.framesetter, CFRangeMake(0, (CFIndex)(self.title.length)), path, NULL); + + NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frameref); + + if (lines.count > 0) { + + NSUInteger indexOfLastLine = MIN(kMaximumNumberOfLines - 1, lines.count - 1); + + CGPoint origins[indexOfLastLine + 1]; + CTFrameGetLineOrigins(frameref, CFRangeMake(0, (CFIndex)indexOfLastLine + 1), origins); + + CTLineRef lastLine = (__bridge CTLineRef)lines[indexOfLastLine]; + CGPoint lastOrigin = origins[indexOfLastLine]; + CGFloat descent; + CTLineGetTypographicBounds(lastLine, NULL, &descent, NULL); + height = r.size.height - (ceil(lastOrigin.y) - ceil(descent)); + height = (NSInteger)ceil(height); + + CTLineRef firstLine = (__bridge CTLineRef)lines[0]; + CGRect firstLineRect = CTLineGetBoundsWithOptions(firstLine, 0); + heightOfFirstLine = (NSInteger)ceil(NSHeight(firstLineRect)); + + } + + CFRelease(path); + CFRelease(frameref); + } + + RSMultiLineRendererMeasurements *measurements = [RSMultiLineRendererMeasurements measurementsWithHeight:height heightOfFirstLine:heightOfFirstLine]; + return measurements; +} + + +- (void)renderTextInRect:(CGRect)r { + + self.rect = r; + + CGContextRef context = [NSGraphicsContext currentContext].CGContext; + CGContextSaveGState(context); + + CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor); + CGContextFillRect(context, r); + + CGContextSetShouldSmoothFonts(context, true); + + CTFrameDraw(self.frameref, context); + + CGContextRestoreGState(context); +} + + +- (CTFrameRef)frameref { + + if (_frameref) { + return _frameref; + } + + CGPathRef path = CGPathCreateWithRect(self.rect, NULL); + + _frameref = CTFramesetterCreateFrame(self.framesetter, CFRangeMake(0, (CFIndex)(self.title.length)), path, NULL); + + CFRelease(path); + + return _frameref; +} + +@end diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRendererMeasurements.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRendererMeasurements.h new file mode 100644 index 000000000..e3a06026d --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRendererMeasurements.h @@ -0,0 +1,18 @@ +// +// RSMultiLineRendererMeasurements.h +// RSTextDrawing +// +// Created by Brent Simmons on 3/4/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +@import Foundation; + +@interface RSMultiLineRendererMeasurements : NSObject + ++ (instancetype)measurementsWithHeight:(NSInteger)height heightOfFirstLine:(NSInteger)heightOfFirstLine; + +@property (nonatomic, readonly) NSInteger height; +@property (nonatomic, readonly) NSInteger heightOfFirstLine; + +@end diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRendererMeasurements.m b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRendererMeasurements.m new file mode 100644 index 000000000..1a3a247cc --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineRendererMeasurements.m @@ -0,0 +1,59 @@ +// +// RSMultiLineRendererMeasurements.m +// RSTextDrawing +// +// Created by Brent Simmons on 3/4/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +#import "RSMultiLineRendererMeasurements.h" + +@implementation RSMultiLineRendererMeasurements + +#pragma mark - Class Methods + ++ (instancetype)measurementsWithHeight:(NSInteger)height heightOfFirstLine:(NSInteger)heightOfFirstLine { + + return [[self alloc] initWithHeight:height heightOfFirstLine:heightOfFirstLine]; +} + + +#pragma mark - Init + +- (instancetype)initWithHeight:(NSInteger)height heightOfFirstLine:(NSInteger)heightOfFirstLine { + + self = [super init]; + if (!self) { + return nil; + } + + _height = height; + _heightOfFirstLine = heightOfFirstLine; + + return self; +} + +- (BOOL)isEqualToMultiLineRendererMeasurements:(RSMultiLineRendererMeasurements *)otherMeasurements { + + return self.height == otherMeasurements.height && self.heightOfFirstLine == otherMeasurements.heightOfFirstLine; +} + +- (BOOL)isEqual:(id)object { + + if (self == object) { + return YES; + } + if (![object isKindOfClass:[self class]]) { + return NO; + } + + return [self isEqualToMultiLineRendererMeasurements:(RSMultiLineRendererMeasurements *)object]; +} + +- (NSUInteger)hash { + + return (NSUInteger)(self.height + (self.heightOfFirstLine * 100000)); +} + + +@end diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineView.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineView.h new file mode 100644 index 000000000..fbb75d11f --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineView.h @@ -0,0 +1,26 @@ +// +// RSMultiLineView.h +// RSTextDrawing +// +// Created by Brent Simmons on 5/27/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +@import Cocoa; + +NS_ASSUME_NONNULL_BEGIN + +@class RSMultiLineRendererMeasurements; + +@interface RSMultiLineView : NSView + +@property (nonatomic) NSAttributedString *attributedStringValue; + +@property (nonatomic, readonly) RSMultiLineRendererMeasurements *measurements; + +@property (nonatomic) BOOL selected; +@property (nonatomic) BOOL emphasized; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineView.m b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineView.m new file mode 100644 index 000000000..6b42c108c --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSMultiLineView.m @@ -0,0 +1,160 @@ +// +// RSMultiLineView.m +// RSTextDrawing +// +// Created by Brent Simmons on 5/27/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +#import "RSMultiLineView.h" +#import "RSMultiLineRenderer.h" +#import "RSMultiLineRendererMeasurements.h" + + +@interface RSMultiLineView () + +@property (nonatomic) RSMultiLineRenderer *renderer; +@property (nonatomic) NSSize intrinsicSize; +@property (nonatomic) BOOL intrinsicSizeIsValid; +@property (nonatomic) RSMultiLineRenderer *selectedRenderer; +@property (nonatomic) NSAttributedString *selectedAttributedStringValue; + +@end + +static NSAttributedString *emptyAttributedString = nil; + +@implementation RSMultiLineView + + +- (instancetype)initWithFrame:(NSRect)frame { + + self = [super initWithFrame:frame]; + if (!self) { + return nil; + } + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + emptyAttributedString = [[NSAttributedString alloc] initWithString:@""]; + }); + + _renderer = [RSMultiLineRenderer rendererWithAttributedTitle:emptyAttributedString]; + + return self; +} + + +- (void)setRenderer:(RSMultiLineRenderer *)renderer { + + if (_renderer == renderer) { + return; + } + _renderer = renderer; + + [self invalidateIntrinsicContentSize]; + self.needsDisplay = YES; +} + +- (RSMultiLineRenderer *)selectedRenderer { + + if (_selectedRenderer) { + return _selectedRenderer; + } + + _selectedRenderer = [RSMultiLineRenderer rendererWithAttributedTitle:self.selectedAttributedStringValue]; + _selectedRenderer.backgroundColor = NSColor.alternateSelectedControlColor; + return _selectedRenderer; +} + + +- (void)setSelected:(BOOL)selected { + + _selected = selected; + self.needsDisplay = YES; +} + + +- (void)setEmphasized:(BOOL)emphasized { + + _emphasized = emphasized; + self.needsDisplay = YES; +} + + +- (NSAttributedString *)selectedAttributedStringValue { + + if (!self.attributedStringValue) { + return emptyAttributedString; + } + + NSMutableAttributedString *s = [self.attributedStringValue mutableCopy]; + [s addAttribute:NSForegroundColorAttributeName value:NSColor.alternateSelectedControlTextColor range:NSMakeRange(0, s.string.length)]; + _selectedAttributedStringValue = s; + + return _selectedAttributedStringValue; +} + + +- (void)setAttributedStringValue:(NSAttributedString *)attributedStringValue { + + if (_attributedStringValue == attributedStringValue) { + return; + } + _attributedStringValue = attributedStringValue; + + self.selectedAttributedStringValue = nil; + self.selectedRenderer = nil; + + self.renderer = [RSMultiLineRenderer rendererWithAttributedTitle:attributedStringValue]; +} + + +- (RSMultiLineRendererMeasurements *)measurements { + + return [self.renderer measurementsForWidth:NSWidth(self.frame)]; +} + + +- (void)invalidateIntrinsicContentSize { + + self.intrinsicSizeIsValid = NO; +} + + +- (NSSize)intrinsicContentSize { + + if (!self.intrinsicSizeIsValid) { + self.intrinsicSize = NSMakeSize(NSWidth(self.frame), self.measurements.height); + self.intrinsicSizeIsValid = YES; + } + return self.intrinsicSize; +} + + +- (void)setFrameSize:(NSSize)newSize { + + [self invalidateIntrinsicContentSize]; + [super setFrameSize:newSize]; +} + + +- (void)drawRect:(NSRect)r { + + if (self.selected) { + + if (self.emphasized) { + [self.selectedRenderer renderTextInRect:self.bounds]; + } + else { + NSColor *savedBackgroundColor = self.renderer.backgroundColor; + self.renderer.backgroundColor = NSColor.secondarySelectedControlColor; + [self.renderer renderTextInRect:self.bounds]; + self.renderer.backgroundColor = savedBackgroundColor; + } + } + + else { + [self.renderer renderTextInRect:self.bounds]; + } +} + +@end diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.h new file mode 100644 index 000000000..5d0300f8c --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.h @@ -0,0 +1,24 @@ +// +// SingleLineRenderer.h +// RSTextDrawing +// +// Created by Brent Simmons on 3/3/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +@import Cocoa; +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RSSingleLineRenderer : NSObject + ++ (instancetype)rendererWithAttributedTitle:(NSAttributedString *)title; + +@property (nonatomic, readonly) NSSize size; + +@property (nonatomic, strong) NSColor *backgroundColor; // Default is white. + +@end + +NS_ASSUME_NONNULL_END diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.m b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.m new file mode 100644 index 000000000..54ba827d3 --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineRenderer.m @@ -0,0 +1,200 @@ +// +// RSSingleLineRenderer.m +// RSTextDrawing +// +// Created by Brent Simmons on 3/3/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +#import "RSSingleLineRenderer.h" + +static NSMutableDictionary *rendererCache = nil; + +@interface RSSingleLineRenderer () + +@property (nonatomic, readonly) NSAttributedString *title; +@property (nonatomic) NSRect rect; +@property (nonatomic, readonly) CTFramesetterRef framesetter; +@property (nonatomic) CTFrameRef frameref; + +@end + + +@implementation RSSingleLineRenderer + +@synthesize size = _size; + +#pragma mark - Class Methods + ++ (void)initialize { + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + + rendererCache = [NSMutableDictionary new]; + }); +} + + ++ (instancetype)rendererWithAttributedTitle:(NSAttributedString *)title { + + RSSingleLineRenderer *cachedRenderer = rendererCache[title]; + if (cachedRenderer != nil) { + return cachedRenderer; + } + + RSSingleLineRenderer *renderer = [[RSSingleLineRenderer alloc] initWithAttributedTitle:title]; + rendererCache[title] = renderer; + return renderer; +} + + ++ (void)emptyCache { + + rendererCache = [NSMutableDictionary new]; +} + + +#pragma mark - Init + +- (instancetype)initWithAttributedTitle:(NSAttributedString *)title { + + self = [super init]; + if (self == nil) { + return nil; + } + + _title = title; + _framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)title); + _backgroundColor = NSColor.whiteColor; + + return self; +} + + +#pragma mark - Dealloc + +- (void)dealloc { + + if (_framesetter) { + CFRelease(_framesetter); + _framesetter = nil; + } + + if (_frameref) { + CFRelease(_frameref); + _frameref = nil; + } +} + + +#pragma mark - Accessors + +- (void)setRect:(NSRect)r { + + r.origin.y = floor(r.origin.y); + r.origin.x = floor(r.origin.x); + r.size.height = floor(r.size.height); + if (r.size.height > self.size.height) { + r.size.height = self.size.height; + } + r.size.width = floor(r.size.width); + if (r.size.width > self.size.width) { + r.size.width = self.size.width; + } + + if (!NSEqualRects(r, _rect)) { + _rect = r; + [self releaseFrameref]; + } +} + + +- (void)releaseFrameref { + + if (_frameref) { + CFRelease(_frameref); + _frameref = nil; + } +} + + +- (NSSize)size { + + if (NSEqualSizes(_size, NSZeroSize)) { + _size = [self calculatedSize]; + } + return _size; +} + +#pragma mark - Measurements + +static const CGFloat kMaxWidth = 10000.0; +static const CGFloat kMaxHeight = 10000.0; + +- (NSSize)calculatedSize { + + NSSize size = NSZeroSize; + + @autoreleasepool { + + CGRect r = CGRectMake(0.0f, 0.0f, kMaxWidth, kMaxHeight); + CGPathRef path = CGPathCreateWithRect(r, NULL); + + CTFrameRef frameref = CTFramesetterCreateFrame(self.framesetter, CFRangeMake(0, (CFIndex)(self.title.length)), path, NULL); + + NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frameref); + + if (lines.count > 0) { + + CTLineRef firstLine = (__bridge CTLineRef)lines[0]; + CGRect firstLineRect = CTLineGetBoundsWithOptions(firstLine, 0); + CGFloat height = ceil(NSHeight(firstLineRect)); + CGFloat width = ceil(NSWidth(firstLineRect)); + size = NSMakeSize(width, height); + } + + CFRelease(path); + CFRelease(frameref); + } + + return size; +} + + +#pragma mark - Drawing + +- (void)renderTextInRect:(CGRect)r { + + self.rect = r; + + CGContextRef context = [NSGraphicsContext currentContext].CGContext; + CGContextSaveGState(context); + + CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor); + CGContextFillRect(context, r); + + CGContextSetShouldSmoothFonts(context, true); + + CTFrameDraw(self.frameref, context); + + CGContextRestoreGState(context); +} + + +- (CTFrameRef)frameref { + + if (_frameref) { + return _frameref; + } + + CGPathRef path = CGPathCreateWithRect(self.rect, NULL); + + _frameref = CTFramesetterCreateFrame(self.framesetter, CFRangeMake(0, (CFIndex)(self.title.length)), path, NULL); + + CFRelease(path); + + return _frameref; +} + +@end diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.h new file mode 100644 index 000000000..da73671c6 --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.h @@ -0,0 +1,22 @@ +// +// RSSingleLineView.h +// RSTextDrawing +// +// Created by Brent Simmons on 5/27/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +@import Cocoa; + +NS_ASSUME_NONNULL_BEGIN + +@interface RSSingleLineView : NSView + +@property (nonatomic, strong) NSAttributedString *attributedStringValue; + +@property (nonatomic) BOOL selected; +@property (nonatomic) BOOL emphasized; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.m b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.m new file mode 100644 index 000000000..f0b6130a0 --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSSingleLineView.m @@ -0,0 +1,142 @@ +// +// RSSingleLineView.m +// RSTextDrawing +// +// Created by Brent Simmons on 5/27/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +@import RSCore; +#import "RSSingleLineView.h" +#import "RSSingleLineRenderer.h" + +@interface RSSingleLineView () + +@property (nonatomic) RSSingleLineRenderer *renderer; +@property (nonatomic) NSSize intrinsicSize; +@property (nonatomic) BOOL intrinsicSizeIsValid; +@property (nonatomic) RSSingleLineRenderer *selectedRenderer; +@property (nonatomic) NSAttributedString *selectedAttributedStringValue; + +@end + +static NSAttributedString *emptyAttributedString = nil; + +@implementation RSSingleLineView + +- (instancetype)initWithFrame:(NSRect)r { + + self = [super initWithFrame:r]; + if (!self) { + return nil; + } + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + emptyAttributedString = [[NSAttributedString alloc] initWithString:@""]; + }); + + _renderer = [RSSingleLineRenderer rendererWithAttributedTitle:emptyAttributedString]; + + return self; +} + + +- (void)setAttributedStringValue:(NSAttributedString *)attributedStringValue { + + _attributedStringValue = attributedStringValue; + self.selectedAttributedStringValue = nil; + self.selectedRenderer = nil; + + self.renderer = [RSSingleLineRenderer rendererWithAttributedTitle:attributedStringValue]; +} + + +- (void)setRenderer:(RSSingleLineRenderer *)renderer { + + _renderer = renderer; + [self invalidateIntrinsicContentSize]; + self.needsDisplay = YES; +} + + +- (RSSingleLineRenderer *)selectedRenderer { + + if (_selectedRenderer) { + return _selectedRenderer; + } + + _selectedRenderer = [RSSingleLineRenderer rendererWithAttributedTitle:self.selectedAttributedStringValue]; + _selectedRenderer.backgroundColor = NSColor.alternateSelectedControlColor; + return _selectedRenderer; +} + + +- (void)setSelected:(BOOL)selected { + + _selected = selected; + self.needsDisplay = YES; +} + + +- (void)setEmphasized:(BOOL)emphasized { + + _emphasized = emphasized; + self.needsDisplay = YES; +} + + +- (NSAttributedString *)selectedAttributedStringValue { + + if (!self.attributedStringValue) { + return emptyAttributedString; + } + + NSMutableAttributedString *s = [self.attributedStringValue mutableCopy]; + [s addAttribute:NSForegroundColorAttributeName value:NSColor.alternateSelectedControlTextColor range:NSMakeRange(0, s.string.length)]; + _selectedAttributedStringValue = s; + + return _selectedAttributedStringValue; +} + + +- (void)invalidateIntrinsicContentSize { + + self.intrinsicSizeIsValid = NO; +} + + +- (NSSize)intrinsicContentSize { + + if (!self.intrinsicSizeIsValid) { + self.intrinsicSize = ((RSSingleLineRenderer *)(self.renderer)).size; + self.intrinsicSizeIsValid = YES; + } + + return self.intrinsicSize; +} + + +- (void)drawRect:(NSRect)r { + + if (self.selected) { + + if (self.emphasized) { + [self.selectedRenderer renderTextInRect:self.bounds]; + } + else { + NSColor *savedBackgroundColor = self.renderer.backgroundColor; + self.renderer.backgroundColor = NSColor.secondarySelectedControlColor; + [self.renderer renderTextInRect:self.bounds]; + self.renderer.backgroundColor = savedBackgroundColor; + } + } + + else { + [self.renderer renderTextInRect:self.bounds]; + } +} + + +@end + diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSTextDrawing.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSTextDrawing.h new file mode 100644 index 000000000..e3ee95a6a --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSTextDrawing.h @@ -0,0 +1,18 @@ +// +// RSTextDrawing.h +// RSTextDrawing +// +// Created by Brent Simmons on 3/3/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +@import Cocoa; + +#import +#import + +#import +#import +#import +#import + diff --git a/Frameworks/RSTextDrawing/RSTextDrawing/RSTextRendererProtocol.h b/Frameworks/RSTextDrawing/RSTextDrawing/RSTextRendererProtocol.h new file mode 100644 index 000000000..e24359de5 --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawing/RSTextRendererProtocol.h @@ -0,0 +1,17 @@ +// +// RSTextRendererProtocol.h +// RSTextDrawing +// +// Created by Brent Simmons on 3/6/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +@import Cocoa; + +@protocol RSTextRenderer + +- (void)renderTextInRect:(NSRect)r; + ++ (void)emptyCache; + +@end \ No newline at end of file diff --git a/Frameworks/RSTextDrawing/RSTextDrawingTests/Info.plist b/Frameworks/RSTextDrawing/RSTextDrawingTests/Info.plist new file mode 100644 index 000000000..ba72822e8 --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawingTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Frameworks/RSTextDrawing/RSTextDrawingTests/RSTextDrawingTests.m b/Frameworks/RSTextDrawing/RSTextDrawingTests/RSTextDrawingTests.m new file mode 100644 index 000000000..367f25482 --- /dev/null +++ b/Frameworks/RSTextDrawing/RSTextDrawingTests/RSTextDrawingTests.m @@ -0,0 +1,39 @@ +// +// RSTextDrawingTests.m +// RSTextDrawingTests +// +// Created by Brent Simmons on 3/3/16. +// Copyright © 2016 Ranchero Software. All rights reserved. +// + +#import + +@interface RSTextDrawingTests : XCTestCase + +@end + +@implementation RSTextDrawingTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end