├── .editorconfig
├── .gitattributes
├── .github
├── FUNDING.yml
├── stale.yml
└── workflows
│ ├── build.yml
│ └── pages.yml
├── .gitignore
├── .nuke
├── build.schema.json
└── parameters.json
├── LICENSE.TXT
├── README.md
├── TypefaceUtil.sln
├── azure-pipelines.yml
├── build.cmd
├── build.ps1
├── build.sh
├── build
├── Avalonia.Controls.ItemsRepeater.props
├── Avalonia.Controls.Skia.props
├── Avalonia.Desktop.props
├── Avalonia.Diagnostics.props
├── Avalonia.ReactiveUI.props
├── Avalonia.Skia.props
├── Avalonia.Themes.Fluent.props
├── Avalonia.Web.props
├── Avalonia.Xaml.Behaviors.props
├── Avalonia.props
├── Base.props
├── ReferenceAssemblies.props
├── SignAssembly.props
├── SkiaSharp.Linux.props
├── SkiaSharp.props
├── SourceLink.props
├── Svg.Skia.props
├── System.CommandLine.props
├── System.Text.Json.props
├── TypefaceUtil.public.snk
├── XUnit.props
└── build
│ ├── Build.cs
│ └── _build.csproj
├── global.json
├── nuget.config
├── src
├── TypefaceUtil.CLI
│ ├── Exporters
│ │ ├── CharacterMapPngExporter.cs
│ │ ├── CharacterMapSvgExporter.cs
│ │ └── CharacterMapXamlExporter.cs
│ ├── Program.cs
│ └── TypefaceUtil.CLI.csproj
├── TypefaceUtil.OpenType
│ ├── BigEndianBinaryReader.cs
│ ├── CharacterMap.cs
│ ├── EncodingRecord.cs
│ ├── SequentialMapGroup.cs
│ ├── TableReader.cs
│ └── TypefaceUtil.OpenType.csproj
├── TypefaceUtilAvalonia.Base
│ ├── App.axaml
│ ├── App.axaml.cs
│ ├── Assets
│ │ └── Icon.ico
│ ├── TypefaceUtilAvalonia.Base.csproj
│ ├── ViewLocator.cs
│ ├── ViewModels
│ │ ├── GlyphViewModel.cs
│ │ ├── MainWindowViewModel.cs
│ │ ├── StorageService.cs
│ │ ├── TypefaceViewModel.cs
│ │ └── ViewModelBase.cs
│ └── Views
│ │ ├── MainView.axaml
│ │ ├── MainView.axaml.cs
│ │ ├── MainWindow.axaml
│ │ ├── MainWindow.axaml.cs
│ │ ├── SandBoxView.axaml
│ │ └── SandBoxView.axaml.cs
├── TypefaceUtilAvalonia.Desktop
│ ├── Program.cs
│ └── TypefaceUtilAvalonia.Desktop.csproj
├── TypefaceUtilAvalonia.Web
│ ├── AppBundle
│ │ ├── Logo.svg
│ │ ├── app.css
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── main.js
│ │ └── staticwebapp.config.json
│ ├── Program.cs
│ ├── Roots.xml
│ ├── TypefaceUtilAvalonia.Web.csproj
│ └── runtimeconfig.template.json
└── UnicodeDataGenerator
│ ├── Program.cs
│ └── UnicodeDataGenerator.csproj
└── tests
└── TypefaceUtil.OpenType.UnitTests
└── TypefaceUtil.OpenType.UnitTests.csproj
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Default settings:
7 | # A newline ending every file
8 | # Use 4 spaces as indentation
9 | [*]
10 | insert_final_newline = true
11 | indent_style = space
12 | indent_size = 4
13 |
14 | # C# files
15 | [*.cs]
16 | # New line preferences
17 | csharp_new_line_before_open_brace = all
18 | csharp_new_line_before_else = true
19 | csharp_new_line_before_catch = true
20 | csharp_new_line_before_finally = true
21 | csharp_new_line_before_members_in_object_initializers = true
22 | csharp_new_line_before_members_in_anonymous_types = true
23 | csharp_new_line_between_query_expression_clauses = true
24 |
25 | # Indentation preferences
26 | csharp_indent_block_contents = true
27 | csharp_indent_braces = false
28 | csharp_indent_case_contents = true
29 | csharp_indent_switch_labels = true
30 | csharp_indent_labels = one_less_than_current
31 |
32 | # avoid this. unless absolutely necessary
33 | dotnet_style_qualification_for_field = false:suggestion
34 | dotnet_style_qualification_for_property = false:suggestion
35 | dotnet_style_qualification_for_method = false:suggestion
36 | dotnet_style_qualification_for_event = false:suggestion
37 |
38 | # prefer var
39 | csharp_style_var_for_built_in_types = true
40 | csharp_style_var_when_type_is_apparent = true
41 | csharp_style_var_elsewhere = true:suggestion
42 |
43 | # use language keywords instead of BCL types
44 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
45 | dotnet_style_predefined_type_for_member_access = true:suggestion
46 |
47 | # name all constant fields using PascalCase
48 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
49 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
50 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
51 |
52 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
53 | dotnet_naming_symbols.constant_fields.required_modifiers = const
54 |
55 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
56 |
57 | # static fields should have s_ prefix
58 | dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
59 | dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
60 | dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
61 |
62 | dotnet_naming_symbols.static_fields.applicable_kinds = field
63 | dotnet_naming_symbols.static_fields.required_modifiers = static
64 |
65 | dotnet_naming_style.static_prefix_style.required_prefix = s_
66 | dotnet_naming_style.static_prefix_style.capitalization = camel_case
67 |
68 | # internal and private fields should be _camelCase
69 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
70 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
71 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
72 |
73 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
74 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
75 |
76 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
77 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
78 |
79 | # use accessibility modifiers
80 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
81 |
82 | # Code style defaults
83 | dotnet_sort_system_directives_first = true
84 | csharp_preserve_single_line_blocks = true
85 | csharp_preserve_single_line_statements = false
86 |
87 | # Expression-level preferences
88 | dotnet_style_object_initializer = true:suggestion
89 | dotnet_style_collection_initializer = true:suggestion
90 | dotnet_style_explicit_tuple_names = true:suggestion
91 | dotnet_style_coalesce_expression = true:suggestion
92 | dotnet_style_null_propagation = true:suggestion
93 |
94 | # Expression-bodied members
95 | csharp_style_expression_bodied_methods = false:none
96 | csharp_style_expression_bodied_constructors = false:none
97 | csharp_style_expression_bodied_operators = false:none
98 | csharp_style_expression_bodied_properties = true:none
99 | csharp_style_expression_bodied_indexers = true:none
100 | csharp_style_expression_bodied_accessors = true:none
101 |
102 | # Pattern matching
103 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
104 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
105 | csharp_style_inlined_variable_declaration = true:suggestion
106 |
107 | # Null checking preferences
108 | csharp_style_throw_expression = true:suggestion
109 | csharp_style_conditional_delegate_call = true:suggestion
110 |
111 | # Space preferences
112 | csharp_space_after_cast = false
113 | csharp_space_after_colon_in_inheritance_clause = true
114 | csharp_space_after_comma = true
115 | csharp_space_after_dot = false
116 | csharp_space_after_keywords_in_control_flow_statements = true
117 | csharp_space_after_semicolon_in_for_statement = true
118 | csharp_space_around_binary_operators = before_and_after
119 | csharp_space_around_declaration_statements = do_not_ignore
120 | csharp_space_before_colon_in_inheritance_clause = true
121 | csharp_space_before_comma = false
122 | csharp_space_before_dot = false
123 | csharp_space_before_open_square_brackets = false
124 | csharp_space_before_semicolon_in_for_statement = false
125 | csharp_space_between_empty_square_brackets = false
126 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
127 | csharp_space_between_method_call_name_and_opening_parenthesis = false
128 | csharp_space_between_method_call_parameter_list_parentheses = false
129 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
130 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
131 | csharp_space_between_method_declaration_parameter_list_parentheses = false
132 | csharp_space_between_parentheses = false
133 | csharp_space_between_square_brackets = false
134 |
135 | # Xaml files
136 | [*.{xaml,axaml}]
137 | indent_style = space
138 | indent_size = 2
139 |
140 | # Xml project files
141 | [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
142 | indent_size = 2
143 |
144 | # Xml build files
145 | [*.builds]
146 | indent_size = 2
147 |
148 | # Xml files
149 | [*.{xml,stylecop,resx,ruleset}]
150 | indent_size = 2
151 |
152 | # Xml config files
153 | [*.{props,targets,config,nuspec}]
154 | indent_size = 2
155 |
156 | # Shell scripts
157 | [*.sh]
158 | end_of_line = lf
159 | [*.{cmd, bat}]
160 | end_of_line = crlf
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [wieslawsoltes]
2 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # https://probot.github.io/apps/stale/
2 |
3 | # Number of days of inactivity before an issue becomes stale
4 | daysUntilStale: 60
5 |
6 | # Number of days of inactivity before a stale issue is closed
7 | daysUntilClose: 7
8 |
9 | # Issues with these labels will never be considered stale
10 | exemptLabels:
11 | - pinned
12 | - security
13 |
14 | # Label to use when marking an issue as stale
15 | staleLabel: stale
16 |
17 | # Comment to post when marking an issue as stale. Set to `false` to disable
18 | markComment: >
19 | This issue has been automatically marked as stale because it has not had
20 | recent activity. It will be closed if no further activity occurs. Thank you
21 | for your contributions.
22 |
23 | # Comment to post when closing a stale issue. Set to `false` to disable
24 | closeComment: true
25 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - release/*
8 | pull_request:
9 | branches:
10 | - master
11 | - release/*
12 |
13 | jobs:
14 |
15 | build:
16 | strategy:
17 | matrix:
18 | os: [ubuntu-latest, windows-latest, macos-latest]
19 | name: Build ${{ matrix.os }}
20 | runs-on: ${{ matrix.os }}
21 | steps:
22 | - uses: actions/checkout@v1
23 | with:
24 | submodules: recursive
25 | - name: Setup .NET Core
26 | uses: actions/setup-dotnet@v1
27 | - name: Install wasm-tools
28 | run: dotnet workload install wasm-tools wasm-experimental
29 | - name: Build
30 | run: dotnet build -c Release
31 | - name: Test
32 | run: dotnet test -c Release
33 | - name: Pack
34 | if: github.event_name != 'pull_request'
35 | run: dotnet pack -c Release -o ./artifacts/NuGet
36 |
--------------------------------------------------------------------------------
/.github/workflows/pages.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | env:
4 | PROJECT_PATH: src/TypefaceUtilAvalonia.Web/TypefaceUtilAvalonia.Web.csproj
5 | OUTPUT_PATH: src/TypefaceUtilAvalonia.Web/bin/Release/net9.0-browser/browser-wasm/AppBundle
6 | on:
7 | push:
8 | branches: [ master ]
9 |
10 | jobs:
11 | deploy-to-github-pages:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 |
16 | - name: Setup .NET Core SDK
17 | uses: actions/setup-dotnet@v1.9.0
18 |
19 | - name: Install wasm-tools
20 | run: dotnet workload install wasm-tools wasm-experimental
21 |
22 | - name: Install DotNetCompress
23 | run: dotnet tool install --global DotNetCompress --version 2.0.0 --no-cache
24 |
25 | - name: Publish .NET Project
26 | run: dotnet publish $PROJECT_PATH -c Release -o release --nologo
27 |
28 | - name: Change base-tag in index.html
29 | run: sed -i 's///g' $OUTPUT_PATH/index.html
30 |
31 | - name: copy index.html to 404.html
32 | run: cp $OUTPUT_PATH/index.html $OUTPUT_PATH/404.html
33 |
34 | - name: Compress Output using Brotli
35 | run: DotNetCompress -d $OUTPUT_PATH/ -p "*.dll" "*.js" "*.wasm" --format br --threads 4
36 |
37 | - name: Compress Output using GZip
38 | run: DotNetCompress -d $OUTPUT_PATH -p "*.dll" "*.js" "*.wasm" --format gz --threads 4
39 |
40 | - name: Add .nojekyll file
41 | run: touch $OUTPUT_PATH/.nojekyll
42 |
43 | - name: Commit wwwroot to GitHub Pages
44 | uses: JamesIves/github-pages-deploy-action@4.1.7
45 | with:
46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47 | BRANCH: gh-pages
48 | FOLDER: ${{ env.OUTPUT_PATH }}
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 |
--------------------------------------------------------------------------------
/.nuke/build.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-04/schema#",
3 | "title": "Build Schema",
4 | "$ref": "#/definitions/build",
5 | "definitions": {
6 | "build": {
7 | "type": "object",
8 | "properties": {
9 | "Configuration": {
10 | "type": "string",
11 | "description": "configuration"
12 | },
13 | "Continue": {
14 | "type": "boolean",
15 | "description": "Indicates to continue a previously failed build attempt"
16 | },
17 | "Help": {
18 | "type": "boolean",
19 | "description": "Shows the help text for this build assembly"
20 | },
21 | "Host": {
22 | "type": "string",
23 | "description": "Host for execution. Default is 'automatic'",
24 | "enum": [
25 | "AppVeyor",
26 | "AzurePipelines",
27 | "Bamboo",
28 | "Bitrise",
29 | "GitHubActions",
30 | "GitLab",
31 | "Jenkins",
32 | "Rider",
33 | "SpaceAutomation",
34 | "TeamCity",
35 | "Terminal",
36 | "TravisCI",
37 | "VisualStudio",
38 | "VSCode"
39 | ]
40 | },
41 | "NoLogo": {
42 | "type": "boolean",
43 | "description": "Disables displaying the NUKE logo"
44 | },
45 | "Partition": {
46 | "type": "string",
47 | "description": "Partition to use on CI"
48 | },
49 | "Plan": {
50 | "type": "boolean",
51 | "description": "Shows the execution plan (HTML)"
52 | },
53 | "Profile": {
54 | "type": "array",
55 | "description": "Defines the profiles to load",
56 | "items": {
57 | "type": "string"
58 | }
59 | },
60 | "PublishFramework": {
61 | "type": "string",
62 | "description": "publish-framework"
63 | },
64 | "PublishProject": {
65 | "type": "string",
66 | "description": "publish-project"
67 | },
68 | "PublishRuntime": {
69 | "type": "string",
70 | "description": "publish-runtime"
71 | },
72 | "PublishSelfContained": {
73 | "type": "boolean",
74 | "description": "publish-self-contained"
75 | },
76 | "Root": {
77 | "type": "string",
78 | "description": "Root directory during build execution"
79 | },
80 | "Skip": {
81 | "type": "array",
82 | "description": "List of targets to be skipped. Empty list skips all dependencies",
83 | "items": {
84 | "type": "string",
85 | "enum": [
86 | "Clean",
87 | "Compile",
88 | "Pack",
89 | "Publish",
90 | "Restore",
91 | "Test"
92 | ]
93 | }
94 | },
95 | "Solution": {
96 | "type": "string",
97 | "description": "Path to a solution file that is automatically loaded"
98 | },
99 | "Target": {
100 | "type": "array",
101 | "description": "List of targets to be invoked. Default is '{default_target}'",
102 | "items": {
103 | "type": "string",
104 | "enum": [
105 | "Clean",
106 | "Compile",
107 | "Pack",
108 | "Publish",
109 | "Restore",
110 | "Test"
111 | ]
112 | }
113 | },
114 | "Verbosity": {
115 | "type": "string",
116 | "description": "Logging verbosity during build execution. Default is 'Normal'",
117 | "enum": [
118 | "Minimal",
119 | "Normal",
120 | "Quiet",
121 | "Verbose"
122 | ]
123 | },
124 | "VersionSuffix": {
125 | "type": "string",
126 | "description": "version-suffix"
127 | }
128 | }
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/.nuke/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./build.schema.json",
3 | "Solution": "TypefaceUtil.sln"
4 | }
--------------------------------------------------------------------------------
/LICENSE.TXT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Wiesław Šoltés
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TypefaceUtil
2 |
3 | [](https://dev.azure.com/wieslawsoltes/GitHub/_build/latest?definitionId=95&branchName=master)
4 | [](https://github.com/wieslawsoltes/TypefaceUtil/actions/workflows/build.yml)
5 |
6 | [](https://www.nuget.org/packages/TypefaceUtil.OpenType)
7 | [](https://www.nuget.org/packages/TypefaceUtil.OpenType)
8 |
9 | [](https://github.com/wieslawsoltes/TypefaceUtil)
10 | [](https://github.com/wieslawsoltes/TypefaceUtil)
11 | [](https://github.com/wieslawsoltes/TypefaceUtil)
12 |
13 | An OpenType typeface utilities.
14 |
15 | ## About
16 |
17 | TypefaceUtil is a set of OpenType typeface utilities.
18 | Currently supported are `cmap` table format parser for `character to glyph index mapping`,
19 | generation of character `png` map, `svg` and `xaml` export for glyphs.
20 |
21 | 
22 |
23 | 
24 |
25 | ## Usage
26 |
27 | ```
28 | TypefaceUtil:
29 | An OpenType typeface utilities.
30 |
31 | Usage:
32 | TypefaceUtil [options]
33 |
34 | Options:
35 | -f, --inputFiles The relative or absolute path to the input files
36 | -d, --inputDirectory The relative or absolute path to the input directory
37 | -p, --pattern The search string to match against the names of files in the input directory [default: *.ttf]
38 | --fontFamily The input font family
39 | -o, --outputDirectory The relative or absolute path to the output directory
40 | --zip Create zip archive from exported files
41 | --zipFile The relative or absolute path to the zip file [default: export.zip]
42 | --printFontFamilies Print available font families
43 | --printCharacterMaps Print character maps info
44 | --png, --pngExport Export text as Png
45 | --pngTextSize Png text size [default: 20]
46 | --pngCellSize Png cell size [default: 40]
47 | --pngColumns Png number of columns [default: 20]
48 | --svg, --svgExport Export text as Svg
49 | --svgTextSize Svg text size [default: 16]
50 | --svgPathFill Svg path fill [default: black]
51 | --xaml, --xamlExport Export text as Xaml
52 | --xamlTextSize Xaml text size [default: 16]
53 | --xamlBrush Xaml brush [default: Black]
54 | --quiet Set verbosity level to quiet
55 | --debug Set verbosity level to debug
56 | --version Show version information
57 | -?, -h, --help Show help and usage information
58 | ```
59 |
60 | ```
61 | TypefaceUtil -h
62 | ```
63 | ```
64 | TypefaceUtil --pngExport -f segoeui.ttf
65 | TypefaceUtil --pngExport --fontFamily "Segoe UI"
66 | ```
67 | ```
68 | TypefaceUtil --svgExport -f seguisym.ttf
69 | TypefaceUtil --svgExport --fontFamily "Segoe UI Symbol"
70 | ```
71 | ```
72 | TypefaceUtil --xamlExport -f calibri.ttf
73 | TypefaceUtil --xamlExport --fontFamily "Calibri"
74 | ```
75 |
76 | ```
77 | TypefaceUtil -d C:\Windows\Fonts --png --svg --xaml -o export
78 | TypefaceUtil -d C:\Windows\Fonts --png --svg --xaml --zip --zipFile "Windows-Fonts-IconPack.zip"
79 | ```
80 |
81 | ## Build
82 |
83 | ```
84 | dotnet build
85 | ```
86 |
87 | ## References
88 |
89 | * https://docs.microsoft.com/en-us/typography/opentype/spec/cmap
90 | * https://docs.microsoft.com/en-us/dotnet/api/skiasharp.sktypeface?view=skiasharp-1.68.1
91 |
92 | ## License
93 |
94 | TypefaceUtil is licensed under the [MIT license](LICENSE.TXT).
95 |
--------------------------------------------------------------------------------
/TypefaceUtil.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30011.22
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TypefaceUtil.CLI", "src\TypefaceUtil.CLI\TypefaceUtil.CLI.csproj", "{35DC5579-5E51-458F-BFFC-65133AE83F50}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TypefaceUtil.OpenType", "src\TypefaceUtil.OpenType\TypefaceUtil.OpenType.csproj", "{A97DD9DD-60D0-4D3C-847E-BAF7322BDBAE}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FA0D294D-2942-4349-AC60-BB837AA95DAB}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypefaceUtilAvalonia.Base", "src\TypefaceUtilAvalonia.Base\TypefaceUtilAvalonia.Base.csproj", "{DDCA69DA-405C-4A3B-AF4F-A969FE107D23}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnicodeDataGenerator", "src\UnicodeDataGenerator\UnicodeDataGenerator.csproj", "{DF30A956-2615-4B4A-A129-981DE567629F}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{13D366ED-5898-462D-B5F6-BCCDBBCD2C5F}"
17 | ProjectSection(SolutionItems) = preProject
18 | build\TypefaceUtil.public.snk = build\TypefaceUtil.public.snk
19 | .editorconfig = .editorconfig
20 | build.ps1 = build.ps1
21 | build.sh = build.sh
22 | global.json = global.json
23 | azure-pipelines.yml = azure-pipelines.yml
24 | EndProjectSection
25 | EndProject
26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{24BDDCC3-B076-4684-94AF-8CBDDD2A8EBA}"
27 | ProjectSection(SolutionItems) = preProject
28 | LICENSE.TXT = LICENSE.TXT
29 | README.md = README.md
30 | EndProjectSection
31 | EndProject
32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C2F75A5F-9D68-48E1-AAD5-094542747228}"
33 | EndProject
34 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypefaceUtil.OpenType.UnitTests", "tests\TypefaceUtil.OpenType.UnitTests\TypefaceUtil.OpenType.UnitTests.csproj", "{80ADDBA5-A00F-4545-8642-951143DA2340}"
35 | EndProject
36 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "git", "git", "{FFC56A7E-7F00-4EAD-827A-6242E9855688}"
37 | ProjectSection(SolutionItems) = preProject
38 | .gitattributes = .gitattributes
39 | .gitignore = .gitignore
40 | EndProjectSection
41 | EndProject
42 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{429210F9-1870-4169-B2E1-FD834DB8BE86}"
43 | ProjectSection(SolutionItems) = preProject
44 | nuget.config = nuget.config
45 | EndProjectSection
46 | EndProject
47 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "props", "props", "{57F2A3FD-99C6-4E6A-9813-B9B5F0FBC11D}"
48 | ProjectSection(SolutionItems) = preProject
49 | build\Avalonia.Controls.Skia.props = build\Avalonia.Controls.Skia.props
50 | build\Avalonia.Desktop.props = build\Avalonia.Desktop.props
51 | build\Avalonia.Diagnostics.props = build\Avalonia.Diagnostics.props
52 | build\Avalonia.props = build\Avalonia.props
53 | build\Avalonia.ReactiveUI.props = build\Avalonia.ReactiveUI.props
54 | build\Avalonia.Skia.props = build\Avalonia.Skia.props
55 | build\Avalonia.Xaml.Behaviors.props = build\Avalonia.Xaml.Behaviors.props
56 | build\Base.props = build\Base.props
57 | build\ReferenceAssemblies.props = build\ReferenceAssemblies.props
58 | build\SignAssembly.props = build\SignAssembly.props
59 | build\SkiaSharp.Linux.props = build\SkiaSharp.Linux.props
60 | build\SkiaSharp.props = build\SkiaSharp.props
61 | build\SourceLink.props = build\SourceLink.props
62 | build\Svg.Skia.props = build\Svg.Skia.props
63 | build\System.CommandLine.props = build\System.CommandLine.props
64 | build\System.Text.Json.props = build\System.Text.Json.props
65 | build\XUnit.props = build\XUnit.props
66 | build\Avalonia.Themes.Fluent.props = build\Avalonia.Themes.Fluent.props
67 | build\Avalonia.Web.props = build\Avalonia.Web.props
68 | build\Avalonia.Controls.ItemsRepeater.props = build\Avalonia.Controls.ItemsRepeater.props
69 | EndProjectSection
70 | EndProject
71 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_build", "build\build\_build.csproj", "{A66A3423-81AE-43B9-AAA6-0FBC44BE5152}"
72 | EndProject
73 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypefaceUtilAvalonia.Desktop", "src\TypefaceUtilAvalonia.Desktop\TypefaceUtilAvalonia.Desktop.csproj", "{0F1B7AE5-AE2D-4311-86E3-46910F7B3ABD}"
74 | EndProject
75 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypefaceUtilAvalonia.Web", "src\TypefaceUtilAvalonia.Web\TypefaceUtilAvalonia.Web.csproj", "{B9F49AF5-FCBE-416E-8289-8D680B579854}"
76 | EndProject
77 | Global
78 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
79 | Debug|Any CPU = Debug|Any CPU
80 | Release|Any CPU = Release|Any CPU
81 | EndGlobalSection
82 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
83 | {35DC5579-5E51-458F-BFFC-65133AE83F50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
84 | {35DC5579-5E51-458F-BFFC-65133AE83F50}.Debug|Any CPU.Build.0 = Debug|Any CPU
85 | {35DC5579-5E51-458F-BFFC-65133AE83F50}.Release|Any CPU.ActiveCfg = Release|Any CPU
86 | {35DC5579-5E51-458F-BFFC-65133AE83F50}.Release|Any CPU.Build.0 = Release|Any CPU
87 | {A97DD9DD-60D0-4D3C-847E-BAF7322BDBAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
88 | {A97DD9DD-60D0-4D3C-847E-BAF7322BDBAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
89 | {A97DD9DD-60D0-4D3C-847E-BAF7322BDBAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
90 | {A97DD9DD-60D0-4D3C-847E-BAF7322BDBAE}.Release|Any CPU.Build.0 = Release|Any CPU
91 | {DDCA69DA-405C-4A3B-AF4F-A969FE107D23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
92 | {DDCA69DA-405C-4A3B-AF4F-A969FE107D23}.Debug|Any CPU.Build.0 = Debug|Any CPU
93 | {DDCA69DA-405C-4A3B-AF4F-A969FE107D23}.Release|Any CPU.ActiveCfg = Release|Any CPU
94 | {DDCA69DA-405C-4A3B-AF4F-A969FE107D23}.Release|Any CPU.Build.0 = Release|Any CPU
95 | {DF30A956-2615-4B4A-A129-981DE567629F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
96 | {DF30A956-2615-4B4A-A129-981DE567629F}.Debug|Any CPU.Build.0 = Debug|Any CPU
97 | {DF30A956-2615-4B4A-A129-981DE567629F}.Release|Any CPU.ActiveCfg = Release|Any CPU
98 | {DF30A956-2615-4B4A-A129-981DE567629F}.Release|Any CPU.Build.0 = Release|Any CPU
99 | {80ADDBA5-A00F-4545-8642-951143DA2340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
100 | {80ADDBA5-A00F-4545-8642-951143DA2340}.Debug|Any CPU.Build.0 = Debug|Any CPU
101 | {80ADDBA5-A00F-4545-8642-951143DA2340}.Release|Any CPU.ActiveCfg = Release|Any CPU
102 | {80ADDBA5-A00F-4545-8642-951143DA2340}.Release|Any CPU.Build.0 = Release|Any CPU
103 | {A66A3423-81AE-43B9-AAA6-0FBC44BE5152}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
104 | {A66A3423-81AE-43B9-AAA6-0FBC44BE5152}.Debug|Any CPU.Build.0 = Debug|Any CPU
105 | {A66A3423-81AE-43B9-AAA6-0FBC44BE5152}.Release|Any CPU.ActiveCfg = Release|Any CPU
106 | {A66A3423-81AE-43B9-AAA6-0FBC44BE5152}.Release|Any CPU.Build.0 = Release|Any CPU
107 | {0F1B7AE5-AE2D-4311-86E3-46910F7B3ABD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
108 | {0F1B7AE5-AE2D-4311-86E3-46910F7B3ABD}.Debug|Any CPU.Build.0 = Debug|Any CPU
109 | {0F1B7AE5-AE2D-4311-86E3-46910F7B3ABD}.Release|Any CPU.ActiveCfg = Release|Any CPU
110 | {0F1B7AE5-AE2D-4311-86E3-46910F7B3ABD}.Release|Any CPU.Build.0 = Release|Any CPU
111 | {B9F49AF5-FCBE-416E-8289-8D680B579854}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
112 | {B9F49AF5-FCBE-416E-8289-8D680B579854}.Debug|Any CPU.Build.0 = Debug|Any CPU
113 | {B9F49AF5-FCBE-416E-8289-8D680B579854}.Release|Any CPU.ActiveCfg = Release|Any CPU
114 | {B9F49AF5-FCBE-416E-8289-8D680B579854}.Release|Any CPU.Build.0 = Release|Any CPU
115 | EndGlobalSection
116 | GlobalSection(SolutionProperties) = preSolution
117 | HideSolutionNode = FALSE
118 | EndGlobalSection
119 | GlobalSection(NestedProjects) = preSolution
120 | {35DC5579-5E51-458F-BFFC-65133AE83F50} = {FA0D294D-2942-4349-AC60-BB837AA95DAB}
121 | {A97DD9DD-60D0-4D3C-847E-BAF7322BDBAE} = {FA0D294D-2942-4349-AC60-BB837AA95DAB}
122 | {DDCA69DA-405C-4A3B-AF4F-A969FE107D23} = {FA0D294D-2942-4349-AC60-BB837AA95DAB}
123 | {DF30A956-2615-4B4A-A129-981DE567629F} = {FA0D294D-2942-4349-AC60-BB837AA95DAB}
124 | {80ADDBA5-A00F-4545-8642-951143DA2340} = {C2F75A5F-9D68-48E1-AAD5-094542747228}
125 | {FFC56A7E-7F00-4EAD-827A-6242E9855688} = {13D366ED-5898-462D-B5F6-BCCDBBCD2C5F}
126 | {429210F9-1870-4169-B2E1-FD834DB8BE86} = {13D366ED-5898-462D-B5F6-BCCDBBCD2C5F}
127 | {57F2A3FD-99C6-4E6A-9813-B9B5F0FBC11D} = {13D366ED-5898-462D-B5F6-BCCDBBCD2C5F}
128 | {A66A3423-81AE-43B9-AAA6-0FBC44BE5152} = {13D366ED-5898-462D-B5F6-BCCDBBCD2C5F}
129 | {0F1B7AE5-AE2D-4311-86E3-46910F7B3ABD} = {FA0D294D-2942-4349-AC60-BB837AA95DAB}
130 | {B9F49AF5-FCBE-416E-8289-8D680B579854} = {FA0D294D-2942-4349-AC60-BB837AA95DAB}
131 | EndGlobalSection
132 | GlobalSection(ExtensibilityGlobals) = postSolution
133 | SolutionGuid = {3D7D93DB-65A8-4680-ADDD-EE67EEC2C975}
134 | EndGlobalSection
135 | EndGlobal
136 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | name: $(date:yyyyMMdd)$(rev:-rr)
2 |
3 | resources:
4 | repositories:
5 | - repository: templates
6 | endpoint: wieslawsoltes
7 | type: github
8 | name: wieslawsoltes/BuildTemplates
9 | ref: refs/tags/v2.0.0
10 |
11 | variables:
12 | BuildConfiguration: 'Release'
13 | BuildPlatform: 'Any CPU'
14 | PublishFramework: 'net9.0'
15 | PublishProject: 'TypefaceUtilAvalonia.Desktop'
16 | PublishRuntime: ''
17 | Workloads: 'wasm-tools wasm-experimental'
18 |
19 | jobs:
20 | - template: Test-PowerShell.yml@templates
21 | parameters:
22 | name: 'Test_Windows'
23 | vmImage: 'windows-2022'
24 | BuildConfiguration: ${{ variables.BuildConfiguration }}
25 | Workloads: ${{ variables.Workloads }}
26 |
27 | - template: Test-Bash.yml@templates
28 | parameters:
29 | name: 'Test_Linux'
30 | vmImage: 'ubuntu-20.04'
31 | BuildConfiguration: ${{ variables.BuildConfiguration }}
32 | Workloads: ${{ variables.Workloads }}
33 |
34 | - template: Test-Bash.yml@templates
35 | parameters:
36 | name: 'Test_macOS'
37 | vmImage: 'macOS-14'
38 | BuildConfiguration: ${{ variables.BuildConfiguration }}
39 | Workloads: ${{ variables.Workloads }}
40 |
41 | - template: Pack-MyGet.yml@templates
42 | parameters:
43 | name: 'Pack_MyGet'
44 | vmImage: 'windows-2022'
45 | BuildConfiguration: ${{ variables.BuildConfiguration }}
46 | Workloads: ${{ variables.Workloads }}
47 |
48 | - template: Pack-NuGet.yml@templates
49 | parameters:
50 | name: 'Pack_NuGet'
51 | vmImage: 'windows-2022'
52 | BuildConfiguration: ${{ variables.BuildConfiguration }}
53 | Workloads: ${{ variables.Workloads }}
54 |
55 | - template: Publish-PowerShell.yml@templates
56 | parameters:
57 | name: 'Publish_Windows'
58 | vmImage: 'windows-2022'
59 | BuildConfiguration: ${{ variables.BuildConfiguration }}
60 | Workloads: ${{ variables.Workloads }}
61 | PublishFramework: ${{ variables.PublishFramework }}
62 | PublishProject: ${{ variables.PublishProject }}
63 | PublishRuntime: 'win-x64'
64 |
65 | - template: Publish-Bash.yml@templates
66 | parameters:
67 | name: 'Publish_Ubuntu'
68 | vmImage: 'ubuntu-20.04'
69 | BuildConfiguration: ${{ variables.BuildConfiguration }}
70 | Workloads: ${{ variables.Workloads }}
71 | PublishFramework: ${{ variables.PublishFramework }}
72 | PublishProject: ${{ variables.PublishProject }}
73 | PublishRuntime: 'linux-x64'
74 |
75 | - template: Publish-Bash.yml@templates
76 | parameters:
77 | name: 'Publish_macOS'
78 | vmImage: 'macOS-14'
79 | BuildConfiguration: ${{ variables.BuildConfiguration }}
80 | Workloads: ${{ variables.Workloads }}
81 | PublishFramework: ${{ variables.PublishFramework }}
82 | PublishProject: ${{ variables.PublishProject }}
83 | PublishRuntime: 'osx-x64'
84 |
--------------------------------------------------------------------------------
/build.cmd:
--------------------------------------------------------------------------------
1 | :; set -eo pipefail
2 | :; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
3 | :; ${SCRIPT_DIR}/build.sh "$@"
4 | :; exit $?
5 |
6 | @ECHO OFF
7 | powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
8 |
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | Param(
3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
4 | [string[]]$BuildArguments
5 | )
6 |
7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
8 |
9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
11 |
12 | ###########################################################################
13 | # CONFIGURATION
14 | ###########################################################################
15 |
16 | $BuildProjectFile = "$PSScriptRoot\build\build\_build.csproj"
17 | $TempDirectory = "$PSScriptRoot\\.nuke\temp"
18 |
19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json"
20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
21 | $DotNetChannel = "Current"
22 |
23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0
26 |
27 | ###########################################################################
28 | # EXECUTION
29 | ###########################################################################
30 |
31 | function ExecSafe([scriptblock] $cmd) {
32 | & $cmd
33 | if ($LASTEXITCODE) { exit $LASTEXITCODE }
34 | }
35 |
36 | # If dotnet CLI is installed globally and it matches requested version, use for execution
37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) {
39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path
40 | }
41 | else {
42 | # Download install script
43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
47 |
48 | # If global.json exists, load expected version
49 | if (Test-Path $DotNetGlobalFile) {
50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
52 | $DotNetVersion = $DotNetGlobal.sdk.version
53 | }
54 | }
55 |
56 | # Install by channel or version
57 | $DotNetDirectory = "$TempDirectory\dotnet-win"
58 | if (!(Test-Path variable:DotNetVersion)) {
59 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
60 | } else {
61 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
62 | }
63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
64 | }
65 |
66 | Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
67 |
68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
70 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bash --version 2>&1 | head -n 1
4 |
5 | set -eo pipefail
6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
7 |
8 | ###########################################################################
9 | # CONFIGURATION
10 | ###########################################################################
11 |
12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/build/build/_build.csproj"
13 | TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
14 |
15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
16 | DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
17 | DOTNET_CHANNEL="Current"
18 |
19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1
20 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
21 | export DOTNET_MULTILEVEL_LOOKUP=0
22 |
23 | ###########################################################################
24 | # EXECUTION
25 | ###########################################################################
26 |
27 | function FirstJsonValue {
28 | perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
29 | }
30 |
31 | # If dotnet CLI is installed globally and it matches requested version, use for execution
32 | if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
33 | export DOTNET_EXE="$(command -v dotnet)"
34 | else
35 | # Download install script
36 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
37 | mkdir -p "$TEMP_DIRECTORY"
38 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
39 | chmod +x "$DOTNET_INSTALL_FILE"
40 |
41 | # If global.json exists, load expected version
42 | if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
43 | DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
44 | if [[ "$DOTNET_VERSION" == "" ]]; then
45 | unset DOTNET_VERSION
46 | fi
47 | fi
48 |
49 | # Install by channel or version
50 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
51 | if [[ -z ${DOTNET_VERSION+x} ]]; then
52 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
53 | else
54 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
55 | fi
56 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
57 | fi
58 |
59 | echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
60 |
61 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
62 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
63 |
--------------------------------------------------------------------------------
/build/Avalonia.Controls.ItemsRepeater.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Avalonia.Controls.Skia.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Avalonia.Desktop.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Avalonia.Diagnostics.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Avalonia.ReactiveUI.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Avalonia.Skia.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Avalonia.Themes.Fluent.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Avalonia.Web.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Avalonia.Xaml.Behaviors.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Avalonia.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/Base.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 11.2.0
5 |
6 | Wiesław Šoltés
7 | Wiesław Šoltés
8 | Copyright © Wiesław Šoltés 2024
9 | https://github.com/wieslawsoltes/TypefaceUtil
10 |
11 |
12 | latest
13 | preview
14 |
15 |
16 |
--------------------------------------------------------------------------------
/build/ReferenceAssemblies.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/SignAssembly.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | True
5 | $(MSBuildThisFileDirectory)\TypefaceUtil.public.snk
6 | false
7 | true
8 |
9 |
10 |
--------------------------------------------------------------------------------
/build/SkiaSharp.Linux.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/SkiaSharp.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/SourceLink.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 | false
5 | true
6 | embedded
7 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
8 |
9 |
10 | true
11 |
12 |
13 | true
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/build/Svg.Skia.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/System.CommandLine.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/System.Text.Json.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build/TypefaceUtil.public.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wieslawsoltes/TypefaceUtil/10b646285e7d26e55e63e6f33d4de1fac870c36d/build/TypefaceUtil.public.snk
--------------------------------------------------------------------------------
/build/XUnit.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/build/build/Build.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Nuke.Common;
3 | using Nuke.Common.Git;
4 | using Nuke.Common.ProjectModel;
5 | using Nuke.Common.Tools.DotNet;
6 | using Nuke.Common.IO;
7 | using static Nuke.Common.IO.FileSystemTasks;
8 | using static Nuke.Common.IO.PathConstruction;
9 | using static Nuke.Common.Tools.DotNet.DotNetTasks;
10 |
11 | class Build : NukeBuild
12 | {
13 | public static int Main() => Execute(x => x.Compile);
14 |
15 | [Solution]
16 | readonly Solution Solution;
17 |
18 | [GitRepository]
19 | readonly GitRepository GitRepository;
20 |
21 | [Parameter("configuration")]
22 | public string Configuration { get; set; }
23 |
24 | [Parameter("version-suffix")]
25 | public string VersionSuffix { get; set; }
26 |
27 | [Parameter("publish-framework")]
28 | public string PublishFramework { get; set; }
29 |
30 | [Parameter("publish-runtime")]
31 | public string PublishRuntime { get; set; }
32 |
33 | [Parameter("publish-project")]
34 | public string PublishProject { get; set; }
35 |
36 | [Parameter("publish-self-contained")]
37 | public bool PublishSelfContained { get; set; } = true;
38 |
39 | AbsolutePath SourceDirectory => RootDirectory / "src";
40 |
41 | AbsolutePath TestsDirectory => RootDirectory / "tests";
42 |
43 | AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts";
44 |
45 | protected override void OnBuildInitialized()
46 | {
47 | Configuration = Configuration ?? "Release";
48 | VersionSuffix = VersionSuffix ?? "";
49 | }
50 |
51 | private void DeleteDirectories(IReadOnlyCollection directories)
52 | {
53 | foreach (var directory in directories)
54 | {
55 | DeleteDirectory(directory);
56 | }
57 | }
58 |
59 | Target Clean => _ => _
60 | .Executes(() =>
61 | {
62 | DeleteDirectories(GlobDirectories(SourceDirectory, "**/bin", "**/obj"));
63 | DeleteDirectories(GlobDirectories(TestsDirectory, "**/bin", "**/obj"));
64 | EnsureCleanDirectory(ArtifactsDirectory);
65 | });
66 |
67 | Target Restore => _ => _
68 | .DependsOn(Clean)
69 | .Executes(() =>
70 | {
71 | DotNetRestore(s => s
72 | .SetProjectFile(Solution));
73 | });
74 |
75 | Target Compile => _ => _
76 | .DependsOn(Restore)
77 | .Executes(() =>
78 | {
79 | DotNetBuild(s => s
80 | .SetProjectFile(Solution)
81 | .SetConfiguration(Configuration)
82 | .SetVersionSuffix(VersionSuffix)
83 | .EnableNoRestore());
84 | });
85 |
86 | Target Test => _ => _
87 | .DependsOn(Compile)
88 | .Executes(() =>
89 | {
90 | DotNetTest(s => s
91 | .SetProjectFile(Solution)
92 | .SetConfiguration(Configuration)
93 | .SetLoggers("trx")
94 | .SetResultsDirectory(ArtifactsDirectory / "TestResults")
95 | .EnableNoBuild()
96 | .EnableNoRestore());
97 | });
98 |
99 | Target Pack => _ => _
100 | .DependsOn(Test)
101 | .Executes(() =>
102 | {
103 | DotNetPack(s => s
104 | .SetProject(Solution)
105 | .SetConfiguration(Configuration)
106 | .SetVersionSuffix(VersionSuffix)
107 | .SetOutputDirectory(ArtifactsDirectory / "NuGet")
108 | .EnableNoBuild()
109 | .EnableNoRestore());
110 | });
111 |
112 | Target Publish => _ => _
113 | .DependsOn(Test)
114 | .Requires(() => PublishRuntime)
115 | .Requires(() => PublishFramework)
116 | .Requires(() => PublishProject)
117 | .Executes(() =>
118 | {
119 | DotNetPublish(s => s
120 | .SetProject(Solution.GetProject(PublishProject))
121 | .SetConfiguration(Configuration)
122 | .SetVersionSuffix(VersionSuffix)
123 | .SetFramework(PublishFramework)
124 | .SetRuntime(PublishRuntime)
125 | .SetSelfContained(PublishSelfContained)
126 | .SetOutput(ArtifactsDirectory / "Publish" / PublishProject + "-" + PublishFramework + "-" + PublishRuntime));
127 | });
128 | }
129 |
--------------------------------------------------------------------------------
/build/build/_build.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | false
7 | False
8 | CS0649;CS0169
9 | 1
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "9.0.100",
4 | "rollForward": "latestMinor",
5 | "allowPrerelease": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/TypefaceUtil.CLI/Exporters/CharacterMapPngExporter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using SkiaSharp;
5 |
6 | namespace TypefaceUtil;
7 |
8 | public static class CharacterMapPngExporter
9 | {
10 | public static void Save(Dictionary characterToGlyphMap, SKTypeface typeface, float textSize, int cellSize, int columns, Stream stream)
11 | {
12 | var skBackgroundColor = new SKColor(0xFF, 0xFF, 0xFF);
13 | var skLineColor = new SKColor(0x00, 0x00, 0x00);
14 | var skTextColor = new SKColor(0x00, 0x00, 0x00);
15 |
16 | var numCharCodes = characterToGlyphMap.Count;
17 |
18 | int rows = (int)Math.Ceiling((double)((double)numCharCodes / (double)columns));
19 | int width = (columns * cellSize);
20 | int height = (rows * cellSize);
21 | var skImageInfo = new SKImageInfo(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
22 | var skBitmap = new SKBitmap(skImageInfo);
23 | using var skCanvas = new SKCanvas(skBitmap);
24 |
25 | skCanvas.Clear(skBackgroundColor);
26 |
27 | using var skLinePaint = new SKPaint
28 | {
29 | IsAntialias = false,
30 | Color = skLineColor,
31 | StrokeWidth = 1
32 | };
33 |
34 | for (float x = cellSize; x < (float)width; x += cellSize)
35 | {
36 | skCanvas.DrawLine(x, 0f, x, (float)height, skLinePaint);
37 | }
38 |
39 | for (float y = cellSize; y < (float)height; y += cellSize)
40 | {
41 | skCanvas.DrawLine(0f, y, (float)width, y, skLinePaint);
42 | }
43 |
44 | using var skTextPaint = new SKPaint
45 | {
46 | IsAntialias = true,
47 | Color = skTextColor,
48 | Typeface = typeface,
49 | TextEncoding = SKTextEncoding.Utf32,
50 | TextSize = textSize,
51 | TextAlign = SKTextAlign.Center,
52 | LcdRenderText = true,
53 | SubpixelText = true
54 | };
55 |
56 | var metrics = skTextPaint.FontMetrics;
57 | var mAscent = metrics.Ascent;
58 | var mDescent = metrics.Descent;
59 |
60 | UInt32 charCodeCount = 0;
61 |
62 | foreach (var kvp in characterToGlyphMap)
63 | {
64 | var charCode = kvp.Key;
65 | var utf32 = Char.ConvertFromUtf32((int)charCode);
66 | int row = (int)Math.Floor((double)((double)charCodeCount / (double)columns));
67 | int column = (int)((((double)charCodeCount * (double)cellSize) % (double)width) / (double)cellSize);
68 | float x = (float)(column * cellSize) + (cellSize / 2f);
69 | float y = (row * cellSize) + ((cellSize / 2.0f) - (mAscent / 2.0f) - mDescent / 2.0f);
70 |
71 | charCodeCount++;
72 |
73 | skCanvas.DrawText(utf32, x, y, skTextPaint);
74 | }
75 |
76 | using var skImage = SKImage.FromBitmap(skBitmap);
77 | using var skData = skImage.Encode(SKEncodedImageFormat.Png, 100);
78 | skData.SaveTo(stream);
79 | }
80 | }
--------------------------------------------------------------------------------
/src/TypefaceUtil.CLI/Exporters/CharacterMapSvgExporter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Text;
6 | using SkiaSharp;
7 |
8 | namespace TypefaceUtil;
9 |
10 | public static class CharacterMapSvgExporter
11 | {
12 | public static void Save(Dictionary characterToGlyphMap, SKTypeface typeface, float textSize, string fill, string outputDirectory, ZipArchive? zipArchive)
13 | {
14 | var skColor = new SKColor(0x00, 0x00, 0x00);
15 |
16 | using var skTextPaint = new SKPaint
17 | {
18 | IsAntialias = true,
19 | Color = skColor,
20 | Typeface = typeface,
21 | TextEncoding = SKTextEncoding.Utf32,
22 | TextSize = textSize,
23 | TextAlign = SKTextAlign.Center,
24 | LcdRenderText = true,
25 | SubpixelText = true
26 | };
27 |
28 | var metrics = skTextPaint.FontMetrics;
29 | var mAscent = metrics.Ascent;
30 | var mDescent = metrics.Descent;
31 |
32 | foreach (var kvp in characterToGlyphMap)
33 | {
34 | var charCode = kvp.Key;
35 | var utf32 = Char.ConvertFromUtf32((int)charCode);
36 | float x = 0;
37 | float y = (mAscent / 2.0f) - mDescent / 2.0f;
38 |
39 | using var outlinePath = skTextPaint.GetTextPath(utf32, x, y);
40 | using var fillPath = skTextPaint.GetFillPath(outlinePath);
41 |
42 | fillPath.Transform(SKMatrix.CreateTranslation(-fillPath.Bounds.Left, -fillPath.Bounds.Top));
43 |
44 | var bounds = fillPath.Bounds;
45 | var svgPathData = fillPath.ToSvgPathData();
46 |
47 | var sb = new StringBuilder();
48 |
49 | sb.AppendLine($"");
52 |
53 | var svg = sb.ToString();
54 |
55 | var outputPath = Path.Combine(outputDirectory, $"{charCode.ToString("X2").PadLeft(5, '0')}_{typeface.FamilyName}.svg");
56 |
57 | if (zipArchive != null)
58 | {
59 | var zipArchiveEntry = zipArchive.CreateEntry(outputPath);
60 | using var streamWriter = new StreamWriter(zipArchiveEntry.Open());
61 | streamWriter.Write(svg);
62 | }
63 | else
64 | {
65 | using var streamWriter = File.CreateText(outputPath);
66 | streamWriter.Write(svg);
67 | }
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/src/TypefaceUtil.CLI/Exporters/CharacterMapXamlExporter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using SkiaSharp;
5 |
6 | namespace TypefaceUtil;
7 |
8 | public static class CharacterMapXamlExporter
9 | {
10 | public static void Save(Dictionary characterToGlyphMap, SKTypeface typeface, float textSize, string brush, StreamWriter streamWriter)
11 | {
12 | var skColor = new SKColor(0x00, 0x00, 0x00);
13 |
14 | using var skTextPaint = new SKPaint
15 | {
16 | IsAntialias = true,
17 | Color = skColor,
18 | Typeface = typeface,
19 | TextEncoding = SKTextEncoding.Utf32,
20 | TextSize = textSize,
21 | TextAlign = SKTextAlign.Center,
22 | LcdRenderText = true,
23 | SubpixelText = true
24 | };
25 |
26 | var metrics = skTextPaint.FontMetrics;
27 | var mAscent = metrics.Ascent;
28 | var mDescent = metrics.Descent;
29 |
30 | streamWriter.WriteLine("");
32 | streamWriter.WriteLine(" ");
54 | streamWriter.WriteLine("");
55 | }
56 | }
--------------------------------------------------------------------------------
/src/TypefaceUtil.CLI/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.CommandLine;
4 | using System.CommandLine.Invocation;
5 | using System.IO;
6 | using System.IO.Compression;
7 | using System.Threading.Tasks;
8 | using SkiaSharp;
9 | using TypefaceUtil.OpenType;
10 |
11 | namespace TypefaceUtil;
12 |
13 | class Settings
14 | {
15 | // Input
16 | public FileInfo[]? InputFiles { get; set; }
17 | public DirectoryInfo? InputDirectory { get; set; }
18 | public string Pattern { get; set; } = "*.ttf";
19 | public string? FontFamily { get; set; }
20 | // Output
21 | public DirectoryInfo? OutputDirectory { get; set; }
22 | public bool Zip { get; set; } = false;
23 | public FileInfo? ZipFile { get; set; }
24 | // Info
25 | public bool PrintFontFamilies { get; set; } = false;
26 | public bool PrintCharacterMaps { get; set; } = false;
27 | // Png Export
28 | public bool PngExport { get; set; } = false;
29 | public float PngTextSize { get; set; } = 20f;
30 | public int PngCellSize { get; set; } = 40;
31 | public int PngColumns { get; set; } = 20;
32 | // Svg Export
33 | public bool SvgExport { get; set; } = false;
34 | public float SvgTextSize { get; set; } = 16f;
35 | public string SvgPathFill { get; set; } = "black";
36 | // Xaml Export
37 | public bool XamlExport { get; set; } = false;
38 | public float XamlTextSize { get; set; } = 16f;
39 | public string XamlBrush { get; set; } = "Black";
40 | // Other
41 | public bool Quiet { get; set; }
42 | public bool Debug { get; set; }
43 | }
44 |
45 | class Program
46 | {
47 | static void Log(string message)
48 | {
49 | Console.WriteLine(message);
50 | }
51 |
52 | static void Error(Exception ex)
53 | {
54 | Log($"{ex.Message}");
55 | Log($"{ex.StackTrace}");
56 | if (ex.InnerException != null)
57 | {
58 | Error(ex.InnerException);
59 | }
60 | }
61 |
62 | static void GetFiles(DirectoryInfo directory, string pattern, List paths)
63 | {
64 | var files = Directory.EnumerateFiles(directory.FullName, pattern);
65 | if (files != null)
66 | {
67 | foreach (var path in files)
68 | {
69 | paths.Add(new FileInfo(path));
70 | }
71 | }
72 | }
73 |
74 | static void Run(Settings settings)
75 | {
76 | var paths = new List();
77 |
78 | if (settings.InputFiles != null)
79 | {
80 | foreach (var file in settings.InputFiles)
81 | {
82 | paths.Add(file);
83 | }
84 | }
85 |
86 | if (settings.InputDirectory != null)
87 | {
88 | var directory = settings.InputDirectory;
89 | var pattern = settings.Pattern;
90 | GetFiles(directory, pattern, paths);
91 | }
92 |
93 | if (settings.OutputDirectory != null && !string.IsNullOrEmpty(settings.OutputDirectory.FullName))
94 | {
95 | if (!Directory.Exists(settings.OutputDirectory.FullName))
96 | {
97 | Directory.CreateDirectory(settings.OutputDirectory.FullName);
98 | }
99 | }
100 |
101 | if (settings.Zip)
102 | {
103 | var outputPathZip = settings.ZipFile == null ? "export.zip" : settings.ZipFile.FullName;
104 | if (settings.OutputDirectory != null && !string.IsNullOrEmpty(settings.OutputDirectory.FullName))
105 | {
106 | outputPathZip = Path.Combine(settings.OutputDirectory.FullName, outputPathZip);
107 | }
108 |
109 | using var zipStream = File.Create(outputPathZip);
110 | using var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create);
111 |
112 | for (int i = 0; i < paths.Count; i++)
113 | {
114 | var inputPath = paths[i];
115 | using var typeface = SKTypeface.FromFile(inputPath.FullName);
116 | if (typeface != null)
117 | {
118 | var characterMaps = Read(typeface, settings.Debug);
119 | if (settings.PrintCharacterMaps)
120 | {
121 | Print(characterMaps, typeface);
122 | }
123 | Export(settings, characterMaps, typeface, zipArchive);
124 | }
125 | else
126 | {
127 | if (!settings.Quiet)
128 | {
129 | Log($"Failed to load typeface from file: {inputPath.FullName}");
130 | }
131 | }
132 | }
133 |
134 | var fontFamily = settings.FontFamily;
135 | if (!string.IsNullOrEmpty(fontFamily))
136 | {
137 | using var typeface = SKTypeface.FromFamilyName(fontFamily);
138 | if (typeface != null)
139 | {
140 | var characterMaps = Read(typeface, settings.Debug);
141 | if (settings.PrintCharacterMaps)
142 | {
143 | Print(characterMaps, typeface);
144 | }
145 | Export(settings, characterMaps, typeface, zipArchive);
146 | }
147 | else
148 | {
149 | if (!settings.Quiet)
150 | {
151 | Log($"Failed to load typeface from family name: {fontFamily}");
152 | }
153 | }
154 | }
155 | }
156 | else
157 | {
158 | for (int i = 0; i < paths.Count; i++)
159 | {
160 | var inputPath = paths[i];
161 | using var typeface = SKTypeface.FromFile(inputPath.FullName);
162 | if (typeface != null)
163 | {
164 | var characterMaps = Read(typeface, settings.Debug);
165 | if (settings.PrintCharacterMaps)
166 | {
167 | Print(characterMaps, typeface);
168 | }
169 | Export(settings, characterMaps, typeface);
170 | }
171 | else
172 | {
173 | if (!settings.Quiet)
174 | {
175 | Log($"Failed to load typeface from file: {inputPath.FullName}");
176 | }
177 | }
178 | }
179 |
180 | var fontFamily = settings.FontFamily;
181 | if (!string.IsNullOrEmpty(fontFamily))
182 | {
183 | using var typeface = SKTypeface.FromFamilyName(fontFamily);
184 | if (typeface != null)
185 | {
186 | var characterMaps = Read(typeface, settings.Debug);
187 | if (settings.PrintCharacterMaps)
188 | {
189 | Print(characterMaps, typeface);
190 | }
191 | Export(settings, characterMaps, typeface);
192 | }
193 | else
194 | {
195 | if (!settings.Quiet)
196 | {
197 | Log($"Failed to load typeface from family name: {fontFamily}");
198 | }
199 | }
200 | }
201 | }
202 | }
203 |
204 | static void Print(List characterMaps, SKTypeface typeface)
205 | {
206 | foreach (var characterMap in characterMaps)
207 | {
208 | if (characterMap.CharacterToGlyphMap != null)
209 | {
210 | var characterToGlyphMap = characterMap.CharacterToGlyphMap;
211 | Log($"[charmap] {typeface.FamilyName}, {characterMap.Name} ({characterToGlyphMap.Count})");
212 | Log($"| CharCode | GlyphIndex |");
213 | Log($"|----------|------------|");
214 | foreach (var kvp in characterToGlyphMap)
215 | {
216 | var charCode = kvp.Key;
217 | var glyphIndex = kvp.Value;
218 | Log($"| {charCode,8} | {glyphIndex,10} |");
219 | }
220 | }
221 | }
222 | }
223 |
224 | static void Export(Settings settings, List characterMaps, SKTypeface typeface)
225 | {
226 | foreach (var characterMap in characterMaps)
227 | {
228 | if (characterMap != null && characterMap.CharacterToGlyphMap != null)
229 | {
230 | if (settings.PngExport)
231 | {
232 | if (!settings.Quiet)
233 | {
234 | Log($"[Png] {typeface.FamilyName}, Name: {characterMap.Name}, PlatformID: {TableReader.GetPlatformID(characterMap.PlatformID)}, EncodingID: {TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}");
235 | }
236 | var outputPath = $"charmap_({typeface.FamilyName})_{characterMap.Name}_({TableReader.GetPlatformID(characterMap.PlatformID)}-{TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}).png";
237 | if (settings.OutputDirectory != null && !string.IsNullOrEmpty(settings.OutputDirectory.FullName))
238 | {
239 | outputPath = Path.Combine(settings.OutputDirectory.FullName, outputPath);
240 | }
241 |
242 | using var stream = File.OpenWrite(outputPath);
243 | CharacterMapPngExporter.Save(characterMap.CharacterToGlyphMap, typeface, settings.PngTextSize, settings.PngCellSize, settings.PngColumns, stream);
244 | }
245 |
246 | if (settings.SvgExport)
247 | {
248 | if (!settings.Quiet)
249 | {
250 | Log($"[Svg] {typeface.FamilyName}, Name: {characterMap.Name}, PlatformID: {TableReader.GetPlatformID(characterMap.PlatformID)}, EncodingID: {TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}");
251 | }
252 |
253 | var outputDirectory = "";
254 |
255 | if (settings.OutputDirectory != null && !string.IsNullOrEmpty(settings.OutputDirectory.FullName))
256 | {
257 | outputDirectory = settings.OutputDirectory.FullName;
258 | }
259 |
260 | outputDirectory = Path.Combine(outputDirectory, $"{typeface.FamilyName}_{characterMap.Name}_({TableReader.GetPlatformID(characterMap.PlatformID)}-{TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)})");
261 |
262 | if (!Directory.Exists(outputDirectory))
263 | {
264 | Directory.CreateDirectory(outputDirectory);
265 | }
266 |
267 | CharacterMapSvgExporter.Save(characterMap.CharacterToGlyphMap, typeface, settings.SvgTextSize, settings.SvgPathFill, outputDirectory, null);
268 | }
269 |
270 | if (settings.XamlExport)
271 | {
272 | if (!settings.Quiet)
273 | {
274 | Log($"[Xaml] {typeface.FamilyName}, Name: {characterMap.Name}, PlatformID: {TableReader.GetPlatformID(characterMap.PlatformID)}, EncodingID: {TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}");
275 | }
276 | var outputPath = $"{typeface.FamilyName}_{characterMap.Name}_({TableReader.GetPlatformID(characterMap.PlatformID)}-{TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}).xaml";
277 | if (settings.OutputDirectory != null && !string.IsNullOrEmpty(settings.OutputDirectory.FullName))
278 | {
279 | outputPath = Path.Combine(settings.OutputDirectory.FullName, outputPath);
280 | }
281 | using var streamWriter = File.CreateText(outputPath);
282 | CharacterMapXamlExporter.Save(characterMap.CharacterToGlyphMap, typeface, settings.XamlTextSize, settings.XamlBrush, streamWriter);
283 | }
284 | }
285 | }
286 | }
287 |
288 | static void Export(Settings settings, List characterMaps, SKTypeface typeface, ZipArchive zipArchive)
289 | {
290 | foreach (var characterMap in characterMaps)
291 | {
292 | if (characterMap != null && characterMap.CharacterToGlyphMap != null)
293 | {
294 | if (settings.PngExport)
295 | {
296 | if (!settings.Quiet)
297 | {
298 | Log($"[Png] {typeface.FamilyName}, Name: {characterMap.Name}, PlatformID: {TableReader.GetPlatformID(characterMap.PlatformID)}, EncodingID: {TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}");
299 | }
300 |
301 | var outputPath = $"charmap_({typeface.FamilyName})_{characterMap.Name}_({TableReader.GetPlatformID(characterMap.PlatformID)}-{TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}).png";
302 | var zipArchiveEntry = zipArchive.CreateEntry(outputPath);
303 | using var stream = zipArchiveEntry.Open();
304 |
305 | CharacterMapPngExporter.Save(characterMap.CharacterToGlyphMap, typeface, settings.PngTextSize, settings.PngCellSize, settings.PngColumns, stream);
306 | }
307 |
308 | if (settings.SvgExport)
309 | {
310 | if (!settings.Quiet)
311 | {
312 | Log($"[Svg] {typeface.FamilyName}, Name: {characterMap.Name}, PlatformID: {TableReader.GetPlatformID(characterMap.PlatformID)}, EncodingID: {TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}");
313 | }
314 |
315 | var outputDirectory = $"{typeface.FamilyName}_{characterMap.Name}_({TableReader.GetPlatformID(characterMap.PlatformID)}-{TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)})";
316 |
317 | CharacterMapSvgExporter.Save(characterMap.CharacterToGlyphMap, typeface, settings.SvgTextSize, settings.SvgPathFill, outputDirectory, zipArchive);
318 | }
319 |
320 | if (settings.XamlExport)
321 | {
322 | if (!settings.Quiet)
323 | {
324 | Log($"[Xaml] {typeface.FamilyName}, Name: {characterMap.Name}, PlatformID: {TableReader.GetPlatformID(characterMap.PlatformID)}, EncodingID: {TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}");
325 | }
326 |
327 | var outputPath = $"{typeface.FamilyName}_{characterMap.Name}_({TableReader.GetPlatformID(characterMap.PlatformID)}-{TableReader.GetEncodingID(characterMap.PlatformID, characterMap.EncodingID)}).xaml";
328 | var zipArchiveEntry = zipArchive.CreateEntry(outputPath);
329 | using var streamWriter = new StreamWriter(zipArchiveEntry.Open());
330 |
331 | CharacterMapXamlExporter.Save(characterMap.CharacterToGlyphMap, typeface, settings.XamlTextSize, settings.XamlBrush, streamWriter);
332 | }
333 | }
334 | }
335 | }
336 |
337 | static List Read(SKTypeface typeface, bool debug)
338 | {
339 | var cmap = typeface.GetTableData(TableReader.GetIntTag("cmap"));
340 | var characterMaps = TableReader.ReadCmapTable(cmap, debug);
341 | return characterMaps;
342 | }
343 |
344 | static void PrintFontFamilies()
345 | {
346 | var fontFamilies = SKFontManager.Default.GetFontFamilies();
347 |
348 | Array.Sort(fontFamilies, StringComparer.InvariantCulture);
349 |
350 | foreach (var fontFamily in fontFamilies)
351 | {
352 | Log($"{fontFamily}");
353 | }
354 | }
355 |
356 | static async Task Main(string[] args)
357 | {
358 | // Input
359 |
360 | var optionInputFiles = new Option(new[] { "--inputFiles", "-f" }, "The relative or absolute path to the input files")
361 | {
362 | Argument = new Argument()
363 | };
364 |
365 | var optionInputDirectory = new Option(new[] { "--inputDirectory", "-d" }, "The relative or absolute path to the input directory")
366 | {
367 | Argument = new Argument()
368 | };
369 |
370 | var optionPattern = new Option(new[] { "--pattern", "-p" }, "The search string to match against the names of files in the input directory")
371 | {
372 | Argument = new Argument(getDefaultValue: () => "*.ttf")
373 | };
374 |
375 | var optionFontFamily = new Option(new[] { "--fontFamily" }, "The input font family")
376 | {
377 | Argument = new Argument()
378 | };
379 |
380 | // Output
381 |
382 | var optionOutputDirectory = new Option(new[] { "--outputDirectory", "-o" }, "The relative or absolute path to the output directory")
383 | {
384 | Argument = new Argument()
385 | };
386 |
387 | var optionZip = new Option(new[] { "--zip" }, "Create zip archive from exported files")
388 | {
389 | Argument = new Argument()
390 | };
391 |
392 | var optionZipFile = new Option(new[] { "--zipFile" }, "The relative or absolute path to the zip file")
393 | {
394 | Argument = new Argument(getDefaultValue: () => new FileInfo("export.zip"))
395 | };
396 |
397 | // Info
398 |
399 | var optionPrintFontFamilies = new Option(new[] { "--printFontFamilies" }, "Print available font families")
400 | {
401 | Argument = new Argument()
402 | };
403 |
404 | var optionPrintCharacterMaps = new Option(new[] { "--printCharacterMaps" }, "Print character maps info")
405 | {
406 | Argument = new Argument()
407 | };
408 |
409 | // Png Export
410 |
411 | var optionPngExport = new Option(new[] { "--pngExport", "--png" }, "Export text as Png")
412 | {
413 | Argument = new Argument()
414 | };
415 |
416 | var optionPngTextSize = new Option(new[] { "--pngTextSize" }, "Png text size")
417 | {
418 | Argument = new Argument(getDefaultValue: () => 20f)
419 | };
420 |
421 | var optionPngCellSize = new Option(new[] { "--pngCellSize" }, "Png cell size")
422 | {
423 | Argument = new Argument(getDefaultValue: () => 40)
424 | };
425 |
426 | var optionPngColumns = new Option(new[] { "--pngColumns" }, "Png number of columns")
427 | {
428 | Argument = new Argument(getDefaultValue: () => 20)
429 | };
430 |
431 | // Svg Export
432 |
433 | var optionSvgExport = new Option(new[] { "--svgExport", "--svg" }, "Export text as Svg")
434 | {
435 | Argument = new Argument()
436 | };
437 |
438 | var optionSvgTextSize = new Option(new[] { "--svgTextSize" }, "Svg text size")
439 | {
440 | Argument = new Argument(getDefaultValue: () => 16f)
441 | };
442 |
443 | var optionSvgPathFill = new Option(new[] { "--svgPathFill" }, "Svg path fill")
444 | {
445 | Argument = new Argument(getDefaultValue: () => "black")
446 | };
447 |
448 | // Xaml Export
449 |
450 | var optionXamlExport = new Option(new[] { "--xamlExport", "--xaml" }, "Export text as Xaml")
451 | {
452 | Argument = new Argument()
453 | };
454 |
455 | var optionXamlTextSize = new Option(new[] { "--xamlTextSize" }, "Xaml text size")
456 | {
457 | Argument = new Argument(getDefaultValue: () => 16f)
458 | };
459 |
460 | var optionXamlBrush = new Option(new[] { "--xamlBrush" }, "Xaml brush")
461 | {
462 | Argument = new Argument(getDefaultValue: () => "Black")
463 | };
464 |
465 | // Other
466 |
467 | var optionQuiet = new Option(new[] { "--quiet" }, "Set verbosity level to quiet")
468 | {
469 | Argument = new Argument()
470 | };
471 |
472 | var optionDebug = new Option(new[] { "--debug" }, "Set verbosity level to debug")
473 | {
474 | Argument = new Argument()
475 | };
476 |
477 | var rootCommand = new RootCommand()
478 | {
479 | Description = "An OpenType typeface utilities."
480 | };
481 |
482 | // Input
483 | rootCommand.AddOption(optionInputFiles);
484 | rootCommand.AddOption(optionInputDirectory);
485 | rootCommand.AddOption(optionPattern);
486 | rootCommand.AddOption(optionFontFamily);
487 | // Output
488 | rootCommand.AddOption(optionOutputDirectory);
489 | rootCommand.AddOption(optionZip);
490 | rootCommand.AddOption(optionZipFile);
491 | // Info
492 | rootCommand.AddOption(optionPrintFontFamilies);
493 | rootCommand.AddOption(optionPrintCharacterMaps);
494 | // Png Export
495 | rootCommand.AddOption(optionPngExport);
496 | rootCommand.AddOption(optionPngTextSize);
497 | rootCommand.AddOption(optionPngCellSize);
498 | rootCommand.AddOption(optionPngColumns);
499 | // Svg Export
500 | rootCommand.AddOption(optionSvgExport);
501 | rootCommand.AddOption(optionSvgTextSize);
502 | rootCommand.AddOption(optionSvgPathFill);
503 | // Xaml Export
504 | rootCommand.AddOption(optionXamlExport);
505 | rootCommand.AddOption(optionXamlTextSize);
506 | rootCommand.AddOption(optionXamlBrush);
507 | // Other
508 | rootCommand.AddOption(optionQuiet);
509 | rootCommand.AddOption(optionDebug);
510 |
511 | rootCommand.Handler = CommandHandler.Create((Settings settings) =>
512 | {
513 | try
514 | {
515 | if (settings.PrintFontFamilies)
516 | {
517 | PrintFontFamilies();
518 | }
519 |
520 | Run(settings);
521 | }
522 | catch (Exception ex)
523 | {
524 | if (settings.Quiet == false)
525 | {
526 | Error(ex);
527 | }
528 | }
529 | });
530 |
531 | return await rootCommand.InvokeAsync(args);
532 | }
533 | }
534 |
--------------------------------------------------------------------------------
/src/TypefaceUtil.CLI/TypefaceUtil.CLI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | Exe
6 | preview
7 | False
8 | enable
9 | TypefaceUtil
10 |
11 |
12 |
13 | An OpenType typeface utilities.
14 | TypefaceUtil.CLI
15 | MIT
16 | opentype;ttf;fonts;cmap;unicode;skiasharp;text;converter;png;svg;xaml;avaloniaui;avalonia;typeface;codepoint
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
34 |
35 |
36 | true
37 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/TypefaceUtil.OpenType/BigEndianBinaryReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace TypefaceUtil.OpenType;
5 |
6 | internal class BigEndianBinaryReader : BinaryReader
7 | {
8 | private readonly byte[] _buffer = new byte[8];
9 |
10 | public BigEndianBinaryReader(Stream input)
11 | : base(input)
12 | {
13 | }
14 |
15 | private byte[] ReadBigEndian(int count)
16 | {
17 | Read(_buffer, 0, count);
18 | Array.Reverse(_buffer);
19 | return _buffer;
20 | }
21 |
22 | public override short ReadInt16() => BitConverter.ToInt16(ReadBigEndian(2), 8 - 2);
23 |
24 | public override ushort ReadUInt16() => BitConverter.ToUInt16(ReadBigEndian(2), 8 - 2);
25 |
26 | public override uint ReadUInt32() => BitConverter.ToUInt32(ReadBigEndian(4), 8 - 4);
27 |
28 | public override ulong ReadUInt64() => BitConverter.ToUInt64(ReadBigEndian(8), 8 - 8);
29 |
30 | public override double ReadDouble() => BitConverter.ToDouble(ReadBigEndian(8), 8 - 8);
31 |
32 | public override int ReadInt32() => BitConverter.ToInt32(ReadBigEndian(4), 8 - 4);
33 |
34 | public override int PeekChar() => throw new NotImplementedException();
35 |
36 | public override int Read() => throw new NotImplementedException();
37 |
38 | public override int Read(byte[] buffer, int index, int count) => base.Read(buffer, index, count);
39 |
40 | public override int Read(char[] buffer, int index, int count) => throw new NotImplementedException();
41 |
42 | public override bool ReadBoolean() => throw new NotImplementedException();
43 |
44 | public override char ReadChar() => throw new NotImplementedException();
45 |
46 | public override char[] ReadChars(int count) => throw new NotImplementedException();
47 |
48 | public override decimal ReadDecimal() => throw new NotImplementedException();
49 |
50 | public override long ReadInt64() => throw new NotImplementedException();
51 |
52 | public override sbyte ReadSByte() => throw new NotImplementedException();
53 |
54 | public override float ReadSingle() => throw new NotImplementedException();
55 |
56 | public override string ReadString() => throw new NotImplementedException();
57 | }
--------------------------------------------------------------------------------
/src/TypefaceUtil.OpenType/CharacterMap.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace TypefaceUtil.OpenType;
4 |
5 | public class CharacterMap
6 | {
7 | public string? Name { get; set; }
8 | public ushort PlatformID { get; set; }
9 | public ushort EncodingID { get; set; }
10 | public Dictionary? CharacterToGlyphMap { get; set; }
11 | }
--------------------------------------------------------------------------------
/src/TypefaceUtil.OpenType/EncodingRecord.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace TypefaceUtil.OpenType;
4 |
5 | internal struct EncodingRecord
6 | {
7 | public UInt16 platformID;
8 | public UInt16 encodingID;
9 | public UInt32 offset;
10 | }
--------------------------------------------------------------------------------
/src/TypefaceUtil.OpenType/SequentialMapGroup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace TypefaceUtil.OpenType;
4 |
5 | internal struct SequentialMapGroup
6 | {
7 | public UInt32 startCharCode;
8 | public UInt32 endCharCode;
9 | public UInt32 startGlyphID;
10 | }
--------------------------------------------------------------------------------
/src/TypefaceUtil.OpenType/TableReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 |
5 | namespace TypefaceUtil.OpenType;
6 |
7 | public static class TableReader
8 | {
9 | public static uint GetIntTag(string tag)
10 | {
11 | return (UInt32)(tag[0]) << 24 | (UInt32)(tag[1]) << 16 | (UInt32)(tag[2]) << 08 | (UInt32)(tag[3]) << 00;
12 | }
13 |
14 | public static string GetPlatformID(UInt16 platformID)
15 | {
16 | return platformID switch
17 | {
18 | 0 => "Unicode",
19 | 1 => "Macintosh",
20 | 3 => "Windows",
21 | 4 => "Custom",
22 | _ => "unknown",
23 | };
24 | }
25 |
26 | public static string GetEncodingID(UInt16 platformID, UInt16 encodingID)
27 | {
28 | switch (platformID)
29 | {
30 | case 3:
31 | {
32 | return encodingID switch
33 | {
34 | 0 => "Symbol",
35 | 1 => "Unicode BMP",
36 | 2 => "ShiftJIS",
37 | 3 => "PRC",
38 | 4 => "Big5",
39 | 5 => "Wansung",
40 | 6 => "Johab",
41 | 7 => "Reserved",
42 | 8 => "Reserved",
43 | 9 => "Reserved",
44 | 10 => "Unicode full repertoire",
45 | _ => "unknown",
46 | };
47 | }
48 | default: return "unknown";
49 | }
50 | }
51 |
52 | public static string GetFormat(UInt16 format)
53 | {
54 | return format switch
55 | {
56 | 0 => "Format 0: Byte encoding table",
57 | 2 => "Format 2: High-byte mapping through table",
58 | 4 => "Format 4: Segment mapping to delta values",
59 | 6 => "Format 6: Trimmed table mapping",
60 | 8 => "Format 8: mixed 16-bit and 32-bit coverage",
61 | 10 => "Format 10: Trimmed array",
62 | 12 => "Format 12: Segmented coverage",
63 | 13 => "Format 13: Many-to-one range mappings",
64 | 14 => "Format 14: Unicode Variation Sequences",
65 | _ => "unknown",
66 | };
67 | }
68 |
69 | private static void Log(string message)
70 | {
71 | Console.WriteLine(message);
72 | }
73 |
74 | private static Dictionary ReadCmapFormat4(BigEndianBinaryReader reader, bool debug)
75 | {
76 | var length = reader.ReadUInt16();
77 | var language = reader.ReadUInt16();
78 | var segCountX2 = reader.ReadUInt16(); // 2 × segCount.
79 | var searchRange = reader.ReadUInt16(); // 2 × (2**floor(log2(segCount)))
80 | var entrySelector = reader.ReadUInt16(); // log2(searchRange/2)
81 | var rangeShift = reader.ReadUInt16(); // 2 × segCount - searchRange
82 |
83 | if (debug)
84 | {
85 | Log($"length: {length}");
86 | Log($"language: {language}");
87 | Log($"segCountX2: {segCountX2}");
88 | Log($"searchRange: {searchRange}");
89 | Log($"entrySelector: {entrySelector}");
90 | Log($"rangeShift: {rangeShift}");
91 | }
92 |
93 | var segCount = segCountX2 / 2;
94 |
95 | if (debug)
96 | {
97 | Log($"segCount: {segCount}");
98 | }
99 |
100 | var endCodes = new UInt16[segCount];
101 | var startCodes = new UInt16[segCount];
102 | var idDeltas = new Int16[segCount];
103 | var idRangeOffsets = new UInt16[segCount];
104 |
105 | for (UInt16 j = 0; j < segCount; j++)
106 | {
107 | endCodes[j] = reader.ReadUInt16();
108 | }
109 |
110 | var reservedPad = reader.ReadUInt16();
111 |
112 | for (UInt16 j = 0; j < segCount; j++)
113 | {
114 | startCodes[j] = reader.ReadUInt16();
115 | }
116 |
117 | for (UInt16 j = 0; j < segCount; j++)
118 | {
119 | idDeltas[j] = reader.ReadInt16();
120 | }
121 |
122 | for (UInt16 j = 0; j < segCount; j++)
123 | {
124 | idRangeOffsets[j] = reader.ReadUInt16();
125 | }
126 |
127 | if (debug)
128 | {
129 | Log($"segments:");
130 | Log($"startCode | endCode | idDelta | idRangeOffset");
131 | }
132 |
133 | for (UInt32 j = 0; j < segCount; j++)
134 | {
135 | var startCode = startCodes[j];
136 | var endCode = endCodes[j];
137 | var idDelta = idDeltas[j];
138 | var idRangeOffset = idRangeOffsets[j];
139 | if (debug)
140 | {
141 | Log($"{startCode.ToString().PadRight(9)} | {endCode.ToString().PadRight(7)} | {idDelta.ToString().PadRight(7)} | {idRangeOffset.ToString()}");
142 | }
143 | }
144 |
145 | // header:
146 | // format 2 bytes
147 | // length 2 bytes
148 | // language 2 bytes
149 | // segCountX2 2 bytes
150 | // searchRange 2 bytes
151 | // entrySelector 2 bytes
152 | // rangeShift 2 bytes
153 | // endCodes segCount*2 bytes
154 | // reservedPad 2 bytes
155 | // startCodes segCount*2 bytes
156 | // idDeltas segCount*2 bytes
157 | // idRangeOffsets segCount*2 bytes
158 | var headerLength = (8 * 2) + (4 * segCount * 2);
159 | var glyphIdArrayLength = (length - headerLength) / 2; // length - header
160 | var glyphIdArray = new UInt16[glyphIdArrayLength];
161 |
162 | if (debug)
163 | {
164 | Log($"headerLength: {headerLength}");
165 | Log($"glyphIdArrayLength: {glyphIdArrayLength}");
166 | }
167 |
168 | for (UInt32 j = 0; j < glyphIdArrayLength; j++)
169 | {
170 | glyphIdArray[j] = reader.ReadUInt16();
171 |
172 | if (debug)
173 | {
174 | Log($"glyphIdArray[{j}]: {glyphIdArray[j]}");
175 | }
176 | }
177 |
178 | // mapping of a Unicode code point to a glyph index
179 | var characterToGlyphMap = new Dictionary();
180 |
181 | for (UInt32 j = 0; j < segCount; j++)
182 | {
183 | var startCode = startCodes[j];
184 | var endCode = endCodes[j];
185 | var idDelta = idDeltas[j];
186 | var idRangeOffset = idRangeOffsets[j];
187 |
188 | for (int c = startCode; c <= endCode; c += 1)
189 | {
190 | UInt16 charCode = (UInt16)c;
191 | if (charCode == 0xFFFF)
192 | {
193 | continue;
194 | }
195 |
196 | if (idRangeOffset != 0)
197 | {
198 | int glyphIndexOffset = (idRangeOffset / 2) + (charCode - startCode) - idRangeOffsets.Length + (int)j;
199 | UInt16 glyphIndex = glyphIdArray[glyphIndexOffset];
200 | if (glyphIndex != 0)
201 | {
202 | glyphIndex = (UInt16)((glyphIndex + idDelta) % 0xFFFF);
203 | }
204 | characterToGlyphMap[(int)charCode] = (ushort)glyphIndex;
205 | }
206 | else
207 | {
208 | UInt16 glyphIndex = (UInt16)((charCode + idDelta) % 0xFFFF);
209 | characterToGlyphMap[(int)charCode] = (ushort)glyphIndex;
210 | }
211 | }
212 | }
213 |
214 | return characterToGlyphMap;
215 | }
216 |
217 | private static Dictionary ReadCmapFormat12(BigEndianBinaryReader reader, bool debug)
218 | {
219 | var reserved = reader.ReadUInt16();
220 | var length = reader.ReadUInt32();
221 | var language = reader.ReadUInt32();
222 | var numGroups = reader.ReadUInt32();
223 |
224 | if (debug)
225 | {
226 | Log($"length: {length}");
227 | Log($"language: {language}");
228 | Log($"numGroups: {numGroups}");
229 | }
230 |
231 | var groups = new SequentialMapGroup[numGroups];
232 |
233 | if (debug)
234 | {
235 | Log($"groups:");
236 | Log($"startCharCode | endCharCode | startGlyphID");
237 | }
238 |
239 | for (UInt32 j = 0; j < numGroups; j++)
240 | {
241 | groups[j].startCharCode = reader.ReadUInt32();
242 | groups[j].endCharCode = reader.ReadUInt32();
243 | groups[j].startGlyphID = reader.ReadUInt32();
244 |
245 | if (debug)
246 | {
247 | Log($"{groups[j].startCharCode.ToString().PadRight(13)} | {groups[j].endCharCode.ToString().PadRight(11)} | {groups[j].startGlyphID.ToString()}");
248 | }
249 | }
250 |
251 | // mapping of a Unicode code point to a glyph index
252 | var characterToGlyphMap = new Dictionary();
253 |
254 | for (UInt32 j = 0; j < numGroups; j++)
255 | {
256 | var startCharCode = groups[j].startCharCode;
257 | var endCharCode = groups[j].endCharCode;
258 | var startGlyphID = groups[j].startGlyphID;
259 |
260 | for (UInt32 charCode = groups[j].startCharCode; charCode < groups[j].endCharCode; charCode++)
261 | {
262 | characterToGlyphMap[(int)charCode] = (ushort)startGlyphID;
263 | startGlyphID++;
264 | }
265 | }
266 |
267 | return characterToGlyphMap;
268 | }
269 |
270 | public static List ReadCmapTable(byte[] cmap, bool debug)
271 | {
272 | var characterMaps = new List();
273 |
274 | using var ms = new MemoryStream(cmap);
275 | using var reader = new BigEndianBinaryReader(ms);
276 |
277 | // cmap — Character to Glyph Index Mapping Table
278 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap
279 |
280 | // 'cmap' Header
281 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#cmap-header
282 | var version = reader.ReadUInt16();
283 | var numTables = reader.ReadUInt16();
284 |
285 | if (debug)
286 | {
287 | Log($"version: {version}");
288 | Log($"numTables: {numTables}");
289 | }
290 |
291 | var encodingRecords = new EncodingRecord[numTables];
292 |
293 | // Encoding records and encodings
294 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#encoding-records-and-encodings
295 |
296 | for (UInt16 i = 0; i < numTables; i++)
297 | {
298 | encodingRecords[i].platformID = reader.ReadUInt16();
299 | encodingRecords[i].encodingID = reader.ReadUInt16();
300 | encodingRecords[i].offset = reader.ReadUInt32();
301 | }
302 |
303 | for (UInt16 i = 0; i < numTables; i++)
304 | {
305 | var platformID = encodingRecords[i].platformID;
306 | var encodingID = encodingRecords[i].encodingID;
307 | var offset = encodingRecords[i].offset;
308 |
309 | ms.Position = offset;
310 |
311 | var format = reader.ReadUInt16();
312 |
313 | if (debug)
314 | {
315 | Log($"platformID: {platformID} ({GetPlatformID(platformID)})");
316 | Log($"encodingID: {encodingID} ({GetEncodingID(platformID, encodingID)})");
317 | Log($"offset: {offset}");
318 | Log($"format: {format} ({GetFormat(format)})");
319 | }
320 |
321 | switch (format)
322 | {
323 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-0-byte-encoding-table
324 | case 0:
325 | {
326 | if (debug)
327 | {
328 | Log($"Format {format} is not supported.");
329 | }
330 | // TODO:
331 | }
332 | break;
333 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-2-high-byte-mapping-through-table
334 | case 2:
335 | {
336 | if (debug)
337 | {
338 | Log($"Format {format} is not supported.");
339 | }
340 | // TODO:
341 | }
342 | break;
343 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values
344 | case 4:
345 | {
346 | var characterToGlyphMap = ReadCmapFormat4(reader, debug);
347 |
348 | var characterMap = new CharacterMap()
349 | {
350 | Name = "Format_4",
351 | PlatformID = platformID,
352 | EncodingID = encodingID,
353 | CharacterToGlyphMap = characterToGlyphMap
354 | };
355 |
356 | characterMaps.Add(characterMap);
357 | }
358 | break;
359 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-6-trimmed-table-mapping
360 | case 6:
361 | {
362 | if (debug)
363 | {
364 | Log($"Format {format} is not supported.");
365 | }
366 | // TODO:
367 | }
368 | break;
369 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-8-mixed-16-bit-and-32-bit-coverage
370 | case 8:
371 | {
372 | if (debug)
373 | {
374 | Log($"Format {format} is not supported.");
375 | }
376 | // TODO:
377 | }
378 | break;
379 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-10-trimmed-array
380 | case 10:
381 | {
382 | if (debug)
383 | {
384 | Log($"Format {format} is not supported.");
385 | }
386 | // TODO:
387 | }
388 | break;
389 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-12-segmented-coverage
390 | case 12:
391 | {
392 | var characterToGlyphMap = ReadCmapFormat12(reader, debug);
393 |
394 | var characterMap = new CharacterMap()
395 | {
396 | Name = "Format_12",
397 | PlatformID = platformID,
398 | EncodingID = encodingID,
399 | CharacterToGlyphMap = characterToGlyphMap
400 | };
401 |
402 | characterMaps.Add(characterMap);
403 | }
404 | break;
405 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-13-many-to-one-range-mappings
406 | case 13:
407 | {
408 | if (debug)
409 | {
410 | Log($"Format {format} is not supported.");
411 | }
412 | // TODO:
413 | }
414 | break;
415 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences
416 | case 14:
417 | {
418 | if (debug)
419 | {
420 | Log($"Format {format} is not supported.");
421 | }
422 | // TODO:
423 | }
424 | break;
425 | default:
426 | {
427 | if (debug)
428 | {
429 | Log($"Format {format} is not supported.");
430 | }
431 | }
432 | break;
433 | }
434 | }
435 |
436 | return characterMaps;
437 | }
438 | }
--------------------------------------------------------------------------------
/src/TypefaceUtil.OpenType/TypefaceUtil.OpenType.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net8.0
5 | Library
6 | True
7 | enable
8 |
9 |
10 |
11 | An OpenType typeface utilities.
12 | TypefaceUtil.OpenType
13 | MIT
14 | opentype;ttf;fonts;cmap;unicode;skiasharp;text;converter;png;svg;xaml;avaloniaui;avalonia;typeface;codepoint
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/App.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 | using TypefaceUtil.Avalonia.ViewModels;
5 | using TypefaceUtil.Avalonia.Views;
6 |
7 | namespace TypefaceUtil.Avalonia;
8 |
9 | public class App : Application
10 | {
11 | public override void Initialize()
12 | {
13 | AvaloniaXamlLoader.Load(this);
14 | }
15 |
16 | public override void OnFrameworkInitializationCompleted()
17 | {
18 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
19 | {
20 | desktop.MainWindow = new MainWindow { DataContext = new MainWindowViewModel() };
21 | }
22 | else if (ApplicationLifetime is ISingleViewApplicationLifetime single)
23 | {
24 | single.MainView = new MainView { DataContext = new MainWindowViewModel() };
25 | }
26 |
27 | base.OnFrameworkInitializationCompleted();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/Assets/Icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wieslawsoltes/TypefaceUtil/10b646285e7d26e55e63e6f33d4de1fac870c36d/src/TypefaceUtilAvalonia.Base/Assets/Icon.ico
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/TypefaceUtilAvalonia.Base.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Library
4 | net8.0
5 | False
6 | enable
7 | TypefaceUtil.Avalonia
8 |
9 |
10 |
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | An OpenType typeface utilities.
20 | TypefaceUtilAvalonia.Base
21 | MIT
22 | opentype;ttf;fonts;cmap;unicode;skiasharp;text;converter;png;svg;xaml;avaloniaui;avalonia;typeface;codepoint
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | all
45 | runtime; build; native; contentfiles; analyzers; buildtransitive
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/ViewLocator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Templates;
4 | using TypefaceUtil.Avalonia.ViewModels;
5 |
6 | namespace TypefaceUtil.Avalonia;
7 |
8 | public class ViewLocator : IDataTemplate
9 | {
10 | public Control Build(object? data)
11 | {
12 | var name = data?.GetType().FullName?.Replace("ViewModel", "View");
13 | var type = name is null ? null : Type.GetType(name);
14 |
15 | if (type != null)
16 | {
17 | return (Control)Activator.CreateInstance(type)!;
18 | }
19 | else
20 | {
21 | return new TextBlock { Text = "Not Found: " + name };
22 | }
23 | }
24 |
25 | public bool Match(object? data)
26 | {
27 | return data is ViewModelBase;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/ViewModels/GlyphViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Threading.Tasks;
4 | using System.Windows.Input;
5 | using Avalonia;
6 | using Avalonia.Controls.ApplicationLifetimes;
7 | using ReactiveUI;
8 | using SkiaSharp;
9 |
10 | namespace TypefaceUtil.Avalonia.ViewModels;
11 |
12 | public partial class GlyphViewModel : ViewModelBase
13 | {
14 | [Reactive]
15 | public partial int CharCode { get; set; }
16 |
17 | [Reactive]
18 | public partial ushort GlyphIndex { get; set; }
19 |
20 | [Reactive]
21 | public partial SKPath? Path { get; set; }
22 |
23 | [Reactive]
24 | public partial SKPaint? Paint { get; set; }
25 |
26 | [Reactive]
27 | public partial string? Color { get; set; }
28 |
29 | [Reactive]
30 | public partial string? SvgPathData { get; set; }
31 |
32 | public ICommand CopyAsCommand { get; }
33 |
34 | public ICommand OpenCommand { get; }
35 |
36 | public ICommand CloseCommand { get; }
37 |
38 | public GlyphViewModel(Action onOpen, Action onClose)
39 | {
40 | CopyAsCommand = ReactiveCommand.CreateFromTask(async (format) =>
41 | {
42 | await CopyAs(format, _color ?? "#000000");
43 | });
44 |
45 | OpenCommand = ReactiveCommand.Create(() => onOpen.Invoke(this));
46 |
47 | CloseCommand = ReactiveCommand.Create(() => onClose.Invoke(this));
48 | }
49 |
50 | public string? Export(string format, string color, bool addXamlKey)
51 | {
52 | if (Path is null || Path.Bounds.IsEmpty)
53 | {
54 | return default;
55 | }
56 |
57 | var indent = " ";
58 | var xamlKey = addXamlKey ? $" x:Key=\"{CharCode}\"" : "";
59 | var text = format switch
60 | {
61 | "XamlStreamGeometry" => $"{SvgPathData}",
62 | "XamlPathIcon" => $"",
63 | "XamlPath" => $"",
64 | "XamlCanvas" => $"",
65 | "XamlGeometryDrawing" => $"",
66 | "XamlDrawingGroup" => $"\r\n{indent}\r\n",
67 | "XamlDrawingImage" => $"\r\n{indent}\r\n",
68 | "XamlImage" => $"\r\n{indent}\r\n{indent}{indent}\r\n\r\n",
69 | "SvgPathData" => $"{SvgPathData}",
70 | "SvgPath" => $"",
71 | "Svg" => $"",
72 | _ => default
73 | };
74 | return text;
75 | }
76 |
77 | public async Task CopyAs(string format, string color)
78 | {
79 | var text = Export(format, color, false);
80 | if (!string.IsNullOrWhiteSpace(text))
81 | {
82 | try
83 | {
84 | if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow.Clipboard: { } clipboard } && text is { })
85 | {
86 | await clipboard.SetTextAsync(text);
87 | }
88 | }
89 | catch
90 | {
91 | // ignored
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/ViewModels/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows.Input;
10 | using Avalonia;
11 | using Avalonia.Controls.ApplicationLifetimes;
12 | using Avalonia.Platform.Storage;
13 | using ReactiveUI;
14 | using SkiaSharp;
15 | using TypefaceUtil.OpenType;
16 |
17 | namespace TypefaceUtil.Avalonia.ViewModels;
18 |
19 | public partial class MainWindowViewModel : ViewModelBase
20 | {
21 | [Reactive]
22 | public partial bool IsLoading { get; set; }
23 |
24 | [Reactive]
25 | public partial string? InputFile { get; set; }
26 |
27 | [Reactive]
28 | public partial MemoryStream? InputData { get; set; }
29 |
30 | [Reactive]
31 | public partial string? FamilyName { get; set; }
32 |
33 | [Reactive]
34 | public partial ObservableCollection? FontFamilies { get; set; }
35 |
36 | [Reactive]
37 | public partial float FontSize { get; set; }
38 |
39 | [Reactive]
40 | public partial string? Color { get; set; }
41 |
42 | [Reactive]
43 | public partial double ItemWidth { get; set; }
44 |
45 | [Reactive]
46 | public partial double ItemHeight { get; set; }
47 |
48 | [Reactive]
49 | public partial TypefaceViewModel? Typeface { get; set; }
50 |
51 | [Reactive]
52 | public partial GlyphViewModel? SelectedGlyph { get; set; }
53 |
54 | public ICommand InputFileCommand { get; }
55 |
56 | public ICommand LoadInputFileCommand { get; }
57 |
58 | public ICommand LoadFamilyNameCommand { get; }
59 |
60 | public ICommand CloseCommand { get; }
61 |
62 | public ICommand CopyAsCommand { get; }
63 |
64 | public MainWindowViewModel()
65 | {
66 | FontFamilies = new ObservableCollection(SetGetFontFamilies());
67 | FamilyName = "Segoe MDL2 Assets";
68 |
69 | FontSize = 32f;
70 | Color = "#000000";
71 |
72 | ItemWidth = 96;
73 | ItemHeight = 96;
74 |
75 | InputFileCommand = ReactiveCommand.CreateFromTask(async () =>
76 | {
77 | var storageProvider = StorageService.GetStorageProvider();
78 | if (storageProvider is null)
79 | {
80 | return;
81 | }
82 |
83 | var result = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
84 | {
85 | Title = "Open drawing",
86 | FileTypeFilter = GetOpenFileTypes(),
87 | AllowMultiple = false
88 | });
89 |
90 | var file = result.FirstOrDefault();
91 |
92 | if (file is not null)
93 | {
94 | try
95 | {
96 | InputData?.Dispose();
97 | InputFile = "";
98 |
99 | InputData = await Task.Run(async () =>
100 | {
101 | await using var stream = await file.OpenReadAsync();
102 | var ms = new MemoryStream();
103 | await stream.CopyToAsync(ms);
104 | ms.Position = 0;
105 | return ms;
106 | });
107 | InputFile = file.Name;
108 | }
109 | catch (Exception ex)
110 | {
111 | Debug.WriteLine(ex.Message);
112 | Debug.WriteLine(ex.StackTrace);
113 | }
114 | }
115 | });
116 |
117 | LoadInputFileCommand = ReactiveCommand.CreateFromTask(async () =>
118 | {
119 | IsLoading = true;
120 | await Task.Run(LoadInputFile);
121 | IsLoading = false;
122 | });
123 |
124 | LoadFamilyNameCommand = ReactiveCommand.CreateFromTask(async () =>
125 | {
126 | IsLoading = true;
127 | await Task.Run(LoadFamilyName);
128 | IsLoading = false;
129 | });
130 |
131 | CloseCommand = ReactiveCommand.Create(() =>
132 | {
133 | SelectedGlyph = null;
134 | Typeface = null;
135 | });
136 |
137 | CopyAsCommand = ReactiveCommand.CreateFromTask(async (format) =>
138 | {
139 | if (Typeface?.Glyphs is { })
140 | {
141 | try
142 | {
143 | var allText = await Task.Run(() =>
144 | {
145 | var sb = new StringBuilder();
146 |
147 | // sb.AppendLine($"");
148 | // sb.AppendLine($" ");
162 | // sb.AppendLine($"");
163 |
164 | return sb.ToString();
165 | });
166 |
167 | if (!string.IsNullOrWhiteSpace(allText))
168 | {
169 | if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow.Clipboard: { } clipboard })
170 | {
171 | await clipboard.SetTextAsync(allText);
172 | }
173 | }
174 | }
175 | catch
176 | {
177 | // ignored
178 | }
179 | }
180 | });
181 | }
182 |
183 | private List GetOpenFileTypes()
184 | {
185 | // TODO:
186 | // "ttf", "otf" : "Font Files"
187 | // "ttf" : "TTF Files"
188 | // "otf" : "OTF Files"
189 | return new List
190 | {
191 | StorageService.All
192 | };
193 | }
194 |
195 | private string[] SetGetFontFamilies()
196 | {
197 | var fontFamilies = SKFontManager.Default.GetFontFamilies();
198 |
199 | Array.Sort(fontFamilies, StringComparer.InvariantCulture);
200 |
201 | return fontFamilies;
202 | }
203 |
204 | private void LoadInputFile()
205 | {
206 | var inputFile = InputFile;
207 | var inputData = InputData;
208 | var fontSize = FontSize;
209 | var color = Color ?? "#000000";
210 | if (string.IsNullOrEmpty(inputFile))
211 | {
212 | return;
213 | }
214 |
215 | var typefaceViewModel = LoadFromData(inputData);
216 | if (typefaceViewModel?.Typeface is null)
217 | {
218 | return;
219 | }
220 |
221 | if (typefaceViewModel.Glyphs is { })
222 | {
223 | foreach (var glyph in LoadGlyphs(typefaceViewModel, fontSize, color).OrderBy(x => x.GlyphIndex))
224 | {
225 | typefaceViewModel.Glyphs.Add(glyph);
226 | }
227 | }
228 |
229 | Typeface = typefaceViewModel;
230 | }
231 |
232 | private void LoadFamilyName()
233 | {
234 | var familyName = FamilyName;
235 | var fontSize = FontSize;
236 | var color = Color ?? "#000000";
237 | if (string.IsNullOrEmpty(familyName))
238 | {
239 | return;
240 | }
241 |
242 | var typefaceViewModel = LoadFromFamilyName(familyName);
243 | if (typefaceViewModel?.Typeface is null)
244 | {
245 | return;
246 | }
247 |
248 | if (typefaceViewModel.Glyphs is { })
249 | {
250 | foreach (var glyph in LoadGlyphs(typefaceViewModel, fontSize, color).OrderBy(x => x.GlyphIndex))
251 | {
252 | typefaceViewModel.Glyphs.Add(glyph);
253 | }
254 | }
255 |
256 | Typeface = typefaceViewModel;
257 | }
258 |
259 | private IEnumerable LoadGlyphs(TypefaceViewModel? typefaceViewModel, float fontSize, string color)
260 | {
261 | if (typefaceViewModel?.Typeface is null
262 | || typefaceViewModel.CharacterMaps is null
263 | || typefaceViewModel.Glyphs is null)
264 | {
265 | yield break;
266 | }
267 |
268 | var skFont = typefaceViewModel.Typeface.ToFont(fontSize);
269 |
270 | foreach (var characterMap in typefaceViewModel.CharacterMaps)
271 | {
272 | if (characterMap.CharacterToGlyphMap != null)
273 | {
274 | var characterToGlyphMap = characterMap.CharacterToGlyphMap;
275 |
276 | foreach (var kvp in characterToGlyphMap)
277 | {
278 | var charCode = kvp.Key;
279 | var glyphIndex = kvp.Value;
280 | var skPath = skFont.GetGlyphPath(glyphIndex);
281 | if (skPath is null)
282 | {
283 | continue;
284 | }
285 |
286 | var skTranslationMatrix = SKMatrix.CreateTranslation(-skPath.Bounds.Left, -skPath.Bounds.Top);
287 | skPath.Transform(skTranslationMatrix);
288 |
289 | var svgPathData = skPath.ToSvgPathData();
290 |
291 | var glyph = new GlyphViewModel(OpenGlyphDetail, CloseGlyphDetail)
292 | {
293 | CharCode = charCode,
294 | GlyphIndex = glyphIndex,
295 | Path = skPath,
296 | Paint = new SKPaint
297 | {
298 | IsAntialias = true,
299 | Color = SKColor.Parse(color),
300 | Style = SKPaintStyle.Fill
301 | },
302 | Color = color,
303 | SvgPathData = svgPathData
304 | };
305 |
306 | yield return glyph;
307 | }
308 | }
309 | }
310 | }
311 |
312 | private void OpenGlyphDetail(GlyphViewModel glyphViewModel)
313 | {
314 | SelectedGlyph = glyphViewModel;
315 | }
316 |
317 | private void CloseGlyphDetail(GlyphViewModel glyphViewModel)
318 | {
319 | SelectedGlyph = null;
320 | }
321 |
322 | private static List Read(SKTypeface typeface)
323 | {
324 | var cmap = typeface.GetTableData(TableReader.GetIntTag("cmap"));
325 | var characterMaps = TableReader.ReadCmapTable(cmap, false);
326 | return characterMaps;
327 | }
328 |
329 | private TypefaceViewModel? LoadFromFamilyName(string fontFamily)
330 | {
331 | if (!string.IsNullOrEmpty(fontFamily))
332 | {
333 | using var typeface = SKTypeface.FromFamilyName(fontFamily);
334 | if (typeface != null)
335 | {
336 | var characterMaps = Read(typeface);
337 | return new TypefaceViewModel()
338 | {
339 | Typeface = typeface,
340 | CharacterMaps = characterMaps,
341 | Glyphs = new ObservableCollection()
342 | };
343 | }
344 | }
345 |
346 | return null;
347 | }
348 |
349 | private TypefaceViewModel? LoadFromData(Stream? stream)
350 | {
351 | if (stream is { })
352 | {
353 | using var typeface = SKTypeface.FromStream(stream);
354 | if (typeface != null)
355 | {
356 | var characterMaps = Read(typeface);
357 | return new TypefaceViewModel()
358 | {
359 | Typeface = typeface,
360 | CharacterMaps = characterMaps,
361 | Glyphs = new ObservableCollection()
362 | };
363 | }
364 | }
365 |
366 | return null;
367 | }
368 | }
369 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/ViewModels/StorageService.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.ApplicationLifetimes;
4 | using Avalonia.Platform.Storage;
5 | using Avalonia.VisualTree;
6 |
7 | namespace TypefaceUtil.Avalonia.ViewModels;
8 |
9 | internal static class StorageService
10 | {
11 | public static FilePickerFileType All { get; } = new("All")
12 | {
13 | Patterns = new[] { "*.*" },
14 | MimeTypes = new[] { "*/*" }
15 | };
16 |
17 | public static IStorageProvider? GetStorageProvider()
18 | {
19 | if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } window })
20 | {
21 | return window.StorageProvider;
22 | }
23 |
24 | if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime { MainView: { } mainView })
25 | {
26 | var visualRoot = mainView.GetVisualRoot();
27 | if (visualRoot is TopLevel topLevel)
28 | {
29 | return topLevel.StorageProvider;
30 | }
31 | }
32 |
33 | return null;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/ViewModels/TypefaceViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.ObjectModel;
3 | using ReactiveUI;
4 | using SkiaSharp;
5 | using TypefaceUtil.OpenType;
6 |
7 | namespace TypefaceUtil.Avalonia.ViewModels;
8 |
9 | public partial class TypefaceViewModel : ViewModelBase
10 | {
11 | [Reactive]
12 | public partial SKTypeface? Typeface { get; set; }
13 |
14 | [Reactive]
15 | public partial List? CharacterMaps { get; set; }
16 |
17 | [Reactive]
18 | public partial ObservableCollection? Glyphs { get; set; }
19 | }
20 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 |
3 | namespace TypefaceUtil.Avalonia.ViewModels;
4 |
5 | public class ViewModelBase : ReactiveObject;
6 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/Views/MainView.axaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
25 |
29 |
33 |
34 |
43 |
47 |
51 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
71 |
72 |
76 |
81 |
82 |
83 |
87 |
92 |
93 |
94 |
98 |
103 |
104 |
105 |
109 |
113 |
114 |
115 |
119 |
126 |
132 |
137 |
138 |
139 |
143 |
150 |
158 |
162 |
163 |
164 |
165 |
169 |
170 |
178 |
200 |
201 |
202 |
204 |
205 |
206 |
212 |
213 |
214 |
215 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
259 |
265 |
266 |
267 |
275 |
297 |
298 |
299 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/Views/MainView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Input;
4 | using Avalonia.Markup.Xaml;
5 | using Avalonia.Media;
6 |
7 | namespace TypefaceUtil.Avalonia.Views;
8 |
9 | public partial class MainView : UserControl
10 | {
11 | private bool _isDragging;
12 | private Control? _dragControl;
13 | private Point _startPoint;
14 | private TranslateTransform? _dragTransform;
15 |
16 | public MainView()
17 | {
18 | InitializeComponent();
19 | }
20 |
21 | private void InitializeComponent()
22 | {
23 | AvaloniaXamlLoader.Load(this);
24 | }
25 |
26 | private void GlyphView_OnPointerPressed(object? sender, PointerPressedEventArgs e)
27 | {
28 | _dragControl = sender as Control;
29 | if (_dragControl is { })
30 | {
31 | if (_dragTransform is null)
32 | {
33 | _dragTransform = new TranslateTransform();
34 | _dragControl.RenderTransform = _dragTransform;
35 | }
36 | _startPoint = e.GetPosition(this) - new Point(_dragTransform.X, _dragTransform.Y);
37 | _isDragging = true;
38 | }
39 | }
40 |
41 | private void GlyphView_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
42 | {
43 | if (_isDragging && _dragControl is { })
44 | {
45 | _isDragging = false;
46 | _dragControl = null;
47 | }
48 | }
49 |
50 | private void GlyphView_OnPointerMoved(object? sender, PointerEventArgs e)
51 | {
52 | if (_isDragging && _dragControl is { } && _dragTransform is { })
53 | {
54 | var currentPoint = e.GetPosition(this);
55 | var delta = currentPoint - _startPoint;
56 | _dragTransform.X = delta.X;
57 | _dragTransform.Y = delta.Y;
58 | }
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/Views/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace TypefaceUtil.Avalonia.Views;
6 |
7 | public partial class MainWindow : Window
8 | {
9 | public MainWindow()
10 | {
11 | InitializeComponent();
12 | #if DEBUG
13 | this.AttachDevTools();
14 | #endif
15 | }
16 |
17 | private void InitializeComponent()
18 | {
19 | AvaloniaXamlLoader.Load(this);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/Views/SandBoxView.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Base/Views/SandBoxView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace TypefaceUtil.Avalonia.Views;
6 |
7 | public partial class SandBoxView : UserControl
8 | {
9 | public SandBoxView()
10 | {
11 | InitializeComponent();
12 | }
13 |
14 | private void InitializeComponent()
15 | {
16 | AvaloniaXamlLoader.Load(this);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Desktop/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia;
3 | using Avalonia.Controls.ApplicationLifetimes;
4 | using Avalonia.ReactiveUI;
5 |
6 | namespace TypefaceUtil.Avalonia;
7 |
8 | class Program
9 | {
10 | // Initialization code. Don't use any Avalonia, third-party APIs or any
11 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
12 | // yet and stuff might break.
13 | public static void Main(string[] args) => BuildAvaloniaApp()
14 | .StartWithClassicDesktopLifetime(args);
15 |
16 | // Avalonia configuration, don't remove; also used by visual designer.
17 | public static AppBuilder BuildAvaloniaApp()
18 | => AppBuilder.Configure()
19 | .UsePlatformDetect()
20 | .LogToTrace()
21 | .UseReactiveUI();
22 | }
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Desktop/TypefaceUtilAvalonia.Desktop.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net9.0
5 | False
6 | enable
7 | ..\TypefaceUtilAvalonia.Base\Assets\Icon.ico
8 | TypefaceUtil.Avalonia
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | False
17 | False
18 | True
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/AppBundle/Logo.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/AppBundle/app.css:
--------------------------------------------------------------------------------
1 | .highlight {
2 | color: white;
3 | font-size: 2.5rem;
4 | display: block;
5 | }
6 |
7 | .purple {
8 | color: #8b44ac;
9 | }
10 |
11 | .icon {
12 | opacity : 0.05;
13 | height: 35%;
14 | width: 35%;
15 | position : absolute;
16 | background-repeat: no-repeat;
17 | right: 0px;
18 | bottom: 0px;
19 | margin-right: 3%;
20 | margin-bottom: 5%;
21 | z-index: 5000;
22 | background-position: right bottom;
23 | pointer-events: none;
24 | }
25 |
26 | #avalonia-splash a{
27 | color: whitesmoke;
28 | text-decoration: none;
29 | }
30 |
31 | .center {
32 | display: flex;
33 | justify-content: center;
34 | align-items: center;
35 | height: 100vh;
36 | }
37 |
38 | #avalonia-splash {
39 | position: relative;
40 | height: 100%;
41 | width: 100%;
42 | color: whitesmoke;
43 | background: #1b2a4e;
44 | font-family: 'Nunito', sans-serif;
45 | background-position: center;
46 | background-size: cover;
47 | background-repeat: no-repeat;
48 | justify-content: center;
49 | align-items: center;
50 | }
51 |
52 | .splash-close {
53 | animation: fadeout 0.25s linear forwards;
54 | }
55 |
56 | @keyframes fadeout {
57 | 0% {
58 | opacity:100%;
59 | }
60 | 100% {
61 | opacity:0;
62 | visibility: collapse;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/AppBundle/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wieslawsoltes/TypefaceUtil/10b646285e7d26e55e63e6f33d4de1fac870c36d/src/TypefaceUtilAvalonia.Web/AppBundle/favicon.ico
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/AppBundle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TypefaceUtil
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/AppBundle/main.js:
--------------------------------------------------------------------------------
1 | import { dotnet } from './dotnet.js'
2 |
3 | const is_browser = typeof window != "undefined";
4 | if (!is_browser) throw new Error(`Expected to be running in a browser`);
5 |
6 | const dotnetRuntime = await dotnet
7 | .withDiagnosticTracing(false)
8 | .withApplicationArgumentsFromQuery()
9 | .create();
10 |
11 | const config = dotnetRuntime.getConfig();
12 |
13 | await dotnetRuntime.runMainAndExit(config.mainAssemblyName, [window.location.search]);
14 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/AppBundle/staticwebapp.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [
3 | {
4 | "route": "/*",
5 | "headers": {
6 | "Cache-Control": "public, must-revalidate, max-age=2419200"
7 | }
8 | }
9 | ]
10 | }
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Versioning;
2 | using System.Threading.Tasks;
3 | using Avalonia;
4 | using Avalonia.Browser;
5 | using TypefaceUtil.Avalonia;
6 |
7 | [assembly:SupportedOSPlatform("browser")]
8 |
9 | internal class Program
10 | {
11 | private static async Task Main(string[] args)
12 | => await BuildAvaloniaApp().StartBrowserAppAsync("out");
13 |
14 | public static AppBuilder BuildAvaloniaApp()
15 | => AppBuilder
16 | .Configure();
17 | }
18 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/Roots.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/TypefaceUtilAvalonia.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0-browser
5 | browser-wasm
6 | AppBundle\main.js
7 | Exe
8 | true
9 | true
10 | False
11 | TypefaceUtil.Avalonia
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/TypefaceUtilAvalonia.Web/runtimeconfig.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "wasmHostProperties": {
3 | "perHostConfig": [
4 | {
5 | "name": "browser",
6 | "html-path": "index.html",
7 | "Host": "browser"
8 | }
9 | ]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/UnicodeDataGenerator/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Text;
6 |
7 | namespace UnicodeDataGenerator;
8 |
9 | class Program
10 | {
11 | static void Main(string[] args)
12 | {
13 | // http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
14 | // https://www.unicode.org/reports/tr44/#UnicodeData.txt
15 | var unicodeData = File.ReadAllLines("UnicodeData.txt", Encoding.UTF8);
16 | var charCodeNameMap = new Dictionary(65536);
17 |
18 | for (int i = 0; i < unicodeData.Length; i++)
19 | {
20 | var fields = unicodeData[i].Split(';');
21 | var charCode = int.Parse(fields[0], NumberStyles.HexNumber);
22 | var charName = fields[1];
23 |
24 | if (charCode >= 0 && charCode <= 0xFFFF)
25 | {
26 | if (charName.EndsWith(", First>"))
27 | {
28 | charName = charName.Replace(", First", String.Empty);
29 | fields = unicodeData[++i].Split(';');
30 | int endCharCode = int.Parse(fields[0], NumberStyles.HexNumber);
31 |
32 | if (!fields[1].EndsWith(", Last>"))
33 | {
34 | throw new Exception("Expected end-of-range indicator.");
35 | }
36 |
37 | for (int charCodeInRange = charCode; charCodeInRange <= endCharCode; charCodeInRange++)
38 | {
39 | charCodeNameMap.Add(charCodeInRange, charName);
40 | }
41 | }
42 | else
43 | {
44 | charCodeNameMap.Add(charCode, charName);
45 | }
46 | }
47 | }
48 |
49 | var sb = new StringBuilder();
50 |
51 | sb.AppendLine("using System.Collections.Generic;");
52 | sb.AppendLine("");
53 | sb.AppendLine("namespace TypefaceUtil.OpenType");
54 | sb.AppendLine("{");
55 | sb.AppendLine(" public static class UnicodeData");
56 | sb.AppendLine(" {");
57 | sb.AppendLine(" public static Dictionary CharCodeNameMap = new Dictionary");
58 | sb.AppendLine(" {");
59 |
60 | foreach (var kvp in charCodeNameMap)
61 | {
62 | sb.AppendLine($" [0x{kvp.Key:X2}] = \"{kvp.Value}\",");
63 | }
64 | sb.AppendLine(" };");
65 | sb.AppendLine(" }");
66 | sb.AppendLine("}");
67 |
68 | File.WriteAllText(@"..\..\..\..\src\TypefaceUtil.OpenType\UnicodeData.cs", sb.ToString());
69 | }
70 | }
--------------------------------------------------------------------------------
/src/UnicodeDataGenerator/UnicodeDataGenerator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | False
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/TypefaceUtil.OpenType.UnitTests/TypefaceUtil.OpenType.UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | Library
6 | False
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------