├── .gitignore ├── BUILDING.md ├── LICENSE ├── Makefile ├── README.md ├── images ├── brutalist.jpg └── brutalist_vs_dejavu.gif ├── scripts ├── colorize.pl ├── generate.pe ├── generate.py ├── italicize.pe ├── langcover.pl ├── lgc.pe ├── merge.pl ├── mescover.pl ├── narrow.pe ├── narrowmerge.pe ├── narrowmerge.sh ├── problems.pl ├── samples.sh ├── sfdnormalize.pl ├── stats.sh ├── status.pl ├── strip_glyphs.pe ├── ttpostproc.pl ├── unhinted.pl ├── unicover.pl └── wikidownloadtemplate.sh └── src ├── BrutalistMono-Bold.sfd ├── BrutalistMono-BoldOblique.sfd ├── BrutalistMono-Oblique.sfd └── BrutalistMono.sfd /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | tmp/ 4 | /src/*.sfd~ 5 | *.DS_Store -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | Building 2 | -------- 3 | 4 | ### Prerequisites 5 | 6 | To build these fonts, you will need: 7 | 8 | * [FontForge][1], installable on Debian through the `fontforge` package: 9 | 10 | ~~~shell 11 | sudo apt-get install software-properties-common 12 | sudo add-apt-repository ppa:fontforge/fontforge 13 | sudo apt-get update 14 | sudo apt-get install fontforge 15 | ~~~ 16 | 17 | macOS users can install using [Homebrew][2]: 18 | 19 | ~~~shell 20 | brew install fontforge 21 | ~~~ 22 | 23 | Fedora users should run the following command as root: 24 | 25 | ~~~shell 26 | yum install fontforge 27 | ~~~ 28 | 29 | See FontForge's [installation docs][3] for more info. 30 | 31 | 32 | * [Perl][4], with the [`Font::TTF`][5] and [`IO::String`][6] modules 33 | installed: 34 | 35 | ~~~shell 36 | cpan Font::TTF IO::String 37 | ~~~ 38 | 39 | Debian users can install this with the `libfont-ttf-perl` package. 40 | 41 | macOS users may need to install as root if they encounter 42 | permission problems. 43 | 44 | 45 | * GNU-compatible [Make][7], installable through Debian package `make`. 46 | 47 | macOS users who have installed XCode's CLI utils should already 48 | have `make` available. 49 | 50 | 51 | ### Generating TrueType files 52 | 53 | To generate all fonts: 54 | 55 | ~~~console 56 | $ make 57 | ~~~ 58 | 59 | [1]: https://fontforge.github.io/en-US/ 60 | [2]: https://brew.sh/ 61 | [3]: http://designwithfontforge.com/en-US/Installing_Fontforge.html 62 | [4]: https://www.perl.org/ 63 | [5]: https://metacpan.org/release/Font-TTF/ 64 | [6]: https://metacpan.org/pod/IO::String 65 | [7]: http://www.gnu.org/software/make/manual/make.html 66 | [8]: https://wiki.freedesktop.org/www/Software/fontconfig/ 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Fonts are (c) Bitstream (see below). Brutalist changes are in public domain. 2 | Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) 3 | 4 | 5 | Bitstream Vera Fonts Copyright 6 | ------------------------------ 7 | 8 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is 9 | a trademark of Bitstream, Inc. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of the fonts accompanying this license ("Fonts") and associated 13 | documentation files (the "Font Software"), to reproduce and distribute the 14 | Font Software, including without limitation the rights to use, copy, merge, 15 | publish, distribute, and/or sell copies of the Font Software, and to permit 16 | persons to whom the Font Software is furnished to do so, subject to the 17 | following conditions: 18 | 19 | The above copyright and trademark notices and this permission notice shall 20 | be included in all copies of one or more of the Font Software typefaces. 21 | 22 | The Font Software may be modified, altered, or added to, and in particular 23 | the designs of glyphs or characters in the Fonts may be modified and 24 | additional glyphs or characters may be added to the Fonts, only if the fonts 25 | are renamed to names not containing either the words "Bitstream" or the word 26 | "Vera". 27 | 28 | This License becomes null and void to the extent applicable to Fonts or Font 29 | Software that has been modified and is distributed under the "Bitstream 30 | Vera" names. 31 | 32 | The Font Software may be sold as part of a larger software package but no 33 | copy of one or more of the Font Software typefaces may be sold by itself. 34 | 35 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 36 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, 37 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, 38 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME 39 | FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING 40 | ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, 41 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 42 | THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE 43 | FONT SOFTWARE. 44 | 45 | Except as contained in this notice, the names of Gnome, the Gnome 46 | Foundation, and Bitstream Inc., shall not be used in advertising or 47 | otherwise to promote the sale, use or other dealings in this Font Software 48 | without prior written authorization from the Gnome Foundation or Bitstream 49 | Inc., respectively. For further information, contact: fonts at gnome dot 50 | org. 51 | 52 | Arev Fonts Copyright 53 | ------------------------------ 54 | 55 | Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining 58 | a copy of the fonts accompanying this license ("Fonts") and 59 | associated documentation files (the "Font Software"), to reproduce 60 | and distribute the modifications to the Bitstream Vera Font Software, 61 | including without limitation the rights to use, copy, merge, publish, 62 | distribute, and/or sell copies of the Font Software, and to permit 63 | persons to whom the Font Software is furnished to do so, subject to 64 | the following conditions: 65 | 66 | The above copyright and trademark notices and this permission notice 67 | shall be included in all copies of one or more of the Font Software 68 | typefaces. 69 | 70 | The Font Software may be modified, altered, or added to, and in 71 | particular the designs of glyphs or characters in the Fonts may be 72 | modified and additional glyphs or characters may be added to the 73 | Fonts, only if the fonts are renamed to names not containing either 74 | the words "Tavmjong Bah" or the word "Arev". 75 | 76 | This License becomes null and void to the extent applicable to Fonts 77 | or Font Software that has been modified and is distributed under the 78 | "Tavmjong Bah Arev" names. 79 | 80 | The Font Software may be sold as part of a larger software package but 81 | no copy of one or more of the Font Software typefaces may be sold by 82 | itself. 83 | 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL 88 | TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | 94 | Except as contained in this notice, the name of Tavmjong Bah shall not 95 | be used in advertising or otherwise to promote the sale, use or other 96 | dealings in this Font Software without prior written authorization 97 | from Tavmjong Bah. For further information, contact: tavmjong @ free 98 | . fr. 99 | 100 | TeX Gyre DJV Math 101 | ----------------- 102 | Fonts are (c) Bitstream (see below). Brutalist changes are in public domain. 103 | 104 | Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski 105 | (on behalf of TeX users groups) are in public domain. 106 | 107 | Letters imported from Euler Fraktur from AMSfonts are (c) American 108 | Mathematical Society (see below). 109 | Bitstream Vera Fonts Copyright 110 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera 111 | is a trademark of Bitstream, Inc. 112 | 113 | Permission is hereby granted, free of charge, to any person obtaining a copy 114 | of the fonts accompanying this license (“Fonts”) and associated 115 | documentation 116 | files (the “Font Software”), to reproduce and distribute the Font Software, 117 | including without limitation the rights to use, copy, merge, publish, 118 | distribute, 119 | and/or sell copies of the Font Software, and to permit persons to whom 120 | the Font Software is furnished to do so, subject to the following 121 | conditions: 122 | 123 | The above copyright and trademark notices and this permission notice 124 | shall be 125 | included in all copies of one or more of the Font Software typefaces. 126 | 127 | The Font Software may be modified, altered, or added to, and in particular 128 | the designs of glyphs or characters in the Fonts may be modified and 129 | additional 130 | glyphs or characters may be added to the Fonts, only if the fonts are 131 | renamed 132 | to names not containing either the words “Bitstream” or the word “Vera”. 133 | 134 | This License becomes null and void to the extent applicable to Fonts or 135 | Font Software 136 | that has been modified and is distributed under the “Bitstream Vera” 137 | names. 138 | 139 | The Font Software may be sold as part of a larger software package but 140 | no copy 141 | of one or more of the Font Software typefaces may be sold by itself. 142 | 143 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 144 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, 145 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, 146 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME 147 | FOUNDATION 148 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, 149 | SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN 150 | ACTION 151 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR 152 | INABILITY TO USE 153 | THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. 154 | Except as contained in this notice, the names of GNOME, the GNOME 155 | Foundation, 156 | and Bitstream Inc., shall not be used in advertising or otherwise to promote 157 | the sale, use or other dealings in this Font Software without prior written 158 | authorization from the GNOME Foundation or Bitstream Inc., respectively. 159 | For further information, contact: fonts at gnome dot org. 160 | 161 | AMSFonts (v. 2.2) copyright 162 | 163 | The PostScript Type 1 implementation of the AMSFonts produced by and 164 | previously distributed by Blue Sky Research and Y&Y, Inc. are now freely 165 | available for general use. This has been accomplished through the 166 | cooperation 167 | of a consortium of scientific publishers with Blue Sky Research and Y&Y. 168 | Members of this consortium include: 169 | 170 | Elsevier Science IBM Corporation Society for Industrial and Applied 171 | Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) 172 | 173 | In order to assure the authenticity of these fonts, copyright will be 174 | held by 175 | the American Mathematical Society. This is not meant to restrict in any way 176 | the legitimate use of the fonts, such as (but not limited to) electronic 177 | distribution of documents containing these fonts, inclusion of these fonts 178 | into other public domain or commercial font collections or computer 179 | applications, use of the outline data to create derivative fonts and/or 180 | faces, etc. However, the AMS does require that the AMS copyright notice be 181 | removed from any derivative versions of the fonts which have been altered in 182 | any way. In addition, to ensure the fidelity of TeX documents using Computer 183 | Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, 184 | has requested that any alterations which yield different font metrics be 185 | given a different name. 186 | 187 | $Id$ 188 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all check munge full sans lgc ttf full-ttf sans-ttf lgc-ttf status dist src-dist full-dist sans-dist lgc-dist norm check-harder pre-patch clean 2 | 3 | # Release version 4 | VERSION = 1.0 5 | # Snapshot version 6 | SNAPSHOT = 7 | # Initial source directory, assumed read-only 8 | SRCDIR = src 9 | # Directory where temporary files live 10 | TMPDIR = tmp 11 | # Directory where final files are created 12 | BUILDDIR = build 13 | # Directory where final archives are created 14 | DISTDIR = dist 15 | 16 | # Release layout 17 | FONTCONFDIR = fontconfig 18 | DOCDIR = . 19 | SCRIPTSDIR = scripts 20 | TTFDIR = ttf 21 | RESOURCEDIR = resources 22 | 23 | ifeq "$(SNAPSHOT)" "" 24 | ARCHIVEVER = $(VERSION) 25 | else 26 | ARCHIVEVER = $(VERSION)-$(SNAPSHOT) 27 | endif 28 | 29 | SRCARCHIVE = brutalist-fonts-$(ARCHIVEVER) 30 | FULLARCHIVE = brutalist-fonts-ttf-$(ARCHIVEVER) 31 | SANSARCHIVE = brutalist-ttf-$(ARCHIVEVER) 32 | LGCARCHIVE = brutalist-lgc-fonts-ttf-$(ARCHIVEVER) 33 | 34 | ARCHIVEEXT = .zip .tar.bz2 35 | SUMEXT = .zip.md5 .tar.bz2.md5 .tar.bz2.sha512 36 | 37 | OLDSTATUS = $(DOCDIR)/status.txt 38 | BLOCKS = $(RESOURCEDIR)/Blocks.txt 39 | UNICODEDATA = $(RESOURCEDIR)/UnicodeData.txt 40 | FC-LANG = $(RESOURCEDIR)/fc-lang 41 | 42 | GENERATE = $(SCRIPTSDIR)/generate.pe 43 | TTPOSTPROC = $(SCRIPTSDIR)/ttpostproc.pl 44 | LGC = $(SCRIPTSDIR)/lgc.pe 45 | UNICOVER = $(SCRIPTSDIR)/unicover.pl 46 | LANGCOVER = $(SCRIPTSDIR)/langcover.pl 47 | STATUS = $(SCRIPTSDIR)/status.pl 48 | PROBLEMS = $(SCRIPTSDIR)/problems.pl 49 | NORMALIZE = $(SCRIPTSDIR)/sfdnormalize.pl 50 | NARROW = $(SCRIPTSDIR)/narrow.pe 51 | 52 | SRC := $(wildcard $(SRCDIR)/*.sfd) 53 | SFDFILES := $(patsubst $(SRCDIR)/%, %, $(SRC)) 54 | FULLSFD := $(patsubst $(SRCDIR)/%.sfd, $(TMPDIR)/%.sfd, $(SRC)) 55 | NORMSFD := $(patsubst %, %.norm, $(FULLSFD)) 56 | MATSHSFD := $(wildcard $(SRCDIR)/*Math*.sfd) 57 | LGCSRC := $(filter-out $(MATSHSFD),$(SRC)) 58 | LGCSFD := $(patsubst $(SRCDIR)/Brutalist%.sfd, $(TMPDIR)/BrutalistLGC%.sfd, $(LGCSRC)) 59 | FULLTTF := $(patsubst $(TMPDIR)/%.sfd, $(BUILDDIR)/%.ttf, $(FULLSFD)) 60 | LGCTTF := $(patsubst $(TMPDIR)/%.sfd, $(BUILDDIR)/%.ttf, $(LGCSFD)) 61 | 62 | FONTCONF := $(wildcard $(FONTCONFDIR)/*.conf) 63 | FONTCONFLGC := $(wildcard $(FONTCONFDIR)/*lgc*.conf) 64 | FONTCONFFULL := $(filter-out $(FONTCONFLGC), $(FONTCONF)) 65 | 66 | STATICDOC := $(addprefix $(DOCDIR)/, AUTHORS LICENSE NEWS README.md) 67 | STATICSRCDOC := $(addprefix $(DOCDIR)/, BUILDING.md) 68 | GENDOCFULL = unicover.txt langcover.txt status.txt 69 | GENDOCSANS = unicover-sans.txt langcover-sans.txt 70 | GENDOCLGC = unicover-lgc.txt langcover-lgc.txt 71 | 72 | all : full sans lgc 73 | 74 | $(TMPDIR)/%.sfd: $(SRCDIR)/%.sfd 75 | @echo "[1] $< => $@" 76 | install -d $(dir $@) 77 | sed "s@\(Version:\? \)\(0\.[0-9]\+\.[0-9]\+\|[1-9][0-9]*\.[0-9]\+\)@\1$(VERSION)@" $< > $@ 78 | touch -r $< $@ 79 | 80 | $(TMPDIR)/BrutalistLGCMathTeXGyre.sfd: $(TMPDIR)/BrutalistMathTeXGyre.sfd 81 | @echo "[2] skipping $<" 82 | 83 | $(TMPDIR)/BrutalistLGC%.sfd: $(TMPDIR)/Brutalist%.sfd 84 | @echo "[2] $< => $@" 85 | sed -e 's,FontName: Brutalist,FontName: BrutalistLGC,'\ 86 | -e 's,FullName: Brutalist,FullName: Brutalist LGC,'\ 87 | -e 's,FamilyName: Brutalist,FamilyName: Brutalist LGC,'\ 88 | -e 's,"Brutalist \(\(Sans\|Serif\)*\( Condensed\| Mono\)*\( Bold\)*\( Oblique\|Italic\)*\)","Brutalist LGC \1",g' < $< > $@ 89 | @echo "Stripping unwanted glyphs from $@" 90 | $(LGC) $@ 91 | touch -r $< $@ 92 | 93 | $(BUILDDIR)/BrutalistLGCMathTeXGyre.ttf: $(TMPDIR)/BrutalistLGCMathTeXGyre.sfd 94 | @echo "[3] skipping $<" 95 | 96 | $(BUILDDIR)/%.ttf: $(TMPDIR)/%.sfd 97 | @echo "[3] $< => $@" 98 | install -d $(dir $@) 99 | $(GENERATE) $< 100 | mv $<.ttf $@ 101 | $(TTPOSTPROC) $@ 102 | $(RM) $@~ 103 | touch -r $< $@ 104 | 105 | $(BUILDDIR)/status.txt: $(FULLSFD) 106 | @echo "[4] => $@" 107 | install -d $(dir $@) 108 | $(STATUS) $(VERSION) $(OLDSTATUS) $(FULLSFD) > $@ 109 | 110 | $(BUILDDIR)/unicover.txt: $(patsubst %, $(TMPDIR)/%.sfd, Brutalist BrutalistSerif BrutalistMono) 111 | @echo "[5] => $@" 112 | install -d $(dir $@) 113 | $(UNICOVER) $(UNICODEDATA) $(BLOCKS) \ 114 | $(TMPDIR)/Brutalist.sfd "Sans" \ 115 | $(TMPDIR)/BrutalistSerif.sfd "Serif" \ 116 | $(TMPDIR)/BrutalistMono.sfd "Sans Mono" > $@ 117 | 118 | $(BUILDDIR)/unicover-sans.txt: $(TMPDIR)/Brutalist.sfd 119 | @echo "[5] => $@" 120 | install -d $(dir $@) 121 | $(UNICOVER) $(UNICODEDATA) $(BLOCKS) \ 122 | $(TMPDIR)/Brutalist.sfd "Sans" > $@ 123 | 124 | $(BUILDDIR)/unicover-lgc.txt: $(patsubst %, $(TMPDIR)/%.sfd, BrutalistLGCSans BrutalistLGCSerif BrutalistLGCSansMono) 125 | @echo "[5] => $@" 126 | install -d $(dir $@) 127 | $(UNICOVER) $(UNICODEDATA) $(BLOCKS) \ 128 | $(TMPDIR)/BrutalistLGCSans.sfd "Sans" \ 129 | $(TMPDIR)/BrutalistLGCSerif.sfd "Serif" \ 130 | $(TMPDIR)/BrutalistLGCSansMono.sfd "Sans Mono" > $@ 131 | 132 | $(BUILDDIR)/langcover.txt: $(patsubst %, $(TMPDIR)/%.sfd, Brutalist BrutalistSerif BrutalistMono) 133 | @echo "[6] => $@" 134 | install -d $(dir $@) 135 | ifeq "$(FC-LANG)" "" 136 | touch $@ 137 | else 138 | $(LANGCOVER) $(FC-LANG) \ 139 | $(TMPDIR)/Brutalist.sfd "Sans" \ 140 | $(TMPDIR)/BrutalistSerif.sfd "Serif" \ 141 | $(TMPDIR)/BrutalistMono.sfd "Sans Mono" > $@ 142 | endif 143 | 144 | $(BUILDDIR)/langcover-sans.txt: $(TMPDIR)/Brutalist.sfd 145 | @echo "[6] => $@" 146 | install -d $(dir $@) 147 | ifeq "$(FC-LANG)" "" 148 | touch $@ 149 | else 150 | $(LANGCOVER) $(FC-LANG) \ 151 | $(TMPDIR)/Brutalist.sfd "Sans" > $@ 152 | endif 153 | 154 | $(BUILDDIR)/langcover-lgc.txt: $(patsubst %, $(TMPDIR)/%.sfd, BrutalistLGCSans BrutalistLGCSerif BrutalistLGCSansMono) 155 | @echo "[6] => $@" 156 | install -d $(dir $@) 157 | ifeq "$(FC-LANG)" "" 158 | touch $@ 159 | else 160 | $(LANGCOVER) $(FC-LANG) \ 161 | $(TMPDIR)/BrutalistLGCSans.sfd "Sans" \ 162 | $(TMPDIR)/BrutalistLGCSerif.sfd "Serif" \ 163 | $(TMPDIR)/BrutalistLGCSansMono.sfd "Sans Mono" > $@ 164 | endif 165 | 166 | $(BUILDDIR)/Makefile: Makefile 167 | @echo "[7] => $@" 168 | install -d $(dir $@) 169 | sed -e "s+^VERSION\([[:space:]]*\)=\(.*\)+VERSION = $(VERSION)+g"\ 170 | -e "s+^SNAPSHOT\([[:space:]]*\)=\(.*\)+SNAPSHOT = $(SNAPSHOT)+g" < $< > $@ 171 | touch -r $< $@ 172 | 173 | $(TMPDIR)/$(SRCARCHIVE): $(addprefix $(BUILDDIR)/, $(GENDOCFULL) Makefile) $(FULLSFD) 174 | @echo "[8] => $@" 175 | install -d -m 0755 $@/$(SCRIPTSDIR) 176 | install -d -m 0755 $@/$(SRCDIR) 177 | install -d -m 0755 $@/$(FONTCONFDIR) 178 | install -d -m 0755 $@/$(DOCDIR) 179 | install -p -m 0644 $(BUILDDIR)/Makefile $@ 180 | install -p -m 0755 $(GENERATE) $(TTPOSTPROC) $(LGC) $(NORMALIZE) \ 181 | $(UNICOVER) $(LANGCOVER) $(STATUS) $(PROBLEMS) \ 182 | $@/$(SCRIPTSDIR) 183 | install -p -m 0644 $(FULLSFD) $@/$(SRCDIR) 184 | install -p -m 0644 $(FONTCONF) $@/$(FONTCONFDIR) 185 | install -p -m 0644 $(addprefix $(BUILDDIR)/, $(GENDOCFULL)) \ 186 | $(STATICDOC) $(STATICSRCDOC) $@/$(DOCDIR) 187 | 188 | $(TMPDIR)/$(FULLARCHIVE): full 189 | @echo "[8] => $@" 190 | install -d -m 0755 $@/$(TTFDIR) 191 | install -d -m 0755 $@/$(FONTCONFDIR) 192 | install -d -m 0755 $@/$(DOCDIR) 193 | install -p -m 0644 $(FULLTTF) $@/$(TTFDIR) 194 | install -p -m 0644 $(FONTCONFFULL) $@/$(FONTCONFDIR) 195 | install -p -m 0644 $(addprefix $(BUILDDIR)/, $(GENDOCFULL)) \ 196 | $(STATICDOC) $@/$(DOCDIR) 197 | 198 | $(TMPDIR)/$(SANSARCHIVE): sans 199 | @echo "[8] => $@" 200 | install -d -m 0755 $@/$(TTFDIR) 201 | install -d -m 0755 $@/$(DOCDIR) 202 | install -p -m 0644 $(BUILDDIR)/Brutalist.ttf $@/$(TTFDIR) 203 | install -p -m 0644 $(addprefix $(BUILDDIR)/, $(GENDOCSANS)) \ 204 | $(STATICDOC) $@/$(DOCDIR) 205 | 206 | $(TMPDIR)/$(LGCARCHIVE): lgc 207 | @echo "[8] => $@" 208 | install -d -m 0755 $@/$(TTFDIR) 209 | install -d -m 0755 $@/$(FONTCONFDIR) 210 | install -d -m 0755 $@/$(DOCDIR) 211 | install -p -m 0644 $(LGCTTF) $@/$(TTFDIR) 212 | install -p -m 0644 $(FONTCONFLGC) $@/$(FONTCONFDIR) 213 | install -p -m 0644 $(addprefix $(BUILDDIR)/, $(GENDOCLGC)) \ 214 | $(STATICDOC) $@/$(DOCDIR) 215 | 216 | $(DISTDIR)/%.zip: $(TMPDIR)/% 217 | @echo "[9] => $@" 218 | install -d $(dir $@) 219 | (cd $(TMPDIR); zip -rv $(abspath $@) $(notdir $<)) 220 | 221 | $(DISTDIR)/%.tar.bz2: $(TMPDIR)/% 222 | @echo "[9] => $@" 223 | install -d $(dir $@) 224 | (cd $(TMPDIR); tar cjvf $(abspath $@) $(notdir $<)) 225 | 226 | %.md5: % 227 | @echo "[10] => $@" 228 | (cd $(dir $<); md5sum -b $(notdir $<) > $(abspath $@)) 229 | 230 | %.sha512: % 231 | @echo "[10] => $@" 232 | (cd $(dir $<); sha512sum -b $(notdir $<) > $(abspath $@)) 233 | 234 | %.sfd.norm: %.sfd 235 | @echo "[11] $< => $@" 236 | $(NORMALIZE) $< 237 | touch -r $< $@ 238 | 239 | check : $(NORMSFD) 240 | for sfd in $^ ; do \ 241 | echo "[12] Checking $$sfd" ;\ 242 | $(PROBLEMS) $$sfd ;\ 243 | done 244 | 245 | munge: $(NORMSFD) 246 | for sfd in $(SFDFILES) ; do \ 247 | echo "[13] $(TMPDIR)/$$sfd.norm => $(SRCDIR)/$$sfd" ;\ 248 | cp $(TMPDIR)/$$sfd.norm $(SRCDIR)/$$sfd ;\ 249 | done 250 | 251 | full : $(FULLTTF) $(addprefix $(BUILDDIR)/, $(GENDOCFULL)) 252 | 253 | sans : $(addprefix $(BUILDDIR)/, Brutalist.ttf $(GENDOCSANS)) 254 | 255 | lgc : $(LGCTTF) $(addprefix $(BUILDDIR)/, $(GENDOCLGC)) 256 | 257 | ttf : full-ttf sans-ttf lgc-ttf 258 | 259 | full-ttf : $(FULLTTF) 260 | 261 | sans-ttf: $(BUILDDIR)/Brutalist.ttf 262 | 263 | lgc-ttf : $(LGCTTF) 264 | 265 | status : $(addprefix $(BUILDDIR)/, $(GENDOCFULL)) 266 | 267 | dist : src-dist full-dist sans-dist lgc-dist 268 | 269 | src-dist : $(addprefix $(DISTDIR)/$(SRCARCHIVE), $(ARCHIVEEXT) $(SUMEXT)) 270 | 271 | full-dist : $(addprefix $(DISTDIR)/$(FULLARCHIVE), $(ARCHIVEEXT) $(SUMEXT)) 272 | 273 | sans-dist : $(addprefix $(DISTDIR)/$(SANSARCHIVE), $(ARCHIVEEXT) $(SUMEXT)) 274 | 275 | lgc-dist : $(addprefix $(DISTDIR)/$(LGCARCHIVE), $(ARCHIVEEXT) $(SUMEXT)) 276 | 277 | norm : $(NORMSFD) 278 | 279 | check-harder : clean check 280 | 281 | pre-patch : munge clean 282 | 283 | clean : 284 | $(RM) -r $(TMPDIR) $(BUILDDIR) $(DISTDIR) 285 | 286 | condensed: $(NORMSFD) 287 | $(NARROW) 90 $(TMPDIR)/Brutalist.sfd.norm 288 | $(NARROW) 90 $(TMPDIR)/Brutalist-Bold.sfd.norm 289 | $(NARROW) 90 $(TMPDIR)/Brutalist-Oblique.sfd.norm 290 | $(NARROW) 90 $(TMPDIR)/Brutalist-BoldOblique.sfd.norm 291 | $(NARROW) 90 $(TMPDIR)/BrutalistSerif.sfd.norm 292 | $(NARROW) 90 $(TMPDIR)/BrutalistSerif-Bold.sfd.norm 293 | $(NARROW) 90 $(TMPDIR)/BrutalistSerif-Italic.sfd.norm 294 | $(NARROW) 90 $(TMPDIR)/BrutalistSerif-BoldItalic.sfd.norm 295 | $(NORMALIZE) $(TMPDIR)/Brutalist.sfd.norm.narrow 296 | $(NORMALIZE) $(TMPDIR)/Brutalist-Bold.sfd.norm.narrow 297 | $(NORMALIZE) $(TMPDIR)/Brutalist-Oblique.sfd.norm.narrow 298 | $(NORMALIZE) $(TMPDIR)/Brutalist-BoldOblique.sfd.norm.narrow 299 | $(NORMALIZE) $(TMPDIR)/BrutalistSerif.sfd.norm.narrow 300 | $(NORMALIZE) $(TMPDIR)/BrutalistSerif-Bold.sfd.norm.narrow 301 | $(NORMALIZE) $(TMPDIR)/BrutalistSerif-Italic.sfd.norm.narrow 302 | $(NORMALIZE) $(TMPDIR)/BrutalistSerif-BoldItalic.sfd.norm.narrow 303 | cp $(TMPDIR)/Brutalist.sfd.norm.narrow.norm $(TMPDIR)/BrutalistCondensed.sfd.norm 304 | cp $(TMPDIR)/Brutalist-Bold.sfd.norm.narrow.norm $(TMPDIR)/BrutalistCondensed-Bold.sfd.norm 305 | cp $(TMPDIR)/Brutalist-Oblique.sfd.norm.narrow.norm $(TMPDIR)/BrutalistCondensed-Oblique.sfd.norm 306 | cp $(TMPDIR)/Brutalist-BoldOblique.sfd.norm.narrow.norm $(TMPDIR)/BrutalistCondensed-BoldOblique.sfd.norm 307 | cp $(TMPDIR)/BrutalistSerif.sfd.norm.narrow.norm $(TMPDIR)/BrutalistSerifCondensed.sfd.norm 308 | cp $(TMPDIR)/BrutalistSerif-Bold.sfd.norm.narrow.norm $(TMPDIR)/BrutalistSerifCondensed-Bold.sfd.norm 309 | cp $(TMPDIR)/BrutalistSerif-Italic.sfd.norm.narrow.norm $(TMPDIR)/BrutalistSerifCondensed-Italic.sfd.norm 310 | cp $(TMPDIR)/BrutalistSerif-BoldItalic.sfd.norm.narrow.norm $(TMPDIR)/BrutalistSerifCondensed-BoldItalic.sfd.norm 311 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brutalist Mono 2 | 3 | Brutalist Mono is a very simple modification on top of [DejaVu Sans Mono](https://github.com/dejavu-fonts/dejavu-fonts) (yes, _another one_), making it more suitable for coding. The changes are very nitpicky and you can probably just move along. 4 | 5 | ![brutalist](images/brutalist.jpg) 6 | 7 | Comparison to DejaVu Sans Mono: 8 | 9 | ![brutalist](images/brutalist_vs_dejavu.gif) 10 | 11 | ## Changes from DejaVu Sans Mono 12 | 13 | v1.0: 14 | 15 | * `r` – offset to the left 16 | * `_` – increase height, reduce width 17 | * `-` (minus) – make wider 18 | * `0` – slashed instead of dotted 19 | 20 | v1.1: 21 | 22 | * `l` – increase upper arm and recenter 23 | * `i` and `j` – increase dot sizes, making them square 24 | 25 | v1.2: 26 | 27 | * `l` – drop foot below the baseline 28 | * `C` – flatter terminals 29 | * `G` – flatter top terminal 30 | * `J` – flatter bottom terminal 31 | * `S` – flatter terminals 32 | * `a` – flatter top terminal 33 | * `c` – flatter terminals 34 | * `e` – flatter bottom terminal 35 | * `g` – flatter bottom terminal 36 | * `r` – flatter top terminal 37 | * `s` – flatter terminals 38 | * `2` – flatter top terminal 39 | * `3` – flatter terminals 40 | * `5` – flatter bottom terminal and mid stroke 41 | * `6` – flatter top terminal 42 | * `9` – flatter bottom terminal 43 | * `$` – flatter terminals 44 | 45 | v2.0: 46 | 47 | * `C` – slightly rounder shape 48 | * `G` – more even upper terminal 49 | * `S` – more even terminals 50 | * `c` – more even terminals 51 | * `l` – more pedantic bottom 52 | * `r` – wider right terminal 53 | * `s` – more even terminals 54 | * `9` – more even lower terminal 55 | * `g` – more even lower terminal 56 | * `J` – clean up lower terminal 57 | * `~` – blatantly stolen from Hack 58 | * `t` – make bottom curved the same as `l` 59 | * `j` – make bottom curved the same as `l` 60 | * `[` – make wider, match `lparen`'s width 61 | * `]` – make wider, match `rparen`'s width 62 | * `5` – more even bottom terminal, add back the "spike" in the middle 63 | * `y` – curved bottom terminal 64 | * `$` – more even terminals 65 | 66 | v2.1: 67 | 68 | * `,`, `.`, `;`, `:`, `!`, `?` – use rounded dots and commas (adapted from Hack) 69 | * `*` – use Menlo variant 70 | * `l` – clean up bottom curve a little bit 71 | 72 | v2.1.1 73 | 74 | * fix: font not showing up as monospace ([#2](https://github.com/BRUTALISM/Brutalist/pull/2)) 75 | 76 | ## Motivation 77 | 78 | Another DejaVu/Vera Mono clone? Yes. But this one is opinionated: 79 | 80 | 1. I don't care about "readability on small font sizes". Make your font larger and/or get a decent monitor. It's the 21st century. 81 | 2. I don't care about font hinting. Modern monitors are high-DPI. 82 | * Note: The native renderer on Windows seems to require manual hinting instructions to be added even when rendering at high DPI screens. I will not be adding this. Therefore, this font looks terrible on Windows, unless you're using an Electron app (VS Code, Atom, etc). They're using a custom renderer and render correctly. 83 | 3. I don't care about `O0`, `lI1|`, or any of that crap. It's fine. See point #1. 84 | 85 | If any of this bothers you, try [Hack](https://github.com/source-foundry/Hack). It's awesome. I made [my mod](https://github.com/BRUTALISM/Hack) available as well. 86 | 87 | ### Why not Hack? 88 | 89 | Hack is great, but has way too many unnecessary modifications on top of the baseline DejaVu Sans Mono. To name a few: 90 | 91 | * the parentheses are unnecessarily spread out in earlier versions, and too rounded in newer ones 92 | * the `1` has an awkward downward facing arm 93 | * contributing is complicated if you're only using plain old FontForge (I don't want to shell out EUR 250 for Glyphs.app) 94 | * [alt-hack](https://github.com/source-foundry/alt-hack) is great but I ended up just using it to revert most mods back to the original DejaVu style, so I figured why bother? 95 | 96 | ### Why not Menlo? 97 | 98 | * the uppercase `N` is hideously wide (once you see it, you can not unsee it) 99 | * punctuation is unnecessarily exaggerated 100 | * there are [many weird tweaks](http://leancrew.com/all-this/2009/10/the-compleat-menlovera-sans-comparison/) done to it (relative to its parent Bitstream Vera Sans Mono) to make it render better on ancient Mac OS versions with low-DPI monitors, which disqualifies it immediately (see point #1 in "Motivation" above) 101 | 102 | ### Why not DejaVu Sans Mono? 103 | 104 | This typeface is almost perfect for programming, except: 105 | 106 | * the underscore is ridiculously thin, making it visually odd when reading `THINGS_WITH_MANY_UNDERSCORES` and esoteric C/C++ identifiers with `__multiple__underscores__` 107 | * the lowercase `r` is offset a bit to the right (Hack has got this right – once you see it, you can not unsee it) 108 | * the `-` glyph is ridiculously narrow 109 | * other nitpicky stuff you really don't care about but I do 110 | 111 | – [Source Code Pro](https://github.com/adobe-fonts/source-code-pro) is still the king, and I don't dare [come at the king](https://www.youtube.com/watch?v=py1WDlaIr9A). 112 | 113 | ## Building 114 | 115 | Short version: 116 | 117 | * Edit the .sdf files with FontForge (if you want to mod) 118 | * `make` 119 | * observe the `build` folder 120 | 121 | This will probably fail, so: [long version](BUILDING.md). 122 | 123 | ## Contributing 124 | 125 | If you really convince me. 126 | 127 | ## License 128 | 129 | Same as DejaVu fonts, see [LICENSE](LICENSE). -------------------------------------------------------------------------------- /images/brutalist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BRUTALISM/Brutalist/780c9de0895b516ff604e546804793a835e8ab0c/images/brutalist.jpg -------------------------------------------------------------------------------- /images/brutalist_vs_dejavu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BRUTALISM/Brutalist/780c9de0895b516ff604e546804793a835e8ab0c/images/brutalist_vs_dejavu.gif -------------------------------------------------------------------------------- /scripts/colorize.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | 4 | # $Id$ 5 | 6 | # glyph comment colorizing 7 | # (c)2004 Stepan Roh (PUBLIC DOMAIN) 8 | # usage: ./colorize.pl color sfd_files+ < glyph_codes_file 9 | # will create files with suffix .color 10 | # color is either none, yellow, green or hexadecimal 24bit value (0xff0000 is red) 11 | # glyph_codes_file should contain codes of glyphs to be colored delimited by whitespace and/or commas 12 | # - glyph codes are U+nnnn (nnnn is hexadecimal) 13 | # - syntax for code ranges: U+nnnn-U+nnnn (no whitespace inside) 14 | # file colorize.pe is created during the execution (it is safe to remove it after) 15 | 16 | # change to the fontforge binary location if it is not in the system PATH 17 | $fontforge = "fontforge"; 18 | 19 | sub decode_color($) { 20 | my ($color) = @_; 21 | if ($color eq 'yellow' || $color eq 'wip') { 22 | return "0xffff00"; 23 | } elsif ($color eq 'green' || $color eq 'req') { 24 | return "0x00ff00"; 25 | } elsif ($color eq 'none') { 26 | return -2; 27 | } 28 | return $color; 29 | } 30 | 31 | sub decode_glyph_code($) { 32 | my ($code) = @_; 33 | $code =~ s/^[Uu]\+//; 34 | return $code; 35 | } 36 | 37 | sub parse_glyph_codes_from_stdin() { 38 | my @ret = (); 39 | my @input = split (/(?:\s|,)+/, join ('', )); 40 | foreach $token (@input) { 41 | if ($token =~ /^(.*)-(.*)$/) { 42 | push (@ret, decode_glyph_code ($1), decode_glyph_code ($2)); 43 | } else { 44 | push (@ret, decode_glyph_code ($token)); 45 | push (@ret, decode_glyph_code ($token)); 46 | } 47 | } 48 | return @ret; 49 | } 50 | 51 | if (@ARGV < 2) { 52 | print STDERR "usage: $0 color sfd_files+ < glyph_codes_file\n"; 53 | exit (1); 54 | } 55 | 56 | @glyph_codes = parse_glyph_codes_from_stdin(); 57 | $color = decode_color(shift @ARGV); 58 | 59 | sub min($$) { 60 | my ($a, $b) = @_; 61 | return ($a < $b) ? $a : $b; 62 | } 63 | 64 | open (PE, '>colorize.pe') || die "Unable to open colorize.pe : $!\n"; 65 | print PE 'i = 1 66 | while ( i < $argc ) 67 | Open($argv[i], 1) 68 | SelectNone() 69 | '; 70 | for (my $i = 0; $i < @glyph_codes; $i += 16) { 71 | print PE ' SelectMore(', join (',', map { '0u'.$_ } @glyph_codes[$i..min($i+15, @glyph_codes-1)]), ")\n"; 72 | } 73 | print PE ' SetCharColor('.$color.') 74 | Save($argv[i] + ".color") 75 | i++ 76 | endloop 77 | '; 78 | close (PE) || die "Unable to close colorize.pe : $!\n"; 79 | 80 | system ($fontforge, '-nosplash', '-script', 'colorize.pe', @ARGV); 81 | 82 | 1; 83 | -------------------------------------------------------------------------------- /scripts/generate.pe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fontforge 2 | # $Id$ 3 | 4 | # script file for FontForge for TTF generation 5 | # usage: 6 | # mkdir generated 7 | # chmod +x generate.pe 8 | # ./generate.pe *.sfd 9 | 10 | # font generation flags: 11 | # 8 => do not include TT instructions (for experimental typefaces) 12 | # 0x20 => generate a 'PfEd' table and store glyph comments 13 | # 0x40 => generate a 'PfEd' table and store glyph colors 14 | # 0x800 => generate old fashioned kern tables. 15 | # - this one is important because it generates correct kerning tables 16 | def_gen_flags = 0x20 + 0x40 + 0x800 17 | exp_gen_flags = def_gen_flags + 8 18 | 19 | if ($version < "20080330") 20 | Error("Your version of FontForge is too old - 20080330 or newer is required"); 21 | endif 22 | # FoundryName is not used in TTF generation 23 | SetPref("FoundryName", "Brutalist") 24 | # first 4 characters of TTFFoundry are used for achVendId 25 | SetPref("TTFFoundry", "Brutalist") 26 | i = 1 27 | while ( i < $argc ) 28 | Open($argv[i], 1) 29 | gen_flags = def_gen_flags 30 | # Serif Italic and Serif Bold Italic are experimental 31 | if ((Strcasestr ($fontname, "Serif") > -1) && (Strcasestr ($fontname, "Italic") > -1)) 32 | gen_flags = exp_gen_flags 33 | endif 34 | if (Strcasestr ($fontname, "Condensed") > -1) 35 | gen_flags = exp_gen_flags 36 | endif 37 | if (Strcasestr ($fontname, "ExtraLight") > -1) 38 | gen_flags = exp_gen_flags 39 | endif 40 | Generate( $curfont + ".ttf", "", gen_flags) 41 | Close() 42 | i++ 43 | endloop 44 | -------------------------------------------------------------------------------- /scripts/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fontforge 2 | # $Id: generate.py 1902 2007-06-21 23:44:12Z apanov $ 3 | 4 | # script file for FontForge for TTF generation 5 | # usage: 6 | # mkdir generated 7 | # chmod +x generate.pe 8 | # ./generate.pe *.sfd 9 | import fontforge, sys; 10 | required_version = "20080330" 11 | 12 | # font generation flags: 13 | # omit-instructions => do not include TT instructions (for experimental typefaces) 14 | # opentype => include OpenType tables 15 | # glyph-comments => generate a 'PfEd' table and store glyph comments 16 | # glyph-colors => generate a 'PfEd' table and store glyph colors 17 | # old-kern => generate old fashioned kern tables. 18 | # - this one is important because it generates correct kerning tables for legacy 19 | # applications 20 | def_gen_flags = ("opentype", "glyph-comments", "glyph-colors", "old-kern") 21 | exp_gen_flags = def_gen_flags + ("omit-instructions",) 22 | 23 | if fontforge.version() < required_version: 24 | print ("Your version of FontForge is too old - %s or newer is required" % (required_version)); 25 | # FoundryName is not used in TTF generation 26 | fontforge.setPrefs("FoundryName", "Brutalist"); 27 | # first 4 characters of TTFFoundry are used for achVendId 28 | fontforge.setPrefs("TTFFoundry", "Brutalist") 29 | i = 1 30 | while i < len(sys.argv): 31 | font=fontforge.open(sys.argv[i]); 32 | gen_flags = def_gen_flags 33 | 34 | # Serif Italic and Serif Bold Italic are experimental 35 | if font.fontname.rfind("Serif") > -1 and font.fontname.rfind("Italic") > -1: 36 | gen_flags = exp_gen_flags; 37 | if font.fontname.rfind("Condensed") > -1: 38 | gen_flags = exp_gen_flags; 39 | if font.fontname.rfind("ExtraLight") > -1: 40 | gen_flags = exp_gen_flags; 41 | 42 | fontname = "generated/" + font.fontname + ".ttf"; 43 | font.generate(fontname,"",gen_flags); 44 | font.close(); 45 | i += 1; 46 | -------------------------------------------------------------------------------- /scripts/italicize.pe: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | # script file for FontForge for font italicization 4 | # usage: fontforge -script italicize.pe angle *.sfd 5 | # created files have suffix .oblique 6 | 7 | if ($argc < 2) 8 | Error("usage: angle sfds...") 9 | endif 10 | angle = Strtol($argv[1]) 11 | i = 2 12 | while (i < $argc) 13 | Open($argv[i], 1) 14 | SelectAll() 15 | Skew(angle) 16 | SetItalicAngle(-angle) 17 | # Panose changes are valid only for Brutalist fonts (I guess) 18 | SetPanose(5, 3) 19 | SetPanose(7, 11) 20 | new_fontname = $fontname 21 | j = Strstr(new_fontname, "-Roman") 22 | if (j > -1) 23 | new_fontname = Strsub(new_fontname, 0, j) 24 | endif 25 | if (Strstr(new_fontname, "-") > -1) 26 | new_fontname = new_fontname + "Oblique" 27 | else 28 | new_fontname = new_fontname + "-Oblique" 29 | endif 30 | new_fullname = $fullname + " Oblique" 31 | SetFontNames(new_fontname, "", new_fullname) 32 | SetTTFName(1033, 3, new_fullname) 33 | Save($argv[i] + ".oblique") 34 | i++ 35 | endloop 36 | -------------------------------------------------------------------------------- /scripts/langcover.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | 4 | # $Id$ 5 | 6 | # language coverage analyzator 7 | # (c)2006 Stepan Roh (PUBLIC DOMAIN) 8 | # usage: ./langcover.pl fc-lang_dir sfd_file1 label1 (sfd_file2 label2...) 9 | # files from http://webcvs.freedesktop.org/fontconfig/fontconfig/fc-lang/ should be downloaded to fc-lang directory 10 | 11 | use FileHandle; 12 | 13 | sub parse_fc_lang_dir($); 14 | sub parse_orth_file($;$); 15 | sub parse_sfd_file($); 16 | sub inc_coverage($$); 17 | sub print_coverage(); 18 | 19 | # map (language code => ( 'name' => name, 'chars' => list of glyphs, 'coverage' => ( sfd_file => coverage ) ) 20 | %langs = (); 21 | 22 | sub parse_fc_lang_dir($) { 23 | my ($fc_lang_dir) = @_; 24 | 25 | opendir(DIR, $fc_lang_dir) || die "Unable to open $fc_lang_dir : $!\n"; 26 | my @orth_files = map { "$fc_lang_dir/$_" } grep { /\.orth$/ } readdir(DIR); 27 | closedir(DIR); 28 | 29 | foreach $orth_file (@orth_files) { 30 | parse_orth_file($orth_file); 31 | } 32 | } 33 | 34 | # missing or UTF-8 lang names 35 | %lang_names = ( 36 | 'gn' => 'Guarani', 37 | 'ja' => 'Japanese', 38 | 'nb' => 'Norwegian Bokmal', 39 | 'no' => 'Norwegian (Bokmal)', 40 | 'se' => 'North Sami', 41 | 'sma' => 'South Sami', 42 | 'smj' => 'Lule Sami', 43 | 'smn' => 'Inari Sami', 44 | 'sms' => 'Skolt Sami', 45 | 'vo' => 'Volapuk', 46 | 'zh-tw' => 'Chinese (traditional)', 47 | ); 48 | 49 | sub parse_orth_file($;$) { 50 | my ($orth_file, $lang) = @_; 51 | 52 | if (!defined $lang) { 53 | ($lang) = ($orth_file =~ m,/([^/]*)\.[^./]*$,); 54 | $lang =~ tr/_/-/; 55 | } 56 | # XXX some names in orth files have different language codes 57 | my $orth_lang = $lang; 58 | $orth_lang = 'kw' if ($orth_lang eq 'ay'); 59 | $orth_lang = 'kw' if ($orth_lang eq 'fj'); 60 | $orth_lang = 'eth' if ($orth_lang eq 'gez'); 61 | $orth_lang = 'hi' if ($orth_lang eq 'pa'); 62 | $orth_lang = 'cu' if ($orth_lang eq 'sco'); 63 | $orth_lang = 'af' if ($orth_lang eq 'sm'); 64 | $orth_lang = 'smj' if ($orth_lang eq 'sms'); 65 | $orth_lang = 'ge' if ($orth_lang eq 'te'); 66 | if (exists($lang_names{$lang})) { 67 | $langs{$lang}{'name'} = $lang_names{$lang}; 68 | } 69 | my $f = new FileHandle($orth_file) || die "Unable to open $orth_file : $!\n"; 70 | while (<$f>) { 71 | if (!exists($langs{$lang}{'name'})) { 72 | if (/^#\s*(.*?)\s*\($lang\)/i) { 73 | $langs{$lang}{'name'} = $1; 74 | next; 75 | } 76 | if (/^#\s*(.*?)\s*\($orth_lang\)/i) { 77 | $langs{$lang}{'name'} = $1; 78 | next; 79 | } 80 | } 81 | next if (/^\s*(#|$)/); 82 | if (/^\s*include\s+(\S+)/) { 83 | my $include = $1; 84 | my $include_file; 85 | ($include_file = $orth_file) =~ s,/[^/]+$,/$include,; 86 | if ($include_file eq $orth_file) { 87 | print "$orth_file creates an include loop"; 88 | } 89 | else { 90 | parse_orth_file($include_file, $lang); 91 | } 92 | next; 93 | } 94 | my ($start) = ($_ =~ /^\s*(\S+)/); 95 | if ($lang eq 'ibo') { 96 | # XXX ibo.orth 1ee1 -> 1ee4 (https://bugs.freedesktop.org/show_bug.cgi?id=6237) 97 | $start = '1ee4' if ($start eq '1ee1'); 98 | } 99 | my $end = $start; 100 | if ($start =~ /-/) { 101 | ($start, $end) = split(/-/, $start); 102 | } 103 | # XXX ab.orth 0re1 -> 04e1 (https://bugs.freedesktop.org/show_bug.cgi?id=6238) 104 | $end = '04e1' if ($end eq '0re1'); 105 | $start = hex ($start); 106 | $end = hex ($end); 107 | for (my $dec_enc = $start; $dec_enc <= $end; $dec_enc++) { 108 | $langs{$lang}{'chars'}{$dec_enc} = 1; 109 | } 110 | } 111 | $f->close(); 112 | } 113 | 114 | sub inc_coverage($$) { 115 | my ($sfd_file, $dec_enc) = @_; 116 | 117 | foreach $lang (keys %langs) { 118 | if (exists $langs{$lang}{'chars'}{$dec_enc}) { 119 | $langs{$lang}{'coverage'}{$sfd_file}++; 120 | } 121 | } 122 | } 123 | 124 | sub parse_sfd_file($) { 125 | my ($sfd_file) = @_; 126 | 127 | open (F, $sfd_file) || die "Unable to open $sfd_file : $!\n"; 128 | my $curchar = ''; 129 | my $curenc = ''; 130 | my $empty = 0; 131 | while () { 132 | if (/^StartChar:\s*(\S+)\s*$/) { 133 | $curchar = $1; 134 | $curenc = ''; 135 | $empty = 0; 136 | } elsif (/^Colour:/) { 137 | # XXX this is quick'n'dirty hack to detect non-empty glyphs 138 | $empty = 1; 139 | } elsif (/^Encoding:\s*\d+\s*(\d+)\s*\d+\s*$/) { 140 | $curenc = $1; 141 | } elsif ($curenc && !$empty && /^EndChar\s*/) { 142 | inc_coverage ($sfd_file, $curenc); 143 | } 144 | } 145 | close (F); 146 | } 147 | 148 | # TODO: formats would be better 149 | sub print_coverage() { 150 | print < 0) { 170 | printf " %3d%%", $percent; 171 | } else { 172 | print " "; 173 | } 174 | printf " %-13s", "($coverage/$length)"; 175 | } 176 | print "\n"; 177 | } 178 | } 179 | 180 | if (@ARGV < 3) { 181 | print STDERR "usage: fc-lang_dir sfd_file1 label1 (sfd_file2 label2...)\n"; 182 | exit 1; 183 | } 184 | 185 | $fc_lang_dir = shift @ARGV; 186 | @sfd_files = (); 187 | %sfd_files = (); 188 | while (@ARGV) { 189 | $sfd_file = shift @ARGV; 190 | $label = shift @ARGV; 191 | push (@sfd_files, $sfd_file); 192 | $sfd_files{$sfd_file} = $label; 193 | } 194 | 195 | parse_fc_lang_dir($fc_lang_dir); 196 | foreach $sfd_file (@sfd_files) { 197 | parse_sfd_file($sfd_file); 198 | } 199 | print_coverage(); 200 | 201 | 1; 202 | -------------------------------------------------------------------------------- /scripts/lgc.pe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fontforge 2 | 3 | # script to strip non-LGC glyphs out of the fonts 4 | 5 | i = 1 6 | while ( i < $argc ) 7 | Open($argv[i], 1) 8 | 9 | Select(0u0530, 0u1cff) # Armenian through Vedic Extensions 10 | SelectMore(0u2c00, 0u2c5f) # Glagolitic 11 | SelectMore(0u2d00, 0u2ddf) # Georgian Supplement through Ethiopic Extended 12 | SelectMore(0u2e80, 0ua63f) # CJK Radicals through Vai 13 | SelectMore(0ua6a0, 0ua6ff) # Banum 14 | SelectMore(0ua800, 0udfff) # Syloti Nagri through Hangul 15 | SelectMore(0uf900, 0ufaff) # CJK Compatibility Ideographs 16 | SelectMore(0ufb07, 0ufe1f) # Alphabetical Presentation Forms (partial) through Vertical Forms 17 | SelectMore(0ufe30, 0uffef) # CJK Compatibility Forms through Halfwidth and Fullwidth Forms 18 | SelectMore(0u10000, 0u1d37f) # Linear B Syllabary through Counting Rod Numerals 19 | SelectMore(0u1ee00, 0u1f02f) # Arabic Mathematical Alphabetic Symbols through Mahjong Tiles 20 | SelectMore(0u1f200, 0u1f2ff) # Enclosed Ideographic Supplement 21 | SelectMore(0u20000, 0ueffff) # CJK Unified Ideographs Extention B and everything thereafter 22 | Clear() 23 | 24 | Save($argv[i]) 25 | i++ 26 | endloop 27 | -------------------------------------------------------------------------------- /scripts/merge.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | 4 | # $Id$ 5 | 6 | # SFD files merger 7 | # (c)2005 Stepan Roh (PUBLIC DOMAIN) 8 | # usage: ./merge.pl sfd_from sfd_to sfd_out 9 | # will merge sfd_to with sfd_from into the sfd_out (if there are some merges) 10 | # will print merged glyph codes on stdout 11 | 12 | # SFD = ( 'header' => @header_lines, 'footer' => @footer_lines, 'glyphs' => ( glyphenc => @glyph_lines ) ) 13 | 14 | sub load_sfd ($) { 15 | my ($sfdfile) = @_; 16 | 17 | my %sfd = (); 18 | open (SFD, $sfdfile) || die "Unable to open $sfdfile : $!\n"; 19 | # -1 = header, 0 = glyphs, 1 = footer 20 | my $section = -1; 21 | my $enc = 0; 22 | my @cur = (); 23 | while () { 24 | if (/^StartChar:/) { 25 | $section = 0; 26 | } elsif (/^Encoding:\s*(\d+)/) { 27 | $enc = $1; 28 | } 29 | if ($section == -1) { 30 | push (@{$sfd{'header'}}, $_); 31 | } elsif ($section == 1) { 32 | push (@{$sfd{'footer'}}, $_); 33 | } else { 34 | push (@cur, $_); 35 | } 36 | if (/^EndChar\s*$/) { 37 | $section = 1; 38 | @{$sfd{'glyphs'}{$enc}} = @cur; 39 | @cur = (); 40 | } 41 | } 42 | close (SFD); 43 | 44 | return %sfd; 45 | } 46 | 47 | sub save_sfd ($\%) { 48 | my ($sfdfile, $sfd_ref) = @_; 49 | 50 | open (SFD, '>'.$sfdfile) || die "Unable to open $sfdfile : $!\n"; 51 | print SFD @{$$sfd_ref{'header'}}; 52 | foreach $enc (sort { $a <=> $b } keys %{$$sfd_ref{'glyphs'}}) { 53 | print SFD @{$$sfd_ref{'glyphs'}{$enc}}; 54 | } 55 | print SFD @{$$sfd_ref{'footer'}}; 56 | close (SFD); 57 | } 58 | 59 | sub is_dummy_glyph (\@) { 60 | my ($glyph_ref) = @_; 61 | 62 | foreach $l (@$glyph_ref) { 63 | if ($l =~ /^Colour:/) { 64 | # XXX this is quick'n'dirty hack to detect dummy glyphs 65 | return 1; 66 | } 67 | } 68 | 69 | return 0; 70 | } 71 | 72 | sub merge_glyphs (\%\%) { 73 | my ($sfd_from, $sfd_to) = @_; 74 | 75 | my $merged = 0; 76 | foreach $enc (sort { $a <=> $b } keys %{$$sfd_from{'glyphs'}}) { 77 | if (!exists ($$sfd_to{'glyphs'}{$enc}) || 78 | (!is_dummy_glyph(@{$$sfd_from{'glyphs'}{$enc}}) && is_dummy_glyph(@{$$sfd_to{'glyphs'}{$enc}}))) { 79 | $$sfd_to{'glyphs'}{$enc} = $$sfd_from{'glyphs'}{$enc}; 80 | print $enc, "\n"; 81 | $merged++; 82 | } 83 | } 84 | 85 | return $merged; 86 | } 87 | 88 | if (@ARGV < 3) { 89 | print STDERR "usage: $0 sfd_from sfd_to sfd_out\n"; 90 | exit (1); 91 | } 92 | 93 | ($sfdfile_from, $sfdfile_to, $sfdfile_out) = @ARGV; 94 | 95 | %sfd_from = load_sfd ($sfdfile_from); 96 | %sfd_to = load_sfd ($sfdfile_to); 97 | if (merge_glyphs (%sfd_from, %sfd_to) > -1) { 98 | save_sfd ($sfdfile_out, %sfd_to); 99 | } 100 | 101 | 1; 102 | -------------------------------------------------------------------------------- /scripts/mescover.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | 4 | # $Id$ 5 | 6 | # MES coverage analyzer 7 | # (c)2005 Stepan Roh (PUBLIC DOMAIN) 8 | # usage: ./mescover.pl mes_spec_file sfd_files+ 9 | 10 | sub parse_mes_spec_file($); 11 | sub init_mes_glyphs(); 12 | sub print_mes_glyphs(); 13 | sub parse_sfd_file($); 14 | 15 | # map (MES glyph dec => 1) 16 | %mes_glyphs = (); 17 | $mes_collection = 'UNKNOWN'; 18 | 19 | sub parse_mes_spec_file($) { 20 | my ($mes_file) = @_; 21 | 22 | open (F, $mes_file) || die "Unable to open $mes_file : $!\n"; 23 | my $plane = '00'; 24 | while () { 25 | if (/^Collection Name:\s+(.*?)\s*$/) { 26 | $mes_collection = $1; 27 | } elsif (/^Plane\s+(\d+)\s*$/) { 28 | $plane = $1; 29 | } elsif (/^([A-F0-9]+)\s+(.*?)\s*$/) { 30 | my $row = $1; 31 | my @cells = split(/\s+/, $2); 32 | foreach $cell (@cells) { 33 | my @range = split(/-/, $cell); 34 | if (@range == 1) { 35 | my $hexenc = $plane.$row.$range[0]; 36 | my $decenc = hex($hexenc); 37 | $mes_glyphs{$decenc} = 1; 38 | } else { 39 | my $hexenc_start = $plane.$row.$range[0]; 40 | my $decenc_start = hex($hexenc_start); 41 | my $hexenc_end = $plane.$row.$range[1]; 42 | my $decenc_end = hex($hexenc_end); 43 | for (my $decenc = $decenc_start; $decenc <= $decenc_end; $decenc++) { 44 | $mes_glyphs{$decenc} = 1; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | close (F); 51 | } 52 | 53 | sub init_mes_glyphs() { 54 | foreach $decenc (keys %mes_glyphs) { 55 | $mes_glyphs{$decenc} = 1; 56 | } 57 | } 58 | 59 | sub print_mes_glyphs() { 60 | my $cnt = 0; 61 | my $missed = 0; 62 | my $lastenc = -100; 63 | my $in_range = 0; 64 | foreach $decenc (sort keys %mes_glyphs) { 65 | if ($mes_glyphs{$decenc} != 0) { 66 | if ($decenc == $lastenc + 1) { 67 | $lastenc = $decenc; 68 | $in_range = 1; 69 | } else { 70 | if ($in_range) { 71 | printf("-U+%04x", $lastenc); 72 | } 73 | printf(" U+%04x", $decenc); 74 | $in_range = 0; 75 | } 76 | $lastenc = $decenc; 77 | $missed++; 78 | } 79 | $cnt++; 80 | } 81 | if ($in_range) { 82 | printf("-U+%04x", $lastenc); 83 | } 84 | print " [$missed/$cnt]"; 85 | } 86 | 87 | sub parse_sfd_file($) { 88 | my ($sfd_file) = @_; 89 | 90 | open (F, $sfd_file) || die "Unable to open $sfd_file : $!\n"; 91 | my $curchar = ''; 92 | my $curenc = ''; 93 | my $empty = 0; 94 | while () { 95 | if (/^StartChar:\s*(\S+)\s*$/) { 96 | $curchar = $1; 97 | $curenc = ''; 98 | $empty = 0; 99 | } elsif (/^Colour:/) { 100 | # XXX this is quick'n'dirty hack to detect non-empty glyphs 101 | $empty = 1; 102 | } elsif (/^Encoding:\s*\d+\s*(\d+)\s*\d+\s*$/) { 103 | $curenc = $1; 104 | } elsif ($curenc && !$empty && /^EndChar\s*/) { 105 | if (defined $mes_glyphs{$curenc}) { 106 | $mes_glyphs{$curenc} = 0; 107 | } 108 | } 109 | } 110 | close (F); 111 | } 112 | 113 | if (@ARGV < 2) { 114 | print STDERR "usage: mes_spec_file sfd_files+\n"; 115 | exit 1; 116 | } 117 | 118 | $mes_spec_file = shift @ARGV; 119 | parse_mes_spec_file($mes_spec_file); 120 | print "Missing glyphs from collection $mes_collection\n\n"; 121 | while (@ARGV) { 122 | $sfd_file = shift @ARGV; 123 | print $sfd_file, ':'; 124 | init_mes_glyphs(); 125 | parse_sfd_file($sfd_file); 126 | print_mes_glyphs(); 127 | print "\n"; 128 | } 129 | 130 | 1; 131 | -------------------------------------------------------------------------------- /scripts/narrow.pe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fontforge 2 | # $Id$ 3 | 4 | # script file for FontForge for font narrowing 5 | # usage: fontforge -script narrow.pe scale *.sfd 6 | # created files have suffix .narrow 7 | 8 | # Brutalist Condensed have scale 90 9 | 10 | if ($version < "20050209") 11 | Error("Your version of FontForge transforms glyphs w/o content"); 12 | endif 13 | if ($argc < 2) 14 | Error("usage: scale sfds...") 15 | endif 16 | scale = Strtol($argv[1]) 17 | i = 2 18 | while (i < $argc) 19 | Open($argv[i], 1) 20 | SelectAll() 21 | Scale(scale, 100, 0, 0) 22 | # Panose changes are valid only for Brutalist fonts (I guess) 23 | SetPanose(3, 6) 24 | new_fontname = $fontname 25 | old_familyname = $familyname 26 | j = Strstr(new_fontname, "-Roman") 27 | if (j > -1) 28 | new_fontname = Strsub(new_fontname, 0, j) 29 | endif 30 | j = Strstr(new_fontname, "-") 31 | if (j > -1) 32 | new_fontname = Strsub(new_fontname, 0, j) + "Condensed" + Strsub(new_fontname, j) 33 | else 34 | new_fontname = new_fontname + "Condensed" 35 | endif 36 | new_fullname = $fullname 37 | newprefsubfamily = "Condensed" 38 | new_subfamily = $weight 39 | j = Strstr(new_fullname, "Bold") 40 | if (j == -1) 41 | j = Strstr(new_fullname, "Oblique") 42 | endif 43 | if (j == -1) 44 | j = Strstr(new_fullname, "Italic") 45 | endif 46 | if (j > -1) 47 | new_subfamily = Strsub(new_fullname, j) 48 | new_fullname = Strsub(new_fullname, 0, j) + "Condensed " + new_subfamily 49 | newprefsubfamily = newprefsubfamily + " " + new_subfamily 50 | else 51 | new_fullname = new_fullname + " Condensed" 52 | endif 53 | new_familyname = $familyname + " Condensed" 54 | SetFontNames(new_fontname, new_familyname, new_fullname) 55 | SetOS2Value("Width",4) 56 | SetTTFName(1033, 2, new_subfamily) # ttf_subfamily 57 | SetTTFName(1033, 3, new_fullname) # ttf_uniqueid 58 | SetTTFName(1033, 16, old_familyname) # ttf_preffamilyname 59 | SetTTFName(1033, 17, newprefsubfamily) # ttf_prefmodifiers 60 | Save($argv[i] + ".narrow") 61 | i++ 62 | endloop 63 | -------------------------------------------------------------------------------- /scripts/narrowmerge.pe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fontforge 2 | # $Id$ 3 | 4 | # script file for FontForge for font narrowing after merge 5 | # usage: fontforge -script narrowmerge.pe scale sfd glyph_codes* 6 | # created file has suffix .narrow 7 | 8 | # Brutalist Condensed have scale 90 9 | 10 | if ($version < "20050209") 11 | Error("Your version of FontForge transforms glyphs w/o content"); 12 | endif 13 | if ($argc < 3) 14 | Error("usage: scale sfd glyph_codes...") 15 | endif 16 | scale = Strtol($argv[1]) 17 | file = $argv[2] 18 | if ($argc < 4) 19 | Print("Nothing to do for ", file) 20 | Quit(0) 21 | endif 22 | Open(file, 1) 23 | i = 3 24 | while (i < $argc) 25 | Select(Strtol($argv[i])) 26 | ref_count = GlyphInfo("RefCount") 27 | if (ref_count == 0) 28 | Scale(scale, 100, 0, 0) 29 | else 30 | Print(GlyphInfo("Name"), " contains references: not scaled") 31 | endif 32 | i++ 33 | endloop 34 | Save(file + ".narrow") 35 | -------------------------------------------------------------------------------- /scripts/narrowmerge.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # $Id$ 4 | 5 | # This script will merge new glyphs into the condensed faces. 6 | 7 | # It scans for Condensed faces in the current directory and creates 8 | # new files called BrutalistCondensed*.sfd.merged 9 | 10 | # The script requires that all fonts are normalized with 11 | # sfdnormalize.pl. The newly created .merged file is automatically 12 | # normalized 13 | 14 | 15 | for tofile in *Condensed*.sfd; do 16 | fromfile=`echo $tofile | sed 's,Condensed,,'` 17 | 18 | echo "Merging: $fromfile" 19 | 20 | # making new narrow font 21 | ./narrow.pe 90 $fromfile 22 | 23 | # normalizing the new narrow font 24 | ./sfdnormalize.pl $fromfile.narrow 25 | 26 | # merging the new normalized narrow font into the existing Condensed font 27 | mergelist=`./merge.pl $fromfile.narrow.norm $tofile $tofile.merged` 28 | 29 | # normalizing the new merged font 30 | ./sfdnormalize.pl $tofile.merged 31 | 32 | # removing files that aren't needed anymore 33 | if [ -e $fromfile.narrow ]; then 34 | rm $fromfile.narrow 35 | fi 36 | if [ -e $fromfile.narrow.norm ]; then 37 | rm $fromfile.narrow.norm 38 | fi 39 | if [ -e $tofile.merged ]; then 40 | rm $tofile.merged 41 | fi 42 | 43 | # renaming normalized file 44 | if [ -e $tofile.merged.norm ]; then 45 | mv $tofile.merged.norm $tofile.merged 46 | fi 47 | 48 | done 49 | -------------------------------------------------------------------------------- /scripts/problems.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | 4 | # $Id$ 5 | 6 | # possible problems finder 7 | # (c)2004,2005 Stepan Roh (PUBLIC DOMAIN) 8 | # usage: ./problems.pl [-l <0|1|2|3>] sfd_files+ 9 | 10 | # detected problems (higher levels contain lower levels): 11 | # level 0: 12 | # monospaced font (with Mono in name) without indication in Panose (and vice-versa) 13 | # glyphs in monospaced face with different width 14 | # not normalized file (looks for WinInfo, DisplaySize, HStem, VStem, Ref, KernsSLIF, different position than encoding, 15 | # unordered glyphs or H or M flag) 16 | # glyphs without width or with negative width 17 | # duplicate glyphs 18 | # combining marks with non-zero width in non-monospaced fonts 19 | # missing point numbers in splines 20 | # level 1 (default): 21 | # colorized glyphs with content 22 | # level 2: 23 | # different set of mapped content glyphs (first SFD file specified on command line is taken as an etalon) 24 | # level 3: 25 | # ligature referencing colorized or missing glyphs 26 | # ligature in colorized glyph (due to bug in FF <20050502 it causes problems on Mac OS X) 27 | # ligature in empty glyph 28 | 29 | sub process_sfd_file($$); 30 | 31 | # glyph name => ( 'dec_enc' => dec_enc, 'hex_enc' => hex_enc ) 32 | %glyphs = (); 33 | $glyphs_loaded = 0; 34 | %problems_counter = (); 35 | 36 | sub process_sfd_file($$) { 37 | local ($sfd_file, $max_level) = @_; 38 | 39 | sub problem($@) { 40 | my ($level, $problem, @args) = @_; 41 | 42 | if ($level <= $max_level) { 43 | print $sfd_file, ': [', $level, '] ', $problem, ': ', @args, "\n"; 44 | $problems_counter{'['.$level.'] '.$problem}++; 45 | } 46 | } 47 | 48 | sub is_combining($) { 49 | my ($dec_enc) = @_; 50 | 51 | return (($dec_enc >= 0x0300) && ($dec_enc <= 0x036F)) 52 | || (($dec_enc >= 0x1DC0) && ($dec_enc <= 0x1DFF)) 53 | || (($dec_enc >= 0x20D0) && ($dec_enc <= 0x20FF)) 54 | || (($dec_enc >= 0xFE20) && ($dec_enc <= 0xFE2F)); 55 | } 56 | 57 | my $curchar = ''; 58 | my $hex_enc = ''; 59 | my $dec_enc = 0; 60 | my $colorized; 61 | my $flags; 62 | my ($fontname, $panose, $is_mono_name, $is_mono_panose) = ('', '', 0, 0); 63 | my $is_mono = 0; 64 | my $font_width = -1; 65 | my $curwidth = 0; 66 | my $has_ligature = 0; 67 | my $is_empty = 1; 68 | my %content_glyphs = (); 69 | my @ligature_refs = (); 70 | my %all_glyphs = (); 71 | my $prev_enc = -1; 72 | my $in_spline_set = 0; 73 | my $has_splines; 74 | my $has_refs; 75 | my %all_names = (); 76 | open (SFD, $sfd_file) || die "Unable to open $sfd_file : $!\n"; 77 | while () { 78 | if (/^StartChar:\s*(\S+)\s*$/) { 79 | $curchar = $1; 80 | if (exists($all_names{$curchar})) { 81 | problem (0, 'duplicate glyph name', $1); 82 | } 83 | $all_names{$curchar} = 1; 84 | $hex_enc = ''; 85 | $dec_enc = 0; 86 | $curwidth = -1; 87 | undef $colorized; 88 | undef $flags; 89 | $has_ligature = 0; 90 | @ligature_refs = (); 91 | $is_empty = 1; 92 | $in_spline_set = 0; 93 | $has_splines = 0; 94 | $has_refs = 0; 95 | } elsif (/^Colour:\s*(\S+)\s*/) { 96 | $colorized = $1; 97 | } elsif (/^Flags:\s*(\S+)\s*/) { 98 | $flags = $1; 99 | if ($flags =~ /([MH])/) { 100 | problem (0, 'not normalized: '.$1.' flag', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : ''), ': flags=', $flags); 101 | } 102 | } elsif (/^Encoding:\s*(\d+)\s*((?:-|\d)+)\s*(\d+)\s*$/) { 103 | $dec_enc = $1; 104 | if ($2 > -1) { 105 | $hex_enc = sprintf ('%04x', $2); 106 | } 107 | if ($dec_enc != $3) { 108 | problem (0, 'not normalized: glyph position differs from encoding', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : ''), ': position=', $3); 109 | } 110 | if ($dec_enc <= $prev_enc) { 111 | problem (0, 'not normalized: unordered glyphs', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : ''), ': previous=', $prev_enc); 112 | } 113 | $prev_enc = $dec_enc; 114 | } elsif (/^Width:\s*(\S+)\s*/) { 115 | $curwidth = $1; 116 | } elsif (/^Ligature:\s*\S*\s*\S*\s*\S*\s*(.*?)\s*$/) { 117 | @ligature_refs = split(/\s+/, $1); 118 | $has_ligature = 1; 119 | } elsif (/^SplineSet\s*$/) { 120 | $is_empty = 0; 121 | $in_spline_set = 1; 122 | problem (0, 'mixed content', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')) if ($has_refs); 123 | $has_splines = 1; 124 | } elsif (/^EndSplineSet\s*$/) { 125 | $in_spline_set = 0; 126 | } elsif ($in_spline_set && !/.+,.+,.+$/) { 127 | problem (0, 'point numbers missing', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')); 128 | } elsif (/^Ref:/) { 129 | $is_empty = 0; 130 | problem (0, 'not normalized: old-style "Ref"', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')); 131 | } elsif (/^Refer:\s*\d+\s*\d+\s*\S\s*(-?\d+(?:\.\d*)?(?:e-?\d+)?)\s*(-?\d+(?:\.\d*)?(?:e-?\d+)?)\s*(-?\d+(?:\.\d*)?(?:e-?\d+)?)\s*(-?\d+(?:\.\d*)?(?:e-?\d+)?)\s*-?\d+(?:\.\d*)?(?:e-?\d+)?\s*-?\d+(?:\.\d*)?(?:e-?\d+)?\s*(\d+)/) { 132 | $is_empty = 0; 133 | if (($5 & 0x2) != 0x2) { 134 | problem (0, 'reference is not rounded to grid', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : ''), ' flags=', $5); 135 | } 136 | if (!(($1 == 1) && ($2 == 0) && ($3 == 0) && ($4 == 1))) { 137 | problem (0, 'transformed reference', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')); 138 | } 139 | problem (0, 'mixed content', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')) if ($has_splines); 140 | $has_refs = 1; 141 | } elsif (/^DisplaySize:/) { 142 | problem (0, 'not normalized: DisplaySize'); 143 | } elsif (/^WinInfo:/) { 144 | problem (0, 'not normalized: WinInfo'); 145 | } elsif (/^HStem:/) { 146 | problem (0, 'not normalized: HStem'); 147 | } elsif (/^VStem:/) { 148 | problem (0, 'not normalized: VStem'); 149 | } elsif (/^KernsSLIF:/) { 150 | problem (0, 'not normalized: KernsSLIF'); 151 | } elsif (/^EndChar\s*$/) { 152 | if (!defined $colorized && !$is_empty) { 153 | $content_glyphs{$curchar}{'dec_enc'} = $dec_enc; 154 | $content_glyphs{$curchar}{'hex_enc'} = $hex_enc; 155 | @{$content_glyphs{$curchar}{'ligature'}} = @ligature_refs; 156 | # only mapped glyphs 157 | if ($hex_enc) { 158 | if ($glyphs_loaded) { 159 | if (!exists $glyphs{$curchar}) { 160 | problem (2, 'etalon-free glyph', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')); 161 | } 162 | } else { 163 | $glyphs{$curchar}{'dec_enc'} = $dec_enc; 164 | $glyphs{$curchar}{'hex_enc'} = $hex_enc; 165 | } 166 | } 167 | } 168 | if (defined $colorized && !$is_empty) { 169 | problem (1, 'colorized content', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '') , ': color=', $colorized); 170 | } 171 | if (defined $colorized && defined $flags && ($flags =~ /W/)) { 172 | problem (1, 'colorized content', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '') , ': color=', $colorized, ', flags=', $flags); 173 | } 174 | if (!$is_mono && defined $colorized && ($curwidth != 2048) && ($curwidth != 0)) { 175 | problem (1, 'colorized content', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '') , ': color=', $colorized, ', width=', $curwidth); 176 | } 177 | if ($curwidth == -1) { 178 | problem (0, 'glyph w/o width', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')); 179 | } elsif ($curwidth < 0) { 180 | problem (0, 'negative width', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : ''), ': width=', $curwidth); 181 | } elsif ($is_mono && defined $flags && ($flags =~ /W/)) { 182 | if ($font_width == -1) { 183 | $font_width = $curwidth; 184 | } elsif ($curwidth != $font_width) { 185 | problem (0, 'incorrect width', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : ''), ': font width=', $font_width, ', glyph width=', $curwidth); 186 | } 187 | } elsif (!$is_mono && is_combining($dec_enc) && ($curwidth != 0) && !$is_empty) { 188 | problem (0, 'combining mark with non-zero width', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : ''), ': width=', $curwidth); 189 | } 190 | if (defined $colorized && $has_ligature) { 191 | problem (3, 'colorized ligature', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : ''), ': color=', $colorized); 192 | } 193 | if ($is_empty && $has_ligature) { 194 | problem (3, 'empty ligature', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')); 195 | } 196 | if (exists $all_glyphs{$dec_enc}) { 197 | problem (0, 'duplicate', $curchar, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')); 198 | } 199 | $all_glyphs{$dec_enc} = 1; 200 | } elsif (/^FontName:\s*(.*?)\s*$/) { 201 | $fontname = $1; 202 | $is_mono_name = ($fontname =~ /mono/i); 203 | $is_mono = 1 if ($is_mono_name); 204 | } elsif (/^Panose:\s*(.*?)\s*$/) { 205 | $panose = $1; 206 | $is_mono_panose = ((split(/\s+/, $panose))[3] == 9); 207 | $is_mono = 1 if ($is_mono_panose); 208 | } 209 | } 210 | close (SFD); 211 | if ($is_mono_name != $is_mono_panose) { 212 | problem (0, 'mixed monospace', 'font name=', $fontname, ', panose=', $panose); 213 | } 214 | foreach $glyph (sort { $content_glyphs{$a}{'dec_enc'} <=> $content_glyphs{$b}{'dec_enc'} } keys %content_glyphs) { 215 | my $dec_enc = $content_glyphs{$glyph}{'dec_enc'}; 216 | my $hex_enc = $content_glyphs{$glyph}{'hex_enc'}; 217 | foreach $liga (@{$content_glyphs{$glyph}{'ligature'}}) { 218 | if (!exists ($content_glyphs{$liga})) { 219 | problem (3, 'ligature references colorized or missing glyph', $glyph, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : ''), ': ligature ref=', $liga); 220 | } 221 | } 222 | } 223 | if ($glyphs_loaded) { 224 | foreach $glyph (sort { $glyphs{$a}{'dec_enc'} <=> $glyphs{$b}{'dec_enc'} } keys %glyphs) { 225 | my $dec_enc = $glyphs{$glyph}{'dec_enc'}; 226 | my $hex_enc = $glyphs{$glyph}{'hex_enc'}; 227 | if (!exists $content_glyphs{$glyph}) { 228 | problem (2, 'missing glyph', $glyph, ' ', $dec_enc, ($hex_enc ? ' U+'.$hex_enc : '')); 229 | } 230 | } 231 | } 232 | $glyphs_loaded = 1; 233 | } 234 | 235 | if (!@ARGV) { 236 | print STDERR "usage: [-l <0|1|2|3>] sfd_files+\n"; 237 | exit 1; 238 | } 239 | 240 | $max_level = 1; 241 | if ($ARGV[0] eq '-l') { 242 | shift @ARGV; 243 | $max_level = shift @ARGV; 244 | } 245 | @sfd_files = @ARGV; 246 | 247 | foreach $sfd_file (@sfd_files) { 248 | process_sfd_file ($sfd_file, $max_level); 249 | } 250 | 251 | foreach $problem (sort keys %problems_counter) { 252 | print $problems_counter{$problem}, ' problems of type "', $problem, '"', "\n"; 253 | } 254 | 255 | 1; 256 | -------------------------------------------------------------------------------- /scripts/samples.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # Author Євгеній Мещеряков 4 | # This file is in public domain 5 | 6 | set -e 7 | 8 | if [ $# != 2 ] 9 | then 10 | echo "Usage: $0 " 11 | exit 1 12 | fi 13 | 14 | new_dir="$1" 15 | old_dir="$2" 16 | tmp_dir=`mktemp -dt gensamples.XXXXXXXXXX` 17 | 18 | for file in $new_dir/*.ttf 19 | do 20 | base_name=`basename $file .ttf` 21 | fntsample -f $file -o $tmp_dir/$base_name.pdf -d $old_dir/$base_name.ttf -l > $tmp_dir/$base_name.txt 22 | pdfoutline $tmp_dir/$base_name.pdf $tmp_dir/$base_name.txt $base_name.pdf 23 | done 24 | 25 | rm -rf $tmp_dir 26 | -------------------------------------------------------------------------------- /scripts/sfdnormalize.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | 4 | # $Id$ 5 | 6 | # SFD normalizer (discards GUI information from SFD files) 7 | # (c)2004,2005 Stepan Roh (PUBLIC DOMAIN) 8 | # usage: ./sfdnormalize.pl sfd_file(s) 9 | # will create files with suffix .norm 10 | 11 | # changes done: 12 | # WinInfo - discarded 13 | # DisplaySize 14 | # - discarded 15 | # Flags - discarded O (open), H (changed since last hinting - useless for TTF), M (manual hinting - useless for TTF) 16 | # Refer - changed S (selected) to N (not selected) 17 | # Fore, Back, SplineSet, Grid 18 | # - all points have 4 masked out from flags (selected) 19 | # and 256 (do not interpolate) 20 | # HStem, VStem 21 | # - discarded (those are Type1 stems = PS hints) 22 | # recalculate number of characters and positional encoding 23 | # NameList 24 | # - discarded (temporary) 25 | # ModificationTime - discarded 26 | # CreationTime - discarded 27 | # VWidth - discarded (for now) 28 | # TeXData - discarded 29 | # TeX - discarded 30 | # Validated - discarded 31 | # UndoRedoHistory - discarded 32 | # changes making it incompatible with FF older than (approx.) 20050728: 33 | # Ref - renamed to Refer 34 | # KernsSLIF 35 | # - renamed to KernsSLIFO 36 | 37 | # !!! Always review changes done by this utility !!! 38 | 39 | sub process_sfd_file($); 40 | 41 | sub process_sfd_file($) { 42 | my ($sfd_file) = @_; 43 | 44 | my $out = $sfd_file . '.norm'; 45 | 46 | open (SFD, $sfd_file) || die "Unable to open $sfd_file : $!\n"; 47 | open (OUT, '>'.$out) || die "Unable to open $out : $!\n"; 48 | 49 | my $curchar = ''; 50 | my %glyphs = (); 51 | my $in_spline_set = 0; 52 | my $max_dec_enc = 0; 53 | my %pos_glyphs_map = (); 54 | my $in_undo_redo = 0; 55 | 56 | while () { 57 | next if (/^(WinInfo|DisplaySize|HStem|VStem|ModificationTime|CreationTime|VWidth|TeX|TeXData|Validated|AltUni2):/); 58 | next if (/^$/); 59 | s,^(NameList:).*$,$1 AGL without afii,; 60 | s,^Ref:,Refer:,; 61 | s,^KernsSLIF:,KernsSLIFO:,; 62 | s,^(Flags:.*?)O(.*)$,$1$2,; 63 | s,^(Flags:.*?)H(.*)$,$1$2,; 64 | s,^(Flags:.*?)M(.*)$,$1$2,; 65 | # remove empty Flags line 66 | next if (/^Flags:\s*$/); 67 | s,^(Refer:.*?)S(.*)$,$1N$2,; 68 | if (/^(Fore|Back|SplineSet|Grid)\s*$/) { 69 | $in_spline_set = 1; 70 | } elsif (/^EndSplineSet\s*$/) { 71 | $in_spline_set = 0; 72 | } elsif ($in_spline_set) { 73 | s/(\s+)(\S+?)(,\S+\s*)$/$1.($2 & ~4 & ~256).$3/e; 74 | s/([\s^])-0(\s)/$1."0".$2/eg 75 | } 76 | #remove UndoRedoHistory block 77 | if (/^UndoRedoHistory/) { 78 | $in_undo_redo = 1; next 79 | } elsif (/^EndUndoRedoHistory/) { 80 | $in_undo_redo = 0; next 81 | } 82 | next if ($in_undo_redo); 83 | if (/^BeginChars:/) { 84 | $in_chars = 1; 85 | } elsif (/^EndChars\s*$/) { 86 | $in_chars = 0; 87 | # adding of 1 to max_dec_enc is strange, but works 88 | # second parameter is set to 0 to avoid merging conflicts (fontforge ignores anyway) 89 | print OUT "BeginChars: ", $max_dec_enc + 1, " 0\n"; 90 | foreach $glyph (sort { $glyphs{$a}{'dec_enc'} <=> $glyphs{$b}{'dec_enc'} } keys %glyphs) { 91 | print OUT "StartChar: ", $glyphs{$glyph}{'name'}, "\n"; 92 | my $dec_enc = $glyphs{$glyph}{'dec_enc'}; 93 | my $mapped_enc = $glyphs{$glyph}{'mapped_enc'}; 94 | print OUT "Encoding: ", $dec_enc, " ", $mapped_enc, " ", $dec_enc, "\n"; 95 | # recalculate references and kerning pairs 96 | foreach $l (@{$glyphs{$glyph}{'lines'}}) { 97 | $l =~ s/^(Refer:\s*)(\S+)/$1.(exists $pos_glyphs_map{$2} ? $pos_glyphs_map{$2} : (warn "Glyph $glyph ($dec_enc) has reference to unknown glyph position $2\n", $2))/e; 98 | if ($l =~ /^KernsSLIFO:\s*(.*)$/) { 99 | my @nums = split(/\s+/, $1); 100 | if ((scalar (@nums) % 4) != 0) { 101 | warn "Kerning definition in glyph $curchar ($dec_enc) is malformed and won't be remapped\n"; 102 | } else { 103 | for (my $i = 0; $i < scalar (@nums); $i += 4) { 104 | my $pos = $nums[$i]; 105 | if (exists $pos_glyphs_map{$pos}) { 106 | $nums[$i] = $pos_glyphs_map{$pos}; 107 | } else { 108 | warn "Glyph $glyph ($dec_enc) has kerning reference to unknown glyph position $pos\n"; 109 | } 110 | } 111 | } 112 | $l = "KernsSLIFO: " . join(' ', @nums) . "\n"; 113 | } 114 | print OUT $l; 115 | } 116 | print OUT "EndChar\n"; 117 | $pos++; 118 | } 119 | print OUT "EndChars\n"; 120 | } elsif (/^StartChar:\s*(\S+)\s*$/) { 121 | my $name = $1; 122 | $curchar = $name; 123 | while (exists $glyphs{$curchar}) { 124 | $curchar .= '#'; 125 | } 126 | $glyphs{$curchar}{'name'} = $name; 127 | } elsif (/^Encoding:\s*(\d+)\s*((?:-|\d)+)\s*(\d+)\s*$/) { 128 | $dec_enc = $1; 129 | $max_dec_enc = $dec_enc if ($dec_enc > $max_dec_enc); 130 | $mapped_enc = $2; 131 | $pos = $3; 132 | $glyphs{$curchar}{'dec_enc'} = $dec_enc; 133 | $glyphs{$curchar}{'mapped_enc'} = $mapped_enc; 134 | $glyphs{$curchar}{'pos'} = $pos; 135 | if (exists $pos_glyphs_map{$pos}) { 136 | warn "Glyph $curchar ($dec_enc) has duplicate glyph position $pos - won't be remapped - possible output corruption!\n"; 137 | } else { 138 | $pos_glyphs_map{$pos} = $dec_enc; 139 | } 140 | } elsif (/^EndChar\s*$/) { 141 | $curchar = ''; 142 | } else { 143 | if (!$in_chars) { 144 | print OUT; 145 | } elsif ($curchar eq '') { 146 | warn "Malformed input file $sfd_file?"; 147 | } else { 148 | push (@{$glyphs{$curchar}{'lines'}}, $_); 149 | } 150 | } 151 | } 152 | 153 | close (SFD); 154 | close (OUT); 155 | } 156 | 157 | if (!@ARGV) { 158 | print STDERR "usage: sfd_files+\n"; 159 | exit 1; 160 | } 161 | 162 | @sfd_files = @ARGV; 163 | 164 | foreach $sfd_file (@sfd_files) { 165 | process_sfd_file ($sfd_file); 166 | } 167 | 168 | 1; 169 | -------------------------------------------------------------------------------- /scripts/stats.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # $Id$ 4 | 5 | # Brutalist fonts statistics generator 6 | # (c)2005 Stepan Roh (PUBLIC DOMAIN) 7 | # usage: ./stats.sh 8 | 9 | # Motto: "To proof that each task can be coded in very complicated way." 10 | 11 | echo "Version New glyphs*)" 12 | echo "-------- ----------" 13 | versions=`grep 'U+' status.txt \ 14 | | tr -s ' ' \ 15 | | cut -d' ' -f 3 \ 16 | | sed 's,original,0.0,' \ 17 | | sed 's,^\(.\)\.\(.\)$,\1.0\2,' \ 18 | | sort \ 19 | | uniq \ 20 | | sed 's,^\(.\).0\(.\)$,\1.\2,' \ 21 | | sed 's,0\.0,original,'` 22 | for ver in $versions; do 23 | sver=`echo $ver | sed 's,\.,\\\\.,'` 24 | count=`grep "^U+.*$sver\( \|$\)" status.txt | wc -l` 25 | printf '%-8s %10i\n' $ver $count 26 | done 27 | echo 28 | echo "*) some glyphs may be counted multiple times if they were added to different faces in different versions" 29 | -------------------------------------------------------------------------------- /scripts/status.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | 4 | # $Id$ 5 | 6 | # status.txt file generator 7 | # (c)2004 Stepan Roh (PUBLIC DOMAIN) 8 | # usage: ./status.pl version_tag status_file sfd_files+ 9 | # will print new status file on standard output 10 | 11 | sub parse_status_file(\%$); 12 | sub parse_sfd_file(\%$$); 13 | sub print_status_file(\%); 14 | sub parse_versions($); 15 | sub format_versions(\%); 16 | sub normalize_version($); 17 | sub denormalize_version($); 18 | sub detect_full_typefaces_support(@); 19 | 20 | # internal parsed format: 21 | # _ => ( header lines* ) 22 | # hexadecimal character encoding => ( 'name' => name, 'versions' => ( version => ( typefaces* ) ) ) 23 | 24 | $debug = 0; 25 | 26 | $name_width = 20; 27 | 28 | if ($debug) { 29 | use Data::Dumper; 30 | 31 | $Data::Dumper::Indent = 1; 32 | $Data::Dumper::Sortkeys = 1; 33 | $Data::Dumper::Purity = 1; 34 | } 35 | 36 | %parsed_typefaces = (); 37 | 38 | sub normalize_version($) { 39 | my ($version) = @_; 40 | 41 | if ($version eq 'original') { 42 | $version = '0.0'; 43 | } 44 | return join ('_', map { sprintf ("%02s", $_) } split (/\./, $version)); 45 | } 46 | 47 | sub denormalize_version($) { 48 | my ($version) = @_; 49 | 50 | $version = join ('.', map { $_ + 0 } split (/_/, $version)); 51 | if ($version eq '0.0') { 52 | $version = 'original'; 53 | } 54 | return $version; 55 | } 56 | 57 | sub parse_versions($) { 58 | my ($versions) = @_; 59 | 60 | my %ret = (); 61 | while ($versions =~ s,^\s*(\S+)\s*(?:\((.*?)\)|),,) { 62 | my ($version, $typefaces) = ($1, $2); 63 | $version = normalize_version($version); 64 | if ($typefaces) { 65 | my @typefaces = split (/\s*,\s*/, $typefaces); 66 | $ret{$version} = \@typefaces; 67 | } else { 68 | $ret{$version} = []; 69 | } 70 | } 71 | return %ret; 72 | } 73 | 74 | sub parse_status_file(\%$) { 75 | my ($parsed_ref, $status_file) = @_; 76 | 77 | open (STATUS, $status_file) || die "Unable to open $status_file : $!\n"; 78 | while () { 79 | if (/^U+/) { 80 | my ($hex_enc, $name, $versions) = ($_ =~ /^U\+(\S+)\s+(\S+)\s+(.*?)\s*$/); 81 | my %versions = parse_versions ($versions); 82 | $$parsed_ref{$hex_enc}{'name'} = $name; 83 | $$parsed_ref{$hex_enc}{'versions'} = \%versions; 84 | } else { 85 | push (@{$$parsed_ref{'_'}}, $_); 86 | } 87 | } 88 | close (STATUS); 89 | } 90 | 91 | sub parse_sfd_file(\%$$) { 92 | my ($parsed_ref, $version_tag, $sfd_file) = @_; 93 | 94 | open (SFD, $sfd_file) || die "Unable to open $sfd_file : $!\n"; 95 | my $typeface = ''; 96 | my $curchar = ''; 97 | my $hex_enc = ''; 98 | my $empty = 0; 99 | $version_tag = normalize_version($version_tag); 100 | while () { 101 | if (/^FullName:\s+\S+\s+(.*?)\s*$/) { 102 | # Brutalist is not included in typeface 103 | $typeface = $1; 104 | $parsed_typefaces{$typeface} = 1; 105 | } elsif (/^StartChar:\s*(\S+)\s*$/) { 106 | $curchar = $1; 107 | $hex_enc = ''; 108 | $empty = 0; 109 | } elsif (/^Colour:/) { 110 | # XXX this is quick'n'dirty hack to detect non-empty glyphs 111 | $empty = 1; 112 | } elsif (/^Encoding:\s*\d+\s*(\d+)\s*\d+\s*$/) { 113 | $hex_enc = sprintf ('%04x', $1); 114 | } elsif ($hex_enc && !$empty && /^EndChar\s*$/) { 115 | $$parsed_ref{$hex_enc}{'name'} = $curchar; 116 | push (@{$$parsed_ref{$hex_enc}{'versions'}{$version_tag}}, $typeface); 117 | } 118 | } 119 | close (SFD); 120 | } 121 | 122 | sub detect_full_typefaces_support(@) { 123 | my %typefaces = (); 124 | foreach $typeface (@_) { 125 | $typefaces{$typeface} = 1; 126 | } 127 | foreach $typeface (keys %parsed_typefaces) { 128 | return 0 if (!exists $typefaces{$typeface}); 129 | } 130 | return 1; 131 | } 132 | 133 | sub format_versions(\%) { 134 | my ($versions_ref) = @_; 135 | 136 | my @ret = (); 137 | my %done_typefaces = (); 138 | foreach $version (sort keys %{$versions_ref}) { 139 | my ($str) = denormalize_version ($version); 140 | my $do_last = 1; 141 | if (@{$$versions_ref{$version}}) { 142 | my @print_typefaces = (); 143 | foreach $typeface (@{$$versions_ref{$version}}) { 144 | if (!exists ($done_typefaces{$typeface})) { 145 | $done_typefaces{$typeface} = 1; 146 | push (@print_typefaces, $typeface); 147 | } 148 | } 149 | next if (!@print_typefaces); 150 | if (!detect_full_typefaces_support(@print_typefaces)) { 151 | $str .= ' (' . join (', ', sort @print_typefaces) . ')'; 152 | $do_last = 0; 153 | } 154 | } 155 | push (@ret, $str); 156 | last if ($do_last); 157 | } 158 | return join (' ', @ret); 159 | } 160 | 161 | sub print_status_file(\%) { 162 | my ($parsed_ref) = @_; 163 | 164 | print @{$$parsed_ref{'_'}}; 165 | delete $$parsed_ref{'_'}; 166 | foreach $hex_enc (sort {hex($a) <=> hex($b)} keys %{$parsed_ref}) { 167 | my ($versions) = format_versions (%{$$parsed_ref{$hex_enc}{'versions'}}); 168 | printf ('U+%s %-'.$name_width.'s %s'."\n", $hex_enc, $$parsed_ref{$hex_enc}{'name'}, $versions); 169 | } 170 | } 171 | 172 | if (@ARGV < 3) { 173 | print STDERR "usage: version_tag status_file sfd_files+\n"; 174 | exit 1; 175 | } 176 | 177 | $version_tag = shift @ARGV; 178 | $status_file = shift @ARGV; 179 | @sfd_files = @ARGV; 180 | 181 | %parsed = (); 182 | 183 | parse_status_file (%parsed, $status_file); 184 | foreach $sfd_file (@sfd_files) { 185 | parse_sfd_file (%parsed, $version_tag, $sfd_file); 186 | } 187 | 188 | if ($debug) { 189 | print STDERR Data::Dumper->Dump([\%parsed], ['*parsed']); 190 | print STDERR "Parsed typefaces: ", join (', ', @parsed_typefaces), "\n"; 191 | } 192 | 193 | print_status_file (%parsed); 194 | 195 | 1; 196 | -------------------------------------------------------------------------------- /scripts/strip_glyphs.pe: -------------------------------------------------------------------------------- 1 | #! /usr/bin/fontforge -script 2 | # 3 | # $Id$ 4 | # 5 | # "glyphs_to_remove" argument can be a single code point 6 | # or a range of codepoints separated by ":" 7 | # 8 | # code points can be specified both as integers and as unicode 9 | # you can also mix the two formats (i.e "48:u54") 10 | # 11 | # example: 12 | # 13 | # "strip_glyphs in.sfd out.sfd 12 u20 100:150 u200:u230 u300:65000" 14 | 15 | if ($argc < 4) 16 | Print( "Usage: strip_glyphs in out glyphs_to_remove..." ) 17 | Quit() 18 | endif 19 | 20 | 21 | Open($1); shift 22 | Reencode("unicode") 23 | out = $1; shift 24 | 25 | # 26 | # Loop through the arguments and select the glyphs 27 | # which need to be cleared 28 | # 29 | while ($argc > 1) 30 | len = Strlen($1) 31 | colon_idx = Strstr($1, ":") 32 | 33 | # 34 | # argument is a single glyph 35 | # 36 | if (colon_idx == -1) 37 | if(Strstr($1, "u") == -1) 38 | SelectMore(Strtol($1)) ## Integer 39 | else 40 | SelectMore($1) ## Unicode code point (i.e "u0027") 41 | endif 42 | 43 | # 44 | # argument is a range low:high 45 | # 46 | else 47 | low = Strsub($1, 0, colon_idx) 48 | if(Strstr($1, "u") == -1) 49 | low = Strtol(low) ## Integer 50 | endif 51 | 52 | high = Strsub($1, colon_idx+1, len) 53 | if(Strstr($1, "u") == -1) 54 | high = Strtol(high) ## Integer 55 | endif 56 | 57 | SelectMore(low, high); 58 | endif 59 | 60 | shift 61 | endloop 62 | 63 | Clear() 64 | 65 | Save(out) 66 | Quit() 67 | -------------------------------------------------------------------------------- /scripts/ttpostproc.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | # $Id$ 4 | 5 | # TTF postprocessing 6 | # usage: 7 | # chmod +x ttpostproc.pl 8 | # ./ttpostproc.pl generated/*.ttf 9 | 10 | use Font::TTF::Font; 11 | 12 | sub postproc($) { 13 | my ($file) = @_; 14 | my $backup = $file.'~'; 15 | 16 | rename($file, $backup) || die "Unable to rename $f as $backup : $!\n"; 17 | 18 | $font = Font::TTF::Font->open($backup) || die "Unable to open font $backup : $!\n"; 19 | 20 | # head.flags: 21 | # 1 => baseline for font at y=0 22 | # 2 => left sidebearing point at x=0 23 | # 4 => instructions may depend on point size 24 | # 8 => force ppem to integer values for all internal scaler math 25 | # 0x10 => instructions may alter advance width 26 | $font->{'head'}->{'flags'} = 1 + 2 + 4 + 8 + 0x10; 27 | 28 | # gasp table 29 | # version = 0 30 | # numRanges = 2 31 | # range[0].rangeMaxPPEM = 8 32 | # range[0].rangeGaspBehavior = 2 (GASP_DOGRAY) 33 | # range[1].rangeMaxPPEM = 65535 (max.) 34 | # range[1].rangeGaspBehavior = 3 (GASP_DOGRAY|GASP_GRIDFIT) 35 | $gasp_data = pack('nnnnnn', 0, 2, 8, 2, 0xffff, 3); 36 | $font->{'gasp'} = Font::TTF::Table->new(( 'dat' => $gasp_data )); 37 | 38 | $font->out($file) || die "Unable to write to $file : $!\n"; 39 | $font->release(); 40 | } 41 | 42 | @files = @ARGV; 43 | foreach $f (@files) { 44 | postproc($f); 45 | } 46 | 47 | 1; 48 | -------------------------------------------------------------------------------- /scripts/unhinted.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | 4 | # $Id$ 5 | 6 | # output (TT-)unhinted glyphs 7 | # (c)2005 Stepan Roh (PUBLIC DOMAIN) 8 | # usage: ./unhinted.pl [-v] [-c] sfd_files+ 9 | 10 | sub parse_sfd_file($$$); 11 | 12 | sub parse_sfd_file($$$) { 13 | my ($sfd_file, $verbose, $composites) = @_; 14 | 15 | open (SFD, $sfd_file) || die "Unable to open $sfd_file : $!\n"; 16 | print $sfd_file, ': '; 17 | my $typeface = ''; 18 | my $curchar = ''; 19 | my $hex_enc = ''; 20 | my $empty = 0; 21 | my $hinted = 0; 22 | my $total = 0; 23 | my $unhinted = 0; 24 | my $experimental = 0; 25 | my @unhinted = (); 26 | my $contours = 0; 27 | while () { 28 | if (/^FullName:\s+\S+\s+(.*?)\s*$/) { 29 | $typeface = $1; 30 | $experimental = ($typeface =~ /Condensed|(Serif.*Oblique)/); 31 | } elsif (/^StartChar:\s*(\S+)\s*$/) { 32 | $curchar = $1; 33 | $hex_enc = ''; 34 | $empty = 0; 35 | $hinted = 0; 36 | $contours = 0; 37 | } elsif (/^TtfInstrs:/) { 38 | $hinted = 1; 39 | } elsif (/^Colour:/) { 40 | # XXX this is quick'n'dirty hack to detect non-empty glyphs 41 | $empty = 1; 42 | } elsif (/^Fore$/) { 43 | $contours = 1; 44 | } elsif (/^Encoding:\s*\d+\s*(\d+)\s*\d+\s*$/) { 45 | $hex_enc = sprintf ('%04X', $1); 46 | } elsif ($hex_enc && !$empty && /^EndChar\s*$/) { 47 | $total++; 48 | if (($composites || $contours) && !$hinted) { 49 | $unhinted++; 50 | push (@unhinted, $curchar . ' (U+' . $hex_enc . ')'); 51 | } 52 | } 53 | } 54 | print "[experimental] " if ($experimental); 55 | printf "%.0d%% (%d/%d)", $unhinted / $total * 100, $unhinted, $total; 56 | print "\n"; 57 | print ' ', join (', ', @unhinted), "\n" if ($verbose); 58 | close (SFD); 59 | } 60 | 61 | if (@ARGV < 1) { 62 | print STDERR "usage: [-v] [-c] sfd_files+\n"; 63 | exit 1; 64 | } 65 | 66 | while ($ARGV[0] =~ /^-/) { 67 | if ($ARGV[0] eq '-v') { 68 | $verbose = 1; 69 | } elsif ($ARGV[0] eq '-c') { 70 | $composites = 1; 71 | } else { 72 | last; 73 | } 74 | shift @ARGV; 75 | } 76 | @sfd_files = @ARGV; 77 | 78 | foreach $sfd_file (@sfd_files) { 79 | parse_sfd_file ($sfd_file, $verbose, $composites); 80 | } 81 | 82 | 1; 83 | -------------------------------------------------------------------------------- /scripts/unicover.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | 4 | # $Id$ 5 | 6 | # unicode coverage analyzator 7 | # (c)2004,2005 Stepan Roh (PUBLIC DOMAIN) 8 | # usage: ./unicover.pl unicode_data_file blocks_file sfd_file1 label1 (sfd_file2 label2...) 9 | # unicode data file can be downloaded from http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 10 | # blocks file can be downloaded from http://www.unicode.org/Public/UNIDATA/Blocks.txt 11 | 12 | sub parse_blocks_file($); 13 | sub parse_unicode_data_file($); 14 | sub parse_sfd_file($); 15 | sub inc_coverage($$); 16 | sub print_coverage(); 17 | sub disable_char($); 18 | 19 | $debug = 0; 20 | 21 | if ($debug) { 22 | use Data::Dumper; 23 | 24 | $Data::Dumper::Indent = 1; 25 | $Data::Dumper::Sortkeys = 1; 26 | $Data::Dumper::Purity = 1; 27 | } 28 | 29 | # map (start dec => ( 'name' => block name, 'end' => end dec, 'coverage' => ( sfd_file => coverage ), 'disabled_count' => number of disabled glyphs ) 30 | %blocks = (); 31 | %chars = (); 32 | 33 | sub parse_blocks_file($) { 34 | my ($blocks_file) = @_; 35 | 36 | open (F, $blocks_file) || die "Unable to open $blocks_file : $!\n"; 37 | while () { 38 | next if (/^\s*(#|$)/); 39 | my ($start, $end, $name) = ($_ =~ /^(.*?)\.\.(.*?);\s*(.*?)\s*$/); 40 | $start = hex ($start); 41 | $end = hex ($end); 42 | $blocks{$start}{'name'} = $name; 43 | $blocks{$start}{'end'} = $end; 44 | } 45 | close (F); 46 | } 47 | 48 | sub disable_char($) { 49 | my ($dec_enc) = @_; 50 | 51 | foreach $block_start (keys %blocks) { 52 | my ($block_end) = $blocks{$block_start}{'end'}; 53 | if (($dec_enc >= $block_start) && ($dec_enc <= $block_end)) { 54 | $blocks{$block_start}{'disabled_count'}++; 55 | last; 56 | } 57 | } 58 | } 59 | 60 | sub disable_char_range($$) { 61 | my ($range_start, $range_end) = @_; 62 | 63 | my $cur_enc = $range_start; 64 | while ($cur_enc <= $range_end) { 65 | my $cur_block_start = -1; 66 | foreach $block_start (keys %blocks) { 67 | my ($block_end) = $blocks{$block_start}{'end'}; 68 | if (($cur_enc >= $block_start) && ($cur_enc <= $block_end)) { 69 | $cur_block_start = $block_start; 70 | last; 71 | } 72 | } 73 | return if ($cur_block_start == -1); 74 | while (($cur_enc <= $range_end) && ($cur_enc <= $blocks{$cur_block_start}{'end'})) { 75 | $blocks{$cur_block_start}{'disabled_count'}++; 76 | $cur_enc++; 77 | } 78 | } 79 | } 80 | 81 | sub parse_unicode_data_file($) { 82 | my ($ud_file) = @_; 83 | 84 | open (F, $ud_file) || die "Unable to open $ud_file : $!\n"; 85 | my $prev_enc = -1; 86 | while () { 87 | next if (/^\s*(#|$)/); 88 | my ($enc, $name) = split (/;/); 89 | $enc = hex ($enc); 90 | if ($prev_enc + 1 < $enc) { 91 | disable_char_range ($prev_enc + 1, $enc - 1); 92 | } 93 | disable_char ($enc) if ($name =~ /^ $last_enc); 102 | } 103 | if ($prev_enc + 1 <= $last_enc) { 104 | disable_char_range ($prev_enc + 1, $last_enc); 105 | } 106 | close (F); 107 | } 108 | 109 | sub inc_coverage($$) { 110 | my ($sfd_file, $dec_enc) = @_; 111 | 112 | foreach $block_start (keys %blocks) { 113 | my ($block_end) = $blocks{$block_start}{'end'}; 114 | if (($dec_enc >= $block_start) && ($dec_enc <= $block_end)) { 115 | if (exists $chars{$dec_enc}) { 116 | $blocks{$block_start}{'coverage'}{$sfd_file}++; 117 | } 118 | last; 119 | } 120 | } 121 | } 122 | 123 | sub parse_sfd_file($) { 124 | my ($sfd_file) = @_; 125 | 126 | open (F, $sfd_file) || die "Unable to open $sfd_file : $!\n"; 127 | my $curchar = ''; 128 | my $curenc = ''; 129 | my $empty = 0; 130 | while () { 131 | if (/^StartChar:\s*(\S+)\s*$/) { 132 | $curchar = $1; 133 | $curenc = ''; 134 | $empty = 0; 135 | } elsif (/^Colour:/) { 136 | # XXX this is quick'n'dirty hack to detect non-empty glyphs 137 | $empty = 1; 138 | } elsif (/^Encoding:\s*\d+\s*(\d+)\s*\d+\s*$/) { 139 | $curenc = $1; 140 | } elsif ($curenc && !$empty && /^EndChar\s*/) { 141 | inc_coverage ($sfd_file, $curenc); 142 | } 143 | } 144 | close (F); 145 | } 146 | 147 | # TODO: formats would be better 148 | sub print_coverage() { 149 | print < $b } keys %blocks) { 163 | my ($block_end) = $blocks{$block_start}{'end'}; 164 | my ($name) = $blocks{$block_start}{'name'}; 165 | my ($disabled) = $blocks{$block_start}{'disabled_count'}; 166 | $disabled = 0 if (!defined $disabled); 167 | my ($length) = $block_end - $block_start + 1 - $disabled; 168 | printf "U+%04x %-40s", $block_start, $name; 169 | foreach $sfd_file (@sfd_files) { 170 | my ($coverage) = $blocks{$block_start}{'coverage'}{$sfd_file}; 171 | $coverage = 0 if (!defined $coverage); 172 | my ($percent) = ($length != 0) ? ($coverage/$length * 100) : 0; 173 | if ($percent > 0) { 174 | printf " %3d%%", $percent; 175 | } else { 176 | print " "; 177 | } 178 | printf " %-13s", "($coverage/$length)"; 179 | } 180 | print "\n"; 181 | } 182 | } 183 | 184 | if (@ARGV < 3) { 185 | print STDERR "usage: unicode_data_file blocks_file sfd_file1 label1 (sfd_file2 label2...)\n"; 186 | exit 1; 187 | } 188 | 189 | $unicode_data_file = shift @ARGV; 190 | $blocks_file = shift @ARGV; 191 | @sfd_files = (); 192 | %sfd_files = (); 193 | while (@ARGV) { 194 | $sfd_file = shift @ARGV; 195 | $label = shift @ARGV; 196 | push (@sfd_files, $sfd_file); 197 | $sfd_files{$sfd_file} = $label; 198 | } 199 | 200 | parse_blocks_file($blocks_file); 201 | parse_unicode_data_file($unicode_data_file); 202 | foreach $sfd_file (@sfd_files) { 203 | parse_sfd_file($sfd_file); 204 | } 205 | print_coverage(); 206 | 207 | if ($debug) { 208 | print STDERR Data::Dumper->Dump([\%blocks], ['*blocks']); 209 | } 210 | 211 | 1; 212 | -------------------------------------------------------------------------------- /scripts/wikidownloadtemplate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # call this script from the main directory like scripts/wikidownloadtemplate.sh 4 | 5 | # use the output of this script to update http://brutalist.sourceforge.net/wiki/index.php?title=Download 6 | 7 | 8 | # assumes that version in Makefile is correct (which it should be or something was done wrong) 9 | VERSION=`grep "VERSION = " Makefile -m1 | cut -d' ' -f3` 10 | 11 | # directory where to look for the files 12 | DIRECTORY=`grep "DISTDIR = " Makefile -m1 | cut -d' ' -f3` 13 | 14 | 15 | function makeentry 16 | # usage: makeentry filename description 17 | { 18 | file=$DIRECTORY/$1 19 | 20 | if [ -f $file ]; then 21 | 22 | echo "{{SFFile|" 23 | 24 | echo " name= $1" 25 | echo "| version= ${VERSION}" 26 | echo "| size= `wc -c $file | cut -f1 -d' '`" 27 | echo "| desc= $2" 28 | echo "| md5= `md5sum $file | cut -f1 -d' '`" 29 | echo "| sha256= `sha256sum $file | cut -f1 -d' '`" 30 | 31 | echo "}}" 32 | 33 | else 34 | 35 | echo "Error: File '$file' does not exist" 36 | 37 | fi 38 | } 39 | 40 | 41 | makeentry "brutalist-fonts-ttf-${VERSION}.tar.bz2" "TrueType fonts packed as [[Wikipedia:tar.bz2|tar.bz2]] archive" 42 | 43 | makeentry "brutalist-fonts-ttf-${VERSION}.zip" "TrueType fonts packed as [[Wikipedia:ZIP (file format)|zip]] archive" 44 | 45 | makeentry "brutalist-fonts-${VERSION}.tar.bz2" "Fonts in source form (SFD) for [[FontForge]]" 46 | 47 | makeentry "brutalist-lgc-fonts-ttf-${VERSION}.tar.bz2" "Brutalist LGC (Latin, Greek, Cyrillic) TrueType fonts packed as [[Wikipedia:tar.bz2|tar.bz2]] archive" 48 | 49 | makeentry "brutalist-lgc-fonts-ttf-${VERSION}.zip" "Brutalist LGC (Latin, Greek, Cyrillic) TrueType fonts packed as [[Wikipedia:ZIP (file format)|zip]] archive" 50 | 51 | makeentry "brutalist-ttf-${VERSION}.zip" "This package only includes Brutalist.ttf in a [[Wikipedia:ZIP (file format)|zip]] archive" 52 | 53 | --------------------------------------------------------------------------------