├── .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 | [![Build Status](https://dev.azure.com/wieslawsoltes/GitHub/_apis/build/status/wieslawsoltes.TypefaceUtil?branchName=master)](https://dev.azure.com/wieslawsoltes/GitHub/_build/latest?definitionId=95&branchName=master) 4 | [![CI](https://github.com/wieslawsoltes/TypefaceUtil/actions/workflows/build.yml/badge.svg)](https://github.com/wieslawsoltes/TypefaceUtil/actions/workflows/build.yml) 5 | 6 | [![NuGet](https://img.shields.io/nuget/v/TypefaceUtil.OpenType.svg)](https://www.nuget.org/packages/TypefaceUtil.OpenType) 7 | [![NuGet](https://img.shields.io/nuget/dt/TypefaceUtil.OpenType.svg)](https://www.nuget.org/packages/TypefaceUtil.OpenType) 8 | 9 | [![GitHub release](https://img.shields.io/github/release/wieslawsoltes/TypefaceUtil.svg)](https://github.com/wieslawsoltes/TypefaceUtil) 10 | [![Github All Releases](https://img.shields.io/github/downloads/wieslawsoltes/TypefaceUtil/total.svg)](https://github.com/wieslawsoltes/TypefaceUtil) 11 | [![Github Releases](https://img.shields.io/github/downloads/wieslawsoltes/TypefaceUtil/latest/total.svg)](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 | ![4dsZT5hb3a](https://user-images.githubusercontent.com/2297442/126555807-1acf614d-a44e-40fc-bd60-5b0d6df4a10d.jpg) 22 | 23 | ![mehzNi5G2T](https://user-images.githubusercontent.com/2297442/126555801-c63a3c0a-1092-451b-9c79-a964f4d7dfe7.jpg) 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($""); // width=\"{bounds.Width}\" height=\"{bounds.Height}\" 50 | sb.AppendLine($" "); 51 | 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" => $"\r\n{indent}\r\n", 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" => $"\r\n{indent}\r\n", 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 | 82 | 83 | 93 | 94 | 104 | 105 | 114 | 115 | 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 | 2 | 3 | 4 | 5 | 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 |
20 |
21 |
22 |

23 | Powered by 24 | Avalonia UI

25 |
26 |
27 |
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 | --------------------------------------------------------------------------------