mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
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:
@@ -365,6 +365,26 @@
|
||||
CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.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, ); }; };
|
||||
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, ); }; };
|
||||
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 */; };
|
||||
@@ -796,6 +816,26 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1220,6 +1260,8 @@
|
||||
058D0A01195D050800B7D73C /* Private */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CCD5230F1EBD658C001F2191 /* ASTextNode2.h */,
|
||||
CCD523101EBD658C001F2191 /* ASTextNode2.mm */,
|
||||
CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */,
|
||||
CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */,
|
||||
CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */,
|
||||
@@ -1294,6 +1336,7 @@
|
||||
CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */,
|
||||
83A7D9581D44542100BF333E /* ASWeakMap.h */,
|
||||
83A7D9591D44542100BF333E /* ASWeakMap.m */,
|
||||
CCCCCCC11EC3EF060087FE10 /* TextExperiment */,
|
||||
);
|
||||
path = Private;
|
||||
sourceTree = "<group>";
|
||||
@@ -1432,6 +1475,55 @@
|
||||
path = Layout;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1509,6 +1601,7 @@
|
||||
E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */,
|
||||
E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */,
|
||||
E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */,
|
||||
CCCCCCE31EC3EF060087FE10 /* NSParagraphStyle+ASText.h in Headers */,
|
||||
E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */,
|
||||
E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */,
|
||||
696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */,
|
||||
@@ -1528,7 +1621,9 @@
|
||||
B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */,
|
||||
68C215581DE10D330019C4BC /* ASCollectionViewLayoutInspector.h in Headers */,
|
||||
B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */,
|
||||
CCD523111EBD658C001F2191 /* ASTextNode2.h in Headers */,
|
||||
B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */,
|
||||
CCCCCCD71EC3EF060087FE10 /* ASTextInput.h in Headers */,
|
||||
B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */,
|
||||
9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */,
|
||||
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */,
|
||||
@@ -1558,6 +1653,7 @@
|
||||
B35062171B010EFD0018CF92 /* ASDataController.h in Headers */,
|
||||
34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */,
|
||||
68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */,
|
||||
CCCCCCD91EC3EF060087FE10 /* ASTextLayout.h in Headers */,
|
||||
A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */,
|
||||
9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */,
|
||||
CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */,
|
||||
@@ -1581,10 +1677,12 @@
|
||||
698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */,
|
||||
9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */,
|
||||
DE7EF4F81DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h in Headers */,
|
||||
CCCCCCD51EC3EF060087FE10 /* ASTextDebugOption.h in Headers */,
|
||||
CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */,
|
||||
254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */,
|
||||
B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */,
|
||||
68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */,
|
||||
CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */,
|
||||
B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */,
|
||||
CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */,
|
||||
B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */,
|
||||
@@ -1598,6 +1696,7 @@
|
||||
69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */,
|
||||
254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */,
|
||||
68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */,
|
||||
CCCCCCDD1EC3EF060087FE10 /* ASTextAttribute.h in Headers */,
|
||||
B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */,
|
||||
044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */,
|
||||
AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */,
|
||||
@@ -1673,7 +1772,10 @@
|
||||
B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */,
|
||||
CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */,
|
||||
25E327571C16819500A2170C /* ASPagerNode.h in Headers */,
|
||||
CCCCCCDB1EC3EF060087FE10 /* ASTextLine.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 */,
|
||||
34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */,
|
||||
CC0F885C1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h in Headers */,
|
||||
@@ -1957,6 +2059,7 @@
|
||||
9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */,
|
||||
3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */,
|
||||
CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */,
|
||||
CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */,
|
||||
8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */,
|
||||
B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */,
|
||||
690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */,
|
||||
@@ -1986,6 +2089,7 @@
|
||||
E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */,
|
||||
9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
|
||||
690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */,
|
||||
CCCCCCD81EC3EF060087FE10 /* ASTextInput.m in Sources */,
|
||||
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
|
||||
DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */,
|
||||
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
|
||||
@@ -2007,10 +2111,12 @@
|
||||
8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */,
|
||||
B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */,
|
||||
767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */,
|
||||
CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.m in Sources */,
|
||||
34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */,
|
||||
B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */,
|
||||
25E327591C16819500A2170C /* ASPagerNode.m in Sources */,
|
||||
636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */,
|
||||
CCD523121EBD658C001F2191 /* ASTextNode2.mm in Sources */,
|
||||
B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */,
|
||||
DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */,
|
||||
254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */,
|
||||
@@ -2039,6 +2145,8 @@
|
||||
34EFC75E1B701BF000AD841F /* ASInternalHelpers.m in Sources */,
|
||||
34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */,
|
||||
DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
|
||||
CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.m in Sources */,
|
||||
CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.m in Sources */,
|
||||
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */,
|
||||
34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */,
|
||||
CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */,
|
||||
@@ -2049,6 +2157,7 @@
|
||||
B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */,
|
||||
34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */,
|
||||
044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m in Sources */,
|
||||
CCCCCCDE1EC3EF060087FE10 /* ASTextAttribute.m in Sources */,
|
||||
CCA282B51E9EA7310037E8B7 /* ASTipsController.m in Sources */,
|
||||
B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */,
|
||||
0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */,
|
||||
@@ -2073,7 +2182,9 @@
|
||||
E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m in Sources */,
|
||||
DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */,
|
||||
68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */,
|
||||
CCCCCCDC1EC3EF060087FE10 /* ASTextLine.m in Sources */,
|
||||
34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */,
|
||||
CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */,
|
||||
690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */,
|
||||
68C2155A1DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */,
|
||||
DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */,
|
||||
@@ -2086,6 +2197,7 @@
|
||||
254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */,
|
||||
34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */,
|
||||
254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */,
|
||||
CCCCCCE21EC3EF060087FE10 /* ASTextUtilities.m in Sources */,
|
||||
CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.m in Sources */,
|
||||
697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */,
|
||||
B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
|
||||
|
||||
@@ -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)
|
||||
- [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)
|
||||
- [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)
|
||||
- [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)
|
||||
|
||||
@@ -21,6 +21,18 @@
|
||||
|
||||
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 ()
|
||||
|
||||
/**
|
||||
@@ -38,6 +50,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@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
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASTextNode2.h>
|
||||
#import <AsyncDisplayKit/ASTextNode+Beta.h>
|
||||
|
||||
#include <mutex>
|
||||
@@ -263,14 +264,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
#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
|
||||
{
|
||||
[super didLoad];
|
||||
@@ -1386,6 +1379,70 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
}
|
||||
#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
|
||||
|
||||
@implementation ASTextNode (Deprecated)
|
||||
|
||||
228
Source/Private/ASTextNode2.h
Normal file
228
Source/Private/ASTextNode2.h
Normal 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
|
||||
|
||||
|
||||
1088
Source/Private/ASTextNode2.mm
Normal file
1088
Source/Private/ASTextNode2.mm
Normal file
File diff suppressed because it is too large
Load Diff
95
Source/Private/TextExperiment/Component/ASTextDebugOption.h
Executable file
95
Source/Private/TextExperiment/Component/ASTextDebugOption.h
Executable 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
|
||||
139
Source/Private/TextExperiment/Component/ASTextDebugOption.m
Executable file
139
Source/Private/TextExperiment/Component/ASTextDebugOption.m
Executable 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
|
||||
|
||||
87
Source/Private/TextExperiment/Component/ASTextInput.h
Executable file
87
Source/Private/TextExperiment/Component/ASTextInput.h
Executable 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
|
||||
152
Source/Private/TextExperiment/Component/ASTextInput.m
Executable file
152
Source/Private/TextExperiment/Component/ASTextInput.m
Executable 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
|
||||
548
Source/Private/TextExperiment/Component/ASTextLayout.h
Executable file
548
Source/Private/TextExperiment/Component/ASTextLayout.h
Executable 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
|
||||
3370
Source/Private/TextExperiment/Component/ASTextLayout.m
Executable file
3370
Source/Private/TextExperiment/Component/ASTextLayout.m
Executable file
File diff suppressed because it is too large
Load Diff
79
Source/Private/TextExperiment/Component/ASTextLine.h
Executable file
79
Source/Private/TextExperiment/Component/ASTextLine.h
Executable 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
|
||||
167
Source/Private/TextExperiment/Component/ASTextLine.m
Executable file
167
Source/Private/TextExperiment/Component/ASTextLine.m
Executable 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
|
||||
347
Source/Private/TextExperiment/String/ASTextAttribute.h
Executable file
347
Source/Private/TextExperiment/String/ASTextAttribute.h
Executable 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
|
||||
485
Source/Private/TextExperiment/String/ASTextAttribute.m
Executable file
485
Source/Private/TextExperiment/String/ASTextAttribute.m
Executable 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
|
||||
|
||||
68
Source/Private/TextExperiment/String/ASTextRunDelegate.h
Executable file
68
Source/Private/TextExperiment/String/ASTextRunDelegate.h
Executable 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
|
||||
71
Source/Private/TextExperiment/String/ASTextRunDelegate.m
Executable file
71
Source/Private/TextExperiment/String/ASTextRunDelegate.m
Executable 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
|
||||
319
Source/Private/TextExperiment/Utility/ASTextUtilities.h
Executable file
319
Source/Private/TextExperiment/Utility/ASTextUtilities.h
Executable 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
|
||||
146
Source/Private/TextExperiment/Utility/ASTextUtilities.m
Executable file
146
Source/Private/TextExperiment/Utility/ASTextUtilities.m
Executable 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;
|
||||
}
|
||||
1393
Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h
Executable file
1393
Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h
Executable file
File diff suppressed because it is too large
Load Diff
1248
Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m
Executable file
1248
Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m
Executable file
File diff suppressed because it is too large
Load Diff
37
Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.h
Executable file
37
Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.h
Executable 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
|
||||
218
Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.m
Executable file
218
Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.m
Executable 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), ¶graphSpacing)) {
|
||||
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), ¶graphSpacingBefore)) {
|
||||
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 = ¶graphSpacing;
|
||||
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 = ¶LineBreak;
|
||||
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 = ¶WritingDirection;
|
||||
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 = ¶graphSpacingBefore;
|
||||
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
|
||||
@@ -18,11 +18,13 @@
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import "ViewController.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[ASTextNode setExperimentOptions:ASTextNodeExperimentRandomInstances];
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
self.window.backgroundColor = [UIColor whiteColor];
|
||||
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]];
|
||||
|
||||
@@ -98,7 +98,20 @@ static const CGFloat kInnerPadding = 10.0f;
|
||||
[self addSubnode:_imageNode];
|
||||
|
||||
// lorem ipsum text, plus some nice styling
|
||||
|
||||
_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]];
|
||||
[self addSubnode:_textNode];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user