mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-10 08:20:16 +00:00
Add latest ComponentKit text kit renderer into project
This commit is contained in:
parent
9032962353
commit
bae3e3b63d
@ -145,6 +145,24 @@
|
||||
251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; };
|
||||
251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; };
|
||||
2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; };
|
||||
2577547A1BED252700737CA5 /* CKTextKitAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754671BED252700737CA5 /* CKTextKitAttributes.h */; };
|
||||
2577547B1BED252700737CA5 /* CKTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754681BED252700737CA5 /* CKTextKitAttributes.mm */; };
|
||||
2577547C1BED252700737CA5 /* CKTextKitContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754691BED252700737CA5 /* CKTextKitContext.h */; };
|
||||
2577547D1BED252700737CA5 /* CKTextKitContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577546A1BED252700737CA5 /* CKTextKitContext.mm */; };
|
||||
2577547E1BED252700737CA5 /* CKTextKitEntityAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577546B1BED252700737CA5 /* CKTextKitEntityAttribute.h */; };
|
||||
2577547F1BED252700737CA5 /* CKTextKitEntityAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 2577546C1BED252700737CA5 /* CKTextKitEntityAttribute.m */; };
|
||||
257754801BED252700737CA5 /* CKTextKitRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577546D1BED252700737CA5 /* CKTextKitRenderer.h */; };
|
||||
257754811BED252700737CA5 /* CKTextKitRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577546E1BED252700737CA5 /* CKTextKitRenderer.mm */; };
|
||||
257754821BED252700737CA5 /* CKTextKitRenderer+Positioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577546F1BED252700737CA5 /* CKTextKitRenderer+Positioning.h */; };
|
||||
257754831BED252700737CA5 /* CKTextKitRenderer+Positioning.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754701BED252700737CA5 /* CKTextKitRenderer+Positioning.mm */; };
|
||||
257754841BED252700737CA5 /* CKTextKitRenderer+TextChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754711BED252700737CA5 /* CKTextKitRenderer+TextChecking.h */; };
|
||||
257754851BED252700737CA5 /* CKTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754721BED252700737CA5 /* CKTextKitRenderer+TextChecking.mm */; };
|
||||
257754881BED252700737CA5 /* CKTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754751BED252700737CA5 /* CKTextKitShadower.h */; };
|
||||
257754891BED252700737CA5 /* CKTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754761BED252700737CA5 /* CKTextKitShadower.mm */; };
|
||||
2577548A1BED252700737CA5 /* CKTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754771BED252700737CA5 /* CKTextKitTailTruncater.h */; };
|
||||
2577548B1BED252700737CA5 /* CKTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754781BED252700737CA5 /* CKTextKitTailTruncater.mm */; };
|
||||
2577548C1BED252700737CA5 /* CKTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754791BED252700737CA5 /* CKTextKitTruncating.h */; };
|
||||
2577548E1BED278B00737CA5 /* CKEqualityHashHelpers.h in Sources */ = {isa = PBXBuildFile; fileRef = 2577548D1BED278B00737CA5 /* CKEqualityHashHelpers.h */; };
|
||||
2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; };
|
||||
2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; };
|
||||
@ -583,6 +601,24 @@
|
||||
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = "<group>"; };
|
||||
251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDataController+Subclasses.h"; sourceTree = "<group>"; };
|
||||
2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = "<group>"; };
|
||||
257754671BED252700737CA5 /* CKTextKitAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKTextKitAttributes.h; path = TextKit/CKTextKitAttributes.h; sourceTree = "<group>"; };
|
||||
257754681BED252700737CA5 /* CKTextKitAttributes.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CKTextKitAttributes.mm; path = TextKit/CKTextKitAttributes.mm; sourceTree = "<group>"; };
|
||||
257754691BED252700737CA5 /* CKTextKitContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKTextKitContext.h; path = TextKit/CKTextKitContext.h; sourceTree = "<group>"; };
|
||||
2577546A1BED252700737CA5 /* CKTextKitContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CKTextKitContext.mm; path = TextKit/CKTextKitContext.mm; sourceTree = "<group>"; };
|
||||
2577546B1BED252700737CA5 /* CKTextKitEntityAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKTextKitEntityAttribute.h; path = TextKit/CKTextKitEntityAttribute.h; sourceTree = "<group>"; };
|
||||
2577546C1BED252700737CA5 /* CKTextKitEntityAttribute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CKTextKitEntityAttribute.m; path = TextKit/CKTextKitEntityAttribute.m; sourceTree = "<group>"; };
|
||||
2577546D1BED252700737CA5 /* CKTextKitRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKTextKitRenderer.h; path = TextKit/CKTextKitRenderer.h; sourceTree = "<group>"; };
|
||||
2577546E1BED252700737CA5 /* CKTextKitRenderer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CKTextKitRenderer.mm; path = TextKit/CKTextKitRenderer.mm; sourceTree = "<group>"; };
|
||||
2577546F1BED252700737CA5 /* CKTextKitRenderer+Positioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "CKTextKitRenderer+Positioning.h"; path = "TextKit/CKTextKitRenderer+Positioning.h"; sourceTree = "<group>"; };
|
||||
257754701BED252700737CA5 /* CKTextKitRenderer+Positioning.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "CKTextKitRenderer+Positioning.mm"; path = "TextKit/CKTextKitRenderer+Positioning.mm"; sourceTree = "<group>"; };
|
||||
257754711BED252700737CA5 /* CKTextKitRenderer+TextChecking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "CKTextKitRenderer+TextChecking.h"; path = "TextKit/CKTextKitRenderer+TextChecking.h"; sourceTree = "<group>"; };
|
||||
257754721BED252700737CA5 /* CKTextKitRenderer+TextChecking.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "CKTextKitRenderer+TextChecking.mm"; path = "TextKit/CKTextKitRenderer+TextChecking.mm"; sourceTree = "<group>"; };
|
||||
257754751BED252700737CA5 /* CKTextKitShadower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKTextKitShadower.h; path = TextKit/CKTextKitShadower.h; sourceTree = "<group>"; };
|
||||
257754761BED252700737CA5 /* CKTextKitShadower.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CKTextKitShadower.mm; path = TextKit/CKTextKitShadower.mm; sourceTree = "<group>"; };
|
||||
257754771BED252700737CA5 /* CKTextKitTailTruncater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKTextKitTailTruncater.h; path = TextKit/CKTextKitTailTruncater.h; sourceTree = "<group>"; };
|
||||
257754781BED252700737CA5 /* CKTextKitTailTruncater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CKTextKitTailTruncater.mm; path = TextKit/CKTextKitTailTruncater.mm; sourceTree = "<group>"; };
|
||||
257754791BED252700737CA5 /* CKTextKitTruncating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKTextKitTruncating.h; path = TextKit/CKTextKitTruncating.h; sourceTree = "<group>"; };
|
||||
2577548D1BED278B00737CA5 /* CKEqualityHashHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CKEqualityHashHelpers.h; path = TextKit/CKEqualityHashHelpers.h; sourceTree = "<group>"; };
|
||||
2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = "<group>"; };
|
||||
292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = "<group>"; };
|
||||
292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerPreload.h; sourceTree = "<group>"; };
|
||||
@ -836,6 +872,7 @@
|
||||
058D09E1195D050800B7D73C /* Details */,
|
||||
058D0A01195D050800B7D73C /* Private */,
|
||||
AC6456051B0A333200CF11B8 /* Layout */,
|
||||
257754661BED245B00737CA5 /* TextKit */,
|
||||
058D09B2195D04C000B7D73C /* Supporting Files */,
|
||||
);
|
||||
path = AsyncDisplayKit;
|
||||
@ -1044,6 +1081,31 @@
|
||||
path = Base;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
257754661BED245B00737CA5 /* TextKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
257754671BED252700737CA5 /* CKTextKitAttributes.h */,
|
||||
257754681BED252700737CA5 /* CKTextKitAttributes.mm */,
|
||||
257754691BED252700737CA5 /* CKTextKitContext.h */,
|
||||
2577546A1BED252700737CA5 /* CKTextKitContext.mm */,
|
||||
2577546B1BED252700737CA5 /* CKTextKitEntityAttribute.h */,
|
||||
2577546C1BED252700737CA5 /* CKTextKitEntityAttribute.m */,
|
||||
2577546D1BED252700737CA5 /* CKTextKitRenderer.h */,
|
||||
2577546E1BED252700737CA5 /* CKTextKitRenderer.mm */,
|
||||
2577546F1BED252700737CA5 /* CKTextKitRenderer+Positioning.h */,
|
||||
257754701BED252700737CA5 /* CKTextKitRenderer+Positioning.mm */,
|
||||
257754711BED252700737CA5 /* CKTextKitRenderer+TextChecking.h */,
|
||||
257754721BED252700737CA5 /* CKTextKitRenderer+TextChecking.mm */,
|
||||
257754751BED252700737CA5 /* CKTextKitShadower.h */,
|
||||
257754761BED252700737CA5 /* CKTextKitShadower.mm */,
|
||||
257754771BED252700737CA5 /* CKTextKitTailTruncater.h */,
|
||||
257754781BED252700737CA5 /* CKTextKitTailTruncater.mm */,
|
||||
257754791BED252700737CA5 /* CKTextKitTruncating.h */,
|
||||
2577548D1BED278B00737CA5 /* CKEqualityHashHelpers.h */,
|
||||
);
|
||||
name = TextKit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AC6456051B0A333200CF11B8 /* Layout */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1120,6 +1182,7 @@
|
||||
AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */,
|
||||
058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */,
|
||||
058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */,
|
||||
257754881BED252700737CA5 /* CKTextKitShadower.h in Headers */,
|
||||
058D0A6A195D05EC00B7D73C /* _ASAsyncTransactionContainer+Private.h in Headers */,
|
||||
058D0A6B195D05EC00B7D73C /* _ASAsyncTransactionContainer.h in Headers */,
|
||||
058D0A6D195D05EC00B7D73C /* _ASAsyncTransactionGroup.h in Headers */,
|
||||
@ -1145,10 +1208,12 @@
|
||||
AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */,
|
||||
205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */,
|
||||
AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */,
|
||||
2577547E1BED252700737CA5 /* CKTextKitEntityAttribute.h in Headers */,
|
||||
058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */,
|
||||
058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */,
|
||||
464052201A3F83C40061C0BA /* ASDataController.h in Headers */,
|
||||
05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */,
|
||||
2577547C1BED252700737CA5 /* CKTextKitContext.h in Headers */,
|
||||
ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */,
|
||||
058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */,
|
||||
DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */,
|
||||
@ -1164,6 +1229,7 @@
|
||||
058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */,
|
||||
058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */,
|
||||
058D0A4F195D05CB00B7D73C /* ASImageNode.h in Headers */,
|
||||
2577547A1BED252700737CA5 /* CKTextKitAttributes.h in Headers */,
|
||||
05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */,
|
||||
430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */,
|
||||
ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */,
|
||||
@ -1181,7 +1247,9 @@
|
||||
ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */,
|
||||
ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */,
|
||||
AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */,
|
||||
2577548C1BED252700737CA5 /* CKTextKitTruncating.h in Headers */,
|
||||
0516FA3D1A15563400B4EBED /* ASLog.h in Headers */,
|
||||
257754821BED252700737CA5 /* CKTextKitRenderer+Positioning.h in Headers */,
|
||||
0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */,
|
||||
0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */,
|
||||
058D0A59195D05DC00B7D73C /* ASMutableAttributedStringBuilder.h in Headers */,
|
||||
@ -1191,6 +1259,7 @@
|
||||
292C59A21A956527007E5DD6 /* ASRangeHandler.h in Headers */,
|
||||
292C59A01A956527007E5DD6 /* ASRangeHandlerPreload.h in Headers */,
|
||||
292C59A31A956527007E5DD6 /* ASRangeHandlerRender.h in Headers */,
|
||||
2577548A1BED252700737CA5 /* CKTextKitTailTruncater.h in Headers */,
|
||||
ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */,
|
||||
AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */,
|
||||
291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */,
|
||||
@ -1202,8 +1271,10 @@
|
||||
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
|
||||
ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */,
|
||||
ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */,
|
||||
257754801BED252700737CA5 /* CKTextKitRenderer.h in Headers */,
|
||||
ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */,
|
||||
ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */,
|
||||
257754841BED252700737CA5 /* CKTextKitRenderer+TextChecking.h in Headers */,
|
||||
9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */,
|
||||
ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */,
|
||||
055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */,
|
||||
@ -1557,11 +1628,15 @@
|
||||
058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */,
|
||||
0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */,
|
||||
464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */,
|
||||
2577548B1BED252700737CA5 /* CKTextKitTailTruncater.mm in Sources */,
|
||||
2577547F1BED252700737CA5 /* CKTextKitEntityAttribute.m in Sources */,
|
||||
058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */,
|
||||
058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */,
|
||||
058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */,
|
||||
2577547B1BED252700737CA5 /* CKTextKitAttributes.mm in Sources */,
|
||||
430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */,
|
||||
ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */,
|
||||
2577548E1BED278B00737CA5 /* CKEqualityHashHelpers.h in Sources */,
|
||||
ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.mm in Sources */,
|
||||
ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */,
|
||||
9C5FA3531B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */,
|
||||
@ -1572,6 +1647,8 @@
|
||||
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
|
||||
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
|
||||
055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */,
|
||||
2577547D1BED252700737CA5 /* CKTextKitContext.mm in Sources */,
|
||||
257754891BED252700737CA5 /* CKTextKitShadower.mm in Sources */,
|
||||
ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */,
|
||||
0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */,
|
||||
055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */,
|
||||
@ -1581,6 +1658,7 @@
|
||||
ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */,
|
||||
AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */,
|
||||
205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */,
|
||||
257754811BED252700737CA5 /* CKTextKitRenderer.mm in Sources */,
|
||||
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */,
|
||||
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
|
||||
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
|
||||
@ -1588,6 +1666,7 @@
|
||||
ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */,
|
||||
ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */,
|
||||
ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */,
|
||||
257754831BED252700737CA5 /* CKTextKitRenderer+Positioning.mm in Sources */,
|
||||
ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */,
|
||||
AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */,
|
||||
055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */,
|
||||
@ -1597,6 +1676,7 @@
|
||||
058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */,
|
||||
058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */,
|
||||
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */,
|
||||
257754851BED252700737CA5 /* CKTextKitRenderer+TextChecking.mm in Sources */,
|
||||
058D0A20195D050800B7D73C /* ASTextNodeWordKerner.m in Sources */,
|
||||
ACC945AB1BA9E7C1005E1FB8 /* ASViewController.m in Sources */,
|
||||
B0F8805B1BEAEC7500D17647 /* ASTableNode.m in Sources */,
|
||||
|
||||
171
AsyncDisplayKit/TextKit/CKEqualityHashHelpers.h
Normal file
171
AsyncDisplayKit/TextKit/CKEqualityHashHelpers.h
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <string>
|
||||
|
||||
// From folly:
|
||||
// This is the Hash128to64 function from Google's cityhash (available
|
||||
// under the MIT License). We use it to reduce multiple 64 bit hashes
|
||||
// into a single hash.
|
||||
inline uint64_t CKHashCombine(const uint64_t upper, const uint64_t lower) {
|
||||
// Murmur-inspired hashing.
|
||||
const uint64_t kMul = 0x9ddfea08eb382d69ULL;
|
||||
uint64_t a = (lower ^ upper) * kMul;
|
||||
a ^= (a >> 47);
|
||||
uint64_t b = (upper ^ a) * kMul;
|
||||
b ^= (b >> 47);
|
||||
b *= kMul;
|
||||
return b;
|
||||
}
|
||||
|
||||
#if __LP64__
|
||||
inline size_t CKHash64ToNative(uint64_t key) {
|
||||
return key;
|
||||
}
|
||||
#else
|
||||
|
||||
// Thomas Wang downscaling hash function
|
||||
inline size_t CKHash64ToNative(uint64_t key) {
|
||||
key = (~key) + (key << 18);
|
||||
key = key ^ (key >> 31);
|
||||
key = key * 21;
|
||||
key = key ^ (key >> 11);
|
||||
key = key + (key << 6);
|
||||
key = key ^ (key >> 22);
|
||||
return (uint32_t) key;
|
||||
}
|
||||
#endif
|
||||
|
||||
NSUInteger CKIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count);
|
||||
|
||||
namespace CK {
|
||||
// Default is not an ObjC class
|
||||
template<typename T, typename V = bool>
|
||||
struct is_objc_class : std::false_type { };
|
||||
|
||||
// Conditionally enable this template specialization on whether T is convertible to id, makes the is_objc_class a true_type
|
||||
template<typename T>
|
||||
struct is_objc_class<T, typename std::enable_if<std::is_convertible<T, id>::value, bool>::type> : std::true_type { };
|
||||
|
||||
// CKUtils::hash<T>()(value) -> either std::hash<T> if c++ or [o hash] if ObjC object.
|
||||
template <typename T, typename Enable = void> struct hash;
|
||||
|
||||
// For non-objc types, defer to std::hash
|
||||
template <typename T> struct hash<T, typename std::enable_if<!is_objc_class<T>::value>::type> {
|
||||
size_t operator ()(const T& a) {
|
||||
return std::hash<T>()(a);
|
||||
}
|
||||
};
|
||||
|
||||
// For objc types, call [o hash]
|
||||
template <typename T> struct hash<T, typename std::enable_if<is_objc_class<T>::value>::type> {
|
||||
size_t operator ()(id o) {
|
||||
return [o hash];
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Enable = void> struct is_equal;
|
||||
|
||||
// For non-objc types use == operator
|
||||
template <typename T> struct is_equal<T, typename std::enable_if<!is_objc_class<T>::value>::type> {
|
||||
bool operator ()(const T& a, const T& b) {
|
||||
return a == b;
|
||||
}
|
||||
};
|
||||
|
||||
// For objc types, check pointer equality, then use -isEqual:
|
||||
template <typename T> struct is_equal<T, typename std::enable_if<is_objc_class<T>::value>::type> {
|
||||
bool operator ()(id a, id b) {
|
||||
return a == b || [a isEqual:b];
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
namespace CKTupleOperations
|
||||
{
|
||||
// Recursive case (hash up to Index)
|
||||
template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
|
||||
struct _hash_helper
|
||||
{
|
||||
static size_t hash(Tuple const& tuple)
|
||||
{
|
||||
size_t prev = _hash_helper<Tuple, Index-1>::hash(tuple);
|
||||
using TypeForIndex = typename std::tuple_element<Index,Tuple>::type;
|
||||
size_t thisHash = CK::hash<TypeForIndex>()(std::get<Index>(tuple));
|
||||
return CKHashCombine(prev, thisHash);
|
||||
}
|
||||
};
|
||||
|
||||
// Base case (hash 0th element)
|
||||
template <class Tuple>
|
||||
struct _hash_helper<Tuple, 0>
|
||||
{
|
||||
static size_t hash(Tuple const& tuple)
|
||||
{
|
||||
using TypeForIndex = typename std::tuple_element<0,Tuple>::type;
|
||||
return CK::hash<TypeForIndex>()(std::get<0>(tuple));
|
||||
}
|
||||
};
|
||||
|
||||
// Recursive case (elements equal up to Index)
|
||||
template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
|
||||
struct _eq_helper
|
||||
{
|
||||
static bool equal(Tuple const& a, Tuple const& b)
|
||||
{
|
||||
bool prev = _eq_helper<Tuple, Index-1>::equal(a, b);
|
||||
using TypeForIndex = typename std::tuple_element<Index,Tuple>::type;
|
||||
auto aValue = std::get<Index>(a);
|
||||
auto bValue = std::get<Index>(b);
|
||||
return prev && CK::is_equal<TypeForIndex>()(aValue, bValue);
|
||||
}
|
||||
};
|
||||
|
||||
// Base case (0th elements equal)
|
||||
template <class Tuple>
|
||||
struct _eq_helper<Tuple, 0>
|
||||
{
|
||||
static bool equal(Tuple const& a, Tuple const& b)
|
||||
{
|
||||
using TypeForIndex = typename std::tuple_element<0,Tuple>::type;
|
||||
auto& aValue = std::get<0>(a);
|
||||
auto& bValue = std::get<0>(b);
|
||||
return CK::is_equal<TypeForIndex>()(aValue, bValue);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename ... TT> struct hash;
|
||||
|
||||
template <typename ... TT>
|
||||
struct hash<std::tuple<TT...>>
|
||||
{
|
||||
size_t operator()(std::tuple<TT...> const& tt) const
|
||||
{
|
||||
return _hash_helper<std::tuple<TT...>>::hash(tt);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename ... TT> struct equal_to;
|
||||
|
||||
template <typename ... TT>
|
||||
struct equal_to<std::tuple<TT...>>
|
||||
{
|
||||
bool operator()(std::tuple<TT...> const& a, std::tuple<TT...> const& b) const
|
||||
{
|
||||
return _eq_helper<std::tuple<TT...>>::equal(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
123
AsyncDisplayKit/TextKit/CKTextKitAttributes.h
Executable file
123
AsyncDisplayKit/TextKit/CKTextKitAttributes.h
Executable file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#ifndef ComponentKit_CKTextKitAttributes_h
|
||||
#define ComponentKit_CKTextKitAttributes_h
|
||||
|
||||
@protocol CKTextKitTruncating;
|
||||
|
||||
extern NSString *const CKTextKitTruncationAttributeName;
|
||||
/**
|
||||
Use CKTextKitEntityAttribute as the value of this attribute to embed a link or other interactable content inside the
|
||||
text.
|
||||
*/
|
||||
extern NSString *const CKTextKitEntityAttributeName;
|
||||
|
||||
static inline BOOL _objectsEqual(id<NSObject> obj1, id<NSObject> obj2)
|
||||
{
|
||||
return obj1 == obj2 ? YES : [obj1 isEqual:obj2];
|
||||
}
|
||||
|
||||
/**
|
||||
All NSObject values in this struct should be copied when passed into the TextComponent.
|
||||
*/
|
||||
struct CKTextKitAttributes {
|
||||
/**
|
||||
The string to be drawn. CKTextKit will not augment this string with default colors, etc. so this must be complete.
|
||||
*/
|
||||
NSAttributedString *attributedString;
|
||||
/**
|
||||
The string to use as the truncation string, usually just "...". If you have a range of text you would like to
|
||||
restrict highlighting to (for instance if you have "... Continue Reading", use the CKTextKitTruncationAttributeName
|
||||
to mark the specific range of the string that should be highlightable.
|
||||
*/
|
||||
NSAttributedString *truncationAttributedString;
|
||||
/**
|
||||
This is the character set that CKTextKit should attempt to avoid leaving as a trailing character before your
|
||||
truncation token. By default this set includes "\s\t\n\r.,!?:;" so you don't end up with ugly looking truncation
|
||||
text like "Hey, this is some fancy Truncation!\n\n...". Instead it would be truncated as "Hey, this is some fancy
|
||||
truncation...". This is not always possible.
|
||||
|
||||
Set this to the empty charset if you want to just use the "dumb" truncation behavior. A nil value will be
|
||||
substituted with the default described above.
|
||||
*/
|
||||
NSCharacterSet *avoidTailTruncationSet;
|
||||
/**
|
||||
The line-break mode to apply to the text. Since this also impacts how TextKit will attempt to truncate the text
|
||||
in your string, we only support NSLineBreakByWordWrapping and NSLineBreakByCharWrapping.
|
||||
*/
|
||||
NSLineBreakMode lineBreakMode;
|
||||
/**
|
||||
The maximum number of lines to draw in the drawable region. Leave blank or set to 0 to define no maximum.
|
||||
*/
|
||||
NSUInteger maximumNumberOfLines;
|
||||
/**
|
||||
The shadow offset for any shadows applied to the text. The coordinate space for this is the same as UIKit, so a
|
||||
positive width means towards the right, and a positive height means towards the bottom.
|
||||
*/
|
||||
CGSize shadowOffset;
|
||||
/**
|
||||
The color to use in drawing the text's shadow.
|
||||
*/
|
||||
UIColor *shadowColor;
|
||||
/**
|
||||
The opacity of the shadow from 0 to 1.
|
||||
*/
|
||||
CGFloat shadowOpacity;
|
||||
/**
|
||||
The radius that should be applied to the shadow blur. Larger values mean a larger, more blurred shadow.
|
||||
*/
|
||||
CGFloat shadowRadius;
|
||||
/**
|
||||
A pointer to a function that that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager.
|
||||
*/
|
||||
NSLayoutManager *(*layoutManagerFactory)(void);
|
||||
|
||||
/**
|
||||
We provide an explicit copy function so we can use aggregate initializer syntax while providing copy semantics for
|
||||
the NSObjects inside.
|
||||
*/
|
||||
const CKTextKitAttributes copy() const
|
||||
{
|
||||
return {
|
||||
[attributedString copy],
|
||||
[truncationAttributedString copy],
|
||||
[avoidTailTruncationSet copy],
|
||||
lineBreakMode,
|
||||
maximumNumberOfLines,
|
||||
shadowOffset,
|
||||
[shadowColor copy],
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
layoutManagerFactory
|
||||
};
|
||||
};
|
||||
|
||||
bool operator==(const CKTextKitAttributes &other) const
|
||||
{
|
||||
// These comparisons are in a specific order to reduce the overall cost of this function.
|
||||
return lineBreakMode == other.lineBreakMode
|
||||
&& maximumNumberOfLines == other.maximumNumberOfLines
|
||||
&& shadowOpacity == other.shadowOpacity
|
||||
&& shadowRadius == other.shadowRadius
|
||||
&& layoutManagerFactory == other.layoutManagerFactory
|
||||
&& CGSizeEqualToSize(shadowOffset, other.shadowOffset)
|
||||
&& _objectsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet)
|
||||
&& _objectsEqual(shadowColor, other.shadowColor)
|
||||
&& _objectsEqual(attributedString, other.attributedString)
|
||||
&& _objectsEqual(truncationAttributedString, other.truncationAttributedString);
|
||||
}
|
||||
|
||||
size_t hash() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
36
AsyncDisplayKit/TextKit/CKTextKitAttributes.mm
Executable file
36
AsyncDisplayKit/TextKit/CKTextKitAttributes.mm
Executable file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "CKTextKitAttributes.h"
|
||||
|
||||
#import "CKEqualityHashHelpers.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
NSString *const CKTextKitTruncationAttributeName = @"ck_truncation";
|
||||
NSString *const CKTextKitEntityAttributeName = @"ck_entity";
|
||||
|
||||
size_t CKTextKitAttributes::hash() const
|
||||
{
|
||||
NSUInteger subhashes[] = {
|
||||
[attributedString hash],
|
||||
[truncationAttributedString hash],
|
||||
[avoidTailTruncationSet hash],
|
||||
std::hash<NSUInteger>()((NSUInteger) layoutManagerFactory),
|
||||
std::hash<NSInteger>()(lineBreakMode),
|
||||
std::hash<NSInteger>()(maximumNumberOfLines),
|
||||
std::hash<CGFloat>()(shadowOffset.width),
|
||||
std::hash<CGFloat>()(shadowOffset.height),
|
||||
[shadowColor hash],
|
||||
std::hash<CGFloat>()(shadowOpacity),
|
||||
std::hash<CGFloat>()(shadowRadius),
|
||||
};
|
||||
return CKIntegerArrayHash(subhashes, sizeof(subhashes) / sizeof(subhashes[0]));
|
||||
}
|
||||
45
AsyncDisplayKit/TextKit/CKTextKitContext.h
Executable file
45
AsyncDisplayKit/TextKit/CKTextKitContext.h
Executable file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
A threadsafe container for the TextKit components that CKTextKit uses to lay out and truncate its text.
|
||||
|
||||
This container is the sole owner and manager of the TextKit classes. This is an important model because of major
|
||||
thread safety issues inside vanilla TextKit. It provides a central locking location for accessing TextKit methods.
|
||||
*/
|
||||
@interface CKTextKitContext : NSObject
|
||||
|
||||
/**
|
||||
Initializes a context and its associated TextKit components.
|
||||
|
||||
Initialization of TextKit components is a globally locking operation so be careful of bottlenecks with this class.
|
||||
*/
|
||||
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
|
||||
lineBreakMode:(NSLineBreakMode)lineBreakMode
|
||||
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
||||
constrainedSize:(CGSize)constrainedSize
|
||||
layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory;
|
||||
|
||||
/**
|
||||
All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to
|
||||
TextKit components may cause crashes.
|
||||
|
||||
The block provided MUST not call out to client code from within its scope or it is possible for this to cause deadlocks
|
||||
in your application. Use with EXTREME care.
|
||||
|
||||
Callers MUST NOT keep a ref to these internal objects and use them later. This WILL cause crashes in your application.
|
||||
*/
|
||||
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *layoutManager,
|
||||
NSTextStorage *textStorage,
|
||||
NSTextContainer *textContainer))block;
|
||||
|
||||
@end
|
||||
58
AsyncDisplayKit/TextKit/CKTextKitContext.mm
Executable file
58
AsyncDisplayKit/TextKit/CKTextKitContext.mm
Executable file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <mutex>
|
||||
|
||||
#import "CKTextKitContext.h"
|
||||
|
||||
@implementation CKTextKitContext
|
||||
{
|
||||
// All TextKit operations (even non-mutative ones) must be executed serially.
|
||||
std::mutex _textKitMutex;
|
||||
|
||||
NSLayoutManager *_layoutManager;
|
||||
NSTextStorage *_textStorage;
|
||||
NSTextContainer *_textContainer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
|
||||
lineBreakMode:(NSLineBreakMode)lineBreakMode
|
||||
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
||||
constrainedSize:(CGSize)constrainedSize
|
||||
layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory
|
||||
{
|
||||
if (self = [super init]) {
|
||||
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock.
|
||||
static std::mutex __static_mutex;
|
||||
std::lock_guard<std::mutex> l(__static_mutex);
|
||||
// Create the TextKit component stack with our default configuration.
|
||||
_textStorage = (attributedString ? [[NSTextStorage alloc] initWithAttributedString:attributedString] : [[NSTextStorage alloc] init]);
|
||||
_layoutManager = layoutManagerFactory ? layoutManagerFactory() : [[NSLayoutManager alloc] init];
|
||||
_layoutManager.usesFontLeading = NO;
|
||||
[_textStorage addLayoutManager:_layoutManager];
|
||||
_textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize];
|
||||
// We want the text laid out up to the very edges of the container.
|
||||
_textContainer.lineFragmentPadding = 0;
|
||||
_textContainer.lineBreakMode = lineBreakMode;
|
||||
_textContainer.maximumNumberOfLines = maximumNumberOfLines;
|
||||
[_layoutManager addTextContainer:_textContainer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *,
|
||||
NSTextStorage *,
|
||||
NSTextContainer *))block
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_textKitMutex);
|
||||
block(_layoutManager, _textStorage, _textContainer);
|
||||
}
|
||||
|
||||
@end
|
||||
28
AsyncDisplayKit/TextKit/CKTextKitEntityAttribute.h
Executable file
28
AsyncDisplayKit/TextKit/CKTextKitEntityAttribute.h
Executable file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
The object that should be embedded with CKTextKitEntityAttributeName. Please note that the entity you provide MUST
|
||||
implement a proper hash and isEqual function or your application performance will grind to a halt due to
|
||||
NSMutableAttributedString's usage of a global hash table of all attributes. This means the entity should NOT be a
|
||||
Foundation Collection (NSArray, NSDictionary, NSSet, etc.) since their hash function is a simple count of the values
|
||||
in the collection, which causes pathological performance problems deep inside NSAttributedString's implementation.
|
||||
|
||||
rdar://19352367
|
||||
*/
|
||||
@interface CKTextKitEntityAttribute : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) id<NSObject> entity;
|
||||
|
||||
- (instancetype)initWithEntity:(id<NSObject>)entity;
|
||||
|
||||
@end
|
||||
40
AsyncDisplayKit/TextKit/CKTextKitEntityAttribute.m
Executable file
40
AsyncDisplayKit/TextKit/CKTextKitEntityAttribute.m
Executable file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "CKTextKitEntityAttribute.h"
|
||||
|
||||
@implementation CKTextKitEntityAttribute
|
||||
|
||||
- (instancetype)initWithEntity:(id<NSObject>)entity
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_entity = entity;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return [_entity hash];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object) {
|
||||
return YES;
|
||||
}
|
||||
if (![object isKindOfClass:[self class]]) {
|
||||
return NO;
|
||||
}
|
||||
CKTextKitEntityAttribute *other = (CKTextKitEntityAttribute *)object;
|
||||
return _entity == other.entity || [_entity isEqual:other.entity];
|
||||
}
|
||||
|
||||
@end
|
||||
103
AsyncDisplayKit/TextKit/CKTextKitRenderer+Positioning.h
Executable file
103
AsyncDisplayKit/TextKit/CKTextKitRenderer+Positioning.h
Executable file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "CKTextKitRenderer.h"
|
||||
|
||||
typedef void (^ck_text_component_index_block_t)(NSUInteger characterIndex,
|
||||
CGRect glyphBoundingRect,
|
||||
BOOL *stop);
|
||||
|
||||
/**
|
||||
Measure options are used to specify which type of line height measurement to use.
|
||||
|
||||
ASTextNodeRendererMeasureOptionLineHeight is faster and will give the height from the baseline to the next line.
|
||||
|
||||
ASTextNodeRendererMeasureOptionCapHeight is a more nuanced measure of the glyphs in the given range that attempts to
|
||||
produce a visually balanced rectangle above and below the glyphs to produce nice looking text highlights.
|
||||
|
||||
ASTextNodeRendererMeasureOptionBlock uses the cap height option to generate each glyph index, but combines all but the
|
||||
first and last line rect into a single block. Looks nice for multiline selection.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, CKTextKitRendererMeasureOption) {
|
||||
CKTextKitRendererMeasureOptionLineHeight,
|
||||
CKTextKitRendererMeasureOptionCapHeight,
|
||||
CKTextKitRendererMeasureOptionBlock
|
||||
};
|
||||
|
||||
@interface CKTextKitRenderer (Positioning)
|
||||
|
||||
/**
|
||||
Returns the bounding rect for the given character range.
|
||||
|
||||
@param textRange The character range for which the bounding rect will be computed. Should be within the range of the
|
||||
attributedString of this renderer.
|
||||
|
||||
@discussion In the external, shadowed coordinate space.
|
||||
*/
|
||||
- (CGRect)frameForTextRange:(NSRange)textRange;
|
||||
|
||||
/**
|
||||
Returns an array of rects representing the lines in the given character range
|
||||
|
||||
@param textRange The character range for which the rects will be computed. Should be within the range of the
|
||||
attributedString of this renderer.
|
||||
@param measureOption The measure option to use for construction of the rects. See CKTextKitRendererMeasureOption
|
||||
docs for usage.
|
||||
|
||||
@discussion This method is useful for providing highlighting text. Returned rects are in the coordinate space of the
|
||||
renderer.
|
||||
|
||||
Triggers initialization of textkit components, truncation, and sizing.
|
||||
*/
|
||||
- (NSArray *)rectsForTextRange:(NSRange)textRange
|
||||
measureOption:(CKTextKitRendererMeasureOption)measureOption;
|
||||
|
||||
/**
|
||||
Enumerate the text character indexes at a position within the coordinate space of the renderer.
|
||||
|
||||
@param position The point in the shadowed coordinate space at which text indexes will be enumerated.
|
||||
@param block The block that will be executed for each index identified that may correspond to the given position. The
|
||||
block is given the character index that corresponds to the glyph at each index in question, as well as the bounding
|
||||
rect for that glyph.
|
||||
|
||||
@discussion Glyph location based on a touch point is not an exact science because user touches are not well-represented
|
||||
by a simple point, especially in the context of link-heavy text. So we have this method to make it a bit easier. This
|
||||
method checks a grid of candidate positions around the touch point you give it, and computes the bounding rect of the
|
||||
glyph corresponding to the character index given.
|
||||
|
||||
The bounding rect of the glyph can be used to identify the best glyph index that corresponds to your touch. For
|
||||
instance, comparing centroidal distance from the glyph bounding rect to the touch center is useful for identifying
|
||||
which link a user actually intended to select.
|
||||
|
||||
Triggers initialization of textkit components, truncation, and sizing.
|
||||
*/
|
||||
- (void)enumerateTextIndexesAtPosition:(CGPoint)position
|
||||
usingBlock:(ck_text_component_index_block_t)block;
|
||||
|
||||
/**
|
||||
Returns the single text index whose glyph's centroid is closest to the given position.
|
||||
|
||||
@param position The point in the shadowed coordinate space that should be checked.
|
||||
|
||||
@discussion This will use the grid enumeration function above, `enumerateTextIndexesAtPosition...`, in order to find
|
||||
the closest glyph, so it is possible that a glyph could be missed, but ultimately unlikely.
|
||||
*/
|
||||
- (NSUInteger)nearestTextIndexAtPosition:(CGPoint)position;
|
||||
|
||||
/**
|
||||
Returns the trailing rect unused by the renderer in the last rendered line.
|
||||
|
||||
@discussion In the external shadowed coordinate space.
|
||||
|
||||
Triggers initialization of textkit components, truncation, and sizing.
|
||||
*/
|
||||
- (CGRect)trailingRect;
|
||||
|
||||
@end
|
||||
374
AsyncDisplayKit/TextKit/CKTextKitRenderer+Positioning.mm
Executable file
374
AsyncDisplayKit/TextKit/CKTextKitRenderer+Positioning.mm
Executable file
@ -0,0 +1,374 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "CKTextKitRenderer+Positioning.h"
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
#import "ASAssert.h"
|
||||
|
||||
#import "CKTextKitContext.h"
|
||||
#import "CKTextKitShadower.h"
|
||||
|
||||
static const CGFloat CKTextKitRendererGlyphTouchHitSlop = 5.0;
|
||||
static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
|
||||
|
||||
@implementation CKTextKitRenderer (Tracking)
|
||||
|
||||
- (NSArray *)rectsForTextRange:(NSRange)textRange
|
||||
measureOption:(CKTextKitRendererMeasureOption)measureOption
|
||||
{
|
||||
__block NSArray *textRects = @[];
|
||||
[self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
BOOL textRangeIsValid = (NSMaxRange(textRange) <= [textStorage length]);
|
||||
ASDisplayNodeCAssertTrue(textRangeIsValid);
|
||||
if (!textRangeIsValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Used for block measure option
|
||||
__block CGRect firstRect = CGRectNull;
|
||||
__block CGRect lastRect = CGRectNull;
|
||||
__block CGRect blockRect = CGRectNull;
|
||||
NSMutableArray *mutableTextRects = [NSMutableArray array];
|
||||
|
||||
NSString *string = textStorage.string;
|
||||
|
||||
NSRange totalGlyphRange = [layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL];
|
||||
|
||||
[layoutManager enumerateLineFragmentsForGlyphRange:totalGlyphRange usingBlock:^(CGRect rect,
|
||||
CGRect usedRect,
|
||||
NSTextContainer *innerTextContainer,
|
||||
NSRange glyphRange,
|
||||
BOOL *stop) {
|
||||
|
||||
CGRect lineRect = CGRectNull;
|
||||
// If we're empty, don't bother looping through glyphs, use the default.
|
||||
if (CGRectIsEmpty(usedRect)) {
|
||||
lineRect = usedRect;
|
||||
} else {
|
||||
// TextKit's bounding rect computations are just a touch off, so we actually
|
||||
// compose the rects by hand from the center of the given TextKit bounds and
|
||||
// imposing the font attributes returned by the glyph's font.
|
||||
NSRange lineGlyphRange = NSIntersectionRange(totalGlyphRange, glyphRange);
|
||||
for (NSUInteger i = lineGlyphRange.location; i < NSMaxRange(lineGlyphRange) && i < string.length; i++) {
|
||||
// We grab the properly sized rect for the glyph
|
||||
CGRect properGlyphRect = [self _internalRectForGlyphAtIndex:i
|
||||
measureOption:measureOption
|
||||
layoutManager:layoutManager
|
||||
textContainer:textContainer
|
||||
textStorage:textStorage];
|
||||
|
||||
// Don't count empty glyphs towards our line rect.
|
||||
if (!CGRectIsEmpty(properGlyphRect)) {
|
||||
lineRect = CGRectIsNull(lineRect) ? properGlyphRect
|
||||
: CGRectUnion(lineRect, properGlyphRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!CGRectIsNull(lineRect)) {
|
||||
if (measureOption == CKTextKitRendererMeasureOptionBlock) {
|
||||
// For the block measurement option we store the first & last rect as
|
||||
// special cases, then merge everything else into a single block rect
|
||||
if (CGRectIsNull(firstRect)) {
|
||||
// We don't have a firstRect, so we must be on the first line.
|
||||
firstRect = lineRect;
|
||||
} else if(CGRectIsNull(lastRect)) {
|
||||
// We don't have a lastRect, but we do have a firstRect, so we must
|
||||
// be on the second line. No need to merge in the blockRect just yet
|
||||
lastRect = lineRect;
|
||||
} else if(CGRectIsNull(blockRect)) {
|
||||
// We have both a first and last rect, so we must be on the third line
|
||||
// we don't have any blockRect to merge it into, so we just set it
|
||||
// directly.
|
||||
blockRect = lastRect;
|
||||
lastRect = lineRect;
|
||||
} else {
|
||||
// Everything is already set, so we just merge this line into the
|
||||
// block.
|
||||
blockRect = CGRectUnion(blockRect, lastRect);
|
||||
lastRect = lineRect;
|
||||
}
|
||||
} else {
|
||||
// If the block option isn't being used then each line is being treated
|
||||
// individually.
|
||||
[mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lineRect]]];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
if (measureOption == CKTextKitRendererMeasureOptionBlock) {
|
||||
// Block measure option is handled differently with just 3 vars for the entire range.
|
||||
if (!CGRectIsNull(firstRect)) {
|
||||
if (!CGRectIsNull(blockRect)) {
|
||||
CGFloat rightEdge = MAX(CGRectGetMaxX(blockRect), CGRectGetMaxX(lastRect));
|
||||
if (rightEdge > CGRectGetMaxX(firstRect)) {
|
||||
// Force the right side of the first rect to properly align with the
|
||||
// right side of the rightmost of the block and last rect
|
||||
firstRect.size.width += rightEdge - CGRectGetMaxX(firstRect);
|
||||
}
|
||||
|
||||
// Force the left side of the block rect to properly align with the
|
||||
// left side of the leftmost of the first and last rect
|
||||
blockRect.origin.x = MIN(CGRectGetMinX(firstRect), CGRectGetMinX(lastRect));
|
||||
// Force the right side of the block rect to properly align with the
|
||||
// right side of the rightmost of the first and last rect
|
||||
blockRect.size.width += MAX(CGRectGetMaxX(firstRect), CGRectGetMaxX(lastRect)) - CGRectGetMaxX(blockRect);
|
||||
}
|
||||
if (!CGRectIsNull(lastRect)) {
|
||||
// Force the left edge of the last rect to properly align with the
|
||||
// left side of the leftmost of the first and block rect, if necessary.
|
||||
CGFloat leftEdge = MIN(CGRectGetMinX(blockRect), CGRectGetMinX(firstRect));
|
||||
CGFloat lastRectNudgeAmount = MAX(CGRectGetMinX(lastRect) - leftEdge, 0);
|
||||
lastRect.origin.x = MIN(leftEdge, CGRectGetMinX(lastRect));
|
||||
lastRect.size.width += lastRectNudgeAmount;
|
||||
}
|
||||
|
||||
[mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:firstRect]]];
|
||||
}
|
||||
if (!CGRectIsNull(blockRect)) {
|
||||
[mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:blockRect]]];
|
||||
}
|
||||
if (!CGRectIsNull(lastRect)) {
|
||||
[mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lastRect]]];
|
||||
}
|
||||
}
|
||||
textRects = mutableTextRects;
|
||||
}];
|
||||
|
||||
return textRects;
|
||||
}
|
||||
|
||||
- (NSUInteger)nearestTextIndexAtPosition:(CGPoint)position
|
||||
{
|
||||
// Check in a 9-point region around the actual touch point so we make sure
|
||||
// we get the best attribute for the touch.
|
||||
__block CGFloat minimumGlyphDistance = CGFLOAT_MAX;
|
||||
__block NSUInteger minimumGlyphCharacterIndex = NSNotFound;
|
||||
|
||||
[self enumerateTextIndexesAtPosition:position usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) {
|
||||
CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect));
|
||||
CGFloat currentDistance = sqrtf(powf(position.x - glyphLocation.x, 2.f) + powf(position.y - glyphLocation.y, 2.f));
|
||||
if (currentDistance < minimumGlyphDistance) {
|
||||
minimumGlyphDistance = currentDistance;
|
||||
minimumGlyphCharacterIndex = characterIndex;
|
||||
}
|
||||
}];
|
||||
return minimumGlyphCharacterIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
Measured from the internal coordinate space of the context, not accounting for shadow offsets. Actually uses CoreText
|
||||
as an approximation to work around problems in TextKit's glyph sizing.
|
||||
*/
|
||||
- (CGRect)_internalRectForGlyphAtIndex:(NSUInteger)glyphIndex
|
||||
measureOption:(CKTextKitRendererMeasureOption)measureOption
|
||||
layoutManager:(NSLayoutManager *)layoutManager
|
||||
textContainer:(NSTextContainer *)textContainer
|
||||
textStorage:(NSTextStorage *)textStorage
|
||||
{
|
||||
NSUInteger charIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
|
||||
CGGlyph glyph = [layoutManager glyphAtIndex:glyphIndex];
|
||||
CTFontRef font = (__bridge_retained CTFontRef)[textStorage attribute:NSFontAttributeName
|
||||
atIndex:charIndex
|
||||
effectiveRange:NULL];
|
||||
if (font == nil) {
|
||||
font = (__bridge_retained CTFontRef)[UIFont systemFontOfSize:12.0];
|
||||
}
|
||||
|
||||
// Glyph Advance
|
||||
// +-------------------------+
|
||||
// | |
|
||||
// | |
|
||||
// +------------------------+--|-------------------------|--+-----------+-----+ What TextKit returns sometimes
|
||||
// | | | XXXXXXXXXXX + | | | (approx. correct height, but
|
||||
// | ---------|--+---------+ XXX XXXX +|-----------|-----| sometimes inaccurate bounding
|
||||
// | | | XXX XXXXX| | | widths)
|
||||
// | | | XX XX | | |
|
||||
// | | | XX | | |
|
||||
// | | | XXX | | |
|
||||
// | | | XX | | |
|
||||
// | | | XXXXXXXXXXX | | |
|
||||
// | Cap Height->| | XX | | |
|
||||
// | | | XX | Ascent-->| |
|
||||
// | | | XX | | |
|
||||
// | | | XX | | |
|
||||
// | | | X | | |
|
||||
// | | | X | | |
|
||||
// | | | X | | |
|
||||
// | | | XX | | |
|
||||
// | | | X | | |
|
||||
// | ---------|-------+ X +-------------------------------------|
|
||||
// | | XX | |
|
||||
// | | X | |
|
||||
// | | XX Descent------>| |
|
||||
// | | XXXXXX | |
|
||||
// | | XXX | |
|
||||
// +------------------------+-------------------------------------------------+
|
||||
// |
|
||||
// +--+Actual bounding box
|
||||
|
||||
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:NSMakeRange(glyphIndex, 1)
|
||||
inTextContainer:textContainer];
|
||||
|
||||
// If it is a NSTextAttachment, we don't have the matched glyph and use width of glyphRect instead of advance.
|
||||
CGFloat advance = (glyph == kCGFontIndexInvalid) ? glyphRect.size.width : CTFontGetAdvancesForGlyphs(font, kCTFontOrientationHorizontal, &glyph, NULL, 1);
|
||||
|
||||
// We treat the center of the glyph's bounding box as the center of our new rect
|
||||
CGPoint glyphCenter = CGPointMake(CGRectGetMidX(glyphRect), CGRectGetMidY(glyphRect));
|
||||
|
||||
CGRect properGlyphRect;
|
||||
if (measureOption == CKTextKitRendererMeasureOptionCapHeight
|
||||
|| measureOption == CKTextKitRendererMeasureOptionBlock) {
|
||||
CGFloat ascent = CTFontGetAscent(font);
|
||||
CGFloat descent = CTFontGetDescent(font);
|
||||
CGFloat capHeight = CTFontGetCapHeight(font);
|
||||
CGFloat leading = CTFontGetLeading(font);
|
||||
CGFloat glyphHeight = ascent + descent;
|
||||
|
||||
// For visual balance, we add the cap height padding above the cap, and
|
||||
// below the baseline, we scale by the descent so it grows with the size of
|
||||
// the text.
|
||||
CGFloat topPadding = CKTextKitRendererTextCapHeightPadding * descent;
|
||||
CGFloat bottomPadding = topPadding;
|
||||
|
||||
properGlyphRect = CGRectMake(glyphCenter.x - advance * 0.5,
|
||||
glyphCenter.y - glyphHeight * 0.5 + (ascent - capHeight) - topPadding + leading,
|
||||
advance,
|
||||
capHeight + topPadding + bottomPadding);
|
||||
} else {
|
||||
// We are just measuring the line heights here, so we can use the
|
||||
// heights used by TextKit, which tend to be pretty good.
|
||||
properGlyphRect = CGRectMake(glyphCenter.x - advance * 0.5,
|
||||
glyphRect.origin.y,
|
||||
advance,
|
||||
glyphRect.size.height);
|
||||
}
|
||||
|
||||
CFRelease(font);
|
||||
|
||||
return properGlyphRect;
|
||||
}
|
||||
|
||||
- (void)enumerateTextIndexesAtPosition:(CGPoint)externalPosition usingBlock:(ck_text_component_index_block_t)block
|
||||
{
|
||||
// This method is a little complex because it has to call out to client code from inside an enumeration that needs
|
||||
// to achieve a lock on the textkit components. It cannot call out to client code from within that lock so we just
|
||||
// perform the textkit-locked ops inside the locked context.
|
||||
CKTextKitContext *lockingContext = self.context;
|
||||
CGPoint internalPosition = [self.shadower offsetPointWithExternalPoint:externalPosition];
|
||||
__block BOOL invalidPosition = NO;
|
||||
[lockingContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
invalidPosition = internalPosition.x > textContainer.size.width
|
||||
|| internalPosition.y > textContainer.size.height
|
||||
|| block == NULL;
|
||||
}];
|
||||
if (invalidPosition) {
|
||||
// Short circuit if the position is outside the size of this renderer, or if the block is null.
|
||||
return;
|
||||
}
|
||||
|
||||
// We break it up into a 44pt box for the touch, and find the closest link attribute-containing glyph to the center of
|
||||
// the touch.
|
||||
CGFloat squareSide = 44.f;
|
||||
// Should be odd if you want to test the center of the touch.
|
||||
NSInteger pointsOnASide = 3;
|
||||
|
||||
// The distance between any 2 of the adjacent points
|
||||
CGFloat pointSeparation = squareSide / pointsOnASide;
|
||||
// These are for tracking which point we're on. We start with -pointsOnASide/2 and go to pointsOnASide/2. So if
|
||||
// pointsOnASide=3, we go from -1 to 1.
|
||||
NSInteger endIndex = pointsOnASide / 2;
|
||||
NSInteger startIndex = -endIndex;
|
||||
|
||||
BOOL stop = NO;
|
||||
for (NSInteger i = startIndex; i <= endIndex && !stop; i++) {
|
||||
for (NSInteger j = startIndex; j <= endIndex && !stop; j++) {
|
||||
CGPoint currentPoint = CGPointMake(internalPosition.x + i * pointSeparation,
|
||||
internalPosition.y + j * pointSeparation);
|
||||
|
||||
__block NSUInteger characterIndex = NSNotFound;
|
||||
__block BOOL isValidGlyph = NO;
|
||||
__block CGRect glyphRect = CGRectNull;
|
||||
|
||||
[lockingContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
// We ask the layout manager for the proper glyph at the touch point
|
||||
NSUInteger glyphIndex = [layoutManager glyphIndexForPoint:currentPoint
|
||||
inTextContainer:textContainer];
|
||||
|
||||
// If it's an invalid glyph, quit.
|
||||
|
||||
[layoutManager glyphAtIndex:glyphIndex isValidIndex:&isValidGlyph];
|
||||
if (!isValidGlyph) {
|
||||
return;
|
||||
}
|
||||
|
||||
characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
|
||||
|
||||
glyphRect = [self _internalRectForGlyphAtIndex:glyphIndex
|
||||
measureOption:CKTextKitRendererMeasureOptionLineHeight
|
||||
layoutManager:layoutManager
|
||||
textContainer:textContainer
|
||||
textStorage:textStorage];
|
||||
}];
|
||||
|
||||
// Sometimes TextKit plays jokes on us and returns glyphs that really aren't close to the point in question.
|
||||
// Silly TextKit...
|
||||
if (!isValidGlyph || !CGRectContainsPoint(CGRectInset(glyphRect, -CKTextKitRendererGlyphTouchHitSlop, -CKTextKitRendererGlyphTouchHitSlop), currentPoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
block(characterIndex, [self.shadower offsetRectWithInternalRect:glyphRect], &stop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CGRect)trailingRect
|
||||
{
|
||||
__block CGRect trailingRect = CGRectNull;
|
||||
[self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
CGSize calculatedSize = textContainer.size;
|
||||
// If have an empty string, then our whole bounds constitute trailing space.
|
||||
if ([textStorage length] == 0) {
|
||||
trailingRect = CGRectMake(0, 0, calculatedSize.width, calculatedSize.height);
|
||||
return;
|
||||
}
|
||||
|
||||
// Take everything after our final character as trailing space.
|
||||
NSArray *finalRects = [self rectsForTextRange:NSMakeRange([textStorage length] - 1, 1) measureOption:CKTextKitRendererMeasureOptionLineHeight];
|
||||
CGRect finalGlyphRect = [[finalRects lastObject] CGRectValue];
|
||||
CGPoint origin = CGPointMake(CGRectGetMaxX(finalGlyphRect), CGRectGetMinY(finalGlyphRect));
|
||||
CGSize size = CGSizeMake(calculatedSize.width - origin.x, calculatedSize.height - origin.y);
|
||||
trailingRect = (CGRect){origin, size};
|
||||
}];
|
||||
return trailingRect;
|
||||
}
|
||||
|
||||
- (CGRect)frameForTextRange:(NSRange)textRange
|
||||
{
|
||||
__block CGRect textRect = CGRectNull;
|
||||
[self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
// Bail on invalid range.
|
||||
if (NSMaxRange(textRange) > [textStorage length]) {
|
||||
ASDisplayNodeCFailAssert(@"Invalid range");
|
||||
return;
|
||||
}
|
||||
|
||||
// Force glyph generation and layout.
|
||||
[layoutManager ensureLayoutForTextContainer:textContainer];
|
||||
|
||||
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL];
|
||||
textRect = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
|
||||
}];
|
||||
return textRect;
|
||||
}
|
||||
|
||||
@end
|
||||
29
AsyncDisplayKit/TextKit/CKTextKitRenderer+TextChecking.h
Executable file
29
AsyncDisplayKit/TextKit/CKTextKitRenderer+TextChecking.h
Executable file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "CKTextKitRenderer.h"
|
||||
|
||||
/**
|
||||
Application extensions to NSTextCheckingType. We're allowed to do this (see NSTextCheckingAllCustomTypes).
|
||||
*/
|
||||
static uint64_t const CKTextKitTextCheckingTypeEntity = 1ULL << 33;
|
||||
static uint64_t const CKTextKitTextCheckingTypeTruncation = 1ULL << 34;
|
||||
|
||||
@class CKTextKitEntityAttribute;
|
||||
|
||||
@interface CKTextKitTextCheckingResult : NSTextCheckingResult
|
||||
@property (nonatomic, strong, readonly) CKTextKitEntityAttribute *entityAttribute;
|
||||
@end
|
||||
|
||||
@interface CKTextKitRenderer (TextChecking)
|
||||
|
||||
- (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point;
|
||||
|
||||
@end
|
||||
102
AsyncDisplayKit/TextKit/CKTextKitRenderer+TextChecking.mm
Executable file
102
AsyncDisplayKit/TextKit/CKTextKitRenderer+TextChecking.mm
Executable file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "CKTextKitRenderer+TextChecking.h"
|
||||
|
||||
#import "CKTextKitAttributes.h"
|
||||
#import "CKTextKitEntityAttribute.h"
|
||||
#import "CKTextKitRenderer+Positioning.h"
|
||||
#import "CKTextKitTailTruncater.h"
|
||||
|
||||
@implementation CKTextKitTextCheckingResult
|
||||
|
||||
{
|
||||
// Be explicit about the fact that we are overriding the super class' implementation of -range and -resultType
|
||||
// and substituting our own custom values. (We could use @synthesize to make these ivars, but our linter correctly
|
||||
// complains; it's weird to use @synthesize for properties that are redeclared on top of an original declaration in
|
||||
// the superclass. We only do it here because NSTextCheckingResult doesn't expose an initializer, which is silly.)
|
||||
NSRange _rangeOverride;
|
||||
NSTextCheckingType _resultTypeOverride;
|
||||
}
|
||||
|
||||
- (instancetype)initWithType:(NSTextCheckingType)type
|
||||
entityAttribute:(CKTextKitEntityAttribute *)entityAttribute
|
||||
range:(NSRange)range
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_resultTypeOverride = type;
|
||||
_rangeOverride = range;
|
||||
_entityAttribute = entityAttribute;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSTextCheckingType)resultType
|
||||
{
|
||||
return _resultTypeOverride;
|
||||
}
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return _rangeOverride;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CKTextKitRenderer (TextChecking)
|
||||
|
||||
- (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point
|
||||
{
|
||||
__block NSTextCheckingResult *result = nil;
|
||||
NSAttributedString *attributedString = self.attributes.attributedString;
|
||||
NSAttributedString *truncationAttributedString = self.attributes.truncationAttributedString;
|
||||
|
||||
// get the index of the last character, so we can handle text in the truncation token
|
||||
NSRange visibleRange = self.truncater.visibleRanges[0];
|
||||
__block NSRange truncationTokenRange = { NSNotFound, 0 };
|
||||
|
||||
[truncationAttributedString enumerateAttribute:CKTextKitTruncationAttributeName inRange:NSMakeRange(0, truncationAttributedString.length)
|
||||
options:0
|
||||
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
||||
if (value != nil && range.length > 0) {
|
||||
truncationTokenRange = range;
|
||||
}
|
||||
}];
|
||||
|
||||
if (truncationTokenRange.location == NSNotFound) {
|
||||
// The truncation string didn't specify a substring which should be highlighted, so we just highlight it all
|
||||
truncationTokenRange = { 0, self.attributes.truncationAttributedString.length };
|
||||
}
|
||||
|
||||
truncationTokenRange.location += NSMaxRange(visibleRange);
|
||||
|
||||
[self enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger index, CGRect glyphBoundingRect, BOOL *stop){
|
||||
if (index >= truncationTokenRange.location) {
|
||||
result = [[CKTextKitTextCheckingResult alloc] initWithType:CKTextKitTextCheckingTypeTruncation
|
||||
entityAttribute:nil
|
||||
range:truncationTokenRange];
|
||||
} else {
|
||||
NSRange range;
|
||||
NSDictionary *attributes = [attributedString attributesAtIndex:index effectiveRange:&range];
|
||||
CKTextKitEntityAttribute *entityAttribute = attributes[CKTextKitEntityAttributeName];
|
||||
if (entityAttribute) {
|
||||
result = [[CKTextKitTextCheckingResult alloc] initWithType:CKTextKitTextCheckingTypeEntity
|
||||
entityAttribute:entityAttribute
|
||||
range:range];
|
||||
}
|
||||
}
|
||||
if (result != nil) {
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
84
AsyncDisplayKit/TextKit/CKTextKitRenderer.h
Executable file
84
AsyncDisplayKit/TextKit/CKTextKitRenderer.h
Executable file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <vector>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "CKTextKitAttributes.h"
|
||||
|
||||
@class CKTextKitContext;
|
||||
@class CKTextKitShadower;
|
||||
@protocol CKTextKitTruncating;
|
||||
|
||||
/**
|
||||
CKTextKitRenderer is a modular object that is responsible for laying out and drawing text.
|
||||
|
||||
A renderer will hold onto the TextKit layouts for the given attributes after initialization. This may constitute a
|
||||
large amount of memory for large enough applications, so care must be taken when keeping many of these around in-memory
|
||||
at once.
|
||||
|
||||
This object is designed to be modular and simple. All complex maintenance of state should occur in sub-objects or be
|
||||
derived via pure functions or categories. No touch-related handling belongs in this class.
|
||||
|
||||
ALL sizing and layout information from this class is in the external coordinate space of the TextKit components. This
|
||||
is an important distinction because all internal sizing and layout operations are carried out within the shadowed
|
||||
coordinate space. Padding will be added for you in order to ensure clipping does not occur, and additional information
|
||||
on this transform is available via the shadower should you need it.
|
||||
*/
|
||||
@interface CKTextKitRenderer : NSObject
|
||||
|
||||
/**
|
||||
Designated Initializer
|
||||
dvlkferufedgjnhjjfhldjedlunvtdtv
|
||||
@discussion Sizing will occur as a result of initialization, so be careful when/where you use this.
|
||||
*/
|
||||
- (instancetype)initWithTextKitAttributes:(const CKTextKitAttributes &)textComponentAttributes
|
||||
constrainedSize:(const CGSize)constrainedSize;
|
||||
|
||||
@property (nonatomic, strong, readonly) CKTextKitContext *context;
|
||||
|
||||
@property (nonatomic, strong, readonly) id<CKTextKitTruncating> truncater;
|
||||
|
||||
@property (nonatomic, strong, readonly) CKTextKitShadower *shadower;
|
||||
|
||||
@property (nonatomic, assign, readonly) CKTextKitAttributes attributes;
|
||||
|
||||
@property (nonatomic, assign, readonly) CGSize constrainedSize;
|
||||
|
||||
#pragma mark - Drawing
|
||||
/*
|
||||
Draw the renderer's text content into the bounds provided.
|
||||
|
||||
@param bounds The rect in which to draw the contents of the renderer.
|
||||
*/
|
||||
- (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds;
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
/*
|
||||
Returns the computed size of the renderer given the constrained size and other parameters in the initializer.
|
||||
*/
|
||||
- (CGSize)size;
|
||||
|
||||
#pragma mark - Text Ranges
|
||||
|
||||
/*
|
||||
The character range from the original attributedString that is displayed by the renderer given the parameters in the
|
||||
initializer.
|
||||
*/
|
||||
- (std::vector<NSRange>)visibleRanges;
|
||||
|
||||
/*
|
||||
The number of lines shown in the string.
|
||||
*/
|
||||
- (NSUInteger)lineCount;
|
||||
|
||||
@end
|
||||
140
AsyncDisplayKit/TextKit/CKTextKitRenderer.mm
Executable file
140
AsyncDisplayKit/TextKit/CKTextKitRenderer.mm
Executable file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "CKTextKitRenderer.h"
|
||||
|
||||
#import "ASAssert.h"
|
||||
|
||||
#import "CKTextKitContext.h"
|
||||
#import "CKTextKitShadower.h"
|
||||
#import "CKTextKitTailTruncater.h"
|
||||
#import "CKTextKitTruncating.h"
|
||||
|
||||
static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
||||
{
|
||||
static NSCharacterSet *truncationCharacterSet;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSMutableCharacterSet *mutableCharacterSet = [[NSMutableCharacterSet alloc] init];
|
||||
[mutableCharacterSet formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
[mutableCharacterSet addCharactersInString:@".,!?:;"];
|
||||
truncationCharacterSet = mutableCharacterSet;
|
||||
});
|
||||
return truncationCharacterSet;
|
||||
}
|
||||
|
||||
@implementation CKTextKitRenderer {
|
||||
CGSize _calculatedSize;
|
||||
}
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithTextKitAttributes:(const CKTextKitAttributes &)attributes
|
||||
constrainedSize:(const CGSize)constrainedSize
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_constrainedSize = constrainedSize;
|
||||
_attributes = attributes;
|
||||
|
||||
_shadower = [[CKTextKitShadower alloc] initWithShadowOffset:attributes.shadowOffset
|
||||
shadowColor:attributes.shadowColor
|
||||
shadowOpacity:attributes.shadowOpacity
|
||||
shadowRadius:attributes.shadowRadius];
|
||||
|
||||
// We must inset the constrained size by the size of the shadower.
|
||||
CGSize shadowConstrainedSize = [_shadower insetSizeWithConstrainedSize:_constrainedSize];
|
||||
|
||||
_context = [[CKTextKitContext alloc] initWithAttributedString:attributes.attributedString
|
||||
lineBreakMode:attributes.lineBreakMode
|
||||
maximumNumberOfLines:attributes.maximumNumberOfLines
|
||||
constrainedSize:shadowConstrainedSize
|
||||
layoutManagerFactory:attributes.layoutManagerFactory];
|
||||
|
||||
_truncater = [[CKTextKitTailTruncater alloc] initWithContext:_context
|
||||
truncationAttributedString:attributes.truncationAttributedString
|
||||
avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet()
|
||||
constrainedSize:shadowConstrainedSize];
|
||||
|
||||
[self _calculateSize];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Sizing
|
||||
|
||||
- (void)_calculateSize
|
||||
{
|
||||
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
|
||||
// -usedRectForTextContainer:).
|
||||
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
[layoutManager ensureLayoutForTextContainer:textContainer];
|
||||
}];
|
||||
|
||||
|
||||
CGRect constrainedRect = {CGPointZero, _constrainedSize};
|
||||
__block CGRect boundingRect;
|
||||
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
boundingRect = [layoutManager usedRectForTextContainer:textContainer];
|
||||
}];
|
||||
|
||||
// TextKit often returns incorrect glyph bounding rects in the horizontal direction, so we clip to our bounding rect
|
||||
// to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect.
|
||||
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
|
||||
|
||||
_calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
|
||||
}
|
||||
|
||||
- (CGSize)size
|
||||
{
|
||||
return _calculatedSize;
|
||||
}
|
||||
|
||||
#pragma mark - Drawing
|
||||
|
||||
- (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds;
|
||||
{
|
||||
// We add an assertion so we can track the rare conditions where a graphics context is not present
|
||||
ASDisplayNodeAssertNotNil(context, @"This is no good without a context.");
|
||||
|
||||
CGRect shadowInsetBounds = [_shadower insetRectWithConstrainedRect:bounds];
|
||||
|
||||
CGContextSaveGState(context);
|
||||
[_shadower setShadowInContext:context];
|
||||
UIGraphicsPushContext(context);
|
||||
|
||||
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
||||
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
||||
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
||||
}];
|
||||
|
||||
UIGraphicsPopContext();
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
#pragma mark - String Ranges
|
||||
|
||||
- (NSUInteger)lineCount
|
||||
{
|
||||
__block NSUInteger lineCount = 0;
|
||||
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]; lineCount++) {
|
||||
[layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange];
|
||||
}
|
||||
}];
|
||||
return lineCount;
|
||||
}
|
||||
|
||||
- (std::vector<NSRange>)visibleRanges
|
||||
{
|
||||
return _truncater.visibleRanges;
|
||||
}
|
||||
|
||||
@end
|
||||
70
AsyncDisplayKit/TextKit/CKTextKitShadower.h
Executable file
70
AsyncDisplayKit/TextKit/CKTextKitShadower.h
Executable file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
* @abstract an immutable class for calculating shadow padding drawing a shadowed background for text
|
||||
*/
|
||||
@interface CKTextKitShadower : NSObject
|
||||
|
||||
- (instancetype)initWithShadowOffset:(CGSize)shadowOffset
|
||||
shadowColor:(UIColor *)shadowColor
|
||||
shadowOpacity:(CGFloat)shadowOpacity
|
||||
shadowRadius:(CGFloat)shadowRadius;
|
||||
|
||||
/**
|
||||
* @abstract The offset from the top-left corner at which the shadow starts.
|
||||
* @discussion A positive width will move the shadow to the right.
|
||||
* A positive height will move the shadow downwards.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) CGSize shadowOffset;
|
||||
|
||||
//! CGColor in which the shadow is drawn
|
||||
@property (nonatomic, readonly, strong) UIColor *shadowColor;
|
||||
|
||||
//! Alpha of the shadow
|
||||
@property (nonatomic, readonly, assign) CGFloat shadowOpacity;
|
||||
|
||||
//! Radius, in pixels
|
||||
@property (nonatomic, readonly, assign) CGFloat shadowRadius;
|
||||
|
||||
/**
|
||||
* @abstract The edge insets which represent shadow padding
|
||||
* @discussion Each edge inset is less than or equal to zero.
|
||||
*
|
||||
* Example:
|
||||
* CGRect boundsWithoutShadowPadding; // Large enough to fit text, not large enough to fit the shadow as well
|
||||
* UIEdgeInsets shadowPadding = [shadower shadowPadding];
|
||||
* CGRect boundsWithShadowPadding = UIEdgeInsetsRect(boundsWithoutShadowPadding, shadowPadding);
|
||||
*/
|
||||
- (UIEdgeInsets)shadowPadding;
|
||||
|
||||
- (CGSize)insetSizeWithConstrainedSize:(CGSize)constrainedSize;
|
||||
|
||||
- (CGRect)insetRectWithConstrainedRect:(CGRect)constrainedRect;
|
||||
|
||||
- (CGSize)outsetSizeWithInsetSize:(CGSize)insetSize;
|
||||
|
||||
- (CGRect)outsetRectWithInsetRect:(CGRect)insetRect;
|
||||
|
||||
- (CGRect)offsetRectWithInternalRect:(CGRect)internalRect;
|
||||
|
||||
- (CGPoint)offsetPointWithInternalPoint:(CGPoint)internalPoint;
|
||||
|
||||
- (CGPoint)offsetPointWithExternalPoint:(CGPoint)externalPoint;
|
||||
|
||||
/**
|
||||
* @abstract draws the shadow for text in the provided CGContext
|
||||
* @discussion Call within the text node's +drawRect method
|
||||
*/
|
||||
- (void)setShadowInContext:(CGContextRef)context;
|
||||
|
||||
@end
|
||||
148
AsyncDisplayKit/TextKit/CKTextKitShadower.mm
Executable file
148
AsyncDisplayKit/TextKit/CKTextKitShadower.mm
Executable file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "CKTextKitShadower.h"
|
||||
|
||||
static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets)
|
||||
{
|
||||
return UIEdgeInsetsInsetRect({.size = size}, insets).size;
|
||||
}
|
||||
|
||||
static inline UIEdgeInsets _invertInsets(UIEdgeInsets insets)
|
||||
{
|
||||
return {
|
||||
.top = -insets.top,
|
||||
.left = -insets.left,
|
||||
.bottom = -insets.bottom,
|
||||
.right = -insets.right
|
||||
};
|
||||
}
|
||||
|
||||
@implementation CKTextKitShadower {
|
||||
UIEdgeInsets _calculatedShadowPadding;
|
||||
}
|
||||
|
||||
- (instancetype)initWithShadowOffset:(CGSize)shadowOffset
|
||||
shadowColor:(UIColor *)shadowColor
|
||||
shadowOpacity:(CGFloat)shadowOpacity
|
||||
shadowRadius:(CGFloat)shadowRadius
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_shadowOffset = shadowOffset;
|
||||
_shadowColor = shadowColor;
|
||||
_shadowOpacity = shadowOpacity;
|
||||
_shadowRadius = shadowRadius;
|
||||
_calculatedShadowPadding = UIEdgeInsetsMake(-INFINITY, -INFINITY, INFINITY, INFINITY);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
* This method is duplicated here because it gets called frequently, and we were
|
||||
* wasting valuable time constructing a state object to ask it.
|
||||
*/
|
||||
- (BOOL)_shouldDrawShadow
|
||||
{
|
||||
return _shadowOpacity != 0.0 && _shadowColor != nil && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero));
|
||||
}
|
||||
|
||||
- (void)setShadowInContext:(CGContextRef)context
|
||||
{
|
||||
if ([self _shouldDrawShadow]) {
|
||||
CGColorRef textShadowColor = CGColorRetain(_shadowColor.CGColor);
|
||||
CGSize textShadowOffset = _shadowOffset;
|
||||
CGFloat textShadowOpacity = _shadowOpacity;
|
||||
CGFloat textShadowRadius = _shadowRadius;
|
||||
|
||||
if (textShadowOpacity != 1.0) {
|
||||
CGFloat inherentAlpha = CGColorGetAlpha(textShadowColor);
|
||||
|
||||
CGColorRef oldTextShadowColor = textShadowColor;
|
||||
textShadowColor = CGColorCreateCopyWithAlpha(textShadowColor, inherentAlpha * textShadowOpacity);
|
||||
CGColorRelease(oldTextShadowColor);
|
||||
}
|
||||
|
||||
CGContextSetShadowWithColor(context, textShadowOffset, textShadowRadius, textShadowColor);
|
||||
|
||||
CGColorRelease(textShadowColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (UIEdgeInsets)shadowPadding
|
||||
{
|
||||
if (_calculatedShadowPadding.top == -INFINITY) {
|
||||
if (![self _shouldDrawShadow]) {
|
||||
return UIEdgeInsetsZero;
|
||||
}
|
||||
|
||||
UIEdgeInsets shadowPadding = UIEdgeInsetsZero;
|
||||
|
||||
// min values are expected to be negative for most typical shadowOffset and
|
||||
// blurRadius settings:
|
||||
shadowPadding.top = fminf(0.0f, _shadowOffset.height - _shadowRadius);
|
||||
shadowPadding.left = fminf(0.0f, _shadowOffset.width - _shadowRadius);
|
||||
|
||||
shadowPadding.bottom = fminf(0.0f, -_shadowOffset.height - _shadowRadius);
|
||||
shadowPadding.right = fminf(0.0f, -_shadowOffset.width - _shadowRadius);
|
||||
|
||||
_calculatedShadowPadding = shadowPadding;
|
||||
}
|
||||
|
||||
return _calculatedShadowPadding;
|
||||
}
|
||||
|
||||
- (CGSize)insetSizeWithConstrainedSize:(CGSize)constrainedSize
|
||||
{
|
||||
return _insetSize(constrainedSize, _invertInsets([self shadowPadding]));
|
||||
}
|
||||
|
||||
- (CGRect)insetRectWithConstrainedRect:(CGRect)constrainedRect
|
||||
{
|
||||
return UIEdgeInsetsInsetRect(constrainedRect, _invertInsets([self shadowPadding]));
|
||||
}
|
||||
|
||||
- (CGSize)outsetSizeWithInsetSize:(CGSize)insetSize
|
||||
{
|
||||
return _insetSize(insetSize, [self shadowPadding]);
|
||||
}
|
||||
|
||||
- (CGRect)outsetRectWithInsetRect:(CGRect)insetRect
|
||||
{
|
||||
return UIEdgeInsetsInsetRect(insetRect, [self shadowPadding]);
|
||||
}
|
||||
|
||||
- (CGRect)offsetRectWithInternalRect:(CGRect)internalRect
|
||||
{
|
||||
return (CGRect){
|
||||
.origin = [self offsetPointWithInternalPoint:internalRect.origin],
|
||||
.size = internalRect.size
|
||||
};
|
||||
}
|
||||
|
||||
- (CGPoint)offsetPointWithInternalPoint:(CGPoint)internalPoint
|
||||
{
|
||||
UIEdgeInsets shadowPadding = [self shadowPadding];
|
||||
return (CGPoint){
|
||||
internalPoint.x + shadowPadding.left,
|
||||
internalPoint.y + shadowPadding.top
|
||||
};
|
||||
}
|
||||
|
||||
- (CGPoint)offsetPointWithExternalPoint:(CGPoint)externalPoint
|
||||
{
|
||||
UIEdgeInsets shadowPadding = [self shadowPadding];
|
||||
return (CGPoint){
|
||||
externalPoint.x - shadowPadding.left,
|
||||
externalPoint.y - shadowPadding.top
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
17
AsyncDisplayKit/TextKit/CKTextKitTailTruncater.h
Executable file
17
AsyncDisplayKit/TextKit/CKTextKitTailTruncater.h
Executable file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "CKTextKitTruncating.h"
|
||||
|
||||
@interface CKTextKitTailTruncater : NSObject <CKTextKitTruncating>
|
||||
|
||||
@end
|
||||
190
AsyncDisplayKit/TextKit/CKTextKitTailTruncater.mm
Executable file
190
AsyncDisplayKit/TextKit/CKTextKitTailTruncater.mm
Executable file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import "ASAssert.h"
|
||||
|
||||
#import "CKTextKitContext.h"
|
||||
#import "CKTextKitTailTruncater.h"
|
||||
|
||||
@implementation CKTextKitTailTruncater
|
||||
{
|
||||
__weak CKTextKitContext *_context;
|
||||
NSAttributedString *_truncationAttributedString;
|
||||
NSCharacterSet *_avoidTailTruncationSet;
|
||||
CGSize _constrainedSize;
|
||||
}
|
||||
@synthesize visibleRanges = _visibleRanges;
|
||||
@synthesize truncationStringRect = _truncationStringRect;
|
||||
|
||||
- (instancetype)initWithContext:(CKTextKitContext *)context
|
||||
truncationAttributedString:(NSAttributedString *)truncationAttributedString
|
||||
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
|
||||
constrainedSize:(CGSize)constrainedSize
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_context = context;
|
||||
_truncationAttributedString = truncationAttributedString;
|
||||
_avoidTailTruncationSet = avoidTailTruncationSet;
|
||||
_constrainedSize = constrainedSize;
|
||||
|
||||
[self _truncate];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
Calculates the intersection of the truncation message within the end of the last line.
|
||||
*/
|
||||
- (NSUInteger)_calculateCharacterIndexBeforeTruncationMessage:(NSLayoutManager *)layoutManager
|
||||
textStorage:(NSTextStorage *)textStorage
|
||||
textContainer:(NSTextContainer *)textContainer
|
||||
{
|
||||
CGRect constrainedRect = (CGRect){ .size = textContainer.size };
|
||||
|
||||
NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:constrainedRect
|
||||
inTextContainer:textContainer];
|
||||
NSInteger lastVisibleGlyphIndex = (NSMaxRange(visibleGlyphRange) - 1);
|
||||
|
||||
if (lastVisibleGlyphIndex < 0) {
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
CGRect lastLineRect = [layoutManager lineFragmentRectForGlyphAtIndex:lastVisibleGlyphIndex
|
||||
effectiveRange:NULL];
|
||||
CGRect lastLineUsedRect = [layoutManager lineFragmentUsedRectForGlyphAtIndex:lastVisibleGlyphIndex
|
||||
effectiveRange:NULL];
|
||||
NSParagraphStyle *paragraphStyle = [textStorage attributesAtIndex:[layoutManager characterIndexForGlyphAtIndex:lastVisibleGlyphIndex]
|
||||
effectiveRange:NULL][NSParagraphStyleAttributeName];
|
||||
// We assume LTR so long as the writing direction is not
|
||||
BOOL rtlWritingDirection = paragraphStyle ? paragraphStyle.baseWritingDirection == NSWritingDirectionRightToLeft : NO;
|
||||
// We only want to treat the trunction rect as left-aligned in the case that we are right-aligned and our writing
|
||||
// direction is RTL.
|
||||
BOOL leftAligned = CGRectGetMinX(lastLineRect) == CGRectGetMinX(lastLineUsedRect) || !rtlWritingDirection;
|
||||
|
||||
// Calculate the bounding rectangle for the truncation message
|
||||
CKTextKitContext *truncationContext = [[CKTextKitContext alloc] initWithAttributedString:_truncationAttributedString
|
||||
lineBreakMode:NSLineBreakByWordWrapping
|
||||
maximumNumberOfLines:1
|
||||
constrainedSize:constrainedRect.size
|
||||
layoutManagerFactory:nil];
|
||||
|
||||
__block CGRect truncationUsedRect;
|
||||
|
||||
[truncationContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *truncationLayoutManager, NSTextStorage *truncationTextStorage, NSTextContainer *truncationTextContainer) {
|
||||
// Size the truncation message
|
||||
[truncationLayoutManager ensureLayoutForTextContainer:truncationTextContainer];
|
||||
NSRange truncationGlyphRange = [truncationLayoutManager glyphRangeForTextContainer:truncationTextContainer];
|
||||
truncationUsedRect = [truncationLayoutManager boundingRectForGlyphRange:truncationGlyphRange
|
||||
inTextContainer:truncationTextContainer];
|
||||
}];
|
||||
CGFloat truncationOriginX = (leftAligned ?
|
||||
CGRectGetMaxX(constrainedRect) - truncationUsedRect.size.width :
|
||||
CGRectGetMinX(constrainedRect));
|
||||
CGRect translatedTruncationRect = CGRectMake(truncationOriginX,
|
||||
CGRectGetMinY(lastLineRect),
|
||||
truncationUsedRect.size.width,
|
||||
truncationUsedRect.size.height);
|
||||
|
||||
// Determine which glyph is the first to be clipped / overlaps the truncation message.
|
||||
CGFloat truncationMessageX = (leftAligned ?
|
||||
CGRectGetMinX(translatedTruncationRect) :
|
||||
CGRectGetMaxX(translatedTruncationRect));
|
||||
CGPoint beginningOfTruncationMessage = CGPointMake(truncationMessageX,
|
||||
CGRectGetMidY(translatedTruncationRect));
|
||||
NSUInteger firstClippedGlyphIndex = [layoutManager glyphIndexForPoint:beginningOfTruncationMessage
|
||||
inTextContainer:textContainer
|
||||
fractionOfDistanceThroughGlyph:NULL];
|
||||
// If it didn't intersect with any text then it should just return the last visible character index, since the
|
||||
// truncation rect can fully fit on the line without clipping any other text.
|
||||
if (firstClippedGlyphIndex == NSNotFound) {
|
||||
return [layoutManager characterIndexForGlyphAtIndex:lastVisibleGlyphIndex];
|
||||
}
|
||||
NSUInteger firstCharacterIndexToReplace = [layoutManager characterIndexForGlyphAtIndex:firstClippedGlyphIndex];
|
||||
|
||||
// Break on word boundaries
|
||||
return [self _findTruncationInsertionPointAtOrBeforeCharacterIndex:firstCharacterIndexToReplace
|
||||
layoutManager:layoutManager
|
||||
textStorage:textStorage];
|
||||
}
|
||||
|
||||
/**
|
||||
Finds the first whitespace at or before the character index do we don't truncate in the middle of words
|
||||
If there are multiple whitespaces together (say a space and a newline), this will backtrack to the first one
|
||||
*/
|
||||
- (NSUInteger)_findTruncationInsertionPointAtOrBeforeCharacterIndex:(NSUInteger)firstCharacterIndexToReplace
|
||||
layoutManager:(NSLayoutManager *)layoutManager
|
||||
textStorage:(NSTextStorage *)textStorage
|
||||
{
|
||||
// Don't attempt to truncate beyond the end of the string
|
||||
if (firstCharacterIndexToReplace >= textStorage.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find the glyph range of the line fragment containing the first character to replace.
|
||||
NSRange lineGlyphRange;
|
||||
[layoutManager lineFragmentRectForGlyphAtIndex:[layoutManager glyphIndexForCharacterAtIndex:firstCharacterIndexToReplace]
|
||||
effectiveRange:&lineGlyphRange];
|
||||
|
||||
// Look for the first whitespace from the end of the line, starting from the truncation point
|
||||
NSUInteger startingSearchIndex = [layoutManager characterIndexForGlyphAtIndex:lineGlyphRange.location];
|
||||
NSUInteger endingSearchIndex = firstCharacterIndexToReplace;
|
||||
NSRange rangeToSearch = NSMakeRange(startingSearchIndex, (endingSearchIndex - startingSearchIndex));
|
||||
|
||||
NSRange rangeOfLastVisibleAvoidedChars = { .location = NSNotFound };
|
||||
if (_avoidTailTruncationSet) {
|
||||
rangeOfLastVisibleAvoidedChars = [textStorage.string rangeOfCharacterFromSet:_avoidTailTruncationSet
|
||||
options:NSBackwardsSearch
|
||||
range:rangeToSearch];
|
||||
}
|
||||
|
||||
// Couldn't find a good place to truncate. Might be because there is no whitespace in the text, or we're dealing
|
||||
// with a foreign language encoding. Settle for truncating at the original place, which may be mid-word.
|
||||
if (rangeOfLastVisibleAvoidedChars.location == NSNotFound) {
|
||||
return firstCharacterIndexToReplace;
|
||||
} else {
|
||||
return rangeOfLastVisibleAvoidedChars.location;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_truncate
|
||||
{
|
||||
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
NSUInteger originalStringLength = textStorage.length;
|
||||
|
||||
[layoutManager ensureLayoutForTextContainer:textContainer];
|
||||
|
||||
NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:{ .size = textContainer.size }
|
||||
inTextContainer:textContainer];
|
||||
NSRange visibleCharacterRange = [layoutManager characterRangeForGlyphRange:visibleGlyphRange
|
||||
actualGlyphRange:NULL];
|
||||
|
||||
// Check if text is truncated, and if so apply our truncation string
|
||||
if (visibleCharacterRange.length < originalStringLength && _truncationAttributedString.length > 0) {
|
||||
NSInteger firstCharacterIndexToReplace = [self _calculateCharacterIndexBeforeTruncationMessage:layoutManager
|
||||
textStorage:textStorage
|
||||
textContainer:textContainer];
|
||||
if (firstCharacterIndexToReplace == 0 || firstCharacterIndexToReplace == NSNotFound) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update/truncate the visible range of text
|
||||
visibleCharacterRange = NSMakeRange(0, firstCharacterIndexToReplace);
|
||||
NSRange truncationReplacementRange = NSMakeRange(firstCharacterIndexToReplace,
|
||||
textStorage.length - firstCharacterIndexToReplace);
|
||||
// Replace the end of the visible message with the truncation string
|
||||
[textStorage replaceCharactersInRange:truncationReplacementRange
|
||||
withAttributedString:_truncationAttributedString];
|
||||
}
|
||||
|
||||
_visibleRanges = { visibleCharacterRange };
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
37
AsyncDisplayKit/TextKit/CKTextKitTruncating.h
Executable file
37
AsyncDisplayKit/TextKit/CKTextKitTruncating.h
Executable file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <vector>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "CKTextKitRenderer.h"
|
||||
|
||||
@protocol CKTextKitTruncating <NSObject>
|
||||
|
||||
@property (nonatomic, assign, readonly) std::vector<NSRange> visibleRanges;
|
||||
@property (nonatomic, assign, readonly) CGRect truncationStringRect;
|
||||
|
||||
/**
|
||||
A truncater object is initialized with the full state of the text. It is a Single Responsibility Object that is
|
||||
mutative. It configures the state of the TextKit components (layout manager, text container, text storage) to achieve
|
||||
the intended truncation, then it stores the resulting state for later fetching.
|
||||
|
||||
The truncater may mutate the state of the text storage such that only the drawn string is actually present in the
|
||||
text storage itself.
|
||||
|
||||
The truncater should not store a strong reference to the context to prevent retain cycles.
|
||||
*/
|
||||
- (instancetype)initWithContext:(CKTextKitContext *)context
|
||||
truncationAttributedString:(NSAttributedString *)truncationAttributedString
|
||||
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
|
||||
constrainedSize:(CGSize)constrainedSize;
|
||||
|
||||
@end
|
||||
Loading…
x
Reference in New Issue
Block a user