├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── example.pdf ├── example.tex ├── fonts ├── wizpen.mf ├── wizpen.otf └── wizpen.ttf ├── img ├── example.png ├── key.png ├── logo.png └── logo.psd ├── lua └── spellbook.lua ├── script ├── tfm2otf.pe ├── tfm2ttf.pe └── wizpen.rb ├── spellbook.pdf ├── spellbook.tex └── texmf ├── fonts └── source │ └── wizpen │ └── wizpen.mf └── tex ├── wizpen.sty └── wizpen.tex /.gitignore: -------------------------------------------------------------------------------- 1 | *.swo 2 | *.swp 3 | 4 | *.aux 5 | *.log 6 | *.pdf 7 | *.pe 8 | *.afm 9 | *.pfb 10 | *.tfm 11 | *.*pk* 12 | 13 | /texmf/fonts/pk 14 | /texmf/fonts/tfm 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The LaTeX Project Public License 2 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 3 | 4 | LPPL Version 1.3c 2008-05-04 5 | 6 | Copyright 1999 2002-2008 LaTeX3 Project 7 | Everyone is allowed to distribute verbatim copies of this 8 | license document, but modification of it is not allowed. 9 | 10 | 11 | PREAMBLE 12 | ======== 13 | 14 | The LaTeX Project Public License (LPPL) is the primary license under 15 | which the LaTeX kernel and the base LaTeX packages are distributed. 16 | 17 | You may use this license for any work of which you hold the copyright 18 | and which you wish to distribute. This license may be particularly 19 | suitable if your work is TeX-related (such as a LaTeX package), but 20 | it is written in such a way that you can use it even if your work is 21 | unrelated to TeX. 22 | 23 | The section `WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE', 24 | below, gives instructions, examples, and recommendations for authors 25 | who are considering distributing their works under this license. 26 | 27 | This license gives conditions under which a work may be distributed 28 | and modified, as well as conditions under which modified versions of 29 | that work may be distributed. 30 | 31 | We, the LaTeX3 Project, believe that the conditions below give you 32 | the freedom to make and distribute modified versions of your work 33 | that conform with whatever technical specifications you wish while 34 | maintaining the availability, integrity, and reliability of 35 | that work. If you do not see how to achieve your goal while 36 | meeting these conditions, then read the document `cfgguide.tex' 37 | and `modguide.tex' in the base LaTeX distribution for suggestions. 38 | 39 | 40 | DEFINITIONS 41 | =========== 42 | 43 | In this license document the following terms are used: 44 | 45 | `Work' 46 | Any work being distributed under this License. 47 | 48 | `Derived Work' 49 | Any work that under any applicable law is derived from the Work. 50 | 51 | `Modification' 52 | Any procedure that produces a Derived Work under any applicable 53 | law -- for example, the production of a file containing an 54 | original file associated with the Work or a significant portion of 55 | such a file, either verbatim or with modifications and/or 56 | translated into another language. 57 | 58 | `Modify' 59 | To apply any procedure that produces a Derived Work under any 60 | applicable law. 61 | 62 | `Distribution' 63 | Making copies of the Work available from one person to another, in 64 | whole or in part. Distribution includes (but is not limited to) 65 | making any electronic components of the Work accessible by 66 | file transfer protocols such as FTP or HTTP or by shared file 67 | systems such as Sun's Network File System (NFS). 68 | 69 | `Compiled Work' 70 | A version of the Work that has been processed into a form where it 71 | is directly usable on a computer system. This processing may 72 | include using installation facilities provided by the Work, 73 | transformations of the Work, copying of components of the Work, or 74 | other activities. Note that modification of any installation 75 | facilities provided by the Work constitutes modification of the Work. 76 | 77 | `Current Maintainer' 78 | A person or persons nominated as such within the Work. If there is 79 | no such explicit nomination then it is the `Copyright Holder' under 80 | any applicable law. 81 | 82 | `Base Interpreter' 83 | A program or process that is normally needed for running or 84 | interpreting a part or the whole of the Work. 85 | 86 | A Base Interpreter may depend on external components but these 87 | are not considered part of the Base Interpreter provided that each 88 | external component clearly identifies itself whenever it is used 89 | interactively. Unless explicitly specified when applying the 90 | license to the Work, the only applicable Base Interpreter is a 91 | `LaTeX-Format' or in the case of files belonging to the 92 | `LaTeX-format' a program implementing the `TeX language'. 93 | 94 | 95 | 96 | CONDITIONS ON DISTRIBUTION AND MODIFICATION 97 | =========================================== 98 | 99 | 1. Activities other than distribution and/or modification of the Work 100 | are not covered by this license; they are outside its scope. In 101 | particular, the act of running the Work is not restricted and no 102 | requirements are made concerning any offers of support for the Work. 103 | 104 | 2. You may distribute a complete, unmodified copy of the Work as you 105 | received it. Distribution of only part of the Work is considered 106 | modification of the Work, and no right to distribute such a Derived 107 | Work may be assumed under the terms of this clause. 108 | 109 | 3. You may distribute a Compiled Work that has been generated from a 110 | complete, unmodified copy of the Work as distributed under Clause 2 111 | above, as long as that Compiled Work is distributed in such a way that 112 | the recipients may install the Compiled Work on their system exactly 113 | as it would have been installed if they generated a Compiled Work 114 | directly from the Work. 115 | 116 | 4. If you are the Current Maintainer of the Work, you may, without 117 | restriction, modify the Work, thus creating a Derived Work. You may 118 | also distribute the Derived Work without restriction, including 119 | Compiled Works generated from the Derived Work. Derived Works 120 | distributed in this manner by the Current Maintainer are considered to 121 | be updated versions of the Work. 122 | 123 | 5. If you are not the Current Maintainer of the Work, you may modify 124 | your copy of the Work, thus creating a Derived Work based on the Work, 125 | and compile this Derived Work, thus creating a Compiled Work based on 126 | the Derived Work. 127 | 128 | 6. If you are not the Current Maintainer of the Work, you may 129 | distribute a Derived Work provided the following conditions are met 130 | for every component of the Work unless that component clearly states 131 | in the copyright notice that it is exempt from that condition. Only 132 | the Current Maintainer is allowed to add such statements of exemption 133 | to a component of the Work. 134 | 135 | a. If a component of this Derived Work can be a direct replacement 136 | for a component of the Work when that component is used with the 137 | Base Interpreter, then, wherever this component of the Work 138 | identifies itself to the user when used interactively with that 139 | Base Interpreter, the replacement component of this Derived Work 140 | clearly and unambiguously identifies itself as a modified version 141 | of this component to the user when used interactively with that 142 | Base Interpreter. 143 | 144 | b. Every component of the Derived Work contains prominent notices 145 | detailing the nature of the changes to that component, or a 146 | prominent reference to another file that is distributed as part 147 | of the Derived Work and that contains a complete and accurate log 148 | of the changes. 149 | 150 | c. No information in the Derived Work implies that any persons, 151 | including (but not limited to) the authors of the original version 152 | of the Work, provide any support, including (but not limited to) 153 | the reporting and handling of errors, to recipients of the 154 | Derived Work unless those persons have stated explicitly that 155 | they do provide such support for the Derived Work. 156 | 157 | d. You distribute at least one of the following with the Derived Work: 158 | 159 | 1. A complete, unmodified copy of the Work; 160 | if your distribution of a modified component is made by 161 | offering access to copy the modified component from a 162 | designated place, then offering equivalent access to copy 163 | the Work from the same or some similar place meets this 164 | condition, even though third parties are not compelled to 165 | copy the Work along with the modified component; 166 | 167 | 2. Information that is sufficient to obtain a complete, 168 | unmodified copy of the Work. 169 | 170 | 7. If you are not the Current Maintainer of the Work, you may 171 | distribute a Compiled Work generated from a Derived Work, as long as 172 | the Derived Work is distributed to all recipients of the Compiled 173 | Work, and as long as the conditions of Clause 6, above, are met with 174 | regard to the Derived Work. 175 | 176 | 8. The conditions above are not intended to prohibit, and hence do not 177 | apply to, the modification, by any method, of any component so that it 178 | becomes identical to an updated version of that component of the Work as 179 | it is distributed by the Current Maintainer under Clause 4, above. 180 | 181 | 9. Distribution of the Work or any Derived Work in an alternative 182 | format, where the Work or that Derived Work (in whole or in part) is 183 | then produced by applying some process to that format, does not relax or 184 | nullify any sections of this license as they pertain to the results of 185 | applying that process. 186 | 187 | 10. a. A Derived Work may be distributed under a different license 188 | provided that license itself honors the conditions listed in 189 | Clause 6 above, in regard to the Work, though it does not have 190 | to honor the rest of the conditions in this license. 191 | 192 | b. If a Derived Work is distributed under a different license, that 193 | Derived Work must provide sufficient documentation as part of 194 | itself to allow each recipient of that Derived Work to honor the 195 | restrictions in Clause 6 above, concerning changes from the Work. 196 | 197 | 11. This license places no restrictions on works that are unrelated to 198 | the Work, nor does this license place any restrictions on aggregating 199 | such works with the Work by any means. 200 | 201 | 12. Nothing in this license is intended to, or may be used to, prevent 202 | complete compliance by all parties with all applicable laws. 203 | 204 | 205 | NO WARRANTY 206 | =========== 207 | 208 | There is no warranty for the Work. Except when otherwise stated in 209 | writing, the Copyright Holder provides the Work `as is', without 210 | warranty of any kind, either expressed or implied, including, but not 211 | limited to, the implied warranties of merchantability and fitness for a 212 | particular purpose. The entire risk as to the quality and performance 213 | of the Work is with you. Should the Work prove defective, you assume 214 | the cost of all necessary servicing, repair, or correction. 215 | 216 | In no event unless required by applicable law or agreed to in writing 217 | will The Copyright Holder, or any author named in the components of the 218 | Work, or any other party who may distribute and/or modify the Work as 219 | permitted above, be liable to you for damages, including any general, 220 | special, incidental or consequential damages arising out of any use of 221 | the Work or out of inability to use the Work (including, but not limited 222 | to, loss of data, data being rendered inaccurate, or losses sustained by 223 | anyone as a result of any failure of the Work to operate with any other 224 | programs), even if the Copyright Holder or said author or said other 225 | party has been advised of the possibility of such damages. 226 | 227 | 228 | MAINTENANCE OF THE WORK 229 | ======================= 230 | 231 | The Work has the status `author-maintained' if the Copyright Holder 232 | explicitly and prominently states near the primary copyright notice in 233 | the Work that the Work can only be maintained by the Copyright Holder 234 | or simply that it is `author-maintained'. 235 | 236 | The Work has the status `maintained' if there is a Current Maintainer 237 | who has indicated in the Work that they are willing to receive error 238 | reports for the Work (for example, by supplying a valid e-mail 239 | address). It is not required for the Current Maintainer to acknowledge 240 | or act upon these error reports. 241 | 242 | The Work changes from status `maintained' to `unmaintained' if there 243 | is no Current Maintainer, or the person stated to be Current 244 | Maintainer of the work cannot be reached through the indicated means 245 | of communication for a period of six months, and there are no other 246 | significant signs of active maintenance. 247 | 248 | You can become the Current Maintainer of the Work by agreement with 249 | any existing Current Maintainer to take over this role. 250 | 251 | If the Work is unmaintained, you can become the Current Maintainer of 252 | the Work through the following steps: 253 | 254 | 1. Make a reasonable attempt to trace the Current Maintainer (and 255 | the Copyright Holder, if the two differ) through the means of 256 | an Internet or similar search. 257 | 258 | 2. If this search is successful, then enquire whether the Work 259 | is still maintained. 260 | 261 | a. If it is being maintained, then ask the Current Maintainer 262 | to update their communication data within one month. 263 | 264 | b. If the search is unsuccessful or no action to resume active 265 | maintenance is taken by the Current Maintainer, then announce 266 | within the pertinent community your intention to take over 267 | maintenance. (If the Work is a LaTeX work, this could be 268 | done, for example, by posting to comp.text.tex.) 269 | 270 | 3a. If the Current Maintainer is reachable and agrees to pass 271 | maintenance of the Work to you, then this takes effect 272 | immediately upon announcement. 273 | 274 | b. If the Current Maintainer is not reachable and the Copyright 275 | Holder agrees that maintenance of the Work be passed to you, 276 | then this takes effect immediately upon announcement. 277 | 278 | 4. If you make an `intention announcement' as described in 2b. above 279 | and after three months your intention is challenged neither by 280 | the Current Maintainer nor by the Copyright Holder nor by other 281 | people, then you may arrange for the Work to be changed so as 282 | to name you as the (new) Current Maintainer. 283 | 284 | 5. If the previously unreachable Current Maintainer becomes 285 | reachable once more within three months of a change completed 286 | under the terms of 3b) or 4), then that Current Maintainer must 287 | become or remain the Current Maintainer upon request provided 288 | they then update their communication data within one month. 289 | 290 | A change in the Current Maintainer does not, of itself, alter the fact 291 | that the Work is distributed under the LPPL license. 292 | 293 | If you become the Current Maintainer of the Work, you should 294 | immediately provide, within the Work, a prominent and unambiguous 295 | statement of your status as Current Maintainer. You should also 296 | announce your new status to the same pertinent community as 297 | in 2b) above. 298 | 299 | 300 | WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE 301 | ====================================================== 302 | 303 | This section contains important instructions, examples, and 304 | recommendations for authors who are considering distributing their 305 | works under this license. These authors are addressed as `you' in 306 | this section. 307 | 308 | Choosing This License or Another License 309 | ---------------------------------------- 310 | 311 | If for any part of your work you want or need to use *distribution* 312 | conditions that differ significantly from those in this license, then 313 | do not refer to this license anywhere in your work but, instead, 314 | distribute your work under a different license. You may use the text 315 | of this license as a model for your own license, but your license 316 | should not refer to the LPPL or otherwise give the impression that 317 | your work is distributed under the LPPL. 318 | 319 | The document `modguide.tex' in the base LaTeX distribution explains 320 | the motivation behind the conditions of this license. It explains, 321 | for example, why distributing LaTeX under the GNU General Public 322 | License (GPL) was considered inappropriate. Even if your work is 323 | unrelated to LaTeX, the discussion in `modguide.tex' may still be 324 | relevant, and authors intending to distribute their works under any 325 | license are encouraged to read it. 326 | 327 | A Recommendation on Modification Without Distribution 328 | ----------------------------------------------------- 329 | 330 | It is wise never to modify a component of the Work, even for your own 331 | personal use, without also meeting the above conditions for 332 | distributing the modified component. While you might intend that such 333 | modifications will never be distributed, often this will happen by 334 | accident -- you may forget that you have modified that component; or 335 | it may not occur to you when allowing others to access the modified 336 | version that you are thus distributing it and violating the conditions 337 | of this license in ways that could have legal implications and, worse, 338 | cause problems for the community. It is therefore usually in your 339 | best interest to keep your copy of the Work identical with the public 340 | one. Many works provide ways to control the behavior of that work 341 | without altering any of its licensed components. 342 | 343 | How to Use This License 344 | ----------------------- 345 | 346 | To use this license, place in each of the components of your work both 347 | an explicit copyright notice including your name and the year the work 348 | was authored and/or last substantially modified. Include also a 349 | statement that the distribution and/or modification of that 350 | component is constrained by the conditions in this license. 351 | 352 | Here is an example of such a notice and statement: 353 | 354 | %% pig.dtx 355 | %% Copyright 2005 M. Y. Name 356 | % 357 | % This work may be distributed and/or modified under the 358 | % conditions of the LaTeX Project Public License, either version 1.3 359 | % of this license or (at your option) any later version. 360 | % The latest version of this license is in 361 | % http://www.latex-project.org/lppl.txt 362 | % and version 1.3 or later is part of all distributions of LaTeX 363 | % version 2005/12/01 or later. 364 | % 365 | % This work has the LPPL maintenance status `maintained'. 366 | % 367 | % The Current Maintainer of this work is M. Y. Name. 368 | % 369 | % This work consists of the files pig.dtx and pig.ins 370 | % and the derived file pig.sty. 371 | 372 | Given such a notice and statement in a file, the conditions 373 | given in this license document would apply, with the `Work' referring 374 | to the three files `pig.dtx', `pig.ins', and `pig.sty' (the last being 375 | generated from `pig.dtx' using `pig.ins'), the `Base Interpreter' 376 | referring to any `LaTeX-Format', and both `Copyright Holder' and 377 | `Current Maintainer' referring to the person `M. Y. Name'. 378 | 379 | If you do not want the Maintenance section of LPPL to apply to your 380 | Work, change `maintained' above into `author-maintained'. 381 | However, we recommend that you use `maintained', as the Maintenance 382 | section was added in order to ensure that your Work remains useful to 383 | the community even when you can no longer maintain and support it 384 | yourself. 385 | 386 | Derived Works That Are Not Replacements 387 | --------------------------------------- 388 | 389 | Several clauses of the LPPL specify means to provide reliability and 390 | stability for the user community. They therefore concern themselves 391 | with the case that a Derived Work is intended to be used as a 392 | (compatible or incompatible) replacement of the original Work. If 393 | this is not the case (e.g., if a few lines of code are reused for a 394 | completely different task), then clauses 6b and 6d shall not apply. 395 | 396 | 397 | Important Recommendations 398 | ------------------------- 399 | 400 | Defining What Constitutes the Work 401 | 402 | The LPPL requires that distributions of the Work contain all the 403 | files of the Work. It is therefore important that you provide a 404 | way for the licensee to determine which files constitute the Work. 405 | This could, for example, be achieved by explicitly listing all the 406 | files of the Work near the copyright notice of each file or by 407 | using a line such as: 408 | 409 | % This work consists of all files listed in manifest.txt. 410 | 411 | in that place. In the absence of an unequivocal list it might be 412 | impossible for the licensee to determine what is considered by you 413 | to comprise the Work and, in such a case, the licensee would be 414 | entitled to make reasonable conjectures as to which files comprise 415 | the Work. 416 | 417 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(file < VERSION) 2 | 3 | SPELLBOOK_TEX := spellbook.tex 4 | SPELLBOOK_PDF := spellbook.pdf 5 | 6 | EXAMPLE_TEX := example.tex 7 | EXAMPLE_PDF := example.pdf 8 | 9 | WIZPEN_MF := texmf/fonts/source/wizpen/wizpen.mf 10 | 11 | WIZPEN_PFB := fonts/wizpen.pfb 12 | WIZPEN_TTF := fonts/wizpen.ttf 13 | WIZPEN_OTF := fonts/wizpen.otf 14 | 15 | all: $(EXAMPLE_PDF) 16 | 17 | spellbook: $(SPELLBOOK_PDF) 18 | 19 | clean: 20 | rm -f $(WIZPEN_MF) 21 | find texmf -type d -perm /1000 -exec rm -rf {} \; || true 22 | rm -f *.aux *.log 23 | rm -f *.pdf 24 | 25 | redo: 26 | $(MAKE) clean 27 | $(MAKE) all 28 | 29 | spellbook-redo: 30 | $(MAKE) clean 31 | $(MAKE) spellbook 32 | 33 | font: $(WIZPEN_TTF) $(WIZPEN_OTF) 34 | 35 | font-clean: 36 | rm -f fonts/* 37 | 38 | .PHONY: all spellbook clean redo spellbook-redo font font-clean 39 | 40 | $(WIZPEN_MF): 41 | mkdir -p $(dir $(WIZPEN_MF)) 42 | ruby script/wizpen.rb $(VERSION) > $(WIZPEN_MF) 43 | 44 | $(WIZPEN_PFB): $(WIZPEN_MF) 45 | ln -s ../$^ fonts/$(notdir $^) 46 | cd fonts && mf2pt1 $(notdir $^) 47 | 48 | $(WIZPEN_TTF): $(WIZPEN_PFB) 49 | ./script/tfm2ttf.pe $^ 50 | 51 | $(WIZPEN_OTF): $(WIZPEN_PFB) 52 | ./script/tfm2otf.pe $^ 53 | 54 | $(EXAMPLE_PDF): $(EXAMPLE_TEX) $(WIZPEN_MF) 55 | TEXMFHOME=texmf lualatex $(EXAMPLE_TEX) 56 | 57 | $(SPELLBOOK_PDF): $(SPELLBOOK_TEX) $(WIZPEN_MF) 58 | TEXMFHOME=texmf lualatex $(SPELLBOOK_TEX) 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🧙 wizpen 2 | 3 | ![The Time of the Dark, (C) 1982 Barbara Hambly. This cover art was great, 4 | let me know if I need to replace it.](img/logo.png) 5 | 6 | A [Pigpen cipher](https://en.wikipedia.org/wiki/Pigpen_cipher)-inspired font, 7 | originally created for a D\&D wizard spellbook. Unlike Pigpen, characters in 8 | this font can share vertical and horizontal strokes while still being readable. 9 | 10 | >Copying a spell into your spellbook involves reproducing the basic form of 11 | >the spell, then deciphering the unique system of notation used by the wizard 12 | >who wrote it (5E PHB, p. 114) 13 | 14 | Based on the LaTeX [pigpen font](https://ctan.org/pkg/pigpen), originally 15 | (C) 2008 Oliver Corff. This is version 1.2.0. Unlike the original, it is 16 | generated entirely with a Ruby script. 17 | 18 | You can use a package like `microtype` to add spacing between characters. 19 | Both "expanded" and "compact" examples are shown below. Regardless of which 20 | you use, one of my design goals was to allow the font to easily be read once 21 | you know the basic scheme. 22 | 23 | ![key](img/key.png) 24 | 25 | Note that uppercase `A-R` are rendered with two dots to mark the center of each 26 | letter, and lowercase `a-r` are rendered with one dot. `S-Z` and `s-z` are 27 | rendered with no dots, because they would get in the way of the diagonals. 28 | 29 | If you don't like the dots, just comment out the `dotted` and `double_dotted` 30 | lines in the generator script (see "Modifying the font" section below). 31 | 32 | Also, `1-9` is just the negative space on `A-J`, and `0` is the negative space 33 | on `Z`. 34 | 35 | ## Using the provided TTF or OTF 36 | 37 | Just [download it](https://github.com/numinit/wizpen/tree/master/fonts) 38 | and install it. It's that easy. 39 | 40 | ## Shoutouts 41 | 42 | * DMs and brewers at [Battlemage Brewery](http://battlemagebrewing.com/) 43 | in San Diego, California 44 | * dc858 folks (especially ice) 45 | * Distractions, Inc for peer review and feedback 46 | 47 | ## Build prereqs 48 | 49 | For just building LaTeX documents: 50 | 51 | * metafont (for rendering the font in TeX) 52 | * lualatex (for syntax highlighting important terms in D\&D spells) 53 | * ruby (for building the metafont definition) 54 | 55 | For building the font in TTF and OTF formats: 56 | 57 | * mf2pt1 58 | * fontforge 59 | 60 | ## Building 61 | 62 | LaTeX files: 63 | 64 | * `make`: Build everything 65 | * `make clean`: Clean everything 66 | * `make redo`: Run `make clean` and `make all` 67 | * `make spellbook` or `make spellbook-redo`: Make the provided example 68 | spellbook. 69 | 70 | TTF and OTF files: 71 | 72 | * `make font`: Build the font into TTF and OTF files. This will take a while. 73 | 74 | ## Using the LaTeX metafont 75 | 76 | Create a `texmf` folder like the one here (or just copy it) and 77 | `export TEXMFHOME=mytexmf` before running `pdflatex`. 78 | 79 | An example document will look like this: 80 | 81 | ```tex 82 | \documentclass{article} 83 | 84 | \usepackage[top=0.125in,bottom=0.125in,left=0.125in,right=0.125in]{geometry} 85 | \usepackage[letterspace=150]{microtype} 86 | \usepackage{setspace} 87 | 88 | \usepackage{wizpen} 89 | 90 | \begin{document} 91 | 92 | { 93 | % Uncomment the \lsstyle to add a little spacing between symbols 94 | \Large \wizpenfont \noindent %\lsstyle 95 | ABCDEFGHI \enspace JKLMNOPQR \enspace STUV \enspace WXYZ \\ \\ 96 | } 97 | 98 | \end{document} 99 | ``` 100 | 101 | ## Example 102 | 103 | Check `spellbook.tex` for spoilers on what all these actually say. 104 | 105 | ![example](img/example.png) 106 | 107 | ## Modifying the font 108 | 109 | `script/wizpen.rb` is how the font gets generated (check the makefile). 110 | If you want to modify it, edit that file, and the font will be regenerated 111 | at the next `make`. 112 | 113 | That code is pretty messy, but produces the whole font from an algorithm. 114 | 115 | ## License 116 | 117 | Licensed under the [LaTeX Project Public License](https://ctan.org/license/lppl): 118 | See [LICENSE](LICENSE) 119 | 120 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | -------------------------------------------------------------------------------- /example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numinit/wizpen/0df0ca89723a5666be0f1ce7f7e370bcf3bdc078/example.pdf -------------------------------------------------------------------------------- /example.tex: -------------------------------------------------------------------------------- 1 | %TeX 2 | 3 | \documentclass[letterpaper]{article} 4 | 5 | \usepackage[top=0.125in,bottom=0.125in,left=0.125in,right=0.125in]{geometry} 6 | \usepackage[letterspace=150]{microtype} 7 | \usepackage{setspace} 8 | \usepackage{blindtext} 9 | 10 | \usepackage{wizpen} 11 | 12 | \begin{document} 13 | 14 | \section*{Hello from {\wizpenfont wizpen}\enspace!} 15 | 16 | This is wizpen, a Pigpen cipher-like font originally made for 17 | a D\&D campaign at Battlemage Brewery in San Diego, California. 18 | 19 | \noindent It is free to use for whatever you want. If you redistribute it, 20 | check the License. 21 | 22 | \section*{Expanded} 23 | 24 | { 25 | \Large \wizpenfont \noindent \lsstyle 26 | ABCDEFGHI JKLMNOPQR STUV WXYZ \\ \\ 27 | } 28 | { 29 | \Large \noindent 30 | \textls[550] {ABCDEFGHI JKLMNOPQR STUV WXYZ} \\ \\ 31 | } 32 | 33 | { 34 | \Large \wizpenfont \noindent \lsstyle 35 | 0123456789 \\ \\ 36 | } 37 | { 38 | \Large \noindent 39 | \textls[700] {0123456789} \\ \\ 40 | } 41 | 42 | \section*{Compact} 43 | 44 | { 45 | \Large \wizpenfont \noindent 46 | ABCDEFGHI JKLMNOPQR STUV WXYZ \\ \\ 47 | } 48 | { 49 | \Large \noindent 50 | \textls[380] {ABCDEFGHI JKLMNOPQR STUV WXYZ} \\ \\ 51 | } 52 | 53 | { 54 | \Large \wizpenfont \noindent 55 | 0123456789 \\ \\ 56 | } 57 | { 58 | \Large \noindent 59 | \textls[550] {0123456789} \\ \\ 60 | } 61 | 62 | \clearpage 63 | 64 | \section*{Examples} 65 | 66 | { 67 | \Large \wizpenfont \noindent %\lsstyle 68 | Burning Hands \\ \\ 69 | Detect Magic \\ \\ 70 | Illusory Script \\ \\ 71 | Mage Armor \\ \\ 72 | Magic Missile \\ \\ 73 | Magic Weapon \\ \\ 74 | Mirror Image \\ \\ 75 | Shatter \\ \\ 76 | Shield \\ \\ 77 | Suggestion \\ 78 | } 79 | 80 | \clearpage 81 | 82 | \section*{Prose} 83 | 84 | % TODO: figure out how characters can share horizontal strokes. Because 85 | % they should be able to. 86 | { 87 | \Large \wizpenfont \noindent % 88 | \begin{sloppypar} 89 | \blindtext 90 | \end{sloppypar} 91 | } 92 | 93 | \end{document} 94 | -------------------------------------------------------------------------------- /fonts/wizpen.mf: -------------------------------------------------------------------------------- 1 | ../texmf/fonts/source/wizpen/wizpen.mf -------------------------------------------------------------------------------- /fonts/wizpen.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numinit/wizpen/0df0ca89723a5666be0f1ce7f7e370bcf3bdc078/fonts/wizpen.otf -------------------------------------------------------------------------------- /fonts/wizpen.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numinit/wizpen/0df0ca89723a5666be0f1ce7f7e370bcf3bdc078/fonts/wizpen.ttf -------------------------------------------------------------------------------- /img/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numinit/wizpen/0df0ca89723a5666be0f1ce7f7e370bcf3bdc078/img/example.png -------------------------------------------------------------------------------- /img/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numinit/wizpen/0df0ca89723a5666be0f1ce7f7e370bcf3bdc078/img/key.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numinit/wizpen/0df0ca89723a5666be0f1ce7f7e370bcf3bdc078/img/logo.png -------------------------------------------------------------------------------- /img/logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numinit/wizpen/0df0ca89723a5666be0f1ce7f7e370bcf3bdc078/img/logo.psd -------------------------------------------------------------------------------- /lua/spellbook.lua: -------------------------------------------------------------------------------- 1 | local spellbook = {} 2 | 3 | spellbook.preamble = function() 4 | local ret = {} 5 | if os.getenv('DEBUG') then 6 | table.insert(ret, '\\renewcommand{\\wizpenfont}{\\normalfont}') 7 | end 8 | return ret 9 | end 10 | 11 | spellbook.match = function(str, pattern, iftrue, iffalse) 12 | if string.match(str, pattern) then 13 | return iftrue 14 | else 15 | return iffalse 16 | end 17 | end 18 | 19 | local trim = function(str) 20 | return str:gsub('^%s*(.-)%s*$', '%1') 21 | end 22 | 23 | spellbook.dice = function(str) 24 | local ret = str:gsub('(%d+)d(%d+)(%s*[%+%-]?%s*%d*)', function(x, y, plus) 25 | x = tonumber(x) or 0 26 | y = tonumber(y) or 0 27 | plus = tonumber(plus) or 0 28 | 29 | local dice = string.format('%dd%d%+d', x, y, plus) 30 | if x <= 0 or y <= 0 then 31 | error('invalid dice: ' .. dice) 32 | else 33 | print('dice: ' .. dice) 34 | end 35 | 36 | if plus ~= 0 then 37 | local sign 38 | if plus < 0 then 39 | sign = '-' 40 | else 41 | sign = '+' 42 | end 43 | return string.format('\\SpellbookDiceFourArg{%d}{%d}{%s}{%d}', x, y, sign, math.abs(plus)) 44 | else 45 | return string.format('\\SpellbookDiceTwoArg{%d}{%d}', x, y) 46 | end 47 | end) 48 | 49 | print(str .. ' => ' .. ret) 50 | return ret 51 | end 52 | 53 | spellbook.distance = function(str) 54 | return trim(str):gsub('(%d+)[%s%-]+foot', function(x) 55 | x = tonumber(x) or 0 56 | 57 | local distance = string.format('%d foot', x) 58 | if x <= 0 then 59 | error('invalid distance: ' .. distance) 60 | else 61 | print('distance: ' .. distance) 62 | end 63 | 64 | return string.format('\\SpellbookDistanceOneArg{%d}', x) 65 | end) 66 | end 67 | 68 | spellbook.range = function(str) 69 | return trim(str):gsub('(%d+)/(%d+)', function(x, y) 70 | x = tonumber(x) or 0 71 | y = tonumber(y) or 0 72 | 73 | local range = string.format('%d/%d', x, y) 74 | if x <= 0 or y <= 0 then 75 | error('invalid range: ' .. range) 76 | else 77 | print('range: ' .. range) 78 | end 79 | 80 | return string.format('\\SpellbookRangeTwoArg{%d}{%d}', x, y) 81 | end) 82 | end 83 | 84 | spellbook.cleanup = (function() 85 | local fixup_ws = function(str) 86 | return str:gsub('%s+', ' ') 87 | end 88 | 89 | local cleanup = function(str) 90 | --return str:gsub("[^A-Za-z0-9%s.,'\\/]", '') 91 | return str:gsub("[^A-Za-z0-9%s%-%+/\\]", '') 92 | end 93 | 94 | local add_breaks = function(str) 95 | return str:gsub('\\par', '\\\\ \\\\ \\par') 96 | end 97 | 98 | local transforms = { 99 | trim, 100 | fixup_ws, 101 | cleanup, 102 | spellbook.dice, 103 | spellbook.distance, 104 | spellbook.range, 105 | add_breaks 106 | } 107 | 108 | return function(str) 109 | for i, fn in ipairs(transforms) do 110 | str = fn(str) 111 | end 112 | print(str) 113 | return str 114 | end 115 | end)() 116 | 117 | return spellbook 118 | -------------------------------------------------------------------------------- /script/tfm2otf.pe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fontforge 2 | 3 | Open($1) 4 | Generate($1:r + ".otf") 5 | -------------------------------------------------------------------------------- /script/tfm2ttf.pe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fontforge 2 | 3 | Open($1) 4 | Generate($1:r + ".ttf") 5 | -------------------------------------------------------------------------------- /script/wizpen.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Wizpen, a Pigpen-inspired metafont for wizard spellbooks in D&D 4 | # 5 | # This is a generator for a pigpen-style LaTeX metafont suitable for wizard 6 | # spellbooks in D&D-style games. 7 | # 8 | # Licensed under the LaTeX Project Public License. 9 | # 10 | # This source is awful. Sorry. 11 | 12 | GRID_A = { 13 | A: 1..2, 14 | B: 1..3, 15 | C: 2..3, 16 | D: 0..2, 17 | E: 0..3, 18 | F: 2..0, 19 | G: 0..1, 20 | H: 3..1, 21 | I: 3..0, 22 | 23 | J: 1...2, 24 | K: 1...3, 25 | L: 2...3, 26 | M: 0...2, 27 | N: 0...3, 28 | O: 2...0, 29 | P: 0...1, 30 | Q: 3...1, 31 | R: 3...0, 32 | } 33 | 34 | GRID_B = { 35 | S: 1..3, 36 | T: 0..2, 37 | U: 2..0, 38 | V: 3..1, 39 | 40 | W: 1...3, 41 | X: 0...2, 42 | Y: 2...0, 43 | Z: 3...1 44 | } 45 | 46 | GRID_C = { 47 | :'0' => 3...1, 48 | :'1' => 1..2, 49 | :'2' => 1..3, 50 | :'3' => 2..3, 51 | :'4' => 0..2, 52 | :'5' => 0..3, 53 | :'6' => 2..0, 54 | :'7' => 0..1, 55 | :'8' => 3..1, 56 | :'9' => 3..0, 57 | } 58 | 59 | CORNERS = { 60 | 0 => %i[west north], 61 | 1 => %i[east north], 62 | 2 => %i[east south], 63 | 3 => %i[west south] 64 | } 65 | 66 | TRIANGLES_A = { 67 | 0 => [%i[west vm], %i[west north], %i[hm north], %i[west vm]], 68 | 1 => [%i[hm north], %i[east vm], %i[east north], %i[hm north]], 69 | 2 => [%i[east vm], %i[hm south], %i[east south], %i[east vm]], 70 | 3 => [%i[hm south], %i[west vm], %i[west south], %i[hm south]] 71 | } 72 | 73 | TRIANGLES_B_MAP = { 74 | 0 => [%i[west north], %i[west south], %i[east south], %i[west north]], 75 | 1 => [%i[west north], %i[east north], %i[east south], %i[west north]] 76 | } 77 | 78 | TRIANGLES_B = { 79 | 1..3 => 0, 80 | 0..2 => 1, 81 | 2..0 => 0, 82 | 3..1 => 1 83 | } 84 | 85 | TEX_CORNERS = { 86 | north: :top, 87 | south: :bot, 88 | west: :lft, 89 | east: :rt 90 | } 91 | 92 | def range_to_corners range, grid 93 | vertices = [] 94 | triangles = [] 95 | diagonals = [] 96 | negative = [] 97 | 98 | idx = range.begin % 4 99 | start = idx 100 | finish = (range.end + 1) % 4 101 | 102 | close = false 103 | if finish == idx 104 | # We need to close the shape 105 | finish = (finish - 1) % 4 106 | close = true 107 | end 108 | 109 | while idx != finish 110 | vertex = CORNERS[idx] 111 | vertices << vertex 112 | 113 | negative << vertex if idx == start && !close 114 | 115 | idx = (idx + 1) % 4 116 | 117 | negative << CORNERS[idx] if idx == finish && !close 118 | 119 | if close || idx != finish 120 | triangle = TRIANGLES_A[idx] 121 | triangles << triangle 122 | triangle.each do |point| 123 | if (point.include?(:vm) or point.include?(:hm)) and !negative.include?(point) 124 | negative << point 125 | end 126 | end 127 | end 128 | end 129 | 130 | vertices << CORNERS[idx] 131 | 132 | if close 133 | idx = (idx + 1) % 4 134 | vertices << CORNERS[idx] 135 | triangle = TRIANGLES_A[idx] 136 | triangles << triangle 137 | triangle.each do |point| 138 | if point.include?(:vm) or point.include?(:hm) and point == negative.first 139 | negative << point 140 | end 141 | end 142 | end 143 | 144 | diagonals << TRIANGLES_B_MAP[TRIANGLES_B[range.begin..range.end]] 145 | 146 | CORNERS.each do |key, point| 147 | if !negative.include? point and !triangles.any? {|t| t.include? point} 148 | negative << point 149 | end 150 | end 151 | 152 | if negative.first != negative.last 153 | negative << negative.first 154 | end 155 | 156 | { 157 | vertices: vertices, 158 | negative: negative, 159 | triangles: triangles, 160 | diagonals: diagonals 161 | } 162 | end 163 | 164 | def get_letter_spec letter 165 | letter = letter.upcase.to_sym 166 | range = GRID_A[letter] 167 | grid = :A 168 | if range.nil? 169 | range = GRID_B[letter] 170 | grid = :B 171 | end 172 | 173 | if range.nil? 174 | range = GRID_C[letter] 175 | grid = :C 176 | end 177 | 178 | return nil if range.nil? 179 | 180 | { 181 | letter: letter, 182 | range: range, 183 | grid: grid, 184 | fill: range.exclude_end?, 185 | corners: range_to_corners(range, grid), 186 | } 187 | end 188 | 189 | def draw_corners corners 190 | variables = {} 191 | order = [] 192 | bbox = {} 193 | cycle = false 194 | 195 | corners.each_with_index do |corner, idx| 196 | if idx > 0 && corner == corners[0] 197 | # Loop back 198 | cycle = true 199 | else 200 | latex = corner.map {|c| TEX_CORNERS[c]} 201 | var = 'z%d' % variables.length 202 | variables[var] = corner 203 | order << var 204 | if latex.compact.length == latex.length 205 | bbox[latex] = var 206 | end 207 | end 208 | end 209 | 210 | {variables: variables, order: order, bbox: bbox, cycle: cycle} 211 | end 212 | 213 | version = ARGV[0] || '0.0.0' 214 | 215 | puts <