Add Experimental Text Node Implementation (#259)

* Add experimental text node implementation, based on YYText

* Fix warnings and alert when unimplemented experimental features are used.

* Address feedback from review

* Extend the cthulog

* Update license headers
This commit is contained in:
Adlai Holler
2017-05-14 12:02:07 -07:00
committed by GitHub
parent 6d113f7a9d
commit d4725a51f2
26 changed files with 10503 additions and 8 deletions

View File

@@ -365,6 +365,26 @@
CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */; }; CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */; };
CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; }; CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; };
CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
CCCCCCD51EC3EF060087FE10 /* ASTextDebugOption.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC31EC3EF060087FE10 /* ASTextDebugOption.h */; };
CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCC41EC3EF060087FE10 /* ASTextDebugOption.m */; };
CCCCCCD71EC3EF060087FE10 /* ASTextInput.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC51EC3EF060087FE10 /* ASTextInput.h */; };
CCCCCCD81EC3EF060087FE10 /* ASTextInput.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCC61EC3EF060087FE10 /* ASTextInput.m */; };
CCCCCCD91EC3EF060087FE10 /* ASTextLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC71EC3EF060087FE10 /* ASTextLayout.h */; };
CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCC81EC3EF060087FE10 /* ASTextLayout.m */; };
CCCCCCDB1EC3EF060087FE10 /* ASTextLine.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC91EC3EF060087FE10 /* ASTextLine.h */; };
CCCCCCDC1EC3EF060087FE10 /* ASTextLine.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCCA1EC3EF060087FE10 /* ASTextLine.m */; };
CCCCCCDD1EC3EF060087FE10 /* ASTextAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCCC1EC3EF060087FE10 /* ASTextAttribute.h */; };
CCCCCCDE1EC3EF060087FE10 /* ASTextAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCCD1EC3EF060087FE10 /* ASTextAttribute.m */; };
CCCCCCDF1EC3EF060087FE10 /* ASTextRunDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCCE1EC3EF060087FE10 /* ASTextRunDelegate.h */; };
CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCCF1EC3EF060087FE10 /* ASTextRunDelegate.m */; };
CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCD11EC3EF060087FE10 /* ASTextUtilities.h */; };
CCCCCCE21EC3EF060087FE10 /* ASTextUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD21EC3EF060087FE10 /* ASTextUtilities.m */; };
CCCCCCE31EC3EF060087FE10 /* NSParagraphStyle+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCD31EC3EF060087FE10 /* NSParagraphStyle+ASText.h */; };
CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */; };
CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */; };
CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */; };
CCD523111EBD658C001F2191 /* ASTextNode2.h in Headers */ = {isa = PBXBuildFile; fileRef = CCD5230F1EBD658C001F2191 /* ASTextNode2.h */; };
CCD523121EBD658C001F2191 /* ASTextNode2.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCD523101EBD658C001F2191 /* ASTextNode2.mm */; };
CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; };
DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; };
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
@@ -796,6 +816,26 @@
CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeManagingNode.h; sourceTree = "<group>"; }; CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeManagingNode.h; sourceTree = "<group>"; };
CCBD05DE1E4147B000D18509 /* ASIGListAdapterBasedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIGListAdapterBasedDataSource.m; sourceTree = "<group>"; }; CCBD05DE1E4147B000D18509 /* ASIGListAdapterBasedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIGListAdapterBasedDataSource.m; sourceTree = "<group>"; };
CCBD05DF1E4147B000D18509 /* ASIGListAdapterBasedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIGListAdapterBasedDataSource.h; sourceTree = "<group>"; }; CCBD05DF1E4147B000D18509 /* ASIGListAdapterBasedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIGListAdapterBasedDataSource.h; sourceTree = "<group>"; };
CCCCCCC31EC3EF060087FE10 /* ASTextDebugOption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextDebugOption.h; sourceTree = "<group>"; };
CCCCCCC41EC3EF060087FE10 /* ASTextDebugOption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextDebugOption.m; sourceTree = "<group>"; };
CCCCCCC51EC3EF060087FE10 /* ASTextInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextInput.h; sourceTree = "<group>"; };
CCCCCCC61EC3EF060087FE10 /* ASTextInput.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextInput.m; sourceTree = "<group>"; };
CCCCCCC71EC3EF060087FE10 /* ASTextLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextLayout.h; sourceTree = "<group>"; };
CCCCCCC81EC3EF060087FE10 /* ASTextLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextLayout.m; sourceTree = "<group>"; };
CCCCCCC91EC3EF060087FE10 /* ASTextLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextLine.h; sourceTree = "<group>"; };
CCCCCCCA1EC3EF060087FE10 /* ASTextLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextLine.m; sourceTree = "<group>"; };
CCCCCCCC1EC3EF060087FE10 /* ASTextAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextAttribute.h; sourceTree = "<group>"; };
CCCCCCCD1EC3EF060087FE10 /* ASTextAttribute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextAttribute.m; sourceTree = "<group>"; };
CCCCCCCE1EC3EF060087FE10 /* ASTextRunDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextRunDelegate.h; sourceTree = "<group>"; };
CCCCCCCF1EC3EF060087FE10 /* ASTextRunDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextRunDelegate.m; sourceTree = "<group>"; };
CCCCCCD11EC3EF060087FE10 /* ASTextUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextUtilities.h; sourceTree = "<group>"; };
CCCCCCD21EC3EF060087FE10 /* ASTextUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextUtilities.m; sourceTree = "<group>"; };
CCCCCCD31EC3EF060087FE10 /* NSParagraphStyle+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSParagraphStyle+ASText.h"; sourceTree = "<group>"; };
CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSParagraphStyle+ASText.m"; sourceTree = "<group>"; };
CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+ASText.h"; sourceTree = "<group>"; };
CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+ASText.m"; sourceTree = "<group>"; };
CCD5230F1EBD658C001F2191 /* ASTextNode2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNode2.h; sourceTree = "<group>"; };
CCD523101EBD658C001F2191 /* ASTextNode2.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNode2.mm; sourceTree = "<group>"; };
CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = "<group>"; }; CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = "<group>"; };
CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListAdapter+AsyncDisplayKit.h"; sourceTree = "<group>"; }; CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListAdapter+AsyncDisplayKit.h"; sourceTree = "<group>"; };
CCE04B211E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "IGListAdapter+AsyncDisplayKit.m"; sourceTree = "<group>"; }; CCE04B211E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "IGListAdapter+AsyncDisplayKit.m"; sourceTree = "<group>"; };
@@ -1220,6 +1260,8 @@
058D0A01195D050800B7D73C /* Private */ = { 058D0A01195D050800B7D73C /* Private */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CCD5230F1EBD658C001F2191 /* ASTextNode2.h */,
CCD523101EBD658C001F2191 /* ASTextNode2.mm */,
CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */, CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */,
CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */, CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */,
CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */, CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */,
@@ -1294,6 +1336,7 @@
CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */, CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */,
83A7D9581D44542100BF333E /* ASWeakMap.h */, 83A7D9581D44542100BF333E /* ASWeakMap.h */,
83A7D9591D44542100BF333E /* ASWeakMap.m */, 83A7D9591D44542100BF333E /* ASWeakMap.m */,
CCCCCCC11EC3EF060087FE10 /* TextExperiment */,
); );
path = Private; path = Private;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1432,6 +1475,55 @@
path = Layout; path = Layout;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CCCCCCC11EC3EF060087FE10 /* TextExperiment */ = {
isa = PBXGroup;
children = (
CCCCCCC21EC3EF060087FE10 /* Component */,
CCCCCCCB1EC3EF060087FE10 /* String */,
CCCCCCD01EC3EF060087FE10 /* Utility */,
);
path = TextExperiment;
sourceTree = "<group>";
};
CCCCCCC21EC3EF060087FE10 /* Component */ = {
isa = PBXGroup;
children = (
CCCCCCC31EC3EF060087FE10 /* ASTextDebugOption.h */,
CCCCCCC41EC3EF060087FE10 /* ASTextDebugOption.m */,
CCCCCCC51EC3EF060087FE10 /* ASTextInput.h */,
CCCCCCC61EC3EF060087FE10 /* ASTextInput.m */,
CCCCCCC71EC3EF060087FE10 /* ASTextLayout.h */,
CCCCCCC81EC3EF060087FE10 /* ASTextLayout.m */,
CCCCCCC91EC3EF060087FE10 /* ASTextLine.h */,
CCCCCCCA1EC3EF060087FE10 /* ASTextLine.m */,
);
path = Component;
sourceTree = "<group>";
};
CCCCCCCB1EC3EF060087FE10 /* String */ = {
isa = PBXGroup;
children = (
CCCCCCCC1EC3EF060087FE10 /* ASTextAttribute.h */,
CCCCCCCD1EC3EF060087FE10 /* ASTextAttribute.m */,
CCCCCCCE1EC3EF060087FE10 /* ASTextRunDelegate.h */,
CCCCCCCF1EC3EF060087FE10 /* ASTextRunDelegate.m */,
);
path = String;
sourceTree = "<group>";
};
CCCCCCD01EC3EF060087FE10 /* Utility */ = {
isa = PBXGroup;
children = (
CCCCCCD11EC3EF060087FE10 /* ASTextUtilities.h */,
CCCCCCD21EC3EF060087FE10 /* ASTextUtilities.m */,
CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */,
CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */,
CCCCCCD31EC3EF060087FE10 /* NSParagraphStyle+ASText.h */,
CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */,
);
path = Utility;
sourceTree = "<group>";
};
CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */ = { CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1509,6 +1601,7 @@
E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */, E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */,
E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */, E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */,
E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */, E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */,
CCCCCCE31EC3EF060087FE10 /* NSParagraphStyle+ASText.h in Headers */,
E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */, E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */,
E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */, E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */,
696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */, 696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */,
@@ -1528,7 +1621,9 @@
B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */,
68C215581DE10D330019C4BC /* ASCollectionViewLayoutInspector.h in Headers */, 68C215581DE10D330019C4BC /* ASCollectionViewLayoutInspector.h in Headers */,
B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */, B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */,
CCD523111EBD658C001F2191 /* ASTextNode2.h in Headers */,
B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */, B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */,
CCCCCCD71EC3EF060087FE10 /* ASTextInput.h in Headers */,
B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */, B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */,
9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */, 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */,
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */, 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */,
@@ -1558,6 +1653,7 @@
B35062171B010EFD0018CF92 /* ASDataController.h in Headers */, B35062171B010EFD0018CF92 /* ASDataController.h in Headers */,
34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */, 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */,
68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */, 68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */,
CCCCCCD91EC3EF060087FE10 /* ASTextLayout.h in Headers */,
A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */, A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */,
9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */, 9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */,
CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */, CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */,
@@ -1581,10 +1677,12 @@
698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */, 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */,
9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */, 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */,
DE7EF4F81DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h in Headers */, DE7EF4F81DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h in Headers */,
CCCCCCD51EC3EF060087FE10 /* ASTextDebugOption.h in Headers */,
CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */, CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */,
254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */,
B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */,
68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */,
CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */,
B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */,
CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */, CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */,
B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */, B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */,
@@ -1598,6 +1696,7 @@
69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */,
254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */, 254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */,
68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */, 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */,
CCCCCCDD1EC3EF060087FE10 /* ASTextAttribute.h in Headers */,
B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */, B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */,
044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */, 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */,
AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */, AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */,
@@ -1673,7 +1772,10 @@
B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */,
CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */, CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */,
25E327571C16819500A2170C /* ASPagerNode.h in Headers */, 25E327571C16819500A2170C /* ASPagerNode.h in Headers */,
CCCCCCDB1EC3EF060087FE10 /* ASTextLine.h in Headers */,
9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */, 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */,
CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */,
CCCCCCDF1EC3EF060087FE10 /* ASTextRunDelegate.h in Headers */,
9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */,
34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */,
CC0F885C1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h in Headers */, CC0F885C1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h in Headers */,
@@ -1957,6 +2059,7 @@
9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */, 9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */,
3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */, 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */,
CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */, CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */,
CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */,
8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */, 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */,
B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */,
690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */, 690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */,
@@ -1986,6 +2089,7 @@
E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */, E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */,
9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */, 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */,
CCCCCCD81EC3EF060087FE10 /* ASTextInput.m in Sources */,
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */,
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
@@ -2007,10 +2111,12 @@
8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */,
B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */,
767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */, 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */,
CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.m in Sources */,
34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */, 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */,
B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */,
25E327591C16819500A2170C /* ASPagerNode.m in Sources */, 25E327591C16819500A2170C /* ASPagerNode.m in Sources */,
636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */, 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */,
CCD523121EBD658C001F2191 /* ASTextNode2.mm in Sources */,
B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */, B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */,
DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */, DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */,
254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */, 254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */,
@@ -2039,6 +2145,8 @@
34EFC75E1B701BF000AD841F /* ASInternalHelpers.m in Sources */, 34EFC75E1B701BF000AD841F /* ASInternalHelpers.m in Sources */,
34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */, 34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */,
DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.m in Sources */,
CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.m in Sources */,
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */,
34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */,
CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */, CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */,
@@ -2049,6 +2157,7 @@
B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */, B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */,
34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */, 34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */,
044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m in Sources */, 044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m in Sources */,
CCCCCCDE1EC3EF060087FE10 /* ASTextAttribute.m in Sources */,
CCA282B51E9EA7310037E8B7 /* ASTipsController.m in Sources */, CCA282B51E9EA7310037E8B7 /* ASTipsController.m in Sources */,
B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */, B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */,
0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */, 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */,
@@ -2073,7 +2182,9 @@
E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m in Sources */, E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m in Sources */,
DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */,
68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */, 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */,
CCCCCCDC1EC3EF060087FE10 /* ASTextLine.m in Sources */,
34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */,
CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */,
690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */, 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */,
68C2155A1DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */, 68C2155A1DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */,
DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */, DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */,
@@ -2086,6 +2197,7 @@
254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */, 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */,
34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */,
254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */,
CCCCCCE21EC3EF060087FE10 /* ASTextUtilities.m in Sources */,
CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.m in Sources */, CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.m in Sources */,
697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */,
B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */,

View File

@@ -18,5 +18,6 @@
- [ASDisplayNode] Pass drawParameter in rendering context callbacks [Michael Schneider](https://github.com/maicki)[#248](https://github.com/TextureGroup/Texture/pull/248) - [ASDisplayNode] Pass drawParameter in rendering context callbacks [Michael Schneider](https://github.com/maicki)[#248](https://github.com/TextureGroup/Texture/pull/248)
- [ASTextNode] Move to class method of drawRect:withParameters:isCancelled:isRasterizing: for drawing [Michael Schneider] (https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232) - [ASTextNode] Move to class method of drawRect:withParameters:isCancelled:isRasterizing: for drawing [Michael Schneider] (https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232)
- [ASDisplayNode] Remove instance:-drawRect:withParameters:isCancelled:isRasterizing: (https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232) - [ASDisplayNode] Remove instance:-drawRect:withParameters:isCancelled:isRasterizing: (https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232)
- [ASTextNode] Add an experimental new implementation. See `+[ASTextNode setExperimentOptions:]`. [Adlai Holler](https://github.com/Adlai-Holler)[#259](https://github.com/TextureGroup/Texture/pull/259)
- [ASVideoNode] Added error reporing to ASVideoNode and it's delegate [#260](https://github.com/TextureGroup/Texture/pull/260) - [ASVideoNode] Added error reporing to ASVideoNode and it's delegate [#260](https://github.com/TextureGroup/Texture/pull/260)
- [ASCollectionNode] Fixed conversion of item index paths between node & view. [Adlai Holler](https://github.com/Adlai-Holler) [#262](https://github.com/TextureGroup/Texture/pull/262) - [ASCollectionNode] Fixed conversion of item index paths between node & view. [Adlai Holler](https://github.com/Adlai-Holler) [#262](https://github.com/TextureGroup/Texture/pull/262)

View File

@@ -21,6 +21,18 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
typedef NS_OPTIONS(NSUInteger, ASTextNodeExperimentOptions) {
// All subclass instances use the experimental implementation.
ASTextNodeExperimentSubclasses = 1 << 0,
// Random instances of ASTextNode (50% chance) (not subclasses) use experimental impl.
// Useful for profiling with apps that have no custom text node subclasses.
ASTextNodeExperimentRandomInstances = 1 << 1,
// All instances of ASTextNode itself use experimental implementation. Supersedes `.randomInstances`.
ASTextNodeExperimentAllInstances = 1 << 2,
// Add highlighting etc. for debugging.
ASTextNodeExperimentDebugging = 1 << 3
};
@interface ASTextNode () @interface ASTextNode ()
/** /**
@@ -38,6 +50,19 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nonatomic, assign) UIEdgeInsets textContainerInset; @property (nonatomic, assign) UIEdgeInsets textContainerInset;
/**
* Opt in to an experimental implementation of text node. The implementation may improve performance and correctness,
* but may not support all features and has not been thoroughly tested in production.
*
* @precondition You may not call this after allocating any text nodes. You may only call this once.
*/
+ (void)setExperimentOptions:(ASTextNodeExperimentOptions)options;
/**
* Returns YES if this node is using the experimental implementation. NO otherwise. Will not change.
*/
@property (atomic, readonly) BOOL usingExperiment;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -16,6 +16,7 @@
// //
#import <AsyncDisplayKit/ASTextNode.h> #import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASTextNode2.h>
#import <AsyncDisplayKit/ASTextNode+Beta.h> #import <AsyncDisplayKit/ASTextNode+Beta.h>
#include <mutex> #include <mutex>
@@ -263,14 +264,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - ASDisplayNode #pragma mark - ASDisplayNode
- (void)clearContents
{
// We discard the backing store and renderer to prevent the very large
// memory overhead of maintaining these for all text nodes. They can be
// regenerated when layout is necessary.
[super clearContents]; // ASDisplayNode will set layer.contents = nil
}
- (void)didLoad - (void)didLoad
{ {
[super didLoad]; [super didLoad];
@@ -1386,6 +1379,70 @@ static NSAttributedString *DefaultTruncationAttributedString()
} }
#endif #endif
static ASDN::Mutex _experimentLock;
static ASTextNodeExperimentOptions _experimentOptions;
static BOOL _hasAllocatedNode;
+ (void)setExperimentOptions:(ASTextNodeExperimentOptions)options
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ASDN::MutexLocker lock(_experimentLock);
// They must call this before allocating any text nodes.
ASDisplayNodeAssertFalse(_hasAllocatedNode);
_experimentOptions = options;
// Set superclass of all subclasses to ASTextNode2
if (options & ASTextNodeExperimentSubclasses) {
unsigned int classCount;
Class originalClass = [ASTextNode class];
Class newClass = [ASTextNode2 class];
Class *classes = objc_copyClassList(&classCount);
for (int i = 0; i < classCount; i++) {
Class c = classes[i];
if (class_getSuperclass(c) == originalClass) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
class_setSuperclass(c, newClass);
#pragma clang diagnostic pop
}
}
free(classes);
}
if (options & ASTextNodeExperimentDebugging) {
[ASTextNode2 enableDebugging];
}
});
}
+ (id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ASDN::MutexLocker lock(_experimentLock);
_hasAllocatedNode = YES;
});
// All instances || (random instances && rand() != 0)
BOOL useExperiment = (_experimentOptions & ASTextNodeExperimentAllInstances)
|| ((_experimentOptions & ASTextNodeExperimentRandomInstances)
&& (arc4random_uniform(2) != 0));
if (useExperiment) {
return (ASTextNode *)[ASTextNode2 allocWithZone:zone];
} else {
return [super allocWithZone:zone];
}
}
- (BOOL)usingExperiment
{
return NO;
}
@end @end
@implementation ASTextNode (Deprecated) @implementation ASTextNode (Deprecated)

View File

@@ -0,0 +1,228 @@
//
// ASTextNode2.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASControlNode.h>
// Import this to get ASTextNodeHighlightStyle
#import <AsyncDisplayKit/ASTextNode.h>
NS_ASSUME_NONNULL_BEGIN
/**
@abstract Draws interactive rich text.
@discussion Backed by the code in TextExperiment folder, on top of CoreText.
*/
@interface ASTextNode2 : ASControlNode
/**
@abstract The styled text displayed by the node.
@discussion Defaults to nil, no text is shown.
For inline image attachments, add an attribute of key NSAttachmentAttributeName, with a value of an NSTextAttachment.
*/
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
#pragma mark - Truncation
/**
@abstract The attributedText to use when the text must be truncated.
@discussion Defaults to a localized ellipsis character.
*/
@property (nullable, nonatomic, copy) NSAttributedString *truncationAttributedText;
/**
@summary The second attributed string appended for truncation.
@discussion This string will be highlighted on touches.
@default nil
*/
@property (nullable, nonatomic, copy) NSAttributedString *additionalTruncationMessage;
/**
@abstract Determines how the text is truncated to fit within the receiver's maximum size.
@discussion Defaults to NSLineBreakByWordWrapping.
@note Setting a truncationMode in attributedString will override the truncation mode set here.
*/
@property (nonatomic, assign) NSLineBreakMode truncationMode;
/**
@abstract If the text node is truncated. Text must have been sized first.
*/
@property (nonatomic, readonly, assign, getter=isTruncated) BOOL truncated;
/**
@abstract The maximum number of lines to render of the text before truncation.
@default 0 (No limit)
*/
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
/**
@abstract The number of lines in the text. Text must have been sized first.
*/
@property (nonatomic, readonly, assign) NSUInteger lineCount;
/**
* An array of path objects representing the regions where text should not be displayed.
*
* @discussion The default value of this property is an empty array. You can
* assign an array of UIBezierPath objects to exclude text from one or more regions in
* the text node's bounds. You can use this property to have text wrap around images,
* shapes or other text like a fancy magazine.
*/
@property (nullable, nonatomic, strong) NSArray<UIBezierPath *> *exclusionPaths;
#pragma mark - Placeholders
/**
* @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES.
*
* @discussion Defaults to NO. When YES, it draws rectangles for each line of text,
* following the true shape of the text's wrapping. This visually mirrors the overall
* shape and weight of paragraphs, making the appearance of the finished text less jarring.
*/
@property (nonatomic, assign) BOOL placeholderEnabled;
/**
@abstract The placeholder color.
*/
@property (nullable, nonatomic, strong) UIColor *placeholderColor;
/**
@abstract Inset each line of the placeholder.
*/
@property (nonatomic, assign) UIEdgeInsets placeholderInsets;
#pragma mark - Shadow
/**
@abstract When you set these ASDisplayNode properties, they are composited into the bitmap instead of being applied by CA.
@property (nonatomic, assign) CGColorRef shadowColor;
@property (nonatomic, assign) CGFloat shadowOpacity;
@property (nonatomic, assign) CGSize shadowOffset;
@property (nonatomic, assign) CGFloat shadowRadius;
*/
/**
@abstract The number of pixels used for shadow padding on each side of the receiver.
@discussion Each inset will be less than or equal to zero, so that applying
UIEdgeInsetsRect(boundingRectForText, shadowPadding)
will return a CGRect large enough to fit both the text and the appropriate shadow padding.
*/
@property (nonatomic, readonly, assign) UIEdgeInsets shadowPadding;
#pragma mark - Positioning
/**
@abstract Returns an array of rects bounding the characters in a given text range.
@param textRange A range of text. Must be valid for the receiver's string.
@discussion Use this method to detect all the different rectangles a given range of text occupies.
The rects returned are not guaranteed to be contiguous (for example, if the given text range spans
a line break, the rects returned will be on opposite sides and different lines). The rects returned
are in the coordinate system of the receiver.
*/
- (NSArray<NSValue *> *)rectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
/**
@abstract Returns an array of rects used for highlighting the characters in a given text range.
@param textRange A range of text. Must be valid for the receiver's string.
@discussion Use this method to detect all the different rectangles the highlights of a given range of text occupies.
The rects returned are not guaranteed to be contiguous (for example, if the given text range spans
a line break, the rects returned will be on opposite sides and different lines). The rects returned
are in the coordinate system of the receiver. This method is useful for visual coordination with a
highlighted range of text.
*/
- (NSArray<NSValue *> *)highlightRectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
/**
@abstract Returns a bounding rect for the given text range.
@param textRange A range of text. Must be valid for the receiver's string.
@discussion The height of the frame returned is that of the receiver's line-height; adjustment for
cap-height and descenders is not performed. This method raises an exception if textRange is not
a valid substring range of the receiver's string.
*/
- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
/**
@abstract Returns the trailing rectangle of space in the receiver, after the final character.
@discussion Use this method to detect which portion of the receiver is not occupied by characters.
The rect returned is in the coordinate system of the receiver.
*/
- (CGRect)trailingRect AS_WARN_UNUSED_RESULT;
#pragma mark - Actions
/**
@abstract The set of attribute names to consider links. Defaults to NSLinkAttributeName.
*/
@property (nonatomic, copy) NSArray<NSString *> *linkAttributeNames;
/**
@abstract Indicates whether the receiver has an entity at a given point.
@param point The point, in the receiver's coordinate system.
@param attributeNameOut The name of the attribute at the point. Can be NULL.
@param rangeOut The ultimate range of the found text. Can be NULL.
@result YES if an entity exists at `point`; NO otherwise.
*/
- (nullable id)linkAttributeValueAtPoint:(CGPoint)point attributeName:(out NSString * _Nullable * _Nullable)attributeNameOut range:(out NSRange * _Nullable)rangeOut AS_WARN_UNUSED_RESULT;
/**
@abstract The style to use when highlighting text.
*/
@property (nonatomic, assign) ASTextNodeHighlightStyle highlightStyle;
/**
@abstract The range of text highlighted by the receiver. Changes to this property are not animated by default.
*/
@property (nonatomic, assign) NSRange highlightRange;
/**
@abstract Set the range of text to highlight, with optional animation.
@param highlightRange The range of text to highlight.
@param animated Whether the text should be highlighted with an animation.
*/
- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated;
/**
@abstract Responds to actions from links in the text node.
@discussion The delegate must be set before the node is loaded, and implement
textNode:longPressedLinkAttribute:value:atPoint:textRange: in order for
the long press gesture recognizer to be installed.
*/
@property (nonatomic, weak) id<ASTextNodeDelegate> delegate;
/**
@abstract If YES and a long press is recognized, touches are cancelled. Default is NO
*/
@property (nonatomic, assign) BOOL longPressCancelsTouches;
/**
@abstract if YES will not intercept touches for non-link areas of the text. Default is NO.
*/
@property (nonatomic, assign) BOOL passthroughNonlinkTouches;
+ (void)enableDebugging;
@end
@interface ASTextNode2 (Unavailable)
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable;
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
//
// ASTextDebugOption.h
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 15/4/8.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
@class ASTextDebugOption;
NS_ASSUME_NONNULL_BEGIN
/**
The ASTextDebugTarget protocol defines the method a debug target should implement.
A debug target can be add to the global container to receive the shared debug
option changed notification.
*/
@protocol ASTextDebugTarget <NSObject>
@required
/**
When the shared debug option changed, this method would be called on main thread.
It should return as quickly as possible. The option's property should not be changed
in this method.
@param option The shared debug option.
*/
- (void)setDebugOption:(nullable ASTextDebugOption *)option;
@end
/**
The debug option for ASText.
*/
@interface ASTextDebugOption : NSObject <NSCopying>
@property (nullable, nonatomic, strong) UIColor *baselineColor; ///< baseline color
@property (nullable, nonatomic, strong) UIColor *CTFrameBorderColor; ///< CTFrame path border color
@property (nullable, nonatomic, strong) UIColor *CTFrameFillColor; ///< CTFrame path fill color
@property (nullable, nonatomic, strong) UIColor *CTLineBorderColor; ///< CTLine bounds border color
@property (nullable, nonatomic, strong) UIColor *CTLineFillColor; ///< CTLine bounds fill color
@property (nullable, nonatomic, strong) UIColor *CTLineNumberColor; ///< CTLine line number color
@property (nullable, nonatomic, strong) UIColor *CTRunBorderColor; ///< CTRun bounds border color
@property (nullable, nonatomic, strong) UIColor *CTRunFillColor; ///< CTRun bounds fill color
@property (nullable, nonatomic, strong) UIColor *CTRunNumberColor; ///< CTRun number color
@property (nullable, nonatomic, strong) UIColor *CGGlyphBorderColor; ///< CGGlyph bounds border color
@property (nullable, nonatomic, strong) UIColor *CGGlyphFillColor; ///< CGGlyph bounds fill color
- (BOOL)needDrawDebug; ///< `YES`: at least one debug color is visible. `NO`: all debug color is invisible/nil.
- (void)clear; ///< Set all debug color to nil.
/**
Add a debug target.
@discussion When `setSharedDebugOption:` is called, all added debug target will
receive `setDebugOption:` in main thread. It maintains an unsafe_unretained
reference to this target. The target must to removed before dealloc.
@param target A debug target.
*/
+ (void)addDebugTarget:(id<ASTextDebugTarget>)target;
/**
Remove a debug target which is added by `addDebugTarget:`.
@param target A debug target.
*/
+ (void)removeDebugTarget:(id<ASTextDebugTarget>)target;
/**
Returns the shared debug option.
@return The shared debug option, default is nil.
*/
+ (nullable ASTextDebugOption *)sharedDebugOption;
/**
Set a debug option as shared debug option.
This method must be called on main thread.
@discussion When call this method, the new option will set to all debug target
which is added by `addDebugTarget:`.
@param option A new debug option (nil is valid).
*/
+ (void)setSharedDebugOption:(nullable ASTextDebugOption *)option;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,139 @@
//
// ASTextDebugOption.m
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 15/4/8.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "ASTextDebugOption.h"
#import <libkern/OSAtomic.h>
#import <pthread.h>
static pthread_mutex_t _sharedDebugLock;
static CFMutableSetRef _sharedDebugTargets = nil;
static ASTextDebugOption *_sharedDebugOption = nil;
static const void* _sharedDebugSetRetain(CFAllocatorRef allocator, const void *value) {
return value;
}
static void _sharedDebugSetRelease(CFAllocatorRef allocator, const void *value) {
}
void _sharedDebugSetFunction(const void *value, void *context) {
id<ASTextDebugTarget> target = (__bridge id<ASTextDebugTarget>)(value);
[target setDebugOption:_sharedDebugOption];
}
static void _initSharedDebug() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
pthread_mutex_init(&_sharedDebugLock, NULL);
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
callbacks.retain = _sharedDebugSetRetain;
callbacks.release = _sharedDebugSetRelease;
_sharedDebugTargets = CFSetCreateMutable(CFAllocatorGetDefault(), 0, &callbacks);
});
}
static void _setSharedDebugOption(ASTextDebugOption *option) {
_initSharedDebug();
pthread_mutex_lock(&_sharedDebugLock);
_sharedDebugOption = option.copy;
CFSetApplyFunction(_sharedDebugTargets, _sharedDebugSetFunction, NULL);
pthread_mutex_unlock(&_sharedDebugLock);
}
static ASTextDebugOption *_getSharedDebugOption() {
_initSharedDebug();
pthread_mutex_lock(&_sharedDebugLock);
ASTextDebugOption *op = _sharedDebugOption;
pthread_mutex_unlock(&_sharedDebugLock);
return op;
}
static void _addDebugTarget(id<ASTextDebugTarget> target) {
_initSharedDebug();
pthread_mutex_lock(&_sharedDebugLock);
CFSetAddValue(_sharedDebugTargets, (__bridge const void *)(target));
pthread_mutex_unlock(&_sharedDebugLock);
}
static void _removeDebugTarget(id<ASTextDebugTarget> target) {
_initSharedDebug();
pthread_mutex_lock(&_sharedDebugLock);
CFSetRemoveValue(_sharedDebugTargets, (__bridge const void *)(target));
pthread_mutex_unlock(&_sharedDebugLock);
}
@implementation ASTextDebugOption
- (id)copyWithZone:(NSZone *)zone {
ASTextDebugOption *op = [self.class new];
op.baselineColor = self.baselineColor;
op.CTFrameBorderColor = self.CTFrameBorderColor;
op.CTFrameFillColor = self.CTFrameFillColor;
op.CTLineBorderColor = self.CTLineBorderColor;
op.CTLineFillColor = self.CTLineFillColor;
op.CTLineNumberColor = self.CTLineNumberColor;
op.CTRunBorderColor = self.CTRunBorderColor;
op.CTRunFillColor = self.CTRunFillColor;
op.CTRunNumberColor = self.CTRunNumberColor;
op.CGGlyphBorderColor = self.CGGlyphBorderColor;
op.CGGlyphFillColor = self.CGGlyphFillColor;
return op;
}
- (BOOL)needDrawDebug {
if (self.baselineColor ||
self.CTFrameBorderColor ||
self.CTFrameFillColor ||
self.CTLineBorderColor ||
self.CTLineFillColor ||
self.CTLineNumberColor ||
self.CTRunBorderColor ||
self.CTRunFillColor ||
self.CTRunNumberColor ||
self.CGGlyphBorderColor ||
self.CGGlyphFillColor) return YES;
return NO;
}
- (void)clear {
self.baselineColor = nil;
self.CTFrameBorderColor = nil;
self.CTFrameFillColor = nil;
self.CTLineBorderColor = nil;
self.CTLineFillColor = nil;
self.CTLineNumberColor = nil;
self.CTRunBorderColor = nil;
self.CTRunFillColor = nil;
self.CTRunNumberColor = nil;
self.CGGlyphBorderColor = nil;
self.CGGlyphFillColor = nil;
}
+ (void)addDebugTarget:(id<ASTextDebugTarget>)target {
if (target) _addDebugTarget(target);
}
+ (void)removeDebugTarget:(id<ASTextDebugTarget>)target {
if (target) _removeDebugTarget(target);
}
+ (ASTextDebugOption *)sharedDebugOption {
return _getSharedDebugOption();
}
+ (void)setSharedDebugOption:(ASTextDebugOption *)option {
NSAssert([NSThread isMainThread], @"This method must be called on the main thread");
_setSharedDebugOption(option);
}
@end

View File

@@ -0,0 +1,87 @@
//
// ASTextInput.h
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 15/4/17.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Text position affinity. For example, the offset appears after the last
character on a line is backward affinity, before the first character on
the following line is forward affinity.
*/
typedef NS_ENUM(NSInteger, ASTextAffinity) {
ASTextAffinityForward = 0, ///< offset appears before the character
ASTextAffinityBackward = 1, ///< offset appears after the character
};
/**
A ASTextPosition object represents a position in a text container; in other words,
it is an index into the backing string in a text-displaying view.
ASTextPosition has the same API as Apple's implementation in UITextView/UITextField,
so you can alse use it to interact with UITextView/UITextField.
*/
@interface ASTextPosition : UITextPosition <NSCopying>
@property (nonatomic, readonly) NSInteger offset;
@property (nonatomic, readonly) ASTextAffinity affinity;
+ (instancetype)positionWithOffset:(NSInteger)offset;
+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity) affinity;
- (NSComparisonResult)compare:(id)otherPosition;
@end
/**
A ASTextRange object represents a range of characters in a text container; in other words,
it identifies a starting index and an ending index in string backing a text-displaying view.
ASTextRange has the same API as Apple's implementation in UITextView/UITextField,
so you can alse use it to interact with UITextView/UITextField.
*/
@interface ASTextRange : UITextRange <NSCopying>
@property (nonatomic, readonly) ASTextPosition *start;
@property (nonatomic, readonly) ASTextPosition *end;
@property (nonatomic, readonly, getter=isEmpty) BOOL empty;
+ (instancetype)rangeWithRange:(NSRange)range;
+ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity) affinity;
+ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end;
+ (instancetype)defaultRange; ///< <{0,0} Forward>
- (NSRange)asRange;
@end
/**
A ASTextSelectionRect object encapsulates information about a selected range of
text in a text-displaying view.
ASTextSelectionRect has the same API as Apple's implementation in UITextView/UITextField,
so you can alse use it to interact with UITextView/UITextField.
*/
@interface ASTextSelectionRect : UITextSelectionRect <NSCopying>
@property (nonatomic, readwrite) CGRect rect;
@property (nonatomic, readwrite) UITextWritingDirection writingDirection;
@property (nonatomic, readwrite) BOOL containsStart;
@property (nonatomic, readwrite) BOOL containsEnd;
@property (nonatomic, readwrite) BOOL isVertical;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,152 @@
//
// ASTextInput.m
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 15/4/17.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "ASTextInput.h"
#import "ASTextUtilities.h"
@implementation ASTextPosition
+ (instancetype)positionWithOffset:(NSInteger)offset {
return [self positionWithOffset:offset affinity:ASTextAffinityForward];
}
+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity)affinity {
ASTextPosition *p = [self new];
p->_offset = offset;
p->_affinity = affinity;
return p;
}
- (instancetype)copyWithZone:(NSZone *)zone {
return [self.class positionWithOffset:_offset affinity:_affinity];
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p> (%@%@)", self.class, self, @(_offset), _affinity == ASTextAffinityForward ? @"F":@"B"];
}
- (NSUInteger)hash {
return _offset * 2 + (_affinity == ASTextAffinityForward ? 1 : 0);
}
- (BOOL)isEqual:(ASTextPosition *)object {
if (!object) return NO;
return _offset == object.offset && _affinity == object.affinity;
}
- (NSComparisonResult)compare:(ASTextPosition *)otherPosition {
if (!otherPosition) return NSOrderedAscending;
if (_offset < otherPosition.offset) return NSOrderedAscending;
if (_offset > otherPosition.offset) return NSOrderedDescending;
if (_affinity == ASTextAffinityBackward && otherPosition.affinity == ASTextAffinityForward) return NSOrderedAscending;
if (_affinity == ASTextAffinityForward && otherPosition.affinity == ASTextAffinityBackward) return NSOrderedDescending;
return NSOrderedSame;
}
@end
@implementation ASTextRange {
ASTextPosition *_start;
ASTextPosition *_end;
}
- (instancetype)init {
self = [super init];
if (!self) return nil;
_start = [ASTextPosition positionWithOffset:0];
_end = [ASTextPosition positionWithOffset:0];
return self;
}
- (ASTextPosition *)start {
return _start;
}
- (ASTextPosition *)end {
return _end;
}
- (BOOL)isEmpty {
return _start.offset == _end.offset;
}
- (NSRange)asRange {
return NSMakeRange(_start.offset, _end.offset - _start.offset);
}
+ (instancetype)rangeWithRange:(NSRange)range {
return [self rangeWithRange:range affinity:ASTextAffinityForward];
}
+ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity)affinity {
ASTextPosition *start = [ASTextPosition positionWithOffset:range.location affinity:affinity];
ASTextPosition *end = [ASTextPosition positionWithOffset:range.location + range.length affinity:affinity];
return [self rangeWithStart:start end:end];
}
+ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end {
if (!start || !end) return nil;
if ([start compare:end] == NSOrderedDescending) {
ASTEXT_SWAP(start, end);
}
ASTextRange *range = [ASTextRange new];
range->_start = start;
range->_end = end;
return range;
}
+ (instancetype)defaultRange {
return [self new];
}
- (instancetype)copyWithZone:(NSZone *)zone {
return [self.class rangeWithStart:_start end:_end];
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p> (%@, %@)%@", self.class, self, @(_start.offset), @(_end.offset - _start.offset), _end.affinity == ASTextAffinityForward ? @"F":@"B"];
}
- (NSUInteger)hash {
return (sizeof(NSUInteger) == 8 ? OSSwapInt64(_start.hash) : OSSwapInt32(_start.hash)) + _end.hash;
}
- (BOOL)isEqual:(ASTextRange *)object {
if (!object) return NO;
return [_start isEqual:object.start] && [_end isEqual:object.end];
}
@end
@implementation ASTextSelectionRect
@synthesize rect = _rect;
@synthesize writingDirection = _writingDirection;
@synthesize containsStart = _containsStart;
@synthesize containsEnd = _containsEnd;
@synthesize isVertical = _isVertical;
- (id)copyWithZone:(NSZone *)zone {
ASTextSelectionRect *one = [self.class new];
one.rect = _rect;
one.writingDirection = _writingDirection;
one.containsStart = _containsStart;
one.containsEnd = _containsEnd;
one.isVertical = _isVertical;
return one;
}
@end

View File

@@ -0,0 +1,548 @@
//
// ASTextLayout.h
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 15/3/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>
#import "ASTextDebugOption.h"
#import "ASTextLine.h"
#import "ASTextInput.h"
@protocol ASTextLinePositionModifier;
NS_ASSUME_NONNULL_BEGIN
/**
The max text container size in layout.
*/
extern const CGSize ASTextContainerMaxSize;
/**
The ASTextContainer class defines a region in which text is laid out.
ASTextLayout class uses one or more ASTextContainer objects to generate layouts.
A ASTextContainer defines rectangular regions (`size` and `insets`) or
nonrectangular shapes (`path`), and you can define exclusion paths inside the
text container's bounding rectangle so that text flows around the exclusion
path as it is laid out.
All methods in this class is thread-safe.
Example:
┌─────────────────────────────┐ <------- container
│ │
│ asdfasdfasdfasdfasdfa <------------ container insets
│ asdfasdfa asdfasdfa │
│ asdfas asdasd │
│ asdfa <----------------------- container exclusion path
│ asdfas adfasd │
│ asdfasdfa asdfasdfa │
│ asdfasdfasdfasdfasdfa │
│ │
└─────────────────────────────┘
*/
@interface ASTextContainer : NSObject <NSCoding, NSCopying>
/// Creates a container with the specified size. @param size The size.
+ (instancetype)containerWithSize:(CGSize)size;
/// Creates a container with the specified size and insets. @param size The size. @param insets The text insets.
+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets;
/// Creates a container with the specified path. @param size The path.
+ (instancetype)containerWithPath:(nullable UIBezierPath *)path;
/// The constrained size. (if the size is larger than ASTextContainerMaxSize, it will be clipped)
@property CGSize size;
/// The insets for constrained size. The inset value should not be negative. Default is UIEdgeInsetsZero.
@property UIEdgeInsets insets;
/// Custom constrained path. Set this property to ignore `size` and `insets`. Default is nil.
@property (nullable, copy) UIBezierPath *path;
/// An array of `UIBezierPath` for path exclusion. Default is nil.
@property (nullable, copy) NSArray<UIBezierPath *> *exclusionPaths;
/// Path line width. Default is 0;
@property CGFloat pathLineWidth;
/// YES:(PathFillEvenOdd) Text is filled in the area that would be painted if the path were given to CGContextEOFillPath.
/// NO: (PathFillWindingNumber) Text is fill in the area that would be painted if the path were given to CGContextFillPath.
/// Default is YES;
@property (getter=isPathFillEvenOdd) BOOL pathFillEvenOdd;
/// Whether the text is vertical form (may used for CJK text layout). Default is NO.
@property (getter=isVerticalForm) BOOL verticalForm;
/// Maximum number of rows, 0 means no limit. Default is 0.
@property NSUInteger maximumNumberOfRows;
/// The line truncation type, default is none.
@property ASTextTruncationType truncationType;
/// The truncation token. If nil, the layout will use "…" instead. Default is nil.
@property (nullable, copy) NSAttributedString *truncationToken;
/// This modifier is applied to the lines before the layout is completed,
/// give you a chance to modify the line position. Default is nil.
@property (nullable, copy) id<ASTextLinePositionModifier> linePositionModifier;
@end
/**
The ASTextLinePositionModifier protocol declares the required method to modify
the line position in text layout progress. See `ASTextLinePositionSimpleModifier` for example.
*/
@protocol ASTextLinePositionModifier <NSObject, NSCopying>
@required
/**
This method will called before layout is completed. The method should be thread-safe.
@param lines An array of ASTextLine.
@param text The full text.
@param container The layout container.
*/
- (void)modifyLines:(NSArray<ASTextLine *> *)lines fromText:(NSAttributedString *)text inContainer:(ASTextContainer *)container;
@end
/**
A simple implementation of `ASTextLinePositionModifier`. It can fix each line's position
to a specified value, lets each line of height be the same.
*/
@interface ASTextLinePositionSimpleModifier : NSObject <ASTextLinePositionModifier>
@property (assign) CGFloat fixedLineHeight; ///< The fixed line height (distance between two baseline).
@end
/**
ASTextLayout class is a readonly class stores text layout result.
All the property in this class is readonly, and should not be changed.
The methods in this class is thread-safe (except some of the draw methods).
example: (layout with a circle exclusion path)
┌──────────────────────────┐ <------ container
│ [--------Line0--------] │ <- Row0
│ [--------Line1--------] │ <- Row1
│ [-Line2-] [-Line3-] │ <- Row2
│ [-Line4] [Line5-] │ <- Row3
│ [-Line6-] [-Line7-] │ <- Row4
│ [--------Line8--------] │ <- Row5
│ [--------Line9--------] │ <- Row6
└──────────────────────────┘
*/
@interface ASTextLayout : NSObject <NSCopying>
#pragma mark - Generate text layout
///=============================================================================
/// @name Generate text layout
///=============================================================================
/**
Generate a layout with the given container size and text.
@param size The text container's size
@param text The text (if nil, returns nil).
@return A new layout, or nil when an error occurs.
*/
+ (nullable ASTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text;
/**
Generate a layout with the given container and text.
@param container The text container (if nil, returns nil).
@param text The text (if nil, returns nil).
@return A new layout, or nil when an error occurs.
*/
+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text;
/**
Generate a layout with the given container and text.
@param container The text container (if nil, returns nil).
@param text The text (if nil, returns nil).
@param range The text range (if out of range, returns nil). If the
length of the range is 0, it means the length is no limit.
@return A new layout, or nil when an error occurs.
*/
+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range;
/**
Generate layouts with the given containers and text.
@param containers An array of ASTextContainer object (if nil, returns nil).
@param text The text (if nil, returns nil).
@return An array of ASTextLayout object (the count is same as containers),
or nil when an error occurs.
*/
+ (nullable NSArray<ASTextLayout *> *)layoutWithContainers:(NSArray<ASTextContainer *> *)containers
text:(NSAttributedString *)text;
/**
Generate layouts with the given containers and text.
@param containers An array of ASTextContainer object (if nil, returns nil).
@param text The text (if nil, returns nil).
@param range The text range (if out of range, returns nil). If the
length of the range is 0, it means the length is no limit.
@return An array of ASTextLayout object (the count is same as containers),
or nil when an error occurs.
*/
+ (nullable NSArray<ASTextLayout *> *)layoutWithContainers:(NSArray<ASTextContainer *> *)containers
text:(NSAttributedString *)text
range:(NSRange)range;
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
#pragma mark - Text layout attributes
///=============================================================================
/// @name Text layout attributes
///=============================================================================
///< The text container
@property (nonatomic, strong, readonly) ASTextContainer *container;
///< The full text
@property (nonatomic, strong, readonly) NSAttributedString *text;
///< The text range in full text
@property (nonatomic, readonly) NSRange range;
///< CTFrameSetter
@property (nonatomic, readonly) CTFramesetterRef frameSetter;
///< CTFrame
@property (nonatomic, readonly) CTFrameRef frame;
///< Array of `ASTextLine`, no truncated
@property (nonatomic, strong, readonly) NSArray<ASTextLine *> *lines;
///< ASTextLine with truncated token, or nil
@property (nullable, nonatomic, strong, readonly) ASTextLine *truncatedLine;
///< Array of `ASTextAttachment`
@property (nullable, nonatomic, strong, readonly) NSArray<ASTextAttachment *> *attachments;
///< Array of NSRange(wrapped by NSValue) in text
@property (nullable, nonatomic, strong, readonly) NSArray<NSValue *> *attachmentRanges;
///< Array of CGRect(wrapped by NSValue) in container
@property (nullable, nonatomic, strong, readonly) NSArray<NSValue *> *attachmentRects;
///< Set of Attachment (UIImage/UIView/CALayer)
@property (nullable, nonatomic, strong, readonly) NSSet *attachmentContentsSet;
///< Number of rows
@property (nonatomic, readonly) NSUInteger rowCount;
///< Visible text range
@property (nonatomic, readonly) NSRange visibleRange;
///< Bounding rect (glyphs)
@property (nonatomic, readonly) CGRect textBoundingRect;
///< Bounding size (glyphs and insets, ceil to pixel)
@property (nonatomic, readonly) CGSize textBoundingSize;
///< Has highlight attribute
@property (nonatomic, readonly) BOOL containsHighlight;
///< Has block border attribute
@property (nonatomic, readonly) BOOL needDrawBlockBorder;
///< Has background border attribute
@property (nonatomic, readonly) BOOL needDrawBackgroundBorder;
///< Has shadow attribute
@property (nonatomic, readonly) BOOL needDrawShadow;
///< Has underline attribute
@property (nonatomic, readonly) BOOL needDrawUnderline;
///< Has visible text
@property (nonatomic, readonly) BOOL needDrawText;
///< Has attachment attribute
@property (nonatomic, readonly) BOOL needDrawAttachment;
///< Has inner shadow attribute
@property (nonatomic, readonly) BOOL needDrawInnerShadow;
///< Has strickthrough attribute
@property (nonatomic, readonly) BOOL needDrawStrikethrough;
///< Has border attribute
@property (nonatomic, readonly) BOOL needDrawBorder;
#pragma mark - Query information from text layout
///=============================================================================
/// @name Query information from text layout
///=============================================================================
/**
The first line index for row.
@param row A row index.
@return The line index, or NSNotFound if not found.
*/
- (NSUInteger)lineIndexForRow:(NSUInteger)row;
/**
The number of lines for row.
@param row A row index.
@return The number of lines, or NSNotFound when an error occurs.
*/
- (NSUInteger)lineCountForRow:(NSUInteger)row;
/**
The row index for line.
@param line A row index.
@return The row index, or NSNotFound if not found.
*/
- (NSUInteger)rowIndexForLine:(NSUInteger)line;
/**
The line index for a specified point.
@discussion It returns NSNotFound if there's no text at the point.
@param point A point in the container.
@return The line index, or NSNotFound if not found.
*/
- (NSUInteger)lineIndexForPoint:(CGPoint)point;
/**
The line index closest to a specified point.
@param point A point in the container.
@return The line index, or NSNotFound if no line exist in layout.
*/
- (NSUInteger)closestLineIndexForPoint:(CGPoint)point;
/**
The offset in container for a text position in a specified line.
@discussion The offset is the text position's baseline point.x.
If the container is vertical form, the offset is the baseline point.y;
@param position The text position in string.
@param lineIndex The line index.
@return The offset in container, or CGFLOAT_MAX if not found.
*/
- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex;
/**
The text position for a point in a specified line.
@discussion This method just call CTLineGetStringIndexForPosition() and does
NOT consider the emoji, line break character, binding text...
@param point A point in the container.
@param lineIndex The line index.
@return The text position, or NSNotFound if not found.
*/
- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex;
/**
The closest text position to a specified point.
@discussion This method takes into account the restrict of emoji, line break
character, binding text and text affinity.
@param point A point in the container.
@return A text position, or nil if not found.
*/
- (nullable ASTextPosition *)closestPositionToPoint:(CGPoint)point;
/**
Returns the new position when moving selection grabber in text view.
@discussion There are two grabber in the text selection period, user can only
move one grabber at the same time.
@param point A point in the container.
@param oldPosition The old text position for the moving grabber.
@param otherPosition The other position in text selection view.
@return A text position, or nil if not found.
*/
- (nullable ASTextPosition *)positionForPoint:(CGPoint)point
oldPosition:(ASTextPosition *)oldPosition
otherPosition:(ASTextPosition *)otherPosition;
/**
Returns the character or range of characters that is at a given point in the container.
If there is no text at the point, returns nil.
@discussion This method takes into account the restrict of emoji, line break
character, binding text and text affinity.
@param point A point in the container.
@return An object representing a range that encloses a character (or characters)
at point. Or nil if not found.
*/
- (nullable ASTextRange *)textRangeAtPoint:(CGPoint)point;
/**
Returns the closest character or range of characters that is at a given point in
the container.
@discussion This method takes into account the restrict of emoji, line break
character, binding text and text affinity.
@param point A point in the container.
@return An object representing a range that encloses a character (or characters)
at point. Or nil if not found.
*/
- (nullable ASTextRange *)closestTextRangeAtPoint:(CGPoint)point;
/**
If the position is inside an emoji, composed character sequences, line break '\\r\\n'
or custom binding range, then returns the range by extend the position. Otherwise,
returns a zero length range from the position.
@param position A text-position object that identifies a location in layout.
@return A text-range object that extend the position. Or nil if an error occurs
*/
- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position;
/**
Returns a text range at a given offset in a specified direction from another
text position to its farthest extent in a certain direction of layout.
@param position A text-position object that identifies a location in layout.
@param direction A constant that indicates a direction of layout (right, left, up, down).
@param offset A character offset from position.
@return A text-range object that represents the distance from position to the
farthest extent in direction. Or nil if an error occurs.
*/
- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position
inDirection:(UITextLayoutDirection)direction
offset:(NSInteger)offset;
/**
Returns the line index for a given text position.
@discussion This method takes into account the text affinity.
@param position A text-position object that identifies a location in layout.
@return The line index, or NSNotFound if not found.
*/
- (NSUInteger)lineIndexForPosition:(ASTextPosition *)position;
/**
Returns the baseline position for a given text position.
@param position An object that identifies a location in the layout.
@return The baseline position for text, or CGPointZero if not found.
*/
- (CGPoint)linePositionForPosition:(ASTextPosition *)position;
/**
Returns a rectangle used to draw the caret at a given insertion point.
@param position An object that identifies a location in the layout.
@return A rectangle that defines the area for drawing the caret. The width is
always zero in normal container, the height is always zero in vertical form container.
If not found, it returns CGRectNull.
*/
- (CGRect)caretRectForPosition:(ASTextPosition *)position;
/**
Returns the first rectangle that encloses a range of text in the layout.
@param range An object that represents a range of text in layout.
@return The first rectangle in a range of text. You might use this rectangle to
draw a correction rectangle. The "first" in the name refers the rectangle
enclosing the first line when the range encompasses multiple lines of text.
If not found, it returns CGRectNull.
*/
- (CGRect)firstRectForRange:(ASTextRange *)range;
/**
Returns the rectangle union that encloses a range of text in the layout.
@param range An object that represents a range of text in layout.
@return A rectangle that defines the area than encloses the range.
If not found, it returns CGRectNull.
*/
- (CGRect)rectForRange:(ASTextRange *)range;
/**
Returns an array of selection rects corresponding to the range of text.
The start and end rect can be used to show grabber.
@param range An object representing a range in text.
@return An array of `ASTextSelectionRect` objects that encompass the selection.
If not found, the array is empty.
*/
- (NSArray<ASTextSelectionRect *> *)selectionRectsForRange:(ASTextRange *)range;
/**
Returns an array of selection rects corresponding to the range of text.
@param range An object representing a range in text.
@return An array of `ASTextSelectionRect` objects that encompass the selection.
If not found, the array is empty.
*/
- (NSArray<ASTextSelectionRect *> *)selectionRectsWithoutStartAndEndForRange:(ASTextRange *)range;
/**
Returns the start and end selection rects corresponding to the range of text.
The start and end rect can be used to show grabber.
@param range An object representing a range in text.
@return An array of `ASTextSelectionRect` objects contains the start and end to
the selection. If not found, the array is empty.
*/
- (NSArray<ASTextSelectionRect *> *)selectionRectsWithOnlyStartAndEndForRange:(ASTextRange *)range;
#pragma mark - Draw text layout
///=============================================================================
/// @name Draw text layout
///=============================================================================
/**
Draw the layout and show the attachments.
@discussion If the `view` parameter is not nil, then the attachment views will
add to this `view`, and if the `layer` parameter is not nil, then the attachment
layers will add to this `layer`.
@warning This method should be called on main thread if `view` or `layer` parameter
is not nil and there's UIView or CALayer attachments in layout.
Otherwise, it can be called on any thread.
@param context The draw context. Pass nil to avoid text and image drawing.
@param size The context size.
@param point The point at which to draw the layout.
@param view The attachment views will add to this view.
@param layer The attachment layers will add to this layer.
@param debug The debug option. Pass nil to avoid debug drawing.
@param cancel The cancel checker block. It will be called in drawing progress.
If it returns YES, the further draw progress will be canceled.
Pass nil to ignore this feature.
*/
- (void)drawInContext:(nullable CGContextRef)context
size:(CGSize)size
point:(CGPoint)point
view:(nullable UIView *)view
layer:(nullable CALayer *)layer
debug:(nullable ASTextDebugOption *)debug
cancel:(nullable BOOL (^)(void))cancel;
/**
Draw the layout text and image (without view or layer attachments).
@discussion This method is thread safe and can be called on any thread.
@param context The draw context. Pass nil to avoid text and image drawing.
@param size The context size.
@param debug The debug option. Pass nil to avoid debug drawing.
*/
- (void)drawInContext:(nullable CGContextRef)context
size:(CGSize)size
debug:(nullable ASTextDebugOption *)debug;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
//
// ASTextLine.h
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 15/3/10.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>
#import "ASTextAttribute.h"
@class ASTextRunGlyphRange;
NS_ASSUME_NONNULL_BEGIN
/**
A text line object wrapped `CTLineRef`, see `ASTextLayout` for more.
*/
@interface ASTextLine : NSObject
+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical;
@property (nonatomic) NSUInteger index; ///< line index
@property (nonatomic) NSUInteger row; ///< line row
@property (nullable, nonatomic, strong) NSArray<NSArray<ASTextRunGlyphRange *> *> *verticalRotateRange; ///< Run rotate range
@property (nonatomic, readonly) CTLineRef CTLine; ///< CoreText line
@property (nonatomic, readonly) NSRange range; ///< string range
@property (nonatomic, readonly) BOOL vertical; ///< vertical form
@property (nonatomic, readonly) CGRect bounds; ///< bounds (ascent + descent)
@property (nonatomic, readonly) CGSize size; ///< bounds.size
@property (nonatomic, readonly) CGFloat width; ///< bounds.size.width
@property (nonatomic, readonly) CGFloat height; ///< bounds.size.height
@property (nonatomic, readonly) CGFloat top; ///< bounds.origin.y
@property (nonatomic, readonly) CGFloat bottom; ///< bounds.origin.y + bounds.size.height
@property (nonatomic, readonly) CGFloat left; ///< bounds.origin.x
@property (nonatomic, readonly) CGFloat right; ///< bounds.origin.x + bounds.size.width
@property (nonatomic) CGPoint position; ///< baseline position
@property (nonatomic, readonly) CGFloat ascent; ///< line ascent
@property (nonatomic, readonly) CGFloat descent; ///< line descent
@property (nonatomic, readonly) CGFloat leading; ///< line leading
@property (nonatomic, readonly) CGFloat lineWidth; ///< line width
@property (nonatomic, readonly) CGFloat trailingWhitespaceWidth;
@property (nullable, nonatomic, readonly) NSArray<ASTextAttachment *> *attachments; ///< ASTextAttachment
@property (nullable, nonatomic, readonly) NSArray<NSValue *> *attachmentRanges; ///< NSRange(NSValue)
@property (nullable, nonatomic, readonly) NSArray<NSValue *> *attachmentRects; ///< CGRect(NSValue)
@end
typedef NS_ENUM(NSUInteger, ASTextRunGlyphDrawMode) {
/// No rotate.
ASTextRunGlyphDrawModeHorizontal = 0,
/// Rotate vertical for single glyph.
ASTextRunGlyphDrawModeVerticalRotate = 1,
/// Rotate vertical for single glyph, and move the glyph to a better position,
/// such as fullwidth punctuation.
ASTextRunGlyphDrawModeVerticalRotateMove = 2,
};
/**
A range in CTRun, used for vertical form.
*/
@interface ASTextRunGlyphRange : NSObject
@property (nonatomic) NSRange glyphRangeInRun;
@property (nonatomic) ASTextRunGlyphDrawMode drawMode;
+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,167 @@
//
// ASYTextLine.m
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 15/3/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "ASTextLine.h"
#import "ASTextUtilities.h"
@implementation ASTextLine {
CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0.
}
+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical {
if (!CTLine) return nil;
ASTextLine *line = [self new];
line->_position = position;
line->_vertical = isVertical;
[line setCTLine:CTLine];
return line;
}
- (void)dealloc {
if (_CTLine) CFRelease(_CTLine);
}
- (void)setCTLine:(_Nonnull CTLineRef)CTLine {
if (_CTLine != CTLine) {
if (CTLine) CFRetain(CTLine);
if (_CTLine) CFRelease(_CTLine);
_CTLine = CTLine;
if (_CTLine) {
_lineWidth = CTLineGetTypographicBounds(_CTLine, &_ascent, &_descent, &_leading);
CFRange range = CTLineGetStringRange(_CTLine);
_range = NSMakeRange(range.location, range.length);
if (CTLineGetGlyphCount(_CTLine) > 0) {
CFArrayRef runs = CTLineGetGlyphRuns(_CTLine);
CTRunRef run = CFArrayGetValueAtIndex(runs, 0);
CGPoint pos;
CTRunGetPositions(run, CFRangeMake(0, 1), &pos);
_firstGlyphPos = pos.x;
} else {
_firstGlyphPos = 0;
}
_trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(_CTLine);
} else {
_lineWidth = _ascent = _descent = _leading = _firstGlyphPos = _trailingWhitespaceWidth = 0;
_range = NSMakeRange(0, 0);
}
[self reloadBounds];
}
}
- (void)setPosition:(CGPoint)position {
_position = position;
[self reloadBounds];
}
- (void)reloadBounds {
if (_vertical) {
_bounds = CGRectMake(_position.x - _descent, _position.y, _ascent + _descent, _lineWidth);
_bounds.origin.y += _firstGlyphPos;
} else {
_bounds = CGRectMake(_position.x, _position.y - _ascent, _lineWidth, _ascent + _descent);
_bounds.origin.x += _firstGlyphPos;
}
_attachments = nil;
_attachmentRanges = nil;
_attachmentRects = nil;
if (!_CTLine) return;
CFArrayRef runs = CTLineGetGlyphRuns(_CTLine);
NSUInteger runCount = CFArrayGetCount(runs);
if (runCount == 0) return;
NSMutableArray *attachments = [NSMutableArray new];
NSMutableArray *attachmentRanges = [NSMutableArray new];
NSMutableArray *attachmentRects = [NSMutableArray new];
for (NSUInteger r = 0; r < runCount; r++) {
CTRunRef run = CFArrayGetValueAtIndex(runs, r);
CFIndex glyphCount = CTRunGetGlyphCount(run);
if (glyphCount == 0) continue;
NSDictionary *attrs = (id)CTRunGetAttributes(run);
ASTextAttachment *attachment = attrs[ASTextAttachmentAttributeName];
if (attachment) {
CGPoint runPosition = CGPointZero;
CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition);
CGFloat ascent, descent, leading, runWidth;
CGRect runTypoBounds;
runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading);
if (_vertical) {
ASTEXT_SWAP(runPosition.x, runPosition.y);
runPosition.y = _position.y + runPosition.y;
runTypoBounds = CGRectMake(_position.x + runPosition.x - descent, runPosition.y , ascent + descent, runWidth);
} else {
runPosition.x += _position.x;
runPosition.y = _position.y - runPosition.y;
runTypoBounds = CGRectMake(runPosition.x, runPosition.y - ascent, runWidth, ascent + descent);
}
NSRange runRange = ASTextNSRangeFromCFRange(CTRunGetStringRange(run));
[attachments addObject:attachment];
[attachmentRanges addObject:[NSValue valueWithRange:runRange]];
[attachmentRects addObject:[NSValue valueWithCGRect:runTypoBounds]];
}
}
_attachments = attachments.count ? attachments : nil;
_attachmentRanges = attachmentRanges.count ? attachmentRanges : nil;
_attachmentRects = attachmentRects.count ? attachmentRects : nil;
}
- (CGSize)size {
return _bounds.size;
}
- (CGFloat)width {
return CGRectGetWidth(_bounds);
}
- (CGFloat)height {
return CGRectGetHeight(_bounds);
}
- (CGFloat)top {
return CGRectGetMinY(_bounds);
}
- (CGFloat)bottom {
return CGRectGetMaxY(_bounds);
}
- (CGFloat)left {
return CGRectGetMinX(_bounds);
}
- (CGFloat)right {
return CGRectGetMaxX(_bounds);
}
- (NSString *)description {
NSMutableString *desc = @"".mutableCopy;
NSRange range = self.range;
[desc appendFormat:@"<ASTextLine: %p> row:%zd range:%tu,%tu",self, self.row, range.location, range.length];
[desc appendFormat:@" position:%@",NSStringFromCGPoint(self.position)];
[desc appendFormat:@" bounds:%@",NSStringFromCGRect(self.bounds)];
return desc;
}
@end
@implementation ASTextRunGlyphRange
+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode {
ASTextRunGlyphRange *one = [self new];
one.glyphRangeInRun = range;
one.drawMode = mode;
return one;
}
@end

View File

@@ -0,0 +1,347 @@
//
// ASTextAttribute.h
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 14/10/26.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Enum Define
/// The attribute type
typedef NS_OPTIONS(NSInteger, ASTextAttributeType) {
ASTextAttributeTypeNone = 0,
ASTextAttributeTypeUIKit = 1 << 0, ///< UIKit attributes, such as UILabel/UITextField/drawInRect.
ASTextAttributeTypeCoreText = 1 << 1, ///< CoreText attributes, used by CoreText.
ASTextAttributeTypeASText = 1 << 2, ///< ASText attributes, used by ASText.
};
/// Get the attribute type from an attribute name.
extern ASTextAttributeType ASTextAttributeGetType(NSString *attributeName);
/**
Line style in ASText (similar to NSUnderlineStyle).
*/
typedef NS_OPTIONS (NSInteger, ASTextLineStyle) {
// basic style (bitmask:0xFF)
ASTextLineStyleNone = 0x00, ///< ( ) Do not draw a line (Default).
ASTextLineStyleSingle = 0x01, ///< (──────) Draw a single line.
ASTextLineStyleThick = 0x02, ///< (━━━━━━━) Draw a thick line.
ASTextLineStyleDouble = 0x09, ///< (══════) Draw a double line.
// style pattern (bitmask:0xF00)
ASTextLineStylePatternSolid = 0x000, ///< (────────) Draw a solid line (Default).
ASTextLineStylePatternDot = 0x100, ///< ( ) Draw a line of dots.
ASTextLineStylePatternDash = 0x200, ///< (— — — —) Draw a line of dashes.
ASTextLineStylePatternDashDot = 0x300, ///< (— ) Draw a line of alternating dashes and dots.
ASTextLineStylePatternDashDotDot = 0x400, ///< (— ) Draw a line of alternating dashes and two dots.
ASTextLineStylePatternCircleDot = 0x900, ///< (••••••••••••) Draw a line of small circle dots.
};
/**
Text vertical alignment.
*/
typedef NS_ENUM(NSInteger, ASTextVerticalAlignment) {
ASTextVerticalAlignmentTop = 0, ///< Top alignment.
ASTextVerticalAlignmentCenter = 1, ///< Center alignment.
ASTextVerticalAlignmentBottom = 2, ///< Bottom alignment.
};
/**
The direction define in ASText.
*/
typedef NS_OPTIONS(NSUInteger, ASTextDirection) {
ASTextDirectionNone = 0,
ASTextDirectionTop = 1 << 0,
ASTextDirectionRight = 1 << 1,
ASTextDirectionBottom = 1 << 2,
ASTextDirectionLeft = 1 << 3,
};
/**
The trunction type, tells the truncation engine which type of truncation is being requested.
*/
typedef NS_ENUM (NSUInteger, ASTextTruncationType) {
/// No truncate.
ASTextTruncationTypeNone = 0,
/// Truncate at the beginning of the line, leaving the end portion visible.
ASTextTruncationTypeStart = 1,
/// Truncate at the end of the line, leaving the start portion visible.
ASTextTruncationTypeEnd = 2,
/// Truncate in the middle of the line, leaving both the start and the end portions visible.
ASTextTruncationTypeMiddle = 3,
};
#pragma mark - Attribute Name Defined in ASText
/// The value of this attribute is a `ASTextBackedString` object.
/// Use this attribute to store the original plain text if it is replaced by something else (such as attachment).
UIKIT_EXTERN NSString *const ASTextBackedStringAttributeName;
/// The value of this attribute is a `ASTextBinding` object.
/// Use this attribute to bind a range of text together, as if it was a single charactor.
UIKIT_EXTERN NSString *const ASTextBindingAttributeName;
/// The value of this attribute is a `ASTextShadow` object.
/// Use this attribute to add shadow to a range of text.
/// Shadow will be drawn below text glyphs. Use ASTextShadow.subShadow to add multi-shadow.
UIKIT_EXTERN NSString *const ASTextShadowAttributeName;
/// The value of this attribute is a `ASTextShadow` object.
/// Use this attribute to add inner shadow to a range of text.
/// Inner shadow will be drawn above text glyphs. Use ASTextShadow.subShadow to add multi-shadow.
UIKIT_EXTERN NSString *const ASTextInnerShadowAttributeName;
/// The value of this attribute is a `ASTextDecoration` object.
/// Use this attribute to add underline to a range of text.
/// The underline will be drawn below text glyphs.
UIKIT_EXTERN NSString *const ASTextUnderlineAttributeName;
/// The value of this attribute is a `ASTextDecoration` object.
/// Use this attribute to add strikethrough (delete line) to a range of text.
/// The strikethrough will be drawn above text glyphs.
UIKIT_EXTERN NSString *const ASTextStrikethroughAttributeName;
/// The value of this attribute is a `ASTextBorder` object.
/// Use this attribute to add cover border or cover color to a range of text.
/// The border will be drawn above the text glyphs.
UIKIT_EXTERN NSString *const ASTextBorderAttributeName;
/// The value of this attribute is a `ASTextBorder` object.
/// Use this attribute to add background border or background color to a range of text.
/// The border will be drawn below the text glyphs.
UIKIT_EXTERN NSString *const ASTextBackgroundBorderAttributeName;
/// The value of this attribute is a `ASTextBorder` object.
/// Use this attribute to add a code block border to one or more line of text.
/// The border will be drawn below the text glyphs.
UIKIT_EXTERN NSString *const ASTextBlockBorderAttributeName;
/// The value of this attribute is a `ASTextAttachment` object.
/// Use this attribute to add attachment to text.
/// It should be used in conjunction with a CTRunDelegate.
UIKIT_EXTERN NSString *const ASTextAttachmentAttributeName;
/// The value of this attribute is a `ASTextHighlight` object.
/// Use this attribute to add a touchable highlight state to a range of text.
UIKIT_EXTERN NSString *const ASTextHighlightAttributeName;
/// The value of this attribute is a `NSValue` object stores CGAffineTransform.
/// Use this attribute to add transform to each glyph in a range of text.
UIKIT_EXTERN NSString *const ASTextGlyphTransformAttributeName;
#pragma mark - String Token Define
UIKIT_EXTERN NSString *const ASTextAttachmentToken; ///< Object replacement character (U+FFFC), used for text attachment.
UIKIT_EXTERN NSString *const ASTextTruncationToken; ///< Horizontal ellipsis (U+2026), used for text truncation "…".
#pragma mark - Attribute Value Define
/**
The tap/long press action callback defined in ASText.
@param containerView The text container view (such as ASLabel/ASTextView).
@param text The whole text.
@param range The text range in `text` (if no range, the range.location is NSNotFound).
@param rect The text frame in `containerView` (if no data, the rect is CGRectNull).
*/
typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect);
/**
ASTextBackedString objects are used by the NSAttributedString class cluster
as the values for text backed string attributes (stored in the attributed
string under the key named ASTextBackedStringAttributeName).
It may used for copy/paste plain text from attributed string.
Example: If :) is replace by a custom emoji (such as😊), the backed string can be set to @":)".
*/
@interface ASTextBackedString : NSObject <NSCoding, NSCopying>
+ (instancetype)stringWithString:(nullable NSString *)string;
@property (nullable, nonatomic, copy) NSString *string; ///< backed string
@end
/**
ASTextBinding objects are used by the NSAttributedString class cluster
as the values for shadow attributes (stored in the attributed string under
the key named ASTextBindingAttributeName).
Add this to a range of text will make the specified characters 'binding together'.
ASTextView will treat the range of text as a single character during text
selection and edit.
*/
@interface ASTextBinding : NSObject <NSCoding, NSCopying>
+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm;
@property (nonatomic) BOOL deleteConfirm; ///< confirm the range when delete in ASTextView
@end
/**
ASTextShadow objects are used by the NSAttributedString class cluster
as the values for shadow attributes (stored in the attributed string under
the key named ASTextShadowAttributeName or ASTextInnerShadowAttributeName).
It's similar to `NSShadow`, but offers more options.
*/
@interface ASTextShadow : NSObject <NSCoding, NSCopying>
+ (instancetype)shadowWithColor:(nullable UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius;
@property (nullable, nonatomic, strong) UIColor *color; ///< shadow color
@property (nonatomic) CGSize offset; ///< shadow offset
@property (nonatomic) CGFloat radius; ///< shadow blur radius
@property (nonatomic) CGBlendMode blendMode; ///< shadow blend mode
@property (nullable, nonatomic, strong) ASTextShadow *subShadow; ///< a sub shadow which will be added above the parent shadow
+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow; ///< convert NSShadow to ASTextShadow
- (NSShadow *)nsShadow; ///< convert ASTextShadow to NSShadow
@end
/**
ASTextDecorationLine objects are used by the NSAttributedString class cluster
as the values for decoration line attributes (stored in the attributed string under
the key named ASTextUnderlineAttributeName or ASTextStrikethroughAttributeName).
When it's used as underline, the line is drawn below text glyphs;
when it's used as strikethrough, the line is drawn above text glyphs.
*/
@interface ASTextDecoration : NSObject <NSCoding, NSCopying>
+ (instancetype)decorationWithStyle:(ASTextLineStyle)style;
+ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(nullable NSNumber *)width color:(nullable UIColor *)color;
@property (nonatomic) ASTextLineStyle style; ///< line style
@property (nullable, nonatomic, strong) NSNumber *width; ///< line width (nil means automatic width)
@property (nullable, nonatomic, strong) UIColor *color; ///< line color (nil means automatic color)
@property (nullable, nonatomic, strong) ASTextShadow *shadow; ///< line shadow
@end
/**
ASTextBorder objects are used by the NSAttributedString class cluster
as the values for border attributes (stored in the attributed string under
the key named ASTextBorderAttributeName or ASTextBackgroundBorderAttributeName).
It can be used to draw a border around a range of text, or draw a background
to a range of text.
Example:
╭──────╮
│ Text │
╰──────╯
*/
@interface ASTextBorder : NSObject <NSCoding, NSCopying>
+ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(nullable UIColor *)color;
+ (instancetype)borderWithFillColor:(nullable UIColor *)color cornerRadius:(CGFloat)cornerRadius;
@property (nonatomic) ASTextLineStyle lineStyle; ///< border line style
@property (nonatomic) CGFloat strokeWidth; ///< border line width
@property (nullable, nonatomic, strong) UIColor *strokeColor; ///< border line color
@property (nonatomic) CGLineJoin lineJoin; ///< border line join
@property (nonatomic) UIEdgeInsets insets; ///< border insets for text bounds
@property (nonatomic) CGFloat cornerRadius; ///< border corder radius
@property (nullable, nonatomic, strong) ASTextShadow *shadow; ///< border shadow
@property (nullable, nonatomic, strong) UIColor *fillColor; ///< inner fill color
@end
/**
ASTextAttachment objects are used by the NSAttributedString class cluster
as the values for attachment attributes (stored in the attributed string under
the key named ASTextAttachmentAttributeName).
When display an attributed string which contains `ASTextAttachment` object,
the content will be placed in text metric. If the content is `UIImage`,
then it will be drawn to CGContext; if the content is `UIView` or `CALayer`,
then it will be added to the text container's view or layer.
*/
@interface ASTextAttachment : NSObject<NSCoding, NSCopying>
+ (instancetype)attachmentWithContent:(nullable id)content;
@property (nullable, nonatomic, strong) id content; ///< Supported type: UIImage, UIView, CALayer
@property (nonatomic) UIViewContentMode contentMode; ///< Content display mode.
@property (nonatomic) UIEdgeInsets contentInsets; ///< The insets when drawing content.
@property (nullable, nonatomic, strong) NSDictionary *userInfo; ///< The user information dictionary.
@end
/**
ASTextHighlight objects are used by the NSAttributedString class cluster
as the values for touchable highlight attributes (stored in the attributed string
under the key named ASTextHighlightAttributeName).
When display an attributed string in `ASLabel` or `ASTextView`, the range of
highlight text can be toucheds down by users. If a range of text is turned into
highlighted state, the `attributes` in `ASTextHighlight` will be used to modify
(set or remove) the original attributes in the range for display.
*/
@interface ASTextHighlight : NSObject <NSCopying>
/**
Attributes that you can apply to text in an attributed string when highlight.
Key: Same as CoreText/ASText Attribute Name.
Value: Modify attribute value when highlight (NSNull for remove attribute).
*/
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id> *attributes;
/**
Creates a highlight object with specified attributes.
@param attributes The attributes which will replace original attributes when highlight,
If the value is NSNull, it will removed when highlight.
*/
+ (instancetype)highlightWithAttributes:(nullable NSDictionary<NSString *, id> *)attributes;
/**
Convenience methods to create a default highlight with the specifeid background color.
@param color The background border color.
*/
+ (instancetype)highlightWithBackgroundColor:(nullable UIColor *)color;
// Convenience methods below to set the `attributes`.
- (void)setFont:(nullable UIFont *)font;
- (void)setColor:(nullable UIColor *)color;
- (void)setStrokeWidth:(nullable NSNumber *)width;
- (void)setStrokeColor:(nullable UIColor *)color;
- (void)setShadow:(nullable ASTextShadow *)shadow;
- (void)setInnerShadow:(nullable ASTextShadow *)shadow;
- (void)setUnderline:(nullable ASTextDecoration *)underline;
- (void)setStrikethrough:(nullable ASTextDecoration *)strikethrough;
- (void)setBackgroundBorder:(nullable ASTextBorder *)border;
- (void)setBorder:(nullable ASTextBorder *)border;
- (void)setAttachment:(nullable ASTextAttachment *)attachment;
/**
The user information dictionary, default is nil.
*/
@property (nullable, nonatomic, copy) NSDictionary *userInfo;
/**
Tap action when user tap the highlight, default is nil.
If the value is nil, ASTextView or ASLabel will ask it's delegate to handle the tap action.
*/
@property (nullable, nonatomic, copy) ASTextAction tapAction;
/**
Long press action when user long press the highlight, default is nil.
If the value is nil, ASTextView or ASLabel will ask it's delegate to handle the long press action.
*/
@property (nullable, nonatomic, copy) ASTextAction longPressAction;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,485 @@
//
// ASTextAttribute.m
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 14/10/26.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "ASTextAttribute.h"
#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>
#import <AsyncDisplayKit/NSAttributedString+ASText.h>
NSString *const ASTextBackedStringAttributeName = @"ASTextBackedString";
NSString *const ASTextBindingAttributeName = @"ASTextBinding";
NSString *const ASTextShadowAttributeName = @"ASTextShadow";
NSString *const ASTextInnerShadowAttributeName = @"ASTextInnerShadow";
NSString *const ASTextUnderlineAttributeName = @"ASTextUnderline";
NSString *const ASTextStrikethroughAttributeName = @"ASTextStrikethrough";
NSString *const ASTextBorderAttributeName = @"ASTextBorder";
NSString *const ASTextBackgroundBorderAttributeName = @"ASTextBackgroundBorder";
NSString *const ASTextBlockBorderAttributeName = @"ASTextBlockBorder";
NSString *const ASTextAttachmentAttributeName = @"ASTextAttachment";
NSString *const ASTextHighlightAttributeName = @"ASTextHighlight";
NSString *const ASTextGlyphTransformAttributeName = @"ASTextGlyphTransform";
NSString *const ASTextAttachmentToken = @"\uFFFC";
NSString *const ASTextTruncationToken = @"\u2026";
ASTextAttributeType ASTextAttributeGetType(NSString *name){
if (name.length == 0) return ASTextAttributeTypeNone;
static NSMutableDictionary *dic;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dic = [NSMutableDictionary new];
NSNumber *All = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeCoreText | ASTextAttributeTypeASText);
NSNumber *CoreText_ASText = @(ASTextAttributeTypeCoreText | ASTextAttributeTypeASText);
NSNumber *UIKit_ASText = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeASText);
NSNumber *UIKit_CoreText = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeCoreText);
NSNumber *UIKit = @(ASTextAttributeTypeUIKit);
NSNumber *CoreText = @(ASTextAttributeTypeCoreText);
NSNumber *ASText = @(ASTextAttributeTypeASText);
dic[NSFontAttributeName] = All;
dic[NSKernAttributeName] = All;
dic[NSForegroundColorAttributeName] = UIKit;
dic[(id)kCTForegroundColorAttributeName] = CoreText;
dic[(id)kCTForegroundColorFromContextAttributeName] = CoreText;
dic[NSBackgroundColorAttributeName] = UIKit;
dic[NSStrokeWidthAttributeName] = All;
dic[NSStrokeColorAttributeName] = UIKit;
dic[(id)kCTStrokeColorAttributeName] = CoreText_ASText;
dic[NSShadowAttributeName] = UIKit_ASText;
dic[NSStrikethroughStyleAttributeName] = UIKit;
dic[NSUnderlineStyleAttributeName] = UIKit_CoreText;
dic[(id)kCTUnderlineColorAttributeName] = CoreText;
dic[NSLigatureAttributeName] = All;
dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit...
dic[NSVerticalGlyphFormAttributeName] = All;
dic[(id)kCTGlyphInfoAttributeName] = CoreText_ASText;
dic[(id)kCTCharacterShapeAttributeName] = CoreText_ASText;
dic[(id)kCTRunDelegateAttributeName] = CoreText_ASText;
dic[(id)kCTBaselineClassAttributeName] = CoreText_ASText;
dic[(id)kCTBaselineInfoAttributeName] = CoreText_ASText;
dic[(id)kCTBaselineReferenceInfoAttributeName] = CoreText_ASText;
dic[(id)kCTWritingDirectionAttributeName] = CoreText_ASText;
dic[NSParagraphStyleAttributeName] = All;
dic[NSStrikethroughColorAttributeName] = UIKit;
dic[NSUnderlineColorAttributeName] = UIKit;
dic[NSTextEffectAttributeName] = UIKit;
dic[NSObliquenessAttributeName] = UIKit;
dic[NSExpansionAttributeName] = UIKit;
dic[(id)kCTLanguageAttributeName] = CoreText_ASText;
dic[NSBaselineOffsetAttributeName] = UIKit;
dic[NSWritingDirectionAttributeName] = All;
dic[NSAttachmentAttributeName] = UIKit;
dic[NSLinkAttributeName] = UIKit;
dic[(id)kCTRubyAnnotationAttributeName] = CoreText;
dic[ASTextBackedStringAttributeName] = ASText;
dic[ASTextBindingAttributeName] = ASText;
dic[ASTextShadowAttributeName] = ASText;
dic[ASTextInnerShadowAttributeName] = ASText;
dic[ASTextUnderlineAttributeName] = ASText;
dic[ASTextStrikethroughAttributeName] = ASText;
dic[ASTextBorderAttributeName] = ASText;
dic[ASTextBackgroundBorderAttributeName] = ASText;
dic[ASTextBlockBorderAttributeName] = ASText;
dic[ASTextAttachmentAttributeName] = ASText;
dic[ASTextHighlightAttributeName] = ASText;
dic[ASTextGlyphTransformAttributeName] = ASText;
});
NSNumber *num = dic[name];
if (num) return num.integerValue;
return ASTextAttributeTypeNone;
}
@implementation ASTextBackedString
+ (instancetype)stringWithString:(NSString *)string {
ASTextBackedString *one = [self new];
one.string = string;
return one;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.string forKey:@"string"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
_string = [aDecoder decodeObjectForKey:@"string"];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
typeof(self) one = [self.class new];
one.string = self.string;
return one;
}
@end
@implementation ASTextBinding
+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm {
ASTextBinding *one = [self new];
one.deleteConfirm = deleteConfirm;
return one;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.deleteConfirm) forKey:@"deleteConfirm"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
_deleteConfirm = ((NSNumber *)[aDecoder decodeObjectForKey:@"deleteConfirm"]).boolValue;
return self;
}
- (id)copyWithZone:(NSZone *)zone {
typeof(self) one = [self.class new];
one.deleteConfirm = self.deleteConfirm;
return one;
}
@end
@implementation ASTextShadow
+ (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius {
ASTextShadow *one = [self new];
one.color = color;
one.offset = offset;
one.radius = radius;
return one;
}
+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow {
if (!nsShadow) return nil;
ASTextShadow *shadow = [self new];
shadow.offset = nsShadow.shadowOffset;
shadow.radius = nsShadow.shadowBlurRadius;
id color = nsShadow.shadowColor;
if (color) {
if (CGColorGetTypeID() == CFGetTypeID((__bridge CFTypeRef)(color))) {
color = [UIColor colorWithCGColor:(__bridge CGColorRef)(color)];
}
if ([color isKindOfClass:[UIColor class]]) {
shadow.color = color;
}
}
return shadow;
}
- (NSShadow *)nsShadow {
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = self.offset;
shadow.shadowBlurRadius = self.radius;
shadow.shadowColor = self.color;
return shadow;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.color forKey:@"color"];
[aCoder encodeObject:@(self.radius) forKey:@"radius"];
[aCoder encodeObject:[NSValue valueWithCGSize:self.offset] forKey:@"offset"];
[aCoder encodeObject:self.subShadow forKey:@"subShadow"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
_color = [aDecoder decodeObjectForKey:@"color"];
_radius = ((NSNumber *)[aDecoder decodeObjectForKey:@"radius"]).floatValue;
_offset = ((NSValue *)[aDecoder decodeObjectForKey:@"offset"]).CGSizeValue;
_subShadow = [aDecoder decodeObjectForKey:@"subShadow"];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
typeof(self) one = [self.class new];
one.color = self.color;
one.radius = self.radius;
one.offset = self.offset;
one.subShadow = self.subShadow.copy;
return one;
}
@end
@implementation ASTextDecoration
- (instancetype)init {
self = [super init];
_style = ASTextLineStyleSingle;
return self;
}
+ (instancetype)decorationWithStyle:(ASTextLineStyle)style {
ASTextDecoration *one = [self new];
one.style = style;
return one;
}
+ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(NSNumber *)width color:(UIColor *)color {
ASTextDecoration *one = [self new];
one.style = style;
one.width = width;
one.color = color;
return one;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.style) forKey:@"style"];
[aCoder encodeObject:self.width forKey:@"width"];
[aCoder encodeObject:self.color forKey:@"color"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
self.style = ((NSNumber *)[aDecoder decodeObjectForKey:@"style"]).unsignedIntegerValue;
self.width = [aDecoder decodeObjectForKey:@"width"];
self.color = [aDecoder decodeObjectForKey:@"color"];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
typeof(self) one = [self.class new];
one.style = self.style;
one.width = self.width;
one.color = self.color;
return one;
}
@end
@implementation ASTextBorder
+ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(UIColor *)color {
ASTextBorder *one = [self new];
one.lineStyle = lineStyle;
one.strokeWidth = width;
one.strokeColor = color;
return one;
}
+ (instancetype)borderWithFillColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius {
ASTextBorder *one = [self new];
one.fillColor = color;
one.cornerRadius = cornerRadius;
one.insets = UIEdgeInsetsMake(-2, 0, 0, -2);
return one;
}
- (instancetype)init {
self = [super init];
self.lineStyle = ASTextLineStyleSingle;
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.lineStyle) forKey:@"lineStyle"];
[aCoder encodeObject:@(self.strokeWidth) forKey:@"strokeWidth"];
[aCoder encodeObject:self.strokeColor forKey:@"strokeColor"];
[aCoder encodeObject:@(self.lineJoin) forKey:@"lineJoin"];
[aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.insets] forKey:@"insets"];
[aCoder encodeObject:@(self.cornerRadius) forKey:@"cornerRadius"];
[aCoder encodeObject:self.shadow forKey:@"shadow"];
[aCoder encodeObject:self.fillColor forKey:@"fillColor"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
_lineStyle = ((NSNumber *)[aDecoder decodeObjectForKey:@"lineStyle"]).unsignedIntegerValue;
_strokeWidth = ((NSNumber *)[aDecoder decodeObjectForKey:@"strokeWidth"]).doubleValue;
_strokeColor = [aDecoder decodeObjectForKey:@"strokeColor"];
_lineJoin = (CGLineJoin)((NSNumber *)[aDecoder decodeObjectForKey:@"join"]).unsignedIntegerValue;
_insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue;
_cornerRadius = ((NSNumber *)[aDecoder decodeObjectForKey:@"cornerRadius"]).doubleValue;
_shadow = [aDecoder decodeObjectForKey:@"shadow"];
_fillColor = [aDecoder decodeObjectForKey:@"fillColor"];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
typeof(self) one = [self.class new];
one.lineStyle = self.lineStyle;
one.strokeWidth = self.strokeWidth;
one.strokeColor = self.strokeColor;
one.lineJoin = self.lineJoin;
one.insets = self.insets;
one.cornerRadius = self.cornerRadius;
one.shadow = self.shadow.copy;
one.fillColor = self.fillColor;
return one;
}
@end
@implementation ASTextAttachment
+ (instancetype)attachmentWithContent:(id)content {
ASTextAttachment *one = [self new];
one.content = content;
return one;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.content forKey:@"content"];
[aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.contentInsets] forKey:@"contentInsets"];
[aCoder encodeObject:self.userInfo forKey:@"userInfo"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
_content = [aDecoder decodeObjectForKey:@"content"];
_contentInsets = ((NSValue *)[aDecoder decodeObjectForKey:@"contentInsets"]).UIEdgeInsetsValue;
_userInfo = [aDecoder decodeObjectForKey:@"userInfo"];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
typeof(self) one = [self.class new];
if ([self.content respondsToSelector:@selector(copy)]) {
one.content = [self.content copy];
} else {
one.content = self.content;
}
one.contentInsets = self.contentInsets;
one.userInfo = self.userInfo.copy;
return one;
}
@end
@implementation ASTextHighlight
+ (instancetype)highlightWithAttributes:(NSDictionary *)attributes {
ASTextHighlight *one = [self new];
one.attributes = attributes;
return one;
}
+ (instancetype)highlightWithBackgroundColor:(UIColor *)color {
ASTextBorder *highlightBorder = [ASTextBorder new];
highlightBorder.insets = UIEdgeInsetsMake(-2, -1, -2, -1);
highlightBorder.cornerRadius = 3;
highlightBorder.fillColor = color;
ASTextHighlight *one = [self new];
[one setBackgroundBorder:highlightBorder];
return one;
}
- (void)setAttributes:(NSDictionary *)attributes {
_attributes = attributes.mutableCopy;
}
- (id)copyWithZone:(NSZone *)zone {
typeof(self) one = [self.class new];
one.attributes = self.attributes.mutableCopy;
return one;
}
- (void)_makeMutableAttributes {
if (!_attributes) {
_attributes = [NSMutableDictionary new];
} else if (![_attributes isKindOfClass:[NSMutableDictionary class]]) {
_attributes = _attributes.mutableCopy;
}
}
- (void)setFont:(UIFont *)font {
[self _makeMutableAttributes];
if (font == (id)[NSNull null] || font == nil) {
((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = [NSNull null];
} else {
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL);
if (ctFont) {
((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = (__bridge id)(ctFont);
CFRelease(ctFont);
}
}
}
- (void)setColor:(UIColor *)color {
[self _makeMutableAttributes];
if (color == (id)[NSNull null] || color == nil) {
((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = [NSNull null];
((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = [NSNull null];
} else {
((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = (__bridge id)(color.CGColor);
((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = color;
}
}
- (void)setStrokeWidth:(NSNumber *)width {
[self _makeMutableAttributes];
if (width == (id)[NSNull null] || width == nil) {
((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = [NSNull null];
} else {
((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = width;
}
}
- (void)setStrokeColor:(UIColor *)color {
[self _makeMutableAttributes];
if (color == (id)[NSNull null] || color == nil) {
((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = [NSNull null];
((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = [NSNull null];
} else {
((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = (__bridge id)(color.CGColor);
((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = color;
}
}
- (void)setTextAttribute:(NSString *)attribute value:(id)value {
[self _makeMutableAttributes];
if (value == nil) value = [NSNull null];
((NSMutableDictionary *)_attributes)[attribute] = value;
}
- (void)setShadow:(ASTextShadow *)shadow {
[self setTextAttribute:ASTextShadowAttributeName value:shadow];
}
- (void)setInnerShadow:(ASTextShadow *)shadow {
[self setTextAttribute:ASTextInnerShadowAttributeName value:shadow];
}
- (void)setUnderline:(ASTextDecoration *)underline {
[self setTextAttribute:ASTextUnderlineAttributeName value:underline];
}
- (void)setStrikethrough:(ASTextDecoration *)strikethrough {
[self setTextAttribute:ASTextStrikethroughAttributeName value:strikethrough];
}
- (void)setBackgroundBorder:(ASTextBorder *)border {
[self setTextAttribute:ASTextBackgroundBorderAttributeName value:border];
}
- (void)setBorder:(ASTextBorder *)border {
[self setTextAttribute:ASTextBorderAttributeName value:border];
}
- (void)setAttachment:(ASTextAttachment *)attachment {
[self setTextAttribute:ASTextAttachmentAttributeName value:attachment];
}
@end

View File

@@ -0,0 +1,68 @@
//
// ASTextRunDelegate.h
// ASText <https://github.com/ibireme/ASText>
//
// Created by ibireme on 14/10/14.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>
NS_ASSUME_NONNULL_BEGIN
/**
Wrapper for CTRunDelegateRef.
Example:
ASTextRunDelegate *delegate = [ASTextRunDelegate new];
delegate.ascent = 20;
delegate.descent = 4;
delegate.width = 20;
CTRunDelegateRef ctRunDelegate = delegate.CTRunDelegate;
if (ctRunDelegate) {
/// add to attributed string
CFRelease(ctRunDelegate);
}
*/
@interface ASTextRunDelegate : NSObject <NSCopying, NSCoding>
/**
Creates and returns the CTRunDelegate.
@discussion You need call CFRelease() after used.
The CTRunDelegateRef has a strong reference to this ASTextRunDelegate object.
In CoreText, use CTRunDelegateGetRefCon() to get this ASTextRunDelegate object.
@return The CTRunDelegate object.
*/
- (nullable CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED;
/**
Additional information about the the run delegate.
*/
@property (nullable, nonatomic, strong) NSDictionary *userInfo;
/**
The typographic ascent of glyphs in the run.
*/
@property (nonatomic) CGFloat ascent;
/**
The typographic descent of glyphs in the run.
*/
@property (nonatomic) CGFloat descent;
/**
The typographic width of glyphs in the run.
*/
@property (nonatomic) CGFloat width;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,71 @@
//
// ASTextRunDelegate.m
// ASText <https://github.com/ibireme/ASText>
//
// Created by ibireme on 14/10/14.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "ASTextRunDelegate.h"
static void DeallocCallback(void *ref) {
ASTextRunDelegate *self = (__bridge_transfer ASTextRunDelegate *)(ref);
self = nil; // release
}
static CGFloat GetAscentCallback(void *ref) {
ASTextRunDelegate *self = (__bridge ASTextRunDelegate *)(ref);
return self.ascent;
}
static CGFloat GetDecentCallback(void *ref) {
ASTextRunDelegate *self = (__bridge ASTextRunDelegate *)(ref);
return self.descent;
}
static CGFloat GetWidthCallback(void *ref) {
ASTextRunDelegate *self = (__bridge ASTextRunDelegate *)(ref);
return self.width;
}
@implementation ASTextRunDelegate
- (CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED {
CTRunDelegateCallbacks callbacks;
callbacks.version = kCTRunDelegateCurrentVersion;
callbacks.dealloc = DeallocCallback;
callbacks.getAscent = GetAscentCallback;
callbacks.getDescent = GetDecentCallback;
callbacks.getWidth = GetWidthCallback;
return CTRunDelegateCreate(&callbacks, (__bridge_retained void *)(self.copy));
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(_ascent) forKey:@"ascent"];
[aCoder encodeObject:@(_descent) forKey:@"descent"];
[aCoder encodeObject:@(_width) forKey:@"width"];
[aCoder encodeObject:_userInfo forKey:@"userInfo"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
_ascent = ((NSNumber *)[aDecoder decodeObjectForKey:@"ascent"]).floatValue;
_descent = ((NSNumber *)[aDecoder decodeObjectForKey:@"descent"]).floatValue;
_width = ((NSNumber *)[aDecoder decodeObjectForKey:@"width"]).floatValue;
_userInfo = [aDecoder decodeObjectForKey:@"userInfo"];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
typeof(self) one = [self.class new];
one.ascent = self.ascent;
one.descent = self.descent;
one.width = self.width;
one.userInfo = self.userInfo;
return one;
}
@end

View File

@@ -0,0 +1,319 @@
//
// ASTextUtilities.h
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 15/4/6.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>
#import <tgmath.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#ifndef ASTEXT_CLAMP // return the clamped value
#define ASTEXT_CLAMP(_x_, _low_, _high_) (((_x_) > (_high_)) ? (_high_) : (((_x_) < (_low_)) ? (_low_) : (_x_)))
#endif
#ifndef ASTEXT_SWAP // swap two value
#define ASTEXT_SWAP(_a_, _b_) do { __typeof__(_a_) _tmp_ = (_a_); (_a_) = (_b_); (_b_) = _tmp_; } while (0)
#endif
NS_ASSUME_NONNULL_BEGIN
/**
Whether the character is 'line break char':
U+000D (\\r or CR)
U+2028 (Unicode line separator)
U+000A (\\n or LF)
U+2029 (Unicode paragraph separator)
@param c A character
@return YES or NO.
*/
static inline BOOL ASTextIsLinebreakChar(unichar c) {
switch (c) {
case 0x000D:
case 0x2028:
case 0x000A:
case 0x2029:
return YES;
default:
return NO;
}
}
/**
Whether the string is a 'line break':
U+000D (\\r or CR)
U+2028 (Unicode line separator)
U+000A (\\n or LF)
U+2029 (Unicode paragraph separator)
\\r\\n, in that order (also known as CRLF)
@param str A string
@return YES or NO.
*/
static inline BOOL ASTextIsLinebreakString(NSString * _Nullable str) {
if (str.length > 2 || str.length == 0) return NO;
if (str.length == 1) {
unichar c = [str characterAtIndex:0];
return ASTextIsLinebreakChar(c);
} else {
return ([str characterAtIndex:0] == '\r') && ([str characterAtIndex:1] == '\n');
}
}
/**
If the string has a 'line break' suffix, return the 'line break' length.
@param str A string.
@return The length of the tail line break: 0, 1 or 2.
*/
static inline NSUInteger ASTextLinebreakTailLength(NSString * _Nullable str) {
if (str.length >= 2) {
unichar c2 = [str characterAtIndex:str.length - 1];
if (ASTextIsLinebreakChar(c2)) {
unichar c1 = [str characterAtIndex:str.length - 2];
if (c1 == '\r' && c2 == '\n') return 2;
else return 1;
} else {
return 0;
}
} else if (str.length == 1) {
return ASTextIsLinebreakChar([str characterAtIndex:0]) ? 1 : 0;
} else {
return 0;
}
}
/**
Whether the font contains color bitmap glyphs.
@discussion Only `AppleColorEmoji` contains color bitmap glyphs in iOS system fonts.
@param font A font.
@return YES: the font contains color bitmap glyphs, NO: the font has no color bitmap glyph.
*/
static inline BOOL ASTextCTFontContainsColorBitmapGlyphs(CTFontRef font) {
return (CTFontGetSymbolicTraits(font) & kCTFontTraitColorGlyphs) != 0;
}
/**
Get the `AppleColorEmoji` font's ascent with a specified font size.
It may used to create custom emoji.
@param fontSize The specified font size.
@return The font ascent.
*/
static inline CGFloat ASTextEmojiGetAscentWithFontSize(CGFloat fontSize) {
if (fontSize < 16) {
return 1.25 * fontSize;
} else if (16 <= fontSize && fontSize <= 24) {
return 0.5 * fontSize + 12;
} else {
return fontSize;
}
}
/**
Get the `AppleColorEmoji` font's descent with a specified font size.
It may used to create custom emoji.
@param fontSize The specified font size.
@return The font descent.
*/
static inline CGFloat ASTextEmojiGetDescentWithFontSize(CGFloat fontSize) {
if (fontSize < 16) {
return 0.390625 * fontSize;
} else if (16 <= fontSize && fontSize <= 24) {
return 0.15625 * fontSize + 3.75;
} else {
return 0.3125 * fontSize;
}
return 0;
}
/**
Get the `AppleColorEmoji` font's glyph bounding rect with a specified font size.
It may used to create custom emoji.
@param fontSize The specified font size.
@return The font glyph bounding rect.
*/
static inline CGRect ASTextEmojiGetGlyphBoundingRectWithFontSize(CGFloat fontSize) {
CGRect rect;
rect.origin.x = 0.75;
rect.size.width = rect.size.height = ASTextEmojiGetAscentWithFontSize(fontSize);
if (fontSize < 16) {
rect.origin.y = -0.2525 * fontSize;
} else if (16 <= fontSize && fontSize <= 24) {
rect.origin.y = 0.1225 * fontSize -6;
} else {
rect.origin.y = -0.1275 * fontSize;
}
return rect;
}
/**
Get the character set which should rotate in vertical form.
@return The shared character set.
*/
NSCharacterSet *ASTextVerticalFormRotateCharacterSet();
/**
Get the character set which should rotate and move in vertical form.
@return The shared character set.
*/
NSCharacterSet *ASTextVerticalFormRotateAndMoveCharacterSet();
/// Get the transform rotation.
/// @return the rotation in radians [-PI,PI] ([-180°,180°])
static inline CGFloat ASTextCGAffineTransformGetRotation(CGAffineTransform transform) {
return atan2(transform.b, transform.a);
}
/// Negates/inverts a UIEdgeInsets.
static inline UIEdgeInsets ASTextUIEdgeInsetsInvert(UIEdgeInsets insets) {
return UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right);
}
/**
Returns a rectangle to fit the @param rect with specified content mode.
@param rect The constrant rect
@param size The content size
@param mode The content mode
@return A rectangle for the given content mode.
@discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill.
*/
CGRect ASTextCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode);
/// Returns the center for the rectangle.
static inline CGPoint ASTextCGRectGetCenter(CGRect rect) {
return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
}
/// Returns the area of the rectangle.
static inline CGFloat ASTextCGRectGetArea(CGRect rect) {
if (CGRectIsNull(rect)) return 0;
rect = CGRectStandardize(rect);
return rect.size.width * rect.size.height;
}
/// Returns the minmium distance between a point to a rectangle.
static inline CGFloat ASTextCGPointGetDistanceToRect(CGPoint p, CGRect r) {
r = CGRectStandardize(r);
if (CGRectContainsPoint(r, p)) return 0;
CGFloat distV, distH;
if (CGRectGetMinY(r) <= p.y && p.y <= CGRectGetMaxY(r)) {
distV = 0;
} else {
distV = p.y < CGRectGetMinY(r) ? CGRectGetMinY(r) - p.y : p.y - CGRectGetMaxY(r);
}
if (CGRectGetMinX(r) <= p.x && p.x <= CGRectGetMaxX(r)) {
distH = 0;
} else {
distH = p.x < CGRectGetMinX(r) ? CGRectGetMinX(r) - p.x : p.x - CGRectGetMaxX(r);
}
return MAX(distV, distH);
}
/// Convert point to pixel.
static inline CGFloat ASTextCGFloatToPixel(CGFloat value) {
return value * ASScreenScale();
}
/// Convert pixel to point.
static inline CGFloat ASTextCGFloatFromPixel(CGFloat value) {
return value / ASScreenScale();
}
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
static inline CGFloat ASTextCGFloatPixelHalf(CGFloat value) {
CGFloat scale = ASScreenScale();
return (floor(value * scale) + 0.5) / scale;
}
/// floor point value for pixel-aligned
static inline CGPoint ASTextCGPointPixelFloor(CGPoint point) {
CGFloat scale = ASScreenScale();
return CGPointMake(floor(point.x * scale) / scale,
floor(point.y * scale) / scale);
}
/// round point value for pixel-aligned
static inline CGPoint ASTextCGPointPixelRound(CGPoint point) {
CGFloat scale = ASScreenScale();
return CGPointMake(round(point.x * scale) / scale,
round(point.y * scale) / scale);
}
/// ceil point value for pixel-aligned
static inline CGPoint ASTextCGPointPixelCeil(CGPoint point) {
CGFloat scale = ASScreenScale();
return CGPointMake(ceil(point.x * scale) / scale,
ceil(point.y * scale) / scale);
}
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
static inline CGPoint ASTextCGPointPixelHalf(CGPoint point) {
CGFloat scale = ASScreenScale();
return CGPointMake((floor(point.x * scale) + 0.5) / scale,
(floor(point.y * scale) + 0.5) / scale);
}
/// round point value for pixel-aligned
static inline CGRect ASTextCGRectPixelRound(CGRect rect) {
CGPoint origin = ASTextCGPointPixelRound(rect.origin);
CGPoint corner = ASTextCGPointPixelRound(CGPointMake(rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height));
return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
}
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
static inline CGRect ASTextCGRectPixelHalf(CGRect rect) {
CGPoint origin = ASTextCGPointPixelHalf(rect.origin);
CGPoint corner = ASTextCGPointPixelHalf(CGPointMake(rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height));
return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
}
static inline UIFont * _Nullable ASTextFontWithBold(UIFont *font) {
return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold] size:font.pointSize];
}
static inline UIFont * _Nullable ASTextFontWithItalic(UIFont *font) {
return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic] size:font.pointSize];
}
static inline UIFont * _Nullable ASTextFontWithBoldItalic(UIFont *font) {
return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold | UIFontDescriptorTraitItalic] size:font.pointSize];
}
/**
Convert CFRange to NSRange
@param range CFRange @return NSRange
*/
static inline NSRange ASTextNSRangeFromCFRange(CFRange range) {
return NSMakeRange(range.location, range.length);
}
/**
Convert NSRange to CFRange
@param range NSRange @return CFRange
*/
static inline CFRange ASTextCFRangeFromNSRange(NSRange range) {
return CFRangeMake(range.location, range.length);
}
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,146 @@
//
// ASTextUtilities.m
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 15/4/6.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "ASTextUtilities.h"
#import <Accelerate/Accelerate.h>
NSCharacterSet *ASTextVerticalFormRotateCharacterSet() {
static NSMutableCharacterSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSMutableCharacterSet new];
[set addCharactersInRange:NSMakeRange(0x1100, 256)]; // Hangul Jamo
[set addCharactersInRange:NSMakeRange(0x2460, 160)]; // Enclosed Alphanumerics
[set addCharactersInRange:NSMakeRange(0x2600, 256)]; // Miscellaneous Symbols
[set addCharactersInRange:NSMakeRange(0x2700, 192)]; // Dingbats
[set addCharactersInRange:NSMakeRange(0x2E80, 128)]; // CJK Radicals Supplement
[set addCharactersInRange:NSMakeRange(0x2F00, 224)]; // Kangxi Radicals
[set addCharactersInRange:NSMakeRange(0x2FF0, 16)]; // Ideographic Description Characters
[set addCharactersInRange:NSMakeRange(0x3000, 64)]; // CJK Symbols and Punctuation
[set removeCharactersInRange:NSMakeRange(0x3008, 10)];
[set removeCharactersInRange:NSMakeRange(0x3014, 12)];
[set addCharactersInRange:NSMakeRange(0x3040, 96)]; // Hiragana
[set addCharactersInRange:NSMakeRange(0x30A0, 96)]; // Katakana
[set addCharactersInRange:NSMakeRange(0x3100, 48)]; // Bopomofo
[set addCharactersInRange:NSMakeRange(0x3130, 96)]; // Hangul Compatibility Jamo
[set addCharactersInRange:NSMakeRange(0x3190, 16)]; // Kanbun
[set addCharactersInRange:NSMakeRange(0x31A0, 32)]; // Bopomofo Extended
[set addCharactersInRange:NSMakeRange(0x31C0, 48)]; // CJK Strokes
[set addCharactersInRange:NSMakeRange(0x31F0, 16)]; // Katakana Phonetic Extensions
[set addCharactersInRange:NSMakeRange(0x3200, 256)]; // Enclosed CJK Letters and Months
[set addCharactersInRange:NSMakeRange(0x3300, 256)]; // CJK Compatibility
[set addCharactersInRange:NSMakeRange(0x3400, 2582)]; // CJK Unified Ideographs Extension A
[set addCharactersInRange:NSMakeRange(0x4E00, 20941)]; // CJK Unified Ideographs
[set addCharactersInRange:NSMakeRange(0xAC00, 11172)]; // Hangul Syllables
[set addCharactersInRange:NSMakeRange(0xD7B0, 80)]; // Hangul Jamo Extended-B
[set addCharactersInString:@""]; // U+F8FF (Private Use Area)
[set addCharactersInRange:NSMakeRange(0xF900, 512)]; // CJK Compatibility Ideographs
[set addCharactersInRange:NSMakeRange(0xFE10, 16)]; // Vertical Forms
[set addCharactersInRange:NSMakeRange(0xFF00, 240)]; // Halfwidth and Fullwidth Forms
[set addCharactersInRange:NSMakeRange(0x1F200, 256)]; // Enclosed Ideographic Supplement
[set addCharactersInRange:NSMakeRange(0x1F300, 768)]; // Enclosed Ideographic Supplement
[set addCharactersInRange:NSMakeRange(0x1F600, 80)]; // Emoticons (Emoji)
[set addCharactersInRange:NSMakeRange(0x1F680, 128)]; // Transport and Map Symbols
// See http://unicode-table.com/ for more information.
});
return set;
}
NSCharacterSet *ASTextVerticalFormRotateAndMoveCharacterSet() {
static NSMutableCharacterSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSMutableCharacterSet new];
[set addCharactersInString:@",。、."];
});
return set;
}
CGRect ASTextCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode) {
rect = CGRectStandardize(rect);
size.width = size.width < 0 ? -size.width : size.width;
size.height = size.height < 0 ? -size.height : size.height;
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
switch (mode) {
case UIViewContentModeScaleAspectFit:
case UIViewContentModeScaleAspectFill: {
if (rect.size.width < 0.01 || rect.size.height < 0.01 ||
size.width < 0.01 || size.height < 0.01) {
rect.origin = center;
rect.size = CGSizeZero;
} else {
CGFloat scale;
if (mode == UIViewContentModeScaleAspectFit) {
if (size.width / size.height < rect.size.width / rect.size.height) {
scale = rect.size.height / size.height;
} else {
scale = rect.size.width / size.width;
}
} else {
if (size.width / size.height < rect.size.width / rect.size.height) {
scale = rect.size.width / size.width;
} else {
scale = rect.size.height / size.height;
}
}
size.width *= scale;
size.height *= scale;
rect.size = size;
rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
}
} break;
case UIViewContentModeCenter: {
rect.size = size;
rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
} break;
case UIViewContentModeTop: {
rect.origin.x = center.x - size.width * 0.5;
rect.size = size;
} break;
case UIViewContentModeBottom: {
rect.origin.x = center.x - size.width * 0.5;
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeLeft: {
rect.origin.y = center.y - size.height * 0.5;
rect.size = size;
} break;
case UIViewContentModeRight: {
rect.origin.y = center.y - size.height * 0.5;
rect.origin.x += rect.size.width - size.width;
rect.size = size;
} break;
case UIViewContentModeTopLeft: {
rect.size = size;
} break;
case UIViewContentModeTopRight: {
rect.origin.x += rect.size.width - size.width;
rect.size = size;
} break;
case UIViewContentModeBottomLeft: {
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeBottomRight: {
rect.origin.x += rect.size.width - size.width;
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeScaleToFill:
case UIViewContentModeRedraw:
default: {
rect = rect;
}
}
return rect;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
//
// NSParagraphStyle+ASText.h
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 14/10/7.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `NSParagraphStyle` to work with CoreText.
*/
@interface NSParagraphStyle (ASText)
/**
Creates a new NSParagraphStyle object from the CoreText Style.
@param CTStyle CoreText Paragraph Style.
@return a new NSParagraphStyle
*/
+ (nullable NSParagraphStyle *)as_styleWithCTStyle:(CTParagraphStyleRef)CTStyle;
/**
Creates and returns a CoreText Paragraph Style. (need call CFRelease() after used)
*/
- (nullable CTParagraphStyleRef)as_CTStyle CF_RETURNS_RETAINED;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,218 @@
//
// NSParagraphStyle+ASText.m
// Modified from YYText <https://github.com/ibireme/YYText>
//
// Created by ibireme on 14/10/7.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSParagraphStyle+ASText.h"
#import "ASTextAttribute.h"
#import <CoreText/CoreText.h>
// Dummy class for category
@interface NSParagraphStyle_ASText : NSObject @end
@implementation NSParagraphStyle_ASText @end
@implementation NSParagraphStyle (ASText)
+ (NSParagraphStyle *)as_styleWithCTStyle:(CTParagraphStyleRef)CTStyle {
if (CTStyle == NULL) return nil;
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CGFloat lineSpacing;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(CGFloat), &lineSpacing)) {
style.lineSpacing = lineSpacing;
}
#pragma clang diagnostic pop
CGFloat paragraphSpacing;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), &paragraphSpacing)) {
style.paragraphSpacing = paragraphSpacing;
}
CTTextAlignment alignment;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment)) {
style.alignment = NSTextAlignmentFromCTTextAlignment(alignment);
}
CGFloat firstLineHeadIndent;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineHeadIndent)) {
style.firstLineHeadIndent = firstLineHeadIndent;
}
CGFloat headIndent;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent)) {
style.headIndent = headIndent;
}
CGFloat tailIndent;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent)) {
style.tailIndent = tailIndent;
}
CTLineBreakMode lineBreakMode;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode)) {
style.lineBreakMode = (NSLineBreakMode)lineBreakMode;
}
CGFloat minimumLineHeight;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minimumLineHeight)) {
style.minimumLineHeight = minimumLineHeight;
}
CGFloat maximumLineHeight;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maximumLineHeight)) {
style.maximumLineHeight = maximumLineHeight;
}
CTWritingDirection baseWritingDirection;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &baseWritingDirection)) {
style.baseWritingDirection = (NSWritingDirection)baseWritingDirection;
}
CGFloat lineHeightMultiple;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple)) {
style.lineHeightMultiple = lineHeightMultiple;
}
CGFloat paragraphSpacingBefore;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), &paragraphSpacingBefore)) {
style.paragraphSpacingBefore = paragraphSpacingBefore;
}
CFArrayRef tabStops;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTabStops, sizeof(CFArrayRef), &tabStops)) {
NSMutableArray *tabs = [NSMutableArray new];
[((__bridge NSArray *)(tabStops))enumerateObjectsUsingBlock : ^(id obj, NSUInteger idx, BOOL *stop) {
CTTextTabRef ctTab = (__bridge CFTypeRef)obj;
NSTextTab *tab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentFromCTTextAlignment(CTTextTabGetAlignment(ctTab)) location:CTTextTabGetLocation(ctTab) options:(__bridge id)CTTextTabGetOptions(ctTab)];
[tabs addObject:tab];
}];
if (tabs.count) {
style.tabStops = tabs;
}
}
CGFloat defaultTabInterval;
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierDefaultTabInterval, sizeof(CGFloat), &defaultTabInterval)) {
style.defaultTabInterval = defaultTabInterval;
}
return style;
}
- (CTParagraphStyleRef)as_CTStyle CF_RETURNS_RETAINED {
CTParagraphStyleSetting set[kCTParagraphStyleSpecifierCount] = { 0 };
int count = 0;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CGFloat lineSpacing = self.lineSpacing;
set[count].spec = kCTParagraphStyleSpecifierLineSpacing;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &lineSpacing;
count++;
#pragma clang diagnostic pop
CGFloat paragraphSpacing = self.paragraphSpacing;
set[count].spec = kCTParagraphStyleSpecifierParagraphSpacing;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &paragraphSpacing;
count++;
CTTextAlignment alignment = NSTextAlignmentToCTTextAlignment(self.alignment);
set[count].spec = kCTParagraphStyleSpecifierAlignment;
set[count].valueSize = sizeof(CTTextAlignment);
set[count].value = &alignment;
count++;
CGFloat firstLineHeadIndent = self.firstLineHeadIndent;
set[count].spec = kCTParagraphStyleSpecifierFirstLineHeadIndent;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &firstLineHeadIndent;
count++;
CGFloat headIndent = self.headIndent;
set[count].spec = kCTParagraphStyleSpecifierHeadIndent;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &headIndent;
count++;
CGFloat tailIndent = self.tailIndent;
set[count].spec = kCTParagraphStyleSpecifierTailIndent;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &tailIndent;
count++;
CTLineBreakMode paraLineBreak = (CTLineBreakMode)self.lineBreakMode;
set[count].spec = kCTParagraphStyleSpecifierLineBreakMode;
set[count].valueSize = sizeof(CTLineBreakMode);
set[count].value = &paraLineBreak;
count++;
CGFloat minimumLineHeight = self.minimumLineHeight;
set[count].spec = kCTParagraphStyleSpecifierMinimumLineHeight;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &minimumLineHeight;
count++;
CGFloat maximumLineHeight = self.maximumLineHeight;
set[count].spec = kCTParagraphStyleSpecifierMaximumLineHeight;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &maximumLineHeight;
count++;
CTWritingDirection paraWritingDirection = (CTWritingDirection)self.baseWritingDirection;
set[count].spec = kCTParagraphStyleSpecifierBaseWritingDirection;
set[count].valueSize = sizeof(CTWritingDirection);
set[count].value = &paraWritingDirection;
count++;
CGFloat lineHeightMultiple = self.lineHeightMultiple;
set[count].spec = kCTParagraphStyleSpecifierLineHeightMultiple;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &lineHeightMultiple;
count++;
CGFloat paragraphSpacingBefore = self.paragraphSpacingBefore;
set[count].spec = kCTParagraphStyleSpecifierParagraphSpacingBefore;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &paragraphSpacingBefore;
count++;
NSMutableArray *tabs = [NSMutableArray array];
NSInteger numTabs = self.tabStops.count;
if (numTabs) {
[self.tabStops enumerateObjectsUsingBlock: ^(NSTextTab *tab, NSUInteger idx, BOOL *stop) {
CTTextTabRef ctTab = CTTextTabCreate(NSTextAlignmentToCTTextAlignment(tab.alignment), tab.location, (__bridge CFTypeRef)tab.options);
[tabs addObject:(__bridge id)ctTab];
CFRelease(ctTab);
}];
CFArrayRef tabStops = (__bridge CFArrayRef)(tabs);
set[count].spec = kCTParagraphStyleSpecifierTabStops;
set[count].valueSize = sizeof(CFArrayRef);
set[count].value = &tabStops;
count++;
}
CGFloat defaultTabInterval = self.defaultTabInterval;
set[count].spec = kCTParagraphStyleSpecifierDefaultTabInterval;
set[count].valueSize = sizeof(CGFloat);
set[count].value = &defaultTabInterval;
count++;
CTParagraphStyleRef style = CTParagraphStyleCreate(set, count);
return style;
}
@end

View File

@@ -18,11 +18,13 @@
#import "AppDelegate.h" #import "AppDelegate.h"
#import "ViewController.h" #import "ViewController.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@implementation AppDelegate @implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ {
[ASTextNode setExperimentOptions:ASTextNodeExperimentRandomInstances];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor]; self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]];

View File

@@ -98,7 +98,20 @@ static const CGFloat kInnerPadding = 10.0f;
[self addSubnode:_imageNode]; [self addSubnode:_imageNode];
// lorem ipsum text, plus some nice styling // lorem ipsum text, plus some nice styling
_textNode = [[ASTextNode alloc] init]; _textNode = [[ASTextNode alloc] init];
_textNode.shadowColor = [UIColor blackColor].CGColor;
_textNode.shadowRadius = 3;
_textNode.shadowOffset = CGSizeMake(-2, -2);
_textNode.shadowOpacity = 0.3;
if (_textNode.usingExperiment) {
_textNode.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:1 alpha:1];
} else {
_textNode.backgroundColor = [UIColor colorWithRed:1 green:0.9 blue:0.9 alpha:1];
}
_textNode.maximumNumberOfLines = 2;
_textNode.truncationAttributedText = [[NSAttributedString alloc] initWithString:@"…"];
_textNode.additionalTruncationMessage = [[NSAttributedString alloc] initWithString:@"More"];
_textNode.attributedText = [[NSAttributedString alloc] initWithString:[self kittyIpsum] attributes:[self textStyle]]; _textNode.attributedText = [[NSAttributedString alloc] initWithString:[self kittyIpsum] attributes:[self textStyle]];
[self addSubnode:_textNode]; [self addSubnode:_textNode];