diff --git a/Source/3rdParty/NSAttributedTextView/NSAttributedTextView.swift b/Source/3rdParty/NSAttributedTextView/NSAttributedTextView.swift new file mode 100644 index 00000000..533f8144 --- /dev/null +++ b/Source/3rdParty/NSAttributedTextView/NSAttributedTextView.swift @@ -0,0 +1,116 @@ +// (c) 2021 and onwards Fuziki (MIT License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) + +// Ref: https://qiita.com/fuziki/items/b31055a69330a3ce55a5 +// Modified by The vChewing Project in order to use it with AppKit. + +import Cocoa +import SwiftUI + +@available(macOS 10.15, *) +public struct VText: NSViewRepresentable { + public var text: String? + + public func makeNSView(context _: Context) -> NSAttributedTextView { + let nsView = NSAttributedTextView() + nsView.direction = .vertical + nsView.text = text + return nsView + } + + public func updateNSView(_ nsView: NSAttributedTextView, context _: Context) { + nsView.text = text + } +} + +@available(macOS 10.15, *) +public struct HText: NSViewRepresentable { + public var text: String? + + public func makeNSView(context _: Context) -> NSAttributedTextView { + let nsView = NSAttributedTextView() + nsView.direction = .horizontal + nsView.text = text + return nsView + } + + public func updateNSView(_ nsView: NSAttributedTextView, context _: Context) { + nsView.text = text + } +} + +public class NSAttributedTextView: NSView { + public enum writingDirection: String { + case horizontal + case vertical + case verticalReversed + } + + public var direction: writingDirection = .horizontal + public var fontSize: CGFloat = NSFont.systemFontSize { + didSet { + attributes[.font] = NSFont.systemFont(ofSize: fontSize) + } + } + + public var textColor: NSColor = .textColor { + didSet { + attributes[.foregroundColor] = textColor + } + } + + public var attributedStringValue: NSAttributedString { + var newAttributes = attributes + let isVertical: Bool = !(direction == .horizontal) + newAttributes[.verticalGlyphForm] = isVertical + let lineHeight: CGFloat = isVertical ? fontSize * 0.8 : fontSize * 1.3 + let newStyle: NSMutableParagraphStyle = newAttributes[.paragraphStyle] as! NSMutableParagraphStyle + newStyle.maximumLineHeight = lineHeight + newStyle.minimumLineHeight = lineHeight + newAttributes[.paragraphStyle] = newStyle + let attributedText = NSMutableAttributedString(string: text ?? "", attributes: newAttributes) + return attributedText + } + + public var backgroundColor: NSColor = .controlBackgroundColor + + public var attributes: [NSAttributedString.Key: Any] = [ + .verticalGlyphForm: true, + .font: NSFont.systemFont(ofSize: NSFont.systemFontSize), + .foregroundColor: NSColor.textColor, + .paragraphStyle: { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .left + return paragraphStyle + }(), + ] + public var text: String? { didSet { ctFrame = nil } } + private var ctFrame: CTFrame? + private(set) var currentRect: NSRect? + + override public func draw(_ rect: CGRect) { + let context = NSGraphicsContext.current?.cgContext + guard let context = context else { return } + let setter = CTFramesetterCreateWithAttributedString(attributedStringValue) + let path = CGPath(rect: rect, transform: nil) + let theCTFrameProgression: CTFrameProgression = { + switch direction { + case .horizontal: return CTFrameProgression.topToBottom + case .vertical: return CTFrameProgression.rightToLeft + case .verticalReversed: return CTFrameProgression.leftToRight + } + }() + let frameAttrs: CFDictionary = + [ + kCTFrameProgressionAttributeName: theCTFrameProgression.rawValue + ] as CFDictionary + let newFrame = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), path, frameAttrs) + ctFrame = newFrame + backgroundColor.setFill() + let bgPath: NSBezierPath = .init(roundedRect: rect, xRadius: 0, yRadius: 0) + bgPath.fill() + currentRect = rect + CTFrameDraw(newFrame, context) + } +} diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 96390b2f..56f02573 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 5B54E743283A7D89001ECBDC /* lmCoreNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B54E742283A7D89001ECBDC /* lmCoreNS.swift */; }; 5B5948CE289CC04500C85824 /* LMInstantiator_DateTimeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5948CD289CC04500C85824 /* LMInstantiator_DateTimeExtension.swift */; }; 5B5E535227EF261400C6AA1E /* IME.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E535127EF261400C6AA1E /* IME.swift */; }; + 5B5F8AEB28C86AAD007C11F1 /* NSAttributedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F8AEA28C86AAD007C11F1 /* NSAttributedTextView.swift */; }; 5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */; }; 5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33527AE795800A19448 /* mgrPrefs.swift */; }; 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */; }; @@ -246,6 +247,7 @@ 5B54E742283A7D89001ECBDC /* lmCoreNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = lmCoreNS.swift; sourceTree = ""; }; 5B5948CD289CC04500C85824 /* LMInstantiator_DateTimeExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LMInstantiator_DateTimeExtension.swift; sourceTree = ""; }; 5B5E535127EF261400C6AA1E /* IME.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = IME.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B5F8AEA28C86AAD007C11F1 /* NSAttributedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAttributedTextView.swift; sourceTree = ""; }; 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = FSEventStreamHelper.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B62A33527AE795800A19448 /* mgrPrefs.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = mgrPrefs.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; @@ -463,11 +465,20 @@ path = SubLMs; sourceTree = ""; }; + 5B5F8AEC28C86AB3007C11F1 /* NSAttributedTextView */ = { + isa = PBXGroup; + children = ( + 5B5F8AEA28C86AAD007C11F1 /* NSAttributedTextView.swift */, + ); + path = NSAttributedTextView; + sourceTree = ""; + }; 5B62A30127AE732800A19448 /* 3rdParty */ = { isa = PBXGroup; children = ( 5B84579B2871AD2200C93B01 /* HotenkaChineseConverter */, 5B949BD72816DC4400D87B5D /* LineReader */, + 5B5F8AEC28C86AB3007C11F1 /* NSAttributedTextView */, 5BA58644289BCFAC0077D02F /* Qwertyyb */, 5B20430528BEE2F300BFC6FD /* Sandbox */, 5BA9FCEA27FED652002DE248 /* SindreSorhus */, @@ -1211,6 +1222,7 @@ 5B887F302826AEA400B6651E /* lmCoreEX.swift in Sources */, 5BA9FD4627FEF3C9002DE248 /* Container.swift in Sources */, 5B2170E5289FACAD00BE7304 /* 6_Node.swift in Sources */, + 5B5F8AEB28C86AAD007C11F1 /* NSAttributedTextView.swift in Sources */, 5B949BD92816DC5400D87B5D /* LineReader.swift in Sources */, 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */, 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */,