├── .appveyor.yml ├── .clang-format ├── .gitignore ├── COPYING ├── FontLoaderSub.sln ├── FontLoaderSub ├── FontLoaderSub.vcxproj ├── FontLoaderSub.vcxproj.filters ├── ass_parser.c ├── ass_parser.h ├── ass_string.c ├── ass_string.h ├── cstl.c ├── cstl.h ├── diff_libass.txt ├── dwrite_c.h ├── exporter.c ├── exporter.h ├── font_loader.c ├── font_loader.h ├── font_set.c ├── font_set.h ├── main.c ├── main.h ├── mock_config.h ├── path.c ├── path.h ├── res │ ├── app.manifest │ ├── res.rc │ ├── res_en-us.rc │ ├── res_zh-cn.rc │ ├── res_zh-tw.rc │ └── resource.h ├── shortcut.c ├── shortcut.h ├── test.c ├── tim_sort.c ├── tim_sort.h ├── ttf_parser.c ├── ttf_parser.h ├── util.c └── util.h └── README.md /.appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2019 2 | 3 | install: 4 | - ps: | 5 | $env:COMM_TAG = $(git describe --always --dirty="(X)") 6 | $env:PROJECT = "FontLoaderSub" 7 | $env:CMDLINE_DEFINE = "FONTLOADERSUB_GIT_VERSION=\""{0}\""" -f $env:COMM_TAG 8 | 9 | build_script: 10 | - msbuild /p:Platform=x86 /p:Configuration=Minimize 11 | 12 | after_build: 13 | - ps: | 14 | $CONFIGS = @( 15 | "Minimize"; 16 | ) 17 | foreach ($conf in $CONFIGS) { 18 | 7z a "$env:PROJECT-$env:COMM_TAG-$conf.zip" "$conf\" "-xr!*.iobj" "-xr!*.ipdb" 19 | } 20 | 21 | Get-ChildItem . -Recurse | where {$_.Extension -eq ".exe"} | Get-FileHash -Algorithm SHA256 22 | 23 | artifacts: 24 | - path: '*.zip' 25 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | PointerAlignment: Right 3 | IndentCaseLabels: false 4 | SortIncludes: false 5 | AlignAfterOpenBracket: AlwaysBreak 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Mm]inimize/ 19 | [Xx]64/ 20 | [Xx]86/ 21 | [Bb]uild/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | 144 | # TODO: Un-comment the next line if you do not want to checkin 145 | # your web deploy settings because they may include unencrypted 146 | # passwords 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # NuGet Packages 151 | *.nupkg 152 | # The packages folder can be ignored because of Package Restore 153 | **/packages/* 154 | # except build/, which is used as an MSBuild target. 155 | !**/packages/build/ 156 | # Uncomment if necessary however generally it will be regenerated when needed 157 | #!**/packages/repositories.config 158 | # NuGet v3's project.json files produces more ignoreable files 159 | *.nuget.props 160 | *.nuget.targets 161 | 162 | # Microsoft Azure Build Output 163 | csx/ 164 | *.build.csdef 165 | 166 | # Microsoft Azure Emulator 167 | ecf/ 168 | rcf/ 169 | 170 | # Windows Store app package directory 171 | AppPackages/ 172 | BundleArtifacts/ 173 | 174 | # Visual Studio cache files 175 | # files ending in .cache can be ignored 176 | *.[Cc]ache 177 | # but keep track of directories ending in .cache 178 | !*.[Cc]ache/ 179 | 180 | # Others 181 | ClientBin/ 182 | [Ss]tyle[Cc]op.* 183 | ~$* 184 | *~ 185 | *.dbmdl 186 | *.dbproj.schemaview 187 | *.pfx 188 | *.publishsettings 189 | node_modules/ 190 | orleans.codegen.cs 191 | 192 | # RIA/Silverlight projects 193 | Generated_Code/ 194 | 195 | # Backup & report files from converting an old project file 196 | # to a newer Visual Studio version. Backup files are not needed, 197 | # because we have git ;-) 198 | _UpgradeReport_Files/ 199 | Backup*/ 200 | UpgradeLog*.XML 201 | UpgradeLog*.htm 202 | 203 | # SQL Server files 204 | *.mdf 205 | *.ldf 206 | 207 | # Business Intelligence projects 208 | *.rdl.data 209 | *.bim.layout 210 | *.bim_*.settings 211 | 212 | # Microsoft Fakes 213 | FakesAssemblies/ 214 | 215 | # GhostDoc plugin setting file 216 | *.GhostDoc.xml 217 | 218 | # Node.js Tools for Visual Studio 219 | .ntvs_analysis.dat 220 | 221 | # Visual Studio 6 build log 222 | *.plg 223 | 224 | # Visual Studio 6 workspace options file 225 | *.opt 226 | 227 | # Visual Studio LightSwitch build output 228 | **/*.HTMLClient/GeneratedArtifacts 229 | **/*.DesktopClient/GeneratedArtifacts 230 | **/*.DesktopClient/ModelManifest.xml 231 | **/*.Server/GeneratedArtifacts 232 | **/*.Server/ModelManifest.xml 233 | _Pvt_Extensions 234 | 235 | # LightSwitch generated files 236 | GeneratedArtifacts/ 237 | ModelManifest.xml 238 | 239 | # Paket dependency manager 240 | .paket/paket.exe 241 | 242 | # FAKE - F# Make 243 | .fake/ 244 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | 342 | ReactOS may be used, runtime linked, and distributed with non-free software 343 | (meaning that such software has no obligations to open-source, or render 344 | free, their non-free code) such as commercial device drivers and commercial 345 | applications. This exception does not alter any other responsibilities of 346 | the licensee under the GPL (meaning that such software must still obey 347 | the GPL for the free ("open-sourced") code that has been integrated into 348 | the said software). 349 | -------------------------------------------------------------------------------- /FontLoaderSub.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2050 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FontLoaderSub", "FontLoaderSub\FontLoaderSub.vcxproj", "{4860FFD1-040F-4F32-B512-326AACE23C34}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Minimize|x64 = Minimize|x64 13 | Minimize|x86 = Minimize|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Debug|x64.ActiveCfg = Debug|x64 19 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Debug|x64.Build.0 = Debug|x64 20 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Debug|x86.ActiveCfg = Debug|Win32 21 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Debug|x86.Build.0 = Debug|Win32 22 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Minimize|x64.ActiveCfg = Minimize|x64 23 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Minimize|x64.Build.0 = Minimize|x64 24 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Minimize|x86.ActiveCfg = Minimize|Win32 25 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Minimize|x86.Build.0 = Minimize|Win32 26 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Release|x64.ActiveCfg = Release|x64 27 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Release|x64.Build.0 = Release|x64 28 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Release|x86.ActiveCfg = Release|Win32 29 | {4860FFD1-040F-4F32-B512-326AACE23C34}.Release|x86.Build.0 = Release|Win32 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {B0B0153B-0569-4F59-A47B-79EE24DBF79D} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /FontLoaderSub/FontLoaderSub.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Minimize 10 | Win32 11 | 12 | 13 | Minimize 14 | x64 15 | 16 | 17 | Release 18 | Win32 19 | 20 | 21 | Debug 22 | x64 23 | 24 | 25 | Release 26 | x64 27 | 28 | 29 | 30 | 15.0 31 | {4860FFD1-040F-4F32-B512-326AACE23C34} 32 | FontLoaderSub 33 | 10.0 34 | 35 | 36 | 37 | Application 38 | true 39 | v142 40 | Unicode 41 | 42 | 43 | Application 44 | false 45 | v142 46 | true 47 | Unicode 48 | 49 | 50 | Application 51 | false 52 | v142 53 | true 54 | Unicode 55 | 56 | 57 | Application 58 | true 59 | v142 60 | Unicode 61 | 62 | 63 | Application 64 | false 65 | v142 66 | true 67 | Unicode 68 | 69 | 70 | Application 71 | false 72 | v142 73 | true 74 | Unicode 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | false 102 | $(SolutionDir)$(Configuration)\$(Platform)\ 103 | $(Configuration)\$(Platform)\ 104 | 105 | 106 | false 107 | $(SolutionDir)$(Configuration)\$(Platform)\ 108 | $(Configuration)\$(Platform)\ 109 | 110 | 111 | false 112 | $(SolutionDir)$(Configuration)\$(Platform)\ 113 | $(Configuration)\$(Platform)\ 114 | 115 | 116 | false 117 | $(SolutionDir)$(Configuration)\$(Platform)\ 118 | $(Configuration)\$(Platform)\ 119 | 120 | 121 | false 122 | $(SolutionDir)$(Configuration)\$(Platform)\ 123 | $(Configuration)\$(Platform)\ 124 | 125 | 126 | false 127 | $(SolutionDir)$(Configuration)\$(Platform)\ 128 | $(Configuration)\$(Platform)\ 129 | 130 | 131 | 132 | Level3 133 | Disabled 134 | true 135 | true 136 | 137 | 138 | ShLwApi.Lib;ComCtl32.Lib;Bcrypt.lib;%(AdditionalDependencies) 139 | 140 | 141 | $(CMDLINE_DEFINE);%(PreprocessorDefinitions) 142 | 143 | 144 | 145 | 146 | Level3 147 | Disabled 148 | true 149 | true 150 | 151 | 152 | ShLwApi.Lib;ComCtl32.Lib;Bcrypt.lib;%(AdditionalDependencies) 153 | 154 | 155 | $(CMDLINE_DEFINE);%(PreprocessorDefinitions) 156 | 157 | 158 | 159 | 160 | Level3 161 | MaxSpeed 162 | true 163 | true 164 | true 165 | true 166 | true 167 | MultiThreaded 168 | 169 | 170 | true 171 | true 172 | ShLwApi.Lib;ComCtl32.Lib;Bcrypt.lib;%(AdditionalDependencies) 173 | 174 | 175 | $(CMDLINE_DEFINE);%(PreprocessorDefinitions) 176 | 177 | 178 | 179 | 180 | Level3 181 | MaxSpeed 182 | true 183 | true 184 | 185 | 186 | true 187 | true 188 | Size 189 | MultiThreaded 190 | false 191 | 192 | 193 | true 194 | true 195 | ShLwApi.Lib;ComCtl32.Lib;Bcrypt.lib;%(AdditionalDependencies) 196 | DebugFull 197 | MyEntryPoint 198 | true 199 | true 200 | /EMITPOGOPHASEINFO %(AdditionalOptions) 201 | 202 | 203 | $(CMDLINE_DEFINE);%(PreprocessorDefinitions) 204 | 205 | 206 | 207 | 208 | Level3 209 | MaxSpeed 210 | true 211 | true 212 | true 213 | true 214 | true 215 | MultiThreaded 216 | 217 | 218 | true 219 | true 220 | ShLwApi.Lib;ComCtl32.Lib;Bcrypt.lib;%(AdditionalDependencies) 221 | 222 | 223 | $(CMDLINE_DEFINE);%(PreprocessorDefinitions) 224 | 225 | 226 | 227 | 228 | Level3 229 | MaxSpeed 230 | true 231 | true 232 | 233 | 234 | true 235 | true 236 | Size 237 | MultiThreaded 238 | false 239 | 240 | 241 | true 242 | true 243 | ShLwApi.Lib;ComCtl32.Lib;Bcrypt.lib;%(AdditionalDependencies) 244 | DebugFull 245 | MyEntryPoint 246 | true 247 | true 248 | /EMITPOGOPHASEINFO %(AdditionalOptions) 249 | 250 | 251 | $(CMDLINE_DEFINE);%(PreprocessorDefinitions) 252 | 253 | 254 | 255 | 256 | 257 | 258 | 0x0804 259 | 0x0804 260 | 0x0804 261 | 0x0804 262 | 0x0804 263 | 0x0804 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /FontLoaderSub/FontLoaderSub.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Resource Files 20 | 21 | 22 | Resource Files 23 | 24 | 25 | Resource Files 26 | 27 | 28 | Resource Files 29 | 30 | 31 | 32 | 33 | Resource Files 34 | 35 | 36 | 37 | 38 | Source Files 39 | 40 | 41 | Source Files 42 | 43 | 44 | Source Files 45 | 46 | 47 | Source Files 48 | 49 | 50 | Source Files 51 | 52 | 53 | Source Files 54 | 55 | 56 | Source Files 57 | 58 | 59 | Source Files 60 | 61 | 62 | Source Files 63 | 64 | 65 | Source Files 66 | 67 | 68 | Source Files 69 | 70 | 71 | Source Files 72 | 73 | 74 | Source Files 75 | 76 | 77 | 78 | 79 | Header Files 80 | 81 | 82 | Header Files 83 | 84 | 85 | Header Files 86 | 87 | 88 | Header Files 89 | 90 | 91 | Header Files 92 | 93 | 94 | Header Files 95 | 96 | 97 | Header Files 98 | 99 | 100 | Header Files 101 | 102 | 103 | Header Files 104 | 105 | 106 | Header Files 107 | 108 | 109 | Header Files 110 | 111 | 112 | Resource Files 113 | 114 | 115 | Header Files 116 | 117 | 118 | Header Files 119 | 120 | 121 | -------------------------------------------------------------------------------- /FontLoaderSub/ass_parser.c: -------------------------------------------------------------------------------- 1 | #include "ass_parser.h" 2 | #include "ass_string.h" 3 | 4 | typedef enum { 5 | PST_UNKNOWN = 0, 6 | // PST_INFO, 7 | PST_STYLES, 8 | PST_EVENTS, 9 | // PST_FONTS 10 | } ASS_ParserState; 11 | 12 | typedef enum { 13 | TRACK_TYPE_UNKNOWN = 0, 14 | TRACK_TYPE_ASS, 15 | TRACK_TYPE_SSA 16 | } ASS_TrackType; 17 | 18 | typedef struct { 19 | ASS_Range Text; 20 | } ASS_Event; 21 | 22 | typedef struct { 23 | ASS_ParserState state; 24 | ASS_TrackType track_type; 25 | ASS_Range format_string; 26 | 27 | ASS_FontCallback callback; 28 | void *cb_arg; 29 | } ASS_Track; 30 | 31 | static void fire_font_cb(ASS_Track *track, ASS_Range *font) { 32 | if (track->callback) { 33 | const wchar_t *begin = ass_skip_spaces(font->begin, font->end); 34 | track->callback(begin, font->end - begin, track->cb_arg); 35 | } 36 | } 37 | 38 | static int next_tok(ASS_Range *input, ASS_Range *tok) { 39 | if (input->begin == input->end) { 40 | return 0; 41 | } 42 | *tok = (ASS_Range){.begin = input->begin, .end = input->begin}; 43 | while (tok->end != input->end && tok->end[0] != ',') { 44 | ++tok->end; 45 | } 46 | if (tok->end[0] == ',') { 47 | input->begin = tok->end + 1; 48 | } else { 49 | input->begin = tok->end; 50 | } 51 | ass_trim(tok); 52 | 53 | return 1; 54 | } 55 | 56 | static int test_tag( 57 | const wchar_t *p, 58 | const wchar_t *end, 59 | const wchar_t *tag, 60 | size_t len, 61 | ASS_Range *arg) { 62 | if (end >= p + len && ass_strncmp(p, tag, len) == 0) { 63 | *arg = (ASS_Range){.begin = p + len, .end = end}; 64 | return 1; 65 | } 66 | return 0; 67 | } 68 | 69 | static const wchar_t * 70 | parse_tags(ASS_Track *track, const wchar_t *p, const wchar_t *end, int nested) { 71 | const wchar_t *q; 72 | for (; p != end; p = q) { 73 | while (p != end && *p != '\\') 74 | ++p; 75 | if (*p != '\\') 76 | break; 77 | ++p; 78 | if (p != end) 79 | p = ass_skip_spaces(p, end); 80 | 81 | q = p; 82 | while (q != end && *q != '(' && *q != '\\') 83 | ++q; 84 | if (q == p) 85 | continue; 86 | 87 | const wchar_t *name_end = q; 88 | 89 | // Split parenthesized arguments 90 | ASS_Range first_arg = {NULL, NULL}; 91 | if (q != end && *q == '(') { 92 | ++q; 93 | while (1) { 94 | if (q != end) 95 | q = ass_skip_spaces(q, end); 96 | const wchar_t *r = q; 97 | while (r != end && *r != ',' && *r != '\\' && *r != ')') 98 | ++r; 99 | 100 | if (r != end && *r == ',') { 101 | // push_arg(args, &argc, q, r); 102 | q = r + 1; 103 | } else { 104 | while (r != end && *r != ')') 105 | ++r; 106 | // push_arg(args, &argc, q, r); 107 | if (first_arg.begin == NULL) { 108 | first_arg = (ASS_Range){q, r}; 109 | } 110 | q = r; 111 | if (q != end) 112 | ++q; 113 | break; 114 | } 115 | } 116 | } 117 | 118 | ASS_Range arg; 119 | if (test_tag(p, name_end, L"fn", 2, &arg)) { 120 | if (ass_strncmp(L"0", arg.begin, arg.end - arg.begin) == 0) { 121 | // restore? 122 | } else { 123 | if (first_arg.begin) 124 | fire_font_cb(track, &first_arg); 125 | else 126 | fire_font_cb(track, &arg); 127 | } 128 | } 129 | } 130 | return p; 131 | } 132 | 133 | static void parse_events(ASS_Track *track, ASS_Event *event) { 134 | if (event->Text.begin == NULL) { 135 | return; 136 | } 137 | 138 | const wchar_t *p = event->Text.begin; 139 | const wchar_t *ep = event->Text.end; 140 | const wchar_t *q; 141 | 142 | while ((p = ass_strnchr(p, '{', ep - p)) != NULL && 143 | (q = ass_strnchr(p, '}', ep - p)) != NULL) { 144 | p = parse_tags(track, p, q, 0); 145 | ++p; 146 | } 147 | } 148 | 149 | static void 150 | process_event_tail(ASS_Track *track, ASS_Range *line, int n_ignored) { 151 | int i; 152 | ASS_Range tok[1], tag[1], format[1]; 153 | ASS_Event event = {0}; 154 | 155 | for (i = 0; i < n_ignored; i++) { 156 | next_tok(line, tok); 157 | } 158 | 159 | *format = track->format_string; 160 | if (format->begin == format->end) { 161 | // using fallback 162 | const int skips = 9; 163 | for (i = 0; i < skips; i++) 164 | next_tok(line, tok); 165 | if (next_tok(line, tok)) { 166 | tok->end = line->end; 167 | event.Text = *tok; 168 | } 169 | } else { 170 | while (next_tok(format, tag)) { 171 | const int r = next_tok(line, tok); 172 | if (r && tag->end - tag->begin == 4 && 173 | ass_strncasecmp(tag->begin, L"text", 4) == 0) { 174 | // till the end 175 | tok->end = line->end; 176 | event.Text = *tok; 177 | break; 178 | } 179 | } 180 | } 181 | parse_events(track, &event); 182 | } 183 | 184 | static void 185 | process_styles(ASS_Track *track, const wchar_t *begin, const wchar_t *end) { 186 | ASS_Range line[1], tok[1], tag[1], format[1]; 187 | *line = (ASS_Range){.begin = begin, .end = end}; 188 | 189 | *format = track->format_string; 190 | if (format->begin == format->end) { 191 | // use default fallback, assuming fontname at column 2 192 | next_tok(line, tok); 193 | const int r = next_tok(line, tok); 194 | if (r) { 195 | fire_font_cb(track, tok); 196 | } 197 | } else { 198 | // using format string 199 | while (next_tok(format, tag)) { 200 | const int r = next_tok(line, tok); 201 | if (r && tag->end - tag->begin == 8 && 202 | ass_strncasecmp(tag->begin, L"fontname", 8) == 0) { 203 | fire_font_cb(track, tok); 204 | } 205 | } 206 | } 207 | } 208 | 209 | static void process_styles_line( 210 | ASS_Track *track, 211 | const wchar_t *begin, 212 | const wchar_t *end) { 213 | if (!ass_strncmp(begin, L"Format:", 7)) { 214 | track->format_string = (ASS_Range){.begin = begin + 7, .end = end}; 215 | ass_trim(&track->format_string); 216 | } else if (!ass_strncmp(begin, L"Style:", 6)) { 217 | process_styles(track, ass_skip_spaces(begin + 6, end), end); 218 | } 219 | } 220 | 221 | static void process_events_line( 222 | ASS_Track *track, 223 | const wchar_t *begin, 224 | const wchar_t *end) { 225 | if (!ass_strncmp(begin, L"Format:", 7)) { 226 | const ASS_Range fmt_str = {.begin = begin + 7, .end = end}; 227 | track->format_string = fmt_str; 228 | ass_trim(&track->format_string); 229 | } else if (!ass_strncmp(begin, L"Dialogue:", 9)) { 230 | ASS_Range range = {.begin = ass_skip_spaces(begin + 9, end), .end = end}; 231 | process_event_tail(track, &range, 0); 232 | } 233 | } 234 | 235 | static void 236 | process_line(ASS_Track *track, const wchar_t *begin, const wchar_t *end) { 237 | int is_content = 0; 238 | 239 | if (!ass_strncasecmp(begin, L"[v4 styles]", 11)) { 240 | track->state = PST_STYLES; 241 | track->track_type = TRACK_TYPE_SSA; 242 | } else if (!ass_strncasecmp(begin, L"[v4+ styles]", 12)) { 243 | track->state = PST_STYLES; 244 | track->track_type = TRACK_TYPE_ASS; 245 | } else if (!ass_strncasecmp(begin, L"[events]", 8)) { 246 | track->state = PST_EVENTS; 247 | } else if (begin[0] == '[') { 248 | track->state = PST_UNKNOWN; 249 | } else { 250 | is_content = 1; 251 | } 252 | 253 | if (!is_content) { 254 | track->format_string = (ASS_Range){.begin = NULL, .end = NULL}; 255 | } else { 256 | switch (track->state) { 257 | case PST_STYLES: 258 | process_styles_line(track, begin, end); 259 | break; 260 | case PST_EVENTS: 261 | process_events_line(track, begin, end); 262 | break; 263 | default: 264 | break; 265 | } 266 | } 267 | } 268 | 269 | void ass_process_data( 270 | const wchar_t *data, 271 | size_t cch, 272 | ASS_FontCallback cb, 273 | void *arg) { 274 | ASS_Track track = {.callback = cb, .cb_arg = arg}; 275 | const wchar_t *p = data; 276 | const wchar_t *eos = data + cch; 277 | while (p != eos) { 278 | // skip blank lines 279 | while (p != eos && ass_is_eol(*p)) 280 | ++p; 281 | // find end of the line 282 | const wchar_t *q = p; 283 | while (q != eos && !ass_is_eol(*q)) 284 | ++q; 285 | 286 | process_line(&track, p, q); 287 | p = q; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /FontLoaderSub/ass_parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef int (*ASS_FontCallback)(const wchar_t *font, size_t cch, void *arg); 6 | 7 | void ass_process_data( 8 | const wchar_t *data, 9 | size_t cch, 10 | ASS_FontCallback cb, 11 | void *arg); 12 | -------------------------------------------------------------------------------- /FontLoaderSub/ass_string.c: -------------------------------------------------------------------------------- 1 | #include "ass_string.h" 2 | 3 | int ass_is_space(int ch) { 4 | return ch == ' ' || ch == '\t'; 5 | } 6 | 7 | void ass_trim(ASS_Range *r) { 8 | if (!r || !r->begin || !r->end || r->begin == r->end) 9 | return; 10 | for (; r->begin != r->end && ass_is_space(*r->begin); r->begin++) { 11 | // nop; 12 | } 13 | if (r->begin == r->end) 14 | return; 15 | for (; ass_is_space(r->end[-1]); r->end--) { 16 | // nop; 17 | } 18 | } 19 | 20 | const wchar_t *ass_skip_spaces(const wchar_t *p, const wchar_t *end) { 21 | for (; ass_is_space(*p) && p != end; p++) { 22 | // nop 23 | } 24 | return p; 25 | } 26 | 27 | int ass_is_eol(int ch) { 28 | return ch == '\r' || ch == '\n'; 29 | } 30 | 31 | int ass_strncmp(const wchar_t *s1, const wchar_t *s2, size_t cch) { 32 | wchar_t a, b; 33 | const wchar_t *last = s2 + cch; 34 | 35 | do { 36 | a = *s1++; 37 | b = *s2++; 38 | } while (s2 != last && a && a == b); 39 | 40 | return a - b; 41 | } 42 | 43 | static wchar_t ass_to_lower(wchar_t ch) { 44 | if (L'A' <= ch && ch <= L'Z') 45 | return ch - L'A' + L'a'; 46 | return ch; 47 | } 48 | 49 | int ass_strncasecmp(const wchar_t *s1, const wchar_t *s2, size_t cch) { 50 | // assume wchar_t is unsigned short 51 | wchar_t a, b; 52 | const wchar_t *last = s2 + cch; 53 | 54 | do { 55 | a = ass_to_lower(*s1++); 56 | b = ass_to_lower(*s2++); 57 | } while (s2 != last && a && a == b); 58 | 59 | return a - b; 60 | } 61 | 62 | const wchar_t *ass_strnchr(const wchar_t *s, wchar_t ch, size_t cch) { 63 | const wchar_t *last = s + cch; 64 | 65 | for (; s != last && *s != ch; s++) { 66 | // nop 67 | } 68 | return s == last ? NULL : s; 69 | } 70 | 71 | size_t ass_strlen(const wchar_t *str) { 72 | const wchar_t *p; 73 | for (p = str; *p; p++) { 74 | // nop 75 | } 76 | return p - str; 77 | } 78 | 79 | size_t ass_strnlen(const wchar_t *str, size_t n) { 80 | for (size_t i = 0; i != n; i++) { 81 | if (str[i] == 0) 82 | return i; 83 | } 84 | return n; 85 | } 86 | -------------------------------------------------------------------------------- /FontLoaderSub/ass_string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct _ASS_Range { 6 | const wchar_t *begin; 7 | const wchar_t *end; 8 | } ASS_Range; 9 | 10 | void ass_trim(ASS_Range *r); 11 | 12 | const wchar_t *ass_skip_spaces(const wchar_t *p, const wchar_t *end); 13 | 14 | int ass_is_eol(int ch); 15 | 16 | int ass_strncmp(const wchar_t *s1, const wchar_t *s2, size_t cch); 17 | 18 | /** 19 | * \brief Compare two strings in lowercase 20 | * \param s1 first string 21 | * \param s2 second string, must in lowercase 22 | * \param cch number of chars 23 | * \return 24 | */ 25 | int ass_strncasecmp(const wchar_t *s1, const wchar_t *s2, size_t cch); 26 | 27 | const wchar_t *ass_strnchr(const wchar_t *s, wchar_t ch, size_t cch); 28 | 29 | size_t ass_strlen(const wchar_t *str); 30 | 31 | size_t ass_strnlen(const wchar_t *str, size_t n); 32 | -------------------------------------------------------------------------------- /FontLoaderSub/cstl.c: -------------------------------------------------------------------------------- 1 | #include "cstl.h" 2 | #include "ass_string.h" 3 | #include "util.h" 4 | 5 | int vec_init(vec_t *v, size_t size, allocator_t *alloc) { 6 | *v = (vec_t){.size = size, .alloc = alloc}; 7 | return 0; 8 | } 9 | 10 | int vec_free(vec_t *v) { 11 | if (v->alloc) { 12 | allocator_t *alloc = v->alloc; 13 | alloc->alloc(v->data, 0, alloc->arg); 14 | } 15 | return 0; 16 | } 17 | 18 | size_t vec_prealloc(vec_t *v, size_t n) { 19 | if (v->alloc && v->n + n > v->capacity) { 20 | allocator_t *alloc = v->alloc; 21 | size_t new_cap = v->capacity * 2; 22 | if (new_cap < n + v->n) 23 | new_cap = n * 2 + v->capacity * 2; 24 | void *new_buf = alloc->alloc(v->data, new_cap * v->size, alloc->arg); 25 | if (new_buf) { 26 | v->capacity = new_cap; 27 | v->data = new_buf; 28 | } 29 | } 30 | if (v->capacity < v->n) { 31 | // already corrupted, stop here 32 | FlBreak(); 33 | } 34 | return v->capacity - v->n; 35 | } 36 | 37 | int vec_append(vec_t *v, void *data, size_t n) { 38 | const size_t space = vec_prealloc(v, n); 39 | if (space < n) 40 | return 0; 41 | 42 | const uint8_t *src = data; 43 | const uint8_t *last = &src[n * v->size]; 44 | uint8_t *dst = v->data; 45 | dst += v->n * v->size; 46 | 47 | v->n += n; 48 | zmemcpy(dst, src, n * v->size); 49 | return 1; 50 | } 51 | 52 | int vec_clear(vec_t *v) { 53 | v->n = 0; 54 | return 0; 55 | } 56 | 57 | int str_db_init( 58 | str_db_t *s, 59 | allocator_t *alloc, 60 | wchar_t ex_pad, 61 | uint16_t pad_len) { 62 | const int r = vec_init(&s->vec, sizeof(wchar_t), alloc); 63 | s->ex_pad = ex_pad; 64 | s->pad_len = pad_len; 65 | return r; 66 | } 67 | 68 | int str_db_free(str_db_t *s) { 69 | return vec_free(&s->vec); 70 | } 71 | 72 | size_t str_db_tell(str_db_t *s) { 73 | return s->vec.n; 74 | } 75 | 76 | size_t str_db_seek(str_db_t *s, size_t pos) { 77 | if (pos <= s->vec.capacity) 78 | s->vec.n = pos; 79 | #if 0 80 | if (pos < s->vec.capacity) { 81 | wchar_t *buf = (wchar_t *)s->vec.data; 82 | buf[pos] = 0; 83 | } 84 | #endif 85 | return s->vec.n; 86 | } 87 | 88 | const wchar_t *str_db_next(str_db_t *s, size_t *next_pos) { 89 | if (*next_pos == s->vec.n) 90 | return NULL; 91 | const wchar_t *ret = str_db_get(s, *next_pos); 92 | if (ret != NULL) { 93 | const size_t len = ass_strlen(ret); 94 | *next_pos += len + s->pad_len; 95 | } 96 | return ret; 97 | } 98 | 99 | const wchar_t *str_db_get(str_db_t *s, size_t pos) { 100 | if (pos > s->vec.n) 101 | return NULL; 102 | wchar_t *buf = (wchar_t *)s->vec.data; 103 | return &buf[pos]; 104 | } 105 | 106 | const wchar_t *str_db_push_prefix(str_db_t *s, const wchar_t *str, size_t cch) { 107 | const wchar_t *ret = str_db_push_u16_le(s, str, cch); 108 | if (ret) { 109 | s->vec.n -= s->pad_len; 110 | } 111 | return ret; 112 | } 113 | 114 | const wchar_t *str_db_push_u16_le(str_db_t *s, const wchar_t *str, size_t cch) { 115 | const size_t len = cch ? ass_strnlen(str, cch) : ass_strlen(str); 116 | const size_t len_all = len + s->pad_len; // always NUL terminated 117 | if (vec_prealloc(&s->vec, len_all + 1) < len_all + 1) 118 | return NULL; 119 | 120 | wchar_t *ret = (wchar_t *)str_db_get(s, str_db_tell(s)); 121 | for (size_t i = 0; i != len; i++) 122 | ret[i] = str[i]; 123 | 124 | for (uint16_t i = 0; i != s->pad_len; i++) 125 | ret[len + i] = s->ex_pad; 126 | ret[len] = 0; 127 | 128 | s->vec.n += len_all; 129 | if (s->vec.n >= s->vec.capacity) { 130 | FlBreak(); 131 | } 132 | return ret; 133 | } 134 | 135 | const wchar_t *str_db_push_u16_be(str_db_t *s, const wchar_t *str, size_t cch) { 136 | wchar_t *ret = (wchar_t *)str_db_push_u16_le(s, str, cch); 137 | if (ret) { 138 | for (wchar_t *p = ret; *p; p++) { 139 | *p = be16(*p); 140 | } 141 | } 142 | return ret; 143 | } 144 | 145 | const wchar_t *str_db_str(str_db_t *s, size_t pos, const wchar_t *str) { 146 | const size_t len = ass_strlen(str) + 1; 147 | size_t it = pos; 148 | const wchar_t *sub; 149 | while ((sub = str_db_next(s, &it)) != NULL) { 150 | if (ass_strncmp(sub, str, len) == 0) { 151 | return sub; 152 | } 153 | } 154 | return NULL; 155 | } 156 | 157 | void str_db_loads(str_db_t *s, const wchar_t *str, size_t cch, wchar_t ex_pad) { 158 | // clang-format off 159 | *s = (str_db_t){ 160 | .vec= (vec_t){ 161 | .data = (void*)str, 162 | .n = cch, 163 | .capacity = cch, 164 | .size = sizeof str[0], 165 | .alloc = NULL 166 | }, 167 | .ex_pad = ex_pad, 168 | .pad_len = ex_pad ? 2 : 1 169 | }; 170 | // clang-format on 171 | } 172 | -------------------------------------------------------------------------------- /FontLoaderSub/cstl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util.h" 4 | 5 | typedef struct _vec_t { 6 | union { 7 | wchar_t *str; // for easy debugging 8 | void *data; 9 | }; 10 | size_t n; 11 | size_t capacity; 12 | size_t size; 13 | allocator_t *alloc; 14 | } vec_t; 15 | 16 | int vec_init(vec_t *v, size_t size, allocator_t *alloc); 17 | 18 | int vec_free(vec_t *v); 19 | 20 | size_t vec_prealloc(vec_t *v, size_t n); 21 | 22 | int vec_append(vec_t *v, void *data, size_t n); 23 | 24 | int vec_clear(vec_t *v); 25 | 26 | typedef struct _str_buf_t { 27 | vec_t vec; 28 | wchar_t ex_pad; 29 | uint16_t pad_len; 30 | } str_db_t; 31 | 32 | int str_db_init( 33 | str_db_t *s, 34 | allocator_t *alloc, 35 | wchar_t ex_pad, 36 | uint16_t pad_len); 37 | 38 | int str_db_free(str_db_t *s); 39 | 40 | size_t str_db_tell(str_db_t *s); 41 | 42 | size_t str_db_seek(str_db_t *s, size_t pos); 43 | 44 | const wchar_t *str_db_next(str_db_t *s, size_t *next_pos); 45 | 46 | const wchar_t *str_db_get(str_db_t *s, size_t pos); 47 | 48 | const wchar_t *str_db_push_prefix(str_db_t *s, const wchar_t *str, size_t cch); 49 | 50 | const wchar_t *str_db_push_u16_le(str_db_t *s, const wchar_t *str, size_t cch); 51 | 52 | const wchar_t *str_db_push_u16_be(str_db_t *s, const wchar_t *str, size_t cch); 53 | 54 | const wchar_t *str_db_str(str_db_t *s, size_t pos, const wchar_t *str); 55 | 56 | void str_db_loads(str_db_t *s, const wchar_t *str, size_t cch, wchar_t ex_pad); 57 | -------------------------------------------------------------------------------- /FontLoaderSub/diff_libass.txt: -------------------------------------------------------------------------------- 1 | Notes on ASS/SSA parsing behaviour difference between FontLoaderSub and libass 2 | 3 | * parse in UTF-16 4 | * accept NULL char 5 | * when `\fn(question)` 6 | -------------------------------------------------------------------------------- /FontLoaderSub/dwrite_c.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This file has no copyright assigned and is placed in the Public Domain. 3 | * This file is part of the mingw-w64 runtime package. 4 | * No warranty is given; refer to the file DISCLAIMER.PD within this package. 5 | */ 6 | /** 7 | * Stripped version. Only definitions needed by libass. Contains fixes to 8 | * make it compile with C. Also needed on MSVC. 9 | */ 10 | #ifndef __INC_DWRITE__ 11 | #define __INC_DWRITE__ 12 | 13 | #define DWRITEAPI DECLSPEC_IMPORT 14 | 15 | #include 16 | 17 | typedef struct IDWriteFactory IDWriteFactory; 18 | typedef struct IDWriteFont IDWriteFont; 19 | typedef struct IDWriteFontCollection IDWriteFontCollection; 20 | typedef struct IDWriteFontFace IDWriteFontFace; 21 | typedef struct IDWriteFontFamily IDWriteFontFamily; 22 | typedef struct IDWriteFontList IDWriteFontList; 23 | typedef struct IDWriteFontFile IDWriteFontFile; 24 | typedef struct IDWriteFontFileLoader IDWriteFontFileLoader; 25 | typedef struct IDWriteFontFileStream IDWriteFontFileStream; 26 | typedef struct IDWriteInlineObject IDWriteInlineObject; 27 | typedef struct IDWriteLocalizedStrings IDWriteLocalizedStrings; 28 | typedef struct IDWritePixelSnapping IDWritePixelSnapping; 29 | typedef struct IDWriteTextFormat IDWriteTextFormat; 30 | typedef struct IDWriteTextLayout IDWriteTextLayout; 31 | typedef struct IDWriteTextRenderer IDWriteTextRenderer; 32 | 33 | #include 34 | 35 | typedef enum DWRITE_INFORMATIONAL_STRING_ID { 36 | DWRITE_INFORMATIONAL_STRING_NONE = 0, 37 | DWRITE_INFORMATIONAL_STRING_COPYRIGHT_NOTICE, 38 | DWRITE_INFORMATIONAL_STRING_VERSION_STRINGS, 39 | DWRITE_INFORMATIONAL_STRING_TRADEMARK, 40 | DWRITE_INFORMATIONAL_STRING_MANUFACTURER, 41 | DWRITE_INFORMATIONAL_STRING_DESIGNER, 42 | DWRITE_INFORMATIONAL_STRING_DESIGNER_URL, 43 | DWRITE_INFORMATIONAL_STRING_DESCRIPTION, 44 | DWRITE_INFORMATIONAL_STRING_FONT_VENDOR_URL, 45 | DWRITE_INFORMATIONAL_STRING_LICENSE_DESCRIPTION, 46 | DWRITE_INFORMATIONAL_STRING_LICENSE_INFO_URL, 47 | DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, 48 | DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES, 49 | DWRITE_INFORMATIONAL_STRING_PREFERRED_FAMILY_NAMES, 50 | DWRITE_INFORMATIONAL_STRING_PREFERRED_SUBFAMILY_NAMES, 51 | DWRITE_INFORMATIONAL_STRING_SAMPLE_TEXT, 52 | DWRITE_INFORMATIONAL_STRING_FULL_NAME, 53 | DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME, 54 | DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_CID_NAME 55 | } DWRITE_INFORMATIONAL_STRING_ID; 56 | 57 | typedef enum DWRITE_FACTORY_TYPE { 58 | DWRITE_FACTORY_TYPE_SHARED = 0, 59 | DWRITE_FACTORY_TYPE_ISOLATED 60 | } DWRITE_FACTORY_TYPE; 61 | 62 | typedef enum DWRITE_FONT_FACE_TYPE { 63 | DWRITE_FONT_FACE_TYPE_CFF = 0, 64 | DWRITE_FONT_FACE_TYPE_TRUETYPE, 65 | DWRITE_FONT_FACE_TYPE_TRUETYPE_COLLECTION, 66 | DWRITE_FONT_FACE_TYPE_TYPE1, 67 | DWRITE_FONT_FACE_TYPE_VECTOR, 68 | DWRITE_FONT_FACE_TYPE_BITMAP, 69 | DWRITE_FONT_FACE_TYPE_UNKNOWN, 70 | DWRITE_FONT_FACE_TYPE_RAW_CFF 71 | } DWRITE_FONT_FACE_TYPE; 72 | 73 | typedef enum DWRITE_FONT_SIMULATIONS { 74 | DWRITE_FONT_SIMULATIONS_NONE = 0x0000, 75 | DWRITE_FONT_SIMULATIONS_BOLD = 0x0001, 76 | DWRITE_FONT_SIMULATIONS_OBLIQUE = 0x0002 77 | } DWRITE_FONT_SIMULATIONS; 78 | 79 | typedef enum DWRITE_FONT_STRETCH { 80 | DWRITE_FONT_STRETCH_UNDEFINED = 0, 81 | DWRITE_FONT_STRETCH_ULTRA_CONDENSED = 1, 82 | DWRITE_FONT_STRETCH_EXTRA_CONDENSED = 2, 83 | DWRITE_FONT_STRETCH_CONDENSED = 3, 84 | DWRITE_FONT_STRETCH_SEMI_CONDENSED = 4, 85 | DWRITE_FONT_STRETCH_NORMAL = 5, 86 | DWRITE_FONT_STRETCH_MEDIUM = 5, 87 | DWRITE_FONT_STRETCH_SEMI_EXPANDED = 6, 88 | DWRITE_FONT_STRETCH_EXPANDED = 7, 89 | DWRITE_FONT_STRETCH_EXTRA_EXPANDED = 8, 90 | DWRITE_FONT_STRETCH_ULTRA_EXPANDED = 9 91 | } DWRITE_FONT_STRETCH; 92 | 93 | typedef enum DWRITE_FONT_STYLE { 94 | DWRITE_FONT_STYLE_NORMAL = 0, 95 | DWRITE_FONT_STYLE_OBLIQUE, 96 | DWRITE_FONT_STYLE_ITALIC 97 | } DWRITE_FONT_STYLE; 98 | 99 | typedef enum DWRITE_FONT_WEIGHT { 100 | DWRITE_FONT_WEIGHT_MEDIUM = 500, 101 | /* rest dropped */ 102 | } DWRITE_FONT_WEIGHT; 103 | 104 | typedef struct DWRITE_FONT_METRICS { 105 | UINT16 designUnitsPerEm; 106 | UINT16 ascent; 107 | UINT16 descent; 108 | INT16 lineGap; 109 | UINT16 capHeight; 110 | UINT16 xHeight; 111 | INT16 underlinePosition; 112 | UINT16 underlineThickness; 113 | INT16 strikethroughPosition; 114 | UINT16 strikethroughThickness; 115 | } DWRITE_FONT_METRICS; 116 | 117 | typedef struct DWRITE_GLYPH_OFFSET DWRITE_GLYPH_OFFSET; 118 | 119 | typedef struct DWRITE_GLYPH_RUN { 120 | IDWriteFontFace *fontFace; 121 | FLOAT fontEmSize; 122 | UINT32 glyphCount; 123 | const UINT16 *glyphIndices; 124 | const FLOAT *glyphAdvances; 125 | const DWRITE_GLYPH_OFFSET *glyphOffsets; 126 | BOOL isSideways; 127 | UINT32 bidiLevel; 128 | } DWRITE_GLYPH_RUN; 129 | 130 | typedef struct DWRITE_GLYPH_RUN_DESCRIPTION DWRITE_GLYPH_RUN_DESCRIPTION; 131 | typedef struct DWRITE_HIT_TEST_METRICS DWRITE_HIT_TEST_METRICS; 132 | typedef struct DWRITE_LINE_METRICS DWRITE_LINE_METRICS; 133 | typedef struct DWRITE_MATRIX DWRITE_MATRIX; 134 | typedef struct DWRITE_STRIKETHROUGH DWRITE_STRIKETHROUGH; 135 | typedef struct DWRITE_TEXT_METRICS DWRITE_TEXT_METRICS; 136 | 137 | typedef struct DWRITE_TEXT_RANGE { 138 | UINT32 startPosition; 139 | UINT32 length; 140 | } DWRITE_TEXT_RANGE; 141 | 142 | typedef struct DWRITE_TRIMMING DWRITE_TRIMMING; 143 | typedef struct DWRITE_UNDERLINE DWRITE_UNDERLINE; 144 | 145 | #ifndef __MINGW_DEF_ARG_VAL 146 | #ifdef __cplusplus 147 | #define __MINGW_DEF_ARG_VAL(x) = x 148 | #else 149 | #define __MINGW_DEF_ARG_VAL(x) 150 | #endif 151 | #endif 152 | 153 | #undef INTERFACE 154 | #define INTERFACE IDWriteFactory 155 | DECLARE_INTERFACE_(IDWriteFactory,IUnknown) 156 | { 157 | BEGIN_INTERFACE 158 | 159 | #ifndef __cplusplus 160 | /* IUnknown methods */ 161 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 162 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 163 | STDMETHOD_(ULONG, Release)(THIS) PURE; 164 | #endif 165 | 166 | /* IDWriteFactory methods */ 167 | STDMETHOD(GetSystemFontCollection)(THIS_ 168 | IDWriteFontCollection **fontCollection, 169 | BOOL checkForUpdates __MINGW_DEF_ARG_VAL(FALSE)) PURE; 170 | 171 | STDMETHOD(dummy1)(THIS); 172 | STDMETHOD(dummy2)(THIS); 173 | STDMETHOD(dummy3)(THIS); 174 | STDMETHOD(dummy4)(THIS); 175 | STDMETHOD(dummy5)(THIS); 176 | STDMETHOD(dummy6)(THIS); 177 | STDMETHOD(dummy7)(THIS); 178 | STDMETHOD(dummy8)(THIS); 179 | STDMETHOD(dummy9)(THIS); 180 | STDMETHOD(dummy10)(THIS); 181 | STDMETHOD(dummy11)(THIS); 182 | 183 | STDMETHOD(CreateTextFormat)(THIS_ 184 | WCHAR const *fontFamilyName, 185 | IDWriteFontCollection *fontCollection, 186 | DWRITE_FONT_WEIGHT fontWeight, 187 | DWRITE_FONT_STYLE fontStyle, 188 | DWRITE_FONT_STRETCH fontStretch, 189 | FLOAT fontSize, 190 | WCHAR const *localeName, 191 | IDWriteTextFormat **textFormat) PURE; 192 | 193 | STDMETHOD(dummy12)(THIS); 194 | STDMETHOD(dummy13)(THIS); 195 | 196 | STDMETHOD(CreateTextLayout)(THIS_ 197 | WCHAR const *string, 198 | UINT32 stringLength, 199 | IDWriteTextFormat *textFormat, 200 | FLOAT maxWidth, 201 | FLOAT maxHeight, 202 | IDWriteTextLayout **textLayout) PURE; 203 | 204 | /* remainder dropped */ 205 | END_INTERFACE 206 | }; 207 | #ifdef COBJMACROS 208 | #define IDWriteFactory_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) 209 | #define IDWriteFactory_AddRef(This) (This)->lpVtbl->AddRef(This) 210 | #define IDWriteFactory_Release(This) (This)->lpVtbl->Release(This) 211 | #define IDWriteFactory_GetSystemFontCollection(This,fontCollection,checkForUpdates) (This)->lpVtbl->GetSystemFontCollection(This,fontCollection,checkForUpdates) 212 | #define IDWriteFactory_CreateTextFormat(This,fontFamilyName,fontCollection,fontWeight,fontStyle,fontStretch,fontSize,localeName,textFormat) (This)->lpVtbl->CreateTextFormat(This,fontFamilyName,fontCollection,fontWeight,fontStyle,fontStretch,fontSize,localeName,textFormat) 213 | #define IDWriteFactory_CreateTextLayout(This,string,stringLength,textFormat,maxWidth,maxHeight,textLayout) (This)->lpVtbl->CreateTextLayout(This,string,stringLength,textFormat,maxWidth,maxHeight,textLayout) 214 | #endif /*COBJMACROS*/ 215 | 216 | #undef INTERFACE 217 | #define INTERFACE IDWriteFont 218 | DECLARE_INTERFACE_(IDWriteFont,IUnknown) 219 | { 220 | BEGIN_INTERFACE 221 | 222 | #ifndef __cplusplus 223 | /* IUnknown methods */ 224 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 225 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 226 | STDMETHOD_(ULONG, Release)(THIS) PURE; 227 | #endif 228 | 229 | /* IDWriteFont methods */ 230 | STDMETHOD(GetFontFamily)(THIS_ 231 | IDWriteFontFamily **fontFamily) PURE; 232 | 233 | STDMETHOD_(DWRITE_FONT_WEIGHT, GetWeight)(THIS) PURE; 234 | STDMETHOD_(DWRITE_FONT_STRETCH, GetStretch)(THIS) PURE; 235 | STDMETHOD_(DWRITE_FONT_STYLE, GetStyle)(THIS) PURE; 236 | STDMETHOD_(BOOL, IsSymbolFont)(THIS) PURE; 237 | 238 | STDMETHOD(GetFaceNames)(THIS_ 239 | IDWriteLocalizedStrings **names) PURE; 240 | 241 | STDMETHOD(GetInformationalStrings)(THIS_ 242 | DWRITE_INFORMATIONAL_STRING_ID informationalStringID, 243 | IDWriteLocalizedStrings **informationalStrings, 244 | BOOL *exists) PURE; 245 | 246 | STDMETHOD_(DWRITE_FONT_SIMULATIONS, GetSimulations)(THIS) PURE; 247 | 248 | STDMETHOD_(void, GetMetrics)(THIS_ 249 | DWRITE_FONT_METRICS *fontMetrics) PURE; 250 | 251 | STDMETHOD(HasCharacter)(THIS_ 252 | UINT32 unicodeValue, 253 | BOOL *exists) PURE; 254 | 255 | STDMETHOD(CreateFontFace)(THIS_ 256 | IDWriteFontFace **fontFace) PURE; 257 | 258 | END_INTERFACE 259 | }; 260 | #ifdef COBJMACROS 261 | #define IDWriteFont_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) 262 | #define IDWriteFont_AddRef(This) (This)->lpVtbl->AddRef(This) 263 | #define IDWriteFont_Release(This) (This)->lpVtbl->Release(This) 264 | #define IDWriteFont_CreateFontFace(This,fontFace) (This)->lpVtbl->CreateFontFace(This,fontFace) 265 | #define IDWriteFont_GetFaceNames(This,names) (This)->lpVtbl->GetFaceNames(This,names) 266 | #define IDWriteFont_GetFontFamily(This,fontFamily) (This)->lpVtbl->GetFontFamily(This,fontFamily) 267 | #define IDWriteFont_GetInformationalStrings(This,informationalStringID,informationalStrings,exists) (This)->lpVtbl->GetInformationalStrings(This,informationalStringID,informationalStrings,exists) 268 | #define IDWriteFont_GetMetrics(This,fontMetrics) (This)->lpVtbl->GetMetrics(This,fontMetrics) 269 | #define IDWriteFont_GetSimulations(This) (This)->lpVtbl->GetSimulations(This) 270 | #define IDWriteFont_GetStretch(This) (This)->lpVtbl->GetStretch(This) 271 | #define IDWriteFont_GetStyle(This) (This)->lpVtbl->GetStyle(This) 272 | #define IDWriteFont_GetWeight(This) (This)->lpVtbl->GetWeight(This) 273 | #define IDWriteFont_HasCharacter(This,unicodeValue,exists) (This)->lpVtbl->HasCharacter(This,unicodeValue,exists) 274 | #define IDWriteFont_IsSymbolFont(This) (This)->lpVtbl->IsSymbolFont(This) 275 | #endif /*COBJMACROS*/ 276 | 277 | #undef INTERFACE 278 | #define INTERFACE IDWriteFontCollection 279 | DECLARE_INTERFACE_(IDWriteFontCollection,IUnknown) 280 | { 281 | BEGIN_INTERFACE 282 | 283 | #ifndef __cplusplus 284 | /* IUnknown methods */ 285 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 286 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 287 | STDMETHOD_(ULONG, Release)(THIS) PURE; 288 | #endif 289 | 290 | /* IDWriteFontCollection methods */ 291 | STDMETHOD_(UINT32, GetFontFamilyCount)(THIS) PURE; 292 | 293 | STDMETHOD(GetFontFamily)(THIS_ 294 | UINT32 index, 295 | IDWriteFontFamily **fontFamily) PURE; 296 | 297 | STDMETHOD(FindFamilyName)(THIS_ 298 | WCHAR const *familyName, 299 | UINT32 *index, 300 | BOOL *exists) PURE; 301 | 302 | STDMETHOD(GetFontFromFontFace)(THIS_ 303 | IDWriteFontFace* fontFace, 304 | IDWriteFont **font) PURE; 305 | 306 | END_INTERFACE 307 | }; 308 | #ifdef COBJMACROS 309 | #define IDWriteFontCollection_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) 310 | #define IDWriteFontCollection_AddRef(This) (This)->lpVtbl->AddRef(This) 311 | #define IDWriteFontCollection_Release(This) (This)->lpVtbl->Release(This) 312 | #define IDWriteFontCollection_FindFamilyName(This,familyName,index,exists) (This)->lpVtbl->FindFamilyName(This,familyName,index,exists) 313 | #define IDWriteFontCollection_GetFontFamily(This,index,fontFamily) (This)->lpVtbl->GetFontFamily(This,index,fontFamily) 314 | #define IDWriteFontCollection_GetFontFamilyCount(This) (This)->lpVtbl->GetFontFamilyCount(This) 315 | #define IDWriteFontCollection_GetFontFromFontFace(This,fontFace,font) (This)->lpVtbl->GetFontFromFontFace(This,fontFace,font) 316 | #endif /*COBJMACROS*/ 317 | 318 | #undef INTERFACE 319 | #define INTERFACE IDWriteFontFace 320 | DECLARE_INTERFACE_(IDWriteFontFace,IUnknown) 321 | { 322 | BEGIN_INTERFACE 323 | 324 | #ifndef __cplusplus 325 | /* IUnknown methods */ 326 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 327 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 328 | STDMETHOD_(ULONG, Release)(THIS) PURE; 329 | #endif 330 | 331 | /* IDWriteFontFace methods */ 332 | STDMETHOD_(DWRITE_FONT_FACE_TYPE, GetType)(THIS) PURE; 333 | 334 | STDMETHOD(GetFiles)(THIS_ 335 | UINT32 *numberOfFiles, 336 | IDWriteFontFile **fontFiles) PURE; 337 | 338 | STDMETHOD_(UINT32, GetIndex)(THIS) PURE; 339 | 340 | /* rest dropped */ 341 | END_INTERFACE 342 | }; 343 | #ifdef COBJMACROS 344 | #define IDWriteFontFace_Release(This) (This)->lpVtbl->Release(This) 345 | #define IDWriteFontFace_GetType(This) (This)->lpVtbl->GetType(This) 346 | #define IDWriteFontFace_GetFiles(This,fontFiles,b) (This)->lpVtbl->GetFiles(This,fontFiles,b) 347 | #define IDWriteFontFace_GetIndex(This) (This)->lpVtbl->GetIndex(This) 348 | #endif /*COBJMACROS*/ 349 | 350 | #undef INTERFACE 351 | #define INTERFACE IDWriteFontFamily 352 | DECLARE_INTERFACE_(IDWriteFontFamily,IDWriteFontList) 353 | { 354 | BEGIN_INTERFACE 355 | 356 | #ifndef __cplusplus 357 | /* IUnknown methods */ 358 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 359 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 360 | STDMETHOD_(ULONG, Release)(THIS) PURE; 361 | 362 | /* IDWriteFontList methods */ 363 | STDMETHOD(GetFontCollection)(THIS_ 364 | IDWriteFontCollection** fontCollection) PURE; 365 | 366 | STDMETHOD_(UINT32, GetFontCount)(THIS) PURE; 367 | 368 | STDMETHOD(GetFont)(THIS_ 369 | UINT32 index, 370 | IDWriteFont **font) PURE; 371 | #endif 372 | 373 | /* IDWriteFontFamily methods */ 374 | STDMETHOD(GetFamilyNames)(THIS_ 375 | IDWriteLocalizedStrings **names) PURE; 376 | 377 | /* rest dropped */ 378 | END_INTERFACE 379 | }; 380 | #ifdef COBJMACROS 381 | #define IDWriteFontFamily_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) 382 | #define IDWriteFontFamily_AddRef(This) (This)->lpVtbl->AddRef(This) 383 | #define IDWriteFontFamily_Release(This) (This)->lpVtbl->Release(This) 384 | #define IDWriteFontFamily_GetFont(This,index,font) (This)->lpVtbl->GetFont(This,index,font) 385 | #define IDWriteFontFamily_GetFontCount(This) (This)->lpVtbl->GetFontCount(This) 386 | #define IDWriteFontFamily_GetFamilyNames(This,names) (This)->lpVtbl->GetFamilyNames(This,names) 387 | #endif /*COBJMACROS*/ 388 | 389 | #undef INTERFACE 390 | #define INTERFACE IDWriteFontFile 391 | DECLARE_INTERFACE_(IDWriteFontFile,IUnknown) 392 | { 393 | BEGIN_INTERFACE 394 | 395 | #ifndef __cplusplus 396 | /* IUnknown methods */ 397 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 398 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 399 | STDMETHOD_(ULONG, Release)(THIS) PURE; 400 | #endif 401 | 402 | /* IDWriteFontFile methods */ 403 | STDMETHOD(GetReferenceKey)(THIS_ 404 | void const **fontFileReferenceKey, 405 | UINT32 *fontFileReferenceKeySize) PURE; 406 | 407 | STDMETHOD(GetLoader)(THIS_ 408 | IDWriteFontFileLoader **fontFileLoader) PURE; 409 | 410 | /* rest dropped */ 411 | END_INTERFACE 412 | }; 413 | #ifdef COBJMACROS 414 | #define IDWriteFontFile_Release(This) (This)->lpVtbl->Release(This) 415 | #define IDWriteFontFile_GetLoader(This,fontFileLoader) (This)->lpVtbl->GetLoader(This,fontFileLoader) 416 | #define IDWriteFontFile_GetReferenceKey(This,fontFileReferenceKey,fontFileReferenceKeySize) (This)->lpVtbl->GetReferenceKey(This,fontFileReferenceKey,fontFileReferenceKeySize) 417 | #endif /*COBJMACROS*/ 418 | 419 | #undef INTERFACE 420 | #define INTERFACE IDWriteFontFileLoader 421 | DECLARE_INTERFACE_(IDWriteFontFileLoader,IUnknown) 422 | { 423 | BEGIN_INTERFACE 424 | 425 | #ifndef __cplusplus 426 | /* IUnknown methods */ 427 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 428 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 429 | STDMETHOD_(ULONG, Release)(THIS) PURE; 430 | #endif 431 | 432 | /* IDWriteFontFileLoader methods */ 433 | STDMETHOD(CreateStreamFromKey)(THIS_ 434 | void const *fontFileReferenceKey, 435 | UINT32 fontFileReferenceKeySize, 436 | IDWriteFontFileStream **fontFileStream) PURE; 437 | 438 | END_INTERFACE 439 | }; 440 | #ifdef COBJMACROS 441 | #define IDWriteFontFileLoader_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) 442 | #define IDWriteFontFileLoader_AddRef(This) (This)->lpVtbl->AddRef(This) 443 | #define IDWriteFontFileLoader_Release(This) (This)->lpVtbl->Release(This) 444 | #define IDWriteFontFileLoader_CreateStreamFromKey(This,fontFileReferenceKey,fontFileReferenceKeySize,fontFileStream) (This)->lpVtbl->CreateStreamFromKey(This,fontFileReferenceKey,fontFileReferenceKeySize,fontFileStream) 445 | #endif /*COBJMACROS*/ 446 | 447 | #undef INTERFACE 448 | #define INTERFACE IDWriteFontFileStream 449 | DECLARE_INTERFACE_(IDWriteFontFileStream,IUnknown) 450 | { 451 | BEGIN_INTERFACE 452 | 453 | #ifndef __cplusplus 454 | /* IUnknown methods */ 455 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 456 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 457 | STDMETHOD_(ULONG, Release)(THIS) PURE; 458 | #endif 459 | 460 | /* IDWriteFontFileStream methods */ 461 | STDMETHOD(ReadFileFragment)(THIS_ 462 | void const **fragmentStart, 463 | UINT64 fileOffset, 464 | UINT64 fragmentSize, 465 | void** fragmentContext) PURE; 466 | 467 | STDMETHOD_(void, ReleaseFileFragment)(THIS_ 468 | void *fragmentContext) PURE; 469 | 470 | STDMETHOD(GetFileSize)(THIS_ 471 | UINT64 *fileSize) PURE; 472 | 473 | STDMETHOD(GetLastWriteTime)(THIS_ 474 | UINT64 *lastWriteTime) PURE; 475 | 476 | END_INTERFACE 477 | }; 478 | #ifdef COBJMACROS 479 | #define IDWriteFontFileStream_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) 480 | #define IDWriteFontFileStream_AddRef(This) (This)->lpVtbl->AddRef(This) 481 | #define IDWriteFontFileStream_Release(This) (This)->lpVtbl->Release(This) 482 | #define IDWriteFontFileStream_GetFileSize(This,fileSize) (This)->lpVtbl->GetFileSize(This,fileSize) 483 | #define IDWriteFontFileStream_ReadFileFragment(This,fragmentStart,fileOffset,fragmentSize,fragmentContext) (This)->lpVtbl->ReadFileFragment(This,fragmentStart,fileOffset,fragmentSize,fragmentContext) 484 | #define IDWriteFontFileStream_ReleaseFileFragment(This,fragmentContext) (This)->lpVtbl->ReleaseFileFragment(This,fragmentContext) 485 | #endif /*COBJMACROS*/ 486 | 487 | #undef INTERFACE 488 | #define INTERFACE IDWriteLocalizedStrings 489 | DECLARE_INTERFACE_(IDWriteLocalizedStrings,IUnknown) 490 | { 491 | BEGIN_INTERFACE 492 | 493 | #ifndef __cplusplus 494 | /* IUnknown methods */ 495 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 496 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 497 | STDMETHOD_(ULONG, Release)(THIS) PURE; 498 | #endif 499 | 500 | /* IDWriteLocalizedStrings methods */ 501 | STDMETHOD_(UINT32, GetCount)(THIS) PURE; 502 | 503 | STDMETHOD(dummy1)(THIS); 504 | STDMETHOD(dummy2)(THIS); 505 | STDMETHOD(dummy3)(THIS); 506 | STDMETHOD(dummy4)(THIS); 507 | 508 | STDMETHOD(GetString)(THIS_ 509 | UINT32 index, 510 | WCHAR *stringBuffer, 511 | UINT32 size) PURE; 512 | 513 | END_INTERFACE 514 | }; 515 | #ifdef COBJMACROS 516 | #define IDWriteLocalizedStrings_Release(This) (This)->lpVtbl->Release(This) 517 | #define IDWriteLocalizedStrings_GetCount(This) (This)->lpVtbl->GetCount(This) 518 | #define IDWriteLocalizedStrings_GetString(This,index,stringBuffer,size) (This)->lpVtbl->GetString(This,index,stringBuffer,size) 519 | #endif /*COBJMACROS*/ 520 | 521 | #undef INTERFACE 522 | #define INTERFACE IDWriteTextFormat 523 | DECLARE_INTERFACE_(IDWriteTextFormat,IUnknown) 524 | { 525 | BEGIN_INTERFACE 526 | 527 | #ifndef __cplusplus 528 | /* IUnknown methods */ 529 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 530 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 531 | STDMETHOD_(ULONG, Release)(THIS) PURE; 532 | #endif 533 | 534 | /* IDWriteTextFormat methods */ 535 | /* rest dropped */ 536 | END_INTERFACE 537 | }; 538 | #ifdef COBJMACROS 539 | #define IDWriteTextFormat_Release(This) (This)->lpVtbl->Release(This) 540 | #endif /*COBJMACROS*/ 541 | 542 | #undef INTERFACE 543 | #define INTERFACE IDWriteTextLayout 544 | DECLARE_INTERFACE_(IDWriteTextLayout,IDWriteTextFormat) 545 | { 546 | BEGIN_INTERFACE 547 | 548 | #ifndef __cplusplus 549 | /* IUnknown methods */ 550 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 551 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 552 | STDMETHOD_(ULONG, Release)(THIS) PURE; 553 | 554 | /* IDWriteTextFormat methods */ 555 | STDMETHOD(dummy1)(THIS); 556 | STDMETHOD(dummy2)(THIS); 557 | STDMETHOD(dummy3)(THIS); 558 | STDMETHOD(dummy4)(THIS); 559 | STDMETHOD(dummy5)(THIS); 560 | STDMETHOD(dummy6)(THIS); 561 | STDMETHOD(dummy7)(THIS); 562 | STDMETHOD(dummy8)(THIS); 563 | STDMETHOD(dummy9)(THIS); 564 | STDMETHOD(dummy10)(THIS); 565 | STDMETHOD(dummy11)(THIS); 566 | STDMETHOD(dummy12)(THIS); 567 | STDMETHOD(dummy13)(THIS); 568 | STDMETHOD(dummy14)(THIS); 569 | STDMETHOD(dummy15)(THIS); 570 | STDMETHOD(dummy16)(THIS); 571 | STDMETHOD(dummy17)(THIS); 572 | STDMETHOD(dummy18)(THIS); 573 | STDMETHOD(dummy19)(THIS); 574 | STDMETHOD(dummy20)(THIS); 575 | STDMETHOD(dummy21)(THIS); 576 | STDMETHOD(dummy22)(THIS); 577 | STDMETHOD(dummy23)(THIS); 578 | STDMETHOD(dummy24)(THIS); 579 | STDMETHOD(dummy25)(THIS); 580 | #endif 581 | 582 | /* IDWriteTextLayout methods */ 583 | STDMETHOD(dummy26)(THIS); 584 | STDMETHOD(dummy27)(THIS); 585 | STDMETHOD(dummy28)(THIS); 586 | STDMETHOD(dummy29)(THIS); 587 | STDMETHOD(dummy30)(THIS); 588 | STDMETHOD(dummy31)(THIS); 589 | STDMETHOD(dummy32)(THIS); 590 | STDMETHOD(dummy33)(THIS); 591 | STDMETHOD(dummy34)(THIS); 592 | STDMETHOD(dummy35)(THIS); 593 | STDMETHOD(dummy36)(THIS); 594 | STDMETHOD(dummy37)(THIS); 595 | STDMETHOD(dummy38)(THIS); 596 | STDMETHOD(dummy39)(THIS); 597 | STDMETHOD(dummy40)(THIS); 598 | STDMETHOD(dummy41)(THIS); 599 | STDMETHOD(dummy42)(THIS); 600 | STDMETHOD(dummy43)(THIS); 601 | STDMETHOD(dummy44)(THIS); 602 | STDMETHOD(dummy45)(THIS); 603 | STDMETHOD(dummy46)(THIS); 604 | STDMETHOD(dummy47)(THIS); 605 | STDMETHOD(dummy48)(THIS); 606 | STDMETHOD(dummy49)(THIS); 607 | STDMETHOD(dummy50)(THIS); 608 | STDMETHOD(dummy51)(THIS); 609 | STDMETHOD(dummy52)(THIS); 610 | STDMETHOD(dummy53)(THIS); 611 | STDMETHOD(dummy54)(THIS); 612 | STDMETHOD(dummy55)(THIS); 613 | STDMETHOD(Draw)(THIS_ 614 | void *clientDrawingContext, 615 | IDWriteTextRenderer *renderer, 616 | FLOAT originX, 617 | FLOAT originY) PURE; 618 | /* rest dropped */ 619 | END_INTERFACE 620 | }; 621 | #ifdef COBJMACROS 622 | #define IDWriteTextLayout_Release(This) (This)->lpVtbl->Release(This) 623 | #define IDWriteTextLayout_Draw(This,clientDrawingContext,renderer,originX,originY) (This)->lpVtbl->Draw(This,clientDrawingContext,renderer,originX,originY) 624 | #endif /*COBJMACROS*/ 625 | 626 | #undef INTERFACE 627 | #define INTERFACE IDWriteTextRenderer 628 | DECLARE_INTERFACE_(IDWriteTextRenderer,IDWritePixelSnapping) 629 | { 630 | BEGIN_INTERFACE 631 | 632 | #ifndef __cplusplus 633 | /* IUnknown methods */ 634 | STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; 635 | STDMETHOD_(ULONG, AddRef)(THIS) PURE; 636 | STDMETHOD_(ULONG, Release)(THIS) PURE; 637 | 638 | /* IDWritePixelSnapping methods */ 639 | STDMETHOD(IsPixelSnappingDisabled)(THIS_ 640 | void *clientDrawingContext, 641 | BOOL *isDisabled) PURE; 642 | STDMETHOD(GetCurrentTransform)(THIS_ 643 | void *clientDrawingContext, 644 | DWRITE_MATRIX *transform) PURE; 645 | STDMETHOD(GetPixelsPerDip)(THIS_ 646 | void *clientDrawingContext, 647 | FLOAT *pixelsPerDip) PURE; 648 | #endif 649 | 650 | /* IDWriteTextRenderer methods */ 651 | STDMETHOD(DrawGlyphRun)(THIS_ 652 | void *clientDrawingContext, 653 | FLOAT baselineOriginX, 654 | FLOAT baselineOriginY, 655 | DWRITE_MEASURING_MODE measuringMode, 656 | DWRITE_GLYPH_RUN const *glyphRun, 657 | DWRITE_GLYPH_RUN_DESCRIPTION const *glyphRunDescription, 658 | IUnknown* clientDrawingEffect) PURE; 659 | STDMETHOD(DrawUnderline)(THIS_ 660 | void *clientDrawingContext, 661 | FLOAT baselineOriginX, 662 | FLOAT baselineOriginY, 663 | DWRITE_UNDERLINE const *underline, 664 | IUnknown *clientDrawingEffect) PURE; 665 | STDMETHOD(DrawStrikethrough)(THIS_ 666 | void *clientDrawingContext, 667 | FLOAT baselineOriginX, 668 | FLOAT baselineOriginY, 669 | DWRITE_STRIKETHROUGH const *strikethrough, 670 | IUnknown* clientDrawingEffect) PURE; 671 | STDMETHOD(DrawInlineObject)(THIS_ 672 | void *clientDrawingContext, 673 | FLOAT originX, 674 | FLOAT originY, 675 | IDWriteInlineObject *inlineObject, 676 | BOOL isSideways, 677 | BOOL isRightToLeft, 678 | IUnknown *clientDrawingEffect) PURE; 679 | 680 | END_INTERFACE 681 | }; 682 | 683 | DEFINE_GUID(IID_IDWriteFactory, 0xb859ee5a,0xd838,0x4b5b,0xa2,0xe8,0x1a,0xdc,0x7d,0x93,0xdb,0x48); 684 | DEFINE_GUID(IID_IDWritePixelSnapping, 0xeaf3a2da,0xecf4,0x4d24,0xb6,0x44,0xb3,0x4f,0x68,0x42,0x02,0x4b); 685 | DEFINE_GUID(IID_IDWriteTextRenderer, 0xef8a8135,0x5cc6,0x45fe,0x88,0x25,0xc5,0xa0,0x72,0x4e,0xb8,0x19); 686 | 687 | #endif /* __INC_DWRITE__ */ 688 | -------------------------------------------------------------------------------- /FontLoaderSub/exporter.c: -------------------------------------------------------------------------------- 1 | #include "exporter.h" 2 | 3 | #include 4 | 5 | #define C_SAVE_RELEASE(obj) \ 6 | do { \ 7 | if ((obj) != NULL) { \ 8 | (obj)->lpVtbl->Release((obj)); \ 9 | obj = NULL; \ 10 | } \ 11 | } while (0) 12 | 13 | typedef struct LoadedFontEnumCtx { 14 | const IEnumShellItemsVtbl *lpVtbl; 15 | FL_AppCtx *app; 16 | IShellItem *root; 17 | size_t i; 18 | ULONG volatile ref; 19 | } LoadedFontEnumCtx; 20 | 21 | static ULONG STDMETHODCALLTYPE LFEC_AddRef(IEnumShellItems *This); 22 | 23 | static HRESULT STDMETHODCALLTYPE 24 | LFEC_QueryInterface(IEnumShellItems *This, REFIID riid, void **out) { 25 | LoadedFontEnumCtx *c = (LoadedFontEnumCtx *)This; 26 | REFIID unk = &IID_IUnknown; 27 | REFIID esi = &IID_IEnumShellItems; 28 | if (IsEqualGUID(riid, unk) || IsEqualGUID(riid, esi)) { 29 | LFEC_AddRef(This); 30 | *out = (void *)c; 31 | return S_OK; 32 | } else { 33 | return E_NOINTERFACE; 34 | } 35 | } 36 | 37 | static ULONG STDMETHODCALLTYPE LFEC_AddRef(IEnumShellItems *This) { 38 | LoadedFontEnumCtx *c = (LoadedFontEnumCtx *)This; 39 | return InterlockedIncrement(&c->ref); 40 | } 41 | 42 | static ULONG STDMETHODCALLTYPE LFEC_Release(IEnumShellItems *This) { 43 | LoadedFontEnumCtx *c = (LoadedFontEnumCtx *)This; 44 | ULONG ret = InterlockedDecrement(&c->ref); 45 | if (ret == 0) { 46 | c->root->lpVtbl->Release(c->root); 47 | c->app->alloc->alloc(c, 0, c->app->alloc->arg); 48 | } 49 | return ret; 50 | } 51 | 52 | static HRESULT LFEC_NextOne(LoadedFontEnumCtx *c, IShellItem **item) { 53 | FL_LoaderCtx *fl = &c->app->loader; 54 | while (c->i != fl->loaded_font.n) { 55 | FL_FontMatch *data = (FL_FontMatch *)fl->loaded_font.data; 56 | FL_FontMatch *m = &data[c->i]; 57 | c->i++; 58 | if (m->filename != NULL && (m->flag & FL_LOAD_DUP) == 0) { 59 | if (item != NULL) { 60 | return SHCreateItemFromRelativeName( 61 | c->root, m->filename, NULL, &IID_IShellItem, (void **)item); 62 | } else { 63 | return S_OK; // simulate create success 64 | } 65 | } 66 | } 67 | return S_FALSE; // no more items 68 | } 69 | 70 | static HRESULT STDMETHODCALLTYPE LFEC_Next( 71 | IEnumShellItems *This, 72 | ULONG celt, 73 | IShellItem **rgelt, 74 | ULONG *pceltFetched) { 75 | LoadedFontEnumCtx *c = (LoadedFontEnumCtx *)This; 76 | ULONG got = 0; 77 | HRESULT hr = S_FALSE; 78 | for (ULONG i = 0; i != celt; i++) { 79 | hr = LFEC_NextOne(c, rgelt ? &rgelt[got] : NULL); 80 | if (hr == S_FALSE) { 81 | // no more left 82 | break; 83 | } else if (FAILED(hr)) { 84 | // should hide something 85 | break; 86 | } else { 87 | got++; 88 | } 89 | } 90 | if (pceltFetched != NULL) { 91 | *pceltFetched = got; 92 | } 93 | return got > 0 ? S_OK : hr; 94 | } 95 | 96 | static HRESULT STDMETHODCALLTYPE LFEC_Skip(IEnumShellItems *This, ULONG celt) { 97 | return LFEC_Next(This, celt, NULL, NULL); 98 | } 99 | 100 | static HRESULT STDMETHODCALLTYPE LFEC_Reset(IEnumShellItems *This) { 101 | LoadedFontEnumCtx *c = (LoadedFontEnumCtx *)This; 102 | c->i = 0; 103 | return S_OK; 104 | } 105 | 106 | static HRESULT STDMETHODCALLTYPE 107 | LFEC_Clone(IEnumShellItems *This, IEnumShellItems **ppenum) { 108 | LoadedFontEnumCtx *c = (LoadedFontEnumCtx *)This; 109 | LoadedFontEnumCtx *that = (LoadedFontEnumCtx *)c->app->alloc->alloc( 110 | NULL, sizeof *c, c->app->alloc->arg); 111 | if (that == NULL) { 112 | return E_OUTOFMEMORY; 113 | } 114 | that->lpVtbl = c->lpVtbl; 115 | that->app = c->app; 116 | that->root = c->root; 117 | that->i = c->i; 118 | that->ref = 1; 119 | c->root->lpVtbl->AddRef(c->root); 120 | *ppenum = (IEnumShellItems *)that; 121 | return S_OK; 122 | } 123 | 124 | static const IEnumShellItemsVtbl kLFEC_Verb = { 125 | .QueryInterface = LFEC_QueryInterface, 126 | .AddRef = LFEC_AddRef, 127 | .Release = LFEC_Release, 128 | .Next = LFEC_Next, 129 | .Skip = LFEC_Skip, 130 | .Reset = LFEC_Reset, 131 | .Clone = LFEC_Clone, 132 | }; 133 | 134 | static HRESULT LFEC_Create(FL_AppCtx *app, IEnumShellItems **ppenum) { 135 | FL_LoaderCtx *fl = &app->loader; 136 | WCHAR font_path_hack = 0; 137 | WCHAR *font_path = (WCHAR *)str_db_get(&fl->font_path, 0); 138 | if (font_path == NULL) 139 | return E_FAIL; 140 | if (font_path[0] == L'\\' && font_path[1] == L'\\' && font_path[2] == L'?' && 141 | font_path[3] == L'\\') { 142 | // case 1: \\?\E:\... -> E:\... 143 | font_path += 4; 144 | if (font_path[0] == L'U' && font_path[1] == L'N' && font_path[2] == L'C' && 145 | font_path[3] == L'\\') { 146 | // case 2: \\?\UNC\tsclient\... -> \\tsclient\... 147 | // hack the first backslash 148 | font_path += 2; 149 | font_path_hack = font_path[0]; 150 | font_path[0] = L'\\'; 151 | } 152 | } 153 | IShellItem *dir_root = NULL; 154 | HRESULT hr = SHCreateItemFromParsingName( 155 | font_path, NULL, &IID_IShellItem, (void **)&dir_root); 156 | if (font_path_hack) { 157 | font_path[0] = font_path_hack; 158 | } 159 | if (FAILED(hr)) 160 | return hr; 161 | 162 | LoadedFontEnumCtx *that = (LoadedFontEnumCtx *)app->alloc->alloc( 163 | NULL, sizeof *that, app->alloc->arg); 164 | if (that == NULL) { 165 | dir_root->lpVtbl->Release(dir_root); 166 | return E_OUTOFMEMORY; 167 | } 168 | that->lpVtbl = &kLFEC_Verb; 169 | that->app = app; 170 | that->root = dir_root; 171 | that->i = 0; 172 | that->ref = 1; 173 | *ppenum = (IEnumShellItems *)that; 174 | return S_OK; 175 | } 176 | 177 | int ExportLoadedFonts(HWND hWnd, FL_AppCtx *c) { 178 | int succ = 0; 179 | HRESULT hr; 180 | IFileDialog *pfd = NULL; 181 | FILEOPENDIALOGOPTIONS options; 182 | IShellItem *dest = NULL; 183 | IEnumShellItems *font_enum = NULL; 184 | LPWSTR path_name = NULL; 185 | IFileOperation *file_opt = NULL; 186 | 187 | do { 188 | // prepare "Select folder" dialog 189 | hr = CoCreateInstance( 190 | &CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, 191 | (void **)&pfd); 192 | if (FAILED(hr)) 193 | break; 194 | hr = pfd->lpVtbl->GetOptions(pfd, &options); 195 | if (FAILED(hr)) 196 | break; 197 | hr = pfd->lpVtbl->SetOptions(pfd, options | FOS_PICKFOLDERS); 198 | if (FAILED(hr)) 199 | break; 200 | hr = pfd->lpVtbl->Show(pfd, hWnd); 201 | if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { 202 | // cancelled 203 | succ = 1; 204 | break; 205 | } else if (FAILED(hr)) 206 | break; 207 | hr = pfd->lpVtbl->GetResult(pfd, &dest); 208 | if (FAILED(hr)) 209 | break; 210 | SIGDN dn = SIGDN_FILESYSPATH; 211 | hr = dest->lpVtbl->GetDisplayName(dest, dn, &path_name); 212 | if (FAILED(hr)) 213 | break; 214 | 215 | // prepare font list and copy 216 | hr = LFEC_Create(c, &font_enum); 217 | if (FAILED(hr)) 218 | break; 219 | hr = CoCreateInstance( 220 | &CLSID_FileOperation, NULL, CLSCTX_ALL, &IID_IFileOperation, 221 | (void **)&file_opt); 222 | if (FAILED(hr)) 223 | break; 224 | hr = file_opt->lpVtbl->SetOwnerWindow(file_opt, hWnd); 225 | if (FAILED(hr)) 226 | break; 227 | hr = file_opt->lpVtbl->CopyItems(file_opt, (IUnknown *)font_enum, dest); 228 | if (FAILED(hr)) 229 | break; 230 | hr = file_opt->lpVtbl->PerformOperations(file_opt); 231 | if (FAILED(hr)) 232 | break; 233 | 234 | ShellExecute(NULL, NULL, path_name, NULL, NULL, SW_SHOW); 235 | succ = 1; 236 | } while (0); 237 | 238 | C_SAVE_RELEASE(pfd); 239 | C_SAVE_RELEASE(dest); 240 | C_SAVE_RELEASE(font_enum); 241 | C_SAVE_RELEASE(file_opt); 242 | CoTaskMemFree(path_name); 243 | if (!succ) { 244 | TaskDialog( 245 | NULL, c->hInst, MAKEINTRESOURCE(IDS_APP_NAME_VER), L"Error...", NULL, 246 | TDCBF_CLOSE_BUTTON, TD_ERROR_ICON, NULL); 247 | } 248 | return 0; 249 | } 250 | -------------------------------------------------------------------------------- /FontLoaderSub/exporter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "main.h" 4 | 5 | int ExportLoadedFonts(HWND hWnd, FL_AppCtx *c); 6 | -------------------------------------------------------------------------------- /FontLoaderSub/font_loader.c: -------------------------------------------------------------------------------- 1 | #include "font_loader.h" 2 | 3 | #include 4 | #include 5 | #include "ass_string.h" 6 | #include "ass_parser.h" 7 | #include "path.h" 8 | #include "mock_config.h" 9 | #include "tim_sort.h" 10 | #include "util.h" 11 | 12 | #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) 13 | 14 | int fl_init(FL_LoaderCtx *c, allocator_t *alloc) { 15 | int r = FL_OK; 16 | zmemset(c, 0, sizeof *c); 17 | c->alloc = alloc; 18 | 19 | do { 20 | vec_init(&c->loaded_font, sizeof(FL_FontMatch), alloc); 21 | str_db_init(&c->sub_font, alloc, 0, 1); 22 | str_db_init(&c->font_path, alloc, 0, 0); 23 | str_db_init(&c->walk_path, alloc, 0, 0); 24 | 25 | c->event_cancel = CreateEvent(NULL, TRUE, FALSE, NULL); 26 | if (!c->event_cancel) { 27 | r = FL_OS_ERROR; 28 | break; 29 | } 30 | const NTSTATUS status = BCryptOpenAlgorithmProvider( 31 | &c->hash_alg, BCRYPT_SHA256_ALGORITHM, NULL, 0); 32 | if (!NT_SUCCESS(status)) { 33 | r = FL_OS_ERROR; 34 | break; 35 | } 36 | } while (0); 37 | 38 | if (r != FL_OK) 39 | fl_free(c); 40 | return r; 41 | } 42 | 43 | int fl_free(FL_LoaderCtx *c) { 44 | CloseHandle(c->event_cancel); 45 | BCryptCloseAlgorithmProvider(c->hash_alg, 0); 46 | vec_free(&c->loaded_font); 47 | str_db_free(&c->sub_font); 48 | str_db_free(&c->font_path); 49 | str_db_free(&c->walk_path); 50 | fs_free(c->font_set); 51 | 52 | return FL_OK; 53 | } 54 | 55 | int fl_cancel(FL_LoaderCtx *c) { 56 | return SetEvent(c->event_cancel) ? FL_OK : FL_OS_ERROR; 57 | } 58 | 59 | static int fl_check_cancel(FL_LoaderCtx *c) { 60 | if (WaitForSingleObject(c->event_cancel, 0) != WAIT_TIMEOUT) 61 | return FL_OS_ERROR; 62 | return FL_OK; 63 | } 64 | 65 | static int fl_sub_font_callback(const wchar_t *font, size_t cch, void *arg) { 66 | FL_LoaderCtx *c = arg; 67 | if (cch != 0) { 68 | if (font[0] == '@') { 69 | // skip prefix '@' 70 | font++; 71 | cch--; 72 | } 73 | if (cch == 0) 74 | return FL_OK; 75 | 76 | const size_t pos = str_db_tell(&c->sub_font); 77 | const wchar_t *insert = str_db_push_u16_le(&c->sub_font, font, cch); 78 | if (insert == NULL) { 79 | return FL_OUT_OF_MEMORY; 80 | } 81 | 82 | const wchar_t *match = str_db_str(&c->sub_font, 0, insert); 83 | if (match == insert) { 84 | // not duplicated 85 | c->num_sub_font++; 86 | } else { 87 | str_db_seek(&c->sub_font, pos); 88 | } 89 | } 90 | return FL_OK; 91 | } 92 | 93 | static int 94 | fl_walk_sub_callback(const wchar_t *path, WIN32_FIND_DATA *data, void *arg) { 95 | FL_LoaderCtx *c = arg; 96 | const int r = fl_check_cancel(c); 97 | if (r != FL_OK) 98 | return r; 99 | 100 | const size_t len = ass_strlen(path); 101 | const wchar_t *ext = path + len - 4; 102 | const int match_attr = 103 | !(data->dwFileAttributes & 104 | (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_DIRECTORY)); 105 | const int match_size = 106 | (data->nFileSizeHigh == 0 && data->nFileSizeLow <= 64 * 1024 * 1024); 107 | const int match_ext = (len > 4) && (ass_strncasecmp(ext, L".ass", 4) == 0 || 108 | ass_strncasecmp(ext, L".ssa", 4) == 0); 109 | if (!(match_attr && match_size && match_ext)) 110 | return FL_OK; 111 | 112 | memmap_t map; 113 | wchar_t *content = NULL; 114 | size_t cch = 0; 115 | do { 116 | FlMemMap(path, &map); 117 | if (!map.data) 118 | break; 119 | content = FlTextDecode(map.data, map.size, &cch, c->alloc); 120 | if (content == NULL) 121 | break; 122 | 123 | c->num_sub++; 124 | ass_process_data(content, cch, fl_sub_font_callback, c); 125 | 126 | if (MOCK_DELAY_SUB) 127 | Sleep(MOCK_DELAY_SUB); 128 | } while (0); 129 | 130 | FlMemUnmap(&map); 131 | c->alloc->alloc(content, 0, c->alloc->arg); 132 | 133 | return FL_OK; 134 | } 135 | 136 | int fl_add_subs(FL_LoaderCtx *c, const wchar_t *path) { 137 | int r; 138 | do { 139 | str_db_seek(&c->walk_path, 0); 140 | r = FlResolvePath(path, &c->walk_path); 141 | if (r == FL_OS_ERROR) { 142 | // ignore error 143 | r = FL_OK; 144 | break; 145 | } else if (r != FL_OK) { 146 | break; 147 | } 148 | 149 | r = FlWalkDirStr(&c->walk_path, fl_walk_sub_callback, c); 150 | if (r != FL_OK) 151 | break; 152 | } while (0); 153 | return r; 154 | } 155 | 156 | static int 157 | fl_walk_font_callback(const wchar_t *path, WIN32_FIND_DATA *data, void *arg) { 158 | FL_LoaderCtx *c = arg; 159 | const int r = fl_check_cancel(c); 160 | if (r != FL_OK) 161 | return r; 162 | 163 | const size_t len = ass_strlen(path); 164 | const wchar_t *ext = path + len - 4; 165 | const int match_attr = 166 | !(data->dwFileAttributes & 167 | (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_DIRECTORY)); 168 | const int match_ext = (len > 4) && (ass_strncasecmp(ext, L".ttc", 4) == 0 || 169 | ass_strncasecmp(ext, L".otf", 4) == 0 || 170 | ass_strncasecmp(ext, L".ttf", 4) == 0); 171 | if (!(match_attr && match_ext)) 172 | return FL_OK; 173 | 174 | // try load the file 175 | memmap_t map; 176 | FlMemMap(path, &map); 177 | if (map.data) { 178 | // skip the base path + '\' 179 | const wchar_t *tag = path + str_db_tell(&c->font_path) + 1; 180 | fs_add_font(c->font_set, tag, map.data, map.size); 181 | FlMemUnmap(&map); 182 | } 183 | return FL_OK; 184 | } 185 | 186 | static void 187 | fl_blacklist_parse(FL_LoaderCtx *c, const wchar_t *data, size_t cch) { 188 | const wchar_t *p = data; 189 | const wchar_t *eos = data + cch; 190 | while (p != eos) { 191 | // skip blank lines 192 | while (p != eos && ass_is_eol(*p)) 193 | ++p; 194 | // find end of the line 195 | const wchar_t *q = p; 196 | while (q != eos && !ass_is_eol(*q)) 197 | ++q; 198 | 199 | fs_blacklist_add(c->font_set, p, q - p); 200 | p = q; 201 | } 202 | } 203 | 204 | static void fl_blacklist_load(FL_LoaderCtx *c, const wchar_t *filename) { 205 | fs_blacklist_clear(c->font_set); 206 | if (!filename) { 207 | return; 208 | } 209 | str_db_seek(&c->walk_path, 0); 210 | if (!str_db_push_u16_le(&c->walk_path, str_db_get(&c->font_path, 0), 0) || 211 | !str_db_push_u16_le(&c->walk_path, L"\\", 1) || 212 | !str_db_push_u16_le(&c->walk_path, filename, 0)) { 213 | return; 214 | } 215 | memmap_t map; 216 | wchar_t *content = NULL; 217 | size_t cch = 0; 218 | do { 219 | FlMemMap(str_db_get(&c->walk_path, 0), &map); 220 | if (!map.data) 221 | break; 222 | content = FlTextDecode(map.data, map.size, &cch, c->alloc); 223 | if (content == NULL) 224 | break; 225 | fl_blacklist_parse(c, content, cch); 226 | 227 | } while ((0)); 228 | 229 | FlMemUnmap(&map); 230 | c->alloc->alloc(content, 0, c->alloc->arg); 231 | } 232 | 233 | int fl_scan_fonts( 234 | FL_LoaderCtx *c, 235 | const wchar_t *path, 236 | const wchar_t *cache, 237 | const wchar_t *black) { 238 | // caller: fl_unload_fonts 239 | 240 | // free previous font set 241 | fs_free(c->font_set); 242 | c->font_set = NULL; 243 | 244 | int r = FlResolvePath(path, &c->font_path); 245 | // if path points to a file, find its parent directory 246 | if (1) { 247 | HANDLE test = CreateFile( 248 | str_db_get(&c->font_path, 0), 0, 249 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, 250 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 251 | if (test != INVALID_HANDLE_VALUE) { 252 | const size_t pos = FlPathParent(&c->font_path); 253 | if (pos) { 254 | str_db_seek(&c->font_path, pos - 1); 255 | wchar_t *buf = (wchar_t *)str_db_get(&c->font_path, 0); 256 | buf[pos - 1] = 0; 257 | } 258 | CloseHandle(test); 259 | } 260 | } 261 | 262 | if (cache) { 263 | if (r == FL_OK) { 264 | // load from cache 265 | str_db_seek(&c->walk_path, 0); 266 | if (!str_db_push_u16_le(&c->walk_path, str_db_get(&c->font_path, 0), 0) || 267 | !str_db_push_u16_le(&c->walk_path, L"\\", 1) || 268 | !str_db_push_u16_le(&c->walk_path, cache, 0)) { 269 | r = FL_OUT_OF_MEMORY; 270 | } 271 | } 272 | if (r == FL_OK) { 273 | r = fs_cache_load(str_db_get(&c->walk_path, 0), c->alloc, &c->font_set); 274 | } 275 | } else { 276 | // search font files 277 | if (r == FL_OK) { 278 | str_db_seek(&c->walk_path, 0); 279 | if (!str_db_push_u16_le(&c->walk_path, str_db_get(&c->font_path, 0), 0)) 280 | r = FL_OUT_OF_MEMORY; 281 | } 282 | if (r == FL_OK) { 283 | r = fs_create(c->alloc, &c->font_set); 284 | } 285 | if (r == FL_OK) { 286 | r = FlWalkDirStr(&c->walk_path, fl_walk_font_callback, c); 287 | } 288 | } 289 | if (r == FL_OK) { 290 | r = fs_build_index(c->font_set); 291 | fl_blacklist_load(c, black); 292 | } 293 | 294 | // failed 295 | if (r != FL_OK) { 296 | fs_free(c->font_set); 297 | c->font_set = NULL; 298 | } 299 | 300 | return r; 301 | } 302 | 303 | int fl_save_cache(FL_LoaderCtx *c, const wchar_t *cache) { 304 | int r = FL_OK; 305 | str_db_seek(&c->walk_path, 0); 306 | if (!str_db_push_u16_le(&c->walk_path, str_db_get(&c->font_path, 0), 0) || 307 | !str_db_push_u16_le(&c->walk_path, L"\\", 1) || 308 | !str_db_push_u16_le(&c->walk_path, cache, 0)) { 309 | r = FL_OUT_OF_MEMORY; 310 | } 311 | 312 | if (r == FL_OK) { 313 | r = fs_cache_dump(c->font_set, str_db_get(&c->walk_path, 0)); 314 | } 315 | return r; 316 | } 317 | 318 | static int CALLBACK enum_fonts( 319 | const LOGFONTW *lfp, 320 | const TEXTMETRICW *tmp, 321 | DWORD fontType, 322 | LPARAM lParam) { 323 | int *r = (int *)lParam; 324 | *r = 1; 325 | return 1; // continue 326 | } 327 | 328 | static int IsFontInstalled(const wchar_t *face) { 329 | if (MOCK_NO_SYS) 330 | return 0; 331 | int found = 0; 332 | HDC dc = GetDC(0); 333 | EnumFontFamilies(dc, face, enum_fonts, (LPARAM)&found); 334 | ReleaseDC(0, dc); 335 | return found; 336 | } 337 | 338 | static int fl_face_loaded(FL_LoaderCtx *c, const wchar_t *face) { 339 | const size_t pos = str_db_tell(&c->walk_path); 340 | FL_FontMatch *data = c->loaded_font.data; 341 | for (size_t i = 0; i != c->loaded_font.n; i++) { 342 | FL_FontMatch *m = &data[i]; 343 | if (m->face == face) 344 | return 1; 345 | } 346 | return 0; 347 | } 348 | 349 | static int fl_file_loaded(FL_LoaderCtx *c, const wchar_t *file) { 350 | const size_t pos = str_db_tell(&c->walk_path); 351 | FL_FontMatch *data = c->loaded_font.data; 352 | for (size_t i = 0; i != c->loaded_font.n; i++) { 353 | FL_FontMatch *m = &data[i]; 354 | if (m->filename == file) 355 | return i; 356 | } 357 | return -1; 358 | } 359 | 360 | static int fl_hash_loaded(FL_LoaderCtx *c, const uint8_t hash[32]) { 361 | const size_t pos = str_db_tell(&c->walk_path); 362 | FL_FontMatch *data = c->loaded_font.data; 363 | for (size_t i = 0; i != c->loaded_font.n; i++) { 364 | FL_FontMatch *m = &data[i]; 365 | if (m->flag & FL_LOAD_OK) { 366 | uint8_t dif = 0; 367 | for (int j = 0; j != 32; j++) { 368 | dif |= m->hash[j] ^ hash[j]; 369 | } 370 | if (!dif) 371 | return i; 372 | } 373 | } 374 | return -1; 375 | } 376 | 377 | static int 378 | fl_calc_hash(FL_LoaderCtx *c, const void *data, size_t size, uint8_t res[32]) { 379 | int ok = 0; 380 | NTSTATUS status; 381 | BCRYPT_HASH_HANDLE hash = NULL; 382 | void *hash_obj = NULL; 383 | DWORD sz_hash_obj = 0; 384 | DWORD sz_data = 0; 385 | allocator_t *alloc = c->alloc; 386 | 387 | do { 388 | status = BCryptGetProperty( 389 | c->hash_alg, BCRYPT_OBJECT_LENGTH, (PBYTE)&sz_hash_obj, 390 | sizeof sz_hash_obj, &sz_data, 0); 391 | if (!NT_SUCCESS(status)) 392 | break; 393 | 394 | hash_obj = alloc->alloc(hash_obj, sz_hash_obj, alloc->arg); 395 | if (hash_obj == NULL) 396 | break; 397 | 398 | status = 399 | BCryptCreateHash(c->hash_alg, &hash, hash_obj, sz_hash_obj, NULL, 0, 0); 400 | if (!NT_SUCCESS(status)) 401 | break; 402 | 403 | status = BCryptHashData(hash, (PBYTE)data, size, 0); 404 | if (!NT_SUCCESS(status)) 405 | break; 406 | 407 | status = BCryptFinishHash(hash, res, 32, 0); 408 | if (!NT_SUCCESS(status)) 409 | break; 410 | 411 | ok = 1; 412 | } while (0); 413 | 414 | BCryptDestroyHash(hash); 415 | alloc->alloc(hash_obj, 0, alloc->arg); 416 | return ok ? FL_OK : FL_OS_ERROR; 417 | } 418 | 419 | static int fl_load_file( 420 | FL_LoaderCtx *c, 421 | const wchar_t *face, 422 | const wchar_t *file, 423 | int *dup) { 424 | int r = FL_OK; 425 | int candidate; 426 | memmap_t map = {0}; 427 | uint8_t hash[32]; 428 | 429 | do { 430 | if (vec_prealloc(&c->loaded_font, 1) == 0) { 431 | r = FL_OUT_OF_MEMORY; 432 | break; 433 | } 434 | 435 | // check 1: if file pointer is loaded 436 | candidate = fl_file_loaded(c, file); 437 | if (candidate != -1) { 438 | *dup = candidate; 439 | r = FL_DUP; 440 | break; 441 | } 442 | 443 | // check 2: hash 444 | str_db_seek(&c->walk_path, 0); 445 | if (!str_db_push_u16_le(&c->walk_path, str_db_get(&c->font_path, 0), 0) || 446 | !str_db_push_u16_le(&c->walk_path, L"\\", 1) || 447 | !str_db_push_u16_le(&c->walk_path, file, 0)) { 448 | r = FL_OUT_OF_MEMORY; 449 | break; 450 | } 451 | 452 | const wchar_t *full_path = str_db_get(&c->walk_path, 0); 453 | FlMemMap(full_path, &map); 454 | if (map.data == NULL) { 455 | r = FL_OS_ERROR; 456 | break; 457 | } 458 | 459 | r = fl_calc_hash(c, map.data, map.size, hash); 460 | if (r != FL_OK) 461 | break; 462 | 463 | candidate = fl_hash_loaded(c, hash); 464 | if (candidate != -1) { 465 | *dup = candidate; 466 | r = FL_DUP; 467 | break; 468 | } 469 | 470 | if (MOCK_FAKE_LOAD) { 471 | if (MOCK_DELAY_FONT) { 472 | Sleep(MOCK_DELAY_FONT); 473 | } 474 | } else if (AddFontResource(full_path) == 0) { 475 | r = FL_OS_ERROR; 476 | break; 477 | } 478 | } while (0); 479 | 480 | FlMemUnmap(&map); 481 | if (r != FL_OUT_OF_MEMORY && r != FL_DUP) { 482 | FL_FontMatch m; 483 | if (r == FL_OK) { 484 | m.flag = FL_LOAD_OK; 485 | c->num_font_loaded++; 486 | } else { 487 | m.flag = FL_LOAD_ERR; 488 | c->num_font_failed++; 489 | } 490 | m.face = face; 491 | m.filename = file; 492 | // copy SHA256 without memcpy 493 | uint64_t *src = (uint64_t *)hash; 494 | uint64_t *dst = (uint64_t *)m.hash; 495 | dst[0] = src[0]; 496 | dst[1] = src[1]; 497 | dst[2] = src[2]; 498 | dst[3] = src[3]; 499 | vec_append(&c->loaded_font, &m, 1); 500 | } 501 | return r; 502 | } 503 | 504 | int fl_load_rec_sort(const void *ptr_a, const void *ptr_b, void *arg) { 505 | const FL_FontMatch *a = ptr_a, *b = ptr_b; 506 | 507 | // sort flag in descent order 508 | const int flag_mask = 16 | 8; // FL_LOAD_ERR | FL_LOAD_MISS 509 | const int df = (b->flag & flag_mask) - (a->flag & flag_mask); 510 | if (df != 0) 511 | return df; 512 | 513 | // sort in filename 514 | if (!a->filename) 515 | return -1; 516 | if (!b->filename) 517 | return 1; 518 | const int ds = FlStrCmpIW(a->filename, b->filename); 519 | if (ds != 0) 520 | return ds; 521 | 522 | // dup comes late 523 | if (b->flag & FL_LOAD_DUP) 524 | return -1; 525 | if (a->flag & FL_LOAD_DUP) 526 | return 1; 527 | 528 | // last resort 529 | return FlStrCmpIW(a->face, b->face); 530 | } 531 | 532 | int fl_load_fonts(FL_LoaderCtx *c) { 533 | // caller: fl_unload_fonts 534 | 535 | int r = FL_OK; 536 | c->num_font_failed = c->num_font_loaded = c->num_font_unmatched = 0; 537 | 538 | // pass 1: scan for existing fonts 539 | size_t pos_it = 0; 540 | const wchar_t *face; 541 | while (r == FL_OK && (face = str_db_next(&c->sub_font, &pos_it)) != NULL) { 542 | if ((r = fl_check_cancel(c)) != FL_OK) 543 | return r; 544 | 545 | if (IsFontInstalled(face)) { 546 | if (vec_prealloc(&c->loaded_font, 1) == 0) 547 | r = FL_OUT_OF_MEMORY; 548 | if (r == FL_OK) { 549 | // FL_FontMatch m = {.flag = FL_OS_LOADED, .face = face}; 550 | FL_FontMatch m; 551 | m.flag = FL_OS_LOADED; 552 | m.face = face; 553 | m.filename = NULL; 554 | vec_append(&c->loaded_font, &m, 1); 555 | } 556 | } 557 | } 558 | 559 | // pass 2: load the missing font 560 | const size_t sys_fonts = c->loaded_font.n; 561 | pos_it = 0; 562 | while (r != FL_OUT_OF_MEMORY && 563 | (face = str_db_next(&c->sub_font, &pos_it)) != NULL) { 564 | if (fl_face_loaded(c, face)) 565 | continue; 566 | if (vec_prealloc(&c->loaded_font, 1) == 0) { 567 | r = FL_OUT_OF_MEMORY; 568 | break; 569 | } 570 | 571 | FS_Iter it; 572 | if (!fs_iter_new(c->font_set, face, &it)) { 573 | // FL_FontMatch m = {.flag = FL_LOAD_MISS, .face = face}; 574 | FL_FontMatch m; 575 | m.flag = FL_LOAD_MISS; 576 | m.face = face; 577 | m.filename = NULL; 578 | vec_append(&c->loaded_font, &m, 1); 579 | c->num_font_unmatched++; 580 | } else { 581 | int num_loaded = 0; 582 | int num_dup = 0; 583 | int num_total = 0; 584 | int dup_candidate = 0; 585 | do { 586 | if ((r = fl_check_cancel(c)) != FL_OK) 587 | return r; 588 | 589 | r = fl_load_file(c, face, it.info.tag, &dup_candidate); 590 | num_total++; 591 | if (r == FL_DUP) 592 | num_dup++; 593 | if (r == FL_OK) 594 | num_loaded++; 595 | } while (r != FL_OUT_OF_MEMORY && num_loaded <= 16 && fs_iter_next(&it)); 596 | if (num_dup == num_total) { 597 | // FL_FontMatch m = {.flag = FL_LOAD_DUP, .face = face}; 598 | FL_FontMatch m; 599 | FL_FontMatch *data = c->loaded_font.data; 600 | FL_FontMatch *ref = &data[dup_candidate]; 601 | m.flag = FL_LOAD_DUP | data[dup_candidate].flag; 602 | m.face = face; 603 | m.filename = ref->filename; 604 | vec_append(&c->loaded_font, &m, 1); 605 | } 606 | } 607 | } 608 | tim_sort( 609 | c->loaded_font.data, c->loaded_font.n, c->loaded_font.size, c->alloc, 610 | fl_load_rec_sort, NULL); 611 | 612 | return FL_OK; 613 | } 614 | 615 | int fl_walk_loaded_fonts(FL_LoaderCtx *c, WalkLoadedCallback cb, void *param) { 616 | str_db_seek(&c->walk_path, 0); 617 | const wchar_t *font_path = str_db_get(&c->font_path, 0); 618 | if (font_path == NULL || font_path[0] == 0) 619 | return FL_OK; 620 | if (!str_db_push_u16_le(&c->walk_path, font_path, 0)) 621 | return FL_OUT_OF_MEMORY; 622 | if (!str_db_push_u16_le(&c->walk_path, L"\\", 1)) 623 | return FL_OUT_OF_MEMORY; 624 | 625 | const size_t pos = str_db_tell(&c->walk_path); 626 | FL_FontMatch *data = c->loaded_font.data; 627 | for (size_t i = 0; i != c->loaded_font.n; i++) { 628 | const wchar_t *path = NULL; 629 | FL_FontMatch *m = &data[i]; 630 | str_db_seek(&c->walk_path, pos); 631 | if (m->filename && str_db_push_u16_le(&c->walk_path, m->filename, 0)) { 632 | path = str_db_get(&c->walk_path, 0); 633 | } 634 | // trigger callback 635 | const int ret = cb(c, i, path, param); 636 | if (ret != FL_OK) 637 | return ret; 638 | } 639 | 640 | return FL_OK; 641 | } 642 | 643 | static int 644 | fl_unload_cb(FL_LoaderCtx *c, size_t i, const wchar_t *path, void *param) { 645 | FL_FontMatch *data = c->loaded_font.data; 646 | FL_FontMatch *m = &data[i]; 647 | if (!(m->flag & FL_LOAD_DUP)) { 648 | if (m->flag & FL_LOAD_OK) { 649 | c->num_font_loaded--; 650 | } 651 | RemoveFontResource(path); 652 | if (MOCK_DELAY_FONT) { 653 | Sleep(MOCK_DELAY_FONT); 654 | } 655 | } 656 | return 0; 657 | } 658 | 659 | int fl_unload_fonts(FL_LoaderCtx *c) { 660 | fl_walk_loaded_fonts(c, fl_unload_cb, NULL); 661 | vec_clear(&c->loaded_font); 662 | c->num_font_loaded = 0; 663 | c->num_font_failed = 0; 664 | c->num_font_unmatched = 0; 665 | 666 | return FL_OK; 667 | } 668 | 669 | static int 670 | fl_cache_cb(FL_LoaderCtx *c, size_t i, const wchar_t *path, void *param) { 671 | FL_FontMatch *data = c->loaded_font.data; 672 | FL_FontMatch *m = &data[i]; 673 | if (m->flag & FL_LOAD_DUP) { 674 | return FL_OK; 675 | } 676 | 677 | HANDLE evt_cancel = *(HANDLE *)param; 678 | if (WaitForSingleObject(evt_cancel, 0) != WAIT_TIMEOUT) { 679 | return FL_OS_ERROR; 680 | } 681 | 682 | // read the file 683 | memmap_t mmap; 684 | DWORD tick = 0; 685 | FlMemMap(path, &mmap); 686 | const size_t step = 4 * 1024; 687 | volatile char chksum = 0; 688 | volatile const char *bytes = mmap.data; 689 | for (size_t pos = 0; pos < mmap.size; pos += step) { 690 | const DWORD now = GetTickCount(); 691 | if (now - tick > 10) { 692 | tick = now; 693 | if (WaitForSingleObject(evt_cancel, 0) != WAIT_TIMEOUT) { 694 | // loop will be terminated on next file 695 | break; 696 | } 697 | } 698 | chksum ^= bytes[pos]; 699 | } 700 | FlMemUnmap(&mmap); 701 | return FL_OK; 702 | } 703 | 704 | int fl_cache_fonts(FL_LoaderCtx *c, HANDLE evt_cancel) { 705 | fl_walk_loaded_fonts(c, fl_cache_cb, &evt_cancel); 706 | return FL_OK; 707 | } 708 | -------------------------------------------------------------------------------- /FontLoaderSub/font_loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cstl.h" 4 | #include "util.h" 5 | #include "font_set.h" 6 | 7 | typedef enum { 8 | FL_OS_LOADED = 1, 9 | FL_LOAD_OK = 2, 10 | FL_LOAD_ERR = 16, 11 | FL_LOAD_DUP = 4, 12 | FL_LOAD_MISS = 8 13 | } FL_MatchFlag; 14 | 15 | typedef struct { 16 | FL_MatchFlag flag; 17 | const wchar_t *face; 18 | const wchar_t *filename; 19 | uint8_t hash[32]; 20 | } FL_FontMatch; 21 | 22 | typedef struct { 23 | allocator_t *alloc; 24 | str_db_t sub_font; 25 | str_db_t font_path; 26 | str_db_t walk_path; 27 | FS_Set *font_set; 28 | 29 | uint32_t num_sub; 30 | uint32_t num_sub_font; 31 | uint32_t num_font_loaded; 32 | uint32_t num_font_failed; 33 | uint32_t num_font_unmatched; 34 | 35 | void *event_cancel; 36 | void *hash_alg; 37 | vec_t loaded_font; 38 | } FL_LoaderCtx; 39 | 40 | int fl_init(FL_LoaderCtx *c, allocator_t *alloc); 41 | 42 | int fl_free(FL_LoaderCtx *c); 43 | 44 | int fl_cancel(FL_LoaderCtx *c); 45 | 46 | int fl_add_subs(FL_LoaderCtx *c, const wchar_t *path); 47 | 48 | int fl_scan_fonts( 49 | FL_LoaderCtx *c, 50 | const wchar_t *path, 51 | const wchar_t *cache, 52 | const wchar_t *black); 53 | 54 | int fl_save_cache(FL_LoaderCtx *c, const wchar_t *cache); 55 | 56 | int fl_load_fonts(FL_LoaderCtx *c); 57 | 58 | int fl_unload_fonts(FL_LoaderCtx *c); 59 | 60 | int fl_cache_fonts(FL_LoaderCtx *c, HANDLE evt_cancel); 61 | 62 | typedef int (*WalkLoadedCallback)( 63 | FL_LoaderCtx *c, 64 | size_t i, 65 | const wchar_t *path, 66 | void *param); 67 | 68 | int fl_walk_loaded_fonts(FL_LoaderCtx *c, WalkLoadedCallback cb, void *param); 69 | -------------------------------------------------------------------------------- /FontLoaderSub/font_set.c: -------------------------------------------------------------------------------- 1 | #include "font_set.h" 2 | 3 | #include "cstl.h" 4 | #include "ttf_parser.h" 5 | #include "ass_string.h" 6 | #include "tim_sort.h" 7 | #include "util.h" 8 | 9 | #define MAKE_TAG(a, b, c, d) \ 10 | ((uint32_t)(((uint8_t)(d) << 24)) | (uint32_t)(((uint8_t)(c) << 16)) | \ 11 | (uint32_t)(((uint8_t)(b) << 8)) | (uint32_t)(((uint8_t)(a)))) 12 | 13 | #define KFontDbMagic (MAKE_TAG('f', 'l', 'd', 'd')) 14 | 15 | struct _FS_Set { 16 | allocator_t *alloc; 17 | str_db_t db; 18 | str_db_t blacklist; 19 | FS_Stat stat; 20 | FS_Index *index; 21 | memmap_t map; 22 | }; 23 | 24 | typedef struct { 25 | FS_Set *set; 26 | uint32_t id; 27 | size_t pos_ver; // point to version 28 | size_t pos_face; // point to first face name 29 | uint32_t count_face; // number of discovered face name 30 | uint16_t last_lang_id; 31 | } FS_ParseCtx; 32 | 33 | typedef struct { 34 | uint32_t magic; 35 | FS_Stat stat; 36 | uint32_t size; 37 | } FS_CacheHeader; 38 | 39 | #define kTagVersion L"\tv:" 40 | #define kTagVersionLen (3) 41 | #define kTagFormat L"\tt:" 42 | #define kTagFormatLen (3) 43 | #define kTagError L"\t!!" 44 | #define kTagErrorLen (3) 45 | 46 | static const WCHAR kFsFmtTag[FS_FmtMax][4] = { // format hack 47 | [FS_FmtNone] = L"", 48 | [FS_FmtOTF] = L"otf", 49 | [FS_FmtTTF] = L"ttf", 50 | [FS_FmtTTC] = L"ttc"}; 51 | 52 | static void fs_format_tag_to_str(FS_Format fmt, WCHAR s[4]); 53 | 54 | static int fs_parser_name_cb( 55 | uint32_t font_id, 56 | OTF_NameRecord *r, 57 | const wchar_t *str, 58 | void *arg) { 59 | FS_ParseCtx *c = (FS_ParseCtx *)arg; 60 | FS_Set *s = c->set; 61 | const uint32_t cch = be16(r->length) / sizeof str[0]; 62 | 63 | if (font_id != c->id) { 64 | // new font 65 | c->id = font_id; 66 | c->pos_ver = str_db_tell(&s->db); 67 | c->pos_face = c->pos_ver; 68 | c->last_lang_id = 0; 69 | } 70 | 71 | if (cch == 0) 72 | return FL_OK; 73 | 74 | if (r->name_id == be16(5)) { 75 | // this is a version record 76 | if (c->last_lang_id == 0 || r->lang_id == be16(0x0409)) { 77 | // no previous version record, or encountered English version. 78 | // update/overwrite 79 | str_db_seek(&s->db, c->pos_ver); 80 | if (!str_db_push_prefix(&s->db, kTagVersion, kTagVersionLen)) 81 | return FL_OUT_OF_MEMORY; 82 | const wchar_t *ver = str_db_push_u16_be(&s->db, str, cch); 83 | if (ver == NULL) 84 | return FL_OUT_OF_MEMORY; 85 | 86 | c->last_lang_id = r->lang_id; 87 | c->pos_face = str_db_tell(&s->db); 88 | } 89 | } else { 90 | // first, convert to little endian (and insert into database) 91 | const size_t pos_insert = str_db_tell(&s->db); 92 | const wchar_t *face = str_db_push_u16_be(&s->db, str, cch); 93 | if (face == NULL) 94 | return FL_OUT_OF_MEMORY; 95 | 96 | // check duplication 97 | const wchar_t *prev_face = str_db_str(&s->db, c->pos_face, face); 98 | if (prev_face == face) { 99 | ++c->count_face; 100 | } else { 101 | // duplicated, revert 102 | str_db_seek(&s->db, pos_insert); 103 | } 104 | } 105 | 106 | return FL_OK; 107 | } 108 | 109 | int fs_create(allocator_t *alloc, FS_Set **out) { 110 | int ok = 0; 111 | FS_Set *p = NULL; 112 | do { 113 | p = (FS_Set *)alloc->alloc(p, sizeof *p, alloc->arg); 114 | if (!p) 115 | break; 116 | str_db_init(&p->db, alloc, '\n', 2); 117 | str_db_init(&p->blacklist, alloc, 0, 1); 118 | 119 | p->alloc = alloc; 120 | ok = 1; 121 | } while (0); 122 | 123 | if (!ok) { 124 | alloc->alloc(p, 0, alloc->arg); 125 | p = NULL; 126 | } 127 | *out = p; 128 | return ok ? FL_OK : FL_OUT_OF_MEMORY; 129 | } 130 | 131 | int fs_free(FS_Set *s) { 132 | if (s) { 133 | allocator_t *alloc = s->alloc; 134 | str_db_free(&s->db); 135 | str_db_free(&s->blacklist); 136 | FlMemUnmap(&s->map); 137 | alloc->alloc(s, 0, alloc->arg); 138 | } 139 | return FL_OK; 140 | } 141 | 142 | int fs_stat(FS_Set *s, FS_Stat *stat) { 143 | if (s) { 144 | *stat = s->stat; 145 | } 146 | return 0; 147 | } 148 | 149 | int fs_add_font(FS_Set *s, const wchar_t *tag, void *buf, size_t size) { 150 | int ok = 0, r = FL_OK; 151 | str_db_t *db = &s->db; 152 | const size_t pos_filename = str_db_tell(db); 153 | size_t pos_db = 0, pos_db_fmt = 0; 154 | 155 | FS_ParseCtx ctx; 156 | WCHAR fmt[4]; 157 | do { 158 | if (str_db_push_u16_le(db, tag, 0) == NULL) 159 | break; 160 | // try TTC 161 | pos_db_fmt = str_db_tell(db); 162 | fs_format_tag_to_str(FS_FmtTTC, fmt); 163 | if (str_db_push_prefix(db, kTagFormat, kTagFormatLen) == NULL || 164 | str_db_push_u16_le(db, fmt, 0) == NULL) 165 | break; 166 | 167 | pos_db = str_db_tell(db); 168 | ctx = (FS_ParseCtx){.set = s, .pos_ver = pos_db, .pos_face = pos_db}; 169 | r = ttc_parse(buf, size, fs_parser_name_cb, &ctx); 170 | if (r == FL_OK && ctx.count_face > 0) { 171 | ok = 1; 172 | break; 173 | } 174 | 175 | // try with TTF/OTF 176 | const uint8_t *buffer = (const uint8_t *)buf; 177 | fs_format_tag_to_str(buffer[0] == 'O' ? FS_FmtOTF : FS_FmtTTF, fmt); 178 | str_db_seek(db, pos_db_fmt); 179 | if (str_db_push_prefix(db, kTagFormat, kTagFormatLen) == NULL || 180 | str_db_push_u16_le(db, fmt, 0) == NULL) 181 | break; 182 | 183 | pos_db = str_db_tell(db); 184 | ctx = (FS_ParseCtx){.set = s, .pos_ver = pos_db, .pos_face = pos_db}; 185 | r = otf_parse(buf, size, fs_parser_name_cb, &ctx); 186 | if (r == FL_OK && ctx.count_face > 0) { 187 | ok = 1; 188 | break; 189 | } 190 | int break_here = 0; 191 | } while (0); 192 | 193 | if (ok) { 194 | if (!str_db_push_u16_le(db, L"", 0)) { 195 | r = FL_OUT_OF_MEMORY; 196 | ok = 0; 197 | } 198 | } 199 | 200 | s->stat.num_file++; 201 | if (ok) { 202 | s->stat.num_face += ctx.count_face; 203 | } else { 204 | // try preserve error message 205 | const wchar_t *m1 = NULL, *m2 = NULL; 206 | if (pos_db != 0) { 207 | str_db_seek(db, pos_db); 208 | m1 = str_db_push_u16_le(db, kTagError, kTagErrorLen); 209 | m2 = str_db_push_u16_le(db, L"", 0); 210 | FlBreak(); 211 | } 212 | if (!(m1 && m2)) { 213 | // completely rollback 214 | str_db_seek(db, pos_filename); 215 | s->stat.num_file--; 216 | } 217 | } 218 | return r; 219 | } 220 | 221 | static int fs_idx_comp(const void *pa, const void *pb, void *arg) { 222 | // FS_Set *s = arg; 223 | const FS_Index *a = pa, *b = pb; 224 | 225 | // first, compare the name 226 | int cmp = FlStrCmpIW(a->face, b->face); 227 | if (cmp == 0) { 228 | // second, compare by format 229 | cmp = 0 - (a->format - b->format); 230 | if (cmp == 0) { 231 | // last, compare by version 232 | cmp = 0 - FlVersionCmp(a->ver, b->ver); 233 | } 234 | } 235 | 236 | return cmp; 237 | } 238 | 239 | static FS_Format fs_format_str_to_tag(const WCHAR s[4]) { 240 | for (int i = 0; i != FS_FmtMax; i++) { 241 | if (ass_strncmp(s, kFsFmtTag[i], 4) == 0) { 242 | return (FS_Format)i; 243 | } 244 | } 245 | return FS_FmtNone; 246 | } 247 | 248 | static void fs_format_tag_to_str(FS_Format fmt, WCHAR s[4]) { 249 | int i = fmt; 250 | if (FS_FmtNone <= i && i < FS_FmtMax) { 251 | zmemcpy(s, kFsFmtTag[i], sizeof kFsFmtTag[i]); 252 | } else { 253 | s[0] = 0; 254 | } 255 | } 256 | 257 | static void fs_debug_write_line(HANDLE f, const WCHAR *line) { 258 | SIZE_T nb = lstrlen(line) * sizeof line[0]; 259 | SIZE_T out = 0; 260 | WriteFile(f, line, nb, &out, NULL); 261 | } 262 | 263 | static void fs_index_debug_dump(FS_Set *s) { 264 | HANDLE f = CreateFile( 265 | L"FontIndexDebugDump.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 266 | FILE_ATTRIBUTE_NORMAL, NULL); 267 | for (unsigned int i = 0; i != s->stat.num_face; i++) { 268 | WCHAR fmt[4]; 269 | fs_format_tag_to_str(s->index[i].format, fmt); 270 | fs_debug_write_line(f, L"["); 271 | fs_debug_write_line(f, fmt); 272 | fs_debug_write_line(f, L"] "); 273 | fs_debug_write_line(f, s->index[i].face); 274 | fs_debug_write_line(f, L" "); 275 | fs_debug_write_line(f, s->index[i].tag); 276 | fs_debug_write_line(f, L" "); 277 | fs_debug_write_line(f, s->index[i].ver); 278 | fs_debug_write_line(f, L"\n"); 279 | } 280 | CloseHandle(f); 281 | } 282 | 283 | int fs_build_index(FS_Set *s) { 284 | allocator_t *alloc = s->alloc; 285 | const size_t idx_size = s->stat.num_face * sizeof s->index[0]; 286 | FS_Index *idx = (FS_Index *)alloc->alloc(s->index, idx_size, alloc->arg); 287 | s->index = idx; 288 | if (idx == NULL) { 289 | return s->stat.num_face ? FL_OUT_OF_MEMORY : FL_OK; 290 | } 291 | 292 | // for checking only 293 | FS_Stat stat = {.num_face = 0, .num_file = 0}; 294 | 295 | int err = 0; 296 | int has_filename = 0; 297 | const wchar_t *line; 298 | size_t pos = 0; 299 | FS_Index last_idx = {0}; 300 | while (!err && (line = str_db_next(&s->db, &pos)) != NULL) { 301 | if (line[0] == 0) { 302 | // empty line 303 | last_idx = (FS_Index){0}; 304 | has_filename = 0; 305 | } else if (ass_strncmp(line, kTagVersion, kTagVersionLen) == 0) { 306 | // update version 307 | last_idx.ver = line + kTagVersionLen; 308 | } else if (ass_strncmp(line, kTagFormat, kTagFormatLen) == 0) { 309 | last_idx.format = fs_format_str_to_tag(line + kTagFormatLen); 310 | } else if (ass_strncmp(line, kTagError, kTagErrorLen) == 0) { 311 | // ignore 312 | } else if (!has_filename) { 313 | // update filename 314 | last_idx.tag = line; 315 | has_filename = 1; 316 | stat.num_file++; 317 | } else { 318 | // face 319 | if (stat.num_face == s->stat.num_face) { 320 | err = 1; 321 | break; 322 | } 323 | last_idx.face = line; 324 | idx[stat.num_face++] = last_idx; 325 | } 326 | } 327 | 328 | if (stat.num_face != s->stat.num_face || stat.num_file != s->stat.num_file) 329 | err = 1; 330 | 331 | if (!err) { 332 | // sort 333 | tim_sort(idx, stat.num_face, sizeof idx[0], s->alloc, fs_idx_comp, s); 334 | // fs_index_debug_dump(s); 335 | } else { 336 | alloc->alloc(idx, 0, alloc->arg); 337 | idx = NULL; 338 | s->index = idx; 339 | } 340 | 341 | return err ? FL_OUT_OF_MEMORY : FL_OK; 342 | } 343 | 344 | int fs_iter_new(FS_Set *s, const wchar_t *face, FS_Iter *it) { 345 | if (s == NULL || s->index == NULL || it == NULL) 346 | return 0; 347 | int a = 0, b = s->stat.num_face - 1; 348 | int m = 0; 349 | if (s->index != NULL && s->stat.num_face != 0) { 350 | while (a <= b) { 351 | m = a + (b - a) / 2; 352 | const wchar_t *got = s->index[m].face; 353 | const int t = FlStrCmpIW(face, got); 354 | if (t == 0) { 355 | a = b = m; 356 | break; 357 | } 358 | if (t > 0) { 359 | a = m + 1; 360 | } else { 361 | b = m - 1; 362 | } 363 | } 364 | } 365 | 366 | do { 367 | if (!(a == b && a == m)) { 368 | break; 369 | } 370 | // found by fontface, skip to first match 371 | while (m > 0 && FlStrCmpIW(face, s->index[m - 1].face) == 0) { 372 | m--; 373 | } 374 | // enforce blacklist 375 | while (m != s->stat.num_face && fs_blacklist_match(s, s->index[m].tag)) { 376 | m++; 377 | } 378 | if (m == s->stat.num_face) { 379 | break; 380 | } 381 | *it = 382 | (FS_Iter){.set = s, .query_id = m, .index_id = m, .info = s->index[m]}; 383 | return 1; 384 | } while ((0)); 385 | 386 | // *it = (FS_Iter){0}; 387 | it->set = NULL; 388 | it->query_id = 0; 389 | it->index_id = 0; 390 | return 0; 391 | } 392 | 393 | static size_t str_cmp_x(const wchar_t *a, const wchar_t *b) { 394 | size_t r; 395 | for (r = 0; a[r] == b[r] && a[r]; r++) { 396 | // nop; 397 | } 398 | return r; 399 | } 400 | 401 | int fs_iter_next(FS_Iter *it) { 402 | FS_Set *s = it->set; 403 | if (s == NULL) 404 | return 0; 405 | if (it->index_id == s->stat.num_face) 406 | return 0; 407 | it->index_id++; 408 | const wchar_t *face = s->index[it->query_id].face; 409 | const wchar_t *ver = s->index[it->query_id].ver; 410 | FS_Format fmt = s->index[it->query_id].format; 411 | 412 | for (; it->index_id != s->stat.num_face; it->index_id++) { 413 | const wchar_t *got_face = s->index[it->index_id].face; 414 | const wchar_t *got_ver = s->index[it->index_id].ver; 415 | FS_Format got_fmt = s->index[it->index_id].format; 416 | 417 | // check if prefix matches 418 | const size_t df = str_cmp_x(face, got_face); 419 | if (face[df] != 0) { 420 | break; 421 | } 422 | 423 | // check format 424 | if (fmt != got_fmt) { 425 | continue; 426 | } 427 | 428 | // check version 429 | if (ver == NULL) { 430 | if (got_ver != NULL) 431 | continue; 432 | } else { 433 | if (got_ver == NULL) 434 | continue; 435 | const size_t dv = str_cmp_x(ver, got_ver); 436 | if (ver[dv] != 0 || got_ver[dv] != 0) 437 | continue; 438 | } 439 | 440 | if (fs_blacklist_match(s, s->index[it->index_id].tag)) { 441 | continue; 442 | } 443 | 444 | // match found 445 | it->info = s->index[it->index_id]; 446 | return 1; 447 | } 448 | // iter end 449 | // *it = (FS_Iter){0}; 450 | it->set = NULL; 451 | return 0; 452 | } 453 | 454 | int fs_cache_load(const wchar_t *path, allocator_t *alloc, FS_Set **out) { 455 | int ok = 0, r; 456 | FS_Set *s = NULL; 457 | memmap_t map = {0}; 458 | 459 | do { 460 | r = FlMemMap(path, &map); 461 | if (map.data == NULL) { 462 | r = FL_OS_ERROR; 463 | break; 464 | } 465 | 466 | r = FL_UNRECOGNIZED; 467 | FS_CacheHeader *head = map.data; 468 | if (head->magic != KFontDbMagic) 469 | break; 470 | if (head->size != map.size) 471 | break; 472 | if (head->size < 8) 473 | break; 474 | 475 | // ensure NUL terminated 476 | const wchar_t *buf_tail = (wchar_t *)((char *)map.data + head->size); 477 | if (buf_tail[-1] != 0 && buf_tail[-2] != 0) 478 | break; 479 | 480 | r = FL_OUT_OF_MEMORY; 481 | fs_create(alloc, &s); 482 | if (s == NULL) 483 | break; 484 | str_db_loads( 485 | &s->db, (const wchar_t *)&head[1], 486 | (head->size - sizeof head[0]) / sizeof(wchar_t), '\n'); 487 | s->stat = head->stat; 488 | 489 | ok = 1; 490 | } while (0); 491 | 492 | if (!ok) { 493 | fs_free(s); 494 | FlMemUnmap(&map); 495 | s = NULL; 496 | } else { 497 | s->map = map; 498 | } 499 | 500 | *out = s; 501 | return ok ? FL_OK : r; 502 | } 503 | 504 | int fs_cache_dump(FS_Set *s, const wchar_t *path) { 505 | int ok = 0; 506 | DWORD flags = FILE_ATTRIBUTE_NORMAL; 507 | if (s->stat.num_file == 0) 508 | flags |= FILE_FLAG_DELETE_ON_CLOSE; 509 | 510 | HANDLE h = CreateFile( 511 | path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, flags, NULL); 512 | do { 513 | if (h == INVALID_HANDLE_VALUE) 514 | break; 515 | const wchar_t *buf = str_db_get(&s->db, 0); 516 | FS_CacheHeader head = { 517 | .magic = KFontDbMagic, 518 | .stat = s->stat, 519 | .size = sizeof head + str_db_tell(&s->db) * sizeof buf[0]}; 520 | 521 | DWORD dw_out; 522 | if (!WriteFile(h, &head, sizeof head, &dw_out, NULL)) 523 | break; 524 | if (!WriteFile(h, buf, str_db_tell(&s->db) * sizeof buf[0], &dw_out, NULL)) 525 | break; 526 | ok = 1; 527 | } while (0); 528 | 529 | CloseHandle(h); 530 | return ok ? FL_OK : FL_OS_ERROR; 531 | } 532 | 533 | int fs_blacklist_clear(FS_Set *s) { 534 | str_db_seek(&s->blacklist, 0); 535 | return 0; 536 | } 537 | 538 | int fs_blacklist_add(FS_Set *s, const wchar_t *path, size_t cch) { 539 | const wchar_t *ret = str_db_push_u16_le(&s->blacklist, path, cch); 540 | return ret == NULL; 541 | } 542 | 543 | int fs_blacklist_match(FS_Set *s, const wchar_t *path) { 544 | size_t pos_it = 0; 545 | const size_t len_path = ass_strlen(path); 546 | const wchar_t *suffix; 547 | while ((suffix = str_db_next(&s->blacklist, &pos_it)) != NULL) { 548 | const size_t len_suffix = ass_strlen(suffix); 549 | if (len_suffix > len_path) { 550 | continue; 551 | } 552 | const wchar_t *path_sfx = path + len_path - len_suffix; 553 | if (ass_strncasecmp(path_sfx, suffix, len_suffix) == 0) { 554 | if (len_path == len_suffix || path_sfx[-1] == '\\') { 555 | return 1; 556 | } 557 | } 558 | } 559 | return 0; 560 | } 561 | -------------------------------------------------------------------------------- /FontLoaderSub/font_set.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "util.h" 5 | 6 | typedef struct _FS_Set FS_Set; 7 | 8 | typedef struct { 9 | uint32_t num_file; 10 | uint32_t num_face; 11 | } FS_Stat; 12 | 13 | // feel free to change its order (except for the last one) 14 | typedef enum { 15 | FS_FmtNone, // least preferred 16 | FS_FmtOTF, 17 | FS_FmtTTF, 18 | FS_FmtTTC, // most preferred 19 | FS_FmtMax // number of formats 20 | } FS_Format; 21 | 22 | typedef struct { 23 | const wchar_t *tag; 24 | const wchar_t *face; 25 | const wchar_t *ver; 26 | FS_Format format; 27 | } FS_Index; 28 | 29 | typedef struct { 30 | // private: 31 | FS_Set *set; 32 | uint32_t query_id; 33 | uint32_t index_id; 34 | // public: 35 | FS_Index info; 36 | } FS_Iter; 37 | 38 | int fs_create(allocator_t *alloc, FS_Set **out); 39 | 40 | int fs_free(FS_Set *s); 41 | 42 | int fs_stat(FS_Set *s, FS_Stat *stat); 43 | 44 | int fs_add_font(FS_Set *s, const wchar_t *tag, void *buf, size_t size); 45 | 46 | int fs_build_index(FS_Set *s); 47 | 48 | int fs_iter_new(FS_Set *s, const wchar_t *face, FS_Iter *it); 49 | 50 | int fs_iter_next(FS_Iter *it); 51 | 52 | int fs_cache_load(const wchar_t *path, allocator_t *alloc, FS_Set **out); 53 | 54 | int fs_cache_dump(FS_Set *s, const wchar_t *path); 55 | 56 | int fs_blacklist_clear(FS_Set *s); 57 | 58 | int fs_blacklist_add(FS_Set *s, const wchar_t *path, size_t cch); 59 | 60 | int fs_blacklist_match(FS_Set *S, const wchar_t *path); 61 | -------------------------------------------------------------------------------- /FontLoaderSub/main.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #include "ass_string.h" 4 | #include "exporter.h" 5 | #include "util.h" 6 | #include "path.h" 7 | #include "shortcut.h" 8 | #include "mock_config.h" 9 | #include "res/resource.h" 10 | 11 | #define kCacheFile L"fc-subs.db" 12 | #define kBlackFile L"fc-ignore.txt" 13 | 14 | static void *mem_realloc(void *existing, size_t size, void *arg) { 15 | HANDLE heap = (HANDLE)arg; 16 | if (size == 0) { 17 | HeapFree(heap, 0, existing); 18 | return NULL; 19 | } 20 | if (existing == NULL) { 21 | return HeapAlloc(heap, HEAP_ZERO_MEMORY, size); 22 | } 23 | return HeapReAlloc(heap, HEAP_ZERO_MEMORY, existing, size); 24 | } 25 | 26 | static void AppHelpUsage(FL_AppCtx *c, HWND hWnd) { 27 | c->show_shortcut = 0; 28 | c->dlg_help.hwndParent = hWnd; 29 | TaskDialogIndirect(&c->dlg_help, NULL, NULL, NULL); 30 | if (c->show_shortcut) { 31 | ShortcutShow(&c->shortcut, hWnd); 32 | } 33 | } 34 | 35 | static int AppBuildLog(FL_AppCtx *c) { 36 | vec_t *loaded = &c->loader.loaded_font; 37 | str_db_t *log = &c->log; 38 | 39 | str_db_seek(log, 0); 40 | FL_FontMatch *data = loaded->data; 41 | 42 | for (size_t i = 0; i != loaded->n; i++) { 43 | const wchar_t *tag; 44 | FL_FontMatch *m = &data[i]; 45 | if (m->flag & (FL_LOAD_DUP)) 46 | tag = L"[^ ] "; 47 | else if (m->flag & (FL_OS_LOADED | FL_LOAD_OK)) 48 | tag = L"[ok] "; 49 | else if (m->flag & (FL_LOAD_ERR)) 50 | tag = L"[ X] "; 51 | else if (1 || m->flag & (FL_LOAD_MISS)) 52 | tag = L"[??] "; 53 | if (!str_db_push_u16_le(log, tag, 0) || 54 | !str_db_push_u16_le(log, m->face, 0)) 55 | return 0; 56 | if (m->filename && !(m->flag & FL_LOAD_DUP)) { 57 | if (!str_db_push_u16_le(log, L" > ", 0) || 58 | !str_db_push_u16_le(log, m->filename, 0)) 59 | return 0; 60 | } 61 | if (!str_db_push_u16_le(log, L"\n", 0)) 62 | return 0; 63 | } 64 | const size_t pos = str_db_tell(log); 65 | if (!pos) 66 | return 0; 67 | wchar_t *buf = (wchar_t *)str_db_get(log, 0); 68 | buf[pos - 1] = 0; 69 | return 1; 70 | } 71 | 72 | static int AppUpdateStatus(FL_AppCtx *c) { 73 | FS_Stat stat = {0}; 74 | if (c->loader.font_set) { 75 | fs_stat(c->loader.font_set, &stat); 76 | } 77 | 78 | DWORD_PTR args[] = { 79 | // arguments 80 | c->loader.num_font_loaded, 81 | c->loader.num_font_failed, 82 | c->loader.num_font_unmatched, 83 | stat.num_file, 84 | stat.num_face, 85 | c->loader.num_sub, 86 | }; 87 | FormatMessage( 88 | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, 89 | ResLoadString(c->hInst, IDS_LOAD_STAT), 0, 0, c->status_txt, 90 | _countof(c->status_txt), (va_list *)args); 91 | 92 | LPARAM cap_id; 93 | if (c->cancelled || c->app_state == APP_CANCELLED) { 94 | cap_id = IDS_WORK_CANCELLING; 95 | } else { 96 | cap_id = c->app_state; 97 | } 98 | 99 | SendMessage(c->work_hwnd, TDM_SET_ELEMENT_TEXT, TDE_MAIN_INSTRUCTION, cap_id); 100 | SendMessage( 101 | c->work_hwnd, TDM_SET_ELEMENT_TEXT, TDE_CONTENT, (LPARAM)c->status_txt); 102 | 103 | return 0; 104 | } 105 | 106 | static DWORD WINAPI AppWorker(LPVOID param) { 107 | FL_AppCtx *c = (FL_AppCtx *)param; 108 | int r = FL_OK; 109 | while (r == FL_OK && !c->cancelled && c->app_state != APP_DONE) { 110 | switch (c->app_state) { 111 | case APP_LOAD_SUB: { 112 | if (MOCK_SUB_PATH) { 113 | r = fl_add_subs(&c->loader, MOCK_SUB_PATH); 114 | } 115 | for (int i = 1; i < c->argc && r == FL_OK; i++) { 116 | r = fl_add_subs(&c->loader, c->argv[i]); 117 | } 118 | c->app_state = APP_LOAD_CACHE; 119 | break; 120 | } 121 | case APP_LOAD_CACHE: { 122 | fl_scan_fonts(&c->loader, c->font_path, kCacheFile, kBlackFile); 123 | FS_Stat stat = {0}; 124 | fs_stat(c->loader.font_set, &stat); 125 | if (stat.num_face == 0) { 126 | c->app_state = APP_SCAN_FONT; 127 | } else { 128 | c->app_state = APP_LOAD_FONT; 129 | } 130 | break; 131 | } 132 | case APP_SCAN_FONT: { 133 | if (fl_scan_fonts(&c->loader, c->font_path, NULL, kBlackFile) == FL_OK) { 134 | fl_save_cache(&c->loader, kCacheFile); 135 | } 136 | c->app_state = APP_LOAD_FONT; 137 | break; 138 | } 139 | case APP_LOAD_FONT: { 140 | r = fl_load_fonts(&c->loader); 141 | if (r == FL_OK) 142 | c->app_state = APP_DONE; 143 | break; 144 | } 145 | case APP_UNLOAD_FONT: { 146 | fl_unload_fonts(&c->loader); 147 | if (c->req_exit) { 148 | c->cancelled = 1; 149 | } else { 150 | c->app_state = APP_SCAN_FONT; 151 | } 152 | break; 153 | } 154 | default: { 155 | // nop 156 | } 157 | } 158 | } 159 | if (c->cancelled) { 160 | fl_unload_fonts(&c->loader); 161 | c->app_state = APP_CANCELLED; 162 | } 163 | 164 | return 0; 165 | } 166 | 167 | static DWORD WINAPI AppCacheWorker(LPVOID param) { 168 | FL_AppCtx *c = (FL_AppCtx *)param; 169 | 170 | while (1) { 171 | fl_cache_fonts(&c->loader, c->evt_stop_cache); 172 | if (WaitForSingleObject(c->evt_stop_cache, 5 * 60 * 1000) != WAIT_TIMEOUT) 173 | break; 174 | } 175 | return 0; 176 | } 177 | 178 | static HRESULT CALLBACK DlgWorkProc( 179 | HWND hWnd, 180 | UINT uNotification, 181 | WPARAM wParam, 182 | LPARAM lParam, 183 | LONG_PTR dwRefData) { 184 | FL_AppCtx *c = (FL_AppCtx *)dwRefData; 185 | int navigated = 0; 186 | if (uNotification == TDN_CREATED || uNotification == TDN_NAVIGATED) { 187 | c->work_hwnd = hWnd; 188 | SendMessage(hWnd, TDM_SET_PROGRESS_BAR_MARQUEE, TRUE, 0); 189 | 190 | DWORD thread_id; 191 | c->thread_load = CreateThread(NULL, 0, AppWorker, c, 0, &thread_id); 192 | if (c->thread_load == NULL) { 193 | // fatal error, try exit early 194 | c->cancelled = 1; 195 | c->app_state = APP_CANCELLED; 196 | PostMessage(hWnd, WM_CLOSE, 0, 0); 197 | } 198 | } else if (uNotification == TDN_BUTTON_CLICKED) { 199 | if (wParam == IDCANCEL) { 200 | if (c->app_state == APP_CANCELLED) { 201 | return S_OK; // exit cleared 202 | } 203 | if (!c->req_exit) { 204 | c->cancelled = 1; 205 | fl_cancel(&c->loader); // signal cancel event 206 | } 207 | } 208 | } else if (uNotification == TDN_TIMER) { 209 | DWORD r = WaitForSingleObject(c->thread_load, 0); 210 | if (r != WAIT_TIMEOUT) { 211 | // worker exited 212 | CloseHandle(c->thread_load); 213 | c->thread_load = NULL; 214 | if (c->taskbar_list3) { 215 | c->taskbar_list3->lpVtbl->SetProgressState( 216 | c->taskbar_list3, hWnd, TBPF_NOPROGRESS); 217 | } 218 | if (c->app_state == APP_DONE) { 219 | // worker exited without error... 220 | if (!c->cancelled) { 221 | // and has not been cancelled 222 | if (AppBuildLog(c)) { 223 | c->dlg_done.pszExpandedInformation = str_db_get(&c->log, 0); 224 | } else { 225 | c->dlg_done.pszExpandedInformation = NULL; 226 | } 227 | AppUpdateStatus(c); 228 | c->dlg_done.pszContent = c->status_txt; 229 | PostMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); 230 | SendMessage(hWnd, TDM_NAVIGATE_PAGE, 0, (LPARAM)&c->dlg_done); 231 | navigated = 1; 232 | } else { 233 | // worker done, then cancelled before timer, 234 | // work again to continue cancellation routine 235 | c->app_state = APP_UNLOAD_FONT; 236 | SendMessage(hWnd, TDM_NAVIGATE_PAGE, 0, (LPARAM)&c->dlg_work); 237 | } 238 | } else { 239 | if (c->app_state != APP_CANCELLED) { 240 | // it's an error 241 | TaskDialog( 242 | hWnd, c->hInst, MAKEINTRESOURCE(IDS_APP_NAME_VER), L"Error...", 243 | NULL, TDCBF_CLOSE_BUTTON, TD_ERROR_ICON, NULL); 244 | } 245 | PostMessage(hWnd, WM_CLOSE, 0, 0); 246 | } 247 | } else { 248 | // work in progress 249 | if (c->taskbar_list3) { 250 | c->taskbar_list3->lpVtbl->SetProgressState( 251 | c->taskbar_list3, hWnd, TBPF_INDETERMINATE); 252 | } 253 | } 254 | } 255 | if (!navigated) 256 | AppUpdateStatus(c); 257 | return S_FALSE; 258 | } 259 | 260 | static HRESULT CALLBACK DlgHelpProc( 261 | HWND hWnd, 262 | UINT uNotification, 263 | WPARAM wParam, 264 | LPARAM lParam, 265 | LONG_PTR dwRefData) { 266 | FL_AppCtx *c = (FL_AppCtx *)dwRefData; 267 | if (uNotification == TDN_HYPERLINK_CLICKED) { 268 | PostMessage(hWnd, WM_CLOSE, 0, 0); 269 | c->show_shortcut = 1; 270 | } 271 | return S_OK; 272 | } 273 | 274 | static HRESULT CALLBACK DlgDoneButtonDispatch( 275 | HWND hWnd, 276 | UINT uNotification, 277 | WPARAM wParam, 278 | LPARAM lParam, 279 | FL_AppCtx *c) { 280 | // all return S_FALSE: never close dialog 281 | switch (wParam) { 282 | case IDCANCEL: 283 | case IDCLOSE: 284 | case ID_BTN_RESCAN: { 285 | if (wParam != ID_BTN_RESCAN) { 286 | c->req_exit = 1; 287 | } 288 | SetEvent(c->evt_stop_cache); 289 | if (WaitForSingleObject(c->thread_cache, 1000) != WAIT_OBJECT_0) { 290 | TerminateThread(c->thread_cache, 2); 291 | } 292 | CloseHandle(c->thread_cache); 293 | c->thread_cache = NULL; 294 | c->app_state = APP_UNLOAD_FONT; 295 | SendMessage(hWnd, TDM_NAVIGATE_PAGE, 0, (LPARAM)&c->dlg_work); 296 | return S_FALSE; 297 | } 298 | case IDOK: { 299 | ShowWindow(hWnd, SW_MINIMIZE); 300 | return S_FALSE; 301 | } 302 | case ID_BTN_MENU: { 303 | RECT rect; 304 | POINT pt; 305 | HMENU menu = GetSubMenu(c->btn_menu, 0); 306 | HWND btn = c->handle_btn_menu; 307 | if (GetWindowRect(btn, &rect)) { 308 | pt.x = rect.left; 309 | pt.y = rect.bottom; 310 | } else { 311 | GetCursorPos(&pt); 312 | } 313 | BOOL r = TrackPopupMenu( 314 | menu, TPM_NONOTIFY | TPM_RETURNCMD, pt.x, pt.y, 0, hWnd, NULL); 315 | if (r != FALSE) { 316 | return DlgDoneButtonDispatch(hWnd, uNotification, r, lParam, c); 317 | } 318 | return S_FALSE; 319 | } 320 | case ID_BTN_EXPORT: { 321 | ExportLoadedFonts(hWnd, c); 322 | return S_FALSE; 323 | } 324 | case ID_BTN_HELP: { 325 | AppHelpUsage(c, hWnd); 326 | return S_FALSE; 327 | } 328 | default: { return S_FALSE; } 329 | } 330 | } 331 | 332 | static BOOL CALLBACK DlgDoneFindMenuBtnCb(HWND hWnd, LPARAM lParam) { 333 | FL_AppCtx *c = (FL_AppCtx *)lParam; 334 | WCHAR buffer[16]; 335 | const WCHAR *target = ResLoadString(c->hInst, IDS_MENU); 336 | if (target == NULL) { 337 | return FALSE; // stop! we are in trouble 338 | } 339 | int len = GetWindowText(hWnd, buffer, _countof(buffer)); 340 | if (len != 0) { 341 | if (ass_strncmp(buffer, target, len + 1) == 0) { 342 | c->handle_btn_menu = hWnd; 343 | return FALSE; 344 | } 345 | } 346 | return TRUE; 347 | } 348 | 349 | static HRESULT CALLBACK DlgDoneProc( 350 | HWND hWnd, 351 | UINT uNotification, 352 | WPARAM wParam, 353 | LPARAM lParam, 354 | LONG_PTR dwRefData) { 355 | FL_AppCtx *c = (FL_AppCtx *)dwRefData; 356 | if (uNotification == TDN_NAVIGATED) { 357 | c->thread_cache = NULL; 358 | 359 | FS_Stat stat = {0}; 360 | fs_stat(c->loader.font_set, &stat); 361 | if (c->loader.num_sub_font == 0 || stat.num_face == 0) { 362 | EnableMenuItem(c->btn_menu, ID_BTN_EXPORT, MF_BYCOMMAND | MF_GRAYED); 363 | AppHelpUsage(c, hWnd); 364 | } else { 365 | DWORD thread_id; 366 | EnableMenuItem(c->btn_menu, ID_BTN_EXPORT, MF_BYCOMMAND | MF_ENABLED); 367 | ResetEvent(c->evt_stop_cache); 368 | c->thread_cache = CreateThread(NULL, 0, AppCacheWorker, c, 0, &thread_id); 369 | } 370 | 371 | // find the "Menu" button 372 | c->handle_btn_menu = NULL; 373 | EnumChildWindows(hWnd, DlgDoneFindMenuBtnCb, (LPARAM)c); 374 | } else if (uNotification == TDN_HYPERLINK_CLICKED) { 375 | // the only URL is the github repo 376 | const WCHAR *url = L"https://github.com/yzwduck/FontLoaderSub"; 377 | ShellExecute(NULL, NULL, url, NULL, NULL, SW_SHOW); 378 | } else if (uNotification == TDN_BUTTON_CLICKED) { 379 | return DlgDoneButtonDispatch(hWnd, uNotification, wParam, lParam, c); 380 | } 381 | return S_OK; 382 | } 383 | 384 | static const TASKDIALOGCONFIG kDlgWorkTemplate = { 385 | .cbSize = sizeof kDlgWorkTemplate, 386 | .pszWindowTitle = MAKEINTRESOURCE(IDS_APP_NAME_VER), 387 | .dwCommonButtons = TDCBF_CANCEL_BUTTON, 388 | .dwFlags = TDF_SHOW_MARQUEE_PROGRESS_BAR | TDF_CALLBACK_TIMER | 389 | TDF_SIZE_TO_CONTENT, 390 | .pszMainInstruction = L"", 391 | .pfCallback = DlgWorkProc, 392 | }; 393 | 394 | static const TASKDIALOG_BUTTON kDlgDoneButtons[] = { 395 | {ID_BTN_MENU, MAKEINTRESOURCE(IDS_MENU)}}; 396 | 397 | static const TASKDIALOGCONFIG kDlgDoneTemplate = { 398 | .cbSize = sizeof kDlgDoneTemplate, 399 | .pszWindowTitle = MAKEINTRESOURCE(IDS_APP_NAME_VER), 400 | .dwCommonButtons = TDCBF_CLOSE_BUTTON | TDCBF_OK_BUTTON, 401 | .pszMainInstruction = MAKEINTRESOURCE(IDS_WORK_DONE), 402 | .dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_ENABLE_HYPERLINKS | 403 | TDF_SIZE_TO_CONTENT, 404 | .pszFooterIcon = TD_SHIELD_ICON, 405 | .pszFooter = L"GPLv2: github.com/yzwduck/FontLoaderSub", 406 | .pfCallback = DlgDoneProc, 407 | .cButtons = _countof(kDlgDoneButtons), 408 | .pButtons = kDlgDoneButtons, 409 | .nDefaultButton = IDOK, 410 | }; 411 | 412 | static const TASKDIALOGCONFIG kDlgHelpTemplate = { 413 | .cbSize = sizeof kDlgHelpTemplate, 414 | .pszWindowTitle = MAKEINTRESOURCE(IDS_APP_NAME_VER), 415 | .pszMainIcon = TD_INFORMATION_ICON, 416 | .pszMainInstruction = MAKEINTRESOURCE(IDS_HELP), 417 | .pszContent = MAKEINTRESOURCE(IDS_USAGE), 418 | .dwCommonButtons = TDCBF_CLOSE_BUTTON, 419 | .dwFlags = TDF_ENABLE_HYPERLINKS | TDF_ALLOW_DIALOG_CANCELLATION, 420 | .pfCallback = DlgHelpProc, 421 | }; 422 | 423 | static int AppInit(FL_AppCtx *c, HINSTANCE hInst, allocator_t *alloc) { 424 | c->hInst = hInst; 425 | c->alloc = alloc; 426 | 427 | memcpy(&c->dlg_work, &kDlgWorkTemplate, sizeof c->dlg_work); 428 | c->dlg_work.hInstance = hInst; 429 | c->dlg_work.lpCallbackData = (LONG_PTR)c; 430 | 431 | memcpy(&c->dlg_done, &kDlgDoneTemplate, sizeof c->dlg_done); 432 | c->dlg_done.hInstance = hInst; 433 | c->dlg_done.lpCallbackData = (LONG_PTR)c; 434 | 435 | memcpy(&c->dlg_help, &kDlgHelpTemplate, sizeof c->dlg_help); 436 | c->dlg_help.hInstance = hInst; 437 | c->dlg_help.lpCallbackData = (LONG_PTR)c; 438 | 439 | c->btn_menu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_BTN_MENU)); 440 | if (c->btn_menu == NULL) 441 | return 0; 442 | 443 | c->argv = CommandLineToArgvW(GetCommandLine(), &c->argc); 444 | if (c->argv == NULL) 445 | return 0; 446 | if (str_db_init(&c->full_exe_path, c->alloc, 0, 0)) 447 | return 0; 448 | 449 | DWORD initial = MAX_PATH; 450 | while (1) { 451 | if (vec_prealloc(&c->full_exe_path.vec, initial) < initial) 452 | return 0; 453 | DWORD ret = GetModuleFileName( 454 | NULL, (WCHAR *)str_db_get(&c->full_exe_path, 0), initial); 455 | if (ret == 0) 456 | return 0; 457 | if (ret < initial) { 458 | // sufficient buffer size 459 | break; 460 | } else { 461 | initial = initial * 2; 462 | } 463 | } 464 | if (str_db_push_u16_le( 465 | &c->full_exe_path, str_db_get(&c->full_exe_path, 0), 0) == NULL) 466 | return 0; 467 | ShortcutInit(&c->shortcut, hInst, c->alloc); 468 | c->shortcut.key = L"FontLoaderSub"; // registry key 469 | c->shortcut.dlg_title = MAKEINTRESOURCE(IDS_APP_NAME_VER); 470 | c->shortcut.dir_bg_menu_str_id = IDS_SHELL_VERB; 471 | c->shortcut.sendto_str_id = IDS_SENDTO; 472 | c->shortcut.path = str_db_get(&c->full_exe_path, 0); 473 | c->app_state = APP_LOAD_SUB; 474 | if (fl_init(&c->loader, c->alloc) != FL_OK) 475 | return 0; 476 | str_db_init(&c->log, c->alloc, 0, 0); 477 | c->font_path = str_db_get(&c->full_exe_path, 0); 478 | 479 | if (MOCK_FONT_PATH) 480 | c->font_path = MOCK_FONT_PATH; 481 | 482 | c->evt_stop_cache = CreateEvent(NULL, TRUE, FALSE, NULL); 483 | if (c->evt_stop_cache == NULL) 484 | return 0; 485 | 486 | if (SUCCEEDED(CoCreateInstance( 487 | &CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, &IID_ITaskbarList3, 488 | (void **)&c->taskbar_list3))) { 489 | if (FAILED(c->taskbar_list3->lpVtbl->HrInit(c->taskbar_list3))) { 490 | c->taskbar_list3->lpVtbl->Release(c->taskbar_list3); 491 | c->taskbar_list3 = NULL; 492 | } 493 | } 494 | 495 | return 1; 496 | } 497 | 498 | static int AppRun(FL_AppCtx *c) { 499 | if (0 && GetAsyncKeyState(VK_SHIFT)) { 500 | ShortcutShow(&c->shortcut, NULL); 501 | return 0; 502 | } 503 | 504 | TaskDialogIndirect(&c->dlg_work, NULL, NULL, NULL); 505 | 506 | // clean up 507 | if (WaitForSingleObject(c->thread_load, 16384) == WAIT_TIMEOUT) { 508 | TerminateThread(c->thread_load, 1); 509 | fl_unload_fonts(&c->loader); 510 | } 511 | PostMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); 512 | return 0; 513 | } 514 | 515 | FL_AppCtx g_app; 516 | 517 | int test_main(); 518 | 519 | int WINAPI _tWinMain( 520 | HINSTANCE hInstance, 521 | HINSTANCE hPrevInstance, 522 | LPTSTR lpCmdLine, 523 | int nCmdShow) { 524 | PerMonitorDpiHack(); 525 | if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK) { 526 | return 0; 527 | } 528 | 529 | HANDLE heap = HeapCreate(0, 0, 0); 530 | allocator_t alloc = {.alloc = mem_realloc, .arg = heap}; 531 | FL_AppCtx *ctx = &g_app; 532 | if (ctx == NULL || !AppInit(ctx, hInstance, &alloc)) { 533 | TaskDialog( 534 | NULL, hInstance, MAKEINTRESOURCE(IDS_APP_NAME_VER), L"Error...", NULL, 535 | TDCBF_CLOSE_BUTTON, TD_ERROR_ICON, NULL); 536 | return 1; 537 | } 538 | AppRun(ctx); 539 | 540 | return 0; 541 | } 542 | 543 | extern IMAGE_DOS_HEADER __ImageBase; 544 | 545 | void MyEntryPoint() { 546 | UINT uRetCode; 547 | uRetCode = _tWinMain((HINSTANCE)&__ImageBase, NULL, NULL, SW_SHOWDEFAULT); 548 | ExitProcess(uRetCode); 549 | } 550 | -------------------------------------------------------------------------------- /FontLoaderSub/main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "res/resource.h" 9 | #include "font_loader.h" 10 | #include "shortcut.h" 11 | 12 | typedef enum { 13 | APP_LOAD_SUB = IDS_WORK_SUBTITLE, 14 | APP_LOAD_CACHE = IDS_WORK_CACHE, 15 | APP_SCAN_FONT = IDS_WORK_FONT, 16 | APP_LOAD_FONT = IDS_WORK_LOAD, 17 | APP_UNLOAD_FONT = IDS_WORK_UNLOAD, 18 | APP_DONE = IDS_WORK_DONE, 19 | APP_CANCELLED 20 | } FL_AppState; 21 | 22 | typedef struct { 23 | HINSTANCE hInst; 24 | allocator_t *alloc; 25 | int argc; 26 | LPWSTR *argv; 27 | 28 | int cancelled; 29 | int error; 30 | int req_exit; 31 | FL_LoaderCtx loader; 32 | FL_AppState app_state; 33 | wchar_t status_txt[256]; // should be sufficient 34 | str_db_t log; 35 | const wchar_t *font_path; 36 | // wchar_t exe_path[MAX_PATH]; 37 | str_db_t full_exe_path; 38 | 39 | HWND work_hwnd; 40 | HANDLE thread_load; 41 | HANDLE thread_cache; 42 | HANDLE evt_stop_cache; 43 | 44 | TASKDIALOGCONFIG dlg_work; 45 | TASKDIALOGCONFIG dlg_done; 46 | TASKDIALOGCONFIG dlg_help; 47 | HMENU btn_menu; // handle to the menu 48 | HWND handle_btn_menu; // handle to the button 49 | int show_shortcut; 50 | FL_ShortCtx shortcut; 51 | ITaskbarList3 *taskbar_list3; 52 | } FL_AppCtx; 53 | -------------------------------------------------------------------------------- /FontLoaderSub/mock_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if 0 4 | #define MOCK_DELAY_SUB 50 5 | #define MOCK_DELAY_FONT 500 6 | #define MOCK_NO_SYS 0 7 | #define MOCK_FAKE_LOAD 1 8 | #define MOCK_SUB_PATH L"E:\\Data\\Subs" 9 | #define MOCK_FONT_PATH L"E:\\Data\\Fonts" 10 | #else 11 | #define MOCK_DELAY_SUB 0 12 | #define MOCK_DELAY_FONT 0 13 | #define MOCK_NO_SYS 0 14 | #define MOCK_FAKE_LOAD 0 15 | #define MOCK_SUB_PATH NULL 16 | #define MOCK_FONT_PATH NULL 17 | #endif 18 | -------------------------------------------------------------------------------- /FontLoaderSub/path.c: -------------------------------------------------------------------------------- 1 | #include "path.h" 2 | 3 | typedef struct { 4 | FL_FileWalkCb callback; 5 | void *arg; 6 | str_db_t path; 7 | } FL_WalkDirCtx; 8 | 9 | int FlResolvePath(const wchar_t *path, str_db_t *s) { 10 | int r = FL_OK; 11 | HANDLE handle; 12 | 13 | do { 14 | handle = CreateFile( 15 | path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 16 | OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); 17 | // return value unchecked 18 | 19 | const DWORD name_flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS; 20 | const DWORD size = GetFinalPathNameByHandle(handle, NULL, 0, name_flags); 21 | if (size == 0) { 22 | r = FL_OS_ERROR; 23 | break; 24 | } 25 | // allocate buffer 26 | str_db_seek(s, 0); 27 | const DWORD space = vec_prealloc(&s->vec, size + MAX_PATH / 2); 28 | if (space < size) { 29 | r = FL_OUT_OF_MEMORY; 30 | break; 31 | } 32 | // get path 33 | wchar_t *buffer = (wchar_t *)str_db_get(s, 0); 34 | const DWORD cch = 35 | GetFinalPathNameByHandle(handle, buffer, space, name_flags); 36 | if (cch == 0 || cch >= size) { 37 | r = FL_OS_ERROR; 38 | break; 39 | } 40 | s->vec.n = cch; 41 | } while (0); 42 | 43 | CloseHandle(handle); 44 | return r; 45 | } 46 | 47 | size_t FlPathParent(str_db_t *path) { 48 | size_t pos = str_db_tell(path); 49 | wchar_t *buf = (wchar_t *)str_db_get(path, 0); 50 | while (pos != 0 && buf[pos - 1] != L'\\') 51 | pos--; 52 | buf[pos] = 0; 53 | str_db_seek(path, pos); 54 | return pos; 55 | } 56 | 57 | static int WalkDirDfs(FL_WalkDirCtx *ctx) { 58 | int r = FL_OK; 59 | WIN32_FIND_DATA fd; 60 | HANDLE find_handle = FindFirstFile(str_db_get(&ctx->path, 0), &fd); 61 | if (find_handle == INVALID_HANDLE_VALUE) { 62 | // ignore error, recommended 63 | return FL_OK; 64 | // stop on any error 65 | // return FL_OS_ERROR; 66 | } 67 | 68 | const size_t pos_root = FlPathParent(&ctx->path); 69 | 70 | do { 71 | if (fd.cFileName[0] == L'.' && fd.cFileName[1] == 0 || 72 | fd.cFileName[0] == L'.' && fd.cFileName[1] == L'.' && 73 | fd.cFileName[2] == 0) { 74 | // ignore current and parent directory 75 | } else { 76 | // construct the full name 77 | str_db_seek(&ctx->path, pos_root); 78 | const wchar_t *filename = 79 | str_db_push_u16_le(&ctx->path, fd.cFileName, MAX_PATH); 80 | if (filename == NULL) { 81 | r = FL_OUT_OF_MEMORY; 82 | break; 83 | } 84 | if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 85 | // it's a directory, append \* 86 | const wchar_t *search = str_db_push_u16_le(&ctx->path, L"\\*", 2); 87 | if (search == NULL) { 88 | r = FL_OUT_OF_MEMORY; 89 | break; 90 | } 91 | r = WalkDirDfs(ctx); 92 | } else { 93 | // it's a file, fire callback 94 | const wchar_t *full = str_db_get(&ctx->path, 0); 95 | r = ctx->callback(full, &fd, ctx->arg); 96 | } 97 | } 98 | } while (r == FL_OK && FindNextFile(find_handle, &fd)); 99 | 100 | FindClose(find_handle); 101 | return r; 102 | } 103 | 104 | int FlWalkDir( 105 | const wchar_t *path, 106 | allocator_t *alloc, 107 | FL_FileWalkCb callback, 108 | void *arg) { 109 | int r; 110 | FL_WalkDirCtx ctx = {.callback = callback, .arg = arg}; 111 | str_db_init(&ctx.path, alloc, 0, 0); 112 | 113 | do { 114 | const wchar_t *a = str_db_push_u16_le(&ctx.path, path, 0); 115 | if (a == NULL) { 116 | r = FL_OUT_OF_MEMORY; 117 | break; 118 | } 119 | 120 | r = WalkDirDfs(&ctx); 121 | } while (0); 122 | 123 | str_db_free(&ctx.path); 124 | return r; 125 | } 126 | 127 | int FlWalkDirStr(str_db_t *path, FL_FileWalkCb callback, void *arg) { 128 | // assume path->pad_len == 0 129 | FL_WalkDirCtx ctx = {.callback = callback, .arg = arg, .path = *path}; 130 | const int r = WalkDirDfs(&ctx); 131 | *path = ctx.path; 132 | return r; 133 | } 134 | -------------------------------------------------------------------------------- /FontLoaderSub/path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util.h" 4 | #include "cstl.h" 5 | 6 | int FlResolvePath(const wchar_t *path, str_db_t *s); 7 | 8 | size_t FlPathParent(str_db_t *path); 9 | 10 | typedef int ( 11 | *FL_FileWalkCb)(const wchar_t *path, WIN32_FIND_DATA *data, void *arg); 12 | 13 | int FlWalkDir( 14 | const wchar_t *path, 15 | allocator_t *alloc, 16 | FL_FileWalkCb callback, 17 | void *arg); 18 | 19 | int FlWalkDirStr(str_db_t *path, FL_FileWalkCb callback, void *arg); 20 | -------------------------------------------------------------------------------- /FontLoaderSub/res/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FontLoaderSub/res/res.rc: -------------------------------------------------------------------------------- 1 | // we like UTF-8 2 | #pragma code_page(65001) 3 | 4 | // this file declares language netural resources 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // #include "resource.h" 11 | 12 | CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "app.manifest" 13 | -------------------------------------------------------------------------------- /FontLoaderSub/res/res_en-us.rc: -------------------------------------------------------------------------------- 1 | #pragma code_page(65001) 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "resource.h" 9 | 10 | #if 1 11 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 12 | STRINGTABLE 13 | { 14 | IDS_APP_NAME_VER "FontLoaderSub " FONTLOADERSUB_GIT_VERSION 15 | IDS_SHELL_VERB "FontLoaderSub here" 16 | IDS_SENDTO "FontLoaderSub" 17 | IDS_LOAD_STAT "%1!i! loaded. %2!i! failed. %3!i! unmatched.\n%4!i! files. %5!i! fonts. %6!i! subs." 18 | IDS_WORK_CANCELLING "Cancelling" 19 | IDS_WORK_SUBTITLE "Subtitle" 20 | IDS_WORK_CACHE "Cache" 21 | IDS_WORK_FONT "Font" 22 | IDS_WORK_LOAD "Load" 23 | IDS_WORK_UNLOAD "Unload" 24 | IDS_WORK_DONE "Done" 25 | IDS_HELP "Usage" 26 | IDS_USAGE "1. Move EXE to font folder,\n2. Drop ass/ssa/folder onto EXE, or use shortcuts,\n3. ""Rebuild index"" if fonts are changed." 27 | IDS_MANAGE_SHORTCUT "Manage shortcuts" 28 | IDS_SHORTCUT_ERROR_ADD "Failed to create shortcut" 29 | IDS_SHORTCUT_ERROR_DEL "Failed to remove shortcut" 30 | IDS_SHORTCUT_ADD_DIR_BG "Add to directory background" 31 | IDS_SHORTCUT_DEL_DIR_BG "Remove from directory background" 32 | IDS_SHORTCUT_ADD_SENDTO "Add to SendTo" 33 | IDS_SHORTCUT_DEL_SENDTO "Remove from SendTo" 34 | IDS_MENU "&Menu" 35 | } 36 | 37 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 38 | IDR_BTN_MENU MENU 39 | { 40 | POPUP "" 41 | { 42 | MENUITEM "&Rebuild index", ID_BTN_RESCAN 43 | MENUITEM "&Export fonts", ID_BTN_EXPORT 44 | MENUITEM SEPARATOR 45 | MENUITEM "&Help", ID_BTN_HELP 46 | } 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /FontLoaderSub/res/res_zh-cn.rc: -------------------------------------------------------------------------------- 1 | #pragma code_page(65001) 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "resource.h" 9 | 10 | #if 1 11 | LANGUAGE LANG_CHINESE, SUBLANG_SYS_DEFAULT 12 | STRINGTABLE 13 | { 14 | IDS_APP_NAME_VER "FontLoaderSub " FONTLOADERSUB_GIT_VERSION 15 | IDS_SHELL_VERB "加载字幕所需字体" 16 | IDS_SENDTO "FontLoaderSub" 17 | IDS_LOAD_STAT "%1!i! 个字体加载成功,%2!i! 个出错,%3!i! 个无匹配。\n索引中有 %4!i! 个字体,%5!i! 种名称;当前共 %6!i! 个字幕。" 18 | IDS_WORK_CANCELLING "取消中" 19 | IDS_WORK_SUBTITLE "解析字幕中" 20 | IDS_WORK_CACHE "读取索引中" 21 | IDS_WORK_FONT "扫描字体中" 22 | IDS_WORK_LOAD "加载中" 23 | IDS_WORK_UNLOAD "卸载中" 24 | IDS_WORK_DONE "完成" 25 | IDS_HELP "使用方法" 26 | IDS_USAGE "1. 将本程序移动到字体文件夹;\n2. 把字幕或文件夹拖动到程序上,或用快捷方式;\n3. 字体库变更后请 ""更新索引""。" 27 | IDS_MANAGE_SHORTCUT "快捷方式管理" 28 | IDS_SHORTCUT_ERROR_ADD "创建快捷方式时出错" 29 | IDS_SHORTCUT_ERROR_DEL "移除快捷方式时出错" 30 | IDS_SHORTCUT_ADD_DIR_BG "在 文件夹空白处右键菜单 中创建" 31 | IDS_SHORTCUT_DEL_DIR_BG "从 文件夹空白处右键菜单 里移除" 32 | IDS_SHORTCUT_ADD_SENDTO "在 发送到 中创建" 33 | IDS_SHORTCUT_DEL_SENDTO "从 发送到 里移除" 34 | IDS_MENU "菜单(&M)" 35 | } 36 | 37 | LANGUAGE LANG_CHINESE, SUBLANG_SYS_DEFAULT 38 | IDR_BTN_MENU MENU 39 | { 40 | POPUP "" 41 | { 42 | MENUITEM "更新索引(&R)", ID_BTN_RESCAN 43 | MENUITEM "导出字体(&E)", ID_BTN_EXPORT 44 | MENUITEM SEPARATOR 45 | MENUITEM "帮助(&H)", ID_BTN_HELP 46 | } 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /FontLoaderSub/res/res_zh-tw.rc: -------------------------------------------------------------------------------- 1 | #pragma code_page(65001) 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "resource.h" 9 | 10 | #if 1 11 | LANGUAGE LANG_CHINESE, SUBLANG_DEFAULT 12 | STRINGTABLE 13 | { 14 | IDS_APP_NAME_VER "FontLoaderSub " FONTLOADERSUB_GIT_VERSION 15 | IDS_SHELL_VERB "載入字幕所需字型" 16 | IDS_SENDTO "FontLoaderSub" 17 | IDS_LOAD_STAT "%1!i! 個字型載入成功,%2!i! 個出錯,%3!i! 個無匹配。\n索引中有 %4!i! 個字型,%5!i! 個名稱;當前共 %6!i! 個字幕。" 18 | IDS_WORK_CANCELLING "取消中" 19 | IDS_WORK_SUBTITLE "解析字幕中" 20 | IDS_WORK_CACHE "讀取索引中" 21 | IDS_WORK_FONT "掃描字型中" 22 | IDS_WORK_LOAD "載入中" 23 | IDS_WORK_UNLOAD "解除中" 24 | IDS_WORK_DONE "完成" 25 | IDS_HELP "使用方法" 26 | IDS_USAGE "1. 將執行檔移動到字型資料夾;\n2. 把字幕或資料夾拖曳到執行檔上,亦可利用捷徑;\n3. 字型庫變更後請「更新索引」。" 27 | IDS_MANAGE_SHORTCUT "捷徑管理" 28 | IDS_SHORTCUT_ERROR_ADD "新增捷徑時出錯" 29 | IDS_SHORTCUT_ERROR_DEL "移除捷徑時出錯" 30 | IDS_SHORTCUT_ADD_DIR_BG "在 資料夾空白處右鍵選單 中新增" 31 | IDS_SHORTCUT_DEL_DIR_BG "從 資料夾空白處右鍵選單 裡移除" 32 | IDS_SHORTCUT_ADD_SENDTO "在 傳送到 中新增" 33 | IDS_SHORTCUT_DEL_SENDTO "從 傳送到 裡移除" 34 | IDS_MENU "選單(&M)" 35 | } 36 | 37 | LANGUAGE LANG_CHINESE, SUBLANG_DEFAULT 38 | IDR_BTN_MENU MENU 39 | { 40 | POPUP "" 41 | { 42 | MENUITEM "更新索引(&R)", ID_BTN_RESCAN 43 | MENUITEM "匯出字型(&E)", ID_BTN_EXPORT 44 | MENUITEM SEPARATOR 45 | MENUITEM "說明(&H)", ID_BTN_HELP 46 | } 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /FontLoaderSub/res/resource.h: -------------------------------------------------------------------------------- 1 | #ifndef RESOURCE_H 2 | #define RESOURCE_H 3 | 4 | #ifndef FONTLOADERSUB_GIT_VERSION 5 | #define FONTLOADERSUB_GIT_VERSION "ver.unknown" 6 | #endif 7 | 8 | #define IDS_APP_NAME_VER 2 9 | #define IDS_SHELL_VERB 4 10 | #define IDS_SENDTO 6 11 | #define IDS_LOAD_STAT 8 12 | #define IDS_WORK_CANCELLING 10 13 | #define IDS_WORK_SUBTITLE 12 14 | #define IDS_WORK_CACHE 14 15 | #define IDS_WORK_FONT 16 16 | #define IDS_WORK_LOAD 18 17 | #define IDS_WORK_UNLOAD 20 18 | #define IDS_WORK_DONE 22 19 | #define IDS_HELP 24 20 | #define IDS_USAGE 26 21 | #define IDS_MANAGE_SHORTCUT 28 22 | #define IDS_SHORTCUT_ERROR_ADD 30 23 | #define IDS_SHORTCUT_ERROR_DEL 32 24 | #define IDS_SHORTCUT_ADD_DIR_BG 34 25 | #define IDS_SHORTCUT_DEL_DIR_BG 36 26 | #define IDS_SHORTCUT_ADD_SENDTO 38 27 | #define IDS_SHORTCUT_DEL_SENDTO 40 28 | #define IDS_MENU 42 29 | 30 | // control id 31 | #define ID_BTN_MENU 101 32 | #define ID_BTN_RESCAN 102 33 | #define ID_BTN_EXPORT 103 34 | #define ID_BTN_HELP 104 35 | 36 | // menu 37 | #define IDR_BTN_MENU 1 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /FontLoaderSub/shortcut.c: -------------------------------------------------------------------------------- 1 | #include "shortcut.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ass_string.h" 8 | #include "res/resource.h" 9 | 10 | typedef enum { 11 | SHORTCUT_MODE_QUERY, 12 | SHORTCUT_MODE_CREATE, 13 | SHORTCUT_MODE_DELETE 14 | } ShortcutMode; 15 | 16 | #define BUTTON_ID_START 1024 17 | 18 | typedef int (*ShortcutTogglers)(FL_ShortCtx *c, ShortcutMode mode); 19 | 20 | static int ShortcutExplorerDirectory( 21 | FL_ShortCtx *ctx, 22 | const WCHAR *key_path, 23 | ShortcutMode mode); 24 | 25 | static void ShortcutRefresh(FL_ShortCtx *ctx, int error); 26 | 27 | static HRESULT CALLBACK DlgShortcutProc( 28 | HWND hWnd, 29 | UINT uNotification, 30 | WPARAM wParam, 31 | LPARAM lParam, 32 | LONG_PTR dwRefData); 33 | 34 | void ShortcutInit(FL_ShortCtx *c, HINSTANCE hInst, allocator_t *alloc) { 35 | c->dlg.cbSize = sizeof c->dlg; 36 | c->dlg.hInstance = hInst; 37 | c->dlg.pszContent = MAKEINTRESOURCE(IDS_MANAGE_SHORTCUT); 38 | c->dlg.pButtons = c->button; 39 | c->dlg.cButtons = FL_SHORTCUT_MAX; 40 | c->dlg.dwCommonButtons = TDCBF_CLOSE_BUTTON; 41 | c->dlg.dwFlags |= TDF_USE_COMMAND_LINKS | TDF_ALLOW_DIALOG_CANCELLATION; 42 | c->dlg.lpCallbackData = (LONG_PTR)c; 43 | c->dlg.pfCallback = DlgShortcutProc; 44 | for (int i = 0; i != FL_SHORTCUT_MAX; i++) { 45 | c->button[i].nButtonID = BUTTON_ID_START + i; 46 | } 47 | str_db_init(&c->tmp, alloc, 0, 0); 48 | } 49 | 50 | void ShortcutShow(FL_ShortCtx *c, HWND hWnd) { 51 | c->dlg.hwndParent = hWnd; 52 | c->dlg.pszWindowTitle = c->dlg_title; 53 | c->dlg.nDefaultButton = IDCLOSE; 54 | ShortcutRefresh(c, 0); 55 | TaskDialogIndirect(&c->dlg, NULL, NULL, NULL); 56 | } 57 | 58 | int ShortcutExplorerDirectory( 59 | FL_ShortCtx *c, 60 | const WCHAR *key_path, 61 | ShortcutMode mode) { 62 | HKEY root = NULL; 63 | HKEY command = NULL; 64 | LSTATUS ret; 65 | int succ = 0; 66 | str_db_seek(&c->tmp, 0); 67 | 68 | do { 69 | if (!str_db_push_u16_le(&c->tmp, key_path, 0) || 70 | !str_db_push_u16_le(&c->tmp, c->key, 0)) 71 | break; 72 | const WCHAR *key = str_db_get(&c->tmp, 0); 73 | 74 | if (mode == SHORTCUT_MODE_QUERY) { 75 | ret = RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_QUERY_VALUE, &root); 76 | if (ret == ERROR_SUCCESS) { 77 | succ = 1; 78 | } 79 | } else if (mode == SHORTCUT_MODE_DELETE) { 80 | ret = RegDeleteTree(HKEY_CURRENT_USER, key); 81 | succ = 1; 82 | } else if (mode == SHORTCUT_MODE_CREATE) { 83 | DWORD disposition; 84 | ret = RegCreateKeyEx( 85 | HKEY_CURRENT_USER, key, 0, NULL, REG_OPTION_NON_VOLATILE, 86 | KEY_ALL_ACCESS, NULL, &root, &disposition); 87 | if (ret != ERROR_SUCCESS) 88 | break; 89 | 90 | WCHAR res_suffix[16]; 91 | FormatMessage( 92 | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, 93 | L"\",-%1!i!", 0, 0, res_suffix, _countof(res_suffix), 94 | (va_list *)&c->dir_bg_menu_str_id); 95 | 96 | str_db_seek(&c->tmp, 0); 97 | if (!str_db_push_u16_le(&c->tmp, L"@\"", 0) || 98 | !str_db_push_u16_le(&c->tmp, c->path, 0) || 99 | !str_db_push_u16_le(&c->tmp, res_suffix, 0)) 100 | break; 101 | const WCHAR *verb = str_db_get(&c->tmp, 0); 102 | ret = RegSetValueEx( 103 | root, TEXT("MUIVerb"), 0, REG_SZ, (const BYTE *)verb, 104 | str_db_tell(&c->tmp) * sizeof verb[0]); 105 | if (ret != ERROR_SUCCESS) 106 | break; 107 | 108 | ret = RegCreateKeyEx( 109 | root, L"command", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 110 | NULL, &command, &disposition); 111 | if (ret != ERROR_SUCCESS) 112 | break; 113 | 114 | str_db_seek(&c->tmp, 0); 115 | if (!str_db_push_u16_le(&c->tmp, L"\"", 0) || 116 | !str_db_push_u16_le(&c->tmp, c->path, 0) || 117 | !str_db_push_u16_le(&c->tmp, L"\" \"%V\"", 0)) 118 | break; 119 | const WCHAR *path = str_db_get(&c->tmp, 0); 120 | ret = RegSetValueEx( 121 | command, NULL, 0, REG_SZ, (const BYTE *)path, 122 | str_db_tell(&c->tmp) * sizeof path[0]); 123 | if (ret != ERROR_SUCCESS) 124 | break; 125 | succ = 1; 126 | } 127 | } while (0); 128 | 129 | if (command) 130 | RegCloseKey(command); 131 | if (root) 132 | RegCloseKey(root); 133 | return succ; 134 | } 135 | 136 | static int ShortcutExplorerDirectoryBackground( 137 | FL_ShortCtx *c, 138 | ShortcutMode mode) { 139 | return ShortcutExplorerDirectory( 140 | c, L"Software\\Classes\\Directory\\Background\\shell\\", mode); 141 | } 142 | 143 | int ShortcutSendTo(FL_ShortCtx *c, ShortcutMode mode) { 144 | int succ = 0; 145 | HRESULT hr; 146 | PWSTR sendto_path = NULL; 147 | str_db_seek(&c->tmp, 0); 148 | 149 | do { 150 | hr = SHGetKnownFolderPath(&FOLDERID_SendTo, 0, NULL, &sendto_path); 151 | if (FAILED(hr)) 152 | break; 153 | if (!str_db_push_u16_le(&c->tmp, sendto_path, 0) || 154 | !str_db_push_u16_le(&c->tmp, L"\\", 0) || 155 | !str_db_push_u16_le( 156 | &c->tmp, ResLoadString(c->dlg.hInstance, c->sendto_str_id), 0) || 157 | !str_db_push_u16_le(&c->tmp, L".lnk", 0)) 158 | break; 159 | const WCHAR *path = str_db_get(&c->tmp, 0); 160 | if (mode == SHORTCUT_MODE_QUERY) { 161 | HANDLE h = CreateFile( 162 | path, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 163 | NULL); 164 | if (h != INVALID_HANDLE_VALUE) { 165 | CloseHandle(h); 166 | succ = 1; 167 | } 168 | } else if (mode == SHORTCUT_MODE_DELETE) { 169 | HANDLE h = CreateFile( 170 | path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 171 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL); 172 | if (h != INVALID_HANDLE_VALUE) { 173 | CloseHandle(h); 174 | succ = 1; 175 | } 176 | } else if (mode == SHORTCUT_MODE_CREATE) { 177 | IShellLink *psl; 178 | hr = CoCreateInstance( 179 | &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLink, 180 | (void **)&psl); 181 | if (SUCCEEDED(hr)) { 182 | // set shortcut target 183 | hr = psl->lpVtbl->SetPath(psl, c->path); 184 | if (SUCCEEDED(hr)) { 185 | IPersistFile *ppf; 186 | hr = psl->lpVtbl->QueryInterface( 187 | psl, &IID_IPersistFile, (void **)&ppf); 188 | if (SUCCEEDED(hr)) { 189 | hr = ppf->lpVtbl->Save(ppf, path, TRUE); 190 | if (SUCCEEDED(hr)) { 191 | succ = 1; 192 | } 193 | ppf->lpVtbl->Release(ppf); 194 | } 195 | } 196 | psl->lpVtbl->Release(psl); 197 | } 198 | } 199 | } while (0); 200 | if (sendto_path) 201 | CoTaskMemFree(sendto_path); 202 | return succ; 203 | } 204 | 205 | static void ShortcutRefresh(FL_ShortCtx *c, int error) { 206 | c->setup[FL_SHORTCUT_CONTEXT] = 207 | ShortcutExplorerDirectoryBackground(c, SHORTCUT_MODE_QUERY); 208 | c->button[FL_SHORTCUT_CONTEXT].pszButtonText = 209 | c->setup[FL_SHORTCUT_CONTEXT] ? MAKEINTRESOURCE(IDS_SHORTCUT_DEL_DIR_BG) 210 | : MAKEINTRESOURCE(IDS_SHORTCUT_ADD_DIR_BG); 211 | c->setup[FL_SHORTCUT_SENDTO] = ShortcutSendTo(c, SHORTCUT_MODE_QUERY); 212 | c->button[FL_SHORTCUT_SENDTO].pszButtonText = 213 | c->setup[FL_SHORTCUT_SENDTO] ? MAKEINTRESOURCE(IDS_SHORTCUT_DEL_SENDTO) 214 | : MAKEINTRESOURCE(IDS_SHORTCUT_ADD_SENDTO); 215 | 216 | if (error) { 217 | c->dlg.pszFooterIcon = TD_WARNING_ICON; 218 | c->dlg.pszFooter = MAKEINTRESOURCE(error); 219 | } else { 220 | c->dlg.pszFooterIcon = NULL; 221 | c->dlg.pszFooter = NULL; 222 | } 223 | } 224 | 225 | static const ShortcutTogglers kShortcutToggler[FL_SHORTCUT_MAX] = { 226 | [FL_SHORTCUT_SENDTO] = ShortcutSendTo, 227 | [FL_SHORTCUT_CONTEXT] = ShortcutExplorerDirectoryBackground}; 228 | 229 | static HRESULT CALLBACK DlgShortcutProc( 230 | HWND hWnd, 231 | UINT uNotification, 232 | WPARAM wParam, 233 | LPARAM lParam, 234 | LONG_PTR dwRefData) { 235 | FL_ShortCtx *c = (FL_ShortCtx *)dwRefData; 236 | switch (uNotification) { 237 | case TDN_BUTTON_CLICKED: { 238 | if (BUTTON_ID_START <= wParam && 239 | wParam < BUTTON_ID_START + FL_SHORTCUT_MAX) { 240 | int id = wParam - BUTTON_ID_START; 241 | int succ = kShortcutToggler[id]( 242 | c, c->setup[id] ? SHORTCUT_MODE_DELETE : SHORTCUT_MODE_CREATE); 243 | // if (GetTickCount() / 1000 % 2 == 0) succ = 0; 244 | int err = c->setup[id] ? IDS_SHORTCUT_ERROR_DEL : IDS_SHORTCUT_ERROR_ADD; 245 | ShortcutRefresh(c, succ ? 0 : err); 246 | c->dlg.nDefaultButton = wParam; 247 | SendMessage(hWnd, TDM_NAVIGATE_PAGE, 0, (LPARAM)&c->dlg); 248 | return S_FALSE; 249 | } 250 | } 251 | } 252 | // return S_FALSE; // not to close 253 | return S_OK; // otherwise 254 | } 255 | -------------------------------------------------------------------------------- /FontLoaderSub/shortcut.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "cstl.h" 7 | 8 | enum { FL_SHORTCUT_SENDTO = 0, FL_SHORTCUT_CONTEXT, FL_SHORTCUT_MAX }; 9 | 10 | typedef struct { 11 | const WCHAR *key; 12 | const WCHAR *dlg_title; 13 | UINT sendto_str_id; 14 | UINT dir_bg_menu_str_id; 15 | const WCHAR *path; 16 | str_db_t tmp; 17 | TASKDIALOGCONFIG dlg; 18 | TASKDIALOG_BUTTON button[FL_SHORTCUT_MAX]; 19 | int setup[FL_SHORTCUT_MAX]; 20 | } FL_ShortCtx; 21 | 22 | void ShortcutInit(FL_ShortCtx *ctx, HINSTANCE hInst, allocator_t *alloc); 23 | 24 | void ShortcutShow(FL_ShortCtx *ctx, HWND hWnd); 25 | -------------------------------------------------------------------------------- /FontLoaderSub/test.c: -------------------------------------------------------------------------------- 1 | #include "ass_parser.h" 2 | 3 | static int null_cb(const wchar_t *font, size_t cch, void *arg) { 4 | return 0; 5 | } 6 | 7 | int test_main() { 8 | const char data[] = {0x5b, 0x00}; 9 | const wchar_t *wc = (const wchar_t *)data; 10 | ass_process_data(wc, sizeof data / 2, null_cb, NULL); 11 | return 1; 12 | } 13 | -------------------------------------------------------------------------------- /FontLoaderSub/tim_sort.c: -------------------------------------------------------------------------------- 1 | #include "tim_sort.h" 2 | #include "util.h" 3 | 4 | typedef struct { 5 | uint8_t *data; 6 | uint8_t *temp; 7 | size_t size; 8 | Sort_Compare comp; 9 | void *arg; 10 | } Sort_Ctx; 11 | 12 | static void select_sort(size_t low, size_t high, Sort_Ctx *ctx) { 13 | uint8_t *data = ctx->data; 14 | const size_t size = ctx->size; 15 | for (size_t i = low; i != high; i++) { 16 | size_t m = i; 17 | for (size_t j = i + 1; j != high; j++) { 18 | if (ctx->comp(&data[m * size], &data[j * size], ctx->arg) > 0) { 19 | m = j; 20 | } 21 | } 22 | if (m != i) { 23 | for (size_t j = 0; j != size; j++) { 24 | const uint8_t t = data[i * size + j]; 25 | data[i * size + j] = data[m * size + j]; 26 | data[m * size + j] = t; 27 | } 28 | } 29 | } 30 | } 31 | 32 | static void tim_sort_i(size_t low, size_t high, Sort_Ctx *ctx) { 33 | if (low + 4 > high) { 34 | select_sort(low, high, ctx); 35 | return; 36 | } 37 | uint8_t *data = ctx->data; 38 | uint8_t *temp = ctx->temp; 39 | const size_t size = ctx->size; 40 | const size_t mid = low + (high - low) / 2; 41 | tim_sort_i(low, mid, ctx); 42 | tim_sort_i(mid, high, ctx); 43 | 44 | uint8_t *pa = &temp[low * size]; 45 | uint8_t *pb = &temp[mid * size]; 46 | uint8_t *p_mid = pb; 47 | uint8_t *p_high = &temp[high * size]; 48 | uint8_t *pt = &data[low * size]; 49 | zmemcpy(pa, pt, size * (high - low)); 50 | 51 | while (pa != p_mid && pb != p_high) { 52 | if (ctx->comp(pa, pb, ctx->arg) <= 0) { 53 | zmemcpy(pt, pa, size); 54 | pa += size; 55 | } else { 56 | zmemcpy(pt, pb, size); 57 | pb += size; 58 | } 59 | pt += size; 60 | } 61 | while (pa != p_mid) { 62 | zmemcpy(pt, pa, size); 63 | pt += size, pa += size; 64 | } 65 | while (pb != p_high) { 66 | zmemcpy(pt, pb, size); 67 | pt += size, pb += size; 68 | } 69 | } 70 | 71 | void tim_sort( 72 | void *ptr, 73 | size_t count, 74 | size_t size, 75 | allocator_t *alloc, 76 | Sort_Compare comp, 77 | void *arg) { 78 | if (count < 2) { 79 | return; 80 | } 81 | Sort_Ctx ctx = {.data = ptr, 82 | .temp = alloc->alloc(NULL, count * size, alloc->arg), 83 | .size = size, 84 | .comp = comp, 85 | .arg = arg}; 86 | if (ctx.temp == NULL) { 87 | // fallback 88 | select_sort(0, count, &ctx); 89 | } else { 90 | tim_sort_i(0, count, &ctx); 91 | alloc->alloc(ctx.temp, 0, alloc->arg); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /FontLoaderSub/tim_sort.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util.h" 4 | 5 | typedef int (*Sort_Compare)(const void *a, const void *b, void *arg); 6 | 7 | void tim_sort( 8 | void *ptr, 9 | size_t count, 10 | size_t size, 11 | allocator_t *alloc, 12 | Sort_Compare comp, 13 | void *arg); 14 | -------------------------------------------------------------------------------- /FontLoaderSub/ttf_parser.c: -------------------------------------------------------------------------------- 1 | #include "ttf_parser.h" 2 | #include "util.h" 3 | 4 | #define MAKE_TAG(a, b, c, d) \ 5 | ((uint32_t)(((uint8_t)(d) << 24)) | (uint32_t)(((uint8_t)(c) << 16)) | \ 6 | (uint32_t)(((uint8_t)(b) << 8)) | (uint32_t)(((uint8_t)(a)))) 7 | 8 | typedef enum FONT_TAG { 9 | FONT_TAG_TTCF = MAKE_TAG('t', 't', 'c', 'f'), 10 | FONT_TAG_OTTO = MAKE_TAG('O', 'T', 'T', 'O'), 11 | FONT_TAG_NAME = MAKE_TAG('n', 'a', 'm', 'e') 12 | } FONT_TAG; 13 | 14 | typedef struct { 15 | union { 16 | uint32_t tag; 17 | char tag_chr[4]; 18 | }; 19 | uint16_t major_ver; 20 | uint16_t minor_ver; 21 | uint32_t num_fonts; 22 | } TTC_Header; 23 | 24 | typedef struct { 25 | union { 26 | uint32_t tag; 27 | char tag_chr[4]; 28 | }; 29 | uint16_t num_tables; 30 | uint16_t search_range; 31 | uint16_t entry_selector; 32 | uint16_t range_shift; 33 | } OTF_Header; 34 | 35 | typedef struct { 36 | union { 37 | uint32_t tag; 38 | char tag_chr[4]; 39 | }; 40 | uint32_t checksum; 41 | uint32_t offset; 42 | uint32_t length; 43 | } OTF_HeaderRecord; 44 | 45 | typedef struct { 46 | uint16_t format; 47 | uint16_t count; 48 | uint16_t offset; 49 | } OTF_NameHeader; 50 | 51 | typedef enum OTF_PLATFORM { 52 | OTF_PLATFORM_UNICODE = 0, 53 | OTF_PLATFORM_WINDOWS = 3 54 | } OTF_PLATFORM; 55 | 56 | static int is_interested_name_id(uint16_t name_id) { 57 | switch (name_id) { 58 | case 1: // Font Family name 59 | case 4: // Full font name 60 | case 6: // PostScript name for the font 61 | return 1; 62 | case 0: // Copyright notice 63 | case 2: // Font Subfamily name 64 | case 3: // Unique font identifier 65 | case 5: // Version string 66 | case 7: // Trademark 67 | case 8: // Manufacturer Name 68 | case 9: // Designer 69 | case 10: // Description 70 | case 11: // URL Vendor 71 | case 12: // URL Designer 72 | case 13: // License Description 73 | case 14: // License Info URL 74 | case 15: // Reserved 75 | case 16: // Typographic Family name 76 | case 17: // Typographic Subfamily name 77 | case 18: // Compatible Full (Macintosh only) 78 | case 19: // Sample text 79 | case 20: // PostScript CID find font name 80 | case 21: // WWS Family Name 81 | case 22: // WWS Subfamily Name 82 | case 23: // Light Background Palette 83 | case 24: // Dark Background Palette 84 | case 25: // Variations PostScript Name Prefix 85 | default: 86 | return 0; 87 | } 88 | } 89 | 90 | static int otf_parse_table_name( 91 | uint32_t font_id, 92 | const uint8_t *buffer, 93 | const uint8_t *eos, 94 | OTF_NameCallback cb, 95 | void *arg) { 96 | OTF_NameHeader *head = (OTF_NameHeader *)buffer; 97 | if (buffer + sizeof *head > eos) 98 | return FL_CORRUPTED; 99 | 100 | // extract header 101 | const uint16_t format = be16(head->format); 102 | if (format != 0) 103 | return FL_UNRECOGNIZED; 104 | const uint16_t count = be16(head->count); 105 | const uint8_t *str_buffer = buffer + be16(head->offset); 106 | OTF_NameRecord *records = (OTF_NameRecord *)(head + 1); 107 | 108 | // range check for records 109 | if (buffer + sizeof *head + count * sizeof records[0] > eos) 110 | return FL_CORRUPTED; 111 | // range check for all strings 112 | for (uint16_t i = 0; i != count; i++) { 113 | OTF_NameRecord *r = &records[i]; 114 | if (str_buffer + be16(r->offset) + be16(r->length) > eos) 115 | return FL_CORRUPTED; 116 | } 117 | 118 | int ret; 119 | 120 | // loop & callback for Version 121 | for (uint16_t i = 0; i != count; i++) { 122 | OTF_NameRecord *r = &records[i]; 123 | // filter version string (name_id == 5) 124 | if (r->name_id == be16(5) && r->platform == be16(OTF_PLATFORM_WINDOWS)) { 125 | wchar_t *str_be = (wchar_t *)(str_buffer + be16(r->offset)); 126 | // fire callback 127 | ret = cb(font_id, r, str_be, arg); 128 | if (ret != FL_OK) 129 | return ret; 130 | } 131 | } 132 | 133 | // loop & callback for each record 134 | for (uint16_t i = 0; i != count; i++) { 135 | OTF_NameRecord *r = &records[i]; 136 | // filter 137 | if (r->platform == be16(OTF_PLATFORM_WINDOWS) && 138 | is_interested_name_id(be16(r->name_id))) { 139 | wchar_t *str_be = (wchar_t *)(str_buffer + be16(r->offset)); 140 | // fire callback 141 | ret = cb(font_id, r, str_be, arg); 142 | if (ret != FL_OK) 143 | return ret; 144 | } 145 | } 146 | return FL_OK; 147 | } 148 | 149 | static int otf_parse_internal( 150 | uint32_t font_id, 151 | const uint8_t *buffer, 152 | const uint8_t *eos, 153 | const uint8_t *start, 154 | OTF_NameCallback cb, 155 | void *arg) { 156 | OTF_Header *head = (OTF_Header *)buffer; 157 | if (buffer + sizeof *head > eos) 158 | return FL_UNRECOGNIZED; 159 | if (head->tag != FONT_TAG_OTTO && head->tag != be32(0x00010000)) 160 | return FL_UNRECOGNIZED; 161 | 162 | const uint16_t num_tables = be16(head->num_tables); 163 | OTF_HeaderRecord *record = (OTF_HeaderRecord *)(head + 1); 164 | if (buffer + sizeof *head + num_tables * sizeof record[0] > eos) 165 | return FL_CORRUPTED; 166 | 167 | for (uint16_t i = 0; i != num_tables; i++) { 168 | if (record[i].tag == FONT_TAG_NAME) { 169 | const uint8_t *ptr = start + be32(record[i].offset); 170 | const uint32_t length = be32(record[i].length); 171 | if (ptr + length > eos) 172 | return FL_CORRUPTED; 173 | const int r = otf_parse_table_name(font_id, ptr, ptr + length, cb, arg); 174 | if (r != FL_OK) 175 | return r; 176 | } 177 | } 178 | return FL_OK; 179 | } 180 | 181 | int otf_parse(const uint8_t *buf, size_t size, OTF_NameCallback cb, void *arg) { 182 | return otf_parse_internal(0, buf, buf + size, buf, cb, arg); 183 | } 184 | 185 | int ttc_parse(const uint8_t *buf, size_t size, OTF_NameCallback cb, void *arg) { 186 | const uint8_t *eos = buf + size; 187 | TTC_Header *head = (TTC_Header *)buf; 188 | if (size < sizeof *head) 189 | return FL_UNRECOGNIZED; 190 | if (head->tag != FONT_TAG_TTCF) 191 | return FL_UNRECOGNIZED; 192 | 193 | const uint32_t num_fonts = be32(head->num_fonts); 194 | const uint32_t *offset = (uint32_t *)(head + 1); 195 | if (size < (sizeof *head) + sizeof(offset[0]) * num_fonts) 196 | return FL_CORRUPTED; 197 | 198 | for (uint32_t i = 0; i != num_fonts; i++) { 199 | const uint8_t *ptr = buf + be32(offset[i]); 200 | if (ptr >= eos) 201 | return FL_CORRUPTED; 202 | 203 | const int r = otf_parse_internal(i, ptr, eos, buf, cb, arg); 204 | if (r != FL_OK) 205 | return r; 206 | } 207 | return FL_OK; 208 | } 209 | -------------------------------------------------------------------------------- /FontLoaderSub/ttf_parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct { 6 | uint16_t platform; 7 | uint16_t encoding; 8 | uint16_t lang_id; 9 | uint16_t name_id; 10 | uint16_t length; // bytes for string 11 | uint16_t offset; // to string, from otf_name_header::offset 12 | } OTF_NameRecord; 13 | 14 | typedef int (*OTF_NameCallback)( 15 | uint32_t font_id, 16 | OTF_NameRecord *r, 17 | const wchar_t *str, 18 | void *arg); 19 | 20 | int otf_parse(const uint8_t *buf, size_t size, OTF_NameCallback cb, void *arg); 21 | 22 | int ttc_parse(const uint8_t *buf, size_t size, OTF_NameCallback cb, void *arg); 23 | -------------------------------------------------------------------------------- /FontLoaderSub/util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #pragma intrinsic(__movsb) 7 | #pragma intrinsic(__stosb) 8 | 9 | int FlMemMap(const wchar_t *path, memmap_t *mmap) { 10 | mmap->map = NULL; 11 | mmap->data = NULL; 12 | mmap->size = 0; 13 | HANDLE h; 14 | do { 15 | h = CreateFile( 16 | path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, 17 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 18 | if (h == INVALID_HANDLE_VALUE) 19 | break; 20 | mmap->map = CreateFileMapping(h, NULL, PAGE_READONLY, 0, 0, NULL); 21 | if (mmap->map == NULL) 22 | break; 23 | mmap->data = MapViewOfFile(mmap->map, FILE_MAP_READ, 0, 0, 0); 24 | if (mmap->data == NULL) 25 | break; 26 | DWORD high = 0; 27 | mmap->size = GetFileSize(h, &high); 28 | // mmap->size = (high * 0x100000000UL) | (mmap->size); 29 | } while (0); 30 | 31 | if (mmap->data == NULL) { 32 | FlMemUnmap(mmap); 33 | } 34 | CloseHandle(h); 35 | return 0; 36 | } 37 | 38 | int FlMemUnmap(memmap_t *mmap) { 39 | UnmapViewOfFile(mmap->data); 40 | CloseHandle(mmap->map); 41 | mmap->map = NULL; 42 | mmap->data = NULL; 43 | mmap->size = 0; 44 | return 0; 45 | } 46 | 47 | static int FlTestUtf8(const uint8_t *buffer, size_t size) { 48 | const uint8_t *p, *last; 49 | int rem = 0; 50 | for (p = buffer, last = buffer + size; p != last; p++) { 51 | if (rem) { 52 | if ((*p & 0xc0) == 0x80) { 53 | // 10xxxxxx 54 | --rem; 55 | } else { 56 | return 0; 57 | } 58 | } else if ((*p & 0x80) == 0) { 59 | // rem = 0; 60 | } else if ((*p & 0xe0) == 0xc0) { 61 | // 110xxxxx 62 | rem = 1; 63 | } else if ((*p & 0xf0) == 0xe0) { 64 | // 1110xxxx 65 | rem = 2; 66 | } else if ((*p & 0xf8) == 0xf0) { 67 | // 11110xxx 68 | rem = 3; 69 | } else { 70 | return 0; 71 | } 72 | } 73 | return rem == 0; 74 | } 75 | 76 | static wchar_t *FlTextTryDecode( 77 | UINT codepage, 78 | const uint8_t *mstr, 79 | size_t bytes, 80 | size_t *cch, 81 | allocator_t *alloc) { 82 | wchar_t *buf = NULL; 83 | int ok = 0; 84 | do { 85 | const int r = 86 | MultiByteToWideChar(codepage, 0, (const char *)mstr, bytes, NULL, 0); 87 | *cch = r; 88 | if (r == 0) 89 | break; 90 | 91 | buf = (wchar_t *)alloc->alloc(buf, (r + 1) * sizeof buf[0], alloc->arg); 92 | if (buf == NULL) 93 | break; 94 | 95 | const int new_r = 96 | MultiByteToWideChar(codepage, 0, (const char *)mstr, bytes, buf, r); 97 | if (new_r == 0 || new_r != r) 98 | break; 99 | buf[r] = 0; 100 | ok = 1; 101 | } while (0); 102 | 103 | if (!ok) { 104 | alloc->alloc(buf, 0, alloc->arg); 105 | buf = NULL; 106 | } 107 | return buf; 108 | } 109 | 110 | static wchar_t *FlTextDecodeUtf16( 111 | int big_endian, 112 | const uint8_t *mstr, 113 | size_t bytes, 114 | size_t *cch, 115 | allocator_t *alloc) { 116 | const wchar_t *wstr = (const wchar_t*)mstr; 117 | wchar_t *buf = NULL; 118 | int ok = 0; 119 | 120 | do { 121 | const size_t r = *cch = bytes / 2; 122 | buf = (wchar_t *)alloc->alloc(buf, (r + 1) * sizeof buf[0], alloc->arg); 123 | if (buf == NULL) 124 | break; 125 | 126 | for (size_t i = 0; i != r; i++) { 127 | buf[i] = big_endian ? be16(wstr[i]) : wstr[i]; 128 | } 129 | buf[r] = 0; 130 | ok = 1; 131 | } while (0); 132 | 133 | if (!ok) { 134 | alloc->alloc(buf, 0, alloc->arg); 135 | buf = NULL; 136 | } 137 | return buf; 138 | } 139 | 140 | wchar_t *FlTextDecode( 141 | const uint8_t *buf, 142 | size_t bytes, 143 | size_t *cch, 144 | allocator_t *alloc) { 145 | wchar_t *res = NULL; 146 | if (bytes < 4) 147 | return res; 148 | 149 | // detect BOM 150 | if (buf[0] == 0xef && buf[1] == 0xbb && buf[2] == 0xbf) { 151 | res = FlTextTryDecode(CP_UTF8, buf + 3, bytes - 3, cch, alloc); 152 | } else if (buf[0] == 0xff && buf[1] == 0xfe) { 153 | // UTF-16 LE 154 | res = FlTextDecodeUtf16(0, buf + 2, bytes - 2, cch, alloc); 155 | } else if (buf[0] == 0xfe && buf[1] == 0xff) { 156 | // UTF-16 BE 157 | res = FlTextDecodeUtf16(1, buf + 2, bytes - 2, cch, alloc); 158 | } 159 | 160 | // detect UTF-8 161 | if (!res && FlTestUtf8(buf, bytes)) { 162 | res = FlTextTryDecode(CP_UTF8, buf, bytes, cch, alloc); 163 | } 164 | // final resort 165 | if (!res) { 166 | res = FlTextTryDecode(CP_ACP, buf, bytes, cch, alloc); 167 | } 168 | return res; 169 | } 170 | 171 | static int is_digit(wchar_t ch) { 172 | if (L'0' <= ch && ch <= L'9') 173 | return ch - L'0'; 174 | else 175 | return -1; 176 | } 177 | 178 | int FlVersionCmp(const wchar_t *a, const wchar_t *b) { 179 | const wchar_t *ptr_a = a, *ptr_b = b; 180 | int cmp = 0; 181 | 182 | if (b == NULL) 183 | return 1; 184 | if (a == NULL) 185 | return -1; 186 | 187 | while (*ptr_a && *ptr_b && cmp == 0) { 188 | if (is_digit(*ptr_a) >= 0 && is_digit(*ptr_b) >= 0) { 189 | // seek to the end of digits 190 | const wchar_t *start_a = ptr_a, *start_b = ptr_b; 191 | while (is_digit(*ptr_a) >= 0) 192 | ptr_a++; 193 | while (is_digit(*ptr_b) >= 0) 194 | ptr_b++; 195 | // compare from right to left 196 | const wchar_t *dig_a = ptr_a, *dig_b = ptr_b; 197 | while (dig_a != start_a && dig_b != start_b) { 198 | dig_a--, dig_b--; 199 | cmp = *dig_a - *dig_b; 200 | } 201 | // leading zero 202 | while (dig_a != start_a && dig_a[-1] == L'0') { 203 | dig_a--; 204 | } 205 | while (dig_b != start_b && dig_b[-1] == L'0') { 206 | dig_b--; 207 | } 208 | if (dig_a != start_a) { 209 | cmp = 1; 210 | } else if (dig_b != start_b) { 211 | cmp = -1; 212 | } 213 | } else if (*ptr_a != *ptr_b) { 214 | cmp = *ptr_a - *ptr_b; 215 | } else { 216 | ptr_a++, ptr_b++; 217 | } 218 | } 219 | 220 | if (cmp == 0) { 221 | if (*ptr_a) 222 | cmp = 1; 223 | else if (*ptr_b) 224 | cmp = -1; 225 | } 226 | 227 | return cmp; 228 | } 229 | 230 | int FlStrCmpIW(const wchar_t *a, const wchar_t *b) { 231 | return StrCmpIW(a, b); 232 | } 233 | 234 | 235 | #include 236 | 237 | BOOL PerMonitorDpiHack() { 238 | typedef BOOL(WINAPI * PFN_SetProcessDpiAwarenessContext)( 239 | DPI_AWARENESS_CONTEXT value); 240 | typedef BOOL(WINAPI * PFN_SetProcessDPIAware)(VOID); 241 | typedef HRESULT(WINAPI * PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); 242 | typedef BOOL(WINAPI * PFN_EnablePerMonitorDialogScaling)(); 243 | PFN_SetProcessDpiAwarenessContext pSetProcessDpiAwarenessContext = NULL; 244 | PFN_EnablePerMonitorDialogScaling pEnablePerMonitorDialogScaling = NULL; 245 | PFN_SetProcessDPIAware pSetProcessDPIAware = NULL; 246 | PFN_SetProcessDpiAwareness pSetProcessDpiAwareness = NULL; 247 | DWORD result = 0; 248 | 249 | HMODULE user32 = GetModuleHandle(L"USER32"); 250 | if (user32 == NULL) 251 | return FALSE; 252 | 253 | pSetProcessDpiAwarenessContext = 254 | (PFN_SetProcessDpiAwarenessContext)GetProcAddress( 255 | user32, "SetProcessDpiAwarenessContext"); 256 | // find a private function, available on RS1, attempt 1 257 | /* 258 | pEnablePerMonitorDialogScaling = 259 | (PFN_EnablePerMonitorDialogScaling)GetProcAddress( 260 | user32, "EnablePerMonitorDialogScaling"); 261 | */ 262 | if (pEnablePerMonitorDialogScaling == NULL) { 263 | // attempt 2: 264 | pEnablePerMonitorDialogScaling = 265 | (PFN_EnablePerMonitorDialogScaling)GetProcAddress(user32, (LPCSTR)2577); 266 | } 267 | pSetProcessDPIAware = 268 | (PFN_SetProcessDPIAware)GetProcAddress(user32, "SetProcessDPIAware"); 269 | pSetProcessDpiAwareness = (PFN_SetProcessDpiAwareness)GetProcAddress( 270 | user32, "SetProcessDpiAwarenessInternal"); 271 | 272 | if (pSetProcessDpiAwarenessContext) { 273 | // preferred, official API, available since Win10 Creators 274 | pSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); 275 | } else if (pSetProcessDpiAwareness) { 276 | if (pEnablePerMonitorDialogScaling) { 277 | // enable per-monitor scaling on Win10RS1+ 278 | result = pSetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); 279 | result = pEnablePerMonitorDialogScaling(); 280 | } else { 281 | result = pSetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE); 282 | } 283 | } else if (pSetProcessDPIAware) { 284 | result = pSetProcessDPIAware(); 285 | } 286 | 287 | return 0; 288 | } 289 | 290 | const TCHAR *ResLoadString(HMODULE hInstance, UINT idText) { 291 | int res; 292 | const TCHAR *textptr = NULL; 293 | res = LoadString(hInstance, idText, (TCHAR *)&textptr, 0); 294 | if (textptr == NULL) { 295 | // logA("Failed to load res string"); 296 | textptr = L""; // failback 297 | } 298 | return textptr; 299 | } 300 | 301 | void *zmemset(void *dest, int ch, size_t count) { 302 | __stosb((unsigned char *)dest, (unsigned char)ch, count); 303 | return dest; 304 | } 305 | 306 | void *zmemcpy(void *dest, const void *src, size_t count) { 307 | __movsb((unsigned char *)dest, (const unsigned char *)src, count); 308 | return dest; 309 | } 310 | -------------------------------------------------------------------------------- /FontLoaderSub/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | enum FL_STATUS { 7 | FL_OK = 0, 8 | FL_OS_ERROR = 1, 9 | FL_OUT_OF_MEMORY = 2, 10 | FL_UNRECOGNIZED = 3, 11 | FL_CORRUPTED = 4, 12 | FL_DUP = 5, 13 | }; 14 | 15 | inline uint16_t be16(uint16_t v) { 16 | // check if compiles to `XCHG` 17 | // alter: _byteswap_ushort 18 | return (v >> 8) | (v << 8); 19 | } 20 | 21 | inline uint32_t be32(uint32_t num) { 22 | // check if compiles to `BSWAP` 23 | // alter: _byteswap_ulong 24 | return ((num >> 24) & 0xff) | // move byte 3 to byte 0 25 | ((num << 8) & 0xff0000) | // move byte 1 to byte 2 26 | ((num >> 8) & 0xff00) | // move byte 2 to byte 1 27 | ((num << 24) & 0xff000000); // byte 0 to byte 3 28 | } 29 | 30 | inline void FlBreak() { 31 | // DebugBreak(); 32 | } 33 | 34 | typedef struct _allocator_t { 35 | void *(*alloc)(void *existing, size_t size, void *arg); 36 | void *arg; 37 | } allocator_t; 38 | 39 | typedef struct { 40 | HANDLE map; 41 | void *data; 42 | size_t size; 43 | } memmap_t; 44 | 45 | int FlMemMap(const wchar_t *path, memmap_t *mmap); 46 | 47 | int FlMemUnmap(memmap_t *mmap); 48 | 49 | wchar_t * 50 | FlTextDecode(const uint8_t *buf, size_t bytes, size_t *cch, allocator_t *alloc); 51 | 52 | int FlVersionCmp(const wchar_t *a, const wchar_t *b); 53 | 54 | int FlStrCmpIW(const wchar_t *a, const wchar_t *b); 55 | 56 | BOOL PerMonitorDpiHack(); 57 | 58 | const TCHAR *ResLoadString(HMODULE hInstance, UINT idText); 59 | 60 | void *zmemset(void *dest, int ch, size_t count); 61 | 62 | void *zmemcpy(void *dest, const void *src, size_t count); 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FontLoaderSub 2 | 3 | OpenType font loader for subtitles (ASS/SSA), inspired by [CryptWizard's FontLoader](https://bitbucket.org/cryptw/fontloader). 4 | 5 | Instead of font files, drag-and-drop ASS/SSA subtitles and it will load corresponding font files in its directory. 6 | 7 | ## Usage 8 | 9 | 1. Move `FontLoaderSub.exe` to the root of font directory; 10 | 1. Drag-and-drop subtitles `*.ass` (or folders) onto `FontLoaderSub.exe`. 11 | 12 | ## UI 13 | 14 | * Line 1: requested fonts from subtitles; 15 | * Line 2: stats for font collection; 16 | * `OK`: minimize; 17 | * `Retry`: rebuild font cache; 18 | * `Close`, Esc key, Alt+F4: unload fonts and exit. 19 | 20 | ## Note 21 | 22 | * In order to work with huge font collections, font cache `fc-subs.db` will be built for fast lookup. 23 | * Only accept ASS/SSA files under 64MB, encoded in Unicode with BOM. 24 | * Windows 7 (or later) required. 25 | --------------------------------------------------------------------------------