From 268566c16518f5c36a887b1374963fcdb3906b65 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 13 Jul 2020 18:12:04 -0400 Subject: [PATCH] Add GeminiRendererFramework --- Gemini.xcodeproj/project.pbxproj | 348 ++++++++++++++++++ .../xcschemes/GeminiFormat.xcscheme | 67 ++++ .../xcschemes/xcschememanagement.plist | 17 +- Gemini/AppDelegate.swift | 1 + GeminiRenderer/DocumentView.swift | 47 +++ GeminiRenderer/Fonts.swift | 41 +++ GeminiRenderer/GeminiRenderer.h | 18 + GeminiRenderer/Info.plist | 22 ++ GeminiRenderer/MaybeLazyVStack.swift | 40 ++ GeminiRenderer/RenderingBlock.swift | 56 +++ GeminiRenderer/RenderingBlockView.swift | 66 ++++ GeminiRendererTests/GeminiRendererTests.swift | 33 ++ GeminiRendererTests/Info.plist | 22 ++ 13 files changed, 776 insertions(+), 2 deletions(-) create mode 100644 Gemini.xcodeproj/xcshareddata/xcschemes/GeminiFormat.xcscheme create mode 100644 GeminiRenderer/DocumentView.swift create mode 100644 GeminiRenderer/Fonts.swift create mode 100644 GeminiRenderer/GeminiRenderer.h create mode 100644 GeminiRenderer/Info.plist create mode 100644 GeminiRenderer/MaybeLazyVStack.swift create mode 100644 GeminiRenderer/RenderingBlock.swift create mode 100644 GeminiRenderer/RenderingBlockView.swift create mode 100644 GeminiRendererTests/GeminiRendererTests.swift create mode 100644 GeminiRendererTests/Info.plist diff --git a/Gemini.xcodeproj/project.pbxproj b/Gemini.xcodeproj/project.pbxproj index b83ccde..62792b4 100644 --- a/Gemini.xcodeproj/project.pbxproj +++ b/Gemini.xcodeproj/project.pbxproj @@ -31,6 +31,19 @@ D62664BE24BBF26A00DF9B88 /* GeminiFormat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D62664C624BBF27300DF9B88 /* GeminiParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664C524BBF27300DF9B88 /* GeminiParser.swift */; }; D62664C824BBF2C600DF9B88 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664C724BBF2C600DF9B88 /* Document.swift */; }; + D62664D724BC081B00DF9B88 /* GeminiRenderer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D62664CE24BC081B00DF9B88 /* GeminiRenderer.framework */; }; + D62664DE24BC081B00DF9B88 /* GeminiRendererTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664DD24BC081B00DF9B88 /* GeminiRendererTests.swift */; }; + D62664E024BC081B00DF9B88 /* GeminiRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = D62664D024BC081B00DF9B88 /* GeminiRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D62664E324BC081B00DF9B88 /* GeminiRenderer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D62664CE24BC081B00DF9B88 /* GeminiRenderer.framework */; }; + D62664E424BC081B00DF9B88 /* GeminiRenderer.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D62664CE24BC081B00DF9B88 /* GeminiRenderer.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D62664EC24BC0B4D00DF9B88 /* DocumentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664EB24BC0B4D00DF9B88 /* DocumentView.swift */; }; + D62664EE24BC0BCE00DF9B88 /* MaybeLazyVStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664ED24BC0BCE00DF9B88 /* MaybeLazyVStack.swift */; }; + D62664F024BC0D7700DF9B88 /* GeminiFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */; }; + D62664F124BC0D7700DF9B88 /* GeminiFormat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D62664FA24BC12BC00DF9B88 /* DocumentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664F924BC12BC00DF9B88 /* DocumentTests.swift */; }; + D664673624BD07F700B0B741 /* RenderingBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673524BD07F700B0B741 /* RenderingBlock.swift */; }; + D664673824BD086F00B0B741 /* RenderingBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673724BD086F00B0B741 /* RenderingBlockView.swift */; }; + D664673A24BD0B8E00B0B741 /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673924BD0B8E00B0B741 /* Fonts.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -76,6 +89,34 @@ remoteGlobalIDString = D62664A724BBF26A00DF9B88; remoteInfo = GeminiFormat; }; + D62664D824BC081B00DF9B88 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D62664CD24BC081B00DF9B88; + remoteInfo = GeminiRenderer; + }; + D62664DA24BC081B00DF9B88 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D626645A24BBF1C200DF9B88; + remoteInfo = Gemini; + }; + D62664E124BC081B00DF9B88 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D62664CD24BC081B00DF9B88; + remoteInfo = GeminiRenderer; + }; + D62664F224BC0D7700DF9B88 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D62664A724BBF26A00DF9B88; + remoteInfo = GeminiFormat; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -85,12 +126,24 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + D62664E424BC081B00DF9B88 /* GeminiRenderer.framework in Embed Frameworks */, D62664BE24BBF26A00DF9B88 /* GeminiFormat.framework in Embed Frameworks */, D626648D24BBF22E00DF9B88 /* GeminiProtocol.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + D62664F424BC0D7700DF9B88 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D62664F124BC0D7700DF9B88 /* GeminiFormat.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -123,6 +176,18 @@ D62664B924BBF26A00DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D62664C524BBF27300DF9B88 /* GeminiParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiParser.swift; sourceTree = ""; }; D62664C724BBF2C600DF9B88 /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = ""; }; + D62664CE24BC081B00DF9B88 /* GeminiRenderer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GeminiRenderer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D62664D024BC081B00DF9B88 /* GeminiRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeminiRenderer.h; sourceTree = ""; }; + D62664D124BC081B00DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D62664D624BC081B00DF9B88 /* GeminiRendererTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GeminiRendererTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D62664DD24BC081B00DF9B88 /* GeminiRendererTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiRendererTests.swift; sourceTree = ""; }; + D62664DF24BC081B00DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D62664EB24BC0B4D00DF9B88 /* DocumentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentView.swift; sourceTree = ""; }; + D62664ED24BC0BCE00DF9B88 /* MaybeLazyVStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeLazyVStack.swift; sourceTree = ""; }; + D62664F924BC12BC00DF9B88 /* DocumentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentTests.swift; sourceTree = ""; }; + D664673524BD07F700B0B741 /* RenderingBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingBlock.swift; sourceTree = ""; }; + D664673724BD086F00B0B741 /* RenderingBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingBlockView.swift; sourceTree = ""; }; + D664673924BD0B8E00B0B741 /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -130,6 +195,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D62664E324BC081B00DF9B88 /* GeminiRenderer.framework in Frameworks */, D62664BD24BBF26A00DF9B88 /* GeminiFormat.framework in Frameworks */, D626648C24BBF22E00DF9B88 /* GeminiProtocol.framework in Frameworks */, ); @@ -165,6 +231,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D62664CB24BC081B00DF9B88 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D62664F024BC0D7700DF9B88 /* GeminiFormat.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D62664D324BC081B00DF9B88 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D62664D724BC081B00DF9B88 /* GeminiRenderer.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -176,7 +258,10 @@ D626648524BBF22E00DF9B88 /* GeminiProtocolTests */, D62664A924BBF26A00DF9B88 /* GeminiFormat */, D62664B624BBF26A00DF9B88 /* GeminiFormatTests */, + D62664CF24BC081B00DF9B88 /* GeminiRenderer */, + D62664DC24BC081B00DF9B88 /* GeminiRendererTests */, D626645C24BBF1C200DF9B88 /* Products */, + D62664EF24BC0D7700DF9B88 /* Frameworks */, ); sourceTree = ""; }; @@ -188,6 +273,8 @@ D626647F24BBF22E00DF9B88 /* GeminiProtocolTests.xctest */, D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */, D62664B024BBF26A00DF9B88 /* GeminiFormatTests.xctest */, + D62664CE24BC081B00DF9B88 /* GeminiRenderer.framework */, + D62664D624BC081B00DF9B88 /* GeminiRendererTests.xctest */, ); name = Products; sourceTree = ""; @@ -253,12 +340,43 @@ D62664B624BBF26A00DF9B88 /* GeminiFormatTests */ = { isa = PBXGroup; children = ( + D62664F924BC12BC00DF9B88 /* DocumentTests.swift */, D62664B724BBF26A00DF9B88 /* GeminiParserTests.swift */, D62664B924BBF26A00DF9B88 /* Info.plist */, ); path = GeminiFormatTests; sourceTree = ""; }; + D62664CF24BC081B00DF9B88 /* GeminiRenderer */ = { + isa = PBXGroup; + children = ( + D62664D024BC081B00DF9B88 /* GeminiRenderer.h */, + D62664D124BC081B00DF9B88 /* Info.plist */, + D664673524BD07F700B0B741 /* RenderingBlock.swift */, + D664673924BD0B8E00B0B741 /* Fonts.swift */, + D62664ED24BC0BCE00DF9B88 /* MaybeLazyVStack.swift */, + D62664EB24BC0B4D00DF9B88 /* DocumentView.swift */, + D664673724BD086F00B0B741 /* RenderingBlockView.swift */, + ); + path = GeminiRenderer; + sourceTree = ""; + }; + D62664DC24BC081B00DF9B88 /* GeminiRendererTests */ = { + isa = PBXGroup; + children = ( + D62664DD24BC081B00DF9B88 /* GeminiRendererTests.swift */, + D62664DF24BC081B00DF9B88 /* Info.plist */, + ); + path = GeminiRendererTests; + sourceTree = ""; + }; + D62664EF24BC0D7700DF9B88 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -278,6 +396,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D62664C924BC081B00DF9B88 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D62664E024BC081B00DF9B88 /* GeminiRenderer.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -295,6 +421,7 @@ dependencies = ( D626648B24BBF22E00DF9B88 /* PBXTargetDependency */, D62664BC24BBF26A00DF9B88 /* PBXTargetDependency */, + D62664E224BC081B00DF9B88 /* PBXTargetDependency */, ); name = Gemini; productName = Gemini; @@ -375,6 +502,45 @@ productReference = D62664B024BBF26A00DF9B88 /* GeminiFormatTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + D62664CD24BC081B00DF9B88 /* GeminiRenderer */ = { + isa = PBXNativeTarget; + buildConfigurationList = D62664E524BC081B00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiRenderer" */; + buildPhases = ( + D62664C924BC081B00DF9B88 /* Headers */, + D62664CA24BC081B00DF9B88 /* Sources */, + D62664CB24BC081B00DF9B88 /* Frameworks */, + D62664CC24BC081B00DF9B88 /* Resources */, + D62664F424BC0D7700DF9B88 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D62664F324BC0D7700DF9B88 /* PBXTargetDependency */, + ); + name = GeminiRenderer; + productName = GeminiRenderer; + productReference = D62664CE24BC081B00DF9B88 /* GeminiRenderer.framework */; + productType = "com.apple.product-type.framework"; + }; + D62664D524BC081B00DF9B88 /* GeminiRendererTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D62664E824BC081B00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiRendererTests" */; + buildPhases = ( + D62664D224BC081B00DF9B88 /* Sources */, + D62664D324BC081B00DF9B88 /* Frameworks */, + D62664D424BC081B00DF9B88 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D62664D924BC081B00DF9B88 /* PBXTargetDependency */, + D62664DB24BC081B00DF9B88 /* PBXTargetDependency */, + ); + name = GeminiRendererTests; + productName = GeminiRendererTests; + productReference = D62664D624BC081B00DF9B88 /* GeminiRendererTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -403,6 +569,14 @@ CreatedOnToolsVersion = 12.0; TestTargetID = D626645A24BBF1C200DF9B88; }; + D62664CD24BC081B00DF9B88 = { + CreatedOnToolsVersion = 12.0; + LastSwiftMigration = 1200; + }; + D62664D524BC081B00DF9B88 = { + CreatedOnToolsVersion = 12.0; + TestTargetID = D626645A24BBF1C200DF9B88; + }; }; }; buildConfigurationList = D626645624BBF1C200DF9B88 /* Build configuration list for PBXProject "Gemini" */; @@ -423,6 +597,8 @@ D626647E24BBF22E00DF9B88 /* GeminiProtocolTests */, D62664A724BBF26A00DF9B88 /* GeminiFormat */, D62664AF24BBF26A00DF9B88 /* GeminiFormatTests */, + D62664CD24BC081B00DF9B88 /* GeminiRenderer */, + D62664D524BC081B00DF9B88 /* GeminiRendererTests */, ); }; /* End PBXProject section */ @@ -466,6 +642,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D62664CC24BC081B00DF9B88 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D62664D424BC081B00DF9B88 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -513,10 +703,31 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D62664FA24BC12BC00DF9B88 /* DocumentTests.swift in Sources */, D62664B824BBF26A00DF9B88 /* GeminiParserTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + D62664CA24BC081B00DF9B88 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D664673624BD07F700B0B741 /* RenderingBlock.swift in Sources */, + D664673A24BD0B8E00B0B741 /* Fonts.swift in Sources */, + D62664EE24BC0BCE00DF9B88 /* MaybeLazyVStack.swift in Sources */, + D62664EC24BC0B4D00DF9B88 /* DocumentView.swift in Sources */, + D664673824BD086F00B0B741 /* RenderingBlockView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D62664D224BC081B00DF9B88 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D62664DE24BC081B00DF9B88 /* GeminiRendererTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -550,6 +761,26 @@ target = D62664A724BBF26A00DF9B88 /* GeminiFormat */; targetProxy = D62664BB24BBF26A00DF9B88 /* PBXContainerItemProxy */; }; + D62664D924BC081B00DF9B88 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D62664CD24BC081B00DF9B88 /* GeminiRenderer */; + targetProxy = D62664D824BC081B00DF9B88 /* PBXContainerItemProxy */; + }; + D62664DB24BC081B00DF9B88 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D626645A24BBF1C200DF9B88 /* Gemini */; + targetProxy = D62664DA24BC081B00DF9B88 /* PBXContainerItemProxy */; + }; + D62664E224BC081B00DF9B88 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D62664CD24BC081B00DF9B88 /* GeminiRenderer */; + targetProxy = D62664E124BC081B00DF9B88 /* PBXContainerItemProxy */; + }; + D62664F324BC0D7700DF9B88 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D62664A724BBF26A00DF9B88 /* GeminiFormat */; + targetProxy = D62664F224BC0D7700DF9B88 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -927,6 +1158,105 @@ }; name = Release; }; + D62664E624BC081B00DF9B88 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = HGYVAQA9FW; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = GeminiRenderer/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiRenderer; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + D62664E724BC081B00DF9B88 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = HGYVAQA9FW; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = GeminiRenderer/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiRenderer; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + D62664E924BC081B00DF9B88 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HGYVAQA9FW; + INFOPLIST_FILE = GeminiRendererTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiRendererTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Gemini.app/Contents/MacOS/Gemini"; + }; + name = Debug; + }; + D62664EA24BC081B00DF9B88 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HGYVAQA9FW; + INFOPLIST_FILE = GeminiRendererTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiRendererTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Gemini.app/Contents/MacOS/Gemini"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -984,6 +1314,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D62664E524BC081B00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiRenderer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D62664E624BC081B00DF9B88 /* Debug */, + D62664E724BC081B00DF9B88 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D62664E824BC081B00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiRendererTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D62664E924BC081B00DF9B88 /* Debug */, + D62664EA24BC081B00DF9B88 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = D626645324BBF1C200DF9B88 /* Project object */; diff --git a/Gemini.xcodeproj/xcshareddata/xcschemes/GeminiFormat.xcscheme b/Gemini.xcodeproj/xcshareddata/xcschemes/GeminiFormat.xcscheme new file mode 100644 index 0000000..bed4ba8 --- /dev/null +++ b/Gemini.xcodeproj/xcshareddata/xcschemes/GeminiFormat.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist b/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist index b8c4466..7a4e844 100644 --- a/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,12 +12,25 @@ GeminiFormat.xcscheme_^#shared#^_ orderHint - 2 + 1 GeminiProtocol.xcscheme_^#shared#^_ orderHint - 1 + 2 + + GeminiRenderer.xcscheme_^#shared#^_ + + orderHint + 3 + + + SuppressBuildableAutocreation + + D62664A724BBF26A00DF9B88 + + primary + diff --git a/Gemini/AppDelegate.swift b/Gemini/AppDelegate.swift index 39fb08f..eefd4d5 100644 --- a/Gemini/AppDelegate.swift +++ b/Gemini/AppDelegate.swift @@ -27,6 +27,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { window.center() window.setFrameAutosaveName("Main Window") window.contentView = NSHostingView(rootView: contentView) + window.title = "Gemini" window.makeKeyAndOrderFront(nil) } diff --git a/GeminiRenderer/DocumentView.swift b/GeminiRenderer/DocumentView.swift new file mode 100644 index 0000000..97b8bf6 --- /dev/null +++ b/GeminiRenderer/DocumentView.swift @@ -0,0 +1,47 @@ +// +// DocumentView.swift +// GeminiRenderer +// +// Created by Shadowfacts on 7/12/20. +// + +import SwiftUI +import GeminiFormat + +public struct DocumentView: View { + private let document: Document + private let blocks: [RenderingBlock] + + public init(document: Document) { + self.document = document + self.blocks = document.renderingBlocks + } + + public var body: some View { + ScrollView(.vertical) { + MaybeLazyVStack(alignment: .leading) { + ForEach(blocks.indices) { (index) in + RenderingBlockView(block: blocks[index]) + } + }.padding() + } + } +} + +struct DocumentView_Previews: PreviewProvider { + static var doc: Document { + Document(url: URL(string: "gemini://example.com")!, lines: [ + .heading("Hello World", level: .h1), + .text("Some text"), + .preformattedToggle(alt: "blah"), + .preformattedText("test"), + .preformattedToggle(alt: nil), + .quote("whatever"), + .unorderedListItem("something") + ]) + } + static var previews: some View { + DocumentView(document: doc) + } +} + diff --git a/GeminiRenderer/Fonts.swift b/GeminiRenderer/Fonts.swift new file mode 100644 index 0000000..d9a44e2 --- /dev/null +++ b/GeminiRenderer/Fonts.swift @@ -0,0 +1,41 @@ +// +// Fonts.swift +// GeminiRenderer +// +// Created by Shadowfacts on 7/13/20. +// + +import SwiftUI +import GeminiFormat + +extension Document.HeadingLevel { + var font: Font { + let style: Font.TextStyle + switch self { + case .h1: + style = .title + case .h2: + if #available(macOS 10.16, iOS 14.0, *) { + style = .title2 + } else { + style = .headline + } + case .h3: + if #available(macOS 10.16, iOS 14.0, *) { + style = .title3 + } else { + style = .subheadline + } + } + return .system(style, design: .serif) + } +} + +extension Font { + static var documentBody: Font { + .system(.body, design: .serif) + } + static var documentBodyPreformatted: Font { + .system(.body, design: .monospaced) + } +} diff --git a/GeminiRenderer/GeminiRenderer.h b/GeminiRenderer/GeminiRenderer.h new file mode 100644 index 0000000..5bdbaf1 --- /dev/null +++ b/GeminiRenderer/GeminiRenderer.h @@ -0,0 +1,18 @@ +// +// GeminiRenderer.h +// GeminiRenderer +// +// Created by Shadowfacts on 7/12/20. +// + +#import + +//! Project version number for GeminiRenderer. +FOUNDATION_EXPORT double GeminiRendererVersionNumber; + +//! Project version string for GeminiRenderer. +FOUNDATION_EXPORT const unsigned char GeminiRendererVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/GeminiRenderer/Info.plist b/GeminiRenderer/Info.plist new file mode 100644 index 0000000..9bcb244 --- /dev/null +++ b/GeminiRenderer/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/GeminiRenderer/MaybeLazyVStack.swift b/GeminiRenderer/MaybeLazyVStack.swift new file mode 100644 index 0000000..77c7d1f --- /dev/null +++ b/GeminiRenderer/MaybeLazyVStack.swift @@ -0,0 +1,40 @@ +// +// MaybeLazyVStack.swift +// GeminiRenderer +// +// Created by Shadowfacts on 7/12/20. +// + +import SwiftUI + +struct MaybeLazyVStack: View { + private let alignment: HorizontalAlignment + private let spacing: CGFloat? + private let content: Content + + init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) { + self.alignment = alignment + self.spacing = spacing + self.content = content() + } + + @ViewBuilder + var body: some View { + if #available(macOS 16.0, iOS 14.0, *) { + LazyVStack(alignment: alignment, spacing: spacing) { + content + } + } else { + VStack(alignment: alignment, spacing: spacing) { + content + } + } + } +} + +struct MaybeLazyVStack_Previews: PreviewProvider { + static var previews: some View { +// MaybeLazyVStack() + EmptyView() + } +} diff --git a/GeminiRenderer/RenderingBlock.swift b/GeminiRenderer/RenderingBlock.swift new file mode 100644 index 0000000..156d719 --- /dev/null +++ b/GeminiRenderer/RenderingBlock.swift @@ -0,0 +1,56 @@ +// +// RenderingBlock.swift +// GeminiRenderer +// +// Created by Shadowfacts on 7/12/20. +// + +import Foundation +import GeminiFormat + +enum RenderingBlock: Equatable { + case text(String) + case link(URL, text: String?) + case preformatted(String, alt: String?) + case heading(String, level: Document.HeadingLevel) + case unorderedListItem(String) + case quote(String) +} + +extension Document { + var renderingBlocks: [RenderingBlock] { + var blocks = [RenderingBlock]() + + var currentPreformatted: (text: String, alt: String?)? + + for line in self.lines { + switch line { + case let .preformattedToggle(alt: alt): + if let (text, alt) = currentPreformatted { + // drop last trailing newline + let realText = String(text.dropLast(1)) + blocks.append(.preformatted(realText, alt: alt)) + currentPreformatted = nil + } else { + currentPreformatted = ("", alt: alt) + } + case let .preformattedText(text): + currentPreformatted!.text += text + currentPreformatted!.text += "\n" + case let .text(text): + blocks.append(.text(text)) + case let .link(url, text: text): + blocks.append(.link(url, text: text)) + case let .heading(text, level: level): + blocks.append(.heading(text, level: level)) + case let .unorderedListItem(text): + blocks.append(.unorderedListItem(text)) + case let .quote(text): + blocks.append(.quote(text)) + } + } + + + return blocks + } +} diff --git a/GeminiRenderer/RenderingBlockView.swift b/GeminiRenderer/RenderingBlockView.swift new file mode 100644 index 0000000..d7a3f67 --- /dev/null +++ b/GeminiRenderer/RenderingBlockView.swift @@ -0,0 +1,66 @@ +// +// RenderingBlockView.swift +// GeminiRenderer +// +// Created by Shadowfacts on 7/13/20. +// + +import SwiftUI +import GeminiFormat + +struct RenderingBlockView: View { + let block: RenderingBlock + + @ViewBuilder + var body: some View { + switch block { + case let .text(text): + Text(verbatim: text) + .font(.documentBody) + .frame(maxWidth: .infinity, alignment: .leading) + case let .link(url, text: linkText): + let text = linkText ?? url.absoluteString + Text(verbatim: text) + .font(.documentBody) + .foregroundColor(.blue) + .underline() + .frame(maxWidth: .infinity, alignment: .leading) + case let .preformatted(text, alt: _): + ScrollView(.horizontal) { + Text(verbatim: text) + .font(.documentBodyPreformatted) + .frame(maxWidth: .infinity, alignment: .leading) + } + case let .heading(text, level: level): + Text(verbatim: text) + .font(level.font) + .frame(maxWidth: .infinity, alignment: .leading) + case let .unorderedListItem(text): + // todo: should this be .firstTextBaseline? + HStack(alignment: .top, spacing: 4) { + Text(verbatim: "\u{2022}") + Text(verbatim: text) + .font(.documentBody) + Spacer() + }.frame(maxWidth: .infinity, alignment: .leading) + case let .quote(text): + HStack(spacing: 4) { + Color.gray + .frame(width: 4) + Text(verbatim: text) + .font(Font.documentBody.italic()) + .foregroundColor(.gray) + Spacer() + }.frame(maxWidth: .infinity, alignment: .leading) + } + } +} + +struct RenderingBlockView_Previews: PreviewProvider { + static var previews: some View { + Group { + RenderingBlockView(block: .text("Some Text")) + RenderingBlockView(block: .quote("A Quote")) + } + } +} diff --git a/GeminiRendererTests/GeminiRendererTests.swift b/GeminiRendererTests/GeminiRendererTests.swift new file mode 100644 index 0000000..7ee5b3c --- /dev/null +++ b/GeminiRendererTests/GeminiRendererTests.swift @@ -0,0 +1,33 @@ +// +// GeminiRendererTests.swift +// GeminiRendererTests +// +// Created by Shadowfacts on 7/12/20. +// + +import XCTest +@testable import GeminiRenderer + +class GeminiRendererTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/GeminiRendererTests/Info.plist b/GeminiRendererTests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/GeminiRendererTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + +