Skip to content

Commit 623fb25

Browse files
authored
Merge pull request #725 from zonble/master
Adds the feature to generate support report
2 parents fdaf53c + f4afd8d commit 623fb25

31 files changed

Lines changed: 1964 additions & 178 deletions

McBopomofo.xcodeproj/project.pbxproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */; };
4040
B058C5272AC9DF51002EDD66 /* ServiceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B058C5262AC9DF51002EDD66 /* ServiceProvider.swift */; };
4141
B0781B352ACA2655003D9F75 /* ServicesMenu.strings in Resources */ = {isa = PBXBuildFile; fileRef = B0781B372ACA2655003D9F75 /* ServicesMenu.strings */; };
42+
D40EFFA22EB669F900C1728A /* InfoCollector in Frameworks */ = {isa = PBXBuildFile; productRef = D40EFFA12EB669F900C1728A /* InfoCollector */; };
4243
D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = D41355D7278D7409005E5CBD /* LanguageModelManager.mm */; };
4344
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */; };
4445
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D41355DC278EA3ED005E5CBD /* UserPhrasesLM.cpp */; };
@@ -177,6 +178,7 @@
177178
B058C5262AC9DF51002EDD66 /* ServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceProvider.swift; sourceTree = "<group>"; };
178179
B0781B362ACA2655003D9F75 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ServicesMenu.strings; sourceTree = "<group>"; };
179180
B0781B382ACA2659003D9F75 /* zh-Hant */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/ServicesMenu.strings"; sourceTree = "<group>"; };
181+
D40EFFA02EB669BB00C1728A /* InfoCollector */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = InfoCollector; path = Packages/InfoCollector; sourceTree = "<group>"; };
180182
D41355D6278D7409005E5CBD /* LanguageModelManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LanguageModelManager.h; sourceTree = "<group>"; };
181183
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = "<group>"; };
182184
D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = McBopomofoLM.cpp; sourceTree = "<group>"; };
@@ -258,6 +260,7 @@
258260
D48F853E2EACDB6C00C2FDAB /* RomanNumbers in Frameworks */,
259261
D4E7917A2B52CDE500676A68 /* ChineseNumbers in Frameworks */,
260262
D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */,
263+
D40EFFA22EB669F900C1728A /* InfoCollector in Frameworks */,
261264
D4C9CAB127AAC9690058DFEA /* NSStringUtils in Frameworks */,
262265
D427F7AE27907B8A004A2160 /* NotifierUI in Frameworks */,
263266
D41B626C2B86EAE900583148 /* BopomofoBraille in Frameworks */,
@@ -459,6 +462,7 @@
459462
D427F766278C9CBD004A2160 /* Packages */ = {
460463
isa = PBXGroup;
461464
children = (
465+
D40EFFA02EB669BB00C1728A /* InfoCollector */,
462466
D4E5EEE22E68A9B30068BC58 /* SystemCharacterInfo */,
463467
D41B626A2B86EAD400583148 /* BopomofoBraille */,
464468
D4E791782B52CDCF00676A68 /* ChineseNumbers */,
@@ -544,6 +548,7 @@
544548
D41B626B2B86EAE900583148 /* BopomofoBraille */,
545549
D4451AC82E688C6B00E8F5AB /* SystemCharacterInfo */,
546550
D48F853D2EACDB6C00C2FDAB /* RomanNumbers */,
551+
D40EFFA12EB669F900C1728A /* InfoCollector */,
547552
);
548553
productName = McBopomofo;
549554
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */;
@@ -1436,6 +1441,10 @@
14361441
/* End XCConfigurationList section */
14371442

14381443
/* Begin XCSwiftPackageProductDependency section */
1444+
D40EFFA12EB669F900C1728A /* InfoCollector */ = {
1445+
isa = XCSwiftPackageProductDependency;
1446+
productName = InfoCollector;
1447+
};
14391448
D41B626B2B86EAE900583148 /* BopomofoBraille */ = {
14401449
isa = XCSwiftPackageProductDependency;
14411450
productName = BopomofoBraille;

Packages/BopomofoBraille/README.md

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,75 @@
22

33
Copyright (c) 2022 and onwards The McBopomofo Authors.
44

5-
The package includes a converter that translates Taiwanese Bopomofo into
6-
Taiwanese Braille, and vice versa.
5+
BopomofoBraille is a Swift package that converts between Taiwanese Bopomofo (注
6+
音符號) and Taiwanese Braille (臺灣點字). It powers the Braille typing feature
7+
in McBopomofo’s Service menu and can also be embedded in standalone projects
8+
that need to translate between these two writing systems.
79

8-
The main class in the package is BopomofoBrailleConverter. You can use its
9-
methods directly. For example:
10+
## Installation
11+
12+
Add the package dependency to your `Package.swift`:
13+
14+
```swift
15+
dependencies: [
16+
.package(path: "../Packages/BopomofoBraille"),
17+
],
18+
targets: [
19+
.target(
20+
name: "YourTarget",
21+
dependencies: ["BopomofoBraille"]
22+
),
23+
]
24+
```
25+
26+
When using the package outside of the McBopomofo repository, replace the local
27+
path with the corresponding Git URL and version requirement.
28+
29+
## Usage
30+
31+
The primary entry point is `BopomofoBrailleConverter`. It exposes two static
32+
conversion methods:
1033

1134
```swift
1235
let bpmf = "ㄓㄨㄥㄨㄣˊㄓㄨˋㄧㄣ"
13-
let convertedBraille = BopomofoBrailleConverter.convert(bopomofo: bpmf)
14-
let convertedBpmf = BopomofoBrailleConverter.convert(braille: convertedBraille)
36+
let braille = BopomofoBrailleConverter.convert(bopomofo: bpmf)
37+
38+
let roundTrip = BopomofoBrailleConverter.convert(braille: braille)
39+
// roundTrip == bpmf
40+
```
41+
42+
Both conversions preserve whitespace, punctuation, Latin letters, and digits
43+
according to the Taiwanese Braille standard. Invalid syllables fall back to
44+
spacing rules so that mixed-content strings remain legible.
45+
46+
### Working With Individual Syllables
47+
48+
`BopomofoSyllable` provides validation and direct access to the Braille for a
49+
single syllable:
50+
51+
```swift
52+
let syllable = try BopomofoSyllable(rawValue: "ㄉㄧˋ")
53+
print(syllable.braille) // ⠙⠡⠐
54+
55+
let reversed = try BopomofoSyllable(braille: "⠋⠪⠂")
56+
print(reversed.rawValue) // ㄊㄧㄠˊ
1557
```
1658

17-
The package helps implement the features in the Service menu, allowing users to
18-
input Taiwanese Braille by pressing the Ctrl and Enter keys.
59+
Specialised token types such as `Letter`, `Digit`, and punctuation helpers are
60+
exposed for callers that need finer control over parsing.
61+
62+
## Testing
63+
64+
Run the package test suite with:
65+
66+
```bash
67+
swift test --package-path Packages/BopomofoBraille
68+
```
69+
70+
The tests cover round-trip conversion, syllable validation, and edge cases for
71+
Latin letters, digits, and punctuation.
72+
73+
## License
74+
75+
This package is released under the MIT license. See the header comments in the
76+
source files for the full text.

Packages/CandidateUI/README.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# CandidateUI
2+
3+
Copyright (c) 2022 and onwards The McBopomofo Authors.
4+
5+
CandidateUI provides the Cocoa candidate window implementation shared by the
6+
McBopomofo input method targets. It bundles a base `CandidateController` with
7+
ready-to-use horizontal and vertical variants that expose an Objective-C
8+
compatible delegate API, making it easy to embed the UI in IMK and AppKit based
9+
projects.
10+
11+
## Features
12+
13+
- Horizontal and vertical `NSWindowController` subclasses ready for IMK/AppKit
14+
hosts
15+
- Delegate driven data source with optional readings and explanations
16+
- Configurable key labels, candidate fonts, tooltip text, and accessibility
17+
notifications
18+
- Built-in pagination and highlight navigation helpers for keyboard control
19+
- Objective-C bridging annotations for seamless use from mixed Swift/ObjC code
20+
21+
## Requirements
22+
23+
- macOS 10.15 or later (AppKit based host process)
24+
- Swift 5.9 or newer (Xcode 15.3 or newer)
25+
- Interactions must occur on the main queue because the window is UI backed
26+
27+
## Installation
28+
29+
Add the package dependency to your `Package.swift`:
30+
31+
```swift
32+
dependencies: [
33+
.package(path: "../Packages/CandidateUI"),
34+
],
35+
targets: [
36+
.target(
37+
name: "YourTarget",
38+
dependencies: ["CandidateUI"]
39+
),
40+
]
41+
```
42+
43+
When integrating outside the McBopomofo repository, replace the local path with
44+
the corresponding Git URL and version requirement.
45+
46+
## Usage
47+
48+
Choose either `HorizontalCandidateController` or `VerticalCandidateController`
49+
depending on the layout you want.
50+
51+
### 1. Implement the delegate
52+
53+
Adopt `CandidateControllerDelegate` to expose candidates, optional readings,
54+
and selection callbacks:
55+
56+
```swift
57+
@MainActor
58+
final class DemoDelegate: NSObject, CandidateControllerDelegate {
59+
private let entries = ["ZhuYin", "Input Method", "McBopomofo"]
60+
private let readings = ["zhu4", "shu1", "mai4"]
61+
62+
func candidateCountForController(_ controller: CandidateController) -> UInt {
63+
UInt(entries.count)
64+
}
65+
66+
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String {
67+
entries[Int(index)]
68+
}
69+
70+
func candidateController(_ controller: CandidateController, readingAtIndex index: UInt) -> String? {
71+
readings[Int(index)]
72+
}
73+
74+
func candidateController(
75+
_ controller: CandidateController,
76+
requestExplanationFor candidate: String,
77+
reading: String
78+
) -> String? {
79+
nil
80+
}
81+
82+
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) {
83+
print("Selected candidate: \(entries[Int(index)])")
84+
}
85+
}
86+
```
87+
88+
### 2. Configure the controller
89+
90+
Instantiate the controller on the main thread, assign the delegate, and adjust
91+
appearance hints. Call `reloadData()` after mutating underlying candidate data.
92+
93+
```swift
94+
let controller = VerticalCandidateController()
95+
controller.delegate = DemoDelegate()
96+
controller.keyLabels = ["1", "2", "3"].map { CandidateKeyLabel(key: $0, displayedText: $0) }
97+
controller.keyLabelFont = NSFont.monospacedSystemFont(ofSize: 13, weight: .regular)
98+
controller.candidateFont = NSFont.systemFont(ofSize: 18)
99+
controller.tooltip = NSLocalizedString("Press arrow keys to browse candidates", comment: "Candidate window tooltip")
100+
controller.set(windowTopLeftPoint: NSPoint(x: 320, y: 400), bottomOutOfScreenAdjustmentHeight: 24)
101+
controller.visible = true
102+
```
103+
104+
`set(windowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:)` keeps the window
105+
visible by nudging it back on screen when it would otherwise cross a screen
106+
edge.
107+
108+
### 3. React to keyboard events
109+
110+
Use the navigation helpers to mirror the IMK key handling semantics:
111+
112+
```swift
113+
@discardableResult
114+
func handleCandidateNavigation(event: NSEvent, controller: CandidateController) -> Bool {
115+
guard let specialKey = event.specialKey else { return false }
116+
switch specialKey {
117+
case .rightArrow: return controller.showNextPage()
118+
case .leftArrow: return controller.showPreviousPage()
119+
case .downArrow: return controller.highlightNextCandidate()
120+
case .upArrow: return controller.highlightPreviousCandidate()
121+
default: return false
122+
}
123+
}
124+
```
125+
126+
For number keys, call `candidateIndexAtKeyLabelIndex(_:)` to translate the
127+
pressed label into a candidate index (`UInt.max` signals “not available”). When
128+
a new selection is confirmed, invoke the delegate’s
129+
`candidateController(_:didSelectCandidateAtIndex:)` method to commit the choice
130+
back to your input pipeline.
131+
132+
### Objective-C integration
133+
134+
All public controllers and protocols are annotated with `@objc`. Import the
135+
generated umbrella header (for example through `McBopomofo-Bridging-Header.h`)
136+
to drive the UI from Objective-C or Objective-C++ sources.
137+
138+
## Testing
139+
140+
Run the package tests with:
141+
142+
```bash
143+
swift test --package-path Packages/CandidateUI
144+
```
145+
146+
The test suite covers pagination, selection handling, and layout options for
147+
both horizontal and vertical controllers.
148+
149+
## License
150+
151+
This package is released under the MIT license. The full text is available in
152+
the source file headers.

Packages/ChineseNumbers/README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# ChineseNumbers
2+
3+
Copyright (c) 2022 and onwards The McBopomofo Authors.
4+
5+
ChineseNumbers converts decimal numbers into Traditional Chinese numeral
6+
strings. It ships with the McBopomofo input method project and exposes `@objc`
7+
entry points so the same conversion logic works in Swift and Objective-C
8+
codebases.
9+
10+
## Features
11+
12+
- Formats integer and decimal sections using lowercase (如「一二三」) or
13+
uppercase (如「壹貳參」) digits via `ChineseNumbers.Case`
14+
- Supports large magnitudes through `` by chunking the input into four-digit
15+
sections (萬、億、兆、京…)
16+
- Emits the `` separator for fractional values and preserves significant
17+
trailing digits
18+
- Provides the `SuzhouNumbers` helper for 蘇州碼 output, including unit labels
19+
and vertical digit preferences
20+
- Includes string utilities for trimming/padding zeros so callers can feed
21+
pre-validated numeric text
22+
- Fully accessible from Objective-C thanks to `@objc` annotations and `NSObject`
23+
inheritance
24+
25+
## Installation
26+
27+
Add ChineseNumbers to the `dependencies` section of your `Package.swift`:
28+
29+
```swift
30+
.dependencies([
31+
.package(path: "Packages/ChineseNumbers")
32+
])
33+
```
34+
35+
Then link the library from a target:
36+
37+
```swift
38+
.target(
39+
name: "YourTarget",
40+
dependencies: [
41+
.product(name: "ChineseNumbers", package: "ChineseNumbers")
42+
]
43+
)
44+
```
45+
46+
If you vend the package from another repository, replace the `.package` path
47+
with the appropriate `.package(url: "…", from: "…")` declaration.
48+
49+
## Usage
50+
51+
```swift
52+
import ChineseNumbers
53+
54+
let integerPart = "1234567890"
55+
let decimalPart = "050"
56+
57+
let lowercase = ChineseNumbers.generate(
58+
intPart: integerPart,
59+
decPart: decimalPart,
60+
digitCase: .lowercase
61+
) // "一十二億三千四百五十六萬七千八百九十點〇五"
62+
63+
let uppercase = ChineseNumbers.generate(
64+
intPart: integerPart,
65+
decPart: decimalPart,
66+
digitCase: .uppercase
67+
) // "壹拾貳億參仟肆佰伍拾陸萬柒仟捌佰玖拾點零伍"
68+
69+
let suzhou = SuzhouNumbers.generate(
70+
intPart: "123",
71+
decPart: "40",
72+
unit: "",
73+
preferInitialVertical: true
74+
)
75+
/*
76+
〡二〣〤
77+
百元
78+
*/
79+
```
80+
81+
`ChineseNumbers.generate` expects pre-separated integer and decimal strings so
82+
you can source them from text fields without lossy floating-point conversion.
83+
`SuzhouNumbers.generate` alternates between vertical digits (〡〢〣) and
84+
horizontal strokes (一二三) based on the `preferInitialVertical` flag and
85+
appends the appropriate place name when multiple characters are produced.
86+
87+
## Testing
88+
89+
Run the package tests with:
90+
91+
```bash
92+
swift test
93+
```
94+
95+
The XCTest suite covers zero trimming, lowercase/uppercase conversions,
96+
fractional rendering, and Suzhou numeral generation.
97+
98+
## License
99+
100+
This package is released under the MIT license. The full text is available in
101+
the source file headers.

0 commit comments

Comments
 (0)