mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-17 03:40:18 +00:00
Merge pull request #825 from levi/levi/text-renderer
Update ASTextNode renderer with the latest derivative of it developed for ComponentKit
This commit is contained in:
commit
771eca5083
@ -58,11 +58,6 @@
|
||||
058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E5195D050800B7D73C /* _ASDisplayView.mm */; };
|
||||
058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */; };
|
||||
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */; };
|
||||
058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */; };
|
||||
058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09ED195D050800B7D73C /* ASTextNodeRenderer.mm */; };
|
||||
058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EF195D050800B7D73C /* ASTextNodeShadower.m */; };
|
||||
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F1195D050800B7D73C /* ASTextNodeTextKitHelpers.mm */; };
|
||||
058D0A20195D050800B7D73C /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */; };
|
||||
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */; };
|
||||
058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F9195D050800B7D73C /* _ASAsyncTransaction.m */; };
|
||||
058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */; };
|
||||
@ -79,9 +74,7 @@
|
||||
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
|
||||
058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */; };
|
||||
058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */; };
|
||||
058D0A3D195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A33195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m */; };
|
||||
058D0A3E195D057000B7D73C /* ASTextNodeRendererTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A34195D057000B7D73C /* ASTextNodeRendererTests.m */; };
|
||||
058D0A3F195D057000B7D73C /* ASTextNodeShadowerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A35195D057000B7D73C /* ASTextNodeShadowerTests.m */; };
|
||||
058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */; };
|
||||
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A36195D057000B7D73C /* ASTextNodeTests.m */; };
|
||||
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */; };
|
||||
058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D5195D050800B7D73C /* ASControlNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@ -95,12 +88,6 @@
|
||||
058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E4195D050800B7D73C /* _ASDisplayView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A59195D05DC00B7D73C /* ASMutableAttributedStringBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A5B195D05DC00B7D73C /* ASTextNodeCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A5D195D05DC00B7D73C /* ASTextNodeRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A5F195D05DC00B7D73C /* ASTextNodeShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EE195D050800B7D73C /* ASTextNodeShadower.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A61195D05DC00B7D73C /* ASTextNodeTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F0195D050800B7D73C /* ASTextNodeTextKitHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A63195D05DC00B7D73C /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F2195D050800B7D73C /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A64195D05DC00B7D73C /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
058D0A6A195D05EC00B7D73C /* _ASAsyncTransactionContainer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@ -145,6 +132,64 @@
|
||||
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 */; };
|
||||
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */; };
|
||||
254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */; };
|
||||
254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; };
|
||||
254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; };
|
||||
254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */; };
|
||||
254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; };
|
||||
254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; };
|
||||
254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; };
|
||||
254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; };
|
||||
254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */; };
|
||||
254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */; };
|
||||
254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */; };
|
||||
254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; };
|
||||
254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; };
|
||||
254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; };
|
||||
254C6B801BF94DF4003EC431 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */; };
|
||||
254C6B821BF94F8A003EC431 /* ASTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */; };
|
||||
254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; };
|
||||
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; };
|
||||
254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; };
|
||||
254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754971BEE44CD00737CA5 /* ASTextKitContext.mm */; };
|
||||
254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */; };
|
||||
254C6B881BF94F8A003EC431 /* ASTextKitRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */; };
|
||||
254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */; };
|
||||
254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; };
|
||||
254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; };
|
||||
254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; };
|
||||
254C6B8D1BF94F8A003EC431 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */; };
|
||||
257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */; };
|
||||
257754A51BEE44CD00737CA5 /* ASTextKitRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; };
|
||||
257754A71BEE44CD00737CA5 /* ASTextKitAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754971BEE44CD00737CA5 /* ASTextKitContext.mm */; };
|
||||
257754AA1BEE44CD00737CA5 /* ASTextKitEntityAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */; };
|
||||
257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */; };
|
||||
257754AD1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */; };
|
||||
257754AF1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; };
|
||||
257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; };
|
||||
257754B31BEE44CD00737CA5 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; };
|
||||
257754B51BEE44CD00737CA5 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */; };
|
||||
257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; };
|
||||
257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754C11BEE458E00737CA5 /* ASTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; };
|
||||
258FF4271C0D152600A83844 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */; };
|
||||
258FF4281C0D152600A83844 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */; };
|
||||
25A977EF1C0D2A5500406B62 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */; };
|
||||
25BAA16F1C0D18D2002747C7 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.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 */; };
|
||||
@ -351,17 +396,6 @@
|
||||
B350622B1B010EFD0018CF92 /* ASRangeHandlerRender.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B350622C1B010EFD0018CF92 /* ASRangeHandlerRender.mm in Sources */ = {isa = PBXBuildFile; fileRef = 292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */; };
|
||||
B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B350622E1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B350622F1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */; };
|
||||
B35062301B010EFD0018CF92 /* ASTextNodeRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B35062311B010EFD0018CF92 /* ASTextNodeRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09ED195D050800B7D73C /* ASTextNodeRenderer.mm */; };
|
||||
B35062321B010EFD0018CF92 /* ASTextNodeShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EE195D050800B7D73C /* ASTextNodeShadower.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B35062331B010EFD0018CF92 /* ASTextNodeShadower.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EF195D050800B7D73C /* ASTextNodeShadower.m */; };
|
||||
B35062341B010EFD0018CF92 /* ASTextNodeTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F0195D050800B7D73C /* ASTextNodeTextKitHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B35062351B010EFD0018CF92 /* ASTextNodeTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F1195D050800B7D73C /* ASTextNodeTextKitHelpers.mm */; };
|
||||
B35062361B010EFD0018CF92 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F2195D050800B7D73C /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B35062371B010EFD0018CF92 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B35062381B010EFD0018CF92 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */; };
|
||||
B35062391B010EFD0018CF92 /* ASThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */; };
|
||||
@ -407,10 +441,6 @@
|
||||
D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; };
|
||||
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
|
||||
DE6D9E321C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6D9E301C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h */; };
|
||||
DE6D9E331C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6D9E301C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h */; };
|
||||
DE6D9E341C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE6D9E311C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm */; };
|
||||
DE6D9E351C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE6D9E311C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm */; };
|
||||
DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; };
|
||||
@ -507,17 +537,6 @@
|
||||
058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASHighlightOverlayLayer.mm; sourceTree = "<group>"; };
|
||||
058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMutableAttributedStringBuilder.h; sourceTree = "<group>"; };
|
||||
058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMutableAttributedStringBuilder.m; sourceTree = "<group>"; };
|
||||
058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeCoreTextAdditions.h; sourceTree = "<group>"; };
|
||||
058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeCoreTextAdditions.m; sourceTree = "<group>"; };
|
||||
058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeRenderer.h; sourceTree = "<group>"; };
|
||||
058D09ED195D050800B7D73C /* ASTextNodeRenderer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeRenderer.mm; sourceTree = "<group>"; };
|
||||
058D09EE195D050800B7D73C /* ASTextNodeShadower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeShadower.h; sourceTree = "<group>"; };
|
||||
058D09EF195D050800B7D73C /* ASTextNodeShadower.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeShadower.m; sourceTree = "<group>"; };
|
||||
058D09F0195D050800B7D73C /* ASTextNodeTextKitHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeTextKitHelpers.h; sourceTree = "<group>"; };
|
||||
058D09F1195D050800B7D73C /* ASTextNodeTextKitHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeTextKitHelpers.mm; sourceTree = "<group>"; };
|
||||
058D09F2195D050800B7D73C /* ASTextNodeTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeTypes.h; sourceTree = "<group>"; };
|
||||
058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeWordKerner.h; sourceTree = "<group>"; };
|
||||
058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeWordKerner.m; sourceTree = "<group>"; };
|
||||
058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableAttributedString+TextKitAdditions.h"; sourceTree = "<group>"; };
|
||||
058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableAttributedString+TextKitAdditions.m"; sourceTree = "<group>"; };
|
||||
058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransaction.h; sourceTree = "<group>"; };
|
||||
@ -528,7 +547,6 @@
|
||||
058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransactionGroup.h; sourceTree = "<group>"; };
|
||||
058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASAsyncTransactionGroup.m; sourceTree = "<group>"; };
|
||||
058D09FF195D050800B7D73C /* UIView+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+ASConvenience.h"; sourceTree = "<group>"; };
|
||||
058D0A00195D050800B7D73C /* UIView+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+ASConvenience.m"; sourceTree = "<group>"; };
|
||||
058D0A02195D050800B7D73C /* _AS-objc-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "_AS-objc-internal.h"; sourceTree = "<group>"; };
|
||||
058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCoreAnimationExtras.h; sourceTree = "<group>"; };
|
||||
058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASCoreAnimationExtras.mm; sourceTree = "<group>"; };
|
||||
@ -551,9 +569,7 @@
|
||||
058D0A30195D057000B7D73C /* ASDisplayNodeTestsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeTestsHelper.h; sourceTree = "<group>"; };
|
||||
058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeTestsHelper.m; sourceTree = "<group>"; };
|
||||
058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMutableAttributedStringBuilderTests.m; sourceTree = "<group>"; };
|
||||
058D0A33195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeCoreTextAdditionsTests.m; sourceTree = "<group>"; };
|
||||
058D0A34195D057000B7D73C /* ASTextNodeRendererTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeRendererTests.m; sourceTree = "<group>"; };
|
||||
058D0A35195D057000B7D73C /* ASTextNodeShadowerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeShadowerTests.m; sourceTree = "<group>"; };
|
||||
058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextKitCoreTextAdditionsTests.m; sourceTree = "<group>"; };
|
||||
058D0A36195D057000B7D73C /* ASTextNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeTests.m; sourceTree = "<group>"; };
|
||||
058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeWordKernerTests.mm; sourceTree = "<group>"; };
|
||||
058D0A43195D058D00B7D73C /* ASAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAssert.h; sourceTree = "<group>"; };
|
||||
@ -583,6 +599,36 @@
|
||||
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>"; };
|
||||
254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTruncationTests.mm; sourceTree = "<group>"; };
|
||||
254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTests.mm; sourceTree = "<group>"; };
|
||||
2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEqualityHashHelpers.mm; sourceTree = "<group>"; };
|
||||
257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitRenderer.h; path = TextKit/ASTextKitRenderer.h; sourceTree = "<group>"; };
|
||||
257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitAttributes.mm; path = TextKit/ASTextKitAttributes.mm; sourceTree = "<group>"; };
|
||||
257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitAttributes.h; path = TextKit/ASTextKitAttributes.h; sourceTree = "<group>"; };
|
||||
257754961BEE44CD00737CA5 /* ASTextKitContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitContext.h; path = TextKit/ASTextKitContext.h; sourceTree = "<group>"; };
|
||||
257754971BEE44CD00737CA5 /* ASTextKitContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitContext.mm; path = TextKit/ASTextKitContext.mm; sourceTree = "<group>"; };
|
||||
257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitEntityAttribute.h; path = TextKit/ASTextKitEntityAttribute.h; sourceTree = "<group>"; };
|
||||
257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitEntityAttribute.m; path = TextKit/ASTextKitEntityAttribute.m; sourceTree = "<group>"; };
|
||||
2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitRenderer.mm; path = TextKit/ASTextKitRenderer.mm; sourceTree = "<group>"; };
|
||||
2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ASTextKitRenderer+Positioning.h"; path = "TextKit/ASTextKitRenderer+Positioning.h"; sourceTree = "<group>"; };
|
||||
2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "ASTextKitRenderer+Positioning.mm"; path = "TextKit/ASTextKitRenderer+Positioning.mm"; sourceTree = "<group>"; };
|
||||
2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ASTextKitRenderer+TextChecking.h"; path = "TextKit/ASTextKitRenderer+TextChecking.h"; sourceTree = "<group>"; };
|
||||
2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "ASTextKitRenderer+TextChecking.mm"; path = "TextKit/ASTextKitRenderer+TextChecking.mm"; sourceTree = "<group>"; };
|
||||
2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitShadower.h; path = TextKit/ASTextKitShadower.h; sourceTree = "<group>"; };
|
||||
257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitShadower.mm; path = TextKit/ASTextKitShadower.mm; sourceTree = "<group>"; };
|
||||
257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTailTruncater.h; path = TextKit/ASTextKitTailTruncater.h; sourceTree = "<group>"; };
|
||||
257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitTailTruncater.mm; path = TextKit/ASTextKitTailTruncater.mm; sourceTree = "<group>"; };
|
||||
257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTruncating.h; path = TextKit/ASTextKitTruncating.h; sourceTree = "<group>"; };
|
||||
257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASEqualityHashHelpers.h; path = TextKit/ASEqualityHashHelpers.h; sourceTree = "<group>"; };
|
||||
257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitHelpers.mm; path = TextKit/ASTextKitHelpers.mm; sourceTree = "<group>"; };
|
||||
257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitCoreTextAdditions.m; path = TextKit/ASTextKitCoreTextAdditions.m; sourceTree = "<group>"; };
|
||||
257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeWordKerner.h; path = TextKit/ASTextNodeWordKerner.h; sourceTree = "<group>"; };
|
||||
257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitHelpers.h; path = TextKit/ASTextKitHelpers.h; sourceTree = "<group>"; };
|
||||
257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitCoreTextAdditions.h; path = TextKit/ASTextKitCoreTextAdditions.h; sourceTree = "<group>"; };
|
||||
257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeTypes.h; path = TextKit/ASTextNodeTypes.h; sourceTree = "<group>"; };
|
||||
257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextNodeWordKerner.m; path = TextKit/ASTextNodeWordKerner.m; sourceTree = "<group>"; };
|
||||
258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerVisible.h; sourceTree = "<group>"; };
|
||||
258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerVisible.mm; 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>"; };
|
||||
@ -604,7 +650,7 @@
|
||||
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = "<group>"; };
|
||||
4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = "<group>"; };
|
||||
4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = "<group>"; };
|
||||
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
|
||||
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
|
||||
9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = "<group>"; };
|
||||
9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAsciiArtBoxCreator.h; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h; sourceTree = "<group>"; };
|
||||
9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASAsciiArtBoxCreator.m; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m; sourceTree = "<group>"; };
|
||||
@ -679,8 +725,6 @@
|
||||
D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
|
||||
D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = "<group>"; };
|
||||
DE6D9E301C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerVisible.h; sourceTree = "<group>"; };
|
||||
DE6D9E311C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerVisible.mm; sourceTree = "<group>"; };
|
||||
DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = "<group>"; };
|
||||
DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = "<group>"; };
|
||||
EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@ -836,6 +880,7 @@
|
||||
058D09E1195D050800B7D73C /* Details */,
|
||||
058D0A01195D050800B7D73C /* Private */,
|
||||
AC6456051B0A333200CF11B8 /* Layout */,
|
||||
257754661BED245B00737CA5 /* TextKit */,
|
||||
058D09B2195D04C000B7D73C /* Supporting Files */,
|
||||
);
|
||||
path = AsyncDisplayKit;
|
||||
@ -881,9 +926,9 @@
|
||||
052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */,
|
||||
058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */,
|
||||
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */,
|
||||
058D0A33195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m */,
|
||||
058D0A34195D057000B7D73C /* ASTextNodeRendererTests.m */,
|
||||
058D0A35195D057000B7D73C /* ASTextNodeShadowerTests.m */,
|
||||
058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */,
|
||||
254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */,
|
||||
254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */,
|
||||
058D0A36195D057000B7D73C /* ASTextNodeTests.m */,
|
||||
058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */,
|
||||
058D09C6195D04C000B7D73C /* Supporting Files */,
|
||||
@ -944,25 +989,14 @@
|
||||
055F1A3619ABD413004DAFF1 /* ASRangeController.h */,
|
||||
055F1A3719ABD413004DAFF1 /* ASRangeController.mm */,
|
||||
292C599C1A956527007E5DD6 /* ASRangeHandler.h */,
|
||||
DE6D9E301C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h */,
|
||||
DE6D9E311C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm */,
|
||||
258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */,
|
||||
258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */,
|
||||
292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */,
|
||||
292C599B1A956527007E5DD6 /* ASRangeHandlerPreload.mm */,
|
||||
292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */,
|
||||
292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */,
|
||||
296A0A311A951715005ACEAA /* ASScrollDirection.h */,
|
||||
205F0E111B371BD7007741D0 /* ASScrollDirection.m */,
|
||||
058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */,
|
||||
058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */,
|
||||
058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */,
|
||||
058D09ED195D050800B7D73C /* ASTextNodeRenderer.mm */,
|
||||
058D09EE195D050800B7D73C /* ASTextNodeShadower.h */,
|
||||
058D09EF195D050800B7D73C /* ASTextNodeShadower.m */,
|
||||
058D09F0195D050800B7D73C /* ASTextNodeTextKitHelpers.h */,
|
||||
058D09F1195D050800B7D73C /* ASTextNodeTextKitHelpers.mm */,
|
||||
058D09F2195D050800B7D73C /* ASTextNodeTypes.h */,
|
||||
058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */,
|
||||
058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */,
|
||||
058D0A12195D050800B7D73C /* ASThread.h */,
|
||||
205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */,
|
||||
205F0E201B376416007741D0 /* CGRect+ASConvenience.m */,
|
||||
@ -972,7 +1006,6 @@
|
||||
205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */,
|
||||
205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */,
|
||||
058D09FF195D050800B7D73C /* UIView+ASConvenience.h */,
|
||||
058D0A00195D050800B7D73C /* UIView+ASConvenience.m */,
|
||||
);
|
||||
path = Details;
|
||||
sourceTree = "<group>";
|
||||
@ -1044,6 +1077,39 @@
|
||||
path = Base;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
257754661BED245B00737CA5 /* TextKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */,
|
||||
257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */,
|
||||
257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */,
|
||||
257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */,
|
||||
257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */,
|
||||
257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */,
|
||||
257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */,
|
||||
257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */,
|
||||
257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */,
|
||||
257754961BEE44CD00737CA5 /* ASTextKitContext.h */,
|
||||
257754971BEE44CD00737CA5 /* ASTextKitContext.mm */,
|
||||
257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */,
|
||||
257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */,
|
||||
257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */,
|
||||
2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */,
|
||||
2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */,
|
||||
2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */,
|
||||
2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */,
|
||||
2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */,
|
||||
2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */,
|
||||
257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */,
|
||||
257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */,
|
||||
257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */,
|
||||
257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */,
|
||||
257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */,
|
||||
2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */,
|
||||
);
|
||||
name = TextKit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AC6456051B0A333200CF11B8 /* Layout */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1117,6 +1183,7 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */,
|
||||
AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */,
|
||||
058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */,
|
||||
058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */,
|
||||
@ -1129,6 +1196,7 @@
|
||||
058D0A74195D05F800B7D73C /* _ASPendingState.h in Headers */,
|
||||
9C5586691BD549CB00B50E3A /* ASAsciiArtBoxCreator.h in Headers */,
|
||||
058D0A76195D05F900B7D73C /* _ASScopeTimer.h in Headers */,
|
||||
257754B31BEE44CD00737CA5 /* ASTextKitTailTruncater.h in Headers */,
|
||||
205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */,
|
||||
058D0A82195D060300B7D73C /* ASAssert.h in Headers */,
|
||||
0516FA3C1A15563400B4EBED /* ASAvailability.h in Headers */,
|
||||
@ -1142,32 +1210,37 @@
|
||||
055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */,
|
||||
ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */,
|
||||
18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
|
||||
257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */,
|
||||
AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */,
|
||||
205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */,
|
||||
AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */,
|
||||
058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */,
|
||||
058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */,
|
||||
257754A51BEE44CD00737CA5 /* ASTextKitRenderer.h in Headers */,
|
||||
464052201A3F83C40061C0BA /* ASDataController.h in Headers */,
|
||||
05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */,
|
||||
ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */,
|
||||
058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */,
|
||||
DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */,
|
||||
058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */,
|
||||
258FF4271C0D152600A83844 /* ASRangeHandlerVisible.h in Headers */,
|
||||
058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */,
|
||||
058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */,
|
||||
AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */,
|
||||
058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */,
|
||||
257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */,
|
||||
058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */,
|
||||
0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */,
|
||||
1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */,
|
||||
257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */,
|
||||
464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */,
|
||||
257754AF1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h in Headers */,
|
||||
058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */,
|
||||
058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */,
|
||||
058D0A4F195D05CB00B7D73C /* ASImageNode.h in Headers */,
|
||||
05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */,
|
||||
430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */,
|
||||
ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */,
|
||||
DE6D9E321C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h in Headers */,
|
||||
ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */,
|
||||
ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */,
|
||||
251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */,
|
||||
@ -1178,10 +1251,12 @@
|
||||
9C5FA3511B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */,
|
||||
9C65A72A1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */,
|
||||
292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */,
|
||||
257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */,
|
||||
ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */,
|
||||
ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */,
|
||||
AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */,
|
||||
0516FA3D1A15563400B4EBED /* ASLog.h in Headers */,
|
||||
257754AA1BEE44CD00737CA5 /* ASTextKitEntityAttribute.h in Headers */,
|
||||
0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */,
|
||||
0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */,
|
||||
058D0A59195D05DC00B7D73C /* ASMutableAttributedStringBuilder.h in Headers */,
|
||||
@ -1197,28 +1272,27 @@
|
||||
D785F6621A74327E00291744 /* ASScrollNode.h in Headers */,
|
||||
058D0A7F195D05F900B7D73C /* ASSentinel.h in Headers */,
|
||||
9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */,
|
||||
257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */,
|
||||
9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */,
|
||||
AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */,
|
||||
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
|
||||
ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */,
|
||||
ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */,
|
||||
257754B51BEE44CD00737CA5 /* ASTextKitTruncating.h in Headers */,
|
||||
ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */,
|
||||
257754A71BEE44CD00737CA5 /* ASTextKitAttributes.h in Headers */,
|
||||
ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */,
|
||||
9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */,
|
||||
ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */,
|
||||
055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */,
|
||||
251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */,
|
||||
257754C11BEE458E00737CA5 /* ASTextKitHelpers.h in Headers */,
|
||||
0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */,
|
||||
058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */,
|
||||
058D0A5B195D05DC00B7D73C /* ASTextNodeCoreTextAdditions.h in Headers */,
|
||||
058D0A5D195D05DC00B7D73C /* ASTextNodeRenderer.h in Headers */,
|
||||
058D0A5F195D05DC00B7D73C /* ASTextNodeShadower.h in Headers */,
|
||||
058D0A61195D05DC00B7D73C /* ASTextNodeTextKitHelpers.h in Headers */,
|
||||
058D0A63195D05DC00B7D73C /* ASTextNodeTypes.h in Headers */,
|
||||
058D0A64195D05DC00B7D73C /* ASTextNodeWordKerner.h in Headers */,
|
||||
058D0A81195D05F900B7D73C /* ASThread.h in Headers */,
|
||||
ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */,
|
||||
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */,
|
||||
257754AD1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h in Headers */,
|
||||
205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */,
|
||||
058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */,
|
||||
205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */,
|
||||
@ -1231,10 +1305,12 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */,
|
||||
25BAA16F1C0D18D2002747C7 /* ASRangeHandlerVisible.h in Headers */,
|
||||
B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */,
|
||||
B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */,
|
||||
B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */,
|
||||
B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */,
|
||||
254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */,
|
||||
B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */,
|
||||
B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */,
|
||||
B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */,
|
||||
@ -1242,9 +1318,14 @@
|
||||
B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */,
|
||||
9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */,
|
||||
B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */,
|
||||
254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */,
|
||||
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */,
|
||||
B35062571B010F070018CF92 /* ASAssert.h in Headers */,
|
||||
254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */,
|
||||
B35062581B010F070018CF92 /* ASAvailability.h in Headers */,
|
||||
254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */,
|
||||
254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */,
|
||||
254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */,
|
||||
34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */,
|
||||
B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */,
|
||||
B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */,
|
||||
@ -1256,6 +1337,7 @@
|
||||
34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */,
|
||||
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
|
||||
B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */,
|
||||
254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */,
|
||||
509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */,
|
||||
B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */,
|
||||
B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */,
|
||||
@ -1263,7 +1345,7 @@
|
||||
B35062171B010EFD0018CF92 /* ASDataController.h in Headers */,
|
||||
B35062191B010EFD0018CF92 /* ASDealloc2MainObject.h in Headers */,
|
||||
34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */,
|
||||
DE6D9E331C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h in Headers */,
|
||||
254C6B801BF94DF4003EC431 /* ASEqualityHashHelpers.h in Headers */,
|
||||
B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */,
|
||||
B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */,
|
||||
B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */,
|
||||
@ -1277,6 +1359,7 @@
|
||||
C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */,
|
||||
AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */,
|
||||
B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */,
|
||||
254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */,
|
||||
B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */,
|
||||
B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */,
|
||||
430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */,
|
||||
@ -1304,13 +1387,17 @@
|
||||
B350622B1B010EFD0018CF92 /* ASRangeHandlerRender.h in Headers */,
|
||||
34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */,
|
||||
34EFC7651B701CCC00AD841F /* ASRelativeSize.h in Headers */,
|
||||
254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */,
|
||||
B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */,
|
||||
254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */,
|
||||
B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */,
|
||||
B35062551B010EFD0018CF92 /* ASSentinel.h in Headers */,
|
||||
9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */,
|
||||
9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */,
|
||||
34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */,
|
||||
254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */,
|
||||
CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
|
||||
254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */,
|
||||
34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */,
|
||||
2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */,
|
||||
044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */,
|
||||
@ -1318,15 +1405,10 @@
|
||||
34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */,
|
||||
9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */,
|
||||
34EFC7731B701D0700AD841F /* ASStaticLayoutSpec.h in Headers */,
|
||||
254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */,
|
||||
B350620A1B010EFD0018CF92 /* ASTableView.h in Headers */,
|
||||
B350620C1B010EFD0018CF92 /* ASTableViewProtocols.h in Headers */,
|
||||
B350620D1B010EFD0018CF92 /* ASTextNode.h in Headers */,
|
||||
B350622E1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.h in Headers */,
|
||||
B35062301B010EFD0018CF92 /* ASTextNodeRenderer.h in Headers */,
|
||||
B35062321B010EFD0018CF92 /* ASTextNodeShadower.h in Headers */,
|
||||
B35062341B010EFD0018CF92 /* ASTextNodeTextKitHelpers.h in Headers */,
|
||||
B35062361B010EFD0018CF92 /* ASTextNodeTypes.h in Headers */,
|
||||
B35062371B010EFD0018CF92 /* ASTextNodeWordKerner.h in Headers */,
|
||||
B35062391B010EFD0018CF92 /* ASThread.h in Headers */,
|
||||
2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */,
|
||||
509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */,
|
||||
@ -1532,7 +1614,9 @@
|
||||
058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */,
|
||||
058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */,
|
||||
058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */,
|
||||
257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */,
|
||||
AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */,
|
||||
257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */,
|
||||
058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */,
|
||||
058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */,
|
||||
9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */,
|
||||
@ -1557,6 +1641,7 @@
|
||||
058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */,
|
||||
0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */,
|
||||
464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */,
|
||||
257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */,
|
||||
058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */,
|
||||
058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */,
|
||||
058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */,
|
||||
@ -1568,39 +1653,43 @@
|
||||
9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
|
||||
251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */,
|
||||
ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */,
|
||||
257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */,
|
||||
0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */,
|
||||
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
|
||||
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
|
||||
055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */,
|
||||
ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */,
|
||||
0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */,
|
||||
257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */,
|
||||
257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */,
|
||||
055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */,
|
||||
044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */,
|
||||
292C59A11A956527007E5DD6 /* ASRangeHandlerPreload.mm in Sources */,
|
||||
292C59A41A956527007E5DD6 /* ASRangeHandlerRender.mm in Sources */,
|
||||
257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */,
|
||||
ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */,
|
||||
AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */,
|
||||
205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */,
|
||||
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */,
|
||||
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
|
||||
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
|
||||
258FF4281C0D152600A83844 /* ASRangeHandlerVisible.mm in Sources */,
|
||||
251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */,
|
||||
ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */,
|
||||
257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */,
|
||||
257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */,
|
||||
ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */,
|
||||
ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */,
|
||||
257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */,
|
||||
ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */,
|
||||
AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */,
|
||||
055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */,
|
||||
058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */,
|
||||
058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */,
|
||||
DE6D9E341C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm in Sources */,
|
||||
058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */,
|
||||
058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */,
|
||||
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */,
|
||||
058D0A20195D050800B7D73C /* ASTextNodeWordKerner.m in Sources */,
|
||||
257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */,
|
||||
ACC945AB1BA9E7C1005E1FB8 /* ASViewController.m in Sources */,
|
||||
B0F8805B1BEAEC7500D17647 /* ASTableNode.m in Sources */,
|
||||
205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */,
|
||||
257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */,
|
||||
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
|
||||
205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */,
|
||||
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */,
|
||||
@ -1632,12 +1721,12 @@
|
||||
058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */,
|
||||
ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */,
|
||||
ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */,
|
||||
254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */,
|
||||
05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */,
|
||||
ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */,
|
||||
3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */,
|
||||
058D0A3D195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m in Sources */,
|
||||
058D0A3E195D057000B7D73C /* ASTextNodeRendererTests.m in Sources */,
|
||||
058D0A3F195D057000B7D73C /* ASTextNodeShadowerTests.m in Sources */,
|
||||
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */,
|
||||
058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */,
|
||||
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */,
|
||||
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */,
|
||||
);
|
||||
@ -1660,6 +1749,7 @@
|
||||
B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */,
|
||||
B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */,
|
||||
509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */,
|
||||
254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */,
|
||||
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
|
||||
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
|
||||
B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */,
|
||||
@ -1674,14 +1764,17 @@
|
||||
34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */,
|
||||
B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */,
|
||||
B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */,
|
||||
254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */,
|
||||
B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */,
|
||||
B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */,
|
||||
B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */,
|
||||
B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */,
|
||||
254C6B881BF94F8A003EC431 /* ASTextKitRenderer.mm in Sources */,
|
||||
B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */,
|
||||
B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */,
|
||||
B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */,
|
||||
B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */,
|
||||
254C6B821BF94F8A003EC431 /* ASTextKitHelpers.mm in Sources */,
|
||||
430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */,
|
||||
34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */,
|
||||
34EFC75E1B701BF000AD841F /* ASInternalHelpers.mm in Sources */,
|
||||
@ -1689,7 +1782,9 @@
|
||||
9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */,
|
||||
9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
|
||||
DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
|
||||
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */,
|
||||
34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */,
|
||||
254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */,
|
||||
B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */,
|
||||
B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */,
|
||||
B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */,
|
||||
@ -1700,8 +1795,11 @@
|
||||
B350622A1B010EFD0018CF92 /* ASRangeHandlerPreload.mm in Sources */,
|
||||
B350622C1B010EFD0018CF92 /* ASRangeHandlerRender.mm in Sources */,
|
||||
34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */,
|
||||
254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */,
|
||||
34EFC7661B701CD200AD841F /* ASRelativeSize.mm in Sources */,
|
||||
254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */,
|
||||
509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */,
|
||||
25A977EF1C0D2A5500406B62 /* ASRangeHandlerVisible.mm in Sources */,
|
||||
B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */,
|
||||
B35062561B010EFD0018CF92 /* ASSentinel.m in Sources */,
|
||||
9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
|
||||
@ -1712,17 +1810,15 @@
|
||||
34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */,
|
||||
B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */,
|
||||
B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */,
|
||||
DE6D9E351C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm in Sources */,
|
||||
B350622F1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.m in Sources */,
|
||||
B35062311B010EFD0018CF92 /* ASTextNodeRenderer.mm in Sources */,
|
||||
B35062331B010EFD0018CF92 /* ASTextNodeShadower.m in Sources */,
|
||||
B35062351B010EFD0018CF92 /* ASTextNodeTextKitHelpers.mm in Sources */,
|
||||
B35062381B010EFD0018CF92 /* ASTextNodeWordKerner.m in Sources */,
|
||||
C78F7E2A1BF7808300CDEAFC /* ASTableNode.m in Sources */,
|
||||
509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */,
|
||||
254C6B8D1BF94F8A003EC431 /* ASEqualityHashHelpers.mm in Sources */,
|
||||
254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */,
|
||||
34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */,
|
||||
254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */,
|
||||
B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
|
||||
044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */,
|
||||
254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1866,6 +1962,7 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
DSTROOT = /tmp/AsyncDisplayKit.dst;
|
||||
GCC_INPUT_FILETYPE = automatic;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
@ -1883,6 +1980,7 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
DSTROOT = /tmp/AsyncDisplayKit.dst;
|
||||
GCC_INPUT_FILETYPE = automatic;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASEqualityHelpers.h"
|
||||
#import "ASTextNodeTextKitHelpers.h"
|
||||
#import "ASTextKitHelpers.h"
|
||||
#import "ASTextNodeWordKerner.h"
|
||||
#import "ASThread.h"
|
||||
|
||||
|
||||
26
AsyncDisplayKit/ASEqualityHashHelpers.mm
Normal file
26
AsyncDisplayKit/ASEqualityHashHelpers.mm
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 "ASEqualityHashHelpers.h"
|
||||
|
||||
#import <functional>
|
||||
#import <objc/runtime.h>
|
||||
#import <stdio.h>
|
||||
#import <string>
|
||||
|
||||
NSUInteger ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count)
|
||||
{
|
||||
uint64_t result = subhashes[0];
|
||||
for (int ii = 1; ii < count; ++ii) {
|
||||
result = ASHashCombine(result, subhashes[ii]);
|
||||
}
|
||||
return ASHash64ToNative(result);
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
|
||||
@abstract The maximum number of lines to render of the text before truncation.
|
||||
@default 0 (No limit)
|
||||
*/
|
||||
@property (nonatomic, assign) NSUInteger maximumLineCount;
|
||||
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
|
||||
|
||||
/**
|
||||
@abstract The number of lines in the text. Text must have been sized first.
|
||||
|
||||
@ -13,13 +13,15 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeCoreTextAdditions.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
|
||||
#import "ASTextKitCoreTextAdditions.h"
|
||||
#import "ASTextKitHelpers.h"
|
||||
#import "ASTextKitRenderer.h"
|
||||
#import "ASTextKitRenderer+Positioning.h"
|
||||
#import "ASTextKitShadower.h"
|
||||
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASTextNodeRenderer.h"
|
||||
#import "ASTextNodeShadower.h"
|
||||
#import "ASEqualityHelpers.h"
|
||||
|
||||
static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15;
|
||||
@ -30,14 +32,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
|
||||
@interface ASTextNodeDrawParameters : NSObject
|
||||
|
||||
- (instancetype)initWithRenderer:(ASTextNodeRenderer *)renderer
|
||||
shadower:(ASTextNodeShadower *)shadower
|
||||
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
|
||||
textOrigin:(CGPoint)textOrigin
|
||||
backgroundColor:(CGColorRef)backgroundColor;
|
||||
|
||||
@property (nonatomic, strong, readonly) ASTextNodeRenderer *renderer;
|
||||
|
||||
@property (nonatomic, strong, readonly) ASTextNodeShadower *shadower;
|
||||
@property (nonatomic, strong, readonly) ASTextKitRenderer *renderer;
|
||||
|
||||
@property (nonatomic, assign, readonly) CGPoint textOrigin;
|
||||
|
||||
@ -47,14 +46,12 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
|
||||
@implementation ASTextNodeDrawParameters
|
||||
|
||||
- (instancetype)initWithRenderer:(ASTextNodeRenderer *)renderer
|
||||
shadower:(ASTextNodeShadower *)shadower
|
||||
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
|
||||
textOrigin:(CGPoint)textOrigin
|
||||
backgroundColor:(CGColorRef)backgroundColor
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_renderer = renderer;
|
||||
_shadower = shadower;
|
||||
_textOrigin = textOrigin;
|
||||
_backgroundColor = CGColorRetain(backgroundColor);
|
||||
}
|
||||
@ -91,8 +88,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
|
||||
CGSize _constrainedSize;
|
||||
|
||||
ASTextNodeRenderer *_renderer;
|
||||
ASTextNodeShadower *_shadower;
|
||||
ASTextKitRenderer *_renderer;
|
||||
|
||||
UILongPressGestureRecognizer *_longPressGestureRecognizer;
|
||||
}
|
||||
@ -117,7 +113,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
self.needsDisplayOnBoundsChange = YES;
|
||||
|
||||
_truncationMode = NSLineBreakByWordWrapping;
|
||||
_truncationAttributedString = DefaultTruncationAttributedString();
|
||||
_composedTruncationString = DefaultTruncationAttributedString();
|
||||
|
||||
// The common case is for a text node to be non-opaque and blended over some background.
|
||||
self.opaque = NO;
|
||||
@ -181,32 +177,13 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
{
|
||||
ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width);
|
||||
ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height);
|
||||
// The supplied constrainedSize should include room for shadowPadding.
|
||||
// Inset the constrainedSize by the shadow padding to get the size available for text.
|
||||
UIEdgeInsets shadowPadding = [[self _shadower] shadowPadding];
|
||||
// Invert the negative values of shadow padding to get a positive inset
|
||||
UIEdgeInsets shadowPaddingOutset = ASDNEdgeInsetsInvert(shadowPadding);
|
||||
|
||||
// Inset the padded constrainedSize to get the remaining size available for text
|
||||
CGRect constrainedRect = CGRect{CGPointZero, constrainedSize};
|
||||
CGSize constrainedSizeForText = UIEdgeInsetsInsetRect(constrainedRect, shadowPaddingOutset).size;
|
||||
ASDisplayNodeAssert(constrainedSizeForText.width >= 0, @"Constrained width for text (%f) after subtracting shadow padding (%@) is too narrow", constrainedSizeForText.width, NSStringFromUIEdgeInsets(shadowPadding));
|
||||
ASDisplayNodeAssert(constrainedSizeForText.height >= 0, @"Constrained height for text (%f) after subtracting shadow padding (%@) is too short", constrainedSizeForText.height, NSStringFromUIEdgeInsets(shadowPadding));
|
||||
|
||||
_constrainedSize = constrainedSizeForText;
|
||||
_constrainedSize = constrainedSize;
|
||||
[self _invalidateRenderer];
|
||||
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
CGSize rendererSize = [[self _renderer] size];
|
||||
|
||||
// Add shadow padding back
|
||||
CGSize renderSizePlusShadowPadding = UIEdgeInsetsInsetRect(CGRect{CGPointZero, rendererSize}, shadowPadding).size;
|
||||
ASDisplayNodeAssert(renderSizePlusShadowPadding.width >= 0, @"Calculated width for text with shadow padding (%f) is too narrow", constrainedSizeForText.width);
|
||||
ASDisplayNodeAssert(renderSizePlusShadowPadding.height >= 0, @"Calculated height for text with shadow padding (%f) is too short", constrainedSizeForText.height);
|
||||
renderSizePlusShadowPadding = ceilSizeValue(renderSizePlusShadowPadding);
|
||||
return CGSizeMake(MIN(renderSizePlusShadowPadding.width, constrainedSize.width),
|
||||
MIN(renderSizePlusShadowPadding.height, constrainedSize.height));
|
||||
return [[self _renderer] size];
|
||||
}
|
||||
|
||||
- (void)displayDidFinish
|
||||
@ -269,21 +246,28 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
|
||||
#pragma mark - Renderer Management
|
||||
|
||||
- (ASTextNodeRenderer *)_renderer
|
||||
- (ASTextKitRenderer *)_renderer
|
||||
{
|
||||
ASDN::MutexLocker l(_rendererLock);
|
||||
if (_renderer == nil) {
|
||||
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : self.bounds.size;
|
||||
_renderer = [[ASTextNodeRenderer alloc] initWithAttributedString:_attributedString
|
||||
truncationString:_composedTruncationString
|
||||
truncationMode:_truncationMode
|
||||
maximumLineCount:_maximumLineCount
|
||||
exclusionPaths:_exclusionPaths
|
||||
constrainedSize:constrainedSize];
|
||||
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
|
||||
constrainedSize:constrainedSize];
|
||||
}
|
||||
return _renderer;
|
||||
}
|
||||
|
||||
- (ASTextKitAttributes)_rendererAttributes
|
||||
{
|
||||
return {
|
||||
.attributedString = _attributedString,
|
||||
.truncationAttributedString = _composedTruncationString,
|
||||
.lineBreakMode = _truncationMode,
|
||||
.maximumNumberOfLines = _maximumNumberOfLines,
|
||||
.exclusionPaths = _exclusionPaths,
|
||||
};
|
||||
}
|
||||
|
||||
- (void)_invalidateRenderer
|
||||
{
|
||||
ASDN::MutexLocker l(_rendererLock);
|
||||
@ -291,7 +275,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
// Destruction of the layout managers/containers/text storage is quite
|
||||
// expensive, and can take some time, so we dispatch onto a bg queue to
|
||||
// actually dealloc.
|
||||
__block ASTextNodeRenderer *renderer = _renderer;
|
||||
__block ASTextKitRenderer *renderer = _renderer;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
renderer = nil;
|
||||
});
|
||||
@ -299,23 +283,6 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
_renderer = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Shadow Drawer Management
|
||||
- (ASTextNodeShadower *)_shadower
|
||||
{
|
||||
if (_shadower == nil) {
|
||||
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:_shadowOffset
|
||||
shadowColor:_shadowColor
|
||||
shadowOpacity:_shadowOpacity
|
||||
shadowRadius:_shadowRadius];
|
||||
}
|
||||
return _shadower;
|
||||
}
|
||||
|
||||
- (void)_invalidateShadower
|
||||
{
|
||||
_shadower = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Modifying User Text
|
||||
|
||||
- (void)setAttributedString:(NSAttributedString *)attributedString {
|
||||
@ -399,11 +366,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
}
|
||||
|
||||
// Draw shadow
|
||||
[parameters.shadower setShadowInContext:context];
|
||||
[[parameters.renderer shadower] setShadowInContext:context];
|
||||
|
||||
// Draw text
|
||||
bounds.origin = parameters.textOrigin;
|
||||
[parameters.renderer drawInRect:bounds inContext:context];
|
||||
[parameters.renderer drawInContext:context bounds:bounds];
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
@ -414,9 +381,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
UIEdgeInsets shadowPadding = [self shadowPadding];
|
||||
CGPoint textOrigin = CGPointMake(self.bounds.origin.x - shadowPadding.left, self.bounds.origin.y - shadowPadding.top);
|
||||
return [[ASTextNodeDrawParameters alloc] initWithRenderer:[self _renderer]
|
||||
shadower:[self _shadower]
|
||||
textOrigin:textOrigin
|
||||
backgroundColor:self.backgroundColor.CGColor];
|
||||
textOrigin:textOrigin
|
||||
backgroundColor:self.backgroundColor.CGColor];
|
||||
}
|
||||
|
||||
#pragma mark - Attributes
|
||||
@ -436,8 +402,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
range:(out NSRange *)rangeOut
|
||||
inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut
|
||||
{
|
||||
ASTextNodeRenderer *renderer = [self _renderer];
|
||||
NSRange visibleRange = [renderer visibleRange];
|
||||
ASTextKitRenderer *renderer = [self _renderer];
|
||||
NSRange visibleRange = renderer.visibleRanges[0];
|
||||
NSAttributedString *attributedString = _attributedString;
|
||||
|
||||
// Check in a 9-point region around the actual touch point so we make sure
|
||||
@ -635,10 +601,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
}
|
||||
|
||||
if (highlightTargetLayer != nil) {
|
||||
NSArray *highlightRects = [[self _renderer] rectsForTextRange:highlightRange measureOption:ASTextNodeRendererMeasureOptionBlock];
|
||||
NSArray *highlightRects = [[self _renderer] rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock];
|
||||
NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count];
|
||||
for (NSValue *rectValue in highlightRects) {
|
||||
CGRect rendererRect = [[self class] _adjustRendererRect:rectValue.CGRectValue forShadowPadding:_shadower.shadowPadding];
|
||||
UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding;
|
||||
CGRect rendererRect = [[self class] _adjustRendererRect:rectValue.CGRectValue forShadowPadding:shadowPadding];
|
||||
CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer];
|
||||
|
||||
// We set our overlay layer's frame to the bounds of the highlight target layer.
|
||||
@ -699,7 +666,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
return rendererRect;
|
||||
}
|
||||
|
||||
- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextNodeRendererMeasureOption)measureOption
|
||||
- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption
|
||||
{
|
||||
NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption];
|
||||
NSMutableArray *adjustedRects = [NSMutableArray array];
|
||||
@ -717,12 +684,12 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
|
||||
- (NSArray *)rectsForTextRange:(NSRange)textRange
|
||||
{
|
||||
return [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionCapHeight];
|
||||
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionCapHeight];
|
||||
}
|
||||
|
||||
- (NSArray *)highlightRectsForTextRange:(NSRange)textRange
|
||||
{
|
||||
return [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionBlock];
|
||||
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionBlock];
|
||||
}
|
||||
|
||||
- (CGRect)trailingRect
|
||||
@ -755,11 +722,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
UIGraphicsBeginImageContext(size);
|
||||
[self.placeholderColor setFill];
|
||||
|
||||
ASTextNodeRenderer *renderer = [self _renderer];
|
||||
NSRange textRange = [renderer visibleRange];
|
||||
ASTextKitRenderer *renderer = [self _renderer];
|
||||
NSRange textRange = renderer.visibleRanges[0];
|
||||
|
||||
// cap height is both faster and creates less subpixel blending
|
||||
NSArray *lineRects = [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionLineHeight];
|
||||
NSArray *lineRects = [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionLineHeight];
|
||||
|
||||
// fill each line with the placeholder color
|
||||
for (NSValue *rectValue in lineRects) {
|
||||
@ -830,7 +797,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1);
|
||||
|
||||
if (inAdditionalTruncationMessage) {
|
||||
NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:[[self _renderer] visibleRange]];
|
||||
NSRange visibleRange = [self _renderer].visibleRanges[0];
|
||||
NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange];
|
||||
[self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES];
|
||||
} else if (range.length && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) {
|
||||
[self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES];
|
||||
@ -905,7 +873,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
CGColorRetain(shadowColor);
|
||||
}
|
||||
_shadowColor = shadowColor;
|
||||
[self _invalidateShadower];
|
||||
[self _invalidateRenderer];
|
||||
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
@ -921,7 +889,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
{
|
||||
if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) {
|
||||
_shadowOffset = shadowOffset;
|
||||
[self _invalidateShadower];
|
||||
[self _invalidateRenderer];
|
||||
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
@ -937,7 +905,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
{
|
||||
if (_shadowOpacity != shadowOpacity) {
|
||||
_shadowOpacity = shadowOpacity;
|
||||
[self _invalidateShadower];
|
||||
[self _invalidateRenderer];
|
||||
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
@ -953,7 +921,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
{
|
||||
if (_shadowRadius != shadowRadius) {
|
||||
_shadowRadius = shadowRadius;
|
||||
[self _invalidateShadower];
|
||||
[self _invalidateRenderer];
|
||||
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
@ -962,7 +930,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
|
||||
- (UIEdgeInsets)shadowPadding
|
||||
{
|
||||
return [[self _shadower] shadowPadding];
|
||||
return [self _renderer].shadower.shadowPadding;
|
||||
}
|
||||
|
||||
#pragma mark - Truncation Message
|
||||
@ -1010,13 +978,14 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
|
||||
- (BOOL)isTruncated
|
||||
{
|
||||
return [[self _renderer] truncationStringCharacterRange].location != NSNotFound;
|
||||
NSRange visibleRange = [self _renderer].visibleRanges[0];
|
||||
return visibleRange.length < _attributedString.length;
|
||||
}
|
||||
|
||||
- (void)setMaximumLineCount:(NSUInteger)maximumLineCount
|
||||
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
||||
{
|
||||
if (_maximumLineCount != maximumLineCount) {
|
||||
_maximumLineCount = maximumLineCount;
|
||||
if (_maximumNumberOfLines != maximumNumberOfLines) {
|
||||
_maximumNumberOfLines = maximumNumberOfLines;
|
||||
[self _invalidateRenderer];
|
||||
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
||||
[self setNeedsDisplay];
|
||||
|
||||
@ -65,12 +65,6 @@
|
||||
#import <AsyncDisplayKit/ASRangeHandler.h>
|
||||
#import <AsyncDisplayKit/ASRangeHandlerPreload.h>
|
||||
#import <AsyncDisplayKit/ASRangeHandlerRender.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeCoreTextAdditions.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeRenderer.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeShadower.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeTypes.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeWordKerner.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/CGRect+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h>
|
||||
|
||||
@ -1,188 +0,0 @@
|
||||
/* 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>
|
||||
|
||||
|
||||
typedef void (^as_renderer_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, ASTextNodeRendererMeasureOption) {
|
||||
ASTextNodeRendererMeasureOptionLineHeight,
|
||||
ASTextNodeRendererMeasureOptionCapHeight,
|
||||
ASTextNodeRendererMeasureOptionBlock
|
||||
};
|
||||
|
||||
/*
|
||||
* This is an immutable textkit renderer that is responsible for sizing and
|
||||
* rendering text.
|
||||
*
|
||||
* @discussion This class implements internal locking to allow it to be used
|
||||
* safely from background threads. It is recommended that you create and cache a
|
||||
* renderer for each combination of parameters.
|
||||
*/
|
||||
@interface ASTextNodeRenderer : NSObject
|
||||
|
||||
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
|
||||
truncationString:(NSAttributedString *)truncationString
|
||||
truncationMode:(NSLineBreakMode)truncationMode
|
||||
maximumLineCount:(NSUInteger)maximumLineCount
|
||||
exclusionPaths:(NSArray *)exclusionPaths
|
||||
constrainedSize:(CGSize)constrainedSize;
|
||||
/*
|
||||
* Designated Initializer
|
||||
*
|
||||
* @discussion No sizing occurs as a result of initializing a renderer.
|
||||
* Instead, sizing and truncation operations occur lazily as they are needed,
|
||||
* so feel free
|
||||
*/
|
||||
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
|
||||
truncationString:(NSAttributedString *)truncationString
|
||||
truncationMode:(NSLineBreakMode)truncationMode
|
||||
maximumLineCount:(NSUInteger)maximumLineCount
|
||||
constrainedSize:(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.
|
||||
* @param context The CGContext in which to draw the contents of the renderer.
|
||||
*
|
||||
* @discussion Note that if a shadow is to be drawn, then the text will actually
|
||||
* draw inside a region that is inset from the bounds provided. Use
|
||||
* shadowPadding to properly transform the bounds such that this is correct for
|
||||
* your use-case. See shadowPadding docs for more.
|
||||
*
|
||||
* Initializes the textkit components lazily if they have not yet been created.
|
||||
* You may want to consider triggering this cost before hitting the draw method
|
||||
* if you are sensitive to this cost in drawInRect...
|
||||
*/
|
||||
- (void)drawInRect:(CGRect)bounds inContext:(CGContextRef)context;
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
/*
|
||||
* Returns the computed size of the renderer given the constrained size and
|
||||
* other parameters in the initializer.
|
||||
*
|
||||
* @discussion No actual computation is done in this method. It simply returns
|
||||
* the cached calculated size from initialization so this is very cheap to call.
|
||||
*
|
||||
* Triggers initialization of textkit components, truncation, and sizing.
|
||||
*/
|
||||
- (CGSize)size;
|
||||
|
||||
/*
|
||||
* Returns the trailing rect unused by the renderer in the last rendered line.
|
||||
*
|
||||
* @discussion In the coordinate space of the renderer.
|
||||
*
|
||||
* Triggers initialization of textkit components, truncation, and sizing.
|
||||
*/
|
||||
- (CGRect)trailingRect;
|
||||
|
||||
/*
|
||||
* 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 coordinate space of the renderer.
|
||||
*
|
||||
* Triggers initialization of textkit components, truncation, and sizing.
|
||||
*/
|
||||
- (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 ASTextNodeRendererMeasureOption 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:(ASTextNodeRendererMeasureOption)measureOption;
|
||||
|
||||
/*
|
||||
* Enumerate the text character indexes at a position within the coordinate
|
||||
* space of the renderer.
|
||||
*
|
||||
* @param position The point inside the coordinate space of the renderer 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:(as_renderer_index_block_t)block;
|
||||
|
||||
#pragma mark - Text Ranges
|
||||
|
||||
/*
|
||||
* The character range that represents the truncationString provided in the
|
||||
* initializer. location will be NSNotFound if no truncation occurred.
|
||||
*
|
||||
* Triggers initialization of textkit components, truncation, and sizing.
|
||||
*/
|
||||
- (NSRange)truncationStringCharacterRange;
|
||||
|
||||
/*
|
||||
* The character range from the original attributedString that is displayed by
|
||||
* the renderer given the parameters in the initializer.
|
||||
*
|
||||
* Triggers initialization of textkit components, truncation, and sizing.
|
||||
*/
|
||||
- (NSRange)visibleRange;
|
||||
|
||||
/*
|
||||
* The number of lines shown in the string.
|
||||
*
|
||||
* Triggers initialization of textkit components, truncation, and sizing.
|
||||
*/
|
||||
- (NSUInteger)lineCount;
|
||||
|
||||
@end
|
||||
@ -1,654 +0,0 @@
|
||||
/* 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 "ASTextNodeRenderer.h"
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
#import "ASAssert.h"
|
||||
#import "ASTextNodeTextKitHelpers.h"
|
||||
#import "ASTextNodeWordKerner.h"
|
||||
#import "ASThread.h"
|
||||
|
||||
static const CGFloat ASTextNodeRendererGlyphTouchHitSlop = 5.0;
|
||||
static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3;
|
||||
|
||||
@interface ASTextNodeRenderer ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeRenderer {
|
||||
CGSize _constrainedSize;
|
||||
CGSize _calculatedSize;
|
||||
|
||||
NSAttributedString *_attributedString;
|
||||
NSAttributedString *_truncationString;
|
||||
NSLineBreakMode _truncationMode;
|
||||
NSUInteger _maximumLineCount;
|
||||
NSRange _truncationCharacterRange;
|
||||
NSRange _visibleRange;
|
||||
|
||||
ASTextNodeWordKerner *_wordKerner;
|
||||
|
||||
ASDN::RecursiveMutex _textKitLock;
|
||||
NSLayoutManager *_layoutManager;
|
||||
NSTextStorage *_textStorage;
|
||||
NSTextContainer *_textContainer;
|
||||
|
||||
NSArray *_exclusionPaths;
|
||||
}
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
|
||||
truncationString:(NSAttributedString *)truncationString
|
||||
truncationMode:(NSLineBreakMode)truncationMode
|
||||
maximumLineCount:(NSUInteger)maximumLineCount
|
||||
exclusionPaths:(NSArray *)exclusionPaths
|
||||
constrainedSize:(CGSize)constrainedSize
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_attributedString = attributedString;
|
||||
_truncationString = truncationString;
|
||||
_truncationMode = truncationMode;
|
||||
_truncationCharacterRange = NSMakeRange(NSNotFound, truncationString.length);
|
||||
|
||||
_maximumLineCount = maximumLineCount;
|
||||
|
||||
_exclusionPaths = exclusionPaths;
|
||||
|
||||
_constrainedSize = constrainedSize;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
|
||||
truncationString:(NSAttributedString *)truncationString
|
||||
truncationMode:(NSLineBreakMode)truncationMode
|
||||
maximumLineCount:(NSUInteger)maximumLineCount
|
||||
constrainedSize:(CGSize)constrainedSize
|
||||
{
|
||||
return [self initWithAttributedString:attributedString truncationString:truncationString truncationMode:truncationMode maximumLineCount:maximumLineCount exclusionPaths:nil constrainedSize:constrainedSize];
|
||||
}
|
||||
|
||||
/*
|
||||
* Use this method to lazily construct the TextKit components.
|
||||
*/
|
||||
- (void)_initializeTextKitComponentsIfNeeded
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
if (_layoutManager == nil) {
|
||||
[self _initializeTextKitComponentsWithAttributedString:_attributedString];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_initializeTextKitComponentsWithAttributedString:(NSAttributedString *)attributedString
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. :(
|
||||
static ASDN::StaticMutex mutex = ASDISPLAYNODE_MUTEX_INITIALIZER;
|
||||
ASDN::StaticMutexLocker gl(mutex);
|
||||
|
||||
// Create the TextKit component stack with our default configuration.
|
||||
_textStorage = (attributedString ? [[NSTextStorage alloc] initWithAttributedString:attributedString] : [[NSTextStorage alloc] init]);
|
||||
_layoutManager = [[NSLayoutManager alloc] init];
|
||||
_layoutManager.usesFontLeading = NO;
|
||||
_wordKerner = [[ASTextNodeWordKerner alloc] init];
|
||||
_layoutManager.delegate = _wordKerner;
|
||||
[_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;
|
||||
// Translate our truncation mode into a line break mode on the container
|
||||
_textContainer.lineBreakMode = _truncationMode;
|
||||
// Set maximum number of lines
|
||||
_textContainer.maximumNumberOfLines = _maximumLineCount;
|
||||
|
||||
_textContainer.exclusionPaths = _exclusionPaths;
|
||||
|
||||
[_layoutManager addTextContainer:_textContainer];
|
||||
|
||||
ASDN::StaticMutexUnlocker gu(mutex);
|
||||
|
||||
[self _invalidateLayout];
|
||||
}
|
||||
|
||||
#pragma mark - Layout Initialization
|
||||
|
||||
- (void)_invalidateLayout
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
// Force a layout, which means we have to recompute our truncation parameters
|
||||
NSInteger originalStringLength = _textStorage.string.length;
|
||||
|
||||
[self _calculateSize];
|
||||
|
||||
NSRange visibleGlyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
|
||||
_visibleRange = [_layoutManager characterRangeForGlyphRange:visibleGlyphRange actualGlyphRange:NULL];
|
||||
|
||||
// Check if text is truncated, and if so apply our truncation string
|
||||
if (_visibleRange.length < originalStringLength && _truncationString.length > 0) {
|
||||
NSInteger firstCharacterIndexToReplace = [self _calculateCharacterIndexBeforeTruncationMessage];
|
||||
if (firstCharacterIndexToReplace == 0 || firstCharacterIndexToReplace == NSNotFound) {
|
||||
// Something went horribly wrong, short-circuit
|
||||
[self _calculateSize];
|
||||
return;
|
||||
}
|
||||
|
||||
// Update/truncate the visible range of text
|
||||
_visibleRange = 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:_truncationString];
|
||||
|
||||
_truncationCharacterRange = NSMakeRange(firstCharacterIndexToReplace, _truncationString.length);
|
||||
|
||||
// We must recompute the calculated size because we may have changed it in
|
||||
// changing the string
|
||||
[self _calculateSize];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Sizing
|
||||
|
||||
/*
|
||||
* Calculates the size of the text in the renderer based on the parameters
|
||||
* stored in the ivars of this class.
|
||||
*
|
||||
* This method can be expensive, so it is important that it not be called
|
||||
* frequently. It not only sizes the text, but it also configures the TextKit
|
||||
* components for drawing, and responding to all other queries made to this
|
||||
* class.
|
||||
*/
|
||||
- (void)_calculateSize
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
if (_attributedString.length == 0) {
|
||||
_calculatedSize = CGSizeZero;
|
||||
return;
|
||||
}
|
||||
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
|
||||
// Force glyph generation and layout, which may not have happened yet (and
|
||||
// isn't triggered by -usedRectForTextContainer:).
|
||||
[_layoutManager ensureLayoutForTextContainer:_textContainer];
|
||||
|
||||
CGRect constrainedRect = CGRect{CGPointZero, _constrainedSize};
|
||||
CGRect 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, (CGRect){.size = constrainedRect.size});
|
||||
|
||||
_calculatedSize = boundingRect.size;
|
||||
}
|
||||
|
||||
- (CGSize)size
|
||||
{
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
|
||||
return _calculatedSize;
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (CGRect)trailingRect
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
|
||||
// If have an empty string, then our whole bounds constitute trailing space.
|
||||
if ([_textStorage length] == 0) {
|
||||
return CGRectMake(0, 0, _calculatedSize.width, _calculatedSize.height);
|
||||
}
|
||||
|
||||
// Take everything after our final character as trailing space.
|
||||
NSArray *finalRects = [self rectsForTextRange:NSMakeRange([_textStorage length] - 1, 1) measureOption:ASTextNodeRendererMeasureOptionLineHeight];
|
||||
CGRect finalGlyphRect = [[finalRects lastObject] CGRectValue];
|
||||
CGPoint origin = CGPointMake(CGRectGetMaxX(finalGlyphRect), CGRectGetMinY(finalGlyphRect));
|
||||
CGSize size = CGSizeMake(_calculatedSize.width - origin.x, _calculatedSize.height - origin.y);
|
||||
return (CGRect){origin, size};
|
||||
}
|
||||
|
||||
- (CGRect)frameForTextRange:(NSRange)textRange
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
|
||||
// Bail on invalid range.
|
||||
if (NSMaxRange(textRange) > [_textStorage length]) {
|
||||
ASDisplayNodeAssertNotNil(nil, @"Invalid range");
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
// Force glyph generation and layout.
|
||||
[_layoutManager ensureLayoutForTextContainer:_textContainer];
|
||||
|
||||
NSRange glyphRange = [_layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL];
|
||||
CGRect textRect = [_layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:_textContainer];
|
||||
return textRect;
|
||||
}
|
||||
|
||||
- (NSArray *)rectsForTextRange:(NSRange)textRange
|
||||
measureOption:(ASTextNodeRendererMeasureOption)measureOption
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
|
||||
BOOL textRangeIsValid = (NSMaxRange(textRange) <= [_textStorage length]);
|
||||
ASDisplayNodeAssertTrue(textRangeIsValid);
|
||||
if (!textRangeIsValid) {
|
||||
return @[];
|
||||
}
|
||||
|
||||
// Used for block measure option
|
||||
__block CGRect firstRect = CGRectNull;
|
||||
__block CGRect lastRect = CGRectNull;
|
||||
__block CGRect blockRect = CGRectNull;
|
||||
NSMutableArray *textRects = [NSMutableArray array];
|
||||
|
||||
NSString *string = _textStorage.string;
|
||||
|
||||
NSRange totalGlyphRange = [_layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL];
|
||||
|
||||
[_layoutManager enumerateLineFragmentsForGlyphRange:totalGlyphRange usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, 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 _rectForGlyphAtIndex:i measureOption:measureOption];
|
||||
|
||||
// Don't count empty glyphs towards our line rect.
|
||||
if (!CGRectIsEmpty(properGlyphRect)) {
|
||||
lineRect = CGRectIsNull(lineRect) ? properGlyphRect
|
||||
: CGRectUnion(lineRect, properGlyphRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!CGRectIsNull(lineRect)) {
|
||||
if (measureOption == ASTextNodeRendererMeasureOptionBlock) {
|
||||
// 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.
|
||||
[textRects addObject:[NSValue valueWithCGRect:lineRect]];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
if (measureOption == ASTextNodeRendererMeasureOptionBlock) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
[textRects addObject:[NSValue valueWithCGRect:firstRect]];
|
||||
}
|
||||
if (!CGRectIsNull(blockRect)) {
|
||||
[textRects addObject:[NSValue valueWithCGRect:blockRect]];
|
||||
}
|
||||
if (!CGRectIsNull(lastRect)) {
|
||||
[textRects addObject:[NSValue valueWithCGRect:lastRect]];
|
||||
}
|
||||
}
|
||||
|
||||
return textRects;
|
||||
}
|
||||
|
||||
- (CGRect)_rectForGlyphAtIndex:(NSUInteger)glyphIndex
|
||||
measureOption:(ASTextNodeRendererMeasureOption)measureOption
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
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 == ASTextNodeRendererMeasureOptionCapHeight
|
||||
|| measureOption == ASTextNodeRendererMeasureOptionBlock) {
|
||||
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 = ASTextNodeRendererTextCapHeightPadding * 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)position usingBlock:(as_renderer_index_block_t)block
|
||||
{
|
||||
if (position.x > _constrainedSize.width
|
||||
|| position.y > _constrainedSize.height
|
||||
|| block == NULL) {
|
||||
// Short circuit if the position is outside the size of this renderer, or
|
||||
// if the block is null.
|
||||
return;
|
||||
}
|
||||
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
|
||||
// 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(position.x + i * pointSeparation,
|
||||
position.y + j * pointSeparation);
|
||||
|
||||
// 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.
|
||||
BOOL isValidGlyph = NO;
|
||||
[_layoutManager glyphAtIndex:glyphIndex isValidIndex:&isValidGlyph];
|
||||
if (!isValidGlyph) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSUInteger characterIndex = [_layoutManager characterIndexForGlyphAtIndex:glyphIndex];
|
||||
|
||||
CGRect glyphRect = [self _rectForGlyphAtIndex:glyphIndex
|
||||
measureOption:ASTextNodeRendererMeasureOptionLineHeight];
|
||||
|
||||
// Sometimes TextKit plays jokes on us and returns glyphs that really
|
||||
// aren't close to the point in question. Silly TextKit...
|
||||
if (!CGRectContainsPoint(CGRectInset(glyphRect, -ASTextNodeRendererGlyphTouchHitSlop, -ASTextNodeRendererGlyphTouchHitSlop), currentPoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
block(characterIndex, glyphRect, &stop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Truncation
|
||||
|
||||
/*
|
||||
* Calculates the intersection of the truncation message within the end of the
|
||||
* last line.
|
||||
*
|
||||
* This is accomplished by temporarily adding an exclusion rect for the size of
|
||||
* the truncation string at the end of the last line of text, and forcing the
|
||||
* layout manager to re-layout and clip the text such that we get a natural
|
||||
* clipping based on the settings of the layout manager.
|
||||
*/
|
||||
- (NSUInteger)_calculateCharacterIndexBeforeTruncationMessage
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
|
||||
CGRect constrainedRect = (CGRect){.size = _calculatedSize};
|
||||
|
||||
NSRange visibleGlyphRange = [_layoutManager glyphRangeForBoundingRect:constrainedRect inTextContainer:_textContainer];
|
||||
NSInteger lastVisibleGlyphIndex = (NSMaxRange(visibleGlyphRange) - 1);
|
||||
CGRect lastLineRect = [_layoutManager lineFragmentRectForGlyphAtIndex:lastVisibleGlyphIndex effectiveRange:NULL];
|
||||
|
||||
// Calculate the bounding rectangle for the truncation message
|
||||
ASTextKitComponents *truncationComponents = [ASTextKitComponents componentsWithAttributedSeedString:_truncationString
|
||||
textContainerSize:constrainedRect.size];
|
||||
|
||||
// Size the truncation message
|
||||
[truncationComponents.layoutManager ensureLayoutForTextContainer:truncationComponents.textContainer];
|
||||
NSRange truncationGlyphRange = [truncationComponents.layoutManager glyphRangeForTextContainer:truncationComponents.textContainer];
|
||||
CGRect truncationUsedRect = [truncationComponents.layoutManager boundingRectForGlyphRange:truncationGlyphRange inTextContainer:truncationComponents.textContainer];
|
||||
CGRect translatedTruncationRect = CGRectMake(CGRectGetMaxX(constrainedRect) - truncationUsedRect.size.width,
|
||||
CGRectGetMinY(lastLineRect),
|
||||
truncationUsedRect.size.width,
|
||||
truncationUsedRect.size.height);
|
||||
|
||||
// Determine which glyph is the first to be clipped / overlaps the truncation message.
|
||||
CGPoint beginningOfTruncationMessage = CGPointMake(translatedTruncationRect.origin.x, CGRectGetMidY(translatedTruncationRect));
|
||||
NSUInteger firstClippedGlyphIndex = [_layoutManager glyphIndexForPoint:beginningOfTruncationMessage inTextContainer:_textContainer fractionOfDistanceThroughGlyph:NULL];
|
||||
NSUInteger firstCharacterIndexToReplace = [_layoutManager characterIndexForGlyphAtIndex:firstClippedGlyphIndex];
|
||||
ASDisplayNodeAssert(firstCharacterIndexToReplace != NSNotFound, @"The beginning of the truncation message exclusion rect (%@) didn't intersect any glyphs", NSStringFromCGPoint(beginningOfTruncationMessage));
|
||||
|
||||
// Break on word boundaries
|
||||
return [self _findTruncationInsertionPointAtOrBeforeCharacterIndex:firstCharacterIndexToReplace];
|
||||
}
|
||||
|
||||
+ (NSCharacterSet *)_truncationCharacterSet
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @abstract Finds the first whitespace at or before the character index do we don't truncate in the middle of words
|
||||
* @discussion If there are multiple whitespaces together (say a space and a newline), this will backtrack to the first one
|
||||
*/
|
||||
- (NSUInteger)_findTruncationInsertionPointAtOrBeforeCharacterIndex:(NSUInteger)firstCharacterIndexToReplace
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
// Don't attempt to truncate beyond the beginning 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));
|
||||
|
||||
NSCharacterSet *truncationCharacterSet = [[self class] _truncationCharacterSet];
|
||||
|
||||
NSRange rangeOfLastVisibleWhitespace = [_textStorage.string rangeOfCharacterFromSet:truncationCharacterSet
|
||||
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 (rangeOfLastVisibleWhitespace.location == NSNotFound) {
|
||||
return firstCharacterIndexToReplace;
|
||||
} else {
|
||||
return rangeOfLastVisibleWhitespace.location;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Drawing
|
||||
|
||||
- (void)drawInRect:(CGRect)bounds inContext:(CGContextRef)context
|
||||
{
|
||||
ASDisplayNodeAssert(context, @"This is no good without a context.");
|
||||
UIGraphicsPushContext(context);
|
||||
CGContextSaveGState(context);
|
||||
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:bounds.origin];
|
||||
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:bounds.origin];
|
||||
}
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
UIGraphicsPopContext();
|
||||
}
|
||||
|
||||
#pragma mark - String Ranges
|
||||
|
||||
- (NSUInteger)lineCount
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
|
||||
NSUInteger lineCount = 0;
|
||||
for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [_layoutManager numberOfGlyphs]; lineCount++) {
|
||||
[_layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange];
|
||||
}
|
||||
return lineCount;
|
||||
}
|
||||
|
||||
- (NSRange)visibleRange
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
return _visibleRange;
|
||||
}
|
||||
|
||||
- (NSRange)truncationStringCharacterRange
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitLock);
|
||||
[self _initializeTextKitComponentsIfNeeded];
|
||||
return _truncationCharacterRange;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,69 +0,0 @@
|
||||
/* 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 <UIKit/UIKit.h>
|
||||
|
||||
|
||||
/**
|
||||
* @abstract Negates/inverts a UIEdgeInsets.
|
||||
* @discussion Useful for undoing the application of shadow padding to a frame/bounds CGRect.
|
||||
* For example,
|
||||
* CGRect insetRect = UIEdgeInsetsRect(originalRect, insets);
|
||||
* CGRect equalToOriginalRect = UIEdgeInsetsRect(originalRect, ASDNEdgeInsetsInvert(insets));
|
||||
*/
|
||||
static inline UIEdgeInsets ASDNEdgeInsetsInvert(UIEdgeInsets insets)
|
||||
{
|
||||
UIEdgeInsets invertedInsets = UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right);
|
||||
return invertedInsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @abstract an immutable class for calculating shadow padding drawing a shadowed background for text
|
||||
*/
|
||||
@interface ASTextNodeShadower : NSObject
|
||||
|
||||
- (instancetype)initWithShadowOffset:(CGSize)shadowOffset
|
||||
shadowColor:(CGColorRef)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, assign) CGColorRef 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;
|
||||
|
||||
/**
|
||||
* @abstract draws the shadow for text in the provided CGContext
|
||||
* @discussion Call within the text node's +drawRect method
|
||||
*/
|
||||
- (void)setShadowInContext:(CGContextRef)context;
|
||||
|
||||
@end
|
||||
@ -1,90 +0,0 @@
|
||||
/* 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 "ASTextNodeShadower.h"
|
||||
|
||||
@implementation ASTextNodeShadower {
|
||||
UIEdgeInsets _calculatedShadowPadding;
|
||||
}
|
||||
|
||||
- (instancetype)initWithShadowOffset:(CGSize)shadowOffset
|
||||
shadowColor:(CGColorRef)shadowColor
|
||||
shadowOpacity:(CGFloat)shadowOpacity
|
||||
shadowRadius:(CGFloat)shadowRadius
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_shadowOffset = shadowOffset;
|
||||
_shadowColor = CGColorRetain(shadowColor);
|
||||
_shadowOpacity = shadowOpacity;
|
||||
_shadowRadius = shadowRadius;
|
||||
_calculatedShadowPadding = UIEdgeInsetsMake(-INFINITY, -INFINITY, INFINITY, INFINITY);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGColorRelease(_shadowColor);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 != NULL && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero));
|
||||
}
|
||||
|
||||
- (void)setShadowInContext:(CGContextRef)context
|
||||
{
|
||||
if ([self _shouldDrawShadow]) {
|
||||
CGColorRef textShadowColor = CGColorRetain(_shadowColor);
|
||||
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;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -8,7 +8,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol ASLayoutableAsciiArtProtocol <NSObject>
|
||||
/**
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
@import UIKit;
|
||||
#import "ASAsciiArtBoxCreator.h"
|
||||
|
||||
static const NSUInteger kDebugBoxPadding = 2;
|
||||
|
||||
171
AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h
Normal file
171
AsyncDisplayKit/TextKit/ASEqualityHashHelpers.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 ASHashCombine(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 ASHash64ToNative(uint64_t key) {
|
||||
return key;
|
||||
}
|
||||
#else
|
||||
|
||||
// Thomas Wang downscaling hash function
|
||||
inline size_t ASHash64ToNative(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 ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count);
|
||||
|
||||
namespace AS {
|
||||
// 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 { };
|
||||
|
||||
// ASUtils::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 ASTupleOperations
|
||||
{
|
||||
// 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 = AS::hash<TypeForIndex>()(std::get<Index>(tuple));
|
||||
return ASHashCombine(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 AS::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 && AS::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 AS::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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
129
AsyncDisplayKit/TextKit/ASTextKitAttributes.h
Executable file
129
AsyncDisplayKit/TextKit/ASTextKitAttributes.h
Executable file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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_ASTextKitAttributes_h
|
||||
#define ComponentKit_ASTextKitAttributes_h
|
||||
|
||||
@protocol ASTextKitTruncating;
|
||||
|
||||
extern NSString *const ASTextKitTruncationAttributeName;
|
||||
/**
|
||||
Use ASTextKitEntityAttribute as the value of this attribute to embed a link or other interactable content inside the
|
||||
text.
|
||||
*/
|
||||
extern NSString *const ASTextKitEntityAttributeName;
|
||||
|
||||
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 ASTextKitAttributes {
|
||||
/**
|
||||
The string to be drawn. ASTextKit 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 ASTextKitTruncationAttributeName
|
||||
to mark the specific range of the string that should be highlightable.
|
||||
*/
|
||||
NSAttributedString *truncationAttributedString;
|
||||
/**
|
||||
This is the character set that ASTextKit 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;
|
||||
/**
|
||||
An array of UIBezierPath objects representing the exclusion paths inside the receiver's bounding rectangle. Default value: nil.
|
||||
*/
|
||||
NSArray *exclusionPaths;
|
||||
/**
|
||||
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 ASTextKitAttributes copy() const
|
||||
{
|
||||
return {
|
||||
[attributedString copy],
|
||||
[truncationAttributedString copy],
|
||||
[avoidTailTruncationSet copy],
|
||||
lineBreakMode,
|
||||
maximumNumberOfLines,
|
||||
[exclusionPaths copy],
|
||||
shadowOffset,
|
||||
[shadowColor copy],
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
layoutManagerFactory
|
||||
};
|
||||
};
|
||||
|
||||
bool operator==(const ASTextKitAttributes &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(exclusionPaths, other.exclusionPaths)
|
||||
&& _objectsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet)
|
||||
&& _objectsEqual(shadowColor, other.shadowColor)
|
||||
&& _objectsEqual(attributedString, other.attributedString)
|
||||
&& _objectsEqual(truncationAttributedString, other.truncationAttributedString);
|
||||
}
|
||||
|
||||
size_t hash() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
37
AsyncDisplayKit/TextKit/ASTextKitAttributes.mm
Executable file
37
AsyncDisplayKit/TextKit/ASTextKitAttributes.mm
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 "ASTextKitAttributes.h"
|
||||
|
||||
#import "ASEqualityHashHelpers.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
NSString *const ASTextKitTruncationAttributeName = @"ck_truncation";
|
||||
NSString *const ASTextKitEntityAttributeName = @"ck_entity";
|
||||
|
||||
size_t ASTextKitAttributes::hash() const
|
||||
{
|
||||
NSUInteger subhashes[] = {
|
||||
[attributedString hash],
|
||||
[truncationAttributedString hash],
|
||||
[avoidTailTruncationSet hash],
|
||||
std::hash<NSUInteger>()((NSUInteger) layoutManagerFactory),
|
||||
std::hash<NSInteger>()(lineBreakMode),
|
||||
std::hash<NSInteger>()(maximumNumberOfLines),
|
||||
[exclusionPaths hash],
|
||||
std::hash<CGFloat>()(shadowOffset.width),
|
||||
std::hash<CGFloat>()(shadowOffset.height),
|
||||
[shadowColor hash],
|
||||
std::hash<CGFloat>()(shadowOpacity),
|
||||
std::hash<CGFloat>()(shadowRadius),
|
||||
};
|
||||
return ASIntegerArrayHash(subhashes, sizeof(subhashes) / sizeof(subhashes[0]));
|
||||
}
|
||||
46
AsyncDisplayKit/TextKit/ASTextKitContext.h
Executable file
46
AsyncDisplayKit/TextKit/ASTextKitContext.h
Executable file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 ASTextKit 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 ASTextKitContext : 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
|
||||
exclusionPaths:(NSArray *)exclusionPaths
|
||||
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
|
||||
60
AsyncDisplayKit/TextKit/ASTextKitContext.mm
Executable file
60
AsyncDisplayKit/TextKit/ASTextKitContext.mm
Executable file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 "ASTextKitContext.h"
|
||||
|
||||
@implementation ASTextKitContext
|
||||
{
|
||||
// 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
|
||||
exclusionPaths:(NSArray *)exclusionPaths
|
||||
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;
|
||||
_textContainer.exclusionPaths = exclusionPaths;
|
||||
[_layoutManager addTextContainer:_textContainer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *,
|
||||
NSTextStorage *,
|
||||
NSTextContainer *))block
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_textKitMutex);
|
||||
block(_layoutManager, _textStorage, _textContainer);
|
||||
}
|
||||
|
||||
@end
|
||||
@ -63,7 +63,7 @@ ASDISPLAYNODE_EXTERN_C_END
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark -
|
||||
@interface NSParagraphStyle (ASTextNodeCoreTextAdditions)
|
||||
@interface NSParagraphStyle (ASTextKitCoreTextAdditions)
|
||||
|
||||
/**
|
||||
@abstract Returns an NSParagraphStyle initialized with the paragraph specifiers from the given CTParagraphStyleRef.
|
||||
@ -6,7 +6,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ASTextNodeCoreTextAdditions.h"
|
||||
#import "ASTextKitCoreTextAdditions.h"
|
||||
|
||||
#import <CoreText/CTFont.h>
|
||||
#import <CoreText/CTStringAttributes.h>
|
||||
@ -155,7 +155,7 @@ NSAttributedString *ASCleanseAttributedStringOfCoreTextAttributes(NSAttributedSt
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark -
|
||||
@implementation NSParagraphStyle (ASTextNodeCoreTextAdditions)
|
||||
@implementation NSParagraphStyle (ASTextKitCoreTextAdditions)
|
||||
|
||||
+ (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle;
|
||||
{
|
||||
28
AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.h
Executable file
28
AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.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 ASTextKitEntityAttributeName. 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 ASTextKitEntityAttribute : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) id<NSObject> entity;
|
||||
|
||||
- (instancetype)initWithEntity:(id<NSObject>)entity;
|
||||
|
||||
@end
|
||||
40
AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.m
Executable file
40
AsyncDisplayKit/TextKit/ASTextKitEntityAttribute.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 "ASTextKitEntityAttribute.h"
|
||||
|
||||
@implementation ASTextKitEntityAttribute
|
||||
|
||||
- (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;
|
||||
}
|
||||
ASTextKitEntityAttribute *other = (ASTextKitEntityAttribute *)object;
|
||||
return _entity == other.entity || [_entity isEqual:other.entity];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -6,7 +6,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ASTextNodeTextKitHelpers.h"
|
||||
#import "ASTextKitHelpers.h"
|
||||
|
||||
@interface ASTextKitComponents ()
|
||||
|
||||
103
AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.h
Executable file
103
AsyncDisplayKit/TextKit/ASTextKitRenderer+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 "ASTextKitRenderer.h"
|
||||
|
||||
typedef void (^as_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, ASTextKitRendererMeasureOption) {
|
||||
ASTextKitRendererMeasureOptionLineHeight,
|
||||
ASTextKitRendererMeasureOptionCapHeight,
|
||||
ASTextKitRendererMeasureOptionBlock
|
||||
};
|
||||
|
||||
@interface ASTextKitRenderer (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 ASTextKitRendererMeasureOption
|
||||
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:(ASTextKitRendererMeasureOption)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:(as_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/ASTextKitRenderer+Positioning.mm
Executable file
374
AsyncDisplayKit/TextKit/ASTextKitRenderer+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 "ASTextKitRenderer+Positioning.h"
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
#import "ASAssert.h"
|
||||
|
||||
#import "ASTextKitContext.h"
|
||||
#import "ASTextKitShadower.h"
|
||||
|
||||
static const CGFloat ASTextKitRendererGlyphTouchHitSlop = 5.0;
|
||||
static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3;
|
||||
|
||||
@implementation ASTextKitRenderer (Tracking)
|
||||
|
||||
- (NSArray *)rectsForTextRange:(NSRange)textRange
|
||||
measureOption:(ASTextKitRendererMeasureOption)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 == ASTextKitRendererMeasureOptionBlock) {
|
||||
// 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 == ASTextKitRendererMeasureOptionBlock) {
|
||||
// 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:(ASTextKitRendererMeasureOption)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 == ASTextKitRendererMeasureOptionCapHeight
|
||||
|| measureOption == ASTextKitRendererMeasureOptionBlock) {
|
||||
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 = ASTextKitRendererTextCapHeightPadding * 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:(as_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.
|
||||
ASTextKitContext *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:ASTextKitRendererMeasureOptionLineHeight
|
||||
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, -ASTextKitRendererGlyphTouchHitSlop, -ASTextKitRendererGlyphTouchHitSlop), 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:ASTextKitRendererMeasureOptionLineHeight];
|
||||
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/ASTextKitRenderer+TextChecking.h
Executable file
29
AsyncDisplayKit/TextKit/ASTextKitRenderer+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 "ASTextKitRenderer.h"
|
||||
|
||||
/**
|
||||
Application extensions to NSTextCheckingType. We're allowed to do this (see NSTextCheckingAllCustomTypes).
|
||||
*/
|
||||
static uint64_t const ASTextKitTextCheckingTypeEntity = 1ULL << 33;
|
||||
static uint64_t const ASTextKitTextCheckingTypeTruncation = 1ULL << 34;
|
||||
|
||||
@class ASTextKitEntityAttribute;
|
||||
|
||||
@interface ASTextKitTextCheckingResult : NSTextCheckingResult
|
||||
@property (nonatomic, strong, readonly) ASTextKitEntityAttribute *entityAttribute;
|
||||
@end
|
||||
|
||||
@interface ASTextKitRenderer (TextChecking)
|
||||
|
||||
- (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point;
|
||||
|
||||
@end
|
||||
102
AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.mm
Executable file
102
AsyncDisplayKit/TextKit/ASTextKitRenderer+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 "ASTextKitRenderer+TextChecking.h"
|
||||
|
||||
#import "ASTextKitAttributes.h"
|
||||
#import "ASTextKitEntityAttribute.h"
|
||||
#import "ASTextKitRenderer+Positioning.h"
|
||||
#import "ASTextKitTailTruncater.h"
|
||||
|
||||
@implementation ASTextKitTextCheckingResult
|
||||
|
||||
{
|
||||
// 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:(ASTextKitEntityAttribute *)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 ASTextKitRenderer (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:ASTextKitTruncationAttributeName 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 = [[ASTextKitTextCheckingResult alloc] initWithType:ASTextKitTextCheckingTypeTruncation
|
||||
entityAttribute:nil
|
||||
range:truncationTokenRange];
|
||||
} else {
|
||||
NSRange range;
|
||||
NSDictionary *attributes = [attributedString attributesAtIndex:index effectiveRange:&range];
|
||||
ASTextKitEntityAttribute *entityAttribute = attributes[ASTextKitEntityAttributeName];
|
||||
if (entityAttribute) {
|
||||
result = [[ASTextKitTextCheckingResult alloc] initWithType:ASTextKitTextCheckingTypeEntity
|
||||
entityAttribute:entityAttribute
|
||||
range:range];
|
||||
}
|
||||
}
|
||||
if (result != nil) {
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
84
AsyncDisplayKit/TextKit/ASTextKitRenderer.h
Executable file
84
AsyncDisplayKit/TextKit/ASTextKitRenderer.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 "ASTextKitAttributes.h"
|
||||
|
||||
@class ASTextKitContext;
|
||||
@class ASTextKitShadower;
|
||||
@protocol ASTextKitTruncating;
|
||||
|
||||
/**
|
||||
ASTextKitRenderer 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 ASTextKitRenderer : NSObject
|
||||
|
||||
/**
|
||||
Designated Initializer
|
||||
dvlkferufedgjnhjjfhldjedlunvtdtv
|
||||
@discussion Sizing will occur as a result of initialization, so be careful when/where you use this.
|
||||
*/
|
||||
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)textComponentAttributes
|
||||
constrainedSize:(const CGSize)constrainedSize;
|
||||
|
||||
@property (nonatomic, strong, readonly) ASTextKitContext *context;
|
||||
|
||||
@property (nonatomic, strong, readonly) id<ASTextKitTruncating> truncater;
|
||||
|
||||
@property (nonatomic, strong, readonly) ASTextKitShadower *shadower;
|
||||
|
||||
@property (nonatomic, assign, readonly) ASTextKitAttributes 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
|
||||
141
AsyncDisplayKit/TextKit/ASTextKitRenderer.mm
Executable file
141
AsyncDisplayKit/TextKit/ASTextKitRenderer.mm
Executable file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 "ASTextKitRenderer.h"
|
||||
|
||||
#import "ASAssert.h"
|
||||
|
||||
#import "ASTextKitContext.h"
|
||||
#import "ASTextKitShadower.h"
|
||||
#import "ASTextKitTailTruncater.h"
|
||||
#import "ASTextKitTruncating.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 ASTextKitRenderer {
|
||||
CGSize _calculatedSize;
|
||||
}
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)attributes
|
||||
constrainedSize:(const CGSize)constrainedSize
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_constrainedSize = constrainedSize;
|
||||
_attributes = attributes;
|
||||
|
||||
_shadower = [[ASTextKitShadower 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 = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
|
||||
lineBreakMode:attributes.lineBreakMode
|
||||
maximumNumberOfLines:attributes.maximumNumberOfLines
|
||||
exclusionPaths:attributes.exclusionPaths
|
||||
constrainedSize:shadowConstrainedSize
|
||||
layoutManagerFactory:attributes.layoutManagerFactory];
|
||||
|
||||
_truncater = [[ASTextKitTailTruncater 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/ASTextKitShadower.h
Executable file
70
AsyncDisplayKit/TextKit/ASTextKitShadower.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 ASTextKitShadower : 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/ASTextKitShadower.mm
Executable file
148
AsyncDisplayKit/TextKit/ASTextKitShadower.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 "ASTextKitShadower.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 ASTextKitShadower {
|
||||
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/ASTextKitTailTruncater.h
Executable file
17
AsyncDisplayKit/TextKit/ASTextKitTailTruncater.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 "ASTextKitTruncating.h"
|
||||
|
||||
@interface ASTextKitTailTruncater : NSObject <ASTextKitTruncating>
|
||||
|
||||
@end
|
||||
191
AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm
Executable file
191
AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm
Executable file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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 "ASTextKitContext.h"
|
||||
#import "ASTextKitTailTruncater.h"
|
||||
|
||||
@implementation ASTextKitTailTruncater
|
||||
{
|
||||
__weak ASTextKitContext *_context;
|
||||
NSAttributedString *_truncationAttributedString;
|
||||
NSCharacterSet *_avoidTailTruncationSet;
|
||||
CGSize _constrainedSize;
|
||||
}
|
||||
@synthesize visibleRanges = _visibleRanges;
|
||||
@synthesize truncationStringRect = _truncationStringRect;
|
||||
|
||||
- (instancetype)initWithContext:(ASTextKitContext *)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
|
||||
ASTextKitContext *truncationContext = [[ASTextKitContext alloc] initWithAttributedString:_truncationAttributedString
|
||||
lineBreakMode:NSLineBreakByWordWrapping
|
||||
maximumNumberOfLines:1
|
||||
exclusionPaths:nil
|
||||
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/ASTextKitTruncating.h
Executable file
37
AsyncDisplayKit/TextKit/ASTextKitTruncating.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 "ASTextKitRenderer.h"
|
||||
|
||||
@protocol ASTextKitTruncating <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:(ASTextKitContext *)context
|
||||
truncationAttributedString:(NSAttributedString *)truncationAttributedString
|
||||
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
|
||||
constrainedSize:(CGSize)constrainedSize;
|
||||
|
||||
@end
|
||||
@ -10,13 +10,13 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "ASTextNodeCoreTextAdditions.h"
|
||||
#import "ASTextKitCoreTextAdditions.h"
|
||||
|
||||
@interface ASTextNodeCoreTextAdditionsTests : XCTestCase
|
||||
@interface ASTextKitCoreTextAdditionsTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeCoreTextAdditionsTests
|
||||
@implementation ASTextKitCoreTextAdditionsTests
|
||||
|
||||
- (void)testAttributeCleansing
|
||||
{
|
||||
136
AsyncDisplayKitTests/ASTextKitTests.mm
Normal file
136
AsyncDisplayKitTests/ASTextKitTests.mm
Normal file
@ -0,0 +1,136 @@
|
||||
/* 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 <XCTest/XCTest.h>
|
||||
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
|
||||
|
||||
#import "ASTextKitAttributes.h"
|
||||
#import "ASTextKitRenderer.h"
|
||||
|
||||
@interface ASTextKitTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
static UITextView *UITextViewWithAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize)
|
||||
{
|
||||
UITextView *textView = [[UITextView alloc] initWithFrame:{ .size = constrainedSize }];
|
||||
textView.backgroundColor = [UIColor clearColor];
|
||||
textView.textContainer.lineBreakMode = attributes.lineBreakMode;
|
||||
textView.textContainer.lineFragmentPadding = 0.f;
|
||||
textView.textContainer.maximumNumberOfLines = attributes.maximumNumberOfLines;
|
||||
textView.textContainerInset = UIEdgeInsetsZero;
|
||||
textView.layoutManager.usesFontLeading = NO;
|
||||
textView.attributedText = attributes.attributedString;
|
||||
return textView;
|
||||
}
|
||||
|
||||
static UIImage *UITextViewImageWithAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize)
|
||||
{
|
||||
UITextView *textView = UITextViewWithAttributes(attributes, constrainedSize);
|
||||
UIGraphicsBeginImageContextWithOptions(constrainedSize, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
[textView.layer renderInContext:context];
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
static UIImage *ASTextKitImageWithAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize)
|
||||
{
|
||||
ASTextKitRenderer *renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:attributes
|
||||
constrainedSize:constrainedSize];
|
||||
UIGraphicsBeginImageContextWithOptions(constrainedSize, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
[renderer drawInContext:context bounds:{.size = constrainedSize}];
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
static BOOL checkAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize)
|
||||
{
|
||||
FBSnapshotTestController *controller = [[FBSnapshotTestController alloc] init];
|
||||
UIImage *labelImage = UITextViewImageWithAttributes(attributes, constrainedSize);
|
||||
UIImage *textKitImage = ASTextKitImageWithAttributes(attributes, constrainedSize);
|
||||
return [controller compareReferenceImage:labelImage toImage:textKitImage error:nil];
|
||||
}
|
||||
|
||||
@implementation ASTextKitTests
|
||||
|
||||
- (void)testSimpleStrings
|
||||
{
|
||||
ASTextKitAttributes attributes {
|
||||
.attributedString = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}]
|
||||
};
|
||||
XCTAssert(checkAttributes(attributes, { 100, 100 }));
|
||||
}
|
||||
|
||||
- (void)testChangingAPropertyChangesHash
|
||||
{
|
||||
NSAttributedString *as = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}];
|
||||
|
||||
ASTextKitAttributes attrib1 {
|
||||
.attributedString = as,
|
||||
.lineBreakMode = NSLineBreakByClipping,
|
||||
};
|
||||
ASTextKitAttributes attrib2 {
|
||||
.attributedString = as,
|
||||
};
|
||||
|
||||
XCTAssertNotEqual(attrib1.hash(), attrib2.hash(), @"Hashes should differ when NSLineBreakByClipping changes.");
|
||||
}
|
||||
|
||||
- (void)testSameStringHashesSame
|
||||
{
|
||||
ASTextKitAttributes attrib1 {
|
||||
.attributedString = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}],
|
||||
};
|
||||
ASTextKitAttributes attrib2 {
|
||||
.attributedString = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}],
|
||||
};
|
||||
|
||||
XCTAssertEqual(attrib1.hash(), attrib2.hash(), @"Hashes should be the same!");
|
||||
}
|
||||
|
||||
|
||||
- (void)testStringsWithVariableAttributes
|
||||
{
|
||||
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}];
|
||||
for (int i = 0; i < attrStr.length; i++) {
|
||||
// Color each character something different
|
||||
CGFloat factor = ((CGFloat)i) / ((CGFloat)attrStr.length);
|
||||
[attrStr addAttribute:NSForegroundColorAttributeName
|
||||
value:[UIColor colorWithRed:factor
|
||||
green:1.0 - factor
|
||||
blue:0.0
|
||||
alpha:1.0]
|
||||
range:NSMakeRange(i, 1)];
|
||||
}
|
||||
ASTextKitAttributes attributes {
|
||||
.attributedString = attrStr
|
||||
};
|
||||
XCTAssert(checkAttributes(attributes, { 100, 100 }));
|
||||
}
|
||||
|
||||
@end
|
||||
146
AsyncDisplayKitTests/ASTextKitTruncationTests.mm
Normal file
146
AsyncDisplayKitTests/ASTextKitTruncationTests.mm
Normal file
@ -0,0 +1,146 @@
|
||||
/* 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 <XCTest/XCTest.h>
|
||||
|
||||
#import "ASTextKitContext.h"
|
||||
#import "ASTextKitTailTruncater.h"
|
||||
|
||||
@interface ASTextKitTruncationTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextKitTruncationTests
|
||||
|
||||
- (NSString *)_sentenceString
|
||||
{
|
||||
return @"90's cray photo booth tote bag bespoke Carles. Plaid wayfarers Odd Future master cleanse tattooed four dollar toast small batch kale chips leggings meh photo booth occupy irony.";
|
||||
}
|
||||
|
||||
- (NSAttributedString *)_sentenceAttributedString
|
||||
{
|
||||
return [[NSAttributedString alloc] initWithString:[self _sentenceString] attributes:@{}];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)_simpleTruncationAttributedString
|
||||
{
|
||||
return [[NSAttributedString alloc] initWithString:@"..." attributes:@{}];
|
||||
}
|
||||
|
||||
- (void)testEmptyTruncationStringSameAsStraightTextKitTailTruncation
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(100, 50);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByWordWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize
|
||||
layoutManagerFactory:nil];
|
||||
__block NSRange textKitVisibleRange;
|
||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
textKitVisibleRange = [layoutManager characterRangeForGlyphRange:[layoutManager glyphRangeForTextContainer:textContainer]
|
||||
actualGlyphRange:NULL];
|
||||
}];
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:nil
|
||||
avoidTailTruncationSet:nil
|
||||
constrainedSize:constrainedSize];
|
||||
XCTAssert(NSEqualRanges(textKitVisibleRange, tailTruncater.visibleRanges[0]));
|
||||
}
|
||||
|
||||
- (void)testSimpleTailTruncation
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(100, 60);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByWordWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize
|
||||
layoutManagerFactory:nil];
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]
|
||||
constrainedSize:constrainedSize];
|
||||
__block NSString *drawnString;
|
||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
drawnString = textStorage.string;
|
||||
}];
|
||||
NSString *expectedString = @"90's cray photo booth tote bag bespoke Carles. Plaid wayfarers...";
|
||||
XCTAssertEqualObjects(expectedString, drawnString);
|
||||
XCTAssert(NSEqualRanges(NSMakeRange(0, 62), tailTruncater.visibleRanges[0]));
|
||||
}
|
||||
|
||||
- (void)testAvoidedCharTailWordBoundaryTruncation
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(100, 50);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByWordWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize
|
||||
layoutManagerFactory:nil];
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
|
||||
constrainedSize:constrainedSize];
|
||||
(void)tailTruncater;
|
||||
__block NSString *drawnString;
|
||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
drawnString = textStorage.string;
|
||||
}];
|
||||
// This should have removed the additional "." in the string right after Carles.
|
||||
NSString *expectedString = @"90's cray photo booth tote bag bespoke Carles...";
|
||||
XCTAssertEqualObjects(expectedString, drawnString);
|
||||
}
|
||||
|
||||
- (void)testAvoidedCharTailCharBoundaryTruncation
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(50, 50);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByCharWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize
|
||||
layoutManagerFactory:nil];
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
|
||||
constrainedSize:constrainedSize];
|
||||
// So Xcode doesn't yell at me for an unused var...
|
||||
(void)tailTruncater;
|
||||
__block NSString *drawnString;
|
||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
drawnString = textStorage.string;
|
||||
}];
|
||||
// This should have removed the additional "." in the string right after Carles.
|
||||
NSString *expectedString = @"90's cray photo booth t...";
|
||||
XCTAssertEqualObjects(expectedString, drawnString);
|
||||
}
|
||||
|
||||
- (void)testHandleZeroHeightConstrainedSize
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(50, 0);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByCharWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize
|
||||
layoutManagerFactory:nil];
|
||||
XCTAssertNoThrow([[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
|
||||
constrainedSize:constrainedSize]);
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,178 +0,0 @@
|
||||
/* 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 <XCTest/XCTest.h>
|
||||
|
||||
#import "ASTextNodeRenderer.h"
|
||||
|
||||
@interface ASTextNodeRendererTests : XCTestCase
|
||||
|
||||
@property (nonatomic, readwrite, strong) ASTextNodeRenderer *renderer;
|
||||
@property (nonatomic, copy, readwrite) NSAttributedString *attributedString;
|
||||
@property (nonatomic, copy, readwrite) NSAttributedString *truncationString;
|
||||
@property (nonatomic, readwrite, assign) NSLineBreakMode truncationMode;
|
||||
@property (nonatomic, readwrite, assign) NSUInteger maximumLineCount;
|
||||
@property (nonatomic, readwrite, assign) CGFloat lineSpacing;
|
||||
|
||||
@property (nonatomic, readwrite, assign) CGSize constrainedSize;
|
||||
@property (nonatomic, readwrite) NSArray *exclusionPaths;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeRendererTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
_truncationMode = NSLineBreakByWordWrapping;
|
||||
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
|
||||
_lineSpacing = 14.0;
|
||||
paragraphStyle.lineSpacing = _lineSpacing;
|
||||
paragraphStyle.maximumLineHeight = _lineSpacing;
|
||||
paragraphStyle.minimumLineHeight = _lineSpacing;
|
||||
NSDictionary *attributes = @{ NSFontAttributeName : [UIFont systemFontOfSize:12.0],
|
||||
NSParagraphStyleAttributeName : paragraphStyle };
|
||||
_attributedString = [[NSAttributedString alloc] initWithString:@"Lorem ipsum" attributes:attributes];
|
||||
_truncationString = [[NSAttributedString alloc] initWithString:@"More"];
|
||||
|
||||
_exclusionPaths = nil;
|
||||
|
||||
_constrainedSize = CGSizeMake(FLT_MAX, FLT_MAX);
|
||||
}
|
||||
|
||||
- (void)setUpRenderer
|
||||
{
|
||||
_renderer = [[ASTextNodeRenderer alloc] initWithAttributedString:_attributedString
|
||||
truncationString:_truncationString
|
||||
truncationMode:_truncationMode
|
||||
maximumLineCount:_maximumLineCount
|
||||
exclusionPaths:_exclusionPaths
|
||||
constrainedSize:_constrainedSize];
|
||||
|
||||
}
|
||||
|
||||
- (void)testCalculateSize
|
||||
{
|
||||
[self setUpRenderer];
|
||||
|
||||
CGSize size = [_renderer size];
|
||||
XCTAssertTrue(size.width > 0, @"Should have a nonzero width");
|
||||
XCTAssertTrue(size.height > 0, @"Should have a nonzero height");
|
||||
}
|
||||
|
||||
- (void)testCalculateSizeWithEmptyString
|
||||
{
|
||||
_attributedString = [[NSAttributedString alloc] initWithString:@""];
|
||||
[self setUpRenderer];
|
||||
CGSize size = [_renderer size];
|
||||
XCTAssertTrue(CGSizeEqualToSize(CGSizeZero, size), @"Empty NSAttributedString should result in CGSizeZero");
|
||||
}
|
||||
|
||||
- (void)testCalculateSizeWithNilString
|
||||
{
|
||||
_attributedString = nil;
|
||||
[self setUpRenderer];
|
||||
CGSize size = [_renderer size];
|
||||
XCTAssertTrue(CGSizeEqualToSize(CGSizeZero, size), @"Nil NSAttributedString should result in CGSizeZero");
|
||||
}
|
||||
|
||||
- (void)testNumberOfLines
|
||||
{
|
||||
[self setUpRenderer];
|
||||
CGSize size = [_renderer size];
|
||||
NSInteger numberOfLines = size.height / _lineSpacing;
|
||||
XCTAssertTrue(numberOfLines == 1 , @"If constrained height (%f) is float max, then there should only be one line of text. Size %@", _constrainedSize.width, NSStringFromCGSize(size));
|
||||
}
|
||||
|
||||
- (void)testMaximumLineCount
|
||||
{
|
||||
NSArray *lines = [NSArray arrayWithObjects:@"Hello!", @"world!", @"foo", @"bar", @"baz", nil];
|
||||
_maximumLineCount = 2;
|
||||
for (int i = 0; i <= [lines count]; i++) {
|
||||
NSString *line = [[lines subarrayWithRange:NSMakeRange(0, i)] componentsJoinedByString:@"\n"];
|
||||
_attributedString = [[NSAttributedString alloc] initWithString:line];
|
||||
[self setUpRenderer];
|
||||
[_renderer size];
|
||||
XCTAssertTrue(_renderer.lineCount <= _maximumLineCount, @"The line count %tu after rendering should be no larger than the maximum line count %tu", _renderer.lineCount, _maximumLineCount);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testNoTruncationIfEnoughSpace
|
||||
{
|
||||
[self setUpRenderer];
|
||||
[_renderer size];
|
||||
NSRange stringRange = NSMakeRange(0, _attributedString.length);
|
||||
NSRange visibleRange = [_renderer visibleRange];
|
||||
XCTAssertTrue(NSEqualRanges(stringRange, visibleRange), @"There should be no truncation if the text has plenty of space to lay out");
|
||||
XCTAssertTrue(NSEqualRanges([_renderer truncationStringCharacterRange], NSMakeRange(NSNotFound, _truncationString.length)), @"There should be no range for the truncation string if no truncation is occurring");
|
||||
}
|
||||
|
||||
- (void)testTruncation
|
||||
{
|
||||
[self setUpRenderer];
|
||||
CGSize calculatedSize = [_renderer size];
|
||||
|
||||
// Make the constrained size just a *little* too small
|
||||
_constrainedSize = CGSizeMake(calculatedSize.width - 2, calculatedSize.height);
|
||||
_renderer = nil;
|
||||
[self setUpRenderer];
|
||||
[_renderer size];
|
||||
NSRange stringRange = NSMakeRange(0, _attributedString.length);
|
||||
NSRange visibleRange = [_renderer visibleRange];
|
||||
XCTAssertTrue(visibleRange.length < stringRange.length, @"Some truncation should occur if the constrained size is smaller than the previously calculated bounding size. String length %tu, visible range %@", _attributedString.length, NSStringFromRange(visibleRange));
|
||||
NSRange truncationRange = [_renderer truncationStringCharacterRange];
|
||||
XCTAssertTrue(truncationRange.location == NSMaxRange(visibleRange), @"Truncation location (%zd) should be after the end of the visible range (%zd)", truncationRange.location, NSMaxRange(visibleRange));
|
||||
XCTAssertTrue(truncationRange.length == _truncationString.length, @"Truncation string length (%zd) should be the full length of the supplied truncation string (%@)", truncationRange.length, _truncationString.string);
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't want to decrease the total number of lines, i.e. truncate too aggressively,
|
||||
* But we also don't want to add extra lines just to display our truncation message
|
||||
*/
|
||||
- (void)testTruncationConservesOriginalHeight
|
||||
{
|
||||
[self setUpRenderer];
|
||||
CGSize calculatedSize = [_renderer size];
|
||||
|
||||
// Make the constrained size just a *little* too small
|
||||
_constrainedSize = CGSizeMake(calculatedSize.width - 1, calculatedSize.height);
|
||||
[self setUpRenderer];
|
||||
CGSize calculatedSizeWithTruncation = [_renderer size];
|
||||
// Floating point equality
|
||||
XCTAssertTrue(fabs(calculatedSizeWithTruncation.height - calculatedSize.height) < .001, @"The height after truncation (%f) doesn't match the normal calculated height (%f)", calculatedSizeWithTruncation.height, calculatedSize.height);
|
||||
}
|
||||
|
||||
- (void)testNoCrashOnTappingEmptyTextNode
|
||||
{
|
||||
_attributedString = [[NSAttributedString alloc] initWithString:@""];
|
||||
[self setUpRenderer];
|
||||
[_renderer size];
|
||||
[_renderer enumerateTextIndexesAtPosition:CGPointZero usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) {
|
||||
XCTFail(@"Shouldn't be any text indexes to enumerate");
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)testExclusionPaths
|
||||
{
|
||||
_constrainedSize = CGSizeMake(200, CGFLOAT_MAX);
|
||||
[self setUpRenderer];
|
||||
CGSize sizeWithoutExclusionPath = [_renderer size];
|
||||
|
||||
CGRect exclusionRect = CGRectMake(20, 0, 180, _lineSpacing * 2.0);
|
||||
_exclusionPaths = @[[UIBezierPath bezierPathWithRect:exclusionRect]];
|
||||
[self setUpRenderer];
|
||||
CGSize sizeWithExclusionPath = [_renderer size];
|
||||
|
||||
XCTAssertEqualWithAccuracy(sizeWithoutExclusionPath.height + exclusionRect.size.height, sizeWithExclusionPath.height, 0.5, @"Using an exclusion path so the the text can not fit into the first two lines should increment the size of the text by the heigth of the exclusion path");
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,156 +0,0 @@
|
||||
/* 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 <XCTest/XCTest.h>
|
||||
|
||||
#import "ASTextNodeShadower.h"
|
||||
|
||||
@interface ASTextNodeShadowerTests : XCTestCase
|
||||
|
||||
@property (nonatomic, readwrite, strong) ASTextNodeShadower *shadower;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeShadowerTests
|
||||
|
||||
- (void)testInstantiation
|
||||
{
|
||||
CGSize shadowOffset = CGSizeMake(3, 5);
|
||||
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
|
||||
CGFloat shadowOpacity = 0.3;
|
||||
CGFloat shadowRadius = 4.2;
|
||||
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
|
||||
shadowColor:shadowColor
|
||||
shadowOpacity:shadowOpacity
|
||||
shadowRadius:shadowRadius];
|
||||
XCTAssertNotNil(_shadower, @"Couldn't instantiate shadow drawer");
|
||||
XCTAssertTrue(CGSizeEqualToSize(_shadower.shadowOffset, shadowOffset), @"Failed to set shadowOffset (%@) to %@", NSStringFromCGSize(_shadower.shadowOffset), NSStringFromCGSize(shadowOffset));
|
||||
XCTAssertTrue(_shadower.shadowColor == shadowColor, @"Failed to set shadowColor (%@) to %@", _shadower.shadowColor, shadowColor);
|
||||
XCTAssertTrue(_shadower.shadowOpacity == shadowOpacity, @"Failed to set shadowOpacity (%f) to %f", _shadower.shadowOpacity, shadowOpacity);
|
||||
XCTAssertTrue(_shadower.shadowRadius == shadowRadius, @"Failed to set shadowRadius (%f) to %f", _shadower.shadowRadius, shadowRadius);
|
||||
CGColorRelease(shadowColor);
|
||||
}
|
||||
|
||||
- (void)testNoShadowIfNoRadiusAndNoOffset
|
||||
{
|
||||
CGSize shadowOffset = CGSizeZero;
|
||||
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
|
||||
CGFloat shadowOpacity = 0.3;
|
||||
CGFloat shadowRadius = 0;
|
||||
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
|
||||
shadowColor:shadowColor
|
||||
shadowOpacity:shadowOpacity
|
||||
shadowRadius:shadowRadius];
|
||||
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, UIEdgeInsetsZero), @"There should be no shadow padding if shadow radius is zero");
|
||||
CGColorRelease(shadowColor);
|
||||
}
|
||||
|
||||
- (void)testShadowIfOffsetButNoRadius
|
||||
{
|
||||
CGSize shadowOffset = CGSizeMake(3, 5);
|
||||
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
|
||||
CGFloat shadowOpacity = 0.3;
|
||||
CGFloat shadowRadius = 0;
|
||||
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
|
||||
shadowColor:shadowColor
|
||||
shadowOpacity:shadowOpacity
|
||||
shadowRadius:shadowRadius];
|
||||
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
|
||||
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(0, 0, -5, -3);
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, expectedInsets), @"Expected insets %@, encountered insets %@", NSStringFromUIEdgeInsets(expectedInsets), NSStringFromUIEdgeInsets(shadowPadding));
|
||||
CGColorRelease(shadowColor);
|
||||
}
|
||||
|
||||
- (void)testNoShadowIfNoOpacity
|
||||
{
|
||||
CGSize shadowOffset = CGSizeMake(3, 5);
|
||||
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
|
||||
CGFloat shadowOpacity = 0;
|
||||
CGFloat shadowRadius = 4;
|
||||
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
|
||||
shadowColor:shadowColor
|
||||
shadowOpacity:shadowOpacity
|
||||
shadowRadius:shadowRadius];
|
||||
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, UIEdgeInsetsZero), @"There should be no shadow padding if shadow opacity is zero");
|
||||
CGColorRelease(shadowColor);
|
||||
}
|
||||
|
||||
- (void)testShadowPaddingForRadiusOf4
|
||||
{
|
||||
CGSize shadowOffset = CGSizeZero;
|
||||
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
|
||||
CGFloat shadowOpacity = 1;
|
||||
CGFloat shadowRadius = 4;
|
||||
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
|
||||
shadowColor:shadowColor
|
||||
shadowOpacity:shadowOpacity
|
||||
shadowRadius:shadowRadius];
|
||||
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
|
||||
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(-shadowRadius, -shadowRadius, -shadowRadius, -shadowRadius);
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, expectedInsets), @"Unexpected edge insets %@ for radius of %f ", NSStringFromUIEdgeInsets(shadowPadding), shadowRadius);
|
||||
CGColorRelease(shadowColor);
|
||||
}
|
||||
|
||||
- (void)testShadowPaddingForRadiusOf4OffsetOf11
|
||||
{
|
||||
CGSize shadowOffset = CGSizeMake(1, 1);
|
||||
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
|
||||
CGFloat shadowOpacity = 1;
|
||||
CGFloat shadowRadius = 4;
|
||||
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
|
||||
shadowColor:shadowColor
|
||||
shadowOpacity:shadowOpacity
|
||||
shadowRadius:shadowRadius];
|
||||
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
|
||||
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(-shadowRadius + shadowOffset.height, // Top: -3
|
||||
-shadowRadius + shadowOffset.width, // Left: -3
|
||||
-shadowRadius - shadowOffset.height, // Bottom: -5
|
||||
-shadowRadius - shadowOffset.width); // Right: -5
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, expectedInsets), @"Unexpected edge insets %@ for radius of %f ", NSStringFromUIEdgeInsets(shadowPadding), shadowRadius);
|
||||
CGColorRelease(shadowColor);
|
||||
}
|
||||
|
||||
- (void)testShadowPaddingForRadiusOf4OffsetOfNegative11
|
||||
{
|
||||
CGSize shadowOffset = CGSizeMake(-1, -1);
|
||||
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
|
||||
CGFloat shadowOpacity = 1;
|
||||
CGFloat shadowRadius = 4;
|
||||
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
|
||||
shadowColor:shadowColor
|
||||
shadowOpacity:shadowOpacity
|
||||
shadowRadius:shadowRadius];
|
||||
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
|
||||
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(-shadowRadius + shadowOffset.height, // Top: -3
|
||||
-shadowRadius + shadowOffset.width, // Left: -5
|
||||
-shadowRadius - shadowOffset.height, // Bottom: -5
|
||||
-shadowRadius - shadowOffset.width); // Right: -3
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, expectedInsets), @"Unexpected edge insets %@ for radius of %f ", NSStringFromUIEdgeInsets(shadowPadding), shadowRadius);
|
||||
CGColorRelease(shadowColor);
|
||||
}
|
||||
|
||||
- (void)testASDNEdgeInsetsInvert
|
||||
{
|
||||
UIEdgeInsets insets = UIEdgeInsetsMake(-5, -7, -3, -2);
|
||||
UIEdgeInsets invertedInsets = ASDNEdgeInsetsInvert(insets);
|
||||
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(5, 7, 3, 2);
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(invertedInsets, expectedInsets), @"Expected %@, actual result %@", NSStringFromUIEdgeInsets(expectedInsets), NSStringFromUIEdgeInsets(invertedInsets));
|
||||
}
|
||||
|
||||
- (void)testASDNEdgeInsetsInvertDoubleNegation
|
||||
{
|
||||
CGRect originalRect = CGRectMake(31, 32, 33, 34);
|
||||
UIEdgeInsets insets = UIEdgeInsetsMake(-5, -7, -3, -2);
|
||||
CGRect insettedRect = UIEdgeInsetsInsetRect(originalRect, insets);
|
||||
CGRect outsettedInsettedRect = UIEdgeInsetsInsetRect(insettedRect, ASDNEdgeInsetsInvert(insets));
|
||||
XCTAssertTrue(CGRectEqualToRect(originalRect, outsettedInsettedRect), @"Insetting a CGRect, and then outsetting it (insetting with the negated edge insets) should return the original CGRect");
|
||||
}
|
||||
|
||||
@end
|
||||
@ -14,12 +14,16 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
|
||||
{
|
||||
return fabs(size1.width - size2.width) < delta && fabs(size1.height - size2.height) < delta;
|
||||
}
|
||||
|
||||
@interface ASTextNodeTestDelegate : NSObject <ASTextNodeDelegate>
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *tappedLinkAttribute;
|
||||
@property (nonatomic, assign, readonly) id tappedLinkValue;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeTestDelegate
|
||||
@ -103,8 +107,8 @@
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_textNode measure:constrainedSize];
|
||||
CGSize recalculatedSize = [_textNode measure:calculatedSize];
|
||||
|
||||
XCTAssertTrue(CGSizeEqualToSize(calculatedSize, recalculatedSize), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
|
||||
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 4.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +119,7 @@
|
||||
CGSize calculatedSize = [_textNode measure:constrainedSize];
|
||||
CGSize recalculatedSize = [_textNode measure:calculatedSize];
|
||||
|
||||
XCTAssertTrue(CGSizeEqualToSize(calculatedSize, recalculatedSize), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 11.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "ASTextNodeTextKitHelpers.h"
|
||||
#import "ASTextKitHelpers.h"
|
||||
#import "ASTextNodeTypes.h"
|
||||
#import "ASTextNodeWordKerner.h"
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
_nameNode = [[ASTextNode alloc] init];
|
||||
_nameNode.attributedString = [[NSAttributedString alloc] initWithString:_post.name
|
||||
attributes:[TextStyles nameStyle]];
|
||||
_nameNode.maximumLineCount = 1;
|
||||
_nameNode.maximumNumberOfLines = 1;
|
||||
[self addSubnode:_nameNode];
|
||||
|
||||
// username node
|
||||
@ -40,7 +40,7 @@
|
||||
attributes:[TextStyles usernameStyle]];
|
||||
_usernameNode.flexShrink = YES; //if name and username don't fit to cell width, allow username shrink
|
||||
_usernameNode.truncationMode = NSLineBreakByTruncatingTail;
|
||||
_usernameNode.maximumLineCount = 1;
|
||||
_usernameNode.maximumNumberOfLines = 1;
|
||||
|
||||
[self addSubnode:_usernameNode];
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user