From b9959c779ccc3370c67050bba0586a2d81ecece9 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 28 Dec 2017 05:23:38 +0200 Subject: [PATCH] Implement tailor made data structure and logic for text on a path rendering. Remove postinstall script and dependencies on PerformanceBezier & QuartzBookPack. --- ios/RNSVG.xcodeproj/project.pbxproj | 114 ++----------- ios/Text/RNSVGTSpan.h | 3 +- ios/Text/RNSVGTSpan.m | 66 +++++-- ios/Utils/BezierElement.h | 26 +++ ios/Utils/BezierElement.m | 62 +++++++ ios/Utils/UIBezierPath+TextRendering.h | 12 ++ ios/Utils/UIBezierPath+TextRendering.m | 227 +++++++++++++++++++++++++ package.json | 3 +- scripts/install.js | 20 --- 9 files changed, 396 insertions(+), 137 deletions(-) create mode 100644 ios/Utils/BezierElement.h create mode 100644 ios/Utils/BezierElement.m create mode 100644 ios/Utils/UIBezierPath+TextRendering.h create mode 100644 ios/Utils/UIBezierPath+TextRendering.m delete mode 100644 scripts/install.js diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index 3044c51e..a5dca387 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -49,14 +49,12 @@ 7F08CE9B1E23476900650F83 /* RNSVGTSpanManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE991E23476900650F83 /* RNSVGTSpanManager.m */; }; 7F08CEA01E23479700650F83 /* RNSVGTextPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE9D1E23479700650F83 /* RNSVGTextPath.m */; }; 7F08CEA11E23479700650F83 /* RNSVGTSpan.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE9F1E23479700650F83 /* RNSVGTSpan.m */; }; - 7F4BB50A1FB1E50000663D5F /* QuartzBookPack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F4BB5051FB1DEC300663D5F /* QuartzBookPack.framework */; }; 7F9CDAFA1E1F809C00E0C805 /* RNSVGPathParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */; }; 7FC260CE1E3499BC00A39833 /* RNSVGViewBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260CD1E3499BC00A39833 /* RNSVGViewBox.m */; }; 7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D01E34A12000A39833 /* RNSVGSymbol.m */; }; 7FC260D41E34A12A00A39833 /* RNSVGSymbolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */; }; 945A8AF81F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; }; 945A8AF91F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; }; - 9494C47A1F47116800D5BCFD /* PerformanceBezier.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */; }; 9494C4D81F473BA700D5BCFD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */; }; 9494C4DA1F473BCB00D5BCFD /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D91F473BCB00D5BCFD /* CoreText.framework */; }; 9494C4DC1F473BD900D5BCFD /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */; }; @@ -89,6 +87,10 @@ 9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */; }; 9494C5481F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; }; 9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; }; + 94EB93171FF4196100C0B251 /* UIBezierPath+TextRendering.m in Sources */ = {isa = PBXBuildFile; fileRef = 94EB93161FF4196100C0B251 /* UIBezierPath+TextRendering.m */; }; + 94EB93181FF4196100C0B251 /* UIBezierPath+TextRendering.m in Sources */ = {isa = PBXBuildFile; fileRef = 94EB93161FF4196100C0B251 /* UIBezierPath+TextRendering.m */; }; + 94EB936C1FF4916F00C0B251 /* BezierElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 94EB936B1FF4916F00C0B251 /* BezierElement.m */; }; + 94EB936D1FF4916F00C0B251 /* BezierElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 94EB936B1FF4916F00C0B251 /* BezierElement.m */; }; A361E76E1EB0C33D00646005 /* RNSVGTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D331CE74E3100887C2B /* RNSVGTextManager.m */; }; A361E76F1EB0C33D00646005 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; }; A361E7701EB0C33D00646005 /* RNSVGRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D471CE74E3D00887C2B /* RNSVGRect.m */; }; @@ -137,37 +139,6 @@ A361E79D1EB0C33D00646005 /* RNSVGDefsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B48C1D3DDCCE0051496D /* RNSVGDefsManager.m */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - 7F4BB5041FB1DEC300663D5F /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 7F917B141FB1D5E900A75AA4; - remoteInfo = QuartzBookPack; - }; - 7F4BB5061FB1DEC300663D5F /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 7F917B1D1FB1D5E900A75AA4; - remoteInfo = QuartzBookPackTests; - }; - 9494C4761F4710FE00D5BCFD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 66F2EBE31A8DC05100D536E9; - remoteInfo = PerformanceBezier; - }; - 9494C4781F4710FE00D5BCFD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 66B9D28C1A8D5FDE00CAC341; - remoteInfo = PerformanceBezierTests; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXCopyFilesBuildPhase section */ 0CF68ABF1AF0540F00FF9E5C /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -281,7 +252,6 @@ 7F08CE9E1E23479700650F83 /* RNSVGTSpan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTSpan.h; path = Text/RNSVGTSpan.h; sourceTree = ""; }; 7F08CE9F1E23479700650F83 /* RNSVGTSpan.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGTSpan.m; path = Text/RNSVGTSpan.m; sourceTree = ""; }; 7F08CEA31E23481F00650F83 /* RNSVGTextAnchor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTextAnchor.h; path = Utils/RNSVGTextAnchor.h; sourceTree = ""; }; - 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = QuartzBookPack.xcodeproj; path = QuartzBookPack/QuartzBookPack.xcodeproj; sourceTree = ""; }; 7F69160D1E3703D800DA6EDC /* RNSVGUnits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGUnits.h; path = Utils/RNSVGUnits.h; sourceTree = ""; }; 7F9CDAF81E1F809C00E0C805 /* RNSVGPathParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGPathParser.h; path = Utils/RNSVGPathParser.h; sourceTree = ""; }; 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGPathParser.m; path = Utils/RNSVGPathParser.m; sourceTree = ""; }; @@ -293,7 +263,6 @@ 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSymbolManager.m; sourceTree = ""; }; 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AlignmentBaseline.m; path = Text/AlignmentBaseline.m; sourceTree = ""; }; 945A8AFA1F4CE41E004BBF6B /* AlignmentBaseline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AlignmentBaseline.h; path = Text/AlignmentBaseline.h; sourceTree = ""; }; - 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PerformanceBezier.xcodeproj; path = PerformanceBezier/PerformanceBezier.xcodeproj; sourceTree = ""; }; 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 9494C4D91F473BCB00D5BCFD /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -327,6 +296,10 @@ 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSide.m; path = Text/TextPathSide.m; sourceTree = ""; }; 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSpacing.m; path = Text/TextPathSpacing.m; sourceTree = ""; }; 94DDAC5C1F3D024300EED511 /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNSVG-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 94EB93151FF4196100C0B251 /* UIBezierPath+TextRendering.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIBezierPath+TextRendering.h"; path = "Utils/UIBezierPath+TextRendering.h"; sourceTree = ""; }; + 94EB93161FF4196100C0B251 /* UIBezierPath+TextRendering.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+TextRendering.m"; path = "Utils/UIBezierPath+TextRendering.m"; sourceTree = ""; }; + 94EB936B1FF4916F00C0B251 /* BezierElement.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierElement.m; path = Utils/BezierElement.m; sourceTree = ""; }; + 94EB93701FF4918D00C0B251 /* BezierElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BezierElement.h; path = Utils/BezierElement.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -334,14 +307,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7F4BB50A1FB1E50000663D5F /* QuartzBookPack.framework in Frameworks */, 9494C4E01F473BED00D5BCFD /* Accelerate.framework in Frameworks */, 9494C4D81F473BA700D5BCFD /* QuartzCore.framework in Frameworks */, 9494C4DA1F473BCB00D5BCFD /* CoreText.framework in Frameworks */, 9494C4DC1F473BD900D5BCFD /* CoreGraphics.framework in Frameworks */, 9494C4DE1F473BDD00D5BCFD /* UIKit.framework in Frameworks */, 9494C4E21F473BF500D5BCFD /* Foundation.framework in Frameworks */, - 9494C47A1F47116800D5BCFD /* PerformanceBezier.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -358,8 +329,6 @@ 0CF68AB81AF0540F00FF9E5C = { isa = PBXGroup; children = ( - 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */, - 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */, 1039D29A1CE7212C001E90A8 /* Utils */, 1039D2801CE71DCF001E90A8 /* Elements */, 1039D27F1CE71D9B001E90A8 /* Text */, @@ -545,19 +514,14 @@ 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */, 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */, 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */, + 94EB93151FF4196100C0B251 /* UIBezierPath+TextRendering.h */, + 94EB93161FF4196100C0B251 /* UIBezierPath+TextRendering.m */, + 94EB936B1FF4916F00C0B251 /* BezierElement.m */, + 94EB93701FF4918D00C0B251 /* BezierElement.h */, ); name = Utils; sourceTree = ""; }; - 7F4BB5001FB1DEC300663D5F /* Products */ = { - isa = PBXGroup; - children = ( - 7F4BB5051FB1DEC300663D5F /* QuartzBookPack.framework */, - 7F4BB5071FB1DEC300663D5F /* QuartzBookPackTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; 9494C2B31F46139600D5BCFD /* Frameworks */ = { isa = PBXGroup; children = ( @@ -571,15 +535,6 @@ name = Frameworks; sourceTree = ""; }; - 9494C4721F4710FE00D5BCFD /* Products */ = { - isa = PBXGroup; - children = ( - 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */, - 9494C4791F4710FE00D5BCFD /* PerformanceBezierTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -640,16 +595,6 @@ mainGroup = 0CF68AB81AF0540F00FF9E5C; productRefGroup = 0CF68AC21AF0540F00FF9E5C /* Products */; projectDirPath = ""; - projectReferences = ( - { - ProductGroup = 9494C4721F4710FE00D5BCFD /* Products */; - ProjectRef = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */; - }, - { - ProductGroup = 7F4BB5001FB1DEC300663D5F /* Products */; - ProjectRef = 7F4BB4FF1FB1DEC300663D5F /* QuartzBookPack.xcodeproj */; - }, - ); projectRoot = ""; targets = ( 0CF68AC01AF0540F00FF9E5C /* RNSVG */, @@ -658,37 +603,6 @@ }; /* End PBXProject section */ -/* Begin PBXReferenceProxy section */ - 7F4BB5051FB1DEC300663D5F /* QuartzBookPack.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = QuartzBookPack.framework; - remoteRef = 7F4BB5041FB1DEC300663D5F /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 7F4BB5071FB1DEC300663D5F /* QuartzBookPackTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = QuartzBookPackTests.xctest; - remoteRef = 7F4BB5061FB1DEC300663D5F /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework.static; - path = PerformanceBezier.framework; - remoteRef = 9494C4761F4710FE00D5BCFD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 9494C4791F4710FE00D5BCFD /* PerformanceBezierTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = PerformanceBezierTests.xctest; - remoteRef = 9494C4781F4710FE00D5BCFD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - /* Begin PBXSourcesBuildPhase section */ 0CF68ABD1AF0540F00FF9E5C /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -700,6 +614,7 @@ 10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */, 10BEC1BC1D3F66F500FDCB19 /* RNSVGLinearGradient.m in Sources */, 9494C5461F4C44DD00D5BCFD /* TextPathSide.m in Sources */, + 94EB93171FF4196100C0B251 /* UIBezierPath+TextRendering.m in Sources */, 1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */, 9494C53C1F4C44DD00D5BCFD /* TextAnchor.m in Sources */, 10BA0D491CE74E3D00887C2B /* RNSVGEllipse.m in Sources */, @@ -734,6 +649,7 @@ 9494C5251F4B605F00D5BCFD /* GlyphContext.m in Sources */, 10BA0D481CE74E3D00887C2B /* RNSVGCircle.m in Sources */, 9494C5401F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */, + 94EB936C1FF4916F00C0B251 /* BezierElement.m in Sources */, 10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */, 1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */, 9494C4FF1F4B5BE800D5BCFD /* FontData.m in Sources */, @@ -767,6 +683,7 @@ A361E7711EB0C33D00646005 /* RNSVGCircleManager.m in Sources */, A361E7721EB0C33D00646005 /* RNSVGLinearGradient.m in Sources */, A361E7731EB0C33D00646005 /* RNSVGPercentageConverter.m in Sources */, + 94EB93181FF4196100C0B251 /* UIBezierPath+TextRendering.m in Sources */, 9494C53F1F4C44DD00D5BCFD /* TextDecoration.m in Sources */, A361E7751EB0C33D00646005 /* RNSVGEllipse.m in Sources */, A361E7761EB0C33D00646005 /* RNSVGPath.m in Sources */, @@ -801,6 +718,7 @@ 9494C53B1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */, 9494C5001F4B5BE800D5BCFD /* FontData.m in Sources */, 9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */, + 94EB936D1FF4916F00C0B251 /* BezierElement.m in Sources */, A361E78D1EB0C33D00646005 /* RNSVGLineManager.m in Sources */, 9494C53D1F4C44DD00D5BCFD /* TextAnchor.m in Sources */, 9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */, diff --git a/ios/Text/RNSVGTSpan.h b/ios/Text/RNSVGTSpan.h index 0f15645b..0b1c7c9e 100644 --- a/ios/Text/RNSVGTSpan.h +++ b/ios/Text/RNSVGTSpan.h @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ #import -#import #import #import #import "RNSVGText.h" @@ -16,6 +15,8 @@ #import "TextPathSpacing.h" #import "TextLengthAdjust.h" #import "AlignmentBaseline.h" +#import "UIBezierPath+TextRendering.h" + @interface RNSVGTSpan : RNSVGText diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index d23da576..ed52be91 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -5,8 +5,6 @@ * This source code is licensed under the MIT-style license found in the * LICENSE file in the root directory of this source tree. */ -#import -#import #import "RNSVGTSpan.h" #import "RNSVGText.h" #import "RNSVGTextPath.h" @@ -17,12 +15,13 @@ NSCharacterSet *separators = nil; @implementation RNSVGTSpan { CGFloat startOffset; - UIBezierPath *_bezierPath; CGPathRef _cache; CGFloat _pathLength; - RNSVGTextPath * textPath; - RNSVGPath * textPathPath; - bool isClosed; + RNSVGTextPath *textPath; + NSMutableArray *lengths; + NSMutableArray *lines; + NSInteger lineCount; + BOOL isClosed; } - (id)init @@ -295,7 +294,7 @@ NSCharacterSet *separators = nil; double textMeasure = CGRectGetWidth(textBounds); double offset = getTextAnchorOffset(textAnchor, textMeasure); - bool hasTextPath = _bezierPath != nil; + bool hasTextPath = textPath != nil; int side = 1; double startOfRendering = 0; @@ -786,9 +785,31 @@ NSCharacterSet *separators = nil; continue; } + int i = 0; + CGFloat totalLength = 0; + CGFloat prevLength = 0; + + // TODO investigate at what lineCount a binary search is faster + while (i < lineCount - 1) { + prevLength = totalLength; + totalLength = [[lengths objectAtIndex: i] floatValue]; + if (totalLength < midPoint) { + i++; + } else { + break; + } + }; + + CGFloat length = totalLength - prevLength; + CGFloat targetPercent = (midPoint - prevLength) / length; + + NSArray * points = [lines objectAtIndex: i]; + CGPoint p1 = [[points objectAtIndex: 0] CGPointValue]; + CGPoint p2 = [[points objectAtIndex: 1] CGPointValue]; + CGPoint slope; - CGFloat percentConsumed = midPoint / _pathLength; - CGPoint mid = [_bezierPath pointAtPercent:percentConsumed withSlope:&slope]; + CGPoint mid = InterpolateLineSegment(p1, p2, targetPercent, &slope); + // Calculate the rotation double angle = atan2(slope.y, slope.x); @@ -814,6 +835,20 @@ NSCharacterSet *separators = nil; return path; } +CGPoint InterpolateLineSegment(CGPoint p1, CGPoint p2, CGFloat percent, CGPoint *slope) +{ + CGFloat dx = p2.x - p1.x; + CGFloat dy = p2.y - p1.y; + + if (slope) + *slope = CGPointMake(dx, dy); + + CGFloat px = p1.x + dx * percent; + CGFloat py = p1.y + dy * percent; + + return CGPointMake(px, py); +} + CGFloat getTextAnchorOffset(enum TextAnchor textAnchor, CGFloat width) { switch (textAnchor) { @@ -830,17 +865,16 @@ CGFloat getTextAnchorOffset(enum TextAnchor textAnchor, CGFloat width) - (void)setupTextPath:(CGContextRef)context { - _bezierPath = nil; textPath = nil; - textPathPath = nil; [self traverseTextSuperviews:^(__kindof RNSVGText *node) { if ([node class] == [RNSVGTextPath class]) { textPath = (RNSVGTextPath*) node; - textPathPath = [textPath getPath]; - _bezierPath = [UIBezierPath bezierPathWithCGPath:[textPathPath getPath:nil]]; - _bezierPath = [_bezierPath bezierPathByFlatteningPathAndImmutable:YES]; - _pathLength = _bezierPath.pathLength; - isClosed = [_bezierPath isClosed]; + RNSVGPath *svgPath = [textPath getPath]; + UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:[svgPath getPath:nil]]; + + lines = [NSMutableArray array]; + lengths = [NSMutableArray array]; + [bezierPath getTextProperties](&_pathLength, &lineCount, lengths, lines, &isClosed); return NO; } return YES; diff --git a/ios/Utils/BezierElement.h b/ios/Utils/BezierElement.h new file mode 100644 index 00000000..17af3394 --- /dev/null +++ b/ios/Utils/BezierElement.h @@ -0,0 +1,26 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + + +#import +#import + +#define NULLPOINT CGRectNull.origin +#define POINT_IS_NULL(_POINT_) CGPointEqualToPoint(_POINT_, NULLPOINT) + +@interface BezierElement : NSObject + +// Element storage +@property (nonatomic, assign) CGPathElementType elementType; +@property (nonatomic, assign) CGPoint point; +@property (nonatomic, assign) CGPoint controlPoint1; +@property (nonatomic, assign) CGPoint controlPoint2; + +// Instance creation ++ (instancetype) elementWithPathElement: (CGPathElement) element; + +@end; + diff --git a/ios/Utils/BezierElement.m b/ios/Utils/BezierElement.m new file mode 100644 index 00000000..ab53465c --- /dev/null +++ b/ios/Utils/BezierElement.m @@ -0,0 +1,62 @@ +/* + + Erica Sadun, http://ericasadun.com + + */ + + +#import "BezierElement.h" + +#pragma mark - Bezier Element - + +@implementation BezierElement +- (instancetype) init +{ + self = [super init]; + if (self) + { + _elementType = kCGPathElementMoveToPoint; + _point = NULLPOINT; + _controlPoint1 = NULLPOINT; + _controlPoint2 = NULLPOINT; + } + return self; +} + ++ (instancetype) elementWithPathElement: (CGPathElement) element +{ + BezierElement *newElement = [[self alloc] init]; + newElement.elementType = element.type; + + switch (newElement.elementType) + { + case kCGPathElementCloseSubpath: + break; + case kCGPathElementMoveToPoint: + case kCGPathElementAddLineToPoint: + { + newElement.point = element.points[0]; + break; + } + case kCGPathElementAddQuadCurveToPoint: + { + newElement.point = element.points[1]; + newElement.controlPoint1 = element.points[0]; + break; + } + case kCGPathElementAddCurveToPoint: + { + newElement.point = element.points[2]; + newElement.controlPoint1 = element.points[0]; + newElement.controlPoint2 = element.points[1]; + break; + } + default: + break; + } + + return newElement; +} + +@end + diff --git a/ios/Utils/UIBezierPath+TextRendering.h b/ios/Utils/UIBezierPath+TextRendering.h new file mode 100644 index 00000000..88eadab2 --- /dev/null +++ b/ios/Utils/UIBezierPath+TextRendering.h @@ -0,0 +1,12 @@ +// +// UIBezierPath+TextRendering.h +// RNSVG +// +// Created by Mikael Sand on 27/12/2017. +// + +#import + +@interface UIBezierPath (TextRendering) +- (void (^)(CGFloat *lengthP, NSInteger *lineCountP, NSMutableArray * lengthsP, NSMutableArray * linesP, BOOL *isClosedP)) getTextProperties; +@end diff --git a/ios/Utils/UIBezierPath+TextRendering.m b/ios/Utils/UIBezierPath+TextRendering.m new file mode 100644 index 00000000..d768d6cb --- /dev/null +++ b/ios/Utils/UIBezierPath+TextRendering.m @@ -0,0 +1,227 @@ +// +// UIBezierPath+TextRendering.m +// RNSVG +// +// Created by Mikael Sand on 27/12/2017. +// + +#import "UIBezierPath+TextRendering.h" +#import "BezierElement.h" + +static CGFloat idealFlatness = .01; + +/** + * returns the distance between two points + */ +CGFloat distance(CGPoint p1, CGPoint p2) +{ + CGFloat dx = p2.x - p1.x; + CGFloat dy = p2.y - p1.y; + + return sqrt(dx*dx + dy*dy); +} + +/** + * returns the dot product of two coordinates + */ +CGFloat dotProduct(const CGPoint p1, const CGPoint p2) { + return p1.x * p2.x + p1.y * p2.y; +} + +/** + * returns the shortest distance from a point to a line + */ +CGFloat distanceOfPointToLine(CGPoint point, CGPoint start, CGPoint end){ + CGPoint v = CGPointMake(end.x - start.x, end.y - start.y); + CGPoint w = CGPointMake(point.x - start.x, point.y - start.y); + CGFloat c1 = dotProduct(w, v); + CGFloat c2 = dotProduct(v, v); + CGFloat d; + if (c1 <= 0) { + d = distance(point, start); + } + else if (c2 <= c1) { + d = distance(point, end); + } + else { + CGFloat b = c1 / c2; + CGPoint Pb = CGPointMake(start.x + b * v.x, start.y + b * v.y); + d = distance(point, Pb); + } + return d; +} + +/** + * calculate the point on a bezier at time t + * where 0 < t < 1 + */ +CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t) +{ + CGPoint q; + CGFloat mt = 1 - t; + + CGPoint bez1[4]; + CGPoint bez2[4]; + + q.x = mt * bez[1].x + t * bez[2].x; + q.y = mt * bez[1].y + t * bez[2].y; + bez1[1].x = mt * bez[0].x + t * bez[1].x; + bez1[1].y = mt * bez[0].y + t * bez[1].y; + bez2[2].x = mt * bez[2].x + t * bez[3].x; + bez2[2].y = mt * bez[2].y + t * bez[3].y; + + bez1[2].x = mt * bez1[1].x + t * q.x; + bez1[2].y = mt * bez1[1].y + t * q.y; + bez2[1].x = mt * q.x + t * bez2[2].x; + bez2[1].y = mt * q.y + t * bez2[2].y; + + bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x; + bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y; + + return CGPointMake(bez1[3].x, bez1[3].y); +} + +// Subdivide a Bézier (specific division) +void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t) +{ + CGPoint q; + CGFloat mt = 1 - t; + + bez1[0].x = bez[0].x; + bez1[0].y = bez[0].y; + bez2[3].x = bez[3].x; + bez2[3].y = bez[3].y; + + q.x = mt * bez[1].x + t * bez[2].x; + q.y = mt * bez[1].y + t * bez[2].y; + bez1[1].x = mt * bez[0].x + t * bez[1].x; + bez1[1].y = mt * bez[0].y + t * bez[1].y; + bez2[2].x = mt * bez[2].x + t * bez[3].x; + bez2[2].y = mt * bez[2].y + t * bez[3].y; + + bez1[2].x = mt * bez1[1].x + t * q.x; + bez1[2].y = mt * bez1[1].y + t * q.y; + bez2[1].x = mt * q.x + t * bez2[2].x; + bez2[1].y = mt * q.y + t * bez2[2].y; + + bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x; + bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y; +} + +void addLine(CGPoint *last, const CGPoint *next, NSMutableArray *lines, CGFloat *length, NSMutableArray *lengths) { + NSArray *line = @[[NSValue valueWithCGPoint:*last], [NSValue valueWithCGPoint:*next]]; + [lines addObject:line]; + *length += distance(*last, *next); + [lengths addObject:[NSNumber numberWithDouble:*length]]; + *last = *next; +} + +// Convert one element to BezierElement and save to array +void GetBezierElements(void *info, const CGPathElement *element) +{ + NSMutableArray *bezierElements = (__bridge NSMutableArray *)info; + if (element) + [bezierElements addObject:[BezierElement elementWithPathElement:*element]]; +} + +@implementation UIBezierPath (TextRendering) + +// Retrieve array of component elements +- (NSArray *) elements +{ + NSMutableArray *elements = [NSMutableArray array]; + CGPathApply(self.CGPath, (__bridge void *)elements, GetBezierElements); + return elements; +} + +- (void (^)(CGFloat *, NSInteger *, NSMutableArray *, NSMutableArray *, BOOL *)) getTextProperties{ + return ^(CGFloat *lengthP, NSInteger *lineCountP, NSMutableArray * lengths, NSMutableArray * lines, BOOL *isClosedP) { + __block CGPoint origin = CGPointMake (0.0, 0.0); + __block CGPoint last = CGPointMake (0.0, 0.0); + __block NSInteger lineCount = 0; + __block CGFloat length = 0; + __block BOOL isClosed = NO; + NSArray * elements = self.elements; + for (BezierElement *element in elements) { + switch (element.elementType) + { + case kCGPathElementMoveToPoint: + origin = last = element.point; + break; + + case kCGPathElementAddLineToPoint: { + CGPoint next = element.point; + addLine(&last, &next, lines, &length, lengths); + lineCount++; + break; + } + + case kCGPathElementAddQuadCurveToPoint: + case kCGPathElementAddCurveToPoint: + { + // handle both curve types gracefully + CGPoint curveTo; + CGPoint ctrl1; + CGPoint ctrl2; + if (element.elementType == kCGPathElementAddQuadCurveToPoint) { + curveTo = element.point; + ctrl1 = element.controlPoint1; + ctrl2 = ctrl1; + } else if (element.elementType == kCGPathElementAddCurveToPoint) { + curveTo = element.point; + ctrl1 = element.controlPoint1; + ctrl2 = element.controlPoint2; + } else { + break; + } + + // ok, this is the bezier for our current element + CGPoint bezier[4] = { last, ctrl1, ctrl2, curveTo }; + + // define our recursive function that will + // help us split the curve up as needed + void (^__block flattenCurve)(CGPoint bez[4]) = ^(CGPoint bez[4]){ + // calculate the error rate of the curve vs + // a line segement between the start and end points + CGPoint onCurve = bezierPointAtT(bez, .5); + CGPoint next = bez[3]; + CGFloat error = distanceOfPointToLine(onCurve, last, next); + + // if the error is less than our accepted level of error, + // then add a line, + // otherwise, split the curve in half and recur + if (error <= idealFlatness) { + addLine(&last, &next, lines, &length, lengths); + lineCount++; + } else { + CGPoint bez1[4], bez2[4]; + subdivideBezierAtT(bez, bez1, bez2, .5); + flattenCurve(bez1); + flattenCurve(bez2); + } + }; + + flattenCurve(bezier); + last = curveTo; + break; + } + + case kCGPathElementCloseSubpath: { + CGPoint next = origin; + addLine(&last, &next, lines, &length, lengths); + lineCount++; + isClosed = YES; + break; + } + + default: + break; + } + } + *lineCountP = lineCount; + *isClosedP = isClosed; + *lengthP = length; + }; +} +@end + diff --git a/package.json b/package.json index 9e67cde6..bf10452e 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,7 @@ "gradient" ], "scripts": { - "lint": "eslint ./", - "postinstall": "node scripts/install.js" + "lint": "eslint ./" }, "peerDependencies": { "react-native": ">=0.50.0", diff --git a/scripts/install.js b/scripts/install.js deleted file mode 100644 index 379fa89a..00000000 --- a/scripts/install.js +++ /dev/null @@ -1,20 +0,0 @@ -var path = require('path'); -var ghdownload = require('github-download'); - -function downloadSubModuleFromGithub(user, repo, callback) { - var dist = path.join(process.cwd(), 'ios/' + repo); - - console.log('\r\n Start downloading ' + repo + ' to `' + dist + '`'); - ghdownload({user: user, repo: repo, ref: 'master'}, dist) - .on('end', function() { - console.log('Download ' + repo + ' library success!'); - callback && callback(); - }) - .on('error', function (err) { - console.error('Download ' + repo + ' library from github failed with err:', err); - }); -} - -downloadSubModuleFromGithub('adamwulf', 'PerformanceBezier', function () { - downloadSubModuleFromGithub('magicismight', 'QuartzBookPack'); -});