diff options
Diffstat (limited to 'misc/pylib/robofab')
79 files changed, 0 insertions, 24333 deletions
diff --git a/misc/pylib/robofab/.gitignore b/misc/pylib/robofab/.gitignore deleted file mode 100644 index e35ddbd94..000000000 --- a/misc/pylib/robofab/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.c -build diff --git a/misc/pylib/robofab/LICENSE.txt b/misc/pylib/robofab/LICENSE.txt deleted file mode 100644 index df0945ab8..000000000 --- a/misc/pylib/robofab/LICENSE.txt +++ /dev/null @@ -1,22 +0,0 @@ -RoboFab License Agreement - -Copyright (c) 2003-2013, The RoboFab Developers: - Erik van Blokland - Tal Leming - Just van Rossum - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -Neither the name of the The RoboFab Developers nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Up to date info on RoboFab: - http://robofab.com/ - -This is the BSD license: - http://www.opensource.org/licenses/BSD-3-Clause - diff --git a/misc/pylib/robofab/__init__.py b/misc/pylib/robofab/__init__.py deleted file mode 100755 index d16579ff6..000000000 --- a/misc/pylib/robofab/__init__.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -ROBOFAB -RoboFab is a Python library with objects -that deal with data usually associated -with fonts and type design. - -DEVELOPERS -RoboFab is developed and maintained by - Tal Leming - Erik van Blokland - Just van Rossum - (in no particular order) - -MORE INFO -The RoboFab homepage, documentation etc. -http://robofab.com - -SVN REPOSITORY -http://svn.robofab.com -TRAC -http://code.robofab.com - -RoboFab License Agreement - -Copyright (c) 2003-2013, The RoboFab Developers: - Erik van Blokland - Tal Leming - Just van Rossum - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -Neither the name of the The RoboFab Developers nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Up to date info on RoboFab: - http://robofab.com/ - -This is the BSD license: - http://www.opensource.org/licenses/BSD-3-Clause - - -HISTORY -RoboFab starts somewhere during the -TypoTechnica in Heidelberg, 2003. - -DEPENDENCIES -RoboFab expects fontTools to be installed. -http://sourceforge.net/projects/fonttools/ -Some of the RoboFab modules require data files -that are included in the source directory. -RoboFab likes to be able to calculate paths -to these data files all by itself, so keep them -together with the source files. - -QUOTES -Yuri Yarmola: -"If data is somehow available to other programs -via some standard data-exchange interface which -can be accessed by some library in Python, you -can make a Python script that uses that library -to apply data to a font opened in FontLab." - -W.A. Dwiggins: -"You will understand that I am not trying to -short-circuit any of your shop operations in -sending drawings of this kind. The closer I can -get to the machine the better the result. -Subtleties of curves are important, as you know, -and if I can make drawings that can be used in -the large size I have got one step closer to the -machine that cuts the punches." [1932] - -""" - -from .exceptions import RoboFabError, RoboFabWarning - -numberVersion = (1, 2, "release", 1) -version = "1.2.1" diff --git a/misc/pylib/robofab/contrib/__init__.py b/misc/pylib/robofab/contrib/__init__.py deleted file mode 100755 index 15f284762..000000000 --- a/misc/pylib/robofab/contrib/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -""" - -Directory for contributed packages. -Packages stored here can be imported from - robofab.contrib.<packagename> - -""" - - - - diff --git a/misc/pylib/robofab/exceptions.py b/misc/pylib/robofab/exceptions.py deleted file mode 100644 index 21ca07507..000000000 --- a/misc/pylib/robofab/exceptions.py +++ /dev/null @@ -1,3 +0,0 @@ -class RoboFabError(Exception): pass - -class RoboFabWarning(Warning): pass diff --git a/misc/pylib/robofab/gString.py b/misc/pylib/robofab/gString.py deleted file mode 100755 index 01f319da3..000000000 --- a/misc/pylib/robofab/gString.py +++ /dev/null @@ -1,625 +0,0 @@ -"""A bunch of stuff useful for glyph name comparisons and such. - -1. A group of sorted glyph name lists that can be called directly: -2. Some tools to work with glyph names to do things like build control strings.""" - -import string - -###################################################### -# THE LISTS -###################################################### - -uppercase_plain = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AE', 'OE', 'Oslash', 'Thorn', 'Eth',] - -uppercase_accents = ['Aacute', 'Abreve', 'Acaron', 'Acircumflex', 'Adblgrave', 'Adieresis', 'Agrave', 'Amacron', 'Aogonek', 'Aring', 'Aringacute', 'Atilde', 'Bdotaccent', 'Cacute', 'Ccaron', 'Ccircumflex', 'Cdotaccent', 'Dcaron', 'Dcedilla', 'Ddotaccent', 'Eacute', 'Ebreve', 'Ecaron', 'Ecircumflex', 'Edblgrave', 'Edieresis', 'Edotaccent', 'Egrave', 'Emacron', 'Eogonek', 'Etilde', 'Fdotaccent', 'Gacute', 'Gbreve', 'Gcaron', 'Gcedilla', 'Gcircumflex', 'Gcommaaccent', 'Gdotaccent', 'Gmacron', 'Hcedilla', 'Hcircumflex', 'Hdieresis', 'Hdotaccent', 'Iacute', 'Ibreve', 'Icaron', 'Icircumflex', 'Idblgrave', 'Idieresis', 'Idieresisacute', 'Idieresisacute', 'Idotaccent', 'Igrave', 'Imacron', 'Iogonek', 'Itilde', 'Jcircumflex', 'Kacute', 'Kcaron', 'Kcedilla', 'Kcommaaccent', 'Lacute', 'Lcaron', 'Lcedilla', 'Lcommaaccent', 'Ldotaccent', 'Macute', 'Mdotaccent', 'Nacute', 'Ncaron', 'Ncedilla', 'Ncommaaccent', 'Ndotaccent', 'Ntilde', 'Oacute', 'Obreve', 'Ocaron', 'Ocircumflex', 'Odblgrave', 'Odieresis', 'Ograve', 'Ohorn', 'Ohungarumlaut', 'Omacron', 'Oogonek', 'Otilde', 'Pacute', 'Pdotaccent', 'Racute', 'Rcaron', 'Rcedilla', 'Rcommaaccent', 'Rdblgrave', 'Rdotaccent', 'Sacute', 'Scaron', 'Scedilla', 'Scircumflex', 'Scommaaccent', 'Sdotaccent', 'Tcaron', 'Tcedilla', 'Tcommaaccent', 'Tdotaccent', 'Uacute', 'Ubreve', 'Ucaron', 'Ucircumflex', 'Udblgrave', 'Udieresis', 'Udieresisacute', 'Udieresisacute', 'Udieresisgrave', 'Udieresisgrave', 'Ugrave', 'Uhorn', 'Uhungarumlaut', 'Umacron', 'Uogonek', 'Uring', 'Utilde', 'Vtilde', 'Wacute', 'Wcircumflex', 'Wdieresis', 'Wdotaccent', 'Wgrave', 'Xdieresis', 'Xdotaccent', 'Yacute', 'Ycircumflex', 'Ydieresis', 'Ydotaccent', 'Ygrave', 'Ytilde', 'Zacute', 'Zcaron', 'Zcircumflex', 'Zdotaccent', 'AEacute', 'Ccedilla', 'Oslashacute', 'Ldot'] - -uppercase_special_accents = ['Dcroat', 'Lslash', 'Hbar', 'Tbar', 'LL', 'Eng'] - -uppercase_ligatures = ['IJ'] - -uppercase = uppercase_plain+uppercase_accents+uppercase_special_accents+uppercase_ligatures - -lowercase_plain = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'dotlessi', 'dotlessj', 'ae', 'oe', 'oslash', 'thorn', 'eth', 'germandbls', 'longs',] - -lowercase_accents = ['aacute', 'abreve', 'acaron', 'acircumflex', 'adblgrave', 'adieresis', 'agrave', 'amacron', 'aogonek', 'aring', 'aringacute', 'atilde', 'bdotaccent', 'cacute', 'ccaron', 'ccircumflex', 'cdotaccent', 'dcaron', 'dcedilla', 'ddotaccent', 'dmacron', 'eacute', 'ebreve', 'ecaron', 'ecircumflex', 'edblgrave', 'edieresis', 'edotaccent', 'egrave', 'emacron', 'eogonek', 'etilde', 'fdotaccent', 'gacute', 'gbreve', 'gcaron', 'gcedilla', 'gcircumflex', 'gcommaaccent', 'gdotaccent', 'gmacron', 'hcedilla', 'hcircumflex', 'hdieresis', 'hdotaccent', 'iacute', 'ibreve', 'icaron', 'icircumflex', 'idblgrave', 'idieresis', 'idieresisacute', 'idieresisacute', 'igrave', 'imacron', 'iogonek', 'itilde', 'jcaron', 'jcircumflex', 'kacute', 'kcaron', 'kcedilla', 'kcommaaccent', 'lacute', 'lcaron', 'lcedilla', 'lcommaaccent', 'ldotaccent', 'macute', 'mdotaccent', 'nacute', 'ncaron', 'ncedilla', 'ncommaaccent', 'ndotaccent', 'ntilde', 'oacute', 'obreve', 'ocaron', 'ocircumflex', 'odblgrave', 'odieresis', 'ograve', 'ohorn', 'ohungarumlaut', 'omacron', 'oogonek', 'otilde', 'pacute', 'pdotaccent', 'racute', 'rcaron', 'rcedilla', 'rcommaaccent', 'rdblgrave', 'rdotaccent', 'sacute', 'scaron', 'scedilla', 'scircumflex', 'scommaaccent', 'sdotaccent', 'tcaron', 'tcedilla', 'tcommaaccent', 'tdieresis', 'tdotaccent', 'uacute', 'ubreve', 'ucaron', 'ucircumflex', 'udblgrave', 'udieresis', 'udieresisacute', 'udieresisacute', 'udieresisgrave', 'udieresisgrave', 'ugrave', 'uhorn', 'uhungarumlaut', 'umacron', 'uogonek', 'uring', 'utilde', 'vtilde', 'wacute', 'wcircumflex', 'wdieresis', 'wdotaccent', 'wgrave', 'wring', 'xdieresis', 'xdotaccent', 'yacute', 'ycircumflex', 'ydieresis', 'ydotaccent', 'ygrave', 'yring', 'ytilde', 'zacute', 'zcaron', 'zcircumflex', 'zdotaccent', 'aeacute', 'ccedilla', 'oslashacute', 'ldot', ] - -lowercase_special_accents = ['dcroat', 'lslash', 'hbar', 'tbar', 'kgreenlandic', 'longs', 'll', 'eng'] - -lowercase_ligatures = ['fi', 'fl', 'ff', 'ffi', 'ffl', 'ij'] - -lowercase = lowercase_plain+lowercase_accents+lowercase_special_accents+lowercase_ligatures - -smallcaps_plain = ['A.sc', 'B.sc', 'C.sc', 'D.sc', 'E.sc', 'F.sc', 'G.sc', 'H.sc', 'I.sc', 'J.sc', 'K.sc', 'L.sc', 'M.sc', 'N.sc', 'O.sc', 'P.sc', 'Q.sc', 'R.sc', 'S.sc', 'T.sc', 'U.sc', 'V.sc', 'W.sc', 'X.sc', 'Y.sc', 'Z.sc', 'AE.sc', 'OE.sc', 'Oslash.sc', 'Thorn.sc', 'Eth.sc', ] - -smallcaps_accents = ['Aacute.sc', 'Acircumflex.sc', 'Adieresis.sc', 'Agrave.sc', 'Aring.sc', 'Atilde.sc', 'Ccedilla.sc', 'Eacute.sc', 'Ecircumflex.sc', 'Edieresis.sc', 'Egrave.sc', 'Iacute.sc', 'Icircumflex.sc', 'Idieresis.sc', 'Igrave.sc', 'Ntilde.sc', 'Oacute.sc', 'Ocircumflex.sc', 'Odieresis.sc', 'Ograve.sc', 'Otilde.sc', 'Scaron.sc', 'Uacute.sc', 'Ucircumflex.sc', 'Udieresis.sc', 'Ugrave.sc', 'Yacute.sc', 'Ydieresis.sc', 'Zcaron.sc', 'Ccedilla.sc', 'Lslash.sc', ] - -smallcaps_special_accents = ['Dcroat.sc', 'Lslash.sc', 'Hbar.sc', 'Tbar.sc', 'LL.sc', 'Eng.sc'] - -smallcaps_ligatures = ['IJ.sc'] - -smallcaps = smallcaps_plain + smallcaps_accents + smallcaps_special_accents + smallcaps_ligatures - -all_accents = uppercase_accents + uppercase_special_accents + lowercase_accents +lowercase_special_accents + smallcaps_accents + smallcaps_special_accents - -digits = ['one', 'onefitted', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'zero'] - -digits_oldstyle = ['eight.oldstyle', 'five.oldstyle', 'four.oldstyle', 'nine.oldstyle', 'one.oldstyle', 'seven.oldstyle', 'six.oldstyle', 'three.oldstyle', 'two.oldstyle', 'zero.oldstyle'] - -digits_superior = ['eight.superior', 'five.superior', 'four.superior', 'nine.superior', 'one.superior', 'seven.superior', 'six.superior', 'three.superior', 'two.superior', 'zero.superior'] - -digits_inferior = ['eight.inferior', 'five.inferior', 'four.inferior', 'nine.inferior', 'one.inferior', 'seven.inferior', 'three.inferior', 'two.inferior', 'zero.inferior'] - -fractions = ['oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onequarter', 'threequarters', 'onethird', 'twothirds', 'onehalf'] - -currency = ['dollar', 'cent', 'currency', 'Euro', 'sterling', 'yen', 'florin', 'franc', 'lira'] - -currency_oldstyle = ['cent.oldstyle', 'dollar.oldstyle'] - -currency_superior = ['cent.superior', 'dollar.superior'] - -currency_inferior = ['cent.inferior', 'dollar.inferior'] - -inferior = ['eight.inferior', 'five.inferior', 'four.inferior', 'nine.inferior', 'one.inferior', 'seven.inferior', 'three.inferior', 'two.inferior', 'zero.inferior', 'cent.inferior', 'dollar.inferior', 'comma.inferior', 'hyphen.inferior', 'parenleft.inferior', 'parenright.inferior', 'period.inferior'] - -superior = ['eight.superior', 'five.superior', 'four.superior', 'nine.superior', 'one.superior', 'seven.superior', 'six.superior', 'three.superior', 'two.superior', 'zero.superior', 'cent.superior', 'dollar.superior', 'Rsmallinverted.superior', 'a.superior', 'b.superior', 'comma.superior', 'd.superior', 'equal.superior', 'e.superior', 'glottalstopreversed.superior', 'hhook.superior', 'h.superior', 'hyphen.superior', 'i.superior', 'j.superior', 'l.superior', 'm.superior', 'n.superior', 'o.superior', 'parenleft.superior', 'parenright.superior', 'period.superior', 'plus.superior', 'r.superior', 'rturned.superior', 's.superior', 't.superior', 'w.superior', 'x.superior', 'y.superior'] - -accents = ['acute', 'acutecomb', 'breve', 'caron', 'cedilla', 'circumflex', 'commaaccent', 'dblgrave', 'dieresis', 'dieresisacute', 'dieresisacute', 'dieresisgrave', 'dieresisgrave', 'dotaccent', 'grave', 'dblgrave', 'gravecomb', 'hungarumlaut', 'macron', 'ogonek', 'ring', 'ringacute', 'tilde', 'tildecomb', 'horn', 'Acute.sc', 'Breve.sc', 'Caron.sc', 'Cedilla.sc', 'Circumflex.sc', 'Dieresis.sc', 'Dotaccent.sc', 'Grave.sc', 'Hungarumlaut.sc', 'Macron.sc', 'Ogonek.sc', 'Ring.sc', 'Tilde.sc'] - -dashes = ['hyphen', 'endash', 'emdash', 'threequartersemdash', 'underscore', 'underscoredbl', 'figuredash'] - -legal = ['trademark', 'trademarksans', 'trademarkserif', 'copyright', 'copyrightsans', 'copyrightserif', 'registered', 'registersans', 'registerserif'] - -ligatures = ['fi', 'fl', 'ff', 'ffi', 'ffl', 'ij', 'IJ'] - -punctuation = ['period', 'periodcentered', 'comma', 'colon', 'semicolon', 'ellipsis', 'exclam', 'exclamdown', 'exclamdbl', 'question', 'questiondown'] - -numerical = ['percent', 'perthousand', 'infinity', 'numbersign', 'degree', 'colonmonetary', 'dotmath'] - -slashes = ['slash', 'backslash', 'bar', 'brokenbar', 'fraction'] - -special = ['ampersand', 'paragraph', 'section', 'bullet', 'dagger', 'daggerdbl', 'asterisk', 'at', 'asciicircum', 'asciitilde'] - - -dependencies = { - 'A': ['Aacute', 'Abreve', 'Acaron', 'Acircumflex', 'Adblgrave', 'Adieresis', 'Agrave', 'Amacron', 'Aogonek', 'Aring', 'Aringacute', 'Atilde'], - 'B': ['Bdotaccent'], - 'C': ['Cacute', 'Ccaron', 'Ccircumflex', 'Cdotaccent', 'Ccedilla'], - 'D': ['Dcaron', 'Dcedilla', 'Ddotaccent'], - 'E': ['Eacute', 'Ebreve', 'Ecaron', 'Ecircumflex', 'Edblgrave', 'Edieresis', 'Edotaccent', 'Egrave', 'Emacron', 'Eogonek', 'Etilde'], - 'F': ['Fdotaccent'], - 'G': ['Gacute', 'Gbreve', 'Gcaron', 'Gcedilla', 'Gcircumflex', 'Gcommaaccent', 'Gdotaccent', 'Gmacron'], - 'H': ['Hcedilla', 'Hcircumflex', 'Hdieresis', 'Hdotaccent'], - 'I': ['Iacute', 'Ibreve', 'Icaron', 'Icircumflex', 'Idblgrave', 'Idieresis', 'Idieresisacute', 'Idieresisacute', 'Idotaccent', 'Igrave', 'Imacron', 'Iogonek', 'Itilde'], - 'J': ['Jcircumflex'], - 'K': ['Kacute', 'Kcaron', 'Kcedilla', 'Kcommaaccent'], - 'L': ['Lacute', 'Lcaron', 'Lcedilla', 'Lcommaaccent', 'Ldotaccent', 'Ldot'], - 'M': ['Macute', 'Mdotaccent'], - 'N': ['Nacute', 'Ncaron', 'Ncedilla', 'Ncommaaccent', 'Ndotaccent', 'Ntilde'], - 'O': ['Oacute', 'Obreve', 'Ocaron', 'Ocircumflex', 'Odblgrave', 'Odieresis', 'Ograve', 'Ohorn', 'Ohungarumlaut', 'Omacron', 'Oogonek', 'Otilde'], - 'P': ['Pacute', 'Pdotaccent'], - 'R': ['Racute', 'Rcaron', 'Rcedilla', 'Rcommaaccent', 'Rdblgrave', 'Rdotaccent'], - 'S': ['Sacute', 'Scaron', 'Scedilla', 'Scircumflex', 'Scommaaccent', 'Sdotaccent'], - 'T': ['Tcaron', 'Tcedilla', 'Tcommaaccent', 'Tdotaccent'], - 'U': ['Uacute', 'Ubreve', 'Ucaron', 'Ucircumflex', 'Udblgrave', 'Udieresis', 'Udieresisacute', 'Udieresisacute', 'Udieresisgrave', 'Udieresisgrave', 'Ugrave', 'Uhorn', 'Uhungarumlaut', 'Umacron', 'Uogonek', 'Uring', 'Utilde'], - 'V': ['Vtilde'], - 'W': ['Wacute', 'Wcircumflex', 'Wdieresis', 'Wdotaccent', 'Wgrave'], - 'X': ['Xdieresis', 'Xdotaccent'], - 'Y': ['Yacute', 'Ycircumflex', 'Ydieresis', 'Ydotaccent', 'Ygrave', 'Ytilde'], - 'Z': ['Zacute', 'Zcaron', 'Zcircumflex', 'Zdotaccent'], - 'AE': ['AEacute'], - 'Oslash': ['Oslashacute'], - - 'a': ['aacute', 'abreve', 'acaron', 'acircumflex', 'adblgrave', 'adieresis', 'agrave', 'amacron', 'aogonek', 'aring', 'aringacute', 'atilde'], - 'b': ['bdotaccent'], - 'c': ['cacute', 'ccaron', 'ccircumflex', 'cdotaccent', 'ccedilla'], - 'd': ['dcaron', 'dcedilla', 'ddotaccent', 'dmacron'], - 'e': ['eacute', 'ebreve', 'ecaron', 'ecircumflex', 'edblgrave', 'edieresis', 'edotaccent', 'egrave', 'emacron', 'eogonek', 'etilde'], - 'f': ['fdotaccent'], - 'g': ['gacute', 'gbreve', 'gcaron', 'gcedilla', 'gcircumflex', 'gcommaaccent', 'gdotaccent', 'gmacron'], - 'h': ['hcedilla', 'hcircumflex', 'hdieresis', 'hdotaccent'], - 'i': ['iacute', 'ibreve', 'icaron', 'icircumflex', 'idblgrave', 'idieresis', 'idieresisacute', 'idieresisacute', 'igrave', 'imacron', 'iogonek', 'itilde'], - 'j': ['jcaron', 'jcircumflex'], - 'k': ['kacute', 'kcaron', 'kcedilla', 'kcommaaccent'], - 'l': ['lacute', 'lcaron', 'lcedilla', 'lcommaaccent', 'ldotaccent', 'ldot'], - 'm': ['macute', 'mdotaccent'], - 'n': ['nacute', 'ncaron', 'ncedilla', 'ncommaaccent', 'ndotaccent', 'ntilde'], - 'o': ['oacute', 'obreve', 'ocaron', 'ocircumflex', 'odblgrave', 'odieresis', 'ograve', 'ohorn', 'ohungarumlaut', 'omacron', 'oogonek', 'otilde'], - 'p': ['pacute', 'pdotaccent'], - 'r': ['racute', 'rcaron', 'rcedilla', 'rcommaaccent', 'rdblgrave', 'rdotaccent'], - 's': ['sacute', 'scaron', 'scedilla', 'scircumflex', 'scommaaccent', 'sdotaccent'], - 't': ['tcaron', 'tcedilla', 'tcommaaccent', 'tdieresis', 'tdotaccent'], - 'u': ['uacute', 'ubreve', 'ucaron', 'ucircumflex', 'udblgrave', 'udieresis', 'udieresisacute', 'udieresisacute', 'udieresisgrave', 'udieresisgrave', 'ugrave', 'uhorn', 'uhungarumlaut', 'umacron', 'uogonek', 'uring', 'utilde'], - 'v': ['vtilde'], - 'w': ['wacute', 'wcircumflex', 'wdieresis', 'wdotaccent', 'wgrave', 'wring'], - 'x': ['xdieresis', 'xdotaccent'], - 'y': ['yacute', 'ycircumflex', 'ydieresis', 'ydotaccent', 'ygrave', 'yring', 'ytilde'], - 'z': ['zacute', 'zcaron', 'zcircumflex', 'zdotaccent'], - 'ae': ['aeacute'], - 'oslash': ['oslashacute'], - } -###################################################### -# MISC TOOLS -###################################################### - -def breakSuffix(glyphname): - """ - Breaks the glyphname into a two item list - 0: glyphname - 1: suffix - - if a suffix is not found it returns None - """ - if glyphname.find('.') != -1: - split = glyphname.split('.') - return split - else: - return None - -def findAccentBase(accentglyph): - """Return the base glyph of an accented glyph - for example: Ugrave.sc returns U.sc""" - base = splitAccent(accentglyph)[0] - return base - -def splitAccent(accentglyph): - """ - Split an accented glyph into a two items - 0: base glyph - 1: accent list - - for example: Yacute.scalt45 returns: (Y.scalt45, [acute]) - and: aacutetilde.alt45 returns (a.alt45, [acute, tilde]) - """ - base = None - suffix = '' - accentList=[] - broken = breakSuffix(accentglyph) - if broken is not None: - suffix = broken[1] - base = broken[0] - else: - base=accentglyph - ogbase=base - temp_special = lowercase_special_accents + uppercase_special_accents - if base in lowercase_plain + uppercase_plain + smallcaps_plain: - pass - elif base not in temp_special: - for accent in accents: - if base.find(accent) != -1: - base = base.replace(accent, '') - accentList.append(accent) - counter={} - for accent in accentList: - counter[ogbase.find(accent)] = accent - counterList = counter.keys() - counterList.sort() - finalAccents = [] - for i in counterList: - finalAccents.append(counter[i]) - accentList = finalAccents - if len(suffix) != 0: - base = '.'.join([base, suffix]) - return base, accentList - - -###################################################### -# UPPER, LOWER, SMALL -###################################################### - -casedict = { - 'germandbls' : 'S/S', - 'dotlessi' : 'I', - 'dotlessj' : 'J', - 'ae' : 'AE', - 'aeacute' : 'AEacute', - 'oe' : 'OE', - 'll' : 'LL' - } - -casedictflip = {} - -smallcapscasedict = { - 'germandbls' : 'S.sc/S.sc', - 'question' : 'question.sc', - 'questiondown' : 'questiondown.sc', - 'exclam' : 'exclam.sc', - 'exclamdown' : 'exclamdown.sc', - 'ampersand' : 'ampersand.sc' - } - -class _InternalCaseFunctions: - """internal functions for doing gymnastics with the casedicts""" - - def expandsmallcapscasedict(self): - for i in casedict.values(): - if i not in smallcapscasedict.keys(): - if len(i) > 1: - if i[:1].upper() == i[:1]: - smallcapscasedict[i] = i[:1] + i[1:] + '.sc' - - for i in uppercase: - if i + '.sc' in smallcaps: - if i not in smallcapscasedict.keys(): - smallcapscasedict[i] = i + '.sc' - - def flipcasedict(self): - for i in casedict.keys(): - if i.find('dotless') != -1: - i = i.replace('dotless', '') - casedictflip[casedict[i]] = i - - def expandcasedict(self): - for i in lowercase_ligatures: - casedict[i] = i.upper() - for i in lowercase: - if i not in casedict.keys(): - if string.capitalize(i) in uppercase: - casedict[i] = string.capitalize(i) - - -def upper(glyphstring): - """Convert all possible characters to uppercase in a glyph string.""" - - _InternalCaseFunctions().expandcasedict() - uc = [] - for i in glyphstring.split('/'): - if i.find('.sc') != -1: - if i[-3] != '.sc': - x = i.replace('.sc', '.') - else: - x = i.replace('.sc', '') - i = x - suffix = '' - bS = breakSuffix(i) - if bS is not None: - suffix = bS[1] - i = bS[0] - if i in casedict.keys(): - i = casedict[i] - if len(suffix) != 0: - i = '.'.join([i, suffix]) - uc.append(i) - return '/'.join(uc) - -def lower(glyphstring): - """Convert all possible characters to lowercase in a glyph string.""" - - _InternalCaseFunctions().expandcasedict() - _InternalCaseFunctions().flipcasedict() - lc = [] - for i in glyphstring.split('/'): - if i.find('.sc') != -1: - if i[-3] != '.sc': - x = i.replace('.sc', '.') - else: - x = i.replace('.sc', '') - i = x - suffix = '' - bS = breakSuffix(i) - if breakSuffix(i) is not None: - suffix = bS[1] - i = bS[0] - if i in casedictflip.keys(): - i = casedictflip[i] - if len(suffix) != 0: - i = '.'.join([i, suffix]) - lc.append(i) - return '/'.join(lc) - -def small(glyphstring): - """Convert all possible characters to smallcaps in a glyph string.""" - - _InternalCaseFunctions().expandcasedict() - _InternalCaseFunctions().expandsmallcapscasedict() - sc = [] - for i in glyphstring.split('/'): - suffix = '' - bS = breakSuffix(i) - if bS is not None: - suffix = bS[1] - if suffix == 'sc': - suffix = '' - i = bS[0] - if i in lowercase: - if i not in smallcapscasedict.keys(): - i = casedict[i] - if i in smallcapscasedict.keys(): - i = smallcapscasedict[i] - if i != 'S.sc/S.sc': - if len(suffix) != 0: - if i[-3:] == '.sc': - i = ''.join([i, suffix]) - else: - i = '.'.join([i, suffix]) - sc.append(i) - return '/'.join(sc) - - -###################################################### -# CONTROL STRING TOOLS -###################################################### - - -controldict = { - 'UC' : ['/H/H', '/H/O/H/O', '/O/O'], - 'LC' : ['/n/n', '/n/o/n/o', '/o/o'], - 'SC' : ['/H.sc/H.sc', '/H.sc/O.sc/H.sc/O.sc', '/O.sc/O.sc'], - 'DIGITS' : ['/one/one', '/one/zero/one/zero', '/zero/zero'], - } - - -def controls(glyphname): - """Send this a glyph name and get a control string - with all glyphs separated by slashes.""" - controlslist = [] - for value in controldict.values(): - for v in value: - for i in v.split('/'): - if len(i) > 0: - if i not in controlslist: - controlslist.append(i) - cs = '' - if glyphname in controlslist: - for key in controldict.keys(): - for v in controldict[key]: - if glyphname in v.split('/'): - con = controldict[key] - striptriple = [] - hold1 = '' - hold2 = '' - for i in ''.join(con).split('/'): - if len(i) != 0: - if i == hold1 and i == hold2: - pass - else: - striptriple.append(i) - hold1 = hold2 - hold2 = i - constr = '/' + '/'.join(striptriple) - # this is a bit of a hack since FL seems to have trouble - # when it encounters the same string more than once. - # so, let's stick the glyph at the end to differentiate it. - # for example: HHOHOOH and HHOHOOO - cs = constr + '/' + glyphname - else: - suffix = '' - bS = breakSuffix(glyphname) - if bS is not None: - suffix = bS[1] - glyphname = bS[0] - if suffix[:2] == 'sc': - controls = controldict['SC'] - elif glyphname in uppercase: - controls = controldict['UC'] - elif glyphname in lowercase: - controls = controldict['LC'] - elif glyphname in digits: - controls = controldict['DIGITS'] - else: - controls = controldict['UC'] - if len(suffix) != 0: - glyphname = '.'.join([glyphname, suffix]) - cs = controls[0] + '/' + glyphname + controls[1] + '/' + glyphname + controls[2] - return cs - - -def sortControlList(list): - """Roughly sort a list of control strings.""" - - controls = [] - for v in controldict.values(): - for w in v: - for x in w.split('/'): - if len(x) is not None: - if x not in controls: - controls.append(x) - temp_digits = digits + digits_oldstyle + fractions - temp_currency = currency + currency_oldstyle - ss_uppercase = [] - ss_lowercase = [] - ss_smallcaps = [] - ss_digits = [] - ss_currency = [] - ss_other = [] - for i in list: - glyphs = i.split('/') - c = glyphs[2] - for glyph in glyphs: - if len(glyph) is not None: - if glyph not in controls: - c = glyph - if c in uppercase: - ss_uppercase.append(i) - elif c in lowercase: - ss_lowercase.append(i) - elif c in smallcaps: - ss_smallcaps.append(i) - elif c in temp_digits: - ss_digits.append(i) - elif c in temp_currency: - ss_currency.append(i) - else: - ss_other.append(i) - ss_uppercase.sort() - ss_lowercase.sort() - ss_smallcaps.sort() - ss_digits.sort() - ss_currency.sort() - ss_other.sort() - return ss_uppercase + ss_lowercase + ss_smallcaps + ss_digits + ss_currency + ss_other - - -# under contruction! -kerncontroldict = { - 'UC/UC' : ['/H/H', '/H/O/H/O/O'], - 'UC/LC' : ['', '/n/n/o/n/e/r/s'], - 'UC/SORTS' : ['/H/H', '/H/O/H/O/O'], - 'UC/DIGITS' : ['/H/H', '/H/O/H/O/O'], - 'LC/LC' : ['/n/n', '/n/o/n/o/o'], - 'LC/SORTS' : ['/n/n', '/n/o/n/o/o'], - 'LC/DIGITS' : ['', '/n/n/o/n/e/r/s'], - 'SC/SC' : ['/H.sc/H.sc', '/H.sc/O.sc/H.sc/O.sc/O.sc'], - 'UC/SC' : ['', '/H.sc/H.sc/O.sc/H.sc/O.sc/O.sc'], - 'SC/SORTS' : ['/H.sc/H.sc', '/H.sc/O.sc/H.sc/O.sc/O.sc'], - 'SC/DIGITS' : ['', '/H.sc/H.sc/O.sc/H.sc/O.sc/O.sc'], - 'DIGITS/DIGITS' : ['/H/H', '/H/O/H/O/O'], - 'DIGITS/SORTS' : ['/H/H', '/H/O/H/O/O'], - 'SORTS/SORTS' : ['/H/H', '/H/O/H/O/O'], - } - -def kernControls(leftglyphname, rightglyphname): - """build a control string based on the left glyph and right glyph""" - - sorts = currency + accents + dashes + legal + numerical + slashes + special - - l = leftglyphname - r = rightglyphname - lSuffix = '' - rSuffix = '' - bSL = breakSuffix(l) - if bSL is not None: - lSuffix = bSL[1] - l = bSL[0] - bSR = breakSuffix(r) - if bSR is not None: - rSuffix = bSR[1] - r = bSR[0] - if lSuffix[:2] == 'sc' or rSuffix[:2] == 'sc': - if l in uppercase or r in uppercase: - controls = kerncontroldict['UC/SC'] - elif l in digits or r in digits: - controls = kerncontroldict['SC/DIGITS'] - elif l in sorts or r in sorts: - controls = kerncontroldict['SC/SORTS'] - else: - controls = kerncontroldict['SC/SC'] - elif l in uppercase or r in uppercase: - if l in lowercase or r in lowercase: - controls = kerncontroldict['UC/LC'] - elif l in digits or r in digits: - controls = kerncontroldict['UC/DIGITS'] - elif l in sorts or r in sorts: - controls = kerncontroldict['UC/SORTS'] - else: - controls = kerncontroldict['UC/UC'] - elif l in lowercase or r in lowercase: - if l in uppercase or r in uppercase: - controls = kerncontroldict['UC/LC'] - elif l in digits or r in digits: - controls = kerncontroldict['LC/DIGITS'] - elif l in sorts or r in sorts: - controls = kerncontroldict['LC/SORTS'] - else: - controls = kerncontroldict['LC/LC'] - elif l in digits or r in digits: - if l in uppercase or r in uppercase: - controls = kerncontroldict['UC/DIGITS'] - elif l in lowercase or r in lowercase: - controls = kerncontroldict['LC/DIGITS'] - elif l in sorts or r in sorts: - controls = kerncontroldict['DIGITS/SORTS'] - else: - controls = kerncontroldict['DIGITS/DIGITS'] - elif l in sorts and r in sorts: - controls = kerncontroldict['SORTS/SORTS'] - else: - controls = kerncontroldict['UC/UC'] - - if len(lSuffix) != 0: - l = '.'.join([l, lSuffix]) - if len(rSuffix) != 0: - r = '.'.join([r, rSuffix]) - - cs = controls[0] + '/' + l + '/' + r + controls[1] - - return cs - - -###################################################### - -class _testing: - def __init__(self): - print - print '##### testing!' - # self.listtest() - # self.accentbasetest() - # self.controlstest() - self.upperlowersmalltest() - # self.stringsorttest() - - def listtest(self): - testlist = [ - uppercase, - uppercase_accents, - lowercase, - lowercase_accents, - smallcaps, - smallcaps_accents, - digits, - digits_oldstyle, - digits_superior, - digits_inferior, - fractions, - currency, - currency_oldstyle, - currency_superior, - currency_inferior, - inferior, - superior, - accents, - dashes, - legal, - ligatures, - punctuation, - numerical, - slashes, - special - ] - for i in testlist: - print i - - - def accentbasetest(self): - print findAccentBase('Adieresis') - print findAccentBase('Adieresis.sc') - print findAccentBase('Thorn.sc') - print findAccentBase('notaralglyphname') - - - def controlstest(self): - print kernControls('A', 'a.swash') - print kernControls('A.sc', '1') - print kernControls('bracket.sc', 'germandbls') - print kernControls('2', 'X') - print kernControls('Y', 'X') - print kernControls('Y.alt', 'X') - print kernControls('Y.scalt', 'X') - #print controls('x') - #print controls('germandbls') - #print controls('L') - #print controls('L.sc') - #print controls('Z.sc') - #print controls('seven') - #print controls('question') - #print controls('unknown') - - def upperlowersmalltest(self): - u = upper('/H/i/Z.sc/ampersand.sc/dotlessi/germandbls/four.superior/LL') - l = lower('/H/I/Z.sc/ampersand.sc/dotlessi/germandbls/four.superior/LL') - s = small('/H/i/Z.sc/ampersand.alt/dotlessi/germandbls/four.superior/LL') - print u - print l - print s - print lower(u) - print upper(l) - print upper(s) - print lower(s) - - def stringsorttest(self): - sample = "/H/H/Euro/H/O/H/O/Euro/O/O /H/H/R/H/O/H/O/R/O/O /H/H/question/H/O/H/O/question/O/O /H/H/sterling/H/O/H/O/sterling/O/O /n/n/r/n/o/n/o/r/o/o" - list = string.split(sample, ' ') - x = sortControlList(list) - print x - -if __name__ == '__main__': - _testing()
\ No newline at end of file diff --git a/misc/pylib/robofab/glifLib.pyx b/misc/pylib/robofab/glifLib.pyx deleted file mode 100755 index 8a447fff4..000000000 --- a/misc/pylib/robofab/glifLib.pyx +++ /dev/null @@ -1,718 +0,0 @@ -# -*- coding: utf-8 -*- -"""glifLib.py -- Generic module for reading and writing the .glif format. - -More info about the .glif format (GLyphInterchangeFormat) can be found here: - - http://robofab.com/ufo/glif.html - -The main class in this module is GlyphSet. It manages a set of .glif files -in a folder. It offers two ways to read glyph data, and one way to write -glyph data. See the class doc string for details. -""" - -__all__ = ["GlyphSet", "GlifLibError", - "readGlyphFromString", "writeGlyphToString", - "glyphNameToFileName"] - -import os -from robofab.xmlTreeBuilder import buildTree, stripCharacterData -from robofab.pens.pointPen import AbstractPointPen -from cStringIO import StringIO - - -class GlifLibError(Exception): pass - - -if os.name == "mac": - WRITE_MODE = "wb" # use unix line endings, even with Classic MacPython - READ_MODE = "rb" -else: - WRITE_MODE = "w" - READ_MODE = "r" - - -class Glyph: - - """Minimal glyph object. It has no glyph attributes until either - the draw() or the drawPoint() method has been called. - """ - - def __init__(self, glyphName, glyphSet): - self.glyphName = glyphName - self.glyphSet = glyphSet - - def draw(self, pen): - """Draw this glyph onto a *FontTools* Pen.""" - from robofab.pens.adapterPens import PointToSegmentPen - pointPen = PointToSegmentPen(pen) - self.drawPoints(pointPen) - - def drawPoints(self, pointPen): - """Draw this glyph onto a PointPen.""" - self.glyphSet.readGlyph(self.glyphName, self, pointPen) - - -def glyphNameToFileName(glyphName, glyphSet): - """Default algorithm for making a file name out of a glyph name. - This one has limited support for case insensitive file systems: - it assumes glyph names are not case sensitive apart from the first - character: - 'a' -> 'a.glif' - 'A' -> 'A_.glif' - 'A.alt' -> 'A_.alt.glif' - 'A.Alt' -> 'A_.Alt.glif' - 'T_H' -> 'T__H_.glif' - 'T_h' -> 'T__h.glif' - 't_h' -> 't_h.glif' - 'F_F_I' -> 'F__F__I_.glif' - 'f_f_i' -> 'f_f_i.glif' - - """ - if glyphName.startswith("."): - # some OSes consider filenames such as .notdef "hidden" - glyphName = "_" + glyphName[1:] - parts = glyphName.split(".") - if parts[0].find("_")!=-1: - # it is a compound name, check the separate parts - bits = [] - for p in parts[0].split("_"): - if p != p.lower(): - bits.append(p+"_") - continue - bits.append(p) - parts[0] = "_".join(bits) - else: - # it is a single name - if parts[0] != parts[0].lower(): - parts[0] += "_" - for i in range(1, len(parts)): - # resolve additional, period separated parts, like alt / Alt - if parts[i] != parts[i].lower(): - parts[i] += "_" - return ".".join(parts) + ".glif" - - - -class GlyphSet: - - """GlyphSet manages a set of .glif files inside one directory. - - GlyphSet's constructor takes a path to an existing directory as it's - first argument. Reading glyph data can either be done through the - readGlyph() method, or by using GlyphSet's dictionary interface, where - the keys are glyph names and the values are (very) simple glyph objects. - - To write a glyph to the glyph set, you use the writeGlyph() method. - The simple glyph objects returned through the dict interface do not - support writing, they are just means as a convenient way to get at - the glyph data. - """ - - glyphClass = Glyph - - def __init__(self, dirName, glyphNameToFileNameFunc=None): - """'dirName' should be a path to an existing directory. - - The optional 'glyphNameToFileNameFunc' argument must be a callback - function that takes two arguments: a glyph name and the GlyphSet - instance. It should return a file name (including the .glif - extension). The glyphNameToFileName function is called whenever - a file name is created for a given glyph name. - """ - self.dirName = dirName - if glyphNameToFileNameFunc is None: - glyphNameToFileNameFunc = glyphNameToFileName - self.glyphNameToFileName = glyphNameToFileNameFunc - self.contents = self._findContents() - self._reverseContents = None - - def rebuildContents(self): - """Rebuild the contents dict by checking what glyphs are available - on disk. - """ - self.contents = self._findContents(forceRebuild=True) - self._reverseContents = None - - def getReverseContents(self): - """Return a reversed dict of self.contents, mapping file names to - glyph names. This is primarily an aid for custom glyph name to file - name schemes that want to make sure they don't generate duplicate - file names. The file names are converted to lowercase so we can - reliably check for duplicates that only differ in case, which is - important for case-insensitive file systems. - """ - if self._reverseContents is None: - d = {} - for k, v in self.contents.iteritems(): - d[v.lower()] = k - self._reverseContents = d - return self._reverseContents - - def writeContents(self): - """Write the contents.plist file out to disk. Call this method when - you're done writing glyphs. - """ - from plistlib import writePlistToString - contentsPath = os.path.join(self.dirName, "contents.plist") - # We need to force Unix line endings, even in OS9 MacPython in FL, - # so we do the writing to file ourselves. - plist = writePlistToString(self.contents) - f = open(contentsPath, WRITE_MODE) - f.write(plist) - f.close() - - # reading/writing API - - def readGlyph(self, glyphName, glyphObject=None, pointPen=None): - """Read a .glif file for 'glyphName' from the glyph set. The - 'glyphObject' argument can be any kind of object (even None); - the readGlyph() method will attempt to set the following - attributes on it: - "width" the advance with of the glyph - "unicodes" a list of unicode values for this glyph - "note" a string - "lib" a dictionary containing custom data - - All attributes are optional, in two ways: - 1) An attribute *won't* be set if the .glif file doesn't - contain data for it. 'glyphObject' will have to deal - with default values itself. - 2) If setting the attribute fails with an AttributeError - (for example if the 'glyphObject' attribute is read- - only), readGlyph() will not propagate that exception, - but ignore that attribute. - - To retrieve outline information, you need to pass an object - conforming to the PointPen protocol as the 'pointPen' argument. - This argument may be None if you don't need the outline data. - - readGlyph() will raise KeyError if the glyph is not present in - the glyph set. - """ - tree = self._getXMLTree(glyphName) - _readGlyphFromTree(tree, glyphObject, pointPen) - - def writeGlyph(self, glyphName, glyphObject=None, drawPointsFunc=None): - """Write a .glif file for 'glyphName' to the glyph set. The - 'glyphObject' argument can be any kind of object (even None); - the writeGlyph() method will attempt to get the following - attributes from it: - "width" the advance with of the glyph - "unicodes" a list of unicode values for this glyph - "note" a string - "lib" a dictionary containing custom data - - All attributes are optional: if 'glyphObject' doesn't - have the attribute, it will simply be skipped. - - To write outline data to the .glif file, writeGlyph() needs - a function (any callable object actually) that will take one - argument: an object that conforms to the PointPen protocol. - The function will be called by writeGlyph(); it has to call the - proper PointPen methods to transfer the outline to the .glif file. - """ - data = writeGlyphToString(glyphName, glyphObject, drawPointsFunc) - - fileName = self.contents.get(glyphName) - if fileName is None: - fileName = self.glyphNameToFileName(glyphName, self) - self.contents[glyphName] = fileName - if self._reverseContents is not None: - self._reverseContents[fileName.lower()] = glyphName - path = os.path.join(self.dirName, fileName) - if os.path.exists(path): - f = open(path, READ_MODE) - oldData = f.read() - f.close() - if data == oldData: - return - f = open(path, WRITE_MODE) - f.write(data) - f.close() - - def deleteGlyph(self, glyphName): - """Permanently delete the glyph from the glyph set on disk. Will - raise KeyError if the glyph is not present in the glyph set. - """ - fileName = self.contents[glyphName] - os.remove(os.path.join(self.dirName, fileName)) - if self._reverseContents is not None: - del self._reverseContents[self.contents[glyphName].lower()] - del self.contents[glyphName] - - # dict-like support - - def keys(self): - return self.contents.keys() - - def has_key(self, glyphName): - return glyphName in self.contents - - __contains__ = has_key - - def __len__(self): - return len(self.contents) - - def __getitem__(self, glyphName): - if glyphName not in self.contents: - raise KeyError, glyphName - return self.glyphClass(glyphName, self) - - # quickly fetching unicode values - - def getUnicodes(self): - """Return a dictionary that maps all glyph names to lists containing - the unicode value[s] for that glyph, if any. This parses the .glif - files partially, so is a lot faster than parsing all files completely. - """ - # XXX: This method is quite wasteful if we've already parsed many .glif - # files completely. We could collect unicodes values in readGlyph, - # and only do _fetchUnicodes() for those we haven't seen yet. - unicodes = {} - for glyphName, fileName in self.contents.iteritems(): - path = os.path.join(self.dirName, fileName) - unicodes[glyphName] = _fetchUnicodes(path) - return unicodes - - # internal methods - - def _findContents(self, forceRebuild=False): - contentsPath = os.path.join(self.dirName, "contents.plist") - if forceRebuild or not os.path.exists(contentsPath): - fileNames = os.listdir(self.dirName) - fileNames = [n for n in fileNames if n.endswith(".glif")] - contents = {} - for n in fileNames: - glyphPath = os.path.join(self.dirName, n) - contents[_fetchGlyphName(glyphPath)] = n - else: - from plistlib import readPlist - contents = readPlist(contentsPath) - return contents - - def _getXMLTree(self, glyphName): - fileName = self.contents[glyphName] - path = os.path.join(self.dirName, fileName) - if not os.path.exists(path): - raise KeyError, glyphName - return _glifTreeFromFile(path) - - -def readGlyphFromString(aString, glyphObject=None, pointPen=None): - """Read .glif data from a string into a glyph object. - - The 'glyphObject' argument can be any kind of object (even None); - the readGlyphFromString() method will attempt to set the following - attributes on it: - "width" the advance with of the glyph - "unicodes" a list of unicode values for this glyph - "note" a string - "lib" a dictionary containing custom data - - All attributes are optional, in two ways: - 1) An attribute *won't* be set if the .glif file doesn't - contain data for it. 'glyphObject' will have to deal - with default values itself. - 2) If setting the attribute fails with an AttributeError - (for example if the 'glyphObject' attribute is read- - only), readGlyphFromString() will not propagate that - exception, but ignore that attribute. - - To retrieve outline information, you need to pass an object - conforming to the PointPen protocol as the 'pointPen' argument. - This argument may be None if you don't need the outline data. - """ - tree = _glifTreeFromFile(StringIO(aString)) - _readGlyphFromTree(tree, glyphObject, pointPen) - - -def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, writer=None): - """Return .glif data for a glyph as a UTF-8 encoded string. - The 'glyphObject' argument can be any kind of object (even None); - the writeGlyphToString() method will attempt to get the following - attributes from it: - "width" the advance with of the glyph - "unicodes" a list of unicode values for this glyph - "note" a string - "lib" a dictionary containing custom data - - All attributes are optional: if 'glyphObject' doesn't - have the attribute, it will simply be skipped. - - To write outline data to the .glif file, writeGlyphToString() needs - a function (any callable object actually) that will take one - argument: an object that conforms to the PointPen protocol. - The function will be called by writeGlyphToString(); it has to call the - proper PointPen methods to transfer the outline to the .glif file. - """ - if writer is None: - try: - from xmlWriter import XMLWriter - except ImportError: - # try the other location - from fontTools.misc.xmlWriter import XMLWriter - aFile = StringIO() - writer = XMLWriter(aFile, encoding="UTF-8") - else: - aFile = None - writer.begintag("glyph", [("name", glyphName), ("format", "1")]) - writer.newline() - - width = getattr(glyphObject, "width", None) - if width is not None: - if not isinstance(width, (int, float)): - raise GlifLibError, "width attribute must be int or float" - writer.simpletag("advance", width=repr(width)) - writer.newline() - - unicodes = getattr(glyphObject, "unicodes", None) - if unicodes: - if isinstance(unicodes, int): - unicodes = [unicodes] - for code in unicodes: - if not isinstance(code, int): - raise GlifLibError, "unicode values must be int" - hexCode = hex(code)[2:].upper() - if len(hexCode) < 4: - hexCode = "0" * (4 - len(hexCode)) + hexCode - writer.simpletag("unicode", hex=hexCode) - writer.newline() - - note = getattr(glyphObject, "note", None) - if note is not None: - if not isinstance(note, (str, unicode)): - raise GlifLibError, "note attribute must be str or unicode" - note = note.encode('utf-8') - writer.begintag("note") - writer.newline() - for line in note.splitlines(): - writer.write(line.strip()) - writer.newline() - writer.endtag("note") - writer.newline() - - if drawPointsFunc is not None: - writer.begintag("outline") - writer.newline() - pen = GLIFPointPen(writer) - drawPointsFunc(pen) - writer.endtag("outline") - writer.newline() - - lib = getattr(glyphObject, "lib", None) - if lib: - from robofab.plistlib import PlistWriter - if not isinstance(lib, dict): - lib = dict(lib) - writer.begintag("lib") - writer.newline() - plistWriter = PlistWriter(writer.file, indentLevel=writer.indentlevel, - indent=writer.indentwhite, writeHeader=False) - plistWriter.writeValue(lib) - writer.endtag("lib") - writer.newline() - - writer.endtag("glyph") - writer.newline() - if aFile is not None: - return aFile.getvalue() - else: - return None - - -# misc helper functions - -def _stripGlyphXMLTree(nodes): - for element, attrs, children in nodes: - # "lib" is formatted as a plist, so we need unstripped - # character data so we can support strings with leading or - # trailing whitespace. Do strip everything else. - recursive = (element != "lib") - stripCharacterData(children, recursive=recursive) - - -def _glifTreeFromFile(aFile): - try: - tree = buildTree(aFile, stripData=False) - stripCharacterData(tree[2], recursive=False) - assert tree[0] == "glyph" - _stripGlyphXMLTree(tree[2]) - return tree - except: - print "Problem with glif file", aFile - raise - return None - - -def _relaxedSetattr(object, attr, value): - try: - setattr(object, attr, value) - except AttributeError: - pass - - -def _number(s): - """Given a numeric string, return an integer or a float, whichever - the string indicates. _number("1") will return the integer 1, - _number("1.0") will return the float 1.0. - """ - try: - n = int(s) - except ValueError: - n = float(s) - return n - - - -def _readGlyphFromTree(tree, glyphObject=None, pointPen=None): - unicodes = [] - assert tree[0] == "glyph" - formatVersion = int(tree[1].get("format", "0")) - if formatVersion not in (0, 1): - raise GlifLibError, "unsupported glif format version: %s" % formatVersion - glyphName = tree[1].get("name") - if glyphName and glyphObject is not None: - _relaxedSetattr(glyphObject, "name", glyphName) - for element, attrs, children in tree[2]: - if element == "outline": - if pointPen is not None: - if formatVersion == 0: - buildOutline_Format0(pointPen, children) - else: - buildOutline_Format1(pointPen, children) - elif glyphObject is None: - continue - elif element == "advance": - width = _number(attrs["width"]) - _relaxedSetattr(glyphObject, "width", width) - elif element == "unicode": - unicodes.append(int(attrs["hex"], 16)) - elif element == "note": - rawNote = "\n".join(children) - lines = rawNote.split("\n") - lines = [line.strip() for line in lines] - note = "\n".join(lines) - _relaxedSetattr(glyphObject, "note", note) - elif element == "lib": - from plistFromTree import readPlistFromTree - assert len(children) == 1 - lib = readPlistFromTree(children[0]) - _relaxedSetattr(glyphObject, "lib", lib) - if unicodes: - _relaxedSetattr(glyphObject, "unicodes", unicodes) - - -class _DoneParsing(Exception): pass - -def _startElementHandler(tagName, attrs): - if tagName != "glyph": - # the top level element of any .glif file must be <glyph> - raise _DoneParsing(None) - glyphName = attrs["name"] - raise _DoneParsing(glyphName) - -def _fetchGlyphName(glyphPath): - # Given a path to an existing .glif file, get the glyph name - # from the XML data. - from xml.parsers.expat import ParserCreate - - p = ParserCreate() - p.StartElementHandler = _startElementHandler - p.returns_unicode = True - f = open(glyphPath) - try: - p.ParseFile(f) - except _DoneParsing, why: - glyphName = why.args[0] - if glyphName is None: - raise ValueError, (".glif file doen't have a <glyph> top-level " - "element: %r" % glyphPath) - else: - assert 0, "it's not expected that parsing the file ends normally" - return glyphName - - -def _fetchUnicodes(glyphPath): - # Given a path to an existing .glif file, get a list of all - # unicode values from the XML data. - # NOTE: this assumes .glif files written by glifLib, since - # we simply stop parsing as soon as we see anything else than - # <glyph>, <advance> or <unicode>. glifLib always writes those - # elements in that order, before anything else. - from xml.parsers.expat import ParserCreate - - unicodes = [] - def _startElementHandler(tagName, attrs, _unicodes=unicodes): - if tagName == "unicode": - _unicodes.append(int(attrs["hex"], 16)) - elif tagName not in ("glyph", "advance"): - raise _DoneParsing() - - p = ParserCreate() - p.StartElementHandler = _startElementHandler - p.returns_unicode = True - f = open(glyphPath) - try: - p.ParseFile(f) - except _DoneParsing: - pass - return unicodes - - -def buildOutline_Format0(pen, xmlNodes): - # This reads the "old" .glif format, retroactively named "format 0", - # later formats have a "format" attribute in the <glyph> element. - for element, attrs, children in xmlNodes: - if element == "contour": - pen.beginPath() - currentSegmentType = None - for subElement, attrs, dummy in children: - if subElement != "point": - continue - x = _number(attrs["x"]) - y = _number(attrs["y"]) - pointType = attrs.get("type", "onCurve") - if pointType == "bcp": - currentSegmentType = "curve" - elif pointType == "offCurve": - currentSegmentType = "qcurve" - elif currentSegmentType is None and pointType == "onCurve": - currentSegmentType = "line" - if pointType == "onCurve": - segmentType = currentSegmentType - currentSegmentType = None - else: - segmentType = None - smooth = attrs.get("smooth") == "yes" - pen.addPoint((x, y), segmentType=segmentType, smooth=smooth) - pen.endPath() - elif element == "component": - baseGlyphName = attrs["base"] - transformation = [] - for attr, default in _transformationInfo: - value = attrs.get(attr) - if value is None: - value = default - else: - value = _number(value) - transformation.append(value) - pen.addComponent(baseGlyphName, tuple(transformation)) - elif element == "anchor": - name, x, y = attrs["name"], _number(attrs["x"]), _number(attrs["y"]) - pen.beginPath() - pen.addPoint((x, y), segmentType="move", name=name) - pen.endPath() - - -def buildOutline_Format1(pen, xmlNodes): - for element, attrs, children in xmlNodes: - if element == "contour": - pen.beginPath() - for subElement, attrs, dummy in children: - if subElement != "point": - continue - x = _number(attrs["x"]) - y = _number(attrs["y"]) - segmentType = attrs.get("type", "offcurve") - if segmentType == "offcurve": - segmentType = None - smooth = attrs.get("smooth") == "yes" - name = attrs.get("name") - pen.addPoint((x, y), segmentType=segmentType, smooth=smooth, name=name) - pen.endPath() - elif element == "component": - baseGlyphName = attrs["base"] - transformation = [] - for attr, default in _transformationInfo: - value = attrs.get(attr) - if value is None: - value = default - else: - value = _number(value) - transformation.append(value) - pen.addComponent(baseGlyphName, tuple(transformation)) - - -_transformationInfo = [ - # field name, default value - ("xScale", 1), - ("xyScale", 0), - ("yxScale", 0), - ("yScale", 1), - ("xOffset", 0), - ("yOffset", 0), -] - -class GLIFPointPen(AbstractPointPen): - - """Helper class using the PointPen protocol to write the <outline> - part of .glif files. - """ - - def __init__(self, xmlWriter): - self.writer = xmlWriter - - def beginPath(self): - self.writer.begintag("contour") - self.writer.newline() - - def endPath(self): - self.writer.endtag("contour") - self.writer.newline() - - def addPoint(self, pt, segmentType=None, smooth=None, name=None, **kwargs): - attrs = [] - if pt is not None: - for coord in pt: - if not isinstance(coord, (int, float)): - raise GlifLibError, "coordinates must be int or float" - attrs.append(("x", repr(pt[0]))) - attrs.append(("y", repr(pt[1]))) - if segmentType is not None: - attrs.append(("type", segmentType)) - if smooth: - attrs.append(("smooth", "yes")) - if name is not None: - attrs.append(("name", name)) - self.writer.simpletag("point", attrs) - self.writer.newline() - - def addComponent(self, glyphName, transformation): - attrs = [("base", glyphName)] - for (attr, default), value in zip(_transformationInfo, transformation): - if not isinstance(value, (int, float)): - raise GlifLibError, "transformation values must be int or float" - if value != default: - attrs.append((attr, repr(value))) - self.writer.simpletag("component", attrs) - self.writer.newline() - - -if __name__ == "__main__": - from pprint import pprint - from robofab.pens.pointPen import PrintingPointPen - class TestGlyph: pass - gs = GlyphSet(".") - def drawPoints(pen): - pen.beginPath() - pen.addPoint((100, 200), name="foo") - pen.addPoint((200, 250), segmentType="curve", smooth=True) - pen.endPath() - pen.addComponent("a", (1, 0, 0, 1, 20, 30)) - glyph = TestGlyph() - glyph.width = 120 - glyph.unicodes = [1, 2, 3, 43215, 66666] - glyph.lib = {"a": "b", "c": [1, 2, 3, True]} - glyph.note = " hallo! " - if 0: - gs.writeGlyph("a", glyph, drawPoints) - g2 = TestGlyph() - gs.readGlyph("a", g2, PrintingPointPen()) - pprint(g2.__dict__) - else: - s = writeGlyphToString("a", glyph, drawPoints) - print s - g2 = TestGlyph() - readGlyphFromString(s, g2, PrintingPointPen()) - pprint(g2.__dict__) - diff --git a/misc/pylib/robofab/glifLib2.py b/misc/pylib/robofab/glifLib2.py deleted file mode 100755 index a3c3c92d3..000000000 --- a/misc/pylib/robofab/glifLib2.py +++ /dev/null @@ -1,747 +0,0 @@ -# -*- coding: utf-8 -*- -"""glifLib.py -- Generic module for reading and writing the .glif format. - -More info about the .glif format (GLyphInterchangeFormat) can be found here: - - http://unifiedfontobject.org - -The main class in this module is GlyphSet. It manages a set of .glif files -in a folder. It offers two ways to read glyph data, and one way to write -glyph data. See the class doc string for details. -""" - -__all__ = ["GlyphSet", "GlifLibError", - "readGlyphFromString", "writeGlyphToString", - "glyphNameToFileName"] - -import os -from robofab.xmlTreeBuilder import buildTree, stripCharacterData -from robofab.pens.pointPen import AbstractPointPen -from cStringIO import StringIO - - -class GlifLibError(Exception): pass - - -if os.name == "mac": - WRITE_MODE = "wb" # use unix line endings, even with Classic MacPython - READ_MODE = "rb" -else: - WRITE_MODE = "w" - READ_MODE = "r" - - -class Glyph: - - """Minimal glyph object. It has no glyph attributes until either - the draw() or the drawPoint() method has been called. - """ - - def __init__(self, glyphName, glyphSet): - self.glyphName = glyphName - self.glyphSet = glyphSet - - def draw(self, pen): - """Draw this glyph onto a *FontTools* Pen.""" - from robofab.pens.adapterPens import PointToSegmentPen - pointPen = PointToSegmentPen(pen) - self.drawPoints(pointPen) - - def drawPoints(self, pointPen): - """Draw this glyph onto a PointPen.""" - self.glyphSet.readGlyph(self.glyphName, self, pointPen) - - -def glyphNameToFileName(glyphName, glyphSet): - """Default algorithm for making a file name out of a glyph name. - This one has limited support for case insensitive file systems: - it assumes glyph names are not case sensitive apart from the first - character: - 'a' -> 'a.glif' - 'A' -> 'A_.glif' - 'A.alt' -> 'A_.alt.glif' - 'A.Alt' -> 'A_.Alt.glif' - 'T_H' -> 'T__H_.glif' - 'T_h' -> 'T__h.glif' - 't_h' -> 't_h.glif' - 'F_F_I' -> 'F__F__I_.glif' - 'f_f_i' -> 'f_f_i.glif' - - """ - if glyphName.startswith("."): - # some OSes consider filenames such as .notdef "hidden" - glyphName = "_" + glyphName[1:] - parts = glyphName.split(".") - if parts[0].find("_")!=-1: - # it is a compound name, check the separate parts - bits = [] - for p in parts[0].split("_"): - if p != p.lower(): - bits.append(p+"_") - continue - bits.append(p) - parts[0] = "_".join(bits) - else: - # it is a single name - if parts[0] != parts[0].lower(): - parts[0] += "_" - for i in range(1, len(parts)): - # resolve additional, period separated parts, like alt / Alt - if parts[i] != parts[i].lower(): - parts[i] += "_" - return ".".join(parts) + ".glif" - - -class GlyphSet: - - """GlyphSet manages a set of .glif files inside one directory. - - GlyphSet's constructor takes a path to an existing directory as it's - first argument. Reading glyph data can either be done through the - readGlyph() method, or by using GlyphSet's dictionary interface, where - the keys are glyph names and the values are (very) simple glyph objects. - - To write a glyph to the glyph set, you use the writeGlyph() method. - The simple glyph objects returned through the dict interface do not - support writing, they are just a convenient way to get at the glyph data. - """ - - glyphClass = Glyph - - def __init__(self, dirName, glyphNameToFileNameFunc=None): - """'dirName' should be a path to an existing directory. - - The optional 'glyphNameToFileNameFunc' argument must be a callback - function that takes two arguments: a glyph name and the GlyphSet - instance. It should return a file name (including the .glif - extension). The glyphNameToFileName function is called whenever - a file name is created for a given glyph name. - """ - self.dirName = dirName - if glyphNameToFileNameFunc is None: - glyphNameToFileNameFunc = glyphNameToFileName - self.glyphNameToFileName = glyphNameToFileNameFunc - self.contents = self._findContents() - self._reverseContents = None - self._glifCache = {} - - def rebuildContents(self): - """Rebuild the contents dict by checking what glyphs are available - on disk. - """ - self.contents = self._findContents(forceRebuild=True) - self._reverseContents = None - - def getReverseContents(self): - """Return a reversed dict of self.contents, mapping file names to - glyph names. This is primarily an aid for custom glyph name to file - name schemes that want to make sure they don't generate duplicate - file names. The file names are converted to lowercase so we can - reliably check for duplicates that only differ in case, which is - important for case-insensitive file systems. - """ - if self._reverseContents is None: - d = {} - for k, v in self.contents.iteritems(): - d[v.lower()] = k - self._reverseContents = d - return self._reverseContents - - def writeContents(self): - """Write the contents.plist file out to disk. Call this method when - you're done writing glyphs. - """ - from plistlib import writePlistToString - contentsPath = os.path.join(self.dirName, "contents.plist") - # We need to force Unix line endings, even in OS9 MacPython in FL, - # so we do the writing to file ourselves. - plist = writePlistToString(self.contents) - f = open(contentsPath, WRITE_MODE) - f.write(plist) - f.close() - - # read caching - - def getGLIF(self, glyphName): - """Get the raw GLIF text for a given glyph name. This only works - for GLIF files that are already on disk. - - This method is useful in situations when the raw XML needs to be - read from a glyph set for a particular glyph before fully parsing - it into an object structure via the readGlyph method. - - Internally, this method will load a GLIF the first time it is - called and then cache it. The next time this method is called - the GLIF will be pulled from the cache if the file's modification - time has not changed since the GLIF was cached. For memory - efficiency, the cached GLIF will be purged by various other methods - such as readGlyph. - """ - needRead = False - fileName = self.contents.get(glyphName) - path = None - if fileName is not None: - path = os.path.join(self.dirName, fileName) - if glyphName not in self._glifCache: - needRead = True - elif fileName is not None and os.path.getmtime(path) != self._glifCache[glyphName][1]: - needRead = True - if needRead: - fileName = self.contents[glyphName] - if not os.path.exists(path): - raise KeyError, glyphName - f = open(path, "rb") - text = f.read() - f.close() - self._glifCache[glyphName] = (text, os.path.getmtime(path)) - return self._glifCache[glyphName][0] - - def _purgeCachedGLIF(self, glyphName): - if glyphName in self._glifCache: - del self._glifCache[glyphName] - - # reading/writing API - - def readGlyph(self, glyphName, glyphObject=None, pointPen=None): - """Read a .glif file for 'glyphName' from the glyph set. The - 'glyphObject' argument can be any kind of object (even None); - the readGlyph() method will attempt to set the following - attributes on it: - "width" the advance with of the glyph - "unicodes" a list of unicode values for this glyph - "note" a string - "lib" a dictionary containing custom data - - All attributes are optional, in two ways: - 1) An attribute *won't* be set if the .glif file doesn't - contain data for it. 'glyphObject' will have to deal - with default values itself. - 2) If setting the attribute fails with an AttributeError - (for example if the 'glyphObject' attribute is read- - only), readGlyph() will not propagate that exception, - but ignore that attribute. - - To retrieve outline information, you need to pass an object - conforming to the PointPen protocol as the 'pointPen' argument. - This argument may be None if you don't need the outline data. - - readGlyph() will raise KeyError if the glyph is not present in - the glyph set. - """ - text = self.getGLIF(glyphName) - self._purgeCachedGLIF(glyphName) - tree = _glifTreeFromFile(StringIO(text)) - _readGlyphFromTree(tree, glyphObject, pointPen) - - def writeGlyph(self, glyphName, glyphObject=None, drawPointsFunc=None): - """Write a .glif file for 'glyphName' to the glyph set. The - 'glyphObject' argument can be any kind of object (even None); - the writeGlyph() method will attempt to get the following - attributes from it: - "width" the advance with of the glyph - "unicodes" a list of unicode values for this glyph - "note" a string - "lib" a dictionary containing custom data - - All attributes are optional: if 'glyphObject' doesn't - have the attribute, it will simply be skipped. - - To write outline data to the .glif file, writeGlyph() needs - a function (any callable object actually) that will take one - argument: an object that conforms to the PointPen protocol. - The function will be called by writeGlyph(); it has to call the - proper PointPen methods to transfer the outline to the .glif file. - """ - self._purgeCachedGLIF(glyphName) - data = writeGlyphToString(glyphName, glyphObject, drawPointsFunc) - fileName = self.contents.get(glyphName) - if fileName is None: - fileName = self.glyphNameToFileName(glyphName, self) - self.contents[glyphName] = fileName - if self._reverseContents is not None: - self._reverseContents[fileName.lower()] = glyphName - path = os.path.join(self.dirName, fileName) - if os.path.exists(path): - f = open(path, READ_MODE) - oldData = f.read() - f.close() - if data == oldData: - return - f = open(path, WRITE_MODE) - f.write(data) - f.close() - - def deleteGlyph(self, glyphName): - """Permanently delete the glyph from the glyph set on disk. Will - raise KeyError if the glyph is not present in the glyph set. - """ - self._purgeCachedGLIF(glyphName) - fileName = self.contents[glyphName] - os.remove(os.path.join(self.dirName, fileName)) - if self._reverseContents is not None: - del self._reverseContents[self.contents[glyphName].lower()] - del self.contents[glyphName] - - # dict-like support - - def keys(self): - return self.contents.keys() - - def has_key(self, glyphName): - return glyphName in self.contents - - __contains__ = has_key - - def __len__(self): - return len(self.contents) - - def __getitem__(self, glyphName): - if glyphName not in self.contents: - raise KeyError, glyphName - return self.glyphClass(glyphName, self) - - # quickly fetching unicode values - - def getUnicodes(self): - """Return a dictionary that maps all glyph names to lists containing - the unicode value[s] for that glyph, if any. This parses the .glif - files partially, so is a lot faster than parsing all files completely. - """ - unicodes = {} - for glyphName in self.contents.keys(): - text = self.getGLIF(glyphName) - unicodes[glyphName] = _fetchUnicodes(text) - return unicodes - - # internal methods - - def _findContents(self, forceRebuild=False): - contentsPath = os.path.join(self.dirName, "contents.plist") - if forceRebuild or not os.path.exists(contentsPath): - fileNames = os.listdir(self.dirName) - fileNames = [n for n in fileNames if n.endswith(".glif")] - contents = {} - for n in fileNames: - glyphPath = os.path.join(self.dirName, n) - contents[_fetchGlyphName(glyphPath)] = n - else: - from plistlib import readPlist - contents = readPlist(contentsPath) - return contents - - -def readGlyphFromString(aString, glyphObject=None, pointPen=None): - """Read .glif data from a string into a glyph object. - - The 'glyphObject' argument can be any kind of object (even None); - the readGlyphFromString() method will attempt to set the following - attributes on it: - "width" the advance with of the glyph - "unicodes" a list of unicode values for this glyph - "note" a string - "lib" a dictionary containing custom data - - All attributes are optional, in two ways: - 1) An attribute *won't* be set if the .glif file doesn't - contain data for it. 'glyphObject' will have to deal - with default values itself. - 2) If setting the attribute fails with an AttributeError - (for example if the 'glyphObject' attribute is read- - only), readGlyphFromString() will not propagate that - exception, but ignore that attribute. - - To retrieve outline information, you need to pass an object - conforming to the PointPen protocol as the 'pointPen' argument. - This argument may be None if you don't need the outline data. - """ - tree = _glifTreeFromFile(StringIO(aString)) - _readGlyphFromTree(tree, glyphObject, pointPen) - - -def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, writer=None): - """Return .glif data for a glyph as a UTF-8 encoded string. - The 'glyphObject' argument can be any kind of object (even None); - the writeGlyphToString() method will attempt to get the following - attributes from it: - "width" the advance with of the glyph - "unicodes" a list of unicode values for this glyph - "note" a string - "lib" a dictionary containing custom data - - All attributes are optional: if 'glyphObject' doesn't - have the attribute, it will simply be skipped. - - To write outline data to the .glif file, writeGlyphToString() needs - a function (any callable object actually) that will take one - argument: an object that conforms to the PointPen protocol. - The function will be called by writeGlyphToString(); it has to call the - proper PointPen methods to transfer the outline to the .glif file. - """ - if writer is None: - try: - from xmlWriter import XMLWriter - except ImportError: - # try the other location - from fontTools.misc.xmlWriter import XMLWriter - aFile = StringIO() - writer = XMLWriter(aFile, encoding="UTF-8") - else: - aFile = None - writer.begintag("glyph", [("name", glyphName), ("format", "1")]) - writer.newline() - - width = getattr(glyphObject, "width", None) - if width is not None: - if not isinstance(width, (int, float)): - raise GlifLibError, "width attribute must be int or float" - writer.simpletag("advance", width=repr(width)) - writer.newline() - - unicodes = getattr(glyphObject, "unicodes", None) - if unicodes: - if isinstance(unicodes, int): - unicodes = [unicodes] - for code in unicodes: - if not isinstance(code, int): - raise GlifLibError, "unicode values must be int" - hexCode = hex(code)[2:].upper() - if len(hexCode) < 4: - hexCode = "0" * (4 - len(hexCode)) + hexCode - writer.simpletag("unicode", hex=hexCode) - writer.newline() - - note = getattr(glyphObject, "note", None) - if note is not None: - if not isinstance(note, (str, unicode)): - raise GlifLibError, "note attribute must be str or unicode" - note = note.encode('utf-8') - writer.begintag("note") - writer.newline() - for line in note.splitlines(): - writer.write(line.strip()) - writer.newline() - writer.endtag("note") - writer.newline() - - if drawPointsFunc is not None: - writer.begintag("outline") - writer.newline() - pen = GLIFPointPen(writer) - drawPointsFunc(pen) - writer.endtag("outline") - writer.newline() - - lib = getattr(glyphObject, "lib", None) - if lib: - from robofab.plistlib import PlistWriter - if not isinstance(lib, dict): - lib = dict(lib) - writer.begintag("lib") - writer.newline() - plistWriter = PlistWriter(writer.file, indentLevel=writer.indentlevel, - indent=writer.indentwhite, writeHeader=False) - plistWriter.writeValue(lib) - writer.endtag("lib") - writer.newline() - - writer.endtag("glyph") - writer.newline() - if aFile is not None: - return aFile.getvalue() - else: - return None - - -# misc helper functions - -def _stripGlyphXMLTree(nodes): - for element, attrs, children in nodes: - # "lib" is formatted as a plist, so we need unstripped - # character data so we can support strings with leading or - # trailing whitespace. Do strip everything else. - recursive = (element != "lib") - stripCharacterData(children, recursive=recursive) - - -def _glifTreeFromFile(aFile): - tree = buildTree(aFile, stripData=False) - stripCharacterData(tree[2], recursive=False) - assert tree[0] == "glyph" - _stripGlyphXMLTree(tree[2]) - return tree - - -def _relaxedSetattr(object, attr, value): - try: - setattr(object, attr, value) - except AttributeError: - pass - - -def _number(s): - """Given a numeric string, return an integer or a float, whichever - the string indicates. _number("1") will return the integer 1, - _number("1.0") will return the float 1.0. - """ - try: - n = int(s) - except ValueError: - n = float(s) - return n - - - -def _readGlyphFromTree(tree, glyphObject=None, pointPen=None): - unicodes = [] - assert tree[0] == "glyph" - formatVersion = int(tree[1].get("format", "0")) - if formatVersion not in (0, 1): - raise GlifLibError, "unsupported glif format version: %s" % formatVersion - glyphName = tree[1].get("name") - if glyphName and glyphObject is not None: - _relaxedSetattr(glyphObject, "name", glyphName) - for element, attrs, children in tree[2]: - if element == "outline": - if pointPen is not None: - if formatVersion == 0: - buildOutline_Format0(pointPen, children) - else: - buildOutline_Format1(pointPen, children) - elif glyphObject is None: - continue - elif element == "advance": - width = _number(attrs["width"]) - _relaxedSetattr(glyphObject, "width", width) - elif element == "unicode": - unicodes.append(int(attrs["hex"], 16)) - elif element == "note": - rawNote = "\n".join(children) - lines = rawNote.split("\n") - lines = [line.strip() for line in lines] - note = "\n".join(lines) - _relaxedSetattr(glyphObject, "note", note) - elif element == "lib": - from plistFromTree import readPlistFromTree - assert len(children) == 1 - lib = readPlistFromTree(children[0]) - _relaxedSetattr(glyphObject, "lib", lib) - if unicodes: - _relaxedSetattr(glyphObject, "unicodes", unicodes) - - -class _DoneParsing(Exception): pass - -def _startElementHandler(tagName, attrs): - if tagName != "glyph": - # the top level element of any .glif file must be <glyph> - raise _DoneParsing(None) - glyphName = attrs["name"] - raise _DoneParsing(glyphName) - -def _fetchGlyphName(glyphPath): - # Given a path to an existing .glif file, get the glyph name - # from the XML data. - from xml.parsers.expat import ParserCreate - - p = ParserCreate() - p.StartElementHandler = _startElementHandler - p.returns_unicode = True - f = open(glyphPath) - try: - p.ParseFile(f) - except _DoneParsing, why: - glyphName = why.args[0] - if glyphName is None: - raise ValueError, (".glif file doen't have a <glyph> top-level " - "element: %r" % glyphPath) - else: - assert 0, "it's not expected that parsing the file ends normally" - return glyphName - - -def _fetchUnicodes(text): - # Given GLIF text, get a list of all unicode values from the XML data. - parser = _FetchUnicodesParser(text) - return parser.unicodes - -class _FetchUnicodesParser(object): - - def __init__(self, text): - from xml.parsers.expat import ParserCreate - self.unicodes = [] - self._elementStack = [] - parser = ParserCreate() - parser.returns_unicode = 0 # XXX, Don't remember why. It sucks, though. - parser.StartElementHandler = self.startElementHandler - parser.EndElementHandler = self.endElementHandler - parser.Parse(text) - - def startElementHandler(self, name, attrs): - if name == "unicode" and len(self._elementStack) == 1 and self._elementStack[0] == "glyph": - value = attrs.get("hex") - value = int(value, 16) - self.unicodes.append(value) - self._elementStack.append(name) - - def endElementHandler(self, name): - other = self._elementStack.pop(-1) - assert other == name - - -def buildOutline_Format0(pen, xmlNodes): - # This reads the "old" .glif format, retroactively named "format 0", - # later formats have a "format" attribute in the <glyph> element. - for element, attrs, children in xmlNodes: - if element == "contour": - pen.beginPath() - currentSegmentType = None - for subElement, attrs, dummy in children: - if subElement != "point": - continue - x = _number(attrs["x"]) - y = _number(attrs["y"]) - pointType = attrs.get("type", "onCurve") - if pointType == "bcp": - currentSegmentType = "curve" - elif pointType == "offCurve": - currentSegmentType = "qcurve" - elif currentSegmentType is None and pointType == "onCurve": - currentSegmentType = "line" - if pointType == "onCurve": - segmentType = currentSegmentType - currentSegmentType = None - else: - segmentType = None - smooth = attrs.get("smooth") == "yes" - pen.addPoint((x, y), segmentType=segmentType, smooth=smooth) - pen.endPath() - elif element == "component": - baseGlyphName = attrs["base"] - transformation = [] - for attr, default in _transformationInfo: - value = attrs.get(attr) - if value is None: - value = default - else: - value = _number(value) - transformation.append(value) - pen.addComponent(baseGlyphName, tuple(transformation)) - elif element == "anchor": - name, x, y = attrs["name"], _number(attrs["x"]), _number(attrs["y"]) - pen.beginPath() - pen.addPoint((x, y), segmentType="move", name=name) - pen.endPath() - - -def buildOutline_Format1(pen, xmlNodes): - for element, attrs, children in xmlNodes: - if element == "contour": - pen.beginPath() - for subElement, attrs, dummy in children: - if subElement != "point": - continue - x = _number(attrs["x"]) - y = _number(attrs["y"]) - segmentType = attrs.get("type", "offcurve") - if segmentType == "offcurve": - segmentType = None - smooth = attrs.get("smooth") == "yes" - name = attrs.get("name") - pen.addPoint((x, y), segmentType=segmentType, smooth=smooth, name=name) - pen.endPath() - elif element == "component": - baseGlyphName = attrs["base"] - transformation = [] - for attr, default in _transformationInfo: - value = attrs.get(attr) - if value is None: - value = default - else: - value = _number(value) - transformation.append(value) - pen.addComponent(baseGlyphName, tuple(transformation)) - - -_transformationInfo = [ - # field name, default value - ("xScale", 1), - ("xyScale", 0), - ("yxScale", 0), - ("yScale", 1), - ("xOffset", 0), - ("yOffset", 0), -] - -class GLIFPointPen(AbstractPointPen): - - """Helper class using the PointPen protocol to write the <outline> - part of .glif files. - """ - - def __init__(self, xmlWriter): - self.writer = xmlWriter - - def beginPath(self): - self.writer.begintag("contour") - self.writer.newline() - - def endPath(self): - self.writer.endtag("contour") - self.writer.newline() - - def addPoint(self, pt, segmentType=None, smooth=None, name=None, **kwargs): - attrs = [] - if pt is not None: - for coord in pt: - if not isinstance(coord, (int, float)): - raise GlifLibError, "coordinates must be int or float" - attrs.append(("x", repr(pt[0]))) - attrs.append(("y", repr(pt[1]))) - if segmentType is not None: - attrs.append(("type", segmentType)) - if smooth: - attrs.append(("smooth", "yes")) - if name is not None: - attrs.append(("name", name)) - self.writer.simpletag("point", attrs) - self.writer.newline() - - def addComponent(self, glyphName, transformation): - attrs = [("base", glyphName)] - for (attr, default), value in zip(_transformationInfo, transformation): - if not isinstance(value, (int, float)): - raise GlifLibError, "transformation values must be int or float" - if value != default: - attrs.append((attr, repr(value))) - self.writer.simpletag("component", attrs) - self.writer.newline() - - -if __name__ == "__main__": - from pprint import pprint - from robofab.pens.pointPen import PrintingPointPen - class TestGlyph: pass - gs = GlyphSet(".") - def drawPoints(pen): - pen.beginPath() - pen.addPoint((100, 200), name="foo") - pen.addPoint((200, 250), segmentType="curve", smooth=True) - pen.endPath() - pen.addComponent("a", (1, 0, 0, 1, 20, 30)) - glyph = TestGlyph() - glyph.width = 120 - glyph.unicodes = [1, 2, 3, 43215, 66666] - glyph.lib = {"a": "b", "c": [1, 2, 3, True]} - glyph.note = " hallo! " - if 0: - gs.writeGlyph("a", glyph, drawPoints) - g2 = TestGlyph() - gs.readGlyph("a", g2, PrintingPointPen()) - pprint(g2.__dict__) - else: - s = writeGlyphToString("a", glyph, drawPoints) - print s - g2 = TestGlyph() - readGlyphFromString(s, g2, PrintingPointPen()) - pprint(g2.__dict__) - diff --git a/misc/pylib/robofab/interface/__init__.py b/misc/pylib/robofab/interface/__init__.py deleted file mode 100755 index 154c42b70..000000000 --- a/misc/pylib/robofab/interface/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" - -Directory for interface related modules. Stuff like widgets, -dialog modules. Please keep them sorted by platform. - -interfaces/all : modules that are platform independent -interfaces/mac : modules that are mac specific -interfaces/win : modules that are windows specific - -""" - - - - diff --git a/misc/pylib/robofab/interface/all/__init__.py b/misc/pylib/robofab/interface/all/__init__.py deleted file mode 100755 index 154c42b70..000000000 --- a/misc/pylib/robofab/interface/all/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" - -Directory for interface related modules. Stuff like widgets, -dialog modules. Please keep them sorted by platform. - -interfaces/all : modules that are platform independent -interfaces/mac : modules that are mac specific -interfaces/win : modules that are windows specific - -""" - - - - diff --git a/misc/pylib/robofab/interface/all/dialogs.py b/misc/pylib/robofab/interface/all/dialogs.py deleted file mode 100755 index 8fdfe847b..000000000 --- a/misc/pylib/robofab/interface/all/dialogs.py +++ /dev/null @@ -1,278 +0,0 @@ -""" - - - Restructured dialogs for Robofab - - dialog file dialogs - -* FontLab 5.04 10.6 dialogKit fl internal * theoretically that should work under windows as well, untested -* FontLab 5.1 10.6 dialogKit fl internal -* FontLab 5.1 10.7 raw cocao fl internal - Glyphs any vanilla vanilla - RoboFont any vanilla vanilla - - This module does a fair amount of sniffing in order to guess - which dialogs to load. Linux and Windows environments are - underrepresented at the moment. Following the prototypes in dialogs_default.py - it is possible (with knowledge of the platform) to extend support. - - The platformApplicationSupport table contains very specific - versions with which certain apps need to work. Moving forward, - it is likely that these versions will change and need to be updated. - - # this calls the new dialogs infrastructure: - from robofab.interface.all.dialogs import Message - - # this calls the old original legacy dialogs infrastructure: - from robofab.interface.all.dialogs_legacy import Message - -""" - - - -# determine platform and application -import sys, os -import platform as _platform - -__verbose__ = False - -platform = None -platformVersion = None -application = None -applicationVersion = None - -if sys.platform in ( - 'mac', - 'darwin', - ): - platform = "mac" - v = _platform.mac_ver()[0] - platformVersion = float('.'.join(v.split('.')[:2])) -elif sys.platform in ( - 'linux1', - 'linux2', # Ubuntu = others? - ): - platform = "linux" -elif os.name == 'nt': - platform = "win" - -# determine application - -try: - # FontLab - # alternative syntax to cheat on the FL import filtering in RF - __import__("FL") - application = "fontlab" - #applicationVersion = fl.version -except ImportError: - pass - -if application is None: - try: - # RoboFont - import mojo - application = 'robofont' - try: - from AppKit import NSBundle - b = NSBundle.mainBundle() - applicationVersion = b.infoDictionary()["CFBundleVersion"] - except ImportError: - pass - except ImportError: - pass - -if application is None: - try: - # Glyphs - import GlyphsApp - application = "glyphs" - except ImportError: - pass - -if application is None: - try: - # fontforge - # note that in some configurations, fontforge can be imported in other pythons as well - # so the availability of the fontforge module is no garuantee that we are in fontforge. - import fontforge - application = "fontforge" - except ImportError: - pass - -pyVersion = sys.version_info[:3] - -# with that out of the way, perhaps we can have a look at where we are -# and which modules we have available. This maps any number of platform / application -# combinations so an independent list of module names. That should make it -# possible to map multiple things to one module. - -platformApplicationSupport = [ - # - # switchboard for platform, application, python version -> dialog implementations - # platform applicatiom python sub module - # | | | | - ('mac', 'fontlab', (2,3,5), "dialogs_fontlab_legacy1"), - # because FontLab 5.01 and earlier on 2.3.5 can run EasyDialogs - # | | | | - # because FontLab 5.1 on mac 10.6 should theoretically be able to run cocoa dialogs, - # but they are very unreliable. So until we know what's going on, FL5.1 on 10.6 - # is going to have to live with DialogKit dialogs. - # | | | | - ('mac', 'fontlab', None, "dialogs_fontlab_legacy2"), - # because FontLab 5.1 on mac, 10.7+ should run cocoa / vanilla - # | | | | - ('mac', None, None, "dialogs_mac_vanilla"), - # perhaps nonelab scripts can run vanilla as well? - # | | | | - ('win', None, None, "dialogs_legacy"), - # older windows stuff might be able to use the legacy dialogs -] - -platformModule = None -foundPlatformModule = False -dialogs = {} - -if __verbose__: - print "robofab.interface.all __init__ - finding out where we were." - -# do we have a support module? -for pl, app, py, platformApplicationModuleName in platformApplicationSupport: - if __verbose__: - print "looking at", pl, app, py, platformApplicationModuleName - if pl is None or pl == platform: - if app is None or app == application: - if py is None or py == pyVersion: - break - if __verbose__: - print "nope" - -if __verbose__: - print "searched for", pl, app, py, platformApplicationModuleName - -# preload the namespace with default functions that do nothing but raise NotImplementedError -from robofab.interface.all.dialogs_default import * - -# now import the module we selected. -if platformApplicationModuleName == "dialogs_fontlab_legacy1": - try: - from robofab.interface.all.dialogs_fontlab_legacy1 import * - foundPlatformModule = True - if __verbose__: - print "loaded robofab.interface.all.dialogs_fontlab_legacy1" - if platform == "mac": - from robofab.interface.mac.getFileOrFolder import GetFile, GetFileOrFolder - except ImportError: - print "can't import", platformApplicationModuleName - -elif platformApplicationModuleName == "dialogs_fontlab_legacy2": - try: - from robofab.interface.all.dialogs_fontlab_legacy2 import * - foundPlatformModule = True - if __verbose__: - print "loaded robofab.interface.all.dialogs_fontlab_legacy2" - if platform == "mac": - # - # - # - # - # - from robofab.interface.all.dialogs_legacy import AskString, TwoChecks, TwoFields, SelectGlyph, FindGlyph, OneList, SearchList, SelectFont, SelectGlyph - except ImportError: - print "can't import", platformApplicationModuleName - -elif platformApplicationModuleName == "dialogs_mac_vanilla": - try: - from robofab.interface.all.dialogs_mac_vanilla import * - foundPlatformModule = True - if __verbose__: - print "loaded robofab.interface.all.dialogs_mac_vanilla" - except ImportError: - print "can't import", platformApplicationModuleName - -elif platformApplicationModuleName == "dialogs_legacy": - try: - from robofab.interface.all.dialogs_legacy import * - foundPlatformModule = True - if __verbose__: - print "loaded robofab.interface.all.dialogs_legacy" - except ImportError: - print "can't import", platformApplicationModuleName - - -__all__ = [ - "AskString", - "AskYesNoCancel", - "FindGlyph", - "GetFile", - "GetFolder", - "GetFileOrFolder", - "Message", - "OneList", - "PutFile", - "SearchList", - "SelectFont", - "SelectGlyph", - "TwoChecks", - "TwoFields", - "ProgressBar", -] - - -def test(): - """ This is a test that prints the available functions and where they're imported from. - The report can be useful for debugging. - - For instance: - - from robofab.interface.all.dialogs import test - test() - - testing RoboFab Dialogs: - python version: (2, 7, 1) - platform: mac - application: None - applicationVersion: None - platformVersion: 10.7 - looking for module: dialogs_mac_vanilla - did we find it? True - - Available dialogs and source: - AskString robofab.interface.all.dialogs_mac_vanilla - AskYesNoCancel robofab.interface.all.dialogs_mac_vanilla - FindGlyph robofab.interface.all.dialogs_mac_vanilla - GetFile robofab.interface.all.dialogs_mac_vanilla - GetFolder robofab.interface.all.dialogs_mac_vanilla - GetFileOrFolder robofab.interface.all.dialogs_mac_vanilla - Message robofab.interface.all.dialogs_mac_vanilla - OneList robofab.interface.all.dialogs_mac_vanilla - PutFile robofab.interface.all.dialogs_mac_vanilla - SearchList robofab.interface.all.dialogs_mac_vanilla - SelectFont robofab.interface.all.dialogs_mac_vanilla - SelectGlyph robofab.interface.all.dialogs_mac_vanilla - TwoChecks robofab.interface.all.dialogs_default - TwoFields robofab.interface.all.dialogs_default - ProgressBar robofab.interface.all.dialogs_mac_vanilla - - """ - - print - print "testing RoboFab Dialogs:" - print "\tpython version:", pyVersion - print "\tplatform:", platform - print "\tapplication:", application - print "\tapplicationVersion:", applicationVersion - print "\tplatformVersion:", platformVersion - print "\tlooking for module:", platformApplicationModuleName - print "\t\tdid we find it?", foundPlatformModule - - print - print "Available dialogs and source:" - for name in __all__: - if name in globals().keys(): - print "\t", name, "\t", globals()[name].__module__ - else: - print "\t", name, "\t not loaded." - -if __name__ == "__main__": - test() - diff --git a/misc/pylib/robofab/interface/all/dialogs_default.py b/misc/pylib/robofab/interface/all/dialogs_default.py deleted file mode 100644 index c513aa9b7..000000000 --- a/misc/pylib/robofab/interface/all/dialogs_default.py +++ /dev/null @@ -1,76 +0,0 @@ -""" - - Dialog prototypes. - - These are loaded before any others. So if a specific platform implementation doesn't - have all functions, these will make sure a NotImplemtedError is raised. - - http://www.robofab.org/tools/dialogs.html - -""" - -__all__ = [ - "AskString", - "AskYesNoCancel", - "FindGlyph", - "GetFile", - "GetFolder", - "GetFileOrFolder", - "Message", - "OneList", - "PutFile", - "SearchList", - "SelectFont", - "SelectGlyph", - "TwoChecks", - "TwoFields", - "ProgressBar", -] - -# start with all the defaults. - -def AskString(message, value='', title='RoboFab'): - raise NotImplementedError - -def AskYesNoCancel(message, title='RoboFab', default=0): - raise NotImplementedError - -def FindGlyph(font, message="Search for a glyph:", title='RoboFab'): - raise NotImplementedError - -def GetFile(message=None): - raise NotImplementedError - -def GetFolder(message=None): - raise NotImplementedError - -def GetFileOrFolder(message=None): - raise NotImplementedError - -def Message(message, title='RoboFab'): - raise NotImplementedError - -def OneList(list, message="Select an item:", title='RoboFab'): - raise PendingDeprecationWarning - -def PutFile(message=None, fileName=None): - raise NotImplementedError - -def SearchList(list, message="Select an item:", title='RoboFab'): - raise NotImplementedError - -def SelectFont(message="Select a font:", title='RoboFab'): - raise NotImplementedError - -def SelectGlyph(font, message="Select a glyph:", title='RoboFab'): - raise NotImplementedError - -def TwoChecks(title_1="One", title_2="Two", value1=1, value2=1, title='RoboFab'): - raise PendingDeprecationWarning - -def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'): - raise PendingDeprecationWarning - -class ProgressBar(object): - pass - diff --git a/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy1.py b/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy1.py deleted file mode 100644 index 73c3d7a15..000000000 --- a/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy1.py +++ /dev/null @@ -1,73 +0,0 @@ -""" - - Dialogs for FontLab < 5.1. - - This one should be loaded for various platforms, using dialogKit - http://www.robofab.org/tools/dialogs.html - -""" - -from FL import * -from dialogKit import ModalDialog, Button, TextBox, EditText - -__all__ = [ - #"AskString", - #"AskYesNoCancel", - #"FindGlyph", - "GetFile", - "GetFolder", - #"Message", - #"OneList", - #"PutFile", - #"SearchList", - #"SelectFont", - #"SelectGlyph", - #"TwoChecks", - #"TwoFields", - "ProgressBar", -] - - -def GetFile(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None): - strFilter = "All Files (*.*)|*.*|" - defaultExt = "" - # using fontlab's internal file dialogs - return fl.GetFileName(1, defaultExt, message, strFilter) - -def GetFolder(message=None, title=None, directory=None, allowsMultipleSelection=False): - # using fontlab's internal file dialogs - if message is None: - message = "" - return fl.GetPathName(message) - -def PutFile(message=None, fileName=None): - # using fontlab's internal file dialogs - # message is not used - if message is None: - message = "" - if fileName is None: - fileName = "" - defaultExt = "" - return fl.GetFileName(0, defaultExt, fileName, '') - -class ProgressBar(object): - - def __init__(self, title="RoboFab...", ticks=0, label=""): - self._tickValue = 1 - fl.BeginProgress(title, ticks) - - def getCurrentTick(self): - return self._tickValue - - def tick(self, tickValue=None): - if not tickValue: - tickValue = self._tickValue - fl.TickProgress(tickValue) - self._tickValue = tickValue + 1 - - def label(self, label): - pass - - def close(self): - fl.EndProgress() - diff --git a/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy2.py b/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy2.py deleted file mode 100644 index 460b73f1e..000000000 --- a/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy2.py +++ /dev/null @@ -1,373 +0,0 @@ -""" - - Dialogs for FontLab 5.1. - This might work in future versions of FontLab as well. - This is basically a butchered version of vanilla.dialogs. - No direct import of, or dependency on Vanilla - - March 7 2012 - It seems only the dialogs that deal with the file system - need to be replaced, the other dialogs still work. - As we're not entirely sure whether it is worth to maintain - these dialogs, let's fix the imports in dialogs.py. - - This is the phenolic aldehyde version of dialogs. - -""" - -#__import__("FL") -from FL import * - -from Foundation import NSObject -from AppKit import NSApplication, NSInformationalAlertStyle, objc, NSAlert, NSAlertFirstButtonReturn, NSAlertSecondButtonReturn, NSAlertThirdButtonReturn, NSSavePanel, NSOKButton, NSOpenPanel - -NSApplication.sharedApplication() - -__all__ = [ -# "AskString", - "AskYesNoCancel", -# "FindGlyph", - "GetFile", - "GetFolder", - "GetFileOrFolder", - "Message", -# "OneList", - "PutFile", -# "SearchList", -# "SelectFont", -# "SelectGlyph", -# "TwoChecks", -# "TwoFields", - "ProgressBar", -] - - -class BaseMessageDialog(NSObject): - - def initWithMessageText_informativeText_alertStyle_buttonTitlesValues_window_resultCallback_(self, - messageText="", - informativeText="", - alertStyle=NSInformationalAlertStyle, - buttonTitlesValues=None, - parentWindow=None, - resultCallback=None): - if buttonTitlesValues is None: - buttonTitlesValues = [] - self = super(BaseMessageDialog, self).init() - self.retain() - self._resultCallback = resultCallback - self._buttonTitlesValues = buttonTitlesValues - # - alert = NSAlert.alloc().init() - alert.setMessageText_(messageText) - alert.setInformativeText_(informativeText) - alert.setAlertStyle_(alertStyle) - for buttonTitle, value in buttonTitlesValues: - alert.addButtonWithTitle_(buttonTitle) - self._value = None - code = alert.runModal() - self._translateValue(code) - return self - - def _translateValue(self, code): - if code == NSAlertFirstButtonReturn: - value = 1 - elif code == NSAlertSecondButtonReturn: - value = 2 - elif code == NSAlertThirdButtonReturn: - value = 3 - else: - value = code - NSAlertThirdButtonReturn + 3 - self._value = self._buttonTitlesValues[value-1][1] - - def windowWillClose_(self, notification): - self.autorelease() - - -class BasePutGetPanel(NSObject): - - def initWithWindow_resultCallback_(self, parentWindow=None, resultCallback=None): - self = super(BasePutGetPanel, self).init() - self.retain() - self._parentWindow = parentWindow - self._resultCallback = resultCallback - return self - - def windowWillClose_(self, notification): - self.autorelease() - - -class PutFilePanel(BasePutGetPanel): - - def initWithWindow_resultCallback_(self, parentWindow=None, resultCallback=None): - self = super(PutFilePanel, self).initWithWindow_resultCallback_(parentWindow, resultCallback) - self.messageText = None - self.title = None - self.fileTypes = None - self.directory = None - self.fileName = None - self.canCreateDirectories = True - self.accessoryView = None - self._result = None - return self - - def run(self): - panel = NSSavePanel.alloc().init() - if self.messageText: - panel.setMessage_(self.messageText) - if self.title: - panel.setTitle_(self.title) - if self.directory: - panel.setDirectory_(self.directory) - if self.fileTypes: - panel.setAllowedFileTypes_(self.fileTypes) - panel.setCanCreateDirectories_(self.canCreateDirectories) - panel.setCanSelectHiddenExtension_(True) - panel.setAccessoryView_(self.accessoryView) - if self._parentWindow is not None: - panel.beginSheetForDirectory_file_modalForWindow_modalDelegate_didEndSelector_contextInfo_( - self.directory, self.fileName, self._parentWindow, self, "savePanelDidEnd:returnCode:contextInfo:", 0) - else: - isOK = panel.runModalForDirectory_file_(self.directory, self.fileName) - if isOK == NSOKButton: - self._result = panel.filename() - - def savePanelDidEnd_returnCode_contextInfo_(self, panel, returnCode, context): - panel.close() - if returnCode: - self._result = panel.filename() - if self._resultCallback is not None: - self._resultCallback(self._result) - - savePanelDidEnd_returnCode_contextInfo_ = objc.selector(savePanelDidEnd_returnCode_contextInfo_, signature="v@:@ii") - - -class GetFileOrFolderPanel(BasePutGetPanel): - - def initWithWindow_resultCallback_(self, parentWindow=None, resultCallback=None): - self = super(GetFileOrFolderPanel, self).initWithWindow_resultCallback_(parentWindow, resultCallback) - self.messageText = None - self.title = None - self.directory = None - self.fileName = None - self.fileTypes = None - self.allowsMultipleSelection = False - self.canChooseDirectories = True - self.canChooseFiles = True - self.resolvesAliases = True - self._result = None - return self - - def run(self): - panel = NSOpenPanel.alloc().init() - if self.messageText: - panel.setMessage_(self.messageText) - if self.title: - panel.setTitle_(self.title) - if self.directory: - panel.setDirectory_(self.directory) - if self.fileTypes: - panel.setAllowedFileTypes_(self.fileTypes) - panel.setCanChooseDirectories_(self.canChooseDirectories) - panel.setCanChooseFiles_(self.canChooseFiles) - panel.setAllowsMultipleSelection_(self.allowsMultipleSelection) - panel.setResolvesAliases_(self.resolvesAliases) - if self._parentWindow is not None: - panel.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_( - self.directory, self.fileName, self.fileTypes, self._parentWindow, self, "openPanelDidEnd:returnCode:contextInfo:", 0) - else: - isOK = panel.runModalForDirectory_file_types_(self.directory, self.fileName, self.fileTypes) - if isOK == NSOKButton: - self._result = panel.filenames() - - def openPanelDidEnd_returnCode_contextInfo_(self, panel, returnCode, context): - panel.close() - if returnCode: - self._result = panel.filenames() - if self._resultCallback is not None: - self._resultCallback(self._result) - - openPanelDidEnd_returnCode_contextInfo_ = objc.selector(openPanelDidEnd_returnCode_contextInfo_, signature="v@:@ii") - - -def Message(message="", title='noLongerUsed', informativeText=""): - """Legacy robofab dialog compatible wrapper.""" - #def _message(messageText="", informativeText="", alertStyle=NSInformationalAlertStyle, parentWindow=None, resultCallback=None): - resultCallback = None - alert = BaseMessageDialog.alloc().initWithMessageText_informativeText_alertStyle_buttonTitlesValues_window_resultCallback_( - messageText=message, - informativeText=informativeText, - alertStyle=NSInformationalAlertStyle, - buttonTitlesValues=[("OK", 1)], - parentWindow=None, - resultCallback=None) - if resultCallback is None: - return 1 - - -def AskYesNoCancel(message, title='noLongerUsed', default=None, informativeText=""): - """ - AskYesNoCancel Dialog - - message the string - title* a title of the window - (may not be supported everywhere) - default* index number of which button should be default - (i.e. respond to return) - informativeText* A string with secundary information - - * may not be supported everywhere - """ - parentWindow = None - alert = BaseMessageDialog.alloc().initWithMessageText_informativeText_alertStyle_buttonTitlesValues_window_resultCallback_( - messageText=message, - informativeText=informativeText, - alertStyle=NSInformationalAlertStyle, - buttonTitlesValues=[("Cancel", -1), ("Yes", 1), ("No", 0)], - parentWindow=None, - resultCallback=None) - return alert._value - -def _askYesNo(messageText="", informativeText="", alertStyle=NSInformationalAlertStyle, parentWindow=None, resultCallback=None): - parentWindow = None - alert = BaseMessageDialog.alloc().initWithMessageText_informativeText_alertStyle_buttonTitlesValues_window_resultCallback_( - messageText=messageText, informativeText=informativeText, alertStyle=alertStyle, buttonTitlesValues=[("Yes", 1), ("No", 0)], parentWindow=parentWindow, resultCallback=resultCallback) - if resultCallback is None: - return alert._value - -def GetFile(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None): - """ Legacy robofab dialog compatible wrapper. - This will select UFO on OSX 10.7, FL5.1 - """ - parentWindow = None - resultCallback=None - basePanel = GetFileOrFolderPanel.alloc().initWithWindow_resultCallback_(parentWindow, resultCallback) - basePanel.messageText = message - basePanel.title = title - basePanel.directory = directory - basePanel.fileName = fileName - basePanel.fileTypes = fileTypes - basePanel.allowsMultipleSelection = allowsMultipleSelection - basePanel.canChooseDirectories = False - basePanel.canChooseFiles = True - basePanel.run() - if basePanel._result is None: - return None - if not allowsMultipleSelection: - # compatibly return only one as we expect - return str(list(basePanel._result)[0]) - else: - # return more if we explicitly expect - return [str(n) for n in list(basePanel._result)] - -def GetFolder(message=None, title=None, directory=None, allowsMultipleSelection=False): - parentWindow = None - resultCallback = None - basePanel = GetFileOrFolderPanel.alloc().initWithWindow_resultCallback_(parentWindow, resultCallback) - basePanel.messageText = message - basePanel.title = title - basePanel.directory = directory - basePanel.allowsMultipleSelection = allowsMultipleSelection - basePanel.canChooseDirectories = True - basePanel.canChooseFiles = False - basePanel.run() - if basePanel._result is None: - return None - if not allowsMultipleSelection: - # compatibly return only one as we expect - return str(list(basePanel._result)[0]) - else: - # return more if we explicitly expect - return [str(n) for n in list(basePanel._result)] - -def GetFileOrFolder(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None, parentWindow=None, resultCallback=None): - parentWindow = None - basePanel = GetFileOrFolderPanel.alloc().initWithWindow_resultCallback_(parentWindow, resultCallback) - basePanel.messageText = message - basePanel.title = title - basePanel.directory = directory - basePanel.fileName = fileName - basePanel.fileTypes = fileTypes - basePanel.allowsMultipleSelection = allowsMultipleSelection - basePanel.canChooseDirectories = True - basePanel.canChooseFiles = True - basePanel.run() - if basePanel._result is None: - return None - if not allowsMultipleSelection: - # compatibly return only one as we expect - return str(list(basePanel._result)[0]) - else: - # return more if we explicitly expect - return [str(n) for n in list(basePanel._result)] - -def PutFile(message=None, title=None, directory=None, fileName=None, canCreateDirectories=True, fileTypes=None): - parentWindow = None - resultCallback=None - accessoryView=None - basePanel = PutFilePanel.alloc().initWithWindow_resultCallback_(parentWindow, resultCallback) - basePanel.messageText = message - basePanel.title = title - basePanel.directory = directory - basePanel.fileName = fileName - basePanel.fileTypes = fileTypes - basePanel.canCreateDirectories = canCreateDirectories - basePanel.accessoryView = accessoryView - basePanel.run() - return str(basePanel._result) - - -class ProgressBar(object): - - def __init__(self, title="RoboFab...", ticks=0, label=""): - self._tickValue = 1 - fl.BeginProgress(title, ticks) - - def getCurrentTick(self): - return self._tickValue - - def tick(self, tickValue=None): - if not tickValue: - tickValue = self._tickValue - fl.TickProgress(tickValue) - self._tickValue = tickValue + 1 - - def label(self, label): - pass - - def close(self): - fl.EndProgress() - - -# we seem to have problems importing from here. -# so let's see what happens if we make the robofab compatible wrappers here as well. - -# start with all the defaults. - -#def AskString(message, value='', title='RoboFab'): -# raise NotImplementedError - -#def FindGlyph(aFont, message="Search for a glyph:", title='RoboFab'): -# raise NotImplementedError - -#def OneList(list, message="Select an item:", title='RoboFab'): -# raise NotImplementedError - -#def PutFile(message=None, fileName=None): -# raise NotImplementedError - -#def SearchList(list, message="Select an item:", title='RoboFab'): -# raise NotImplementedError - -#def SelectFont(message="Select a font:", title='RoboFab'): -# raise NotImplementedError - -#def SelectGlyph(font, message="Select a glyph:", title='RoboFab'): -# raise NotImplementedError - -#def TwoChecks(title_1="One", title_2="Two", value1=1, value2=1, title='RoboFab'): -# raise NotImplementedError - -#def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'): -# raise NotImplementedError - diff --git a/misc/pylib/robofab/interface/all/dialogs_legacy.py b/misc/pylib/robofab/interface/all/dialogs_legacy.py deleted file mode 100755 index 8223a7d64..000000000 --- a/misc/pylib/robofab/interface/all/dialogs_legacy.py +++ /dev/null @@ -1,737 +0,0 @@ - -""" - -Dialogs. -Cross-platform and cross-application compatible. Some of them anyway. -(Not all dialogs work on PCs outside of FontLab. Some dialogs are for FontLab only. Sorry.) - -Mac and FontLab implementation written by the RoboFab development team. -PC implementation by Eigi Eigendorf and is (C)2002 Eigi Eigendorf. - -""" - -import os -import sys -from robofab import RoboFabError -from warnings import warn - -MAC = False -PC = False -haveMacfs = False - -if sys.platform in ('mac', 'darwin'): - MAC = True -elif os.name == 'nt': - PC = True -else: - warn("dialogs.py only supports Mac and PC platforms.") -pyVersion = sys.version_info[:3] - -inFontLab = False -try: - from FL import * - inFontLab = True -except ImportError: pass - - -try: - import W - hasW = True -except ImportError: - hasW = False - -try: - import dialogKit - hasDialogKit = True -except ImportError: - hasDialogKit = False - -try: - import EasyDialogs - hasEasyDialogs = True -except: - hasEasyDialogs = False - -if MAC: - if pyVersion < (2, 3, 0): - import macfs - haveMacfs = True -elif PC and not inFontLab: - from win32com.shell import shell - import win32ui - import win32con - - -def _raisePlatformError(dialog): - """error raiser""" - if MAC: - p = 'Macintosh' - elif PC: - p = 'PC' - else: - p = sys.platform - raise RoboFabError("%s is not currently available on the %s platform"%(dialog, p)) - - -class _FontLabDialogOneList: - """A one list dialog for FontLab. This class should not be called directly. Use the OneList function.""" - - def __init__(self, list, message, title='RoboFab'): - self.message = message - self.selected = None - self.list = list - self.d = Dialog(self) - self.d.size = Point(250, 250) - self.d.title = title - self.d.Center() - self.d.AddControl(LISTCONTROL, Rect(12, 30, 238, 190), "list", STYLE_LIST, self.message) - self.list_index = 0 - - def Run(self): - return self.d.Run() - - def on_cancel(self, code): - self.selected = None - - def on_ok(self, code): - self.d.GetValue('list') - # Since FLS v5.2, the GetValue() method of the Dialog() class returns - # a 'wrong' index value from the specified LISTCONTROL. - # If the selected index is n, it will return n-1. For example, when - # the index is 1, it returns 0; when it's 2, it returns 1, and so on. - # If the selection is empty, FLS v5.2 returns -2, while the old v5.0 - # returned None. - # See also: - # - http://forum.fontlab.com/index.php?topic=8807.0 - # - http://forum.fontlab.com/index.php?topic=9003.0 - # - # Edited based on feedback from Adam Twardoch - if fl.buildnumber > 4600 and sys.platform == 'win32': - if self.list_index == -2: - self.selected = None - else: - self.selected = self.list_index + 1 - else: - self.selected = self.list_index - - -class _FontLabDialogSearchList: - """A dialog for searching through a list. It contains a text field and a results list FontLab. This class should not be called directly. Use the SearchList function.""" - - def __init__(self, aList, message, title="RoboFab"): - self.d = Dialog(self) - self.d.size = Point(250, 290) - self.d.title = title - self.d.Center() - - self.message = message - self._fullContent = aList - self.possibleHits = list(aList) - self.possibleHits.sort() - self.possibleHits_index = 0 - self.entryField = "" - self.selected = None - - self.d.AddControl(STATICCONTROL, Rect(10, 10, 240, 30), "message", STYLE_LABEL, message) - self.d.AddControl(EDITCONTROL, Rect(10, 30, 240, aAUTO), "entryField", STYLE_EDIT, "") - self.d.AddControl(LISTCONTROL, Rect(12, 60, 238, 230), "possibleHits", STYLE_LIST, "") - - - def run(self): - self.d.Run() - - def on_entryField(self, code): - self.d.GetValue("entryField") - entry = self.entryField - count = len(entry) - possibleHits = [ - i for i in self._fullContent - if len(i) >= count - and i[:count] == entry - ] - possibleHits.sort() - self.possibleHits = possibleHits - self.possibleHits_index = 0 - self.d.PutValue("possibleHits") - - def on_ok(self, code): - self.d.GetValue("possibleHits") - sel = self.possibleHits_index - if sel == -1: - self.selected = None - else: - self.selected = self.possibleHits[sel] - - def on_cancel(self, code): - self.selected = None - - -class _FontLabDialogTwoFields: - """A two field dialog for FontLab. This class should not be called directly. Use the TwoFields function.""" - - def __init__(self, title_1, value_1, title_2, value_2, title='RoboFab'): - self.d = Dialog(self) - self.d.size = Point(200, 125) - self.d.title = title - self.d.Center() - self.d.AddControl(EDITCONTROL, Rect(120, 10, aIDENT2, aAUTO), "v1edit", STYLE_EDIT, title_1) - self.d.AddControl(EDITCONTROL, Rect(120, 40, aIDENT2, aAUTO), "v2edit", STYLE_EDIT, title_2) - self.v1edit = value_1 - self.v2edit = value_2 - - def Run(self): - return self.d.Run() - - def on_cancel(self, code): - self.v1edit = None - self.v2edit = None - - def on_ok(self, code): - self.d.GetValue("v1edit") - self.d.GetValue("v2edit") - self.v1 = self.v1edit - self.v2 = self.v2edit - -class _FontLabDialogTwoChecks: - """A two check box dialog for FontLab. This class should not be called directly. Use the TwoChecks function.""" - - def __init__(self, title_1, title_2, value1=1, value2=1, title='RoboFab'): - self.d = Dialog(self) - self.d.size = Point(200, 105) - self.d.title = title - self.d.Center() - self.d.AddControl(CHECKBOXCONTROL, Rect(10, 10, aIDENT2, aAUTO), "check1", STYLE_CHECKBOX, title_1) - self.d.AddControl(CHECKBOXCONTROL, Rect(10, 30, aIDENT2, aAUTO), "check2", STYLE_CHECKBOX, title_2) - self.check1 = value1 - self.check2 = value2 - - def Run(self): - return self.d.Run() - - def on_cancel(self, code): - self.check1 = None - self.check2 = None - - def on_ok(self, code): - self.d.GetValue("check1") - self.d.GetValue("check2") - - -class _FontLabDialogAskString: - """A one simple string prompt dialog for FontLab. This class should not be called directly. Use the GetString function.""" - - def __init__(self, message, value, title='RoboFab'): - self.d = Dialog(self) - self.d.size = Point(350, 130) - self.d.title = title - self.d.Center() - self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, aAUTO), "label", STYLE_LABEL, message) - self.d.AddControl(EDITCONTROL, Rect(aIDENT, 40, aIDENT, aAUTO), "value", STYLE_EDIT, '') - self.value=value - - def Run(self): - return self.d.Run() - - def on_cancel(self, code): - self.value = None - - def on_ok(self, code): - self.d.GetValue("value") - -class _FontLabDialogMessage: - """A simple message dialog for FontLab. This class should not be called directly. Use the SimpleMessage function.""" - - def __init__(self, message, title='RoboFab'): - self.d = Dialog(self) - self.d.size = Point(350, 130) - self.d.title = title - self.d.Center() - self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, 80), "label", STYLE_LABEL, message) - - def Run(self): - return self.d.Run() - -class _FontLabDialogGetYesNoCancel: - """A yes no cancel message dialog for FontLab. This class should not be called directly. Use the YesNoCancel function.""" - - def __init__(self, message, title='RoboFab'): - self.d = Dialog(self) - self.d.size = Point(350, 130) - self.d.title = title - self.d.Center() - self.d.ok = 'Yes' - self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, 80), "label", STYLE_LABEL, message) - self.d.AddControl(BUTTONCONTROL, Rect(100, 95, 172, 115), "button", STYLE_BUTTON, "No") - self.value = 0 - - def Run(self): - return self.d.Run() - - def on_ok(self, code): - self.value = 1 - - def on_cancel(self, code): - self.value = -1 - - def on_button(self, code): - self.value = 0 - self.d.End() - - -class _MacOneListW: - """A one list dialog for Macintosh. This class should not be called directly. Use the OneList function.""" - - def __init__(self, list, message='Make a selection'): - import W - self.list = list - self.selected = None - self.w = W.ModalDialog((200, 240)) - self.w.message = W.TextBox((10, 10, -10, 30), message) - self.w.list = W.List((10, 35, -10, -50), list) - self.w.l = W.HorizontalLine((10, -40, -10, 1), 1) - self.w.cancel = W.Button((10, -30, 87, -10), 'Cancel', self.cancel) - self.w.ok = W.Button((102, -30, 88, -10), 'OK', self.ok) - self.w.setdefaultbutton(self.w.ok) - self.w.bind('cmd.', self.w.cancel.push) - self.w.open() - - def ok(self): - if len(self.w.list.getselection()) == 1: - self.selected = self.w.list.getselection()[0] - self.w.close() - - def cancel(self): - self.selected = None - self.w.close() - -class _MacTwoChecksW: - """ Version using W """ - - def __init__(self, title_1, title_2, value1=1, value2=1, title='RoboFab'): - import W - self.check1 = value1 - self.check2 = value2 - self.w = W.ModalDialog((200, 100)) - self.w.check1 = W.CheckBox((10, 10, -10, 16), title_1, value=value1) - self.w.check2 = W.CheckBox((10, 35, -10, 16), title_2, value=value2) - self.w.l = W.HorizontalLine((10, 60, -10, 1), 1) - self.w.cancel = W.Button((10, 70, 85, 20), 'Cancel', self.cancel) - self.w.ok = W.Button((105, 70, 85, 20), 'OK', self.ok) - self.w.setdefaultbutton(self.w.ok) - self.w.bind('cmd.', self.w.cancel.push) - self.w.open() - - def ok(self): - self.check1 = self.w.check1.get() - self.check2 = self.w.check2.get() - self.w.close() - - def cancel(self): - self.check1 = None - self.check2 = None - self.w.close() - - -class ProgressBar: - def __init__(self, title='RoboFab...', ticks=0, label=''): - """ - A progress bar. - Availability: FontLab, Mac - """ - self._tickValue = 1 - - if inFontLab: - fl.BeginProgress(title, ticks) - elif MAC and hasEasyDialogs: - import EasyDialogs - self._bar = EasyDialogs.ProgressBar(title, maxval=ticks, label=label) - else: - _raisePlatformError('Progress') - - def getCurrentTick(self): - return self._tickValue - - - def tick(self, tickValue=None): - """ - Tick the progress bar. - Availability: FontLab, Mac - """ - if not tickValue: - tickValue = self._tickValue - - if inFontLab: - fl.TickProgress(tickValue) - elif MAC: - self._bar.set(tickValue) - else: - pass - - self._tickValue = tickValue + 1 - - def label(self, label): - """ - Set the label on the progress bar. - Availability: Mac - """ - if inFontLab: - pass - elif MAC: - self._bar.label(label) - else: - pass - - - def close(self): - """ - Close the progressbar. - Availability: FontLab, Mac - """ - if inFontLab: - fl.EndProgress() - elif MAC: - del self._bar - else: - pass - - -def SelectFont(message="Select a font:", title='RoboFab'): - """ - Returns font instance if there is one, otherwise it returns None. - Availability: FontLab - """ - from robofab.world import RFont - if inFontLab: - list = [] - for i in range(fl.count): - list.append(fl[i].full_name) - name = OneList(list, message, title) - if name is None: - return None - else: - return RFont(fl[list.index(name)]) - else: - _raisePlatformError('SelectFont') - -def SelectGlyph(font, message="Select a glyph:", title='RoboFab'): - """ - Returns glyph instance if there is one, otherwise it returns None. - Availability: FontLab - """ - from fontTools.misc.textTools import caselessSort - - if inFontLab: - tl = font.keys() - list = caselessSort(tl) - glyphname = OneList(list, message, title) - if glyphname is None: - return None - else: - return font[glyphname] - else: - _raisePlatformError('SelectGlyph') - -def FindGlyph(font, message="Search for a glyph:", title='RoboFab'): - """ - Returns glyph instance if there is one, otherwise it returns None. - Availability: FontLab - """ - - if inFontLab: - glyphname = SearchList(font.keys(), message, title) - if glyphname is None: - return None - else: - return font[glyphname] - else: - _raisePlatformError('SelectGlyph') - -def OneList(list, message="Select an item:", title='RoboFab'): - """ - Returns selected item, otherwise it returns None. - Availability: FontLab, Macintosh - """ - if inFontLab: - ol = _FontLabDialogOneList(list, message) - ol.Run() - selected = ol.selected - if selected is None: - return None - else: - try: - return list[selected] - except: - return None - elif MAC: - if hasW: - d = _MacOneListW(list, message) - sel = d.selected - if sel is None: - return None - else: - return list[sel] - else: - _raisePlatformError('OneList') - elif PC: - _raisePlatformError('OneList') - -def SearchList(list, message="Select an item:", title='RoboFab'): - """ - Returns selected item, otherwise it returns None. - Availability: FontLab - """ - if inFontLab: - sl = _FontLabDialogSearchList(list, message, title) - sl.run() - selected = sl.selected - if selected is None: - return None - else: - return selected - else: - _raisePlatformError('SearchList') - -def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'): - """ - Returns (value 1, value 2). - Availability: FontLab - """ - if inFontLab: - tf = _FontLabDialogTwoFields(title_1, value_1, title_2, value_2, title) - tf.Run() - try: - v1 = tf.v1 - v2 = tf.v2 - return (v1, v2) - except: - return None - else: - _raisePlatformError('TwoFields') - -def TwoChecks(title_1="One", title_2="Two", value1=1, value2=1, title='RoboFab'): - """ - Returns check value: - 1 if check box 1 is checked - 2 if check box 2 is checked - 3 if both are checked - 0 if neither are checked - None if cancel is clicked. - - Availability: FontLab, Macintosh - """ - tc = None - if inFontLab: - tc = _FontLabDialogTwoChecks(title_1, title_2, value1, value2, title) - tc.Run() - elif MAC: - if hasW: - tc = _MacTwoChecksW(title_1, title_2, value1, value2, title) - else: - _raisePlatformError('TwoChecks') - else: - _raisePlatformError('TwoChecks') - c1 = tc.check1 - c2 = tc.check2 - if c1 == 1 and c2 == 0: - return 1 - elif c1 == 0 and c2 == 1: - return 2 - elif c1 == 1 and c2 == 1: - return 3 - elif c1 == 0 and c2 == 0: - return 0 - else: - return None - -def Message(message, title='RoboFab'): - """ - A simple message dialog. - Availability: FontLab, Macintosh - """ - if inFontLab: - _FontLabDialogMessage(message, title).Run() - elif MAC: - import EasyDialogs - EasyDialogs.Message(message) - else: - _raisePlatformError('Message') - -def AskString(message, value='', title='RoboFab'): - """ - Returns entered string. - Availability: FontLab, Macintosh - """ - if inFontLab: - askString = _FontLabDialogAskString(message, value, title) - askString.Run() - v = askString.value - if v is None: - return None - else: - return v - elif MAC: - import EasyDialogs - askString = EasyDialogs.AskString(message) - if askString is None: - return None - if len(askString) == 0: - return None - else: - return askString - else: - _raisePlatformError('GetString') - -def AskYesNoCancel(message, title='RoboFab', default=0): - """ - Returns 1 for 'Yes', 0 for 'No' and -1 for 'Cancel'. - Availability: FontLab, Macintosh - ("default" argument only available on Macintosh) - """ - if inFontLab: - gync = _FontLabDialogGetYesNoCancel(message, title) - gync.Run() - v = gync.value - return v - elif MAC: - import EasyDialogs - gync = EasyDialogs.AskYesNoCancel(message, default=default) - return gync - else: - _raisePlatformError('GetYesNoCancel') - -def GetFile(message=None): - """ - Select file dialog. Returns path if one is selected. Otherwise it returns None. - Availability: FontLab, Macintosh, PC - """ - path = None - if MAC: - if haveMacfs: - fss, ok = macfs.PromptGetFile(message) - if ok: - path = fss.as_pathname() - else: - from robofab.interface.mac.getFileOrFolder import GetFile - path = GetFile(message) - elif PC: - if inFontLab: - if not message: - message = '' - path = fl.GetFileName(1, message, '', '') - else: - openFlags = win32con.OFN_FILEMUSTEXIST|win32con.OFN_EXPLORER - mode_open = 1 - myDialog = win32ui.CreateFileDialog(mode_open,None,None,openFlags) - myDialog.SetOFNTitle(message) - is_OK = myDialog.DoModal() - if is_OK == 1: - path = myDialog.GetPathName() - else: - _raisePlatformError('GetFile') - return path - -def GetFolder(message=None): - """ - Select folder dialog. Returns path if one is selected. Otherwise it returns None. - Availability: FontLab, Macintosh, PC - """ - path = None - if MAC: - if haveMacfs: - fss, ok = macfs.GetDirectory(message) - if ok: - path = fss.as_pathname() - else: - from robofab.interface.mac.getFileOrFolder import GetFileOrFolder - # This _also_ allows the user to select _files_, but given the - # package/folder dichotomy, I think we have no other choice. - path = GetFileOrFolder(message) - elif PC: - if inFontLab: - if not message: - message = '' - path = fl.GetPathName('', message) - else: - myTuple = shell.SHBrowseForFolder(0, None, message, 64) - try: - path = shell.SHGetPathFromIDList(myTuple[0]) - except: - pass - else: - _raisePlatformError('GetFile') - return path - -GetDirectory = GetFolder - -def PutFile(message=None, fileName=None): - """ - Save file dialog. Returns path if one is entered. Otherwise it returns None. - Availability: FontLab, Macintosh, PC - """ - path = None - if MAC: - if haveMacfs: - fss, ok = macfs.StandardPutFile(message, fileName) - if ok: - path = fss.as_pathname() - else: - import EasyDialogs - path = EasyDialogs.AskFileForSave(message, savedFileName=fileName) - elif PC: - if inFontLab: - if not message: - message = '' - if not fileName: - fileName = '' - path = fl.GetFileName(0, message, fileName, '') - else: - openFlags = win32con.OFN_OVERWRITEPROMPT|win32con.OFN_EXPLORER - mode_save = 0 - myDialog = win32ui.CreateFileDialog(mode_save, None, fileName, openFlags) - myDialog.SetOFNTitle(message) - is_OK = myDialog.DoModal() - if is_OK == 1: - path = myDialog.GetPathName() - else: - _raisePlatformError('GetFile') - return path - - -if __name__=='__main__': - import traceback - - print "dialogs hasW", hasW - print "dialogs hasDialogKit", hasDialogKit - print "dialogs MAC", MAC - print "dialogs PC", PC - print "dialogs inFontLab", inFontLab - print "dialogs hasEasyDialogs", hasEasyDialogs - - def tryDialog(dialogClass, args=None): - print - print "tryDialog:", dialogClass, "with args:", args - try: - if args is not None: - apply(dialogClass, args) - else: - apply(dialogClass) - except: - traceback.print_exc(limit=0) - - tryDialog(TwoChecks, ('hello', 'world', 1, 0, 'ugh')) - tryDialog(TwoFields) - tryDialog(TwoChecks, ('hello', 'world', 1, 0, 'ugh')) - tryDialog(OneList, (['a', 'b', 'c'], 'hello world')) - tryDialog(Message, ('hello world',)) - tryDialog(AskString, ('hello world',)) - tryDialog(AskYesNoCancel, ('hello world',)) - - try: - b = ProgressBar('hello', 50, 'world') - for i in range(50): - if i == 25: - b.label('ugh.') - b.tick(i) - b.close() - except: - traceback.print_exc(limit=0) diff --git a/misc/pylib/robofab/interface/all/dialogs_mac_vanilla.py b/misc/pylib/robofab/interface/all/dialogs_mac_vanilla.py deleted file mode 100644 index d4b76f9c7..000000000 --- a/misc/pylib/robofab/interface/all/dialogs_mac_vanilla.py +++ /dev/null @@ -1,267 +0,0 @@ -""" - - Dialogs for environments that support cocao / vanilla. - -""" - -import vanilla.dialogs -from AppKit import NSApp, NSModalPanelWindowLevel, NSWindowCloseButton, NSWindowZoomButton, NSWindowMiniaturizeButton - -__all__ = [ - "AskString", - "AskYesNoCancel", - "FindGlyph", - "GetFile", - "GetFileOrFolder", - "GetFolder", - "Message", - "OneList", - "PutFile", - "SearchList", - "SelectFont", - "SelectGlyph", -# "TwoChecks", -# "TwoFields", - "ProgressBar", -] - -class _ModalWindow(vanilla.Window): - - nsWindowLevel = NSModalPanelWindowLevel - - def __init__(self, *args, **kwargs): - super(_ModalWindow, self).__init__(*args, **kwargs) - self._window.standardWindowButton_(NSWindowCloseButton).setHidden_(True) - self._window.standardWindowButton_(NSWindowZoomButton).setHidden_(True) - self._window.standardWindowButton_(NSWindowMiniaturizeButton).setHidden_(True) - - def open(self): - super(_ModalWindow, self).open() - self.center() - NSApp().runModalForWindow_(self._window) - - def windowWillClose_(self, notification): - super(_ModalWindow, self).windowWillClose_(notification) - NSApp().stopModal() - - -class _baseWindowController(object): - - def setUpBaseWindowBehavior(self): - self._getValue = None - - self.w.okButton = vanilla.Button((-70, -30, -15, 20), "OK", callback=self.okCallback, sizeStyle="small") - self.w.setDefaultButton(self.w.okButton) - - self.w.closeButton = vanilla.Button((-150, -30, -80, 20), "Cancel", callback=self.closeCallback, sizeStyle="small") - self.w.closeButton.bind(".", ["command"]) - self.w.closeButton.bind(unichr(27), []) - - self.cancelled = False - - def okCallback(self, sender): - self.w.close() - - def closeCallback(self, sender): - self.cancelled = True - self.w.close() - - def get(self): - raise NotImplementedError - - -class _AskStringController(_baseWindowController): - - def __init__(self, message, value, title): - self.w = _ModalWindow((370, 110), title) - - self.w.infoText = vanilla.TextBox((15, 10, -15, 22), message) - self.w.input = vanilla.EditText((15, 40, -15, 22)) - self.w.input.set(value) - - self.setUpBaseWindowBehavior() - self.w.open() - - def get(self): - if self.cancelled: - return None - return self.w.input.get() - - -class _listController(_baseWindowController): - - def __init__(self, items, message, title, showSearch=False): - - self.items = items - - self.w = _ModalWindow((350, 300), title) - y = 10 - self.w.infoText = vanilla.TextBox((15, y, -15, 22), message) - y += 25 - if showSearch: - self.w.search = vanilla.SearchBox((15, y, -15, 22), callback=self.searchCallback) - y += 25 - self.w.itemList = vanilla.List((15, y, -15, -40), self.items, allowsMultipleSelection=False) - - self.setUpBaseWindowBehavior() - self.w.open() - - def searchCallback(self, sender): - search = sender.get() - - newItems = [item for item in self.items if repr(item).startswith(search)] - self.w.itemList.set(newItems) - if newItems: - self.w.itemList.setSelection([0]) - - def get(self): - index = self.w.itemList.getSelection() - if index: - index = index[0] - return self.w.itemList[index] - return None - - -def AskString(message, value='', title='RoboFab'): - """ - AskString Dialog - - message the string - value a default value - title a title of the window (may not be supported everywhere) - """ - w = _AskStringController(message, value, title) - return w.get() - -def AskYesNoCancel(message, title='RoboFab', default=0, informativeText=""): - """ - AskYesNoCancel Dialog - - message the string - title* a title of the window - (may not be supported everywhere) - default* index number of which button should be default - (i.e. respond to return) - informativeText* A string with secundary information - - * may not be supported everywhere - """ - return vanilla.dialogs.askYesNoCancel(messageText=message, informativeText=informativeText) - -def FindGlyph(aFont, message="Search for a glyph:", title='RoboFab'): - items = aFont.keys() - items.sort() - w = _listController(items, message, title, showSearch=True) - glyphName = w.get() - if glyphName is not None: - return aFont[glyphName] - return None - -def GetFile(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None): - result = vanilla.dialogs.getFile(messageText=message, title=title, directory=directory, fileName=fileName, allowsMultipleSelection=allowsMultipleSelection, fileTypes=fileTypes) - if result is None: - return None - if not allowsMultipleSelection: - return str(list(result)[0]) - else: - return [str(n) for n in list(result)] - -def GetFolder(message=None, title=None, directory=None, allowsMultipleSelection=False): - result = vanilla.dialogs.getFolder(messageText=message, title=title, directory=directory, allowsMultipleSelection=allowsMultipleSelection) - if result is None: - return None - if not allowsMultipleSelection: - return str(list(result)[0]) - else: - return [str(n) for n in list(result)] - -def GetFileOrFolder(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None): - result = vanilla.dialogs.getFileOrFolder(messageText=message, title=title, directory=directory, fileName=fileName, allowsMultipleSelection=allowsMultipleSelection, fileTypes=fileTypes) - if result is None: - return None - if not allowsMultipleSelection: - return str(list(result)[0]) - else: - return [str(n) for n in list(result)] - -def Message(message, title='RoboFab', informativeText=""): - vanilla.dialogs.message(messageText=message, informativeText=informativeText) - -def OneList(items, message="Select an item:", title='RoboFab'): - w = _listController(items, message, title, showSearch=False) - return w.get() - -def PutFile(message=None, fileName=None): - return vanilla.dialogs.putFile(messageText=message, fileName=fileName) - -def SearchList(list, message="Select an item:", title='RoboFab'): - w = _listController(list, message, title, showSearch=True) - return w.get() - -def SelectFont(message="Select a font:", title='RoboFab', allFonts=None): - if allFonts is None: - from robofab.world import AllFonts - fonts = AllFonts() - else: - fonts = allFonts - - data = dict() - for font in fonts: - data["%s" %font] = font - - items = data.keys() - items.sort() - w = _listController(items, message, title, showSearch=False) - value = w.get() - return data.get(value, None) - -def SelectGlyph(aFont, message="Select a glyph:", title='RoboFab'): - items = aFont.keys() - items.sort() - w = _listController(items, message, title, showSearch=False) - glyphName = w.get() - if glyphName is not None: - return aFont[glyphName] - return None - -def TwoChecks(title_1="One", title_2="Two", value1=1, value2=1, title='RoboFab'): - raise NotImplementedError - -def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'): - raise NotImplementedError - - -class ProgressBar(object): - def __init__(self, title="RoboFab...", ticks=None, label=""): - self.w = vanilla.Window((250, 60), title) - if ticks is None: - isIndeterminate = True - ticks = 0 - else: - isIndeterminate = False - self.w.progress = vanilla.ProgressBar((15, 15, -15, 12), maxValue=ticks, isIndeterminate=isIndeterminate, sizeStyle="small") - self.w.text = vanilla.TextBox((15, 32, -15, 14), label, sizeStyle="small") - self.w.progress.start() - self.w.center() - self.w.open() - - def close(self): - self.w.progress.stop() - self.w.close() - - def getCurrentTick(self): - return self.w.progress.get() - - def label(self, label): - self.w.text.set(label) - self.w.text._nsObject.display() - - def tick(self, tickValue=None): - if tickValue is None: - self.w.progress.increment() - else: - self.w.progress.set(tickValue) - - -if __name__ == "__main__": - pass
\ No newline at end of file diff --git a/misc/pylib/robofab/interface/mac/__init__.py b/misc/pylib/robofab/interface/mac/__init__.py deleted file mode 100755 index 15f7d59c5..000000000 --- a/misc/pylib/robofab/interface/mac/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" - -Directory for interface related modules. -Stuff for MacOSX, widgets, quartz - -""" - - - - diff --git a/misc/pylib/robofab/interface/mac/getFileOrFolder.py b/misc/pylib/robofab/interface/mac/getFileOrFolder.py deleted file mode 100755 index da7edff61..000000000 --- a/misc/pylib/robofab/interface/mac/getFileOrFolder.py +++ /dev/null @@ -1,80 +0,0 @@ -"""This module provides two functions, very similar to -EasyDialogs.AskFileForOpen() and EasyDialogs.AskFolder(): GetFile() and -GetFileOrFolder(). The main difference is that the functions here fully -support "packages" or "bundles", ie. folders that appear to be files in -the finder and open/save dialogs. The second difference is that -GetFileOrFolder() allows the user to select a file _or_ a folder. -""" - - -__all__ = ["GetFile", "GetFileOrFolder"] - - -from EasyDialogs import _process_Nav_args, _interact -import Nav -import Carbon.File - - -# Lots of copy/paste from EasyDialogs.py, for one because althought the -# EasyDialogs counterparts take a million options, they don't take the -# one option I need: the flag to support packages... - -kNavSupportPackages = 0x00001000 - - -def GetFile(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None): - """Ask the user to select a file. - - Some of these arguments are not supported: - title, directory, fileName, allowsMultipleSelection and fileTypes are here for compatibility reasons. - """ - default_flags = 0x56 | kNavSupportPackages - args, tpwanted = _process_Nav_args(default_flags, message=message) - _interact() - try: - rr = Nav.NavChooseFile(args) - good = 1 - except Nav.error, arg: - if arg[0] != -128: # userCancelledErr - raise Nav.error, arg - return None - if not rr.validRecord or not rr.selection: - return None - if issubclass(tpwanted, Carbon.File.FSRef): - return tpwanted(rr.selection_fsr[0]) - if issubclass(tpwanted, Carbon.File.FSSpec): - return tpwanted(rr.selection[0]) - if issubclass(tpwanted, str): - return tpwanted(rr.selection_fsr[0].as_pathname()) - if issubclass(tpwanted, unicode): - return tpwanted(rr.selection_fsr[0].as_pathname(), 'utf8') - raise TypeError, "Unknown value for argument 'wanted': %s" % repr(tpwanted) - - -def GetFileOrFolder(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None): - """Ask the user to select a file or a folder. - - Some of these arguments are not supported: - title, directory, fileName, allowsMultipleSelection and fileTypes are here for compatibility reasons. - """ - default_flags = 0x17 | kNavSupportPackages - args, tpwanted = _process_Nav_args(default_flags, message=message) - _interact() - try: - rr = Nav.NavChooseObject(args) - good = 1 - except Nav.error, arg: - if arg[0] != -128: # userCancelledErr - raise Nav.error, arg - return None - if not rr.validRecord or not rr.selection: - return None - if issubclass(tpwanted, Carbon.File.FSRef): - return tpwanted(rr.selection_fsr[0]) - if issubclass(tpwanted, Carbon.File.FSSpec): - return tpwanted(rr.selection[0]) - if issubclass(tpwanted, str): - return tpwanted(rr.selection_fsr[0].as_pathname()) - if issubclass(tpwanted, unicode): - return tpwanted(rr.selection_fsr[0].as_pathname(), 'utf8') - raise TypeError, "Unknown value for argument 'wanted': %s" % repr(tpwanted) diff --git a/misc/pylib/robofab/interface/win/__init__.py b/misc/pylib/robofab/interface/win/__init__.py deleted file mode 100755 index fe360c4ea..000000000 --- a/misc/pylib/robofab/interface/win/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" - -Directory for interface related modules. -Stuff for Windows - -""" - - - - diff --git a/misc/pylib/robofab/misc/__init__.py b/misc/pylib/robofab/misc/__init__.py deleted file mode 100755 index 5ed66a4e4..000000000 --- a/misc/pylib/robofab/misc/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -""" - - arrayTools and bezierTools, originally from fontTools and using Numpy, - now in a pure python implementation. This should ease the Numpy dependency - for normal UFO input/output and basic scripting tasks. - - comparison test and speedtest provided. - -""" - - - - diff --git a/misc/pylib/robofab/misc/arrayTools.pyx b/misc/pylib/robofab/misc/arrayTools.pyx deleted file mode 100644 index 95884aa20..000000000 --- a/misc/pylib/robofab/misc/arrayTools.pyx +++ /dev/null @@ -1,160 +0,0 @@ -# -# Various array and rectangle tools, but mostly rectangles, hence the -# name of this module (not). -# - -""" -Rewritten to elimate the numpy dependency -""" - -import math - -def calcBounds(array): - """Return the bounding rectangle of a 2D points array as a tuple: - (xMin, yMin, xMax, yMax) - """ - if len(array) == 0: - return 0, 0, 0, 0 - xs = [x for x, y in array] - ys = [y for x, y in array] - return min(xs), min(ys), max(xs), max(ys) - -def updateBounds(bounds, pt, min=min, max=max): - """Return the bounding recangle of rectangle bounds and point (x, y).""" - xMin, yMin, xMax, yMax = bounds - x, y = pt - return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y) - -def pointInRect(pt, rect): - """Return True when point (x, y) is inside rect.""" - xMin, yMin, xMax, yMax = rect - return (xMin <= pt[0] <= xMax) and (yMin <= pt[1] <= yMax) - -def pointsInRect(array, rect): - """Find out which points or array are inside rect. - Returns an array with a boolean for each point. - """ - if len(array) < 1: - return [] - xMin, yMin, xMax, yMax = rect - return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array] - -def vectorLength(vector): - """Return the length of the given vector.""" - x, y = vector - return math.sqrt(x**2 + y**2) - -def asInt16(array): - """Round and cast to 16 bit integer.""" - return [int(math.floor(i+0.5)) for i in array] - - -def normRect(box): - """Normalize the rectangle so that the following holds: - xMin <= xMax and yMin <= yMax - """ - return min(box[0], box[2]), min(box[1], box[3]), max(box[0], box[2]), max(box[1], box[3]) - -def scaleRect(box, x, y): - """Scale the rectangle by x, y.""" - return box[0] * x, box[1] * y, box[2] * x, box[3] * y - -def offsetRect(box, dx, dy): - """Offset the rectangle by dx, dy.""" - return box[0]+dx, box[1]+dy, box[2]+dx, box[3]+dy - -def insetRect(box, dx, dy): - """Inset the rectangle by dx, dy on all sides.""" - return box[0]+dx, box[1]+dy, box[2]-dx, box[3]-dy - -def sectRect(box1, box2): - """Return a boolean and a rectangle. If the input rectangles intersect, return - True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input - rectangles don't intersect. - """ - xMin, yMin, xMax, yMax = (max(box1[0], box2[0]), max(box1[1], box2[1]), - min(box1[2], box2[2]), min(box1[3], box2[3])) - if xMin >= xMax or yMin >= yMax: - return 0, (0, 0, 0, 0) - return 1, (xMin, yMin, xMax, yMax) - -def unionRect(box1, box2): - """Return the smallest rectangle in which both input rectangles are fully - enclosed. In other words, return the total bounding rectangle of both input - rectangles. - """ - return (max(box1[0], box2[0]), max(box1[1], box2[1]), - min(box1[2], box2[2]), min(box1[3], box2[3])) - -def rectCenter(box): - """Return the center of the rectangle as an (x, y) coordinate.""" - return (box[0]+box[2])/2, (box[1]+box[3])/2 - -def intRect(box): - """Return the rectangle, rounded off to integer values, but guaranteeing that - the resulting rectangle is NOT smaller than the original. - """ - xMin, yMin, xMax, yMax = box - xMin = int(math.floor(xMin)) - yMin = int(math.floor(yMin)) - xMax = int(math.ceil(xMax)) - yMax = int(math.ceil(yMax)) - return (xMin, yMin, xMax, yMax) - - -def _test(): - """ - >>> import math - >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)]) - (0, 10, 80, 100) - >>> updateBounds((0, 0, 0, 0), (100, 100)) - (0, 0, 100, 100) - >>> pointInRect((50, 50), (0, 0, 100, 100)) - True - >>> pointInRect((0, 0), (0, 0, 100, 100)) - True - >>> pointInRect((100, 100), (0, 0, 100, 100)) - True - >>> not pointInRect((101, 100), (0, 0, 100, 100)) - True - >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100))) - [True, True, True, False] - >>> vectorLength((3, 4)) - 5.0 - >>> vectorLength((1, 1)) == math.sqrt(2) - True - >>> list(asInt16([0, 0.1, 0.5, 0.9])) - [0, 0, 1, 1] - >>> normRect((0, 10, 100, 200)) - (0, 10, 100, 200) - >>> normRect((100, 200, 0, 10)) - (0, 10, 100, 200) - >>> scaleRect((10, 20, 50, 150), 1.5, 2) - (15.0, 40, 75.0, 300) - >>> offsetRect((10, 20, 30, 40), 5, 6) - (15, 26, 35, 46) - >>> insetRect((10, 20, 50, 60), 5, 10) - (15, 30, 45, 50) - >>> insetRect((10, 20, 50, 60), -5, -10) - (5, 10, 55, 70) - >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50)) - >>> not intersects - True - >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50)) - >>> intersects - 1 - >>> rect - (5, 20, 20, 30) - >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50)) - (0, 10, 20, 50) - >>> rectCenter((0, 0, 100, 200)) - (50, 100) - >>> rectCenter((0, 0, 100, 199.0)) - (50, 99.5) - >>> intRect((0.9, 2.9, 3.1, 4.1)) - (0, 2, 4, 5) - """ - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/misc/pylib/robofab/misc/bezierTools.py b/misc/pylib/robofab/misc/bezierTools.py deleted file mode 100644 index 9872060b1..000000000 --- a/misc/pylib/robofab/misc/bezierTools.py +++ /dev/null @@ -1,416 +0,0 @@ -"""fontTools.misc.bezierTools.py -- tools for working with bezier path segments. -Rewritten to elimate the numpy dependency -""" - - -__all__ = [ - "calcQuadraticBounds", - "calcCubicBounds", - "splitLine", - "splitQuadratic", - "splitCubic", - "splitQuadraticAtT", - "splitCubicAtT", - "solveQuadratic", - "solveCubic", -] - -from robofab.misc.arrayTools import calcBounds - -epsilon = 1e-12 - - -def calcQuadraticBounds(pt1, pt2, pt3): - """Return the bounding rectangle for a qudratic bezier segment. - pt1 and pt3 are the "anchor" points, pt2 is the "handle". - - >>> calcQuadraticBounds((0, 0), (50, 100), (100, 0)) - (0, 0, 100, 50.0) - >>> calcQuadraticBounds((0, 0), (100, 0), (100, 100)) - (0.0, 0.0, 100, 100) - """ - (ax, ay), (bx, by), (cx, cy) = calcQuadraticParameters(pt1, pt2, pt3) - ax2 = ax*2.0 - ay2 = ay*2.0 - roots = [] - if ax2 != 0: - roots.append(-bx/ax2) - if ay2 != 0: - roots.append(-by/ay2) - points = [(ax*t*t + bx*t + cx, ay*t*t + by*t + cy) for t in roots if 0 <= t < 1] + [pt1, pt3] - return calcBounds(points) - - -def calcCubicBounds(pt1, pt2, pt3, pt4): - """Return the bounding rectangle for a cubic bezier segment. - pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles". - - >>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0)) - (0, 0, 100, 75.0) - >>> calcCubicBounds((0, 0), (50, 0), (100, 50), (100, 100)) - (0.0, 0.0, 100, 100) - >>> calcCubicBounds((50, 0), (0, 100), (100, 100), (50, 0)) - (35.566243270259356, 0, 64.43375672974068, 75.0) - """ - (ax, ay), (bx, by), (cx, cy), (dx, dy) = calcCubicParameters(pt1, pt2, pt3, pt4) - # calc first derivative - ax3 = ax * 3.0 - ay3 = ay * 3.0 - bx2 = bx * 2.0 - by2 = by * 2.0 - xRoots = [t for t in solveQuadratic(ax3, bx2, cx) if 0 <= t < 1] - yRoots = [t for t in solveQuadratic(ay3, by2, cy) if 0 <= t < 1] - roots = xRoots + yRoots - - points = [(ax*t*t*t + bx*t*t + cx * t + dx, ay*t*t*t + by*t*t + cy * t + dy) for t in roots] + [pt1, pt4] - return calcBounds(points) - - -def splitLine(pt1, pt2, where, isHorizontal): - """Split the line between pt1 and pt2 at position 'where', which - is an x coordinate if isHorizontal is False, a y coordinate if - isHorizontal is True. Return a list of two line segments if the - line was successfully split, or a list containing the original - line. - - >>> printSegments(splitLine((0, 0), (100, 200), 50, False)) - ((0, 0), (50.0, 100.0)) - ((50.0, 100.0), (100, 200)) - >>> printSegments(splitLine((0, 0), (100, 200), 50, True)) - ((0, 0), (25.0, 50.0)) - ((25.0, 50.0), (100, 200)) - >>> printSegments(splitLine((0, 0), (100, 100), 50, True)) - ((0, 0), (50.0, 50.0)) - ((50.0, 50.0), (100, 100)) - >>> printSegments(splitLine((0, 0), (100, 100), 100, True)) - ((0, 0), (100, 100)) - >>> printSegments(splitLine((0, 0), (100, 100), 0, True)) - ((0, 0), (0.0, 0.0)) - ((0.0, 0.0), (100, 100)) - >>> printSegments(splitLine((0, 0), (100, 100), 0, False)) - ((0, 0), (0.0, 0.0)) - ((0.0, 0.0), (100, 100)) - """ - pt1x, pt1y = pt1 - pt2x, pt2y = pt2 - - ax = (pt2x - pt1x) - ay = (pt2y - pt1y) - - bx = pt1x - by = pt1y - - ax1 = (ax, ay)[isHorizontal] - - if ax1 == 0: - return [(pt1, pt2)] - - t = float(where - (bx, by)[isHorizontal]) / ax1 - if 0 <= t < 1: - midPt = ax * t + bx, ay * t + by - return [(pt1, midPt), (midPt, pt2)] - else: - return [(pt1, pt2)] - - -def splitQuadratic(pt1, pt2, pt3, where, isHorizontal): - """Split the quadratic curve between pt1, pt2 and pt3 at position 'where', - which is an x coordinate if isHorizontal is False, a y coordinate if - isHorizontal is True. Return a list of curve segments. - - >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False)) - ((0, 0), (50, 100), (100, 0)) - >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, False)) - ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0)) - ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0)) - >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, False)) - ((0.0, 0.0), (12.5, 25.0), (25.0, 37.5)) - ((25.0, 37.5), (62.5, 75.0), (100.0, 0.0)) - >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, True)) - ((0.0, 0.0), (7.32233047034, 14.6446609407), (14.6446609407, 25.0)) - ((14.6446609407, 25.0), (50.0, 75.0), (85.3553390593, 25.0)) - ((85.3553390593, 25.0), (92.6776695297, 14.6446609407), (100.0, -7.1054273576e-15)) - >>> # XXX I'm not at all sure if the following behavior is desirable: - >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, True)) - ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0)) - ((50.0, 50.0), (50.0, 50.0), (50.0, 50.0)) - ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0)) - """ - a, b, c = calcQuadraticParameters(pt1, pt2, pt3) - solutions = solveQuadratic(a[isHorizontal], b[isHorizontal], - c[isHorizontal] - where) - solutions = [t for t in solutions if 0 <= t < 1] - solutions.sort() - if not solutions: - return [(pt1, pt2, pt3)] - return _splitQuadraticAtT(a, b, c, *solutions) - - -def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal): - """Split the cubic curve between pt1, pt2, pt3 and pt4 at position 'where', - which is an x coordinate if isHorizontal is False, a y coordinate if - isHorizontal is True. Return a list of curve segments. - - >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False)) - ((0, 0), (25, 100), (75, 100), (100, 0)) - >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 50, False)) - ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0)) - ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0)) - >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 25, True)) - ((0.0, 0.0), (2.2937927384, 9.17517095361), (4.79804488188, 17.5085042869), (7.47413641001, 25.0)) - ((7.47413641001, 25.0), (31.2886200204, 91.6666666667), (68.7113799796, 91.6666666667), (92.52586359, 25.0)) - ((92.52586359, 25.0), (95.2019551181, 17.5085042869), (97.7062072616, 9.17517095361), (100.0, 1.7763568394e-15)) - """ - a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4) - solutions = solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal], - d[isHorizontal] - where) - solutions = [t for t in solutions if 0 <= t < 1] - solutions.sort() - if not solutions: - return [(pt1, pt2, pt3, pt4)] - return _splitCubicAtT(a, b, c, d, *solutions) - - -def splitQuadraticAtT(pt1, pt2, pt3, *ts): - """Split the quadratic curve between pt1, pt2 and pt3 at one or more - values of t. Return a list of curve segments. - - >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5)) - ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0)) - ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0)) - >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5, 0.75)) - ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0)) - ((50.0, 50.0), (62.5, 50.0), (75.0, 37.5)) - ((75.0, 37.5), (87.5, 25.0), (100.0, 0.0)) - """ - a, b, c = calcQuadraticParameters(pt1, pt2, pt3) - return _splitQuadraticAtT(a, b, c, *ts) - - -def splitCubicAtT(pt1, pt2, pt3, pt4, *ts): - """Split the cubic curve between pt1, pt2, pt3 and pt4 at one or more - values of t. Return a list of curve segments. - - >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5)) - ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0)) - ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0)) - >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75)) - ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0)) - ((50.0, 75.0), (59.375, 75.0), (68.75, 68.75), (77.34375, 56.25)) - ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25.0), (100.0, 0.0)) - """ - a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4) - return _splitCubicAtT(a, b, c, d, *ts) - - -def _splitQuadraticAtT(a, b, c, *ts): - ts = list(ts) - segments = [] - ts.insert(0, 0.0) - ts.append(1.0) - ax, ay = a - bx, by = b - cx, cy = c - for i in range(len(ts) - 1): - t1 = ts[i] - t2 = ts[i+1] - delta = (t2 - t1) - # calc new a, b and c - a1x = ax * delta**2 - a1y = ay * delta**2 - b1x = (2*ax*t1 + bx) * delta - b1y = (2*ay*t1 + by) * delta - c1x = ax*t1**2 + bx*t1 + cx - c1y = ay*t1**2 + by*t1 + cy - - pt1, pt2, pt3 = calcQuadraticPoints((a1x, a1y), (b1x, b1y), (c1x, c1y)) - segments.append((pt1, pt2, pt3)) - return segments - - -def _splitCubicAtT(a, b, c, d, *ts): - ts = list(ts) - ts.insert(0, 0.0) - ts.append(1.0) - segments = [] - ax, ay = a - bx, by = b - cx, cy = c - dx, dy = d - for i in range(len(ts) - 1): - t1 = ts[i] - t2 = ts[i+1] - delta = (t2 - t1) - # calc new a, b, c and d - a1x = ax * delta**3 - a1y = ay * delta**3 - b1x = (3*ax*t1 + bx) * delta**2 - b1y = (3*ay*t1 + by) * delta**2 - c1x = (2*bx*t1 + cx + 3*ax*t1**2) * delta - c1y = (2*by*t1 + cy + 3*ay*t1**2) * delta - d1x = ax*t1**3 + bx*t1**2 + cx*t1 + dx - d1y = ay*t1**3 + by*t1**2 + cy*t1 + dy - pt1, pt2, pt3, pt4 = calcCubicPoints((a1x, a1y), (b1x, b1y), (c1x, c1y), (d1x, d1y)) - segments.append((pt1, pt2, pt3, pt4)) - return segments - - -# -# Equation solvers. -# - -from math import sqrt, acos, cos, pi - - -def solveQuadratic(a, b, c, - sqrt=sqrt): - """Solve a quadratic equation where a, b and c are real. - a*x*x + b*x + c = 0 - This function returns a list of roots. Note that the returned list - is neither guaranteed to be sorted nor to contain unique values! - """ - if abs(a) < epsilon: - if abs(b) < epsilon: - # We have a non-equation; therefore, we have no valid solution - roots = [] - else: - # We have a linear equation with 1 root. - roots = [-c/b] - else: - # We have a true quadratic equation. Apply the quadratic formula to find two roots. - DD = b*b - 4.0*a*c - if DD >= 0.0: - rDD = sqrt(DD) - roots = [(-b+rDD)/2.0/a, (-b-rDD)/2.0/a] - else: - # complex roots, ignore - roots = [] - return roots - - -def solveCubic(a, b, c, d, - abs=abs, pow=pow, sqrt=sqrt, cos=cos, acos=acos, pi=pi): - """Solve a cubic equation where a, b, c and d are real. - a*x*x*x + b*x*x + c*x + d = 0 - This function returns a list of roots. Note that the returned list - is neither guaranteed to be sorted nor to contain unique values! - """ - # - # adapted from: - # CUBIC.C - Solve a cubic polynomial - # public domain by Ross Cottrell - # found at: http://www.strangecreations.com/library/snippets/Cubic.C - # - if abs(a) < epsilon: - # don't just test for zero; for very small values of 'a' solveCubic() - # returns unreliable results, so we fall back to quad. - return solveQuadratic(b, c, d) - a = float(a) - a1 = b/a - a2 = c/a - a3 = d/a - - Q = (a1*a1 - 3.0*a2)/9.0 - R = (2.0*a1*a1*a1 - 9.0*a1*a2 + 27.0*a3)/54.0 - R2_Q3 = R*R - Q*Q*Q - - if R2_Q3 < 0: - theta = acos(R/sqrt(Q*Q*Q)) - rQ2 = -2.0*sqrt(Q) - x0 = rQ2*cos(theta/3.0) - a1/3.0 - x1 = rQ2*cos((theta+2.0*pi)/3.0) - a1/3.0 - x2 = rQ2*cos((theta+4.0*pi)/3.0) - a1/3.0 - return [x0, x1, x2] - else: - if Q == 0 and R == 0: - x = 0 - else: - x = pow(sqrt(R2_Q3)+abs(R), 1/3.0) - x = x + Q/x - if R >= 0.0: - x = -x - x = x - a1/3.0 - return [x] - - -# -# Conversion routines for points to parameters and vice versa -# - -def calcQuadraticParameters(pt1, pt2, pt3): - x2, y2 = pt2 - x3, y3 = pt3 - cx, cy = pt1 - bx = (x2 - cx) * 2.0 - by = (y2 - cy) * 2.0 - ax = x3 - cx - bx - ay = y3 - cy - by - return (ax, ay), (bx, by), (cx, cy) - - -def calcCubicParameters(pt1, pt2, pt3, pt4): - x2, y2 = pt2 - x3, y3 = pt3 - x4, y4 = pt4 - dx, dy = pt1 - cx = (x2 -dx) * 3.0 - cy = (y2 -dy) * 3.0 - bx = (x3 - x2) * 3.0 - cx - by = (y3 - y2) * 3.0 - cy - ax = x4 - dx - cx - bx - ay = y4 - dy - cy - by - return (ax, ay), (bx, by), (cx, cy), (dx, dy) - - -def calcQuadraticPoints(a, b, c): - ax, ay = a - bx, by = b - cx, cy = c - x1 = cx - y1 = cy - x2 = (bx * 0.5) + cx - y2 = (by * 0.5) + cy - x3 = ax + bx + cx - y3 = ay + by + cy - return (x1, y1), (x2, y2), (x3, y3) - - -def calcCubicPoints(a, b, c, d): - ax, ay = a - bx, by = b - cx, cy = c - dx, dy = d - x1 = dx - y1 = dy - x2 = (cx / 3.0) + dx - y2 = (cy / 3.0) + dy - x3 = (bx + cx) / 3.0 + x2 - y3 = (by + cy) / 3.0 + y2 - x4 = ax + dx + cx + bx - y4 = ay + dy + cy + by - return (x1, y1), (x2, y2), (x3, y3), (x4, y4) - - -def _segmentrepr(obj): - """ - >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], [0.1, 2.2]]]]) - '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))' - """ - try: - it = iter(obj) - except TypeError: - return str(obj) - else: - return "(%s)" % ", ".join([_segmentrepr(x) for x in it]) - - -def printSegments(segments): - """Helper for the doctests, displaying each segment in a list of - segments on a single line as a tuple. - """ - for segment in segments: - print _segmentrepr(segment) - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/misc/pylib/robofab/misc/speedTestCase.py b/misc/pylib/robofab/misc/speedTestCase.py deleted file mode 100644 index e5003ac41..000000000 --- a/misc/pylib/robofab/misc/speedTestCase.py +++ /dev/null @@ -1,99 +0,0 @@ -""" - - Speed comparison between the fontTools numpy based arrayTools and bezierTools, - and the pure python implementation in robofab.path.arrayTools and robofab.path.bezierTools - -""" - -import time - -from fontTools.misc import arrayTools -from fontTools.misc import bezierTools - -import numpy - -import robofab.misc.arrayTools as noNumpyArrayTools -import robofab.misc.bezierTools as noNumpyBezierTools - -################ - -pt1 = (100, 100) -pt2 = (200, 20) -pt3 = (30, 580) -pt4 = (153, 654) -rect = [20, 20, 100, 100] - -## loops -c = 10000 - -print "(loop %s)"%c - - -print "with numpy:" -print "calcQuadraticParameters\t\t", -n = time.time() -for i in range(c): - bezierTools.calcQuadraticParameters(pt1, pt2, pt3) -print time.time() - n - -print "calcBounds\t\t\t", -n = time.time() -for i in range(c): - arrayTools.calcBounds([pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3]) -print time.time() - n - -print "pointsInRect\t\t\t", -n = time.time() -for i in range(c): - arrayTools.pointsInRect([pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt4], rect) -print time.time() - n - -print "calcQuadraticBounds\t\t", -n = time.time() -for i in range(c): - bezierTools.calcQuadraticBounds(pt1, pt2, pt3) -print time.time() - n - -print "calcCubicBounds\t\t\t", -n = time.time() -for i in range(c): - bezierTools.calcCubicBounds(pt1, pt2, pt3, pt4) -print time.time() - n - -print -############## - -print "no-numpy" -print "calcQuadraticParameters\t\t", -n = time.time() -for i in range(c): - noNumpyBezierTools.calcQuadraticParameters(pt1, pt2, pt3) -print time.time() - n - -print "calcBounds\t\t\t", -n = time.time() -for i in range(c): - noNumpyArrayTools.calcBounds([pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3]) -print time.time() - n - -print "pointsInRect\t\t\t", -n = time.time() -for i in range(c): - noNumpyArrayTools.pointsInRect([pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt4], rect) -print time.time() - n - -print "calcQuadraticBounds\t\t", -n = time.time() -for i in range(c): - noNumpyBezierTools.calcQuadraticBounds(pt1, pt2, pt3) -print time.time() - n - -print "calcCubicBounds\t\t\t", -n = time.time() -for i in range(c): - noNumpyBezierTools.calcCubicBounds(pt1, pt2, pt3, pt4) -print time.time() - n - - - - diff --git a/misc/pylib/robofab/misc/test.py b/misc/pylib/robofab/misc/test.py deleted file mode 100644 index 99a795c48..000000000 --- a/misc/pylib/robofab/misc/test.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -doc test requires fontTools to compare and defon to make the test font. -""" - -import random -from fontTools.pens.basePen import BasePen - -from fontTools.misc import arrayTools -from fontTools.misc import bezierTools - -import robofab.misc.arrayTools as noNumpyArrayTools -import robofab.misc.bezierTools as noNumpyBezierTools - - -def drawMoveTo(pen, maxBox): - pen.moveTo((maxBox*random.random(), maxBox*random.random())) - -def drawLineTo(pen, maxBox): - pen.lineTo((maxBox*random.random(), maxBox*random.random())) - -def drawCurveTo(pen, maxBox): - pen.curveTo((maxBox*random.random(), maxBox*random.random()), - (maxBox*random.random(), maxBox*random.random()), - (maxBox*random.random(), maxBox*random.random())) - -def drawClosePath(pen): - pen.closePath() - -def createRandomFont(): - from defcon import Font - - maxGlyphs = 1000 - maxContours = 10 - maxSegments = 10 - maxBox = 700 - drawCallbacks = [drawLineTo, drawCurveTo] - f = Font() - for i in range(maxGlyphs): - name = "%s" %i - f.newGlyph(name) - g = f[name] - g.width = maxBox - pen = g.getPen() - for c in range(maxContours): - drawMoveTo(pen, maxBox) - for s in range(maxSegments): - random.choice(drawCallbacks)(pen, maxBox) - drawClosePath(pen) - return f - -class BoundsPen(BasePen): - - def __init__(self, glyphSet, at, bt): - BasePen.__init__(self, glyphSet) - self.bounds = None - self._start = None - self._arrayTools = at - self._bezierTools = bt - - def _moveTo(self, pt): - self._start = pt - - def _addMoveTo(self): - if self._start is None: - return - bounds = self.bounds - if bounds: - self.bounds = self._arrayTools.updateBounds(bounds, self._start) - else: - x, y = self._start - self.bounds = (x, y, x, y) - self._start = None - - def _lineTo(self, pt): - self._addMoveTo() - self.bounds = self._arrayTools.updateBounds(self.bounds, pt) - - def _curveToOne(self, bcp1, bcp2, pt): - self._addMoveTo() - bounds = self.bounds - bounds = self._arrayTools.updateBounds(bounds, pt) - if not self._arrayTools.pointInRect(bcp1, bounds) or not self._arrayTools.pointInRect(bcp2, bounds): - bounds = self._arrayTools.unionRect(bounds, self._bezierTools.calcCubicBounds( - self._getCurrentPoint(), bcp1, bcp2, pt)) - self.bounds = bounds - - def _qCurveToOne(self, bcp, pt): - self._addMoveTo() - bounds = self.bounds - bounds = self._arrayTools.updateBounds(bounds, pt) - if not self._arrayTools.pointInRect(bcp, bounds): - bounds = self._arrayToolsunionRect(bounds, self._bezierTools.calcQuadraticBounds( - self._getCurrentPoint(), bcp, pt)) - self.bounds = bounds - - - -def _testFont(font): - succes = True - for glyph in font: - fontToolsBoundsPen = BoundsPen(font, arrayTools, bezierTools) - glyph.draw(fontToolsBoundsPen) - noNumpyBoundsPen = BoundsPen(font, noNumpyArrayTools, noNumpyBezierTools) - glyph.draw(noNumpyBoundsPen) - if fontToolsBoundsPen.bounds != noNumpyBoundsPen.bounds: - succes = False - return succes - - -def testCompareAgainstFontTools(): - """ - >>> font = createRandomFont() - >>> _testFont(font) - True - """ - -if __name__ == "__main__": - import doctest - doctest.testmod()
\ No newline at end of file diff --git a/misc/pylib/robofab/objects/__init__.py b/misc/pylib/robofab/objects/__init__.py deleted file mode 100755 index ad85fd002..000000000 --- a/misc/pylib/robofab/objects/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - -Directory for modules supporting - - Unified - - Font - - Objects - -""" - - - - diff --git a/misc/pylib/robofab/objects/objectsBase.pyx b/misc/pylib/robofab/objects/objectsBase.pyx deleted file mode 100755 index 3154fb3f4..000000000 --- a/misc/pylib/robofab/objects/objectsBase.pyx +++ /dev/null @@ -1,3426 +0,0 @@ -""" -Base classes for the Unified Font Objects (UFO), -a series of classes that deal with fonts, glyphs, -contours and related things. - -Unified Font Objects are: -- platform independent -- application independent - -About Object Inheritance: -objectsFL and objectsRF objects inherit -methods and attributes from these objects. -In other words, if it is in here, you can -do it with the objectsFL and objectsRF. -""" - - -from __future__ import generators -from __future__ import division - -from warnings import warn -import math -import copy - -from robofab import ufoLib -from robofab import RoboFabError -from robofab.misc.arrayTools import updateBounds, pointInRect, unionRect, sectRect -from fontTools.pens.basePen import AbstractPen -from fontTools.pens.areaPen import AreaPen -from ..exceptions import RoboFabError, RoboFabWarning - -try: - set -except NameError: - from sets import Set as set - -#constants for dealing with segments, points and bPoints -MOVE = 'move' -LINE = 'line' -CORNER = 'corner' -CURVE = 'curve' -QCURVE = 'qcurve' -OFFCURVE = 'offcurve' - -DEGREE = 180 / math.pi - - - -# the key for the postscript hint data stored in the UFO -postScriptHintDataLibKey = "org.robofab.postScriptHintData" - -# from http://svn.typesupply.com/packages/fontMath/mathFunctions.py - -def add(v1, v2): - return v1 + v2 - -def sub(v1, v2): - return v1 - v2 - -def mul(v, f): - return v * f - -def div(v, f): - return v / f - -def issequence(x): - "Is x a sequence? We say it is if it has a __getitem__ method." - return hasattr(x, '__getitem__') - - - -class BasePostScriptHintValues(object): - """ Base class for postscript hinting information. - """ - - def __init__(self, data=None): - if data is not None: - self.fromDict(data) - else: - for name in self._attributeNames.keys(): - setattr(self, name, self._attributeNames[name]['default']) - - def getParent(self): - """this method will be overwritten with a weakref if there is a parent.""" - return None - - def setParent(self, parent): - import weakref - self.getParent = weakref.ref(parent) - - def isEmpty(self): - """Check all attrs and decide if they're all empty.""" - empty = True - for name in self._attributeNames: - if getattr(self, name): - empty = False - break - return empty - - def clear(self): - """Set all attributes to default / empty""" - for name in self._attributeNames: - setattr(self, name, self._attributeNames[name]['default']) - - def _loadFromLib(self, lib): - data = lib.get(postScriptHintDataLibKey) - if data is not None: - self.fromDict(data) - - def _saveToLib(self, lib): - parent = self.getParent() - if parent is not None: - parent.setChanged(True) - hintsDict = self.asDict() - if hintsDict: - lib[postScriptHintDataLibKey] = hintsDict - - def fromDict(self, data): - for name in self._attributeNames: - if name in data: - setattr(self, name, data[name]) - - def asDict(self): - d = {} - for name in self._attributeNames: - try: - value = getattr(self, name) - except AttributeError: - print "%s attribute not supported"%name - continue - if value: - d[name] = getattr(self, name) - return d - - def update(self, other): - assert isinstance(other, BasePostScriptHintValues) - for name in self._attributeNames.keys(): - v = getattr(other, name) - if v is not None: - setattr(self, name, v) - - def __repr__(self): - return "<Base PS Hint Data>" - - def copy(self, aParent=None): - """Duplicate this object. Pass an object for parenting if you want.""" - n = self.__class__(data=self.asDict()) - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent'] - for k in self.__dict__.keys(): - if k in dont: - continue - dup = copy.deepcopy(self.__dict__[k]) - setattr(n, k, dup) - return n - -class BasePostScriptGlyphHintValues(BasePostScriptHintValues): - """ Base class for glyph-level postscript hinting information. - vStems, hStems - """ - _attributeNames = { - # some of these values can have only a certain number of elements - 'vHints': {'default': None, 'max':100, 'isVertical':True}, - 'hHints': {'default': None, 'max':100, 'isVertical':False}, - } - - def __init__(self, data=None): - if data is not None: - self.fromDict(data) - else: - for name in self._attributeNames.keys(): - setattr(self, name, self._attributeNames[name]['default']) - - def __repr__(self): - return "<PostScript Glyph Hints Values>" - - def round(self): - """Round the values to reasonable values. - - stems are rounded to int - """ - for name, values in self._attributeNames.items(): - v = getattr(self, name) - if v is None: - continue - new = [] - for n in v: - new.append((int(round(n[0])), int(round(n[1])))) - setattr(self, name, new) - - # math operations for psHint object - # Note: math operations can change integers to floats. - def __add__(self, other): - assert isinstance(other, BasePostScriptHintValues) - copied = self.copy() - self._processMathOne(copied, other, add) - return copied - - def __sub__(self, other): - assert isinstance(other, BasePostScriptHintValues) - copied = self.copy() - self._processMathOne(copied, other, sub) - return copied - - def __mul__(self, factor): - #if isinstance(factor, tuple): - # factor = factor[0] - copiedInfo = self.copy() - self._processMathTwo(copiedInfo, factor, mul) - return copiedInfo - - __rmul__ = __mul__ - - def __div__(self, factor): - #if isinstance(factor, tuple): - # factor = factor[0] - copiedInfo = self.copy() - self._processMathTwo(copiedInfo, factor, div) - return copiedInfo - - __rdiv__ = __div__ - - def _processMathOne(self, copied, other, funct): - for name, values in self._attributeNames.items(): - a = None - b = None - v = None - if hasattr(copied, name): - a = getattr(copied, name) - if hasattr(other, name): - b = getattr(other, name) - if a is not None and b is not None: - if len(a) != len(b): - # can't do math with non matching zones - continue - l = len(a) - for i in range(l): - if v is None: - v = [] - ai = a[i] - bi = b[i] - l2 = min(len(ai), len(bi)) - v2 = [funct(ai[j], bi[j]) for j in range(l2)] - v.append(v2) - if v is not None: - setattr(copied, name, v) - - def _processMathTwo(self, copied, factor, funct): - for name, values in self._attributeNames.items(): - a = None - b = None - v = None - isVertical = self._attributeNames[name]['isVertical'] - splitFactor = factor - if isinstance(factor, tuple): - #print "mathtwo", name, funct, factor, isVertical - if isVertical: - splitFactor = factor[1] - else: - splitFactor = factor[0] - if hasattr(copied, name): - a = getattr(copied, name) - if a is not None: - for i in range(len(a)): - if v is None: - v = [] - v2 = [funct(a[i][j], splitFactor) for j in range(len(a[i]))] - v.append(v2) - if v is not None: - setattr(copied, name, v) - - -class BasePostScriptFontHintValues(BasePostScriptHintValues): - """ Base class for font-level postscript hinting information. - Blues values, stem values. - """ - - _attributeNames = { - # some of these values can have only a certain number of elements - # default: what the value should be when initialised - # max: the maximum number of items this attribute is allowed to have - # isVertical: the vertical relevance - 'blueFuzz': {'default': None, 'max':1, 'isVertical':True}, - 'blueScale': {'default': None, 'max':1, 'isVertical':True}, - 'blueShift': {'default': None, 'max':1, 'isVertical':True}, - 'forceBold': {'default': None, 'max':1, 'isVertical':False}, - 'blueValues': {'default': None, 'max':7, 'isVertical':True}, - 'otherBlues': {'default': None, 'max':5, 'isVertical':True}, - 'familyBlues': {'default': None, 'max':7, 'isVertical':True}, - 'familyOtherBlues': {'default': None, 'max':5, 'isVertical':True}, - 'vStems': {'default': None, 'max':6, 'isVertical':True}, - 'hStems': {'default': None, 'max':11, 'isVertical':False}, - } - - def __init__(self, data=None): - if data is not None: - self.fromDict(data) - - def __repr__(self): - return "<PostScript Font Hints Values>" - - # route attribute calls to info object - - def _bluesToPairs(self, values): - values.sort() - finalValues = [] - for value in values: - if not finalValues or len(finalValues[-1]) == 2: - finalValues.append([]) - finalValues[-1].append(value) - return finalValues - - def _bluesFromPairs(self, values): - finalValues = [] - for value1, value2 in values: - finalValues.append(value1) - finalValues.append(value2) - finalValues.sort() - return finalValues - - def _get_blueValues(self): - values = self.getParent().info.postscriptBlueValues - if values is None: - values = [] - values = self._bluesToPairs(values) - return values - - def _set_blueValues(self, values): - if values is None: - values = [] - values = self._bluesFromPairs(values) - self.getParent().info.postscriptBlueValues = values - - blueValues = property(_get_blueValues, _set_blueValues) - - def _get_otherBlues(self): - values = self.getParent().info.postscriptOtherBlues - if values is None: - values = [] - values = self._bluesToPairs(values) - return values - - def _set_otherBlues(self, values): - if values is None: - values = [] - values = self._bluesFromPairs(values) - self.getParent().info.postscriptOtherBlues = values - - otherBlues = property(_get_otherBlues, _set_otherBlues) - - def _get_familyBlues(self): - values = self.getParent().info.postscriptFamilyBlues - if values is None: - values = [] - values = self._bluesToPairs(values) - return values - - def _set_familyBlues(self, values): - if values is None: - values = [] - values = self._bluesFromPairs(values) - self.getParent().info.postscriptFamilyBlues = values - - familyBlues = property(_get_familyBlues, _set_familyBlues) - - def _get_familyOtherBlues(self): - values = self.getParent().info.postscriptFamilyOtherBlues - if values is None: - values = [] - values = self._bluesToPairs(values) - return values - - def _set_familyOtherBlues(self, values): - if values is None: - values = [] - values = self._bluesFromPairs(values) - self.getParent().info.postscriptFamilyOtherBlues = values - - familyOtherBlues = property(_get_familyOtherBlues, _set_familyOtherBlues) - - def _get_vStems(self): - return self.getParent().info.postscriptStemSnapV - - def _set_vStems(self, value): - if value is None: - value = [] - self.getParent().info.postscriptStemSnapV = list(value) - - vStems = property(_get_vStems, _set_vStems) - - def _get_hStems(self): - return self.getParent().info.postscriptStemSnapH - - def _set_hStems(self, value): - if value is None: - value = [] - self.getParent().info.postscriptStemSnapH = list(value) - - hStems = property(_get_hStems, _set_hStems) - - def _get_blueScale(self): - return self.getParent().info.postscriptBlueScale - - def _set_blueScale(self, value): - self.getParent().info.postscriptBlueScale = value - - blueScale = property(_get_blueScale, _set_blueScale) - - def _get_blueShift(self): - return self.getParent().info.postscriptBlueShift - - def _set_blueShift(self, value): - self.getParent().info.postscriptBlueShift = value - - blueShift = property(_get_blueShift, _set_blueShift) - - def _get_blueFuzz(self): - return self.getParent().info.postscriptBlueFuzz - - def _set_blueFuzz(self, value): - self.getParent().info.postscriptBlueFuzz = value - - blueFuzz = property(_get_blueFuzz, _set_blueFuzz) - - def _get_forceBold(self): - return self.getParent().info.postscriptForceBold - - def _set_forceBold(self, value): - self.getParent().info.postscriptForceBold = value - - forceBold = property(_get_forceBold, _set_forceBold) - - def round(self): - """Round the values to reasonable values. - - blueScale is not rounded, it is a float - - forceBold is set to False if -0.5 < value < 0.5. Otherwise it will be True. - - blueShift, blueFuzz are rounded to int - - stems are rounded to int - - blues are rounded to int - """ - for name, values in self._attributeNames.items(): - if name == "blueScale": - continue - elif name == "forceBold": - v = getattr(self, name) - if v is None: - continue - if -0.5 <= v <= 0.5: - setattr(self, name, False) - else: - setattr(self, name, True) - elif name in ['blueFuzz', 'blueShift']: - v = getattr(self, name) - if v is None: - continue - setattr(self, name, int(round(v))) - elif name in ['hStems', 'vStems']: - v = getattr(self, name) - if v is None: - continue - new = [] - for n in v: - new.append(int(round(n))) - setattr(self, name, new) - else: - v = getattr(self, name) - if v is None: - continue - new = [] - for n in v: - new.append([int(round(m)) for m in n]) - setattr(self, name, new) - - - -class RoboFabInterpolationError(Exception): pass - - -def _interpolate(a,b,v): - """interpolate values by factor v""" - return a + (b-a) * v - -def _interpolatePt(a, b, v): - """interpolate point by factor v""" - xa, ya = a - xb, yb = b - if not isinstance(v, tuple): - xv = v - yv = v - else: - xv, yv = v - return xa + (xb-xa) * xv, ya + (yb-ya) * yv - -def _scalePointFromCenter(pt, scale, center): - """scale a point from a center point""" - pointX, pointY = pt - scaleX, scaleY = scale - centerX, centerY = center - ogCenter = center - scaledCenter = (centerX * scaleX, centerY * scaleY) - shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) - scaledPointX = (pointX * scaleX) - shiftVal[0] - scaledPointY = (pointY * scaleY) - shiftVal[1] - return (scaledPointX, scaledPointY) - -def _box(objectToMeasure, fontObject=None): - """calculate the bounds of the object and return it as a (xMin, yMin, xMax, yMax)""" - #from fontTools.pens.boundsPen import BoundsPen - from robofab.pens.boundsPen import BoundsPen - boundsPen = BoundsPen(glyphSet=fontObject) - objectToMeasure.draw(boundsPen) - bounds = boundsPen.bounds - if bounds is None: - bounds = (0, 0, 0, 0) - return bounds - -def roundPt(pt): - """Round a vector""" - return int(round(pt[0])), int(round(pt[1])) - -def addPt(ptA, ptB): - """Add two vectors""" - return ptA[0] + ptB[0], ptA[1] + ptB[1] - -def subPt(ptA, ptB): - """Substract two vectors""" - return ptA[0] - ptB[0], ptA[1] - ptB[1] - -def mulPt(ptA, scalar): - """Multiply a vector with scalar""" - if not isinstance(scalar, tuple): - f1 = scalar - f2 = scalar - else: - f1, f2 = scalar - return ptA[0]*f1, ptA[1]*f2 - -def relativeBCPIn(anchor, BCPIn): - """convert absolute incoming bcp value to a relative value""" - return (BCPIn[0] - anchor[0], BCPIn[1] - anchor[1]) - -def absoluteBCPIn(anchor, BCPIn): - """convert relative incoming bcp value to an absolute value""" - return (BCPIn[0] + anchor[0], BCPIn[1] + anchor[1]) - -def relativeBCPOut(anchor, BCPOut): - """convert absolute outgoing bcp value to a relative value""" - return (BCPOut[0] - anchor[0], BCPOut[1] - anchor[1]) - -def absoluteBCPOut(anchor, BCPOut): - """convert relative outgoing bcp value to an absolute value""" - return (BCPOut[0] + anchor[0], BCPOut[1] + anchor[1]) - -class FuzzyNumber(object): - - def __init__(self, value, threshold): - self.value = value - self.threshold = threshold - - def __cmp__(self, other): - if abs(self.value - other.value) < self.threshold: - return 0 - else: - return cmp(self.value, other.value) - - -class RBaseObject(object): - - """Base class for wrapper objects""" - - attrMap= {} - _title = "RoboFab Wrapper" - - def __init__(self): - self._object = {} - self.changed = False # if the object needs to be saved - self.selected = False - - def __len__(self): - return len(self._object) - - def __repr__(self): - try: - name = `self._object` - except: - name = "None" - return "<%s for %s>" %(self._title, name) - - def copy(self, aParent=None): - """Duplicate this object. Pass an object for parenting if you want.""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent'] - for k in self.__dict__.keys(): - if k in dont: - continue - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - setattr(n, k, dup) - return n - - def round(self): - pass - - def isRobofab(self): - """Presence of this method indicates a Robofab object""" - return 1 - - def naked(self): - """Return the wrapped object itself, in case it is needed for direct access.""" - return self._object - - def setChanged(self, state=True): - self.changed = state - - def getParent(self): - """this method will be overwritten with a weakref if there is a parent.""" - return None - - def setParent(self, parent): - import weakref - self.getParent = weakref.ref(parent) - - def _writeXML(self, writer): - pass - - def dump(self, private=False): - """Print a dump of this object to the std out.""" - from robofab.tools.objectDumper import dumpObject - dumpObject(self, private) - - - -class BaseFont(RBaseObject): - - """Base class for all font objects.""" - - _allFonts = [] - - def __init__(self): - import weakref - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self._allFonts.append(weakref.ref(self)) - self._supportHints = False - - def __repr__(self): - try: - name = self.info.postscriptFullName - except AttributeError: - name = "unnamed_font" - return "<RFont font for %s>" %(name) - - def __eq__(self, other): - #Compare this font with another, compare if they refer to the same file. - return self._compare(other) - - def _compare(self, other): - """Compare this font to other. RF and FL UFO implementations need - slightly different ways of comparing fonts. This method does the - basic stuff. Start with simple and quick comparisons, then move into - detailed comparisons of glyphs.""" - if not hasattr(other, "fileName"): - return False - if self.fileName is not None and self.fileName == other.fileName: - return True - if self.fileName <> other.fileName: - return False - # this will falsely identify two distinct "Untitled" as equal - # so test some more. A lot of work to please some dolt who - # does not save his fonts while running scripts. - try: - if len(self) <> len(other): - return False - except TypeError: - return False - # same name and length. start comparing glyphs - namesSelf = self.keys() - namesOther = other.keys() - namesSelf.sort() - namesOther.sort() - for i in range(len(namesSelf)): - if namesSelf[i] <> namesOther[i]: - return False - for c in self: - if not c == other[c.name]: - return False - return True - - def keys(self): - # must be implemented by subclass - raise NotImplementedError - - def __iter__(self): - for glyphName in self.keys(): - yield self.getGlyph(glyphName) - - def __getitem__(self, glyphName): - return self.getGlyph(glyphName) - - def __contains__(self, glyphName): - return self.has_key(glyphName) - - def _hasChanged(self): - #mark the object as changed - self.setChanged(True) - - def update(self): - """update the font""" - pass - - def close(self, save=1): - """Close the font, saving is optional.""" - pass - - def round(self): - """round all of the points in all of the glyphs""" - for glyph in self: - glyph.round() - - def autoUnicodes(self): - """Using fontTools.agl, assign Unicode lists to all glyphs in the font""" - for glyph in self: - glyph.autoUnicodes() - - def getCharacterMapping(self): - """Create a dictionary of unicode -> [glyphname, ...] mappings. - Note that this dict is created each time this method is called, - which can make it expensive for larger fonts. All glyphs are loaded. - Note that one glyph can have multiple unicode values, - and a unicode value can have multiple glyphs pointing to it.""" - map = {} - for glyph in self: - for u in glyph.unicodes: - if not map.has_key(u): - map[u] = [] - map[u].append(glyph.name) - return map - - def getReverseComponentMapping(self): - """ - Get a reversed map of component references in the font. - { - 'A' : ['Aacute', 'Aring'] - 'acute' : ['Aacute'] - 'ring' : ['Aring'] - etc. - } - """ - map = {} - for glyph in self: - glyphName = glyph.name - for component in glyph.components: - baseGlyphName = component.baseGlyph - if not map.has_key(baseGlyphName): - map[baseGlyphName] = [] - map[baseGlyphName].append(glyphName) - return map - - def compileGlyph(self, glyphName, baseName, accentNames, \ - adjustWidth=False, preflight=False, printErrors=True): - """Compile components into a new glyph using components and anchorpoints. - glyphName: the name of the glyph where it all needs to go - baseName: the name of the base glyph - accentNames: a list of accentName, anchorName tuples, [('acute', 'top'), etc] - """ - anchors = {} - errors = {} - baseGlyph = self[baseName] - for anchor in baseGlyph.getAnchors(): - anchors[anchor.name] = anchor.position - destGlyph = self.newGlyph(glyphName, clear=True) - destGlyph.appendComponent(baseName) - destGlyph.width = baseGlyph.width - for accentName, anchorName in accentNames: - try: - accent = self[accentName] - except IndexError: - errors["glyph '%s' is missing in font %s"%(accentName, self.info.fullName)] = 1 - continue - shift = None - for accentAnchor in accent.getAnchors(): - if '_'+anchorName == accentAnchor.name: - shift = anchors[anchorName][0] - accentAnchor.position[0], anchors[anchorName][1] - accentAnchor.position[1] - destGlyph.appendComponent(accentName, offset=shift) - break - if shift is not None: - for accentAnchor in accent.getAnchors(): - if accentAnchor.name in anchors: - anchors[accentAnchor.name] = shift[0]+accentAnchor.position[0], shift[1]+accentAnchor.position[1] - if printErrors: - for px in errors.keys(): - print px - return destGlyph - - def generateGlyph(self, glyphName, replace=1, preflight=False, printErrors=True): - """Generate a glyph and return it. Assembled from GlyphConstruction.txt""" - from robofab.tools.toolsAll import readGlyphConstructions - con = readGlyphConstructions() - entry = con.get(glyphName, None) - if not entry: - print "glyph '%s' is not listed in the robofab/Data/GlyphConstruction.txt"%(glyphName) - return - baseName = con[glyphName][0] - parts = con[glyphName][1:] - return self.compileGlyph(glyphName, baseName, parts, adjustWidth=1, preflight=preflight, printErrors=printErrors) - - def interpolate(self, factor, minFont, maxFont, suppressError=True, analyzeOnly=False, doProgress=False): - """Traditional interpolation method. Interpolates by factor between minFont and maxFont. - suppressError will supress all tracebacks and analyze only will not perform the interpolation - but it will analyze all glyphs and return a dict of problems.""" - errors = {} - if not isinstance(factor, tuple): - factor = factor, factor - minGlyphNames = minFont.keys() - maxGlyphNames = maxFont.keys() - allGlyphNames = list(set(minGlyphNames) | set(maxGlyphNames)) - if doProgress: - from robofab.interface.all.dialogs import ProgressBar - progress = ProgressBar('Interpolating...', len(allGlyphNames)) - tickCount = 0 - # some dimensions and values - self.info.ascender = _interpolate(minFont.info.ascender, maxFont.info.ascender, factor[1]) - self.info.descender = _interpolate(minFont.info.descender, maxFont.info.descender, factor[1]) - # check for the presence of the glyph in each of the fonts - for glyphName in allGlyphNames: - if doProgress: - progress.label(glyphName) - fatalError = False - if glyphName not in minGlyphNames: - fatalError = True - if not errors.has_key('Missing Glyphs'): - errors['Missing Glyphs'] = [] - errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info.postscriptFullName)) - if glyphName not in maxGlyphNames: - fatalError = True - if not errors.has_key('Missing Glyphs'): - errors['Missing Glyphs'] = [] - errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info.postscriptFullName)) - # if no major problems, proceed. - if not fatalError: - # remove the glyph since FontLab has a problem with - # interpolating an existing glyph that contains - # some contour data. - oldLib = {} - oldMark = None - oldNote = None - if self.has_key(glyphName): - glyph = self[glyphName] - oldLib = dict(glyph.lib) - oldMark = glyph.mark - oldNote = glyph.note - self.removeGlyph(glyphName) - selfGlyph = self.newGlyph(glyphName) - selfGlyph.lib.update(oldLib) - if oldMark != None: - selfGlyph.mark = oldMark - selfGlyph.note = oldNote - min = minFont[glyphName] - max = maxFont[glyphName] - ok, glyphErrors = selfGlyph.interpolate(factor, min, max, suppressError=suppressError, analyzeOnly=analyzeOnly) - if not errors.has_key('Glyph Errors'): - errors['Glyph Errors'] = {} - errors['Glyph Errors'][glyphName] = glyphErrors - if doProgress: - progress.tick(tickCount) - tickCount = tickCount + 1 - if doProgress: - progress.close() - return errors - - def getGlyphNameToFileNameFunc(self): - funcName = self.lib.get("org.robofab.glyphNameToFileNameFuncName") - if funcName is None: - return None - parts = funcName.split(".") - module = ".".join(parts[:-1]) - try: - item = __import__(module) - for sub in parts[1:]: - item = getattr(item, sub) - except (ImportError, AttributeError): - warn("Can't find glyph name to file name converter function, " - "falling back to default scheme (%s)" % funcName, RoboFabWarning) - return None - else: - return item - - -class BaseGlyph(RBaseObject): - - """Base class for all glyph objects.""" - - def __init__(self): - RBaseObject.__init__(self) - #self.contours = [] - #self.components = [] - #self.anchors = [] - #self.width = 0 - #self.note = None - ##self.unicodes = [] - #self.selected = None - self.changed = False # if the object needs to be saved - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - fontParent = self.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: - pass - try: - glyph = self.name - except AttributeError: - pass - return "<RGlyph for %s.%s>" %(font, glyph) - - # - # Glyph Math - # - - def _getMathData(self): - from robofab.pens.mathPens import GetMathDataPointPen - pen = GetMathDataPointPen() - self.drawPoints(pen) - data = pen.getData() - return data - - def _setMathData(self, data, destination=None): - from robofab.pens.mathPens import CurveSegmentFilterPointPen - if destination is None: - newGlyph = self._mathCopy() - else: - newGlyph = destination - newGlyph.clear() - # - # draw the data onto the glyph - pointPen = newGlyph.getPointPen() - filterPen = CurveSegmentFilterPointPen(pointPen) - for contour in data['contours']: - filterPen.beginPath() - for segmentType, pt, smooth, name in contour: - filterPen.addPoint(pt=pt, segmentType=segmentType, smooth=smooth, name=name) - filterPen.endPath() - for baseName, transformation in data['components']: - filterPen.addComponent(baseName, transformation) - for pt, name in data['anchors']: - filterPen.beginPath() - filterPen.addPoint(pt=pt, segmentType="move", smooth=False, name=name) - filterPen.endPath() - newGlyph.width = data['width'] - psHints = data.get('psHints') - if psHints is not None: - newGlyph.psHints.update(psHints) - # - return newGlyph - - def _getMathDestination(self): - # make a new, empty glyph - return self.__class__() - - def _mathCopy(self): - # copy self without contour, component and anchor data - glyph = self._getMathDestination() - glyph.name = self.name - glyph.unicodes = list(self.unicodes) - glyph.width = self.width - glyph.note = self.note - glyph.lib = dict(self.lib) - return glyph - - def _processMathOne(self, otherGlyph, funct): - # used by: __add__, __sub__ - # - newData = { - 'contours':[], - 'components':[], - 'anchors':[], - 'width':None - } - selfData = self._getMathData() - otherData = otherGlyph._getMathData() - # - # contours - selfContours = selfData['contours'] - otherContours = otherData['contours'] - newContours = newData['contours'] - if len(selfContours) > 0: - for contourIndex in xrange(len(selfContours)): - newContours.append([]) - selfContour = selfContours[contourIndex] - otherContour = otherContours[contourIndex] - for pointIndex in xrange(len(selfContour)): - segType, pt, smooth, name = selfContour[pointIndex] - newX, newY = funct(selfContour[pointIndex][1], otherContour[pointIndex][1]) - newContours[-1].append((segType, (newX, newY), smooth, name)) - # anchors - selfAnchors = selfData['anchors'] - otherAnchors = otherData['anchors'] - newAnchors = newData['anchors'] - if len(selfAnchors) > 0: - selfAnchors, otherAnchors = self._mathAnchorCompare(selfAnchors, otherAnchors) - anchorNames = selfAnchors.keys() - for anchorName in anchorNames: - selfAnchorList = selfAnchors[anchorName] - otherAnchorList = otherAnchors[anchorName] - for i in range(len(selfAnchorList)): - selfAnchor = selfAnchorList[i] - otherAnchor = otherAnchorList[i] - newAnchor = funct(selfAnchor, otherAnchor) - newAnchors.append((newAnchor, anchorName)) - # components - selfComponents = selfData['components'] - otherComponents = otherData['components'] - newComponents = newData['components'] - if len(selfComponents) > 0: - selfComponents, otherComponents = self._mathComponentCompare(selfComponents, otherComponents) - componentNames = selfComponents.keys() - for componentName in componentNames: - selfComponentList = selfComponents[componentName] - otherComponentList = otherComponents[componentName] - for i in range(len(selfComponentList)): - # transformation breakdown: xScale, xyScale, yxScale, yScale, xOffset, yOffset - selfXScale, selfXYScale, selfYXScale, selfYScale, selfXOffset, selfYOffset = selfComponentList[i] - otherXScale, otherXYScale, otherYXScale, otherYScale, otherXOffset, otherYOffset = otherComponentList[i] - newXScale, newXYScale = funct((selfXScale, selfXYScale), (otherXScale, otherXYScale)) - newYXScale, newYScale = funct((selfYXScale, selfYScale), (otherYXScale, otherYScale)) - newXOffset, newYOffset = funct((selfXOffset, selfYOffset), (otherXOffset, otherYOffset)) - newComponents.append((componentName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset))) - return newData - - def _processMathTwo(self, factor, funct): - # used by: __mul__, __div__ - # - newData = { - 'contours':[], - 'components':[], - 'anchors':[], - 'width':None - } - selfData = self._getMathData() - # contours - selfContours = selfData['contours'] - newContours = newData['contours'] - for selfContour in selfContours: - newContours.append([]) - for segType, pt, smooth, name in selfContour: - newX, newY = funct(pt, factor) - newContours[-1].append((segType, (newX, newY), smooth, name)) - # anchors - selfAnchors = selfData['anchors'] - newAnchors = newData['anchors'] - for pt, anchorName in selfAnchors: - newPt = funct(pt, factor) - newAnchors.append((newPt, anchorName)) - # components - selfComponents = selfData['components'] - newComponents = newData['components'] - for baseName, transformation in selfComponents: - xScale, xyScale, yxScale, yScale, xOffset, yOffset = transformation - newXOffset, newYOffset = funct((xOffset, yOffset), factor) - newXScale, newYScale = funct((xScale, yScale), factor) - newXYScale, newYXScale = funct((xyScale, yxScale), factor) - newComponents.append((baseName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset))) - # return the data - return newData - - def _mathAnchorCompare(self, selfMathAnchors, otherMathAnchors): - # collect compatible anchors - selfAnchors = {} - for pt, name in selfMathAnchors: - if not selfAnchors.has_key(name): - selfAnchors[name] = [] - selfAnchors[name].append(pt) - otherAnchors = {} - for pt, name in otherMathAnchors: - if not otherAnchors.has_key(name): - otherAnchors[name] = [] - otherAnchors[name].append(pt) - compatAnchors = set(selfAnchors.keys()) & set(otherAnchors.keys()) - finalSelfAnchors = {} - finalOtherAnchors = {} - for name in compatAnchors: - if not finalSelfAnchors.has_key(name): - finalSelfAnchors[name] = [] - if not finalOtherAnchors.has_key(name): - finalOtherAnchors[name] = [] - selfList = selfAnchors[name] - otherList = otherAnchors[name] - selfCount = len(selfList) - otherCount = len(otherList) - if selfCount != otherCount: - r = range(min(selfCount, otherCount)) - else: - r = range(selfCount) - for i in r: - finalSelfAnchors[name].append(selfList[i]) - finalOtherAnchors[name].append(otherList[i]) - return finalSelfAnchors, finalOtherAnchors - - def _mathComponentCompare(self, selfMathComponents, otherMathComponents): - # collect compatible components - selfComponents = {} - for baseName, transformation in selfMathComponents: - if not selfComponents.has_key(baseName): - selfComponents[baseName] = [] - selfComponents[baseName].append(transformation) - otherComponents = {} - for baseName, transformation in otherMathComponents: - if not otherComponents.has_key(baseName): - otherComponents[baseName] = [] - otherComponents[baseName].append(transformation) - compatComponents = set(selfComponents.keys()) & set(otherComponents.keys()) - finalSelfComponents = {} - finalOtherComponents = {} - for baseName in compatComponents: - if not finalSelfComponents.has_key(baseName): - finalSelfComponents[baseName] = [] - if not finalOtherComponents.has_key(baseName): - finalOtherComponents[baseName] = [] - selfList = selfComponents[baseName] - otherList = otherComponents[baseName] - selfCount = len(selfList) - otherCount = len(otherList) - if selfCount != otherCount: - r = range(min(selfCount, otherCount)) - else: - r = range(selfCount) - for i in r: - finalSelfComponents[baseName].append(selfList[i]) - finalOtherComponents[baseName].append(otherList[i]) - return finalSelfComponents, finalOtherComponents - - def __mul__(self, factor): - assert isinstance(factor, (int, float, tuple)), "Glyphs can only be multiplied by int, float or a 2-tuple." - if not isinstance(factor, tuple): - factor = (factor, factor) - data = self._processMathTwo(factor, mulPt) - data['width'] = self.width * factor[0] - # psHints - if not self.psHints.isEmpty(): - newPsHints = self.psHints * factor - data['psHints'] = newPsHints - return self._setMathData(data) - - __rmul__ = __mul__ - - def __div__(self, factor): - assert isinstance(factor, (int, float, tuple)), "Glyphs can only be divided by int, float or a 2-tuple." - # calculate reverse factor, and cause nice ZeroDivisionError if it can't - if isinstance(factor, tuple): - reverse = 1.0/factor[0], 1.0/factor[1] - else: - reverse = 1.0/factor - return self.__mul__(reverse) - - def __add__(self, other): - assert isinstance(other, BaseGlyph), "Glyphs can only be added to other glyphs." - data = self._processMathOne(other, addPt) - data['width'] = self.width + other.width - return self._setMathData(data) - - def __sub__(self, other): - assert isinstance(other, BaseGlyph), "Glyphs can only be substracted from other glyphs." - data = self._processMathOne(other, subPt) - data['width'] = self.width + other.width - return self._setMathData(data) - - # - # Interpolation - # - - def interpolate(self, factor, minGlyph, maxGlyph, suppressError=True, analyzeOnly=False): - """Traditional interpolation method. Interpolates by factor between minGlyph and maxGlyph. - suppressError will supress all tracebacks and analyze only will not perform the interpolation - but it will analyze all glyphs and return a dict of problems.""" - if not isinstance(factor, tuple): - factor = factor, factor - fatalError = False - if analyzeOnly: - ok, errors = minGlyph.isCompatible(maxGlyph) - return ok, errors - minData = None - maxData = None - minName = minGlyph.name - maxName = maxGlyph.name - try: - minData = minGlyph._getMathData() - maxData = maxGlyph._getMathData() - newContours = self._interpolateContours(factor, minData['contours'], maxData['contours']) - newComponents = self._interpolateComponents(factor, minData['components'], maxData['components']) - newAnchors = self._interpolateAnchors(factor, minData['anchors'], maxData['anchors']) - newWidth = _interpolate(minGlyph.width, maxGlyph.width, factor[0]) - newData = { - 'contours':newContours, - 'components':newComponents, - 'anchors':newAnchors, - 'width':newWidth - } - self._setMathData(newData, self) - except IndexError: - if not suppressError: - ok, errors = minGlyph.isCompatible(maxGlyph) - ok = not ok - return ok, errors - self.update() - return False, [] - - def isCompatible(self, otherGlyph, report=True): - """Return a bool value if the glyph is compatible with otherGlyph. - With report = True, isCompatible will return a report of what's wrong. - The interpolate method requires absolute equality between contour data. - Absolute equality is preferred among component and anchor data, but - it is NOT required. Interpolation between components and anchors - will only deal with compatible data and incompatible data will be - ignored. This method reflects this system.""" - selfName = self.name - selfData = self._getMathData() - otherName = otherGlyph.name - otherData = otherGlyph._getMathData() - compatible, errors = self._isCompatibleInternal(selfName, otherName, selfData, otherData) - if report: - return compatible, errors - return compatible - - def _isCompatibleInternal(self, selfName, otherName, selfData, otherData): - fatalError = False - errors = [] - ## contours - # any contour incompatibilities - # result in fatal errors - selfContours = selfData['contours'] - otherContours = otherData['contours'] - if len(selfContours) != len(otherContours): - fatalError = True - errors.append("Fatal error: glyph %s and glyph %s don't have the same number of contours." %(selfName, otherName)) - else: - for contourIndex in xrange(len(selfContours)): - selfContour = selfContours[contourIndex] - otherContour = otherContours[contourIndex] - if len(selfContour) != len(otherContour): - fatalError = True - errors.append("Fatal error: contour %d in glyph %s and glyph %s don't have the same number of segments." %(contourIndex, selfName, otherName)) - ## components - # component incompatibilities - # do not result in fatal errors - selfComponents = selfData['components'] - otherComponents = otherData['components'] - if len(selfComponents) != len(otherComponents): - errors.append("Error: glyph %s and glyph %s don't have the same number of components." %(selfName, otherName)) - for componentIndex in xrange(min(len(selfComponents), len(otherComponents))): - selfBaseName, selfTransformation = selfComponents[componentIndex] - otherBaseName, otherTransformation = otherComponents[componentIndex] - if selfBaseName != otherBaseName: - errors.append("Error: component %d in glyph %s and glyph %s don't have the same base glyph." %(componentIndex, selfName, otherName)) - ## anchors - # anchor incompatibilities - # do not result in fatal errors - selfAnchors = selfData['anchors'] - otherAnchors = otherData['anchors'] - if len(selfAnchors) != len(otherAnchors): - errors.append("Error: glyph %s and glyph %s don't have the same number of anchors." %(selfName, otherName)) - for anchorIndex in xrange(min(len(selfAnchors), len(otherAnchors))): - selfPt, selfAnchorName = selfAnchors[anchorIndex] - otherPt, otherAnchorName = otherAnchors[anchorIndex] - if selfAnchorName != otherAnchorName: - errors.append("Error: anchor %d in glyph %s and glyph %s don't have the same name." %(anchorIndex, selfName, otherName)) - return not fatalError, errors - - def _interpolateContours(self, factor, minContours, maxContours): - newContours = [] - for contourIndex in xrange(len(minContours)): - minContour = minContours[contourIndex] - maxContour = maxContours[contourIndex] - newContours.append([]) - for pointIndex in xrange(len(minContour)): - segType, pt, smooth, name = minContour[pointIndex] - minPoint = minContour[pointIndex][1] - maxPoint = maxContour[pointIndex][1] - newX, newY = _interpolatePt(minPoint, maxPoint, factor) - newContours[-1].append((segType, (newX, newY), smooth, name)) - return newContours - - def _interpolateComponents(self, factor, minComponents, maxComponents): - newComponents = [] - minComponents, maxComponents = self._mathComponentCompare(minComponents, maxComponents) - componentNames = minComponents.keys() - for componentName in componentNames: - minComponentList = minComponents[componentName] - maxComponentList = maxComponents[componentName] - for i in xrange(len(minComponentList)): - # transformation breakdown: xScale, xyScale, yxScale, yScale, xOffset, yOffset - minXScale, minXYScale, minYXScale, minYScale, minXOffset, minYOffset = minComponentList[i] - maxXScale, maxXYScale, maxYXScale, maxYScale, maxXOffset, maxYOffset = maxComponentList[i] - newXScale, newXYScale = _interpolatePt((minXScale, minXYScale), (maxXScale, maxXYScale), factor) - newYXScale, newYScale = _interpolatePt((minYXScale, minYScale), (maxYXScale, maxYScale), factor) - newXOffset, newYOffset = _interpolatePt((minXOffset, minYOffset), (maxXOffset, maxYOffset), factor) - newComponents.append((componentName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset))) - return newComponents - - def _interpolateAnchors(self, factor, minAnchors, maxAnchors): - newAnchors = [] - minAnchors, maxAnchors = self._mathAnchorCompare(minAnchors, maxAnchors) - anchorNames = minAnchors.keys() - for anchorName in anchorNames: - minAnchorList = minAnchors[anchorName] - maxAnchorList = maxAnchors[anchorName] - for i in range(len(minAnchorList)): - minAnchor = minAnchorList[i] - maxAnchor = maxAnchorList[i] - newAnchor = _interpolatePt(minAnchor, maxAnchor, factor) - newAnchors.append((newAnchor, anchorName)) - return newAnchors - - # - # comparisons - # - - def __eq__(self, other): - if isinstance(other, BaseGlyph): - return self._getDigest() == other._getDigest() - return False - - def __ne__(self, other): - return not self.__eq__(other) - - def _getDigest(self, pointsOnly=False): - """Calculate a digest of coordinates, points, things in this glyph. - With pointsOnly == True the digest consists of a flat tuple of all - coordinate pairs in the glyph, without the order of contours. - """ - from robofab.pens.digestPen import DigestPointPen - mp = DigestPointPen() - self.drawPoints(mp) - if pointsOnly: - return "%s|%d|%s"%(mp.getDigestPointsOnly(), self.width, self.unicode) - else: - return "%s|%d|%s"%(mp.getDigest(), self.width, self.unicode) - - def _getStructure(self): - """Calculate a digest of points, things in this glyph, but NOT coordinates.""" - from robofab.pens.digestPen import DigestPointStructurePen - mp = DigestPointStructurePen() - self.drawPoints(mp) - return mp.getDigest() - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def _get_box(self): - bounds = _box(self, fontObject=self.getParent()) - return bounds - - box = property(_get_box, doc="the bounding box of the glyph: (xMin, yMin, xMax, yMax)") - - def _get_leftMargin(self): - if self.isEmpty(): - return 0 - xMin, yMin, xMax, yMax = self.box - return xMin - - def _set_leftMargin(self, value): - if self.isEmpty(): - self.width = self.width + value - else: - diff = value - self.leftMargin - self.move((diff, 0)) - self.width = self.width + diff - - leftMargin = property(_get_leftMargin, _set_leftMargin, doc="the left margin") - - def _get_rightMargin(self): - if self.isEmpty(): - return self.width - xMin, yMin, xMax, yMax = self.box - return self.width - xMax - - def _set_rightMargin(self, value): - if self.isEmpty(): - self.width = value - else: - xMin, yMin, xMax, yMax = self.box - self.width = xMax + value - - rightMargin = property(_get_rightMargin, _set_rightMargin, doc="the right margin") - - def copy(self, aParent=None): - """Duplicate this glyph""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - dont = ['_object', 'getParent'] - for k in self.__dict__.keys(): - ok = True - if k in dont: - continue - elif k == "contours": - dup = [] - for i in self.contours: - dup.append(i.copy(n)) - elif k == "components": - dup = [] - for i in self.components: - dup.append(i.copy(n)) - elif k == "anchors": - dup = [] - for i in self.anchors: - dup.append(i.copy(n)) - elif k == "psHints": - dup = self.psHints.copy() - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - if ok: - setattr(n, k, dup) - return n - - def _setParentTree(self): - """Set the parents of all contained and dependent objects (and their dependents) right.""" - for item in self.contours: - item.setParent(self) - item._setParentTree() - for item in self.components: - item.setParent(self) - for items in self.anchors: - item.setParent(self) - - def getGlyph(self, glyphName): - """Provided there is a font parent for this glyph, return a sibling glyph.""" - if glyphName == self.name: - return self - if self.getParent() is not None: - return self.getParent()[glyphName] - return None - - def getPen(self): - """Return a Pen object for creating an outline in this glyph.""" - from robofab.pens.adapterPens import SegmentToPointPen - return SegmentToPointPen(self.getPointPen()) - - def getPointPen(self): - """Return a PointPen object for creating an outline in this glyph.""" - raise NotImplementedError, "getPointPen() must be implemented by subclass" - - def deSelect(self): - """Set all selected attrs in glyph to False: for the glyph, components, anchors, points.""" - for a in self.anchors: - a.selected = False - for a in self.components: - a.selected = False - for c in self.contours: - for p in c.points: - p.selected = False - self.selected = False - - def isEmpty(self): - """return true if the glyph has no contours or components""" - if len(self.contours) + len(self.components) == 0: - return True - else: - return False - - def _saveToGlyphSet(self, glyphSet, glyphName=None, force=False): - """Save the glyph to GlyphSet, a private method that's part of the saving process.""" - # save stuff in the lib first - if force or self.changed: - if glyphName is None: - glyphName = self.name - glyphSet.writeGlyph(glyphName, self, self.drawPoints) - - def update(self): - """update the glyph""" - pass - - def draw(self, pen): - """draw the object with a RoboFab segment pen""" - try: - pen.setWidth(self.width) - if self.note is not None: - pen.setNote(self.note) - except AttributeError: - # FontTools pens don't have these methods - pass - for a in self.anchors: - a.draw(pen) - for c in self.contours: - c.draw(pen) - for c in self.components: - c.draw(pen) - try: - pen.doneDrawing() - except AttributeError: - # FontTools pens don't have a doneDrawing() method - pass - - def drawPoints(self, pen): - """draw the object with a point pen""" - for a in self.anchors: - a.drawPoints(pen) - for c in self.contours: - c.drawPoints(pen) - for c in self.components: - c.drawPoints(pen) - - def appendContour(self, aContour, offset=(0, 0)): - """append a contour to the glyph""" - x, y = offset - pen = self.getPointPen() - aContour.drawPoints(pen) - self.contours[-1].move((x, y)) - - def appendGlyph(self, aGlyph, offset=(0, 0)): - """append another glyph to the glyph""" - x, y = offset - pen = self.getPointPen() - #to handle the offsets, move the source glyph and then move it back! - aGlyph.move((x, y)) - aGlyph.drawPoints(pen) - aGlyph.move((-x, -y)) - - def round(self): - """round all coordinates in all contours, components and anchors""" - for n in self.contours: - n.round() - for n in self.components: - n.round() - for n in self.anchors: - n.round() - self.width = int(round(self.width)) - - def autoUnicodes(self): - """Using fontTools.agl, assign Unicode list to the glyph""" - from fontTools.agl import AGL2UV - if AGL2UV.has_key(self.name): - self.unicode = AGL2UV[self.name] - self._hasChanged() - - def pointInside(self, pt, evenOdd=0): - """determine if the point is in the black or white of the glyph""" - x, y = pt - from fontTools.pens.pointInsidePen import PointInsidePen - font = self.getParent() - piPen = PointInsidePen(glyphSet=font, testPoint=(x, y), evenOdd=evenOdd) - self.draw(piPen) - return piPen.getResult() - - def correctDirection(self, trueType=False): - """corect the direction of the contours in the glyph.""" - #this is a bit slow, but i'm not sure how much more it can be optimized. - #it also has a bug somewhere that is causeing some contours to be set incorrectly. - #try to run it on the copyright symbol to see the problem. hm. - # - #establish the default direction that an outer contour should follow - #i believe for TT this is clockwise and for PS it is counter - #i could be wrong about this, i need to double check. - from fontTools.pens.pointInsidePen import PointInsidePen - baseDirection = 0 - if trueType: - baseDirection = 1 - #we don't need to do all the work if the contour count is < 2 - count = len(self.contours) - if count == 0: - return - elif count == 1: - self.contours[0].clockwise = baseDirection - return - #store up needed before we start - #i think the .box calls are eating a big chunk of the time - contourDict = {} - for contourIndex in range(len(self.contours)): - contour = self.contours[contourIndex] - contourDict[contourIndex] = {'box':contour.box, 'dir':contour.clockwise, 'hit':[], 'notHit':[]} - #now, for every contour, determine which contours it intersects - #as we go, we will also store contours that it doesn't intersct - #and we store this value for both contours - allIndexes = contourDict.keys() - for contourIndex in allIndexes: - for otherContourIndex in allIndexes: - if otherContourIndex != contourIndex: - if contourIndex not in contourDict[otherContourIndex]['hit'] and contourIndex not in contourDict[otherContourIndex]['notHit']: - xMin1, yMin1, xMax1, yMax1 = contourDict[contourIndex]['box'] - xMin2, yMin2, xMax2, yMax2= contourDict[otherContourIndex]['box'] - hit, pos = sectRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)) - if hit == 1: - contourDict[contourIndex]['hit'].append(otherContourIndex) - contourDict[otherContourIndex]['hit'].append(contourIndex) - else: - contourDict[contourIndex]['notHit'].append(otherContourIndex) - contourDict[otherContourIndex]['notHit'].append(contourIndex) - #set up the pen here to shave a bit of time - font = self.getParent() - piPen = PointInsidePen(glyphSet=font, testPoint=(0, 0), evenOdd=0) - #now do the pointInside work - for contourIndex in allIndexes: - direction = baseDirection - contour = self.contours[contourIndex] - startPoint = contour.segments[0].onCurve - if startPoint is not None: #skip TT paths with no onCurve - if len(contourDict[contourIndex]['hit']) != 0: - for otherContourIndex in contourDict[contourIndex]['hit']: - piPen.setTestPoint(testPoint=(startPoint.x, startPoint.y)) - otherContour = self.contours[otherContourIndex] - otherContour.draw(piPen) - direction = direction + piPen.getResult() - newDirection = direction % 2 - #now set the direction if we need to - if newDirection != contourDict[contourIndex]['dir']: - contour.reverseContour() - - def autoContourOrder(self): - """attempt to sort the contours based on their centers""" - # sort is based on (in this order): - # - the (negative) point count - # - the (negative) segment count - # - fuzzy x value of the center of the contour - # - fuzzy y value of the center of the contour - # - the (negative) surface of the bounding box of the contour: width * height - # the latter is a safety net for for instances like a very thin 'O' where the - # x centers could be close enough to rely on the y for the sort which could - # very well be the same for both contours. We use the _negative_ of the surface - # to ensure that larger contours appear first, which seems more natural. - tempContourList = [] - contourList = [] - xThreshold = None - yThreshold = None - for contour in self.contours: - xMin, yMin, xMax, yMax = contour.box - width = xMax - xMin - height = yMax - yMin - xC = 0.5 * (xMin + xMax) - yC = 0.5 * (yMin + yMax) - xTh = abs(width * .5) - yTh = abs(height * .5) - if xThreshold is None or xThreshold > xTh: - xThreshold = xTh - if yThreshold is None or yThreshold > yTh: - yThreshold = yTh - tempContourList.append((-len(contour.points), -len(contour.segments), xC, yC, -(width * height), contour)) - for points, segments, x, y, surface, contour in tempContourList: - contourList.append((points, segments, FuzzyNumber(x, xThreshold), FuzzyNumber(y, yThreshold), surface, contour)) - contourList.sort() - for i in range(len(contourList)): - points, segments, xO, yO, surface, contour = contourList[i] - contour.index = i - - def rasterize(self, cellSize=50, xMin=None, yMin=None, xMax=None, yMax=None): - """ - Slice the glyph into a grid based on the cell size. - It returns a list of lists containing bool values - that indicate the black (True) or white (False) - value of that particular cell. These lists are - arranged from top to bottom of the glyph and - proceed from left to right. - This is an expensive operation! - """ - from fontTools.pens.pointInsidePen import PointInsidePen - piPen = PointInsidePen(glyphSet=self.getParent(), testPoint=(0, 0), evenOdd=0) - if xMin is None or yMin is None or xMax is None or yMax is None: - _xMin, _yMin, _xMax, _yMax = self.box - if xMin is None: - xMin = _xMin - if yMin is None: - yMin = _yMin - if xMax is None: - xMax = _xMax - if yMax is None: - yMax = _yMax - # - hitXMax = False - hitYMin = False - xSlice = 0 - ySlice = 0 - halfCellSize = cellSize / 2.0 - # - map = [] - # - while not hitYMin: - map.append([]) - yScan = -(ySlice * cellSize) + yMax - halfCellSize - if yScan < yMin: - hitYMin = True - while not hitXMax: - xScan = (xSlice * cellSize) + xMin - halfCellSize - if xScan > xMax: - hitXMax = True - piPen.setTestPoint((xScan, yScan)) - self.draw(piPen) - test = piPen.getResult() - if test: - map[-1].append(True) - else: - map[-1].append(False) - xSlice = xSlice + 1 - hitXMax = False - xSlice = 0 - ySlice = ySlice + 1 - return map - - def move(self, pt, contours=True, components=True, anchors=True): - """Move a glyph's items that are flagged as True""" - x, y = roundPt(pt) - if contours: - for contour in self.contours: - contour.move((x, y)) - if components: - for component in self.components: - component.move((x, y)) - if anchors: - for anchor in self.anchors: - anchor.move((x, y)) - - def scale(self, pt, center=(0, 0)): - """scale the glyph""" - x, y = pt - for contour in self.contours: - contour.scale((x, y), center=center) - for component in self.components: - offset = component.offset - component.offset = _scalePointFromCenter(offset, pt, center) - sX, sY = component.scale - component.scale = (sX*x, sY*y) - for anchor in self.anchors: - anchor.scale((x, y), center=center) - - def transform(self, matrix): - """Transform this glyph. - Use a Transform matrix object from - robofab.transform""" - n = [] - for c in self.contours: - c.transform(matrix) - for a in self.anchors: - a.transform(matrix) - - def rotate(self, angle, offset=None): - """rotate the glyph""" - from fontTools.misc.transform import Identity - radAngle = angle / DEGREE # convert from degrees to radians - if offset is None: - offset = (0,0) - rT = Identity.translate(offset[0], offset[1]) - rT = rT.rotate(radAngle) - rT = rT.translate(-offset[0], -offset[1]) - self.transform(rT) - - def skew(self, angle, offset=None): - """skew the glyph""" - from fontTools.misc.transform import Identity - radAngle = angle / DEGREE # convert from degrees to radians - if offset is None: - offset = (0,0) - rT = Identity.translate(offset[0], offset[1]) - rT = rT.skew(radAngle) - self.transform(rT) - - -class BaseContour(RBaseObject): - - """Base class for all contour objects.""" - - def __init__(self): - RBaseObject.__init__(self) - #self.index = None - self.changed = False # if the object needs to be saved - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - glyphParent = self.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - try: - idx = `self.index` - except ValueError: - # XXXX - idx = "XXX" - return "<RContour for %s.%s[%s]>"%(font, glyph, idx) - - def __len__(self): - return len(self.segments) - - def __mul__(self, factor): - warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.segments = [] - for i in range(len(self.segments)): - n.segments.append(self.segments[i] * factor) - n._setParentTree() - return n - - __rmul__ = __mul__ - - def __add__(self, other): - warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.segments = [] - for i in range(len(self.segments)): - n.segments.append(self.segments[i] + other.segments[i]) - n._setParentTree() - return n - - def __sub__(self, other): - warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.segments = [] - for i in range(len(self.segments)): - n.segments.append(self.segments[i] - other.segments[i]) - n._setParentTree() - return n - - def __getitem__(self, index): - return self.segments[index] - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def _nextSegment(self, segmentIndex): - return self.segments[(segmentIndex + 1) % len(self.segments)] - - def _prevSegment(self, segmentIndex): - segments = self.segments - return self.segments[(segmentIndex - 1) % len(self.segments)] - - def _get_box(self): - bounds = _box(self) - return bounds - - box = property(_get_box, doc="the bounding box for the contour") - - def _set_clockwise(self, value): - if self.clockwise != value: - self.reverseContour() - - def _get_clockwise(self): - pen = AreaPen(self) - self.draw(pen) - return pen.value < 0 - - clockwise = property(_get_clockwise, _set_clockwise, doc="direction of contour: positive=counterclockwise negative=clockwise") - - def copy(self, aParent=None): - """Duplicate this contour""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['_object', 'points', 'bPoints', 'getParent'] - for k in self.__dict__.keys(): - ok = True - if k in dont: - continue - elif k == "segments": - dup = [] - for i in self.segments: - dup.append(i.copy(n)) - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - if ok: - setattr(n, k, dup) - return n - - def _setParentTree(self): - """Set the parents of all contained and dependent objects (and their dependents) right.""" - for item in self.segments: - item.setParent(self) - - def round(self): - """round the value of all points in the contour""" - for n in self.points: - n.round() - - def draw(self, pen): - """draw the object with a fontTools pen""" - firstOn = self.segments[0].onCurve - firstType = self.segments[0].type - lastOn = self.segments[-1].onCurve - # this is a special exception for FontLab - # FL can have a contour that does not contain a move. - # this will only happen if the contour begins with a qcurve. - # in this case, we move to the segment's on curve, - # then we iterate through the rest of the points, - # then we add the first qcurve and finally we - # close the path. after this, i say "ugh." - if firstType == QCURVE: - pen.moveTo((firstOn.x, firstOn.y)) - for segment in self.segments[1:]: - segmentType = segment.type - pt = segment.onCurve.x, segment.onCurve.y - if segmentType == LINE: - pen.lineTo(pt) - elif segmentType == CURVE: - pts = [(point.x, point.y) for point in segment.points] - pen.curveTo(*pts) - elif segmentType == QCURVE: - pts = [(point.x, point.y) for point in segment.points] - pen.qCurveTo(*pts) - else: - assert 0, "unsupported segment type" - pts = [(point.x, point.y) for point in self.segments[0].points] - pen.qCurveTo(*pts) - pen.closePath() - else: - if firstType == MOVE and (firstOn.x, firstOn.y) == (lastOn.x, lastOn.y): - closed = True - else: - closed = True - for segment in self.segments: - segmentType = segment.type - pt = segment.onCurve.x, segment.onCurve.y - if segmentType == MOVE: - pen.moveTo(pt) - elif segmentType == LINE: - pen.lineTo(pt) - elif segmentType == CURVE: - pts = [(point.x, point.y) for point in segment.points] - pen.curveTo(*pts) - elif segmentType == QCURVE: - pts = [(point.x, point.y) for point in segment.points] - pen.qCurveTo(*pts) - else: - assert 0, "unsupported segment type" - if closed: - pen.closePath() - else: - pen.endPath() - - def drawPoints(self, pen): - """draw the object with a point pen""" - pen.beginPath() - lastOn = self.segments[-1].onCurve - didLastOn = False - flQCurveException = False - lastIndex = len(self.segments) - 1 - for i in range(len(self.segments)): - segment = self.segments[i] - segmentType = segment.type - # the new protocol states that we start with an onCurve - # so, if we have a move and a nd a last point overlapping, - # add the last point to the beginning and skip the move - if segmentType == MOVE and (segment.onCurve.x, segment.onCurve.y) == (lastOn.x, lastOn.y): - point = self.segments[-1].onCurve - name = getattr(segment.onCurve, 'name', None) - pen.addPoint((point.x, point.y), point.type, smooth=self.segments[-1].smooth, name=name) - didLastOn = True - continue - # this is an exception for objectsFL - # the problem is that quad contours are - # represented differently that they are in - # objectsRF: - # FL: [qcurve, qcurve, qcurve, qcurve] - # RF: [move, qcurve, qcurve, qcurve, qcurve] - # so, we need to catch this, and shift the offCurves to - # to the end of the contour - if i == 0 and segmentType == QCURVE: - flQCurveException = True - if segmentType == MOVE: - segmentType = LINE - ## the offCurves - if i == 0 and flQCurveException: - pass - else: - for point in segment.offCurve: - name = getattr(point, 'name', None) - pen.addPoint((point.x, point.y), segmentType=None, smooth=None, name=name, selected=point.selected) - ## the onCurve - # skip the last onCurve if it was used as the move - if i == lastIndex and didLastOn: - continue - point = segment.onCurve - name = getattr(point, 'name', None) - pen.addPoint((point.x, point.y), segmentType, smooth=segment.smooth, name=name, selected=point.selected) - # if we have the special qCurve case with objectsFL - # take care of the offCurves associated with the first contour - if flQCurveException: - for point in self.segments[0].offCurve: - name = getattr(point, 'name', None) - pen.addPoint((point.x, point.y), segmentType=None, smooth=None, name=name, selected=point.selected) - pen.endPath() - - def move(self, pt): - """move the contour""" - #this will be faster if we go straight to the points - for point in self.points: - point.move(pt) - - def scale(self, pt, center=(0, 0)): - """scale the contour""" - #this will be faster if we go straight to the points - for point in self.points: - point.scale(pt, center=center) - - def transform(self, matrix): - """Transform this contour. - Use a Transform matrix object from - robofab.transform""" - n = [] - for s in self.segments: - s.transform(matrix) - - def rotate(self, angle, offset=None): - """rotate the contour""" - from fontTools.misc.transform import Identity - radAngle = angle / DEGREE # convert from degrees to radians - if offset is None: - offset = (0,0) - rT = Identity.translate(offset[0], offset[1]) - rT = rT.rotate(radAngle) - self.transform(rT) - - def skew(self, angle, offset=None): - """skew the contour""" - from fontTools.misc.transform import Identity - radAngle = angle / DEGREE # convert from degrees to radians - if offset is None: - offset = (0,0) - rT = Identity.translate(offset[0], offset[1]) - rT = rT.skew(radAngle) - self.transform(rT) - - def pointInside(self, pt, evenOdd=0): - """determine if the point is inside or ouside of the contour""" - from fontTools.pens.pointInsidePen import PointInsidePen - glyph = self.getParent() - font = glyph.getParent() - piPen = PointInsidePen(glyphSet=font, testPoint=pt, evenOdd=evenOdd) - self.draw(piPen) - return piPen.getResult() - - def autoStartSegment(self): - """automatically set the lower left point of the contour as the first point.""" - #adapted from robofog - startIndex = 0 - startSegment = self.segments[0] - for i in range(len(self.segments)): - segment = self.segments[i] - startOn = startSegment.onCurve - on = segment.onCurve - if on.y <= startOn.y: - if on.y == startOn.y: - if on.x < startOn.x: - startSegment = segment - startIndex = i - else: - startSegment = segment - startIndex = i - if startIndex != 0: - self.setStartSegment(startIndex) - - def appendBPoint(self, pointType, anchor, bcpIn=(0, 0), bcpOut=(0, 0)): - """append a bPoint to the contour""" - self.insertBPoint(len(self.segments), pointType=pointType, anchor=anchor, bcpIn=bcpIn, bcpOut=bcpOut) - - def insertBPoint(self, index, pointType, anchor, bcpIn=(0, 0), bcpOut=(0, 0)): - """insert a bPoint at index on the contour""" - #insert a CURVE point that we can work with - nextSegment = self._nextSegment(index-1) - if nextSegment.type == QCURVE: - return - if nextSegment.type == MOVE: - prevSegment = self.segments[index-1] - prevOn = prevSegment.onCurve - if bcpIn != (0, 0): - new = self.appendSegment(CURVE, [(prevOn.x, prevOn.y), absoluteBCPIn(anchor, bcpIn), anchor], smooth=False) - if pointType == CURVE: - new.smooth = True - else: - new = self.appendSegment(LINE, [anchor], smooth=False) - #if the user wants an outgoing bcp, we must add a CURVE ontop of the move - if bcpOut != (0, 0): - nextOn = nextSegment.onCurve - self.appendSegment(CURVE, [absoluteBCPOut(anchor, bcpOut), (nextOn.x, nextOn.y), (nextOn.x, nextOn.y)], smooth=False) - else: - #handle the bcps - if nextSegment.type != CURVE: - prevSegment = self.segments[index-1] - prevOn = prevSegment.onCurve - prevOutX, prevOutY = (prevOn.x, prevOn.y) - else: - prevOut = nextSegment.offCurve[0] - prevOutX, prevOutY = (prevOut.x, prevOut.y) - self.insertSegment(index, segmentType=CURVE, points=[(prevOutX, prevOutY), anchor, anchor], smooth=False) - newSegment = self.segments[index] - prevSegment = self._prevSegment(index) - nextSegment = self._nextSegment(index) - if nextSegment.type == MOVE: - raise RoboFabError, 'still working out curving at the end of a contour' - elif nextSegment.type == QCURVE: - return - #set the new incoming bcp - newIn = newSegment.offCurve[1] - nIX, nIY = absoluteBCPIn(anchor, bcpIn) - newIn.x = nIX - newIn.y = nIY - #set the new outgoing bcp - hasCurve = True - if nextSegment.type != CURVE: - if bcpOut != (0, 0): - nextSegment.type = CURVE - hasCurve = True - else: - hasCurve = False - if hasCurve: - newOut = nextSegment.offCurve[0] - nOX, nOY = absoluteBCPOut(anchor, bcpOut) - newOut.x = nOX - newOut.y = nOY - #now check to see if we can convert the CURVE segment to a LINE segment - newAnchor = newSegment.onCurve - newA = newSegment.offCurve[0] - newB = newSegment.offCurve[1] - nextAnchor = nextSegment.onCurve - prevAnchor = prevSegment.onCurve - if (prevAnchor.x, prevAnchor.y) == (newA.x, newA.y) and (newAnchor.x, newAnchor.y) == (newB.x, newB.y): - newSegment.type = LINE - #the user wants a smooth segment - if pointType == CURVE: - newSegment.smooth = True - - -class BaseSegment(RBaseObject): - - """Base class for all segment objects""" - - def __init__(self): - self.changed = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - contourIndex = "unknown_contour" - contourParent = self.getParent() - if contourParent is not None: - try: - contourIndex = `contourParent.index` - except AttributeError: pass - glyphParent = contourParent.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - try: - idx = `self.index` - except ValueError: - idx = "XXX" - return "<RSegment for %s.%s[%s][%s]>"%(font, glyph, contourIndex, idx) - - def __mul__(self, factor): - warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.points = [] - for i in range(len(self.points)): - n.points.append(self.points[i] * factor) - n._setParentTree() - return n - - __rmul__ = __mul__ - - def __add__(self, other): - warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.points = [] - for i in range(len(self.points)): - n.points.append(self.points[i] + other.points[i]) - return n - - def __sub__(self, other): - warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.points = [] - for i in range(len(self.points)): - n.points.append(self.points[i] - other.points[i]) - return n - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def copy(self, aParent=None): - """Duplicate this segment""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['_object', 'getParent', 'offCurve', 'onCurve'] - for k in self.__dict__.keys(): - ok = True - if k in dont: - continue - if k == "points": - dup = [] - for i in self.points: - dup.append(i.copy(n)) - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - if ok: - setattr(n, k, dup) - return n - - def _setParentTree(self): - """Set the parents of all contained and dependent objects (and their dependents) right.""" - for item in self.points: - item.setParent(self) - - def round(self): - """round all points in the segment""" - for point in self.points: - point.round() - - def move(self, pt): - """move the segment""" - for point in self.points: - point.move(pt) - - def scale(self, pt, center=(0, 0)): - """scale the segment""" - for point in self.points: - point.scale(pt, center=center) - - def transform(self, matrix): - """Transform this segment. - Use a Transform matrix object from - robofab.transform""" - n = [] - for p in self.points: - p.transform(matrix) - - def _get_onCurve(self): - return self.points[-1] - - def _get_offCurve(self): - return self.points[:-1] - - offCurve = property(_get_offCurve, doc="on curve point for the segment") - onCurve = property(_get_onCurve, doc="list of off curve points for the segment") - - - -class BasePoint(RBaseObject): - - """Base class for point objects.""" - - def __init__(self): - #RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - contourIndex = "unknown_contour" - segmentIndex = "unknown_segment" - segmentParent = self.getParent() - if segmentParent is not None: - try: - segmentIndex = `segmentParent.index` - except AttributeError: pass - contourParent = self.getParent().getParent() - if contourParent is not None: - try: - contourIndex = `contourParent.index` - except AttributeError: pass - glyphParent = contourParent.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RPoint for %s.%s[%s][%s]>"%(font, glyph, contourIndex, segmentIndex) - - def __add__(self, other): - warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) - #Add one point to another - n = self.copy() - n.x, n.y = addPt((self.x, self.y), (other.x, other.y)) - return n - - def __sub__(self, other): - warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) - #Subtract one point from another - n = self.copy() - n.x, n.y = subPt((self.x, self.y), (other.x, other.y)) - return n - - def __mul__(self, factor): - warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) - #Multiply the point with factor. Factor can be a tuple of 2 *(f1, f2) - n = self.copy() - n.x, n.y = mulPt((self.x, self.y), factor) - return n - - __rmul__ = __mul__ - - def _hasChanged(self): - #mark the object and it's parent as changed - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def copy(self, aParent=None): - """Duplicate this point""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent', 'offCurve', 'onCurve'] - for k in self.__dict__.keys(): - ok = True - if k in dont: - continue - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - if ok: - setattr(n, k, dup) - return n - - def select(self, state=True): - """Set the selection of this point. - XXXX This method should be a lot more versatile, dealing with - different kinds of selection, select the bcp's seperately etc. - But that's for later when we need it more. For now it's just - one flag for the entire thing.""" - self.selected = state - - def round(self): - """round the values in the point""" - self.x, self.y = roundPt((self.x, self.y)) - - def move(self, pt): - """Move the point""" - self.x, self.y = addPt((self.x, self.y), pt) - - def scale(self, pt, center=(0, 0)): - """scale the point""" - nX, nY = _scalePointFromCenter((self.x, self.y), pt, center) - self.x = nX - self.y = nY - - def transform(self, matrix): - """Transform this point. Use a Transform matrix - object from fontTools.misc.transform""" - self.x, self.y = matrix.transformPoint((self.x, self.y)) - - -class BaseBPoint(RBaseObject): - - """Base class for bPoints objects.""" - - def __init__(self): - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - contourIndex = "unknown_contour" - segmentIndex = "unknown_segment" - segmentParent = self.getParent() - if segmentParent is not None: - try: - segmentIndex = `segmentParent.index` - except AttributeError: pass - contourParent = segmentParent.getParent() - if contourParent is not None: - try: - contourIndex = `contourParent.index` - except AttributeError: pass - glyphParent = contourParent.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RBPoint for %s.%s[%s][%s][%s]>"%(font, glyph, contourIndex, segmentIndex, `self.index`) - - - def __add__(self, other): - warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) - #Add one bPoint to another - n = self.copy() - n.anchor = addPt(self.anchor, other.anchor) - n.bcpIn = addPt(self.bcpIn, other.bcpIn) - n.bcpOut = addPt(self.bcpOut, other.bcpOut) - return n - - def __sub__(self, other): - warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) - #Subtract one bPoint from another - n = self.copy() - n.anchor = subPt(self.anchor, other.anchor) - n.bcpIn = subPt(self.bcpIn, other.bcpIn) - n.bcpOut = subPt(self.bcpOut, other.bcpOut) - return n - - def __mul__(self, factor): - warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) - #Multiply the bPoint with factor. Factor can be a tuple of 2 *(f1, f2) - n = self.copy() - n.anchor = mulPt(self.anchor, factor) - n.bcpIn = mulPt(self.bcpIn, factor) - n.bcpOut = mulPt(self.bcpOut, factor) - return n - - __rmul__ = __mul__ - - def _hasChanged(self): - #mark the object and it's parent as changed - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def select(self, state=True): - """Set the selection of this point. - XXXX This method should be a lot more versatile, dealing with - different kinds of selection, select the bcp's seperately etc. - But that's for later when we need it more. For now it's just - one flag for the entire thing.""" - self.selected = state - - def round(self): - """Round the coordinates to integers""" - self.anchor = roundPt(self.anchor) - pSeg = self._parentSegment - if pSeg.type != MOVE: - self.bcpIn = roundPt(self.bcpIn) - if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: - self.bcpOut = roundPt(self.bcpOut) - - def move(self, pt): - """move the bPoint""" - x, y = pt - bcpIn = self.bcpIn - bcpOut = self.bcpOut - self.anchor = (self.anchor[0] + x, self.anchor[1] + y) - pSeg = self._parentSegment - if pSeg.type != MOVE: - self.bcpIn = bcpIn - if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: - self.bcpOut = bcpOut - - def scale(self, pt, center=(0, 0)): - """scale the bPoint""" - x, y = pt - centerX, centerY = center - ogCenter = (centerX, centerY) - scaledCenter = (centerX * x, centerY * y) - shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) - anchor = self.anchor - bcpIn = self.bcpIn - bcpOut = self.bcpOut - self.anchor = ((anchor[0] * x) - shiftVal[0], (anchor[1] * y) - shiftVal[1]) - pSeg = self._parentSegment - if pSeg.type != MOVE: - self.bcpIn = ((bcpIn[0] * x), (bcpIn[1] * y)) - if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: - self.bcpOut = ((bcpOut[0] * x), (bcpOut[1] * y)) - - def transform(self, matrix): - """Transform this point. Use a Transform matrix - object from fontTools.misc.transform""" - self.anchor = matrix.transformPoint(self.anchor) - pSeg = self._parentSegment - if pSeg.type != MOVE: - self.bcpIn = matrix.transformPoint(self.bcpIn) - if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: - self.bcpOut = matrix.transformPoint(self.bcpOut) - - def _get__anchorPoint(self): - return self._parentSegment.onCurve - - _anchorPoint = property(_get__anchorPoint, doc="the oncurve point in the parent segment") - - def _get_anchor(self): - point = self._anchorPoint - return (point.x, point.y) - - def _set_anchor(self, value): - x, y = value - point = self._anchorPoint - point.x = x - point.y = y - - anchor = property(_get_anchor, _set_anchor, doc="the position of the anchor") - - def _get_bcpIn(self): - pSeg = self._parentSegment - pCount = len(pSeg.offCurve) - if pCount == 2: - p = pSeg.offCurve[1] - pOn = pSeg.onCurve - return relativeBCPIn((pOn.x, pOn.y), (p.x, p.y)) - else: - return (0, 0) - - def _set_bcpIn(self, value): - x, y = (absoluteBCPIn(self.anchor, value)) - pSeg = self._parentSegment - if pSeg.type == MOVE: - #the user wants to have a bcp leading into the MOVE - if value == (0, 0) and self.bcpOut == (0, 0): - #we have a straight line between the two anchors - pass - else: - #we need to insert a new CURVE segment ontop of the move - contour = self._parentSegment.getParent() - #set the prev segment outgoing bcp to the onCurve - prevSeg = contour._prevSegment(self._parentSegment.index) - prevOn = prevSeg.onCurve - contour.appendSegment(CURVE, [(prevOn.x, prevOn.y), (x, y), self.anchor], smooth=False) - else: - pCount = len(pSeg.offCurve) - if pCount == 2: - #if the two points in the offCurvePoints list are located at the - #anchor coordinates we can switch to a LINE segment type - if value == (0, 0) and self.bcpOut == (0, 0): - pSeg.type = LINE - pSeg.smooth = False - else: - pSeg.offCurve[1].x = x - pSeg.offCurve[1].y = y - elif value != (0, 0): - pSeg.type = CURVE - pSeg.offCurve[1].x = x - pSeg.offCurve[1].y = y - - bcpIn = property(_get_bcpIn, _set_bcpIn, doc="the (x,y) for the incoming bcp") - - def _get_bcpOut(self): - pSeg = self._parentSegment - nextSeg = pSeg.getParent()._nextSegment(pSeg.index) - nsCount = len(nextSeg.offCurve) - if nsCount == 2: - p = nextSeg.offCurve[0] - return relativeBCPOut(self.anchor, (p.x, p.y)) - else: - return (0, 0) - - def _set_bcpOut(self, value): - x, y = (absoluteBCPOut(self.anchor, value)) - pSeg = self._parentSegment - nextSeg = pSeg.getParent()._nextSegment(pSeg.index) - if nextSeg.type == MOVE: - if value == (0, 0) and self.bcpIn == (0, 0): - pass - else: - #we need to insert a new CURVE segment ontop of the move - contour = self._parentSegment.getParent() - nextOn = nextSeg.onCurve - contour.appendSegment(CURVE, [(x, y), (nextOn.x, nextOn.y), (nextOn.x, nextOn.y)], smooth=False) - else: - nsCount = len(nextSeg.offCurve) - if nsCount == 2: - #if the two points in the offCurvePoints list are located at the - #anchor coordinates we can switch to a LINE segment type - if value == (0, 0) and self.bcpIn == (0, 0): - nextSeg.type = LINE - nextSeg.smooth = False - else: - nextSeg.offCurve[0].x = x - nextSeg.offCurve[0].y = y - elif value != (0, 0): - nextSeg.type = CURVE - nextSeg.offCurve[0].x = x - nextSeg.offCurve[0].y = y - - bcpOut = property(_get_bcpOut, _set_bcpOut, doc="the (x,y) for the outgoing bcp") - - def _get_type(self): - pType = self._parentSegment.type - bpType = CORNER - if pType == CURVE: - if self._parentSegment.smooth: - bpType = CURVE - return bpType - - def _set_type(self, pointType): - pSeg = self._parentSegment - segType = pSeg.type - #user wants a curve where there is a line - if pointType == CURVE and segType == LINE: - pSeg.type = CURVE - pSeg.smooth = True - #the anchor is a curve segment. so, all we need to do is turn the smooth off - elif pointType == CORNER and segType == CURVE: - pSeg.smooth = False - - type = property(_get_type, _set_type, doc="the type of bPoint, either 'corner' or 'curve'") - - -class BaseComponent(RBaseObject): - - """Base class for all component objects.""" - - def __init__(self): - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - glyphParent = self.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RComponent for %s.%s.components[%s]>"%(font, glyph, `self.index`) - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def copy(self, aParent=None): - """Duplicate this component.""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent', '_object'] - for k in self.__dict__.keys(): - if k in dont: - continue - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - setattr(n, k, dup) - return n - - def __add__(self, other): - warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) - #Add one Component to another - n = self.copy() - n.offset = addPt(self.offset, other.offset) - n.scale = addPt(self.scale, other.scale) - return n - - def __sub__(self, other): - warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) - #Subtract one Component from another - n = self.copy() - n.offset = subPt(self.offset, other.offset) - n.scale = subPt(self.scale, other.scale) - return n - - def __mul__(self, factor): - warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) - #Multiply the Component with factor. Factor can be a tuple of 2 *(f1, f2) - n = self.copy() - n.offset = mulPt(self.offset, factor) - n.scale = mulPt(self.scale, factor) - return n - - __rmul__ = __mul__ - - def _get_box(self): - parentGlyph = self.getParent() - # the component is an orphan - if parentGlyph is None: - return None - parentFont = parentGlyph.getParent() - # the glyph that contains the component - # does not hae a parent - if parentFont is None: - return None - # the font does not have a glyph - # that matches the glyph that - # this component references - if not parentFont.has_key(self.baseGlyph): - return None - return _box(self, parentFont) - - box = property(_get_box, doc="the bounding box of the component: (xMin, yMin, xMax, yMax)") - - def round(self): - """round the offset values""" - self.offset = roundPt(self.offset) - self._hasChanged() - - def draw(self, pen): - """Segment pen drawing method.""" - if isinstance(pen, AbstractPen): - # It's a FontTools pen, which for addComponent is identical - # to PointPen. - self.drawPoints(pen) - else: - # It's an "old" 'Fab pen - pen.addComponent(self.baseGlyph, self.offset, self.scale) - - def drawPoints(self, pen): - """draw the object with a point pen""" - oX, oY = self.offset - sX, sY = self.scale - #xScale, xyScale, yxScale, yScale, xOffset, yOffset - pen.addComponent(self.baseGlyph, (sX, 0, 0, sY, oX, oY)) - - -class BaseAnchor(RBaseObject): - - """Base class for all anchor point objects.""" - - def __init__(self): - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - glyphParent = self.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RAnchor for %s.%s.anchors[%s]>"%(font, glyph, `self.index`) - - def __add__(self, other): - warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) - #Add one anchor to another - n = self.copy() - n.x, n.y = addPt((self.x, self.y), (other.x, other.y)) - return n - - def __sub__(self, other): - warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) - #Substract one anchor from another - n = self.copy() - n.x, n.y = subPt((self.x, self.y), (other.x, other.y)) - return n - - def __mul__(self, factor): - warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) - #Multiply the anchor with factor. Factor can be a tuple of 2 *(f1, f2) - n = self.copy() - n.x, n.y = mulPt((self.x, self.y), factor) - return n - - __rmul__ = __mul__ - - def _hasChanged(self): - #mark the object and it's parent as changed - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def copy(self, aParent=None): - """Duplicate this anchor.""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent', '_object'] - for k in self.__dict__.keys(): - if k in dont: - continue - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - setattr(n, k, dup) - return n - - def round(self): - """round the values in the anchor""" - self.x, self.y = roundPt((self.x, self.y)) - self._hasChanged() - - def draw(self, pen): - """Draw the object onto a segment pen""" - if isinstance(pen, AbstractPen): - # It's a FontTools pen - pen.moveTo((self.x, self.y)) - pen.endPath() - else: - # It's an "old" 'Fab pen - pen.addAnchor(self.name, (self.x, self.y)) - - def drawPoints(self, pen): - """draw the object with a point pen""" - pen.beginPath() - pen.addPoint((self.x, self.y), segmentType="move", smooth=False, name=self.name) - pen.endPath() - - def move(self, pt): - """Move the anchor""" - x, y = pt - pX, pY = self.position - self.position = (pX+x, pY+y) - - def scale(self, pt, center=(0, 0)): - """scale the anchor""" - pos = self.position - self.position = _scalePointFromCenter(pos, pt, center) - - def transform(self, matrix): - """Transform this anchor. Use a Transform matrix - object from fontTools.misc.transform""" - self.x, self.y = matrix.transformPoint((self.x, self.y)) - - -class BaseGuide(RBaseObject): - - """Base class for all guide objects.""" - - def __init__(self): - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - -class BaseInfo(RBaseObject): - - _baseAttributes = ["_object", "changed", "selected", "getParent"] - _deprecatedAttributes = ufoLib.deprecatedFontInfoAttributesVersion2 - _infoAttributes = ufoLib.fontInfoAttributesVersion2 - # subclasses may define a list of environment - # specific attributes that can be retrieved or set. - _environmentAttributes = [] - # subclasses may define a list of attributes - # that should not follow the standard get/set - # order provided by __setattr__ and __getattr__. - # for these attributes, the environment specific - # set and get methods must handle this value - # without any pre-call validation. - # (yeah. this is because of some FontLab dumbness.) - _environmentOverrides = [] - - def __setattr__(self, attr, value): - # check to see if the attribute has been - # deprecated. if so, warn the caller and - # update the attribute and value. - if attr in self._deprecatedAttributes: - newAttr, newValue = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) - note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) - warn(note, DeprecationWarning) - attr = newAttr - value = newValue - # setting a known attribute - if attr in self._infoAttributes or attr in self._environmentAttributes: - # lightly test the validity of the value - if value is not None: - isValidValue = ufoLib.validateFontInfoVersion2ValueForAttribute(attr, value) - if not isValidValue: - raise RoboFabError("Invalid value (%s) for attribute (%s)." % (repr(value), attr)) - # use the environment specific info attr set - # method if it is defined. - if hasattr(self, "_environmentSetAttr"): - self._environmentSetAttr(attr, value) - # fallback to super - else: - super(BaseInfo, self).__setattr__(attr, value) - # unknown attribute, test to see if it is a python attr - elif attr in self.__dict__ or attr in self._baseAttributes: - super(BaseInfo, self).__setattr__(attr, value) - # raise an attribute error - else: - raise AttributeError("Unknown attribute %s." % attr) - - # subclasses with environment specific attr setting can - # implement this method. __setattr__ will call it if present. - # def _environmentSetAttr(self, attr, value): - # pass - - def __getattr__(self, attr): - if attr in self._environmentOverrides: - return self._environmentGetAttr(attr) - # check to see if the attribute has been - # deprecated. if so, warn the caller and - # flag the value as needing conversion. - needValueConversionTo1 = False - if attr in self._deprecatedAttributes: - oldAttr = attr - oldValue = attr - newAttr, x = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, None) - note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) - warn(note, DeprecationWarning) - attr = newAttr - needValueConversionTo1 = True - # getting a known attribute - if attr in self._infoAttributes or attr in self._environmentAttributes: - # use the environment specific info attr get - # method if it is defined. - if hasattr(self, "_environmentGetAttr"): - value = self._environmentGetAttr(attr) - # fallback to super - else: - try: - value = super(BaseInfo, self).__getattribute__(attr) - except AttributeError: - return None - if needValueConversionTo1: - oldAttr, value = ufoLib.convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) - return value - # raise an attribute error - else: - raise AttributeError("Unknown attribute %s." % attr) - - # subclasses with environment specific attr retrieval can - # implement this method. __getattr__ will call it if present. - # it should return the requested value. - # def _environmentGetAttr(self, attr): - # pass - -class BaseFeatures(RBaseObject): - - def __init__(self): - RBaseObject.__init__(self) - self._text = "" - - def _get_text(self): - return self._text - - def _set_text(self, value): - assert isinstance(value, basestring) - self._text = value - - text = property(_get_text, _set_text, doc="raw feature text.") - - -class BaseGroups(dict): - - """Base class for all RFont.groups objects""" - - def __init__(self): - pass - - def __repr__(self): - font = "unnamed_font" - fontParent = self.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RGroups for %s>"%font - - def getParent(self): - """this method will be overwritten with a weakref if there is a parent.""" - pass - - def setParent(self, parent): - import weakref - self.__dict__['getParent'] = weakref.ref(parent) - - def __setitem__(self, key, value): - #override base class to insure proper data is being stored - if not isinstance(key, str): - raise RoboFabError, 'key must be a string' - if not isinstance(value, list): - raise RoboFabError, 'group must be a list' - super(BaseGroups, self).__setitem__(key, value) - - def findGlyph(self, glyphName): - """return a list of all groups contianing glyphName""" - found = [] - for i in self.keys(): - l = self[i] - if glyphName in l: - found.append(i) - return found - - -class BaseLib(dict): - - """Base class for all lib objects""" - - def __init__(self): - pass - - def __repr__(self): - #this is a doozy! - parent = "unknown_parent" - parentObject = self.getParent() - if parentObject is not None: - #do we have a font? - try: - parent = parentObject.info.postscriptFullName - except AttributeError: - #or do we have a glyph? - try: - parent = parentObject.name - #we must be an orphan - except AttributeError: pass - return "<RLib for %s>"%parent - - def getParent(self): - """this method will be overwritten with a weakref if there is a parent.""" - pass - - def setParent(self, parent): - import weakref - self.__dict__['getParent'] = weakref.ref(parent) - - def copy(self, aParent=None): - """Duplicate this lib.""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - for k in self.keys(): - n[k] = copy.deepcopy(self[k]) - return n - - -class BaseKerning(RBaseObject): - - """Base class for all kerning objects. Object behaves like a dict but has - some special kerning specific tricks.""" - - def __init__(self, kerningDict=None): - if not kerningDict: - kerningDict = {} - self._kerning = kerningDict - self.changed = False # if the object needs to be saved - - def __repr__(self): - font = "unnamed_font" - fontParent = self.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RKerning for %s>"%font - - def __getitem__(self, key): - if isinstance(key, tuple): - pair = key - return self.get(pair) - elif isinstance(key, str): - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - keys = self.keys() - if key > len(keys): - raise IndexError - keys.sort() - pair = keys[key] - if not self._kerning.has_key(pair): - raise IndexError - else: - return pair - - def __setitem__(self, pair, value): - if not isinstance(pair, tuple): - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if len(pair) != 2: - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if value == 0: - if self._kerning.get(pair) is not None: - del self._kerning[pair] - else: - self._kerning[pair] = value - self._hasChanged() - - def __len__(self): - return len(self._kerning.keys()) - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def keys(self): - """return list of kerning pairs""" - return self._kerning.keys() - - def values(self): - """return a list of kerning values""" - return self._kerning.values() - - def items(self): - """return a list of kerning items""" - return self._kerning.items() - - def has_key(self, pair): - return self._kerning.has_key(pair) - - def get(self, pair, default=None): - """get a value. return None if the pair does not exist""" - value = self._kerning.get(pair, default) - return value - - def remove(self, pair): - """remove a kerning pair""" - self[pair] = 0 - - def getAverage(self): - """return average of all kerning pairs""" - if len(self) == 0: - return 0 - value = 0 - for i in self.values(): - value = value + i - return value / float(len(self)) - - def getExtremes(self): - """return the lowest and highest kerning values""" - if len(self) == 0: - return 0 - values = self.values() - values.append(0) - values.sort() - return (values[0], values[-1]) - - def update(self, kerningDict): - """replace kerning data with the data in the given kerningDict""" - for pair in kerningDict.keys(): - self[pair] = kerningDict[pair] - - def clear(self): - """clear all kerning""" - self._kerning = {} - - def add(self, value): - """add value to all kerning pairs""" - for pair in self.keys(): - self[pair] = self[pair] + value - - def scale(self, value): - """scale all kernng pairs by value""" - for pair in self.keys(): - self[pair] = self[pair] * value - - def minimize(self, minimum=10): - """eliminate pairs with value less than minimum""" - for pair in self.keys(): - if abs(self[pair]) < minimum: - self[pair] = 0 - - def eliminate(self, leftGlyphsToEliminate=None, rightGlyphsToEliminate=None, analyzeOnly=False): - """eliminate pairs containing a left glyph that is in the leftGlyphsToEliminate list - or a right glyph that is in the rightGlyphsToELiminate list. - sideGlyphsToEliminate can be a string: 'a' or list: ['a', 'b']. - analyzeOnly will not remove pairs. it will return a count - of all pairs that would be removed.""" - if analyzeOnly: - count = 0 - lgte = leftGlyphsToEliminate - rgte = rightGlyphsToEliminate - if isinstance(lgte, str): - lgte = [lgte] - if isinstance(rgte, str): - rgte = [rgte] - for pair in self.keys(): - left, right = pair - if left in lgte or right in rgte: - if analyzeOnly: - count = count + 1 - else: - self[pair] = 0 - if analyzeOnly: - return count - else: - return None - - def interpolate(self, sourceDictOne, sourceDictTwo, value, clearExisting=True): - """interpolate the kerning between sourceDictOne - and sourceDictTwo. clearExisting will clear existing - kerning first.""" - if isinstance(value, tuple): - # in case the value is a x, y tuple: use the x only. - value = value[0] - if clearExisting: - self.clear() - pairs = set(sourceDictOne.keys()) | set(sourceDictTwo.keys()) - for pair in pairs: - s1 = sourceDictOne.get(pair, 0) - s2 = sourceDictTwo.get(pair, 0) - self[pair] = _interpolate(s1, s2, value) - - def round(self, multiple=10): - """round the kerning pair values to increments of multiple""" - for pair in self.keys(): - value = self[pair] - self[pair] = int(round(value / float(multiple))) * multiple - - def occurrenceCount(self, glyphsToCount): - """return a dict with glyphs as keys and the number of - occurances of that glyph in the kerning pairs as the value - glyphsToCount can be a string: 'a' or list: ['a', 'b']""" - gtc = glyphsToCount - if isinstance(gtc, str): - gtc = [gtc] - gtcDict = {} - for glyph in gtc: - gtcDict[glyph] = 0 - for pair in self.keys(): - left, right = pair - if not gtcDict.get(left): - gtcDict[left] = 0 - if not gtcDict.get(right): - gtcDict[right] = 0 - gtcDict[left] = gtcDict[left] + 1 - gtcDict[right] = gtcDict[right] + 1 - found = {} - for glyphName in gtc: - found[glyphName] = gtcDict[glyphName] - return found - - def getLeft(self, glyphName): - """Return a list of kerns with glyphName as left character.""" - hits = [] - for k, v in self.items(): - if k[0] == glyphName: - hits.append((k, v)) - return hits - - def getRight(self, glyphName): - """Return a list of kerns with glyphName as left character.""" - hits = [] - for k, v in self.items(): - if k[1] == glyphName: - hits.append((k, v)) - return hits - - def combine(self, kerningDicts, overwriteExisting=True): - """combine two or more kerning dictionaries. - overwrite exsisting duplicate pairs if overwriteExisting=True""" - if isinstance(kerningDicts, dict): - kerningDicts = [kerningDicts] - for kd in kerningDicts: - for pair in kd.keys(): - exists = self.has_key(pair) - if exists and overwriteExisting: - self[pair] = kd[pair] - elif not exists: - self[pair] = kd[pair] - - def swapNames(self, swapTable): - """change glyph names in all kerning pairs based on swapTable. - swapTable = {'BeforeName':'AfterName', ...}""" - for pair in self.keys(): - foundInstance = False - left, right = pair - if swapTable.has_key(left): - left = swapTable[left] - foundInstance = True - if swapTable.has_key(right): - right = swapTable[right] - foundInstance = True - if foundInstance: - self[(left, right)] = self[pair] - self[pair] = 0 - - def explodeClasses(self, leftClassDict=None, rightClassDict=None, analyzeOnly=False): - """turn class kerns into real kerning pairs. classes should - be defined in dicts: {'O':['C', 'G', 'Q'], 'H':['B', 'D', 'E', 'F', 'I']}. - analyzeOnly will not remove pairs. it will return a count - of all pairs that would be added""" - if not leftClassDict: - leftClassDict = {} - if not rightClassDict: - rightClassDict = {} - if analyzeOnly: - count = 0 - for pair in self.keys(): - left, right = pair - value = self[pair] - if leftClassDict.get(left) and rightClassDict.get(right): - allLeft = leftClassDict[left] + [left] - allRight = rightClassDict[right] + [right] - for leftSub in allLeft: - for rightSub in allRight: - if analyzeOnly: - count = count + 1 - else: - self[(leftSub, rightSub)] = value - elif leftClassDict.get(left) and not rightClassDict.get(right): - allLeft = leftClassDict[left] + [left] - for leftSub in allLeft: - if analyzeOnly: - count = count + 1 - else: - self[(leftSub, right)] = value - elif rightClassDict.get(right) and not leftClassDict.get(left): - allRight = rightClassDict[right] + [right] - for rightSub in allRight: - if analyzeOnly: - count = count + 1 - else: - self[(left, rightSub)] = value - if analyzeOnly: - return count - else: - return None - - def implodeClasses(self, leftClassDict=None, rightClassDict=None, analyzeOnly=False): - """condense the number of kerning pairs by applying classes. - this will eliminate all pairs containg the classed glyphs leaving - pairs that contain the key glyphs behind. analyzeOnly will not - remove pairs. it will return a count of all pairs that would be removed.""" - if not leftClassDict: - leftClassDict = {} - if not rightClassDict: - rightClassDict = {} - leftImplode = [] - rightImplode = [] - for value in leftClassDict.values(): - leftImplode = leftImplode + value - for value in rightClassDict.values(): - rightImplode = rightImplode + value - analyzed = self.eliminate(leftGlyphsToEliminate=leftImplode, rightGlyphsToEliminate=rightImplode, analyzeOnly=analyzeOnly) - if analyzeOnly: - return analyzed - else: - return None - - def importAFM(self, path, clearExisting=True): - """Import kerning pairs from an AFM file. clearExisting=True will - clear all exising kerning""" - from fontTools.afmLib import AFM - #a nasty hack to fix line ending problems - f = open(path, 'rb') - text = f.read().replace('\r', '\n') - f.close() - f = open(path, 'wb') - f.write(text) - f.close() - #/nasty hack - kerning = AFM(path)._kerning - if clearExisting: - self.clear() - for pair in kerning.keys(): - self[pair] = kerning[pair] - - def asDict(self, returnIntegers=True): - """return the object as a dictionary""" - if not returnIntegers: - return self._kerning - else: - #duplicate the kerning dict so that we aren't destroying it - kerning = {} - for pair in self.keys(): - kerning[pair] = int(round(self[pair])) - return kerning - - def __add__(self, other): - new = self.__class__() - k = set(self.keys()) | set(other.keys()) - for key in k: - new[key] = self.get(key, 0) + other.get(key, 0) - return new - - def __sub__(self, other): - new = self.__class__() - k = set(self.keys()) | set(other.keys()) - for key in k: - new[key] = self.get(key, 0) - other.get(key, 0) - return new - - def __mul__(self, factor): - new = self.__class__() - for name, value in self.items(): - new[name] = value * factor - return new - - __rmul__ = __mul__ - - def __div__(self, factor): - if factor == 0: - raise ZeroDivisionError - return self.__mul__(1.0/factor) - diff --git a/misc/pylib/robofab/objects/objectsFF.py b/misc/pylib/robofab/objects/objectsFF.py deleted file mode 100644 index 4ed6aae1e..000000000 --- a/misc/pylib/robofab/objects/objectsFF.py +++ /dev/null @@ -1,1253 +0,0 @@ - - -__DEBUG__ = True -__version__ = "0.2" - -""" - RoboFab API Objects for FontForge - http://fontforge.sourceforge.net - - FontForge python docs: - http://fontforge.sourceforge.net/python.html - - Note: This is dead. EvB: "objectsFF.py is very dead and should only serve as an example of "dead" - - History - Version zero. May 2007. EvB - Experiment to see how far the API can be made to work. - - 0.1 extended testing and comparisons for attributes. - 0.2 checked into svn. Still quite raw. Lots of print statements and tests at the end. - - Notes - This code is best used with fontforge compiled as a python extension. - - FontForge Python API: - __doc__ - str(object) -> string - - Return a nice string representation of the object. - If the argument is a string, the return value is the same object. - - __file__ - str(object) -> string - - Return a nice string representation of the object. - If the argument is a string, the return value is the same object. - - __name__ - str(object) -> string - - Return a nice string representation of the object. - If the argument is a string, the return value is the same object. - - activeFont - If invoked from the UI, this returns the currently active font. When not in UI this returns None - - activeFontInUI - If invoked from the UI, this returns the currently active font. When not in UI this returns None - - activeGlyph - If invoked from the UI, this returns the currently active glyph (or None) - - ask - Pops up a dialog asking the user a question and providing a set of buttons for the user to reply with - - askChoices - Pops up a dialog asking the user a question and providing a scrolling list for the user to reply with - - askString - Pops up a dialog asking the user a question and providing a textfield for the user to reply with - - contour - fontforge Contour objects - - contouriter - None - - cvt - fontforge cvt objects - - defaultOtherSubrs - Use FontForge's default "othersubrs" functions for Type1 fonts - - font - FontForge Font object - - fontiter - None - - fonts - Returns a tuple of all loaded fonts - - fontsInFile - Returns a tuple containing the names of any fonts in an external file - - getPrefs - Get FontForge preference items - - glyph - FontForge GlyphPen object - - glyphPen - FontForge Glyph object - - hasSpiro - Returns whether this fontforge has access to Raph Levien's spiro package - - hasUserInterface - Returns whether this fontforge session has a user interface (True if it has opened windows) or is just running a script (False) - - hooks - dict() -> new empty dictionary. - dict(mapping) -> new dictionary initialized from a mapping object's - (key, value) pairs. - dict(seq) -> new dictionary initialized as if via: - d = {} - for k, v in seq: - d[k] = v - dict(**kwargs) -> new dictionary initialized with the name=value pairs - in the keyword argument list. For example: dict(one=1, two=2) - - layer - fontforge Layer objects - - layeriter - None - - loadEncodingFile - Load an encoding file into the list of encodings - - loadNamelist - Load a namelist into the list of namelists - - loadNamelistDir - Load a directory of namelist files into the list of namelists - - loadPlugin - Load a FontForge plugin - - loadPluginDir - Load a directory of FontForge plugin files - - loadPrefs - Load FontForge preference items - - logWarning - Adds a non-fatal message to the Warnings window - - open - Opens a font and returns it - - openFilename - Pops up a file picker dialog asking the user for a filename to open - - parseTTInstrs - Takes a string and parses it into a tuple of truetype instruction bytes - - point - fontforge Point objects - - postError - Pops up an error dialog box with the given title and message - - postNotice - Pops up an notice window with the given title and message - - preloadCidmap - Load a cidmap file - - printSetup - Prepare to print a font sample (select default printer or file, page size, etc.) - - private - FontForge private dictionary - - privateiter - None - - readOtherSubrsFile - Read from a file, "othersubrs" functions for Type1 fonts - - registerImportExport - Adds an import/export spline conversion module - - registerMenuItem - Adds a menu item (which runs a python script) to the font or glyph (or both) windows -- in the Tools menu - - saveFilename - Pops up a file picker dialog asking the user for a filename to use for saving - - savePrefs - Save FontForge preference items - - selection - fontforge selection objects - - setPrefs - Set FontForge preference items - - spiroCorner - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroG2 - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroG4 - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroLeft - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroOpen - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroRight - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - unParseTTInstrs - Takes a tuple of truetype instruction bytes and converts to a human readable string - - unicodeFromName - Given a name, look it up in the namelists and find what unicode code point it maps to (returns -1 if not found) - - version - Returns a string containing the current version of FontForge, as 20061116 - - - - -Problems: - XXX: reading glif from UFO: is the contour order changed in some way? - - -ToDo: - - segments ? - - -""" - -import os -from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\ - BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseGroups, BaseLib,\ - roundPt, addPt, _box,\ - MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ - relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut - -from robofab.objects.objectsRF import RGlyph as _RGlyph - -import fontforge -import psMat - - -# a list of attributes that are to be copied when copying a glyph. -# this is used by glyph.copy and font.insertGlyph -GLYPH_COPY_ATTRS = [ - "name", - "width", - "unicodes", - "note", - "lib", - ] - - - -def CurrentFont(): - if fontforge.hasUserInterface(): - _font = fontforge.activeFontInUI() - return RFont(_font) - if __DEBUG__: - print "CurrentFont(): fontforge not running with user interface," - return None - -def OpenFont(fontPath): - obj = fontforge.open(fontPath) - if __DEBUG__: - print "OpenFont", fontPath - print "result:", obj - return RFont(obj) - -def NewFont(fontPath=None): - _font = fontforge.font() - if __DEBUG__: - print "NewFont", fontPath - print "result:", _font - return RFont(_font) - - - - -class RFont(BaseFont): - def __init__(self, font=None): - if font is None: - # make a new font - pass - else: - self._object = font - - # ----------------------------------------------------------------- - # - # access - - def keys(self): - """FF implements __iter__ for the font object - better?""" - return [n.glyphname for n in self._object.glyphs()] - - def has_key(self, glyphName): - return glyphName in self - - def _get_info(self): - return RInfo(self._object) - - info = property(_get_info, doc="font info object") - - def __iter__(self): - for glyphName in self.keys(): - yield self.getGlyph(glyphName) - - - # ----------------------------------------------------------------- - # - # file - - def _get_path(self): - return self._object.path - - path = property(_get_path, doc="path of this file") - - def __contains__(self, glyphName): - return glyphName in self.keys() - - def save(self, path=None): - """Save this font as sfd file. - XXX: how to set a sfd path if is none - """ - if path is not None: - # trying to save it somewhere else - _path = path - else: - _path = self.path - if os.path.splitext(_path)[-1] != ".sfd": - _path = os.path.splitext(_path)[0]+".sfd" - if __DEBUG__: - print "RFont.save() to", _path - self._object.save(_path) - - def naked(self): - return self._object - - def close(self): - if __DEBUG__: - print "RFont.close()" - self._object.close() - - - # ----------------------------------------------------------------- - # - # generate - - def dummyGeneratePreHook(self, *args): - print "dummyGeneratePreHook", args - - def dummyGeneratePostHook(self, *args): - print "dummyGeneratePostHook", args - - def generate(self, outputType, path=None): - """ - generate the font. outputType is the type of font to ouput. - --Ouput Types: - 'pctype1' : PC Type 1 font (binary/PFB) - 'pcmm' : PC MultipleMaster font (PFB) - 'pctype1ascii' : PC Type 1 font (ASCII/PFA) - 'pcmmascii' : PC MultipleMaster font (ASCII/PFA) - 'unixascii' : UNIX ASCII font (ASCII/PFA) - 'mactype1' : Mac Type 1 font (generates suitcase and LWFN file) - 'otfcff' : PS OpenType (CFF-based) font (OTF) - 'otfttf' : PC TrueType/TT OpenType font (TTF) - 'macttf' : Mac TrueType font (generates suitcase) - 'macttdfont' : Mac TrueType font (generates suitcase with resources in data fork) - (doc adapted from http://dev.fontlab.net/flpydoc/) - - path can be a directory or a directory file name combo: - path="DirectoryA/DirectoryB" - path="DirectoryA/DirectoryB/MyFontName" - if no path is given, the file will be output in the same directory - as the vfb file. if no file name is given, the filename will be the - vfb file name with the appropriate suffix. - """ - - extensions = { - 'pctype1': 'pfm', - 'otfcff': 'otf', - } - - if __DEBUG__: - print "font.generate", outputType, path - - # set pre and post hooks (necessary?) - temp = getattr(self._object, "temporary") - if temp is None: - self._object.temporary = {} - else: - if type(self._object.temporary)!=dict: - self._object.temporary = {} - self._object.temporary['generateFontPreHook'] = self.dummyGeneratePreHook - self._object.temporary['generateFontPostHook'] = self.dummyGeneratePostHook - - # make a path for the destination - if path is None: - fileName = os.path.splitext(os.path.basename(self.path))[0] - dirName = os.path.dirname(self.path) - extension = extensions.get(outputType) - if extension is not None: - fileName = "%s.%s"%(fileName, extension) - else: - if __DEBUG__: - print "can't generate font in %s format"%outputType - return - path = os.path.join(dirName, fileName) - - # prepare OTF fields - generateFlags = [] - generateFlags.append('opentype') - # generate - self._object.generate(filename=path, flags=generateFlags) - if __DEBUG__: - print "font.generate():", path - return path - - - # ----------------------------------------------------------------- - # - # kerning stuff - - def _get_kerning(self): - kerning = {} - f = self._object - for g in f.glyphs: - for p in g.kerning: - try: - key = (g.name, f[p.key].name) - kerning[key] = p.value - except AttributeError: pass #catch for TT exception - rk = RKerning(kerning) - rk.setParent(self) - return rk - - kerning = property(_get_kerning, doc="a kerning object") - - # ----------------------------------------------------------------- - # - # glyph stuff - - def getGlyph(self, glyphName): - try: - ffGlyph = self._object[glyphName] - except TypeError: - print "font.getGlyph, can't find glyphName, returning new glyph" - return self.newGlyph(glyphName) - glyph = RGlyph(ffGlyph) - glyph.setParent(self) - return glyph - - def newGlyph(self, glyphName, clear=True): - """Make a new glyph - - Notes: not sure how to make a new glyph without an encoded name. - createChar() seems to be intended for that, but when I pass it -1 - for the unicode, it complains that it wants -1. Perhaps a bug? - """ - # is the glyph already there? - glyph = None - if glyphName in self: - if clear: - self._object[glyphName].clear() - return self[glyphName] - else: - # is the glyph in an encodable place: - slot = self._object.findEncodingSlot(glyphName) - if slot == -1: - # not encoded - print "font.newGlyph: unencoded slot", slot, glyphName - glyph = self._object.createChar(-1, glyphName) - else: - glyph = self._object.createMappedChar(glyphName) - glyph = RGlyph(self._object[glyphName]) - glyph.setParent(self) - return glyph - - def removeGlyph(self, glyphName): - self._object.removeGlyph(glyphName) - - - - -class RGlyph(BaseGlyph): - """Fab wrapper for FF Glyph object""" - def __init__(self, ffGlyph=None): - if ffGlyph is None: - raise RoboFabError - self._object = ffGlyph - # XX anchors seem to be supported, but in a different way - # XX so, I will ignore them for now to get something working. - self.anchors = [] - self.lib = {} - - def naked(self): - return self._object - - def setChanged(self): - self._object.changed() - - - # ----------------------------------------------------------------- - # - # attributes - - def _get_name(self): - return self._object.glyphname - def _set_name(self, value): - self._object.glyphname = value - name = property(_get_name, _set_name, doc="name") - - def _get_note(self): - return self._object.comment - def _set_note(self, note): - self._object.comment = note - note = property(_get_note, _set_note, doc="note") - - def _get_width(self): - return self._object.width - def _set_width(self, width): - self._object.width = width - width = property(_get_width, _set_width, doc="width") - - def _get_leftMargin(self): - return self._object.left_side_bearing - def _set_leftMargin(self, leftMargin): - self._object.left_side_bearing = leftMargin - leftMargin = property(_get_leftMargin, _set_leftMargin, doc="leftMargin") - - def _get_rightMargin(self): - return self._object.right_side_bearing - def _set_rightMargin(self, rightMargin): - self._object.right_side_bearing = rightMargin - rightMargin = property(_get_rightMargin, _set_rightMargin, doc="rightMargin") - - def _get_unicodes(self): - return [self._object.unicode] - def _set_unicodes(self, unicodes): - assert len(unicodes)==1 - self._object.unicode = unicodes[0] - unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes") - - def _get_unicode(self): - return self._object.unicode - def _set_unicode(self, unicode): - self._object.unicode = unicode - unicode = property(_get_unicode, _set_unicode, doc="unicode") - - def _get_box(self): - bounds = self._object.boundingBox() - return bounds - box = property(_get_box, doc="the bounding box of the glyph: (xMin, yMin, xMax, yMax)") - - def _get_mark(self): - """color of the glyph box in the font view. This accepts a 6 hex digit number. - - XXX the FL implementation accepts a - """ - import colorsys - r = (self._object.color&0xff0000)>>16 - g = (self._object.color&0xff00)>>8 - g = (self._object.color&0xff)>>4 - return colorsys.rgb_to_hsv( r, g, b)[0] - - def _set_mark(self, markColor=-1): - import colorsys - self._object.color = colorSys.hsv_to_rgb(markColor, 1, 1) - - mark = property(_get_mark, _set_mark, doc="the color of the glyph box in the font view") - - - # ----------------------------------------------------------------- - # - # pen, drawing - - def getPen(self): - return self._object.glyphPen() - - def __getPointPen(self): - """Return a point pen. - - Note: FontForge doesn't support segment pen, so return an adapter. - """ - from robofab.pens.adapterPens import PointToSegmentPen - segmentPen = self._object.glyphPen() - return PointToSegmentPen(segmentPen) - - def getPointPen(self): - from robofab.pens.rfUFOPen import RFUFOPointPen - pen = RFUFOPointPen(self) - #print "getPointPen", pen, pen.__class__, dir(pen) - return pen - - def draw(self, pen): - """draw - - """ - self._object.draw(pen) - pen = None - - def drawPoints(self, pen): - """drawPoints - - Note: FontForge implements glyph.draw, but not glyph.drawPoints. - """ - from robofab.pens.adapterPens import PointToSegmentPen, SegmentToPointPen - adapter = SegmentToPointPen(pen) - self._object.draw(adapter) - pen = None - - def appendGlyph(self, other): - pen = self.getPen() - other.draw(pen) - - # ----------------------------------------------------------------- - # - # glyphmath - - def round(self): - self._object.round() - - def _getMathDestination(self): - from robofab.objects.objectsRF import RGlyph as _RGlyph - return _RGlyph() - - def _mathCopy(self): - # copy self without contour, component and anchor data - glyph = self._getMathDestination() - glyph.name = self.name - glyph.unicodes = list(self.unicodes) - glyph.width = self.width - glyph.note = self.note - glyph.lib = dict(self.lib) - return glyph - - def __mul__(self, factor): - if __DEBUG__: - print "glyphmath mul", factor - return self.copy() *factor - - __rmul__ = __mul__ - - def __sub__(self, other): - if __DEBUG__: - print "glyphmath sub", other, other.__class__ - return self.copy() - other.copy() - - def __add__(self, other): - if __DEBUG__: - print "glyphmath add", other, other.__class__ - return self.copy() + other.copy() - - def getParent(self): - return self - - def copy(self, aParent=None): - """Make a copy of this glyph. - Note: the copy is not a duplicate fontlab glyph, but - a RF RGlyph with the same outlines. The new glyph is - not part of the fontlab font in any way. Use font.appendGlyph(glyph) - to get it in a FontLab glyph again.""" - from robofab.objects.objectsRF import RGlyph as _RGlyph - newGlyph = _RGlyph() - newGlyph.appendGlyph(self) - for attr in GLYPH_COPY_ATTRS: - value = getattr(self, attr) - setattr(newGlyph, attr, value) - parent = self.getParent() - if aParent is not None: - newGlyph.setParent(aParent) - elif self.getParent() is not None: - newGlyph.setParent(self.getParent()) - return newGlyph - - def _get_contours(self): - # find the contour data and wrap it - - """get the contours in this glyph""" - contours = [] - for n in range(len(self._object.foreground)): - item = self._object.foreground[n] - rc = RContour(item, n) - rc.setParent(self) - contours.append(rc) - #print contours - return contours - - contours = property(_get_contours, doc="allow for iteration through glyph.contours") - - # ----------------------------------------------------------------- - # - # transformations - - def move(self, (x, y)): - matrix = psMat.translate((x,y)) - self._object.transform(matrix) - - def scale(self, (x, y), center=(0,0)): - matrix = psMat.scale(x,y) - self._object.transform(matrix) - - def transform(self, matrix): - self._object.transform(matrix) - - def rotate(self, angle, offset=None): - matrix = psMat.rotate(angle) - self._object.transform(matrix) - - def skew(self, angle, offset=None): - matrix = psMat.skew(angle) - self._object.transform(matrix) - - # ----------------------------------------------------------------- - # - # components stuff - - def decompose(self): - self._object.unlinkRef() - - # ----------------------------------------------------------------- - # - # unicode stuff - - def autoUnicodes(self): - if __DEBUG__: - print "objectsFF.RGlyph.autoUnicodes() not implemented yet." - - # ----------------------------------------------------------------- - # - # contour stuff - - def removeOverlap(self): - self._object.removeOverlap() - - def correctDirection(self, trueType=False): - # no option for trueType, really. - self._object.correctDirection() - - def clear(self): - self._object.clear() - - def __getitem__(self, index): - return self.contours[index] - - -class RContour(BaseContour): - def __init__(self, contour, index=None): - self._object = contour - self.index = index - - def _get_points(self): - pts = [] - for pt in self._object: - wpt = RPoint(pt) - wpt.setParent(self) - pts.append(wpt) - return pts - - points = property(_get_points, doc="get contour points") - - def _get_box(self): - return self._object.boundingBox() - - box = property(_get_box, doc="get contour bounding box") - - def __len__(self): - return len(self._object) - - def __getitem__(self, index): - return self.points[index] - - - -class RPoint(BasePoint): - - def __init__(self, pointObject): - self._object = pointObject - - def _get_x(self): - return self._object.x - - def _set_x(self, value): - self._object.x = value - - x = property(_get_x, _set_x, doc="") - - def _get_y(self): - return self._object.y - - def _set_y(self, value): - self._object.y = value - - y = property(_get_y, _set_y, doc="") - - def _get_type(self): - if self._object.on_curve == 0: - return OFFCURVE - - # XXX not always curve - return CURVE - - def _set_type(self, value): - self._type = value - self._hasChanged() - - type = property(_get_type, _set_type, doc="") - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - contourIndex = "unknown_contour" - contourParent = self.getParent() - if contourParent is not None: - try: - contourIndex = `contourParent.index` - except AttributeError: pass - glyphParent = contourParent.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.fullName - except AttributeError: pass - return "<RPoint for %s.%s[%s]>"%(font, glyph, contourIndex) - - -class RInfo(BaseInfo): - def __init__(self, font): - BaseInfo.__init__(self) - self._object = font - - def _get_familyName(self): - return self._object.familyname - def _set_familyName(self, value): - self._object.familyname = value - familyName = property(_get_familyName, _set_familyName, doc="familyname") - - def _get_fondName(self): - return self._object.fondname - def _set_fondName(self, value): - self._object.fondname = value - fondName = property(_get_fondName, _set_fondName, doc="fondname") - - def _get_fontName(self): - return self._object.fontname - def _set_fontName(self, value): - self._object.fontname = value - fontName = property(_get_fontName, _set_fontName, doc="fontname") - - # styleName doesn't have a specific field, FF has a whole sfnt dict. - # implement fullName because a repr depends on it - def _get_fullName(self): - return self._object.fullname - def _set_fullName(self, value): - self._object.fullname = value - fullName = property(_get_fullName, _set_fullName, doc="fullname") - - def _get_unitsPerEm(self): - return self._object.em - def _set_unitsPerEm(self, value): - self._object.em = value - unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="unitsPerEm value") - - def _get_ascender(self): - return self._object.ascent - def _set_ascender(self, value): - self._object.ascent = value - ascender = property(_get_ascender, _set_ascender, doc="ascender value") - - def _get_descender(self): - return -self._object.descent - def _set_descender(self, value): - self._object.descent = -value - descender = property(_get_descender, _set_descender, doc="descender value") - - def _get_copyright(self): - return self._object.copyright - def _set_copyright(self, value): - self._object.copyright = value - copyright = property(_get_copyright, _set_copyright, doc="copyright") - - - -class RKerning(BaseKerning): - - """ Object representing the kerning. - This is going to need some thinking about. - """ - - -__all__ = [ 'RFont', 'RGlyph', 'RContour', 'RPoint', 'RInfo', - 'OpenFont', 'CurrentFont', 'NewFont', 'CurrentFont' - ] - - - -if __name__ == "__main__": - import os - from robofab.objects.objectsRF import RFont as _RFont - from sets import Set - - def dumpFontForgeAPI(testFontPath, printModule=False, - printFont=False, printGlyph=False, - printLayer=False, printContour=False, printPoint=False): - def printAPI(item, name): - print - print "-"*80 - print "API of", item - names = dir(item) - names.sort() - print - - if printAPI: - for n in names: - print - print "%s.%s"%(name, n) - try: - print getattr(item, n).__doc__ - except: - print "# error showing", n - # module - if printModule: - print "module file:", fontforge.__file__ - print "version:", fontforge.version() - print "module doc:", fontforge.__doc__ - print "has User Interface:", fontforge.hasUserInterface() - print "has Spiro:", fontforge.hasSpiro() - printAPI(fontforge, "fontforge") - - # font - fontObj = fontforge.open(testFontPath) - if printFont: - printAPI(fontObj, "font") - - # glyph - glyphObj = fontObj["A"] - if printGlyph: - printAPI(glyphObj, "glyph") - - # layer - layerObj = glyphObj.foreground - if printLayer: - printAPI(layerObj, "layer") - - # contour - contourObj = layerObj[0] - if printContour: - printAPI(contourObj, "contour") - - # point - if printPoint: - pointObj = contourObj[0] - printAPI(pointObj, "point") - - - # other objects - penObj = glyphObj.glyphPen() - printAPI(penObj, "glyphPen") - - # use your own paths here. - demoRoot = "/Users/erik/Develop/Mess/FontForge/objectsFF_work/" - UFOPath = os.path.join(demoRoot, "Test.ufo") - SFDPath = os.path.join(demoRoot, "Test_realSFD2.sfd") - - #dumpFontForgeAPI(UFOPath, printPoint=True) - - # should return None - CurrentFont() - - def compareAttr(obj1, obj2, attrName, isMethod=False): - if isMethod: - a = getattr(obj1, attrName)() - b = getattr(obj2, attrName)() - else: - a = getattr(obj1, attrName) - b = getattr(obj2, attrName) - if a == b and a is not None and b is not None: - print "\tattr %s ok"%attrName, a - return True - else: - print "\t?\t%s error:"%attrName, "%s:"%obj1.__class__, a, "%s:"%obj2.__class__, b - return False - - f = OpenFont(UFOPath) - #f = OpenFont(SFDPath) - ref = _RFont(UFOPath) - - if False: - print - print "test font attributes" - compareAttr(f, ref, "path") - - a = Set(f.keys()) - b = Set(ref.keys()) - print "glyphs in ref, not in f", b.difference(a) - print "glyphs in f, not in ref", a.difference(b) - - print "A" in f, "A" in ref - print f.has_key("A"), ref.has_key("A") - - print - print "test font info attributes" - compareAttr(f.info, ref.info, "ascender") - compareAttr(f.info, ref.info, "descender") - compareAttr(f.info, ref.info, "unitsPerEm") - compareAttr(f.info, ref.info, "copyright") - compareAttr(f.info, ref.info, "fullName") - compareAttr(f.info, ref.info, "familyName") - compareAttr(f.info, ref.info, "fondName") - compareAttr(f.info, ref.info, "fontName") - - # crash - f.save() - - otfOutputPath = os.path.join(demoRoot, "test_ouput.otf") - ufoOutputPath = os.path.join(demoRoot, "test_ouput.ufo") - # generate without path, should end up in the source folder - - f['A'].removeOverlap() - f.generate('otfcff') #, otfPath) - f.generate('pctype1') #, otfPath) - - # generate with path. Type is taken from the extension. - f.generate('otfcff', otfOutputPath) #, otfPath) - f.generate(None, ufoOutputPath) #, otfPath) - - featurePath = os.path.join(demoRoot, "testFeatureOutput.fea") - f.naked().generateFeatureFile(featurePath) - - if False: - # new glyphs - # unencoded - print "new glyph: unencoded", f.newGlyph("test_unencoded_glyph") - # encoded - print "new glyph: encoded", f.newGlyph("Adieresis") - # existing - print "new glyph: existing", f.newGlyph("K") - - print - print "test glyph attributes" - compareAttr(f['A'], ref['A'], "width") - compareAttr(f['A'], ref['A'], "unicode") - compareAttr(f['A'], ref['A'], "name") - compareAttr(f['A'], ref['A'], "box") - compareAttr(f['A'], ref['A'], "leftMargin") - compareAttr(f['A'], ref['A'], "rightMargin") - - if False: - print - print "comparing glyph digests" - failed = [] - for n in f.keys(): - g1 = f[n] - #g1.round() - g2 = ref[n] - #g2.round() - d1 = g1._getDigest() - d2 = g2._getDigest() - if d1 != d2: - failed.append(n) - #print "f: ", d1 - #print "ref: ", d2 - print "digest failed for %s"%". ".join(failed) - - g3 = f['A'] *.333 - print g3 - print g3._getDigest() - f.save() - - if False: - print - print "test contour attributes" - compareAttr(f['A'].contours[0], ref['A'].contours[0], "index") - - #for c in f['A'].contours: - # for p in c.points: - # print p, p.type - - # test with a glyph with just 1 contour so we can be sure we're comparing the same thing - compareAttr(f['C'].contours[0], ref['C'].contours[0], "box") - compareAttr(f['C'].contours[0], ref['C'].contours[0], "__len__", isMethod=True) - - ptf = f['C'].contours[0].points[0] - ptref = ref['C'].contours[0].points[0] - print "x, y", (ptf.x, ptf.y) == (ptref.x, ptref.y), (ptref.x, ptref.y) - print 'type', ptf.type, ptref.type - - print "point inside", f['A'].pointInside((50,10)), ref['A'].pointInside((50,10)) - - - print ref.kerning.keys() - - class GlyphLookupWrapper(dict): - """A wrapper for the lookups / subtable data in a FF glyph. - A lot of data is stored there, so it helps to have something to sort things out. - """ - def __init__(self, ffGlyph): - self._object = ffGlyph - self.refresh() - - def __repr__(self): - return "<GlyphLookupWrapper for %s, %d keys>"%(self._object.glyphname, len(self)) - - def refresh(self): - """Pick some of the values apart.""" - lookups = self._object.getPosSub('*') - for t in lookups: - print 'lookup', t - lookupName = t[0] - lookupType = t[1] - if not lookupName in self: - self[lookupName] = [] - self[lookupName].append(t[1:]) - - def getKerning(self): - """Get a regular kerning dict for this glyph""" - d = {} - left = self._object.glyphname - for name in self.keys(): - for item in self[name]: - print 'item', item - if item[0]!="Pair": - continue - #print 'next glyph:', item[1] - #print 'first glyph x Pos:', item[2] - #print 'first glyph y Pos:', item[3] - #print 'first glyph h Adv:', item[4] - #print 'first glyph v Adv:', item[5] - - #print 'second glyph x Pos:', item[6] - #print 'second glyph y Pos:', item[7] - #print 'second glyph h Adv:', item[8] - #print 'second glyph v Adv:', item[9] - right = item[1] - d[(left, right)] = item[4] - return d - - def setKerning(self, kernDict): - """Set the values of a regular kerning dict to the lookups in a FF glyph.""" - for left, right in kernDict.keys(): - if left != self._object.glyphname: - # should we filter the dict before it gets here? - # easier just to filter it here. - continue - - - - # lets try to find the kerning - A = f['A'].naked() - positionTypes = [ "Position", "Pair", "Substitution", "AltSubs", "MultSubs","Ligature"] - print A.getPosSub('*') - #for t in A.getPosSub('*'): - # print 'lookup subtable name:', t[0] - # print 'positioning type:', t[1] - # if t[1]in positionTypes: - # print 'next glyph:', t[2] - # print 'first glyph x Pos:', t[3] - # print 'first glyph y Pos:', t[4] - # print 'first glyph h Adv:', t[5] - # print 'first glyph v Adv:', t[6] - - # print 'second glyph x Pos:', t[7] - # print 'second glyph y Pos:', t[8] - # print 'second glyph h Adv:', t[9] - # print 'second glyph v Adv:', t[10] - - gw = GlyphLookupWrapper(A) - print gw - print gw.keys() - print gw.getKerning() - - name = "'kern' Horizontal Kerning in Latin lookup 0 subtable" - item = (name, 'quoteright', 0, 0, -200, 0, 0, 0, 0, 0) - - A.removePosSub(name) - apply(A.addPosSub, item) - - - print "after", A.getPosSub('*') - - fn = f.naked() - - name = "'kern' Horizontal Kerning in Latin lookup 0" - - - print "before removing stuff", fn.gpos_lookups - print "removing stuff", fn.removeLookup(name) - print "after removing stuff", fn.gpos_lookups - - flags = () - feature_script_lang = (("kern",(("latn",("dflt")),)),) - print fn.addLookup('kern', 'gpos_pair', flags, feature_script_lang) - print fn.addLookupSubtable('kern', 'myKerning') - - - print fn.gpos_lookups - A.addPosSub('myKerning', 'A', 0, 0, -400, 0, 0, 0, 0, 0) - A.addPosSub('myKerning', 'B', 0, 0, 200, 0, 0, 0, 0, 0) - A.addPosSub('myKerning', 'C', 0, 0, 10, 0, 0, 0, 0, 0) - A.addPosSub('myKerning', 'A', 0, 0, 77, 0, 0, 0, 0, 0) - - - gw = GlyphLookupWrapper(A) - print gw - print gw.keys() - print gw.getKerning() - diff --git a/misc/pylib/robofab/objects/objectsFL.py b/misc/pylib/robofab/objects/objectsFL.py deleted file mode 100755 index 3b78ddc14..000000000 --- a/misc/pylib/robofab/objects/objectsFL.py +++ /dev/null @@ -1,3112 +0,0 @@ -"""UFO implementation for the objects as used by FontLab 4.5 and higher""" - -from FL import * - -from robofab.tools.toolsFL import GlyphIndexTable, NewGlyph -from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\ - BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseFeatures, BaseGroups, BaseLib,\ - roundPt, addPt, _box,\ - MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ - relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut,\ - BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues -from robofab.misc import arrayTools -from robofab.pens.flPen import FLPointPen, FLPointContourPen -from robofab import RoboFabError -import os -from robofab.plistlib import Data, Dict, readPlist, writePlist -from StringIO import StringIO -from robofab import ufoLib -from warnings import warn -import datetime -from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab - - -try: - set -except NameError: - from sets import Set as set - -# local encoding -if os.name in ["mac", "posix"]: - LOCAL_ENCODING = "macroman" -else: - LOCAL_ENCODING = "latin-1" - -_IN_UFO_EXPORT = False - -# a list of attributes that are to be copied when copying a glyph. -# this is used by glyph.copy and font.insertGlyph -GLYPH_COPY_ATTRS = [ - "name", - "width", - "unicodes", - "note", - "lib", - ] - -# Generate Types -PC_TYPE1 = 'pctype1' -PC_MM = 'pcmm' -PC_TYPE1_ASCII = 'pctype1ascii' -PC_MM_ASCII = 'pcmmascii' -UNIX_ASCII = 'unixascii' -MAC_TYPE1 = 'mactype1' -OTF_CFF = 'otfcff' -OTF_TT = 'otfttf' -MAC_TT = 'macttf' -MAC_TT_DFONT = 'macttdfont' - -# doc for these functions taken from: http://dev.fontlab.net/flpydoc/ -# internal name (FontLab name, extension) -_flGenerateTypes ={ PC_TYPE1 : (ftTYPE1, 'pfb'), # PC Type 1 font (binary/PFB) - PC_MM : (ftTYPE1_MM, 'mm'), # PC MultipleMaster font (PFB) - PC_TYPE1_ASCII : (ftTYPE1ASCII, 'pfa'), # PC Type 1 font (ASCII/PFA) - PC_MM_ASCII : (ftTYPE1ASCII_MM, 'mm'), # PC MultipleMaster font (ASCII/PFA) - UNIX_ASCII : (ftTYPE1ASCII, 'pfa'), # UNIX ASCII font (ASCII/PFA) - OTF_TT : (ftTRUETYPE, 'ttf'), # PC TrueType/TT OpenType font (TTF) - OTF_CFF : (ftOPENTYPE, 'otf'), # PS OpenType (CFF-based) font (OTF) - MAC_TYPE1 : (ftMACTYPE1, 'suit'), # Mac Type 1 font (generates suitcase and LWFN file, optionally AFM) - MAC_TT : (ftMACTRUETYPE, 'ttf'), # Mac TrueType font (generates suitcase) - MAC_TT_DFONT : (ftMACTRUETYPE_DFONT, 'dfont'), # Mac TrueType font (generates suitcase with resources in data fork) - } - -## FL Hint stuff -# this should not be referenced outside of this module -# since we may be changing the way this works in the future. - - -""" - - FontLab implementation of psHints objects - - Most of the FL methods relating to ps hints return a list of 16 items. - These values are for the 16 corners of a 4 axis multiple master. - The odd thing is that even single masters get these 16 values. - RoboFab doesn't access the MM masters, so by default, the psHints - object only works with the first element. If you want to access the other - values in the list, give a value between 0 and 15 for impliedMasterIndex - when creating the object. - - From the FontLab docs: - http://dev.fontlab.net/flpydoc/ - - blue_fuzz - blue_scale - blue_shift - - blue_values_num(integer) - number of defined blue values - blue_values[integer[integer]] - two-dimentional array of BlueValues - master index is top-level index - - other_blues_num(integer) - number of defined OtherBlues values - other_blues[integer[integer]] - two-dimentional array of OtherBlues - master index is top-level index - - family_blues_num(integer) - number of FamilyBlues records - family_blues[integer[integer]] - two-dimentional array of FamilyBlues - master index is top-level index - - family_other_blues_num(integer) - number of FamilyOtherBlues records - family_other_blues[integer[integer]] - two-dimentional array of FamilyOtherBlues - master index is top-level index - - force_bold[integer] - list of Force Bold values, one for - each master - stem_snap_h_num(integer) - stem_snap_h - stem_snap_v_num(integer) - stem_snap_v - """ - -class PostScriptFontHintValues(BasePostScriptFontHintValues): - """ Wrapper for font-level PostScript hinting information for FontLab. - Blues values, stem values. - """ - def __init__(self, font=None, impliedMasterIndex=0): - self._object = font.naked() - self._masterIndex = impliedMasterIndex - - def copy(self): - from robofab.objects.objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues - return _PostScriptFontHintValues(data=self.asDict()) - - -class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues): - """ Wrapper for glyph-level PostScript hinting information for FontLab. - vStems, hStems. - """ - def __init__(self, glyph=None): - self._object = glyph.naked() - - def copy(self): - from robofab.objects.objectsRF import PostScriptGlyphHintValues as _PostScriptGlyphHintValues - return _PostScriptGlyphHintValues(data=self.asDict()) - - def _hintObjectsToList(self, item): - data = [] - done = [] - for hint in item: - p = (hint.position, hint.width) - if p in done: - continue - data.append(p) - done.append(p) - data.sort() - return data - - def _listToHintObjects(self, item): - hints = [] - done = [] - for pos, width in item: - if (pos, width) in done: - # we don't want to set duplicates - continue - hints.append(Hint(pos, width)) - done.append((pos,width)) - return hints - - def _getVHints(self): - return self._hintObjectsToList(self._object.vhints) - - def _setVHints(self, values): - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - self._object.RemoveHints(2) - if values is None: - # just clearing it then - return - values.sort() - for hint in self._listToHintObjects(values): - self._object.vhints.append(hint) - - def _getHHints(self): - return self._hintObjectsToList(self._object.hhints) - - def _setHHints(self, values): - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - self._object.RemoveHints(1) - if values is None: - # just clearing it then - return - values.sort() - for hint in self._listToHintObjects(values): - self._object.hhints.append(hint) - - vHints = property(_getVHints, _setVHints, doc="postscript hints: vertical hint zones") - hHints = property(_getHHints, _setHHints, doc="postscript hints: horizontal hint zones") - - - -def _glyphHintsToDict(glyph): - data = {} - ## - ## horizontal and vertical hints - ## - # glyph.hhints and glyph.vhints returns a list of Hint objects. - # Hint objects have position and width attributes. - data['hHints'] = [] - for index in xrange(len(glyph.hhints)): - hint = glyph.hhints[index] - data['hHints'].append((hint.position, hint.width)) - if not data['hHints']: - del data['hHints'] - data['vHints'] = [] - for index in xrange(len(glyph.vhints)): - hint = glyph.vhints[index] - data['vHints'].append((hint.position, hint.width)) - if not data['vHints']: - del data['vHints'] - ## - ## horizontal and vertical links - ## - # glyph.hlinks and glyph.vlinks returns a list of Link objects. - # Link objects have node1 and node2 attributes. - data['hLinks'] = [] - for index in xrange(len(glyph.hlinks)): - link = glyph.hlinks[index] - d = { 'node1' : link.node1, - 'node2' : link.node2, - } - data['hLinks'].append(d) - if not data['hLinks']: - del data['hLinks'] - data['vLinks'] = [] - for index in xrange(len(glyph.vlinks)): - link = glyph.vlinks[index] - d = { 'node1' : link.node1, - 'node2' : link.node2, - } - data['vLinks'].append(d) - if not data['vLinks']: - del data['vLinks'] - ## - ## replacement table - ## - # glyph.replace_table returns a list of Replace objects. - # Replace objects have type and index attributes. - data['replaceTable'] = [] - for index in xrange(len(glyph.replace_table)): - replace = glyph.replace_table[index] - d = { 'type' : replace.type, - 'index' : replace.index, - } - data['replaceTable'].append(d) - if not data['replaceTable']: - del data['replaceTable'] - # XXX - # need to support glyph.instructions and glyph.hdmx? - # they are not documented very well. - return data - -def _dictHintsToGlyph(glyph, aDict): - # clear existing hints first - # RemoveHints requires an "integer mode" argument - # but it is not documented. from some simple experiments - # i deduced that - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - glyph.RemoveHints(3) - ## - ## horizontal and vertical hints - ## - if aDict.has_key('hHints'): - for d in aDict['hHints']: - glyph.hhints.append(Hint(d[0], d[1])) - if aDict.has_key('vHints'): - for d in aDict['vHints']: - glyph.vhints.append(Hint(d[0], d[1])) - ## - ## horizontal and vertical links - ## - if aDict.has_key('hLinks'): - for d in aDict['hLinks']: - glyph.hlinks.append(Link(d['node1'], d['node2'])) - if aDict.has_key('vLinks'): - for d in aDict['vLinks']: - glyph.vlinks.append(Link(d['node1'], d['node2'])) - ## - ## replacement table - ## - if aDict.has_key('replaceTable'): - for d in aDict['replaceTable']: - glyph.replace_table.append(Replace(d['type'], d['index'])) - -# FL Node Types -flMOVE = 17 -flLINE = 1 -flCURVE = 35 -flOFFCURVE = 65 -flSHARP = 0 -# I have no idea what the difference between -# "smooth" and "fixed" is, but booth values -# are returned by FL -flSMOOTH = 4096 -flFIXED = 12288 - - -_flToRFSegmentDict = { flMOVE : MOVE, - flLINE : LINE, - flCURVE : CURVE, - flOFFCURVE : OFFCURVE - } - -_rfToFLSegmentDict = {} -for k, v in _flToRFSegmentDict.items(): - _rfToFLSegmentDict[v] = k - -def _flToRFSegmentType(segmentType): - return _flToRFSegmentDict[segmentType] - -def _rfToFLSegmentType(segmentType): - return _rfToFLSegmentDict[segmentType] - -def _scalePointFromCenter((pointX, pointY), (scaleX, scaleY), (centerX, centerY)): - ogCenter = (centerX, centerY) - scaledCenter = (centerX * scaleX, centerY * scaleY) - shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) - scaledPointX = (pointX * scaleX) - shiftVal[0] - scaledPointY = (pointY * scaleY) - shiftVal[1] - return (scaledPointX, scaledPointY) - -# Nostalgia code: -def CurrentFont(): - """Return a RoboFab font object for the currently selected font.""" - f = fl.font - if f is not None: - return RFont(fl.font) - return None - -def CurrentGlyph(): - """Return a RoboFab glyph object for the currently selected glyph.""" - currentPath = fl.font.file_name - if fl.glyph is None: - return None - glyphName = fl.glyph.name - currentFont = None - # is this font already loaded as an RFont? - for font in AllFonts(): - # ugh this won't work because AllFonts sees non RFonts as well.... - if font.path == currentPath: - currentFont = font - break - xx = currentFont[glyphName] - #print "objectsFL.CurrentGlyph parent for %d"% id(xx), xx.getParent() - return xx - -def OpenFont(path=None, note=None): - """Open a font from a path.""" - if path == None: - from robofab.interface.all.dialogs import GetFile - path = GetFile(note) - if path: - if path[-4:].lower() in ['.vfb', '.VFB', '.bak', '.BAK']: - f = Font(path) - fl.Add(f) - return RFont(f) - return None - -def NewFont(familyName=None, styleName=None): - """Make a new font""" - from FL import fl, Font - f = Font() - fl.Add(f) - rf = RFont(f) - if familyName is not None: - rf.info.familyName = familyName - if styleName is not None: - rf.info.styleName = styleName - return rf - -def AllFonts(): - """Return a list of all open fonts.""" - fontCount = len(fl) - all = [] - for index in xrange(fontCount): - naked = fl[index] - all.append(RFont(naked)) - return all - - from robofab.world import CurrentGlyph - -def getGlyphFromMask(g): - """Get a Fab glyph object for the data in the mask layer.""" - from robofab.objects.objectsFL import RGlyph as FL_RGlyph - from robofab.objects.objectsRF import RGlyph as RF_RGlyph - n = g.naked() - mask = n.mask - fg = FL_RGlyph(mask) - rf = RF_RGlyph() - pen = rf.getPointPen() - fg.drawPoints(pen) - rf.width = g.width # can we get to the mask glyph width without flipping the UI? - return rf - -def setMaskToGlyph(maskGlyph, targetGlyph, clear=True): - """Set the maskGlyph as a mask layer in targetGlyph. - maskGlyph is a FontLab or RoboFab RGlyph, orphaned or not. - targetGlyph is a FontLab RGLyph. - clear is a bool. False: keep the existing mask data, True: clear the existing mask data. - """ - from robofab.objects.objectsFL import RGlyph as FL_RGlyph - from FL import Glyph as FL_NakedGlyph - flGlyph = FL_NakedGlyph() # new, orphaned FL glyph - wrapped = FL_RGlyph(flGlyph) # rf wrapper for FL glyph - if not clear: - # copy the existing mask data first - existingMask = getGlyphFromMask(targetGlyph) - if existingMask is not None: - pen = FLPointContourPen(existingMask) - existingMask.drawPoints(pen) - pen = FLPointContourPen(wrapped) - maskGlyph.drawPoints(pen) # draw the data - targetGlyph.naked().mask = wrapped .naked() - targetGlyph.update() - -# the lib getter and setter are shared by RFont and RGlyph -def _get_lib(self): - data = self._object.customdata - if data: - f = StringIO(data) - try: - pList = readPlist(f) - except: # XXX ugh, plistlib can raise lots of things - # Anyway, customdata does not contain valid plist data, - # but we don't need to toss it! - pList = {"org.robofab.fontlab.customdata": Data(data)} - else: - pList = {} - # pass it along to the lib object - l = RLib(pList) - l.setParent(self) - return l - -def _set_lib(self, aDict): - l = RLib({}) - l.setParent(self) - l.update(aDict) - - -def _normalizeLineEndings(s): - return s.replace("\r\n", "\n").replace("\r", "\n") - - -class RFont(BaseFont): - """RoboFab UFO wrapper for FL Font object""" - - _title = "FLFont" - - def __init__(self, font=None): - BaseFont.__init__(self) - if font is None: - from FL import fl, Font - # rather than raise an error we could just start a new font. - font = Font() - fl.Add(font) - #raise RoboFabError, "RFont: there's nothing to wrap!?" - self._object = font - self._lib = {} - self._supportHints = True - self.psHints = PostScriptFontHintValues(self) - self.psHints.setParent(self) - - def keys(self): - keys = {} - for glyph in self._object.glyphs: - glyphName = glyph.name - if glyphName in keys: - n = 1 - while ("%s#%s" % (glyphName, n)) in keys: - n += 1 - newGlyphName = "%s#%s" % (glyphName, n) - print "RoboFab encountered a duplicate glyph name, renaming %r to %r" % (glyphName, newGlyphName) - glyphName = newGlyphName - glyph.name = glyphName - keys[glyphName] = None - return keys.keys() - - def has_key(self, glyphName): - glyph = self._object[glyphName] - if glyph is None: - return False - else: - return True - - __contains__ = has_key - - def __setitem__(self, glyphName, glyph): - self._object[glyphName] = glyph.naked() - - def __cmp__(self, other): - if not hasattr(other, '_object'): - return -1 - return self._compare(other) - # if self._object.file_name == other._object.file_name: - # # so, names match. - # # this will falsely identify two distinct "Untitled" - # # let's check some more - # return 0 - # else: - # return -1 - - -# def _get_psHints(self): -# h = PostScriptFontHintValues(self) -# h.setParent(self) -# return h -# -# psHints = property(_get_psHints, doc="font level postscript hint data") - - def _get_info(self): - return RInfo(self._object) - - info = property(_get_info, doc="font info object") - - def _get_features(self): - return RFeatures(self._object) - - features = property(_get_features, doc="features object") - - def _get_kerning(self): - kerning = {} - f = self._object - for g in f.glyphs: - for p in g.kerning: - try: - key = (g.name, f[p.key].name) - kerning[key] = p.value - except AttributeError: pass #catch for TT exception - rk = RKerning(kerning) - rk.setParent(self) - return rk - - kerning = property(_get_kerning, doc="a kerning object") - - def _set_groups(self, aDict): - g = RGroups({}) - g.setParent(self) - g.update(aDict) - - def _get_groups(self): - groups = {} - for i in self._object.classes: - # test to make sure that the class is properly formatted - if i.find(':') == -1: - continue - key = i.split(':')[0] - value = i.split(':')[1].lstrip().split(' ') - groups[key] = value - rg = RGroups(groups) - rg.setParent(self) - return rg - - groups = property(_get_groups, _set_groups, doc="a group object") - - lib = property(_get_lib, _set_lib, doc="font lib object") - - # - # attributes - # - - def _get_fontIndex(self): - # find the index of the font - # by comparing the file_name - # to all open fonts. if the - # font has no file_name, meaning - # it is a new, unsaved font, - # return the index of the first - # font with no file_name. - selfFileName = self._object.file_name - fontCount = len(fl) - for index in xrange(fontCount): - other = fl[index] - if other.file_name == selfFileName: - return index - - fontIndex = property(_get_fontIndex, doc="the fontindex for this font") - - def _get_path(self): - return self._object.file_name - - path = property(_get_path, doc="path to the font") - - def _get_fileName(self): - if self.path is None: - return None - return os.path.split(self.path) - - fileName = property(_get_fileName, doc="the font's file name") - - def _get_selection(self): - # return a list of glyph names for glyphs selected in the font window - l=[] - for i in range(len(self._object.glyphs)): - if fl.Selected(i) == 1: - l.append(self._object[i].name) - return l - - def _set_selection(self, list): - fl.Unselect() - for i in list: - fl.Select(i) - - selection = property(_get_selection, _set_selection, doc="the glyph selection in the font window") - - - def _makeGlyphlist(self): - # To allow iterations through Font.glyphs. Should become really big in fonts with lotsa letters. - gl = [] - for c in self: - gl.append(c) - return gl - - def _get_glyphs(self): - return self._makeGlyphlist() - - glyphs = property(_get_glyphs, doc="A list of all glyphs in the font, to allow iterations through Font.glyphs") - - def update(self): - """Don't forget to update the font when you are done.""" - fl.UpdateFont(self.fontIndex) - - def save(self, path=None): - """Save the font, path is required.""" - if not path: - if not self._object.file_name: - raise RoboFabError, "No destination path specified." - else: - path = self._object.file_name - fl.Save(self.fontIndex, path) - - def close(self, save=False): - """Close the font, saving is optional.""" - if save: - self.save() - else: - self._object.modified = 0 - fl.Close(self.fontIndex) - - def getGlyph(self, glyphName): - # XXX may need to become private - flGlyph = self._object[glyphName] - if flGlyph is not None: - glyph = RGlyph(flGlyph) - glyph.setParent(self) - return glyph - return self.newGlyph(glyphName) - - def newGlyph(self, glyphName, clear=True): - """Make a new glyph.""" - # the old implementation always updated the font. - # that proved to be very slow. so, the updating is - # now left up to the caller where it can be more - # efficiently managed. - g = NewGlyph(self._object, glyphName, clear, updateFont=False) - return RGlyph(g) - - def insertGlyph(self, glyph, name=None): - """Returns a new glyph that has been inserted into the font. - name = another glyphname if you want to insert as with that.""" - from robofab.objects.objectsRF import RFont as _RFont - from robofab.objects.objectsRF import RGlyph as _RGlyph - oldGlyph = glyph - if name is None: - name = oldGlyph.name - # clear the destination glyph if it exists. - if self.has_key(name): - self[name].clear() - # get the parent for the glyph - otherFont = oldGlyph.getParent() - # in some cases we will use the native - # FL method for appending a glyph. - useNative = True - testingNative = True - while testingNative: - # but, maybe it is an orphan glyph. - # in that case we should not use the native method. - if otherFont is None: - useNative = False - testingNative = False - # or maybe the glyph is coming from a NoneLab font - if otherFont is not None: - if isinstance(otherFont, _RFont): - useNative = False - testingNative = False - # but, it could be a copied FL glyph - # which is a NoneLab glyph that - # has a FontLab font as the parent - elif isinstance(otherFont, RFont): - useNative = False - testingNative = False - # or, maybe the glyph is being replaced, in which - # case the native method should not be used - # since FL will destroy any references to the glyph - if self.has_key(name): - useNative = False - testingNative = False - # if the glyph contains components the native - # method should not be used since FL does - # not reference glyphs in components by - # name, but by index (!!!). - if len(oldGlyph.components) != 0: - useNative = False - testingNative = False - testingNative = False - # finally, insert the glyph. - if useNative: - font = self.naked() - otherFont = oldGlyph.getParent().naked() - self.naked().glyphs.append(otherFont[name]) - newGlyph = self.getGlyph(name) - else: - newGlyph = self.newGlyph(name) - newGlyph.appendGlyph(oldGlyph) - for attr in GLYPH_COPY_ATTRS: - if attr == "name": - value = name - else: - value = getattr(oldGlyph, attr) - setattr(newGlyph, attr, value) - if self._supportHints: - # now we need to transfer the hints from - # the old glyph to the new glyph. we'll do this - # via the dict to hint functions. - hintDict = {} - # if the glyph is a NoneLab glyph, then we need - # to extract the ps hints from the lib - if isinstance(oldGlyph, _RGlyph): - hintDict = oldGlyph.lib.get(postScriptHintDataLibKey, {}) - # otherwise we need to extract the hint dict from the glyph - else: - hintDict = _glyphHintsToDict(oldGlyph.naked()) - # now apply the hint data - if hintDict: - _dictHintsToGlyph(newGlyph.naked(), hintDict) - # delete any remaining hint data from the glyph lib - if newGlyph.lib.has_key(postScriptHintDataLibKey): - del newGlyph.lib[postScriptHintDataLibKey] - return newGlyph - - def removeGlyph(self, glyphName): - """remove a glyph from the font""" - index = self._object.FindGlyph(glyphName) - if index != -1: - del self._object.glyphs[index] - - # - # opentype - # - - def getOTClasses(self): - """Return all OpenType classes as a dict. Relies on properly formatted classes.""" - classes = {} - c = self._object.ot_classes - if c is None: - return classes - c = c.replace('\r', '').replace('\n', '').split(';') - for i in c: - if i.find('=') != -1: - value = [] - i = i.replace(' = ', '=') - name = i.split('=')[0] - v = i.split('=')[1].replace('[', '').replace(']', '').split(' ') - #catch double spaces? - for j in v: - if len(j) > 0: - value.append(j) - classes[name] = value - return classes - - def setOTClasses(self, dict): - """Set all OpenType classes.""" - l = [] - for i in dict.keys(): - l.append(''.join([i, ' = [', ' '.join(dict[i]), '];'])) - self._object.ot_classes = '\n'.join(l) - - def getOTClass(self, name): - """Get a specific OpenType class.""" - classes = self.getOTClasses() - return classes[name] - - def setOTClass(self, name, list): - """Set a specific OpenType class.""" - classes = self.getOTClasses() - classes[name] = list - self.setOTClasses(classes) - - def getOTFeatures(self): - """Return all OpenType features as a dict keyed by name. - The value is a string of the text of the feature.""" - features = {} - for i in self._object.features: - v = [] - for j in i.value.replace('\r', '\n').split('\n'): - if j.find(i.tag) == -1: - v.append(j) - features[i.tag] = '\n'.join(v) - return features - - def setOTFeatures(self, dict): - """Set all OpenType features in the font.""" - features= {} - for i in dict.keys(): - f = [] - f.append('feature %s {'%i) - f.append(dict[i]) - f.append('} %s;'%i) - features[i] = '\n'.join(f) - self._object.features.clean() - for i in features.keys(): - self._object.features.append(Feature(i, features[i])) - - def getOTFeature(self, name): - """return a specific OpenType feature.""" - features = self.getOTFeatures() - return features[name] - - def setOTFeature(self, name, text): - """Set a specific OpenType feature.""" - features = self.getOTFeatures() - features[name] = text - self.setOTFeatures(features) - - # - # guides - # - - def getVGuides(self): - """Return a list of wrapped vertical guides in this RFont""" - vguides=[] - for i in range(len(self._object.vguides)): - g = RGuide(self._object.vguides[i], i) - g.setParent(self) - vguides.append(g) - return vguides - - def getHGuides(self): - """Return a list of wrapped horizontal guides in this RFont""" - hguides=[] - for i in range(len(self._object.hguides)): - g = RGuide(self._object.hguides[i], i) - g.setParent(self) - hguides.append(g) - return hguides - - def appendHGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - angle = int(round(angle)) - g=Guide(position, angle) - self._object.hguides.append(g) - - def appendVGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - angle = int(round(angle)) - g=Guide(position, angle) - self._object.vguides.append(g) - - def removeHGuide(self, guide): - """Remove a horizontal guide.""" - pos = (guide.position, guide.angle) - for g in self.getHGuides(): - if (g.position, g.angle) == pos: - del self._object.hguides[g.index] - break - - def removeVGuide(self, guide): - """Remove a vertical guide.""" - pos = (guide.position, guide.angle) - for g in self.getVGuides(): - if (g.position, g.angle) == pos: - del self._object.vguides[g.index] - break - - def clearHGuides(self): - """Clear all horizontal guides.""" - self._object.hguides.clean() - - def clearVGuides(self): - """Clear all vertical guides.""" - self._object.vguides.clean() - - - # - # generators - # - - def generate(self, outputType, path=None): - """ - generate the font. outputType is the type of font to ouput. - --Ouput Types: - 'pctype1' : PC Type 1 font (binary/PFB) - 'pcmm' : PC MultipleMaster font (PFB) - 'pctype1ascii' : PC Type 1 font (ASCII/PFA) - 'pcmmascii' : PC MultipleMaster font (ASCII/PFA) - 'unixascii' : UNIX ASCII font (ASCII/PFA) - 'mactype1' : Mac Type 1 font (generates suitcase and LWFN file) - 'otfcff' : PS OpenType (CFF-based) font (OTF) - 'otfttf' : PC TrueType/TT OpenType font (TTF) - 'macttf' : Mac TrueType font (generates suitcase) - 'macttdfont' : Mac TrueType font (generates suitcase with resources in data fork) - (doc adapted from http://dev.fontlab.net/flpydoc/) - - path can be a directory or a directory file name combo: - path="DirectoryA/DirectoryB" - path="DirectoryA/DirectoryB/MyFontName" - if no path is given, the file will be output in the same directory - as the vfb file. if no file name is given, the filename will be the - vfb file name with the appropriate suffix. - """ - outputType = outputType.lower() - if not _flGenerateTypes.has_key(outputType): - raise RoboFabError, "%s output type is not supported"%outputType - flOutputType, suffix = _flGenerateTypes[outputType] - if path is None: - filePath, fileName = os.path.split(self.path) - fileName = fileName.replace('.vfb', '') - else: - if os.path.isdir(path): - filePath = path - fileName = os.path.split(self.path)[1].replace('.vfb', '') - else: - filePath, fileName = os.path.split(path) - if '.' in fileName: - raise RoboFabError, "filename cannot contain periods.", fileName - fileName = '.'.join([fileName, suffix]) - finalPath = os.path.join(filePath, fileName) - if isinstance(finalPath, unicode): - finalPath = finalPath.encode("utf-8") - # generate is (oddly) an application level method - # rather than a font level method. because of this, - # the font must be the current font. so, make it so. - fl.ifont = self.fontIndex - fl.GenerateFont(flOutputType, finalPath) - - def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None, - doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None, formatVersion=2): - from robofab.interface.all.dialogs import ProgressBar, Message - # special glyph name to file name conversion - if glyphNameToFileNameFunc is None: - glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() - if glyphNameToFileNameFunc is None: - from robofab.tools.glyphNameSchemes import glyphNameToShortFileName - glyphNameToFileNameFunc = glyphNameToShortFileName - # get a valid path - if not path: - if self.path is None: - Message("Please save this font first before exporting to UFO...") - return - else: - path = ufoLib.makeUFOPath(self.path) - # get the glyphs to export - if glyphs is None: - glyphs = self.keys() - # if the file exists, check the format version. - # if the format version being written is different - # from the format version of the existing UFO - # and only some files are set to be written - # raise an error. - if os.path.exists(path): - if os.path.exists(os.path.join(path, "metainfo.plist")): - reader = ufoLib.UFOReader(path) - existingFormatVersion = reader.formatVersion - if formatVersion != existingFormatVersion: - if False in [doInfo, doKerning, doGroups, doLib, doFeatures, set(glyphs) == set(self.keys())]: - Message("When overwriting an existing UFO with a different format version all files must be written.") - return - # the lib must be written if format version is 1 - if not doLib and formatVersion == 1: - Message("The lib must be written when exporting format version 1.") - return - # set up the progress bar - nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) - bar = None - if doProgress: - bar = ProgressBar("Exporting UFO", nonGlyphCount + len(glyphs)) - # try writing - try: - writer = ufoLib.UFOWriter(path, formatVersion=formatVersion) - ## We make a shallow copy if lib, since we add some stuff for export - ## that doesn't need to be retained in memory. - fontLib = dict(self.lib) - # write the font info - if doInfo: - global _IN_UFO_EXPORT - _IN_UFO_EXPORT = True - writer.writeInfo(self.info) - _IN_UFO_EXPORT = False - if bar: - bar.tick() - # write the kerning - if doKerning: - writer.writeKerning(self.kerning.asDict()) - if bar: - bar.tick() - # write the groups - if doGroups: - writer.writeGroups(self.groups) - if bar: - bar.tick() - # write the features - if doFeatures: - if formatVersion == 2: - writer.writeFeatures(self.features.text) - else: - self._writeOpenTypeFeaturesToLib(fontLib) - if bar: - bar.tick() - # write the lib - if doLib: - ## Always export the postscript font hint values to the lib in format version 1 - if formatVersion == 1: - d = self.psHints.asDict() - fontLib[postScriptHintDataLibKey] = d - ## Export the glyph order to the lib - glyphOrder = [nakedGlyph.name for nakedGlyph in self.naked().glyphs] - fontLib["public.glyphOrder"] = glyphOrder - ## export the features - if doFeatures and formatVersion == 1: - self._writeOpenTypeFeaturesToLib(fontLib) - if bar: - bar.tick() - writer.writeLib(fontLib) - if bar: - bar.tick() - # write the glyphs - if glyphs: - glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) - count = nonGlyphCount - for nakedGlyph in self.naked().glyphs: - if nakedGlyph.name not in glyphs: - continue - glyph = RGlyph(nakedGlyph) - if doHints: - hintStuff = _glyphHintsToDict(glyph.naked()) - if hintStuff: - glyph.lib[postScriptHintDataLibKey] = hintStuff - glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) - # remove the hint dict from the lib - if doHints and glyph.lib.has_key(postScriptHintDataLibKey): - del glyph.lib[postScriptHintDataLibKey] - if bar and not count % 10: - bar.tick(count) - count = count + 1 - glyphSet.writeContents() - # only blindly stop if the user says to - except KeyboardInterrupt: - if bar: - bar.close() - bar = None - # kill the bar - if bar: - bar.close() - - def _writeOpenTypeFeaturesToLib(self, fontLib): - # this should only be used for UFO format version 1 - flFont = self.naked() - cls = flFont.ot_classes - if cls is not None: - fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings(cls).rstrip() + "\n" - if flFont.features: - features = {} - order = [] - for feature in flFont.features: - order.append(feature.tag) - features[feature.tag] = _normalizeLineEndings(feature.value).rstrip() + "\n" - fontLib["org.robofab.opentype.features"] = features - fontLib["org.robofab.opentype.featureorder"] = order - - def readUFO(self, path, doProgress=False, - doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None): - """read a .ufo into the font""" - from robofab.pens.flPen import FLPointPen - from robofab.interface.all.dialogs import ProgressBar - # start up the reader - reader = ufoLib.UFOReader(path) - glyphSet = reader.getGlyphSet() - # get a list of glyphs that should be imported - if glyphs is None: - glyphs = glyphSet.keys() - # set up the progress bar - nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) - bar = None - if doProgress: - bar = ProgressBar("Importing UFO", nonGlyphCount + len(glyphs)) - # start reading - try: - fontLib = reader.readLib() - # info - if doInfo: - reader.readInfo(self.info) - if bar: - bar.tick() - # glyphs - count = 1 - glyphOrder = self._getGlyphOrderFromLib(fontLib, glyphSet) - for glyphName in glyphOrder: - if glyphName not in glyphs: - continue - glyph = self.newGlyph(glyphName, clear=True) - pen = FLPointPen(glyph.naked()) - glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) - if doHints: - hintData = glyph.lib.get(postScriptHintDataLibKey) - if hintData: - _dictHintsToGlyph(glyph.naked(), hintData) - # now that the hints have been extracted from the glyph - # there is no reason to keep the location in the lib. - if glyph.lib.has_key(postScriptHintDataLibKey): - del glyph.lib[postScriptHintDataLibKey] - if bar and not count % 10: - bar.tick(count) - count = count + 1 - # features - if doFeatures: - if reader.formatVersion == 1: - self._readOpenTypeFeaturesFromLib(fontLib) - else: - featureText = reader.readFeatures() - self.features.text = featureText - if bar: - bar.tick() - else: - # remove features stored in the lib - self._readOpenTypeFeaturesFromLib(fontLib, setFeatures=False) - # kerning - if doKerning: - self.kerning.clear() - self.kerning.update(reader.readKerning()) - if bar: - bar.tick() - # groups - if doGroups: - self.groups.clear() - self.groups.update(reader.readGroups()) - if bar: - bar.tick() - # hints in format version 1 - if doHints and reader.formatVersion == 1: - self.psHints._loadFromLib(fontLib) - else: - # remove hint data stored in the lib - if fontLib.has_key(postScriptHintDataLibKey): - del fontLib[postScriptHintDataLibKey] - # lib - if doLib: - self.lib.clear() - self.lib.update(fontLib) - if bar: - bar.tick() - # update the font - self.update() - # only blindly stop if the user says to - except KeyboardInterrupt: - bar.close() - bar = None - # kill the bar - if bar: - bar.close() - - def _getGlyphOrderFromLib(self, fontLib, glyphSet): - key = "public.glyphOrder" - glyphOrder = fontLib.get(key) - if glyphOrder is None: - key = "org.robofab.glyphOrder" - glyphOrder = fontLib.get(key) - if glyphOrder is not None: - # no need to keep track if the glyph order in lib once the font is loaded. - del fontLib[key] - glyphNames = [] - done = {} - for glyphName in glyphOrder: - if glyphName in glyphSet: - glyphNames.append(glyphName) - done[glyphName] = 1 - allGlyphNames = glyphSet.keys() - allGlyphNames.sort() - for glyphName in allGlyphNames: - if glyphName not in done: - glyphNames.append(glyphName) - else: - glyphNames = glyphSet.keys() - glyphNames.sort() - return glyphNames - - def _readOpenTypeFeaturesFromLib(self, fontLib, setFeatures=True): - # setFeatures may be False. in this case, this method - # should only clear the data from the lib. - classes = fontLib.get("org.robofab.opentype.classes") - if classes is not None: - del fontLib["org.robofab.opentype.classes"] - if setFeatures: - self.naked().ot_classes = classes - features = fontLib.get("org.robofab.opentype.features") - if features is not None: - order = fontLib.get("org.robofab.opentype.featureorder") - if order is None: - # for UFOs saved without the feature order, do the same as before. - order = features.keys() - order.sort() - else: - del fontLib["org.robofab.opentype.featureorder"] - del fontLib["org.robofab.opentype.features"] - #features = features.items() - orderedFeatures = [] - for tag in order: - oneFeature = features.get(tag) - if oneFeature is not None: - orderedFeatures.append((tag, oneFeature)) - if setFeatures: - self.naked().features.clean() - for tag, src in orderedFeatures: - self.naked().features.append(Feature(tag, src)) - - - -class RGlyph(BaseGlyph): - """RoboFab wrapper for FL Glyph object""" - - _title = "FLGlyph" - - def __init__(self, flGlyph): - #BaseGlyph.__init__(self) - if flGlyph is None: - raise RoboFabError, "RGlyph: there's nothing to wrap!?" - self._object = flGlyph - self._lib = {} - self._contours = None - - def __getitem__(self, index): - return self.contours[index] - - def __delitem__(self, index): - self._object.DeleteContour(index) - self._invalidateContours() - - def __len__(self): - return len(self.contours) - - lib = property(_get_lib, _set_lib, doc="glyph lib object") - - def _invalidateContours(self): - self._contours = None - - def _buildContours(self): - self._contours = [] - for contourIndex in range(self._object.GetContoursNumber()): - c = RContour(contourIndex) - c.setParent(self) - c._buildSegments() - self._contours.append(c) - - # - # attribute handlers - # - - def _get_index(self): - return self._object.parent.FindGlyph(self.name) - - index = property(_get_index, doc="return the index of the glyph in the font") - - def _get_name(self): - return self._object.name - - def _set_name(self, value): - self._object.name = value - - name = property(_get_name, _set_name, doc="name") - - def _get_psName(self): - return self._object.name - - def _set_psName(self, value): - self._object.name = value - - psName = property(_get_psName, _set_psName, doc="name") - - def _get_baseName(self): - return self._object.name.split('.')[0] - - baseName = property(_get_baseName, doc="") - - def _get_unicode(self): - return self._object.unicode - - def _set_unicode(self, value): - self._object.unicode = value - - unicode = property(_get_unicode, _set_unicode, doc="unicode") - - def _get_unicodes(self): - return self._object.unicodes - - def _set_unicodes(self, value): - self._object.unicodes = value - - unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes") - - def _get_width(self): - return self._object.width - - def _set_width(self, value): - value = int(round(value)) - self._object.width = value - - width = property(_get_width, _set_width, doc="the width") - - def _get_box(self): - if not len(self.contours) and not len(self.components): - return (0, 0, 0, 0) - r = self._object.GetBoundingRect() - return (int(round(r.ll.x)), int(round(r.ll.y)), int(round(r.ur.x)), int(round(r.ur.y))) - - box = property(_get_box, doc="box of glyph as a tuple (xMin, yMin, xMax, yMax)") - - def _get_selected(self): - if fl.Selected(self._object.parent.FindGlyph(self._object.name)): - return 1 - else: - return 0 - - def _set_selected(self, value): - fl.Select(self._object.name, value) - - selected = property(_get_selected, _set_selected, doc="Select or deselect the glyph in the font window") - - def _get_mark(self): - return self._object.mark - - def _set_mark(self, value): - self._object.mark = value - - mark = property(_get_mark, _set_mark, doc="mark") - - def _get_note(self): - s = self._object.note - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_note(self, value): - if value is None: - value = '' - if type(value) == type(u""): - value = value.encode(LOCAL_ENCODING) - self._object.note = value - - note = property(_get_note, _set_note, doc="note") - - def _get_psHints(self): - # get an object representing the postscript zone information - return PostScriptGlyphHintValues(self) - - psHints = property(_get_psHints, doc="postscript hint data") - - # - # necessary evil - # - - def update(self): - """Don't forget to update the glyph when you are done.""" - fl.UpdateGlyph(self._object.parent.FindGlyph(self._object.name)) - - # - # methods to make RGlyph compatible with FL.Glyph - # ##are these still needed? - # - - def GetBoundingRect(self, masterIndex): - """FL compatibility""" - return self._object.GetBoundingRect(masterIndex) - - def GetMetrics(self, masterIndex): - """FL compatibility""" - return self._object.GetMetrics(masterIndex) - - def SetMetrics(self, value, masterIndex): - """FL compatibility""" - return self._object.SetMetrics(value, masterIndex) - - # - # object builders - # - - def _get_anchors(self): - return self.getAnchors() - - anchors = property(_get_anchors, doc="allow for iteration through glyph.anchors") - - def _get_components(self): - return self.getComponents() - - components = property(_get_components, doc="allow for iteration through glyph.components") - - def _get_contours(self): - if self._contours is None: - self._buildContours() - return self._contours - - contours = property(_get_contours, doc="allow for iteration through glyph.contours") - - def getAnchors(self): - """Return a list of wrapped anchors in this RGlyph.""" - anchors=[] - for i in range(len(self._object.anchors)): - a = RAnchor(self._object.anchors[i], i) - a.setParent(self) - anchors.append(a) - return anchors - - def getComponents(self): - """Return a list of wrapped components in this RGlyph.""" - components=[] - for i in range(len(self._object.components)): - c = RComponent(self._object.components[i], i) - c.setParent(self) - components.append(c) - return components - - def getVGuides(self): - """Return a list of wrapped vertical guides in this RGlyph""" - vguides=[] - for i in range(len(self._object.vguides)): - g = RGuide(self._object.vguides[i], i) - g.setParent(self) - vguides.append(g) - return vguides - - def getHGuides(self): - """Return a list of wrapped horizontal guides in this RGlyph""" - hguides=[] - for i in range(len(self._object.hguides)): - g = RGuide(self._object.hguides[i], i) - g.setParent(self) - hguides.append(g) - return hguides - - # - # tools - # - - def getPointPen(self): - self._invalidateContours() - # Now just don't muck with glyph.contours before you're done drawing... - return FLPointPen(self) - - def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)): - """Append a component to the glyph. x and y are optional offset values""" - offset = roundPt((offset[0], offset[1])) - p = FLPointPen(self.naked()) - xx, yy = scale - dx, dy = offset - p.addComponent(baseGlyph, (xx, 0, 0, yy, dx, dy)) - - def appendAnchor(self, name, position): - """Append an anchor to the glyph""" - value = roundPt((position[0], position[1])) - anchor = Anchor(name, value[0], value[1]) - self._object.anchors.append(anchor) - - def appendHGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - g = Guide(position, angle) - self._object.hguides.append(g) - - def appendVGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - g = Guide(position, angle) - self._object.vguides.append(g) - - def clearContours(self): - self._object.Clear() - self._invalidateContours() - - def clearComponents(self): - """Clear all components.""" - self._object.components.clean() - - def clearAnchors(self): - """Clear all anchors.""" - self._object.anchors.clean() - - def clearHGuides(self): - """Clear all horizontal guides.""" - self._object.hguides.clean() - - def clearVGuides(self): - """Clear all vertical guides.""" - self._object.vguides.clean() - - def removeComponent(self, component): - """Remove a specific component from the glyph. This only works - if the glyph does not have duplicate components in the same location.""" - pos = (component.baseGlyph, component.offset, component.scale) - a = self.getComponents() - found = [] - for i in a: - if (i.baseGlyph, i.offset, i.scale) == pos: - found.append(i) - if len(found) > 1: - raise RoboFabError, 'Found more than one possible component to remove' - elif len(found) == 1: - del self._object.components[found[0].index] - else: - raise RoboFabError, 'Component does not exist' - - def removeContour(self, index): - """remove a specific contour from the glyph""" - self._object.DeleteContour(index) - self._invalidateContours() - - def removeAnchor(self, anchor): - """Remove a specific anchor from the glyph. This only works - if the glyph does not have anchors with duplicate names - in exactly the same location with the same mark.""" - pos = (anchor.name, anchor.position, anchor.mark) - a = self.getAnchors() - found = [] - for i in a: - if (i.name, i.position, i.mark) == pos: - found.append(i) - if len(found) > 1: - raise RoboFabError, 'Found more than one possible anchor to remove' - elif len(found) == 1: - del self._object.anchors[found[0].index] - else: - raise RoboFabError, 'Anchor does not exist' - - def removeHGuide(self, guide): - """Remove a horizontal guide.""" - pos = (guide.position, guide.angle) - for g in self.getHGuides(): - if (g.position, g.angle) == pos: - del self._object.hguides[g.index] - break - - def removeVGuide(self, guide): - """Remove a vertical guide.""" - pos = (guide.position, guide.angle) - for g in self.getVGuides(): - if (g.position, g.angle) == pos: - del self._object.vguides[g.index] - break - - def center(self, padding=None): - """Equalise sidebearings, set to padding if wanted.""" - left = self.leftMargin - right = self.rightMargin - if padding: - e_left = e_right = padding - else: - e_left = (left + right)/2 - e_right = (left + right) - e_left - self.leftMargin= e_left - self.rightMargin= e_right - - def removeOverlap(self): - """Remove overlap""" - self._object.RemoveOverlap() - self._invalidateContours() - - def decompose(self): - """Decompose all components""" - self._object.Decompose() - self._invalidateContours() - - ##broken! - #def removeHints(self): - # """Remove the hints.""" - # self._object.RemoveHints() - - def autoHint(self): - """Automatically generate type 1 hints.""" - self._object.Autohint() - - def move(self, (x, y), contours=True, components=True, anchors=True): - """Move a glyph's items that are flagged as True""" - x, y = roundPt((x, y)) - self._object.Shift(Point(x, y)) - for c in self.getComponents(): - c.move((x, y)) - for a in self.getAnchors(): - a.move((x, y)) - - def clear(self, contours=True, components=True, anchors=True, guides=True, hints=True): - """Clear all items marked as true from the glyph""" - if contours: - self._object.Clear() - self._invalidateContours() - if components: - self._object.components.clean() - if anchors: - self._object.anchors.clean() - if guides: - self._object.hguides.clean() - self._object.vguides.clean() - if hints: - # RemoveHints requires an "integer mode" argument - # but it is not documented. from some simple experiments - # i deduced that - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - self._object.RemoveHints(3) - - # - # special treatment for GlyphMath support in FontLab - # - - def _getMathDestination(self): - from robofab.objects.objectsRF import RGlyph as _RGlyph - return _RGlyph() - - def copy(self, aParent=None): - """Make a copy of this glyph. - Note: the copy is not a duplicate fontlab glyph, but - a RF RGlyph with the same outlines. The new glyph is - not part of the fontlab font in any way. Use font.appendGlyph(glyph) - to get it in a FontLab glyph again.""" - from robofab.objects.objectsRF import RGlyph as _RGlyph - newGlyph = _RGlyph() - newGlyph.appendGlyph(self) - for attr in GLYPH_COPY_ATTRS: - value = getattr(self, attr) - setattr(newGlyph, attr, value) - # hints - doHints = False - parent = self.getParent() - if parent is not None and parent._supportHints: - hintStuff = _glyphHintsToDict(self.naked()) - if hintStuff: - newGlyph.lib[postScriptHintDataLibKey] = hintStuff - if aParent is not None: - newGlyph.setParent(aParent) - elif self.getParent() is not None: - newGlyph.setParent(self.getParent()) - return newGlyph - - def __mul__(self, factor): - return self.copy() *factor - - __rmul__ = __mul__ - - def __sub__(self, other): - return self.copy() - other.copy() - - def __add__(self, other): - return self.copy() + other.copy() - - - -class RContour(BaseContour): - - """RoboFab wrapper for non FL contour object""" - - _title = "FLContour" - - def __init__(self, index): - self._index = index - self._parentGlyph = None - self.segments = [] - - def __len__(self): - return len(self.points) - - def _buildSegments(self): - ####################### - # Notes about FL node contour structure - ####################### - # for TT curves, FL lists them as seperate nodes: - # [move, off, off, off, line, off, off] - # and, this list is sequential. after the last on curve, - # it is possible (and likely) that there will be more offCurves - # in our segment object, these should be associated with the - # first segment in the contour. - # - # for PS curves, it is a very different scenerio. - # curve nodes contain points: - # [on, off, off] - # and the list is not in sequential order. the first point in - # the list is the on curve and the subsequent points are the off - # curve points leading up to that on curve. - # - # it is very important to remember these structures when trying - # to understand the code below - - self.segments = [] - offList = [] - nodes = self._nakedParent.nodes - for index in range(self._nodeLength): - x = index + self._startNodeIndex - node = nodes[x] - # we do have a loose off curve. deal with it. - if node.type == flOFFCURVE: - offList.append(x) - # we are not dealing with a loose off curve - else: - s = RSegment(x) - s.setParent(self) - # but do we have a collection of loose off curves above? - # if so, apply them to the segment, and clear the list - if len(offList) != 0: - s._looseOffCurve = offList - offList = [] - self.segments.append(s) - # do we have some off curves now that the contour is complete? - if len(offList) != 0: - # ugh. apply them to the first segment - self.segments[0]._looseOffCurve = offList - - def setParent(self, parentGlyph): - self._parentGlyph = parentGlyph - - def getParent(self): - return self._parentGlyph - - def _get__nakedParent(self): - return self._parentGlyph.naked() - - _nakedParent = property(_get__nakedParent, doc="") - - def _get__startNodeIndex(self): - return self._nakedParent.GetContourBegin(self._index) - - _startNodeIndex = property(_get__startNodeIndex, doc="") - - def _get__nodeLength(self): - return self._nakedParent.GetContourLength(self._index) - - _nodeLength = property(_get__nodeLength, doc="") - - def _get__lastNodeIndex(self): - return self._startNodeIndex + self._nodeLength - 1 - - _lastNodeIndex = property(_get__lastNodeIndex, doc="") - - def _previousNodeIndex(self, index): - return (index - 1) % self._nodeLength - - def _nextNodeIndex(self, index): - return (index + 1) % self._nodeLength - - def _getNode(self, index): - return self._nodes[index] - - def _get__nodes(self): - nodes = [] - for node in self._nakedParent.nodes[self._startNodeIndex:self._startNodeIndex+self._nodeLength-1]: - nodes.append(node) - return nodes - - _nodes = property(_get__nodes, doc="") - - def _get_points(self): - points = [] - for segment in self.segments: - for point in segment.points: - points.append(point) - return points - - points = property(_get_points, doc="") - - def _get_bPoints(self): - bPoints = [] - for segment in self.segments: - bp = RBPoint(segment.index) - bp.setParent(self) - bPoints.append(bp) - return bPoints - - bPoints = property(_get_bPoints, doc="") - - def _get_index(self): - return self._index - - def _set_index(self, index): - if index != self._index: - self._nakedParent.ReorderContour(self._index, index) - # reorder and set the _index of the existing RContour objects - # this will be a better solution than reconstructing all the objects - # segment objects will still, sadly, have to be reconstructed - contourList = self.getParent().contours - contourList.insert(index, contourList.pop(self._index)) - for i in range(len(contourList)): - contourList[i]._index = i - contourList[i]._buildSegments() - - - index = property(_get_index, _set_index, doc="the index of the contour") - - def _get_selected(self): - selected = 0 - nodes = self._nodes - for node in nodes: - if node.selected == 1: - selected = 1 - break - return selected - - def _set_selected(self, value): - if value == 1: - self._nakedParent.SelectContour(self._index) - else: - for node in self._nodes: - node.selected = value - - selected = property(_get_selected, _set_selected, doc="selection of the contour: 1-selected or 0-unselected") - - def appendSegment(self, segmentType, points, smooth=False): - segment = self.insertSegment(index=self._nodeLength, segmentType=segmentType, points=points, smooth=smooth) - return segment - - def insertSegment(self, index, segmentType, points, smooth=False): - """insert a seggment into the contour""" - # do a qcurve insertion - if segmentType == QCURVE: - count = 0 - for point in points[:-1]: - newNode = Node(flOFFCURVE, Point(point[0], point[1])) - self._nakedParent.Insert(newNode, self._startNodeIndex + index + count) - count = count + 1 - newNode = Node(flLINE, Point(points[-1][0], points[-1][1])) - self._nakedParent.Insert(newNode, self._startNodeIndex + index +len(points) - 1) - # do a regular insertion - else: - onX, onY = points[-1] - newNode = Node(_rfToFLSegmentType(segmentType), Point(onX, onY)) - # fix the off curves in case the user is inserting a curve - # but is not specifying off curve points - if segmentType == CURVE and len(points) == 1: - pSeg = self._prevSegment(index) - pOn = pSeg.onCurve - newNode.points[1].Assign(Point(pOn.x, pOn.y)) - newNode.points[2].Assign(Point(onX, onY)) - for pointIndex in range(len(points[:-1])): - x, y = points[pointIndex] - newNode.points[1 + pointIndex].Assign(Point(x, y)) - if smooth: - newNode.alignment = flSMOOTH - self._nakedParent.Insert(newNode, self._startNodeIndex + index) - self._buildSegments() - return self.segments[index] - - def removeSegment(self, index): - """remove a segment from the contour""" - segment = self.segments[index] - # we have a qcurve. umph. - if segment.type == QCURVE: - indexList = [segment._nodeIndex] + segment._looseOffCurve - indexList.sort() - indexList.reverse() - parent = self._nakedParent - for nodeIndex in indexList: - parent.DeleteNode(nodeIndex) - # we have a more sane structure to follow - else: - # store some info for later - next = self._nextSegment(index) - nextOffA = None - nextOffB = None - nextType = next.type - if nextType != LINE and nextType != MOVE: - pA = next.offCurve[0] - nextOffA = (pA.x, pA.y) - pB = next.offCurve[-1] - nextOffB = (pB.x, pB.y) - nodeIndex = segment._nodeIndex - self._nakedParent.DeleteNode(nodeIndex) - self._buildSegments() - # now we must override FL guessing about offCurves - next = self._nextSegment(index - 1) - nextType = next.type - if nextType != LINE and nextType != MOVE: - pA = next.offCurve[0] - pB = next.offCurve[-1] - pA.x, pA.y = nextOffA - pB.x, pB.y = nextOffB - - def reverseContour(self): - """reverse contour direction""" - self._nakedParent.ReverseContour(self._index) - self._buildSegments() - - def setStartSegment(self, segmentIndex): - """set the first node on the contour""" - self._nakedParent.SetStartNode(self._startNodeIndex + segmentIndex) - self.getParent()._invalidateContours() - self.getParent()._buildContours() - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RContour is not implemented." - - - -class RSegment(BaseSegment): - - _title = "FLSegment" - - def __init__(self, flNodeIndex): - BaseSegment.__init__(self) - self._nodeIndex = flNodeIndex - self._looseOffCurve = [] #a list of indexes to loose off curve nodes - - def _get__node(self): - glyph = self.getParent()._nakedParent - return glyph.nodes[self._nodeIndex] - - _node = property(_get__node, doc="") - - def _get_qOffCurve(self): - nodes = self.getParent()._nakedParent.nodes - off = [] - for x in self._looseOffCurve: - off.append(nodes[x]) - return off - - _qOffCurve = property(_get_qOffCurve, doc="free floating off curve nodes in the segment") - - def _get_index(self): - contour = self.getParent() - return self._nodeIndex - contour._startNodeIndex - - index = property(_get_index, doc="") - - def _isQCurve(self): - # loose off curves only appear in q curves - if len(self._looseOffCurve) != 0: - return True - return False - - def _get_type(self): - if self._isQCurve(): - return QCURVE - return _flToRFSegmentType(self._node.type) - - def _set_type(self, segmentType): - if self._isQCurve(): - raise RoboFabError, 'qcurve point types cannot be changed' - oldNode = self._node - oldType = oldNode.type - oldPointType = _flToRFSegmentType(oldType) - if oldPointType == MOVE: - raise RoboFabError, '%s point types cannot be changed'%oldPointType - if segmentType == MOVE or segmentType == OFFCURVE: - raise RoboFabError, '%s point types cannot be assigned'%oldPointType - if oldPointType == segmentType: - return - oldNode.type = _rfToFLSegmentType(segmentType) - - type = property(_get_type, _set_type, doc="") - - def _get_smooth(self): - alignment = self._node.alignment - if alignment == flSMOOTH or alignment == flFIXED: - return True - return False - - def _set_smooth(self, value): - if value: - self._node.alignment = flSMOOTH - else: - self._node.alignment = flSHARP - - smooth = property(_get_smooth, _set_smooth, doc="") - - def _get_points(self): - points = [] - node = self._node - # gather the off curves - # - # are we dealing with a qCurve? ugh. - # gather the loose off curves - if self._isQCurve(): - off = self._qOffCurve - x = 0 - for n in off: - p = RPoint(0) - p.setParent(self) - p._qOffIndex = x - points.append(p) - x = x + 1 - # otherwise get the points associated with the node - else: - index = 1 - for point in node.points[1:]: - p = RPoint(index) - p.setParent(self) - points.append(p) - index = index + 1 - # the last point should always be the on curve - p = RPoint(0) - p.setParent(self) - points.append(p) - return points - - points = property(_get_points, doc="") - - def _get_selected(self): - return self._node.selected - - def _set_selected(self, value): - self._node.selected = value - - selected = property(_get_selected, _set_selected, doc="") - - def move(self, (x, y)): - x, y = roundPt((x, y)) - self._node.Shift(Point(x, y)) - if self._isQCurve(): - qOff = self._qOffCurve - for node in qOff: - node.Shift(Point(x, y)) - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RSegment is not implemented." - - - -class RPoint(BasePoint): - - _title = "FLPoint" - - def __init__(self, pointIndex): - #BasePoint.__init__(self) - self._pointIndex = pointIndex - self._qOffIndex = None - - def _get__parentGlyph(self): - return self._parentContour.getParent() - - _parentGlyph = property(_get__parentGlyph, doc="") - - def _get__parentContour(self): - return self._parentSegment.getParent() - - _parentContour = property(_get__parentContour, doc="") - - def _get__parentSegment(self): - return self.getParent() - - _parentSegment = property(_get__parentSegment, doc="") - - def _get__node(self): - if self._qOffIndex is not None: - return self.getParent()._qOffCurve[self._qOffIndex] - return self.getParent()._node - - _node = property(_get__node, doc="") - - def _get__point(self): - return self._node.points[self._pointIndex] - - _point = property(_get__point, doc="") - - def _get_x(self): - return self._point.x - - def _set_x(self, value): - value = int(round(value)) - self._point.x = value - - x = property(_get_x, _set_x, doc="") - - def _get_y(self): - return self._point.y - - def _set_y(self, value): - value = int(round(value)) - self._point.y = value - - y = property(_get_y, _set_y, doc="") - - def _get_type(self): - if self._pointIndex == 0: - # FL store quad contour data as a list of off curves and lines - # (see note in RContour._buildSegments). So, we need to do - # a bit of trickery to return a decent point type. - # if the straight FL node type is off curve, it is a loose - # quad off curve. return that. - tp = _flToRFSegmentType(self._node.type) - if tp == OFFCURVE: - return OFFCURVE - # otherwise we are dealing with an on curve. in this case, - # we attempt to get the parent segment type and return it. - segment = self.getParent() - if segment is not None: - return segment.type - # we must not have a segment, fall back to straight conversion - return tp - return OFFCURVE - - type = property(_get_type, doc="") - - def _set_selected(self, value): - if self._pointIndex == 0: - self._node.selected = value - - def _get_selected(self): - if self._pointIndex == 0: - return self._node.selected - return False - - selected = property(_get_selected, _set_selected, doc="") - - def move(self, (x, y)): - x, y = roundPt((x, y)) - self._point.Shift(Point(x, y)) - - def scale(self, (x, y), center=(0, 0)): - centerX, centerY = roundPt(center) - point = self._point - point.x, point.y = _scalePointFromCenter((point.x, point.y), (x, y), (centerX, centerY)) - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RPoint is not implemented." - - -class RBPoint(BaseBPoint): - - _title = "FLBPoint" - - def __init__(self, segmentIndex): - #BaseBPoint.__init__(self) - self._segmentIndex = segmentIndex - - def _get__parentSegment(self): - return self.getParent().segments[self._segmentIndex] - - _parentSegment = property(_get__parentSegment, doc="") - - def _get_index(self): - return self._segmentIndex - - index = property(_get_index, doc="") - - def _get_selected(self): - return self._parentSegment.selected - - def _set_selected(self, value): - self._parentSegment.selected = value - - selected = property(_get_selected, _set_selected, doc="") - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RBPoint is not implemented." - - -class RComponent(BaseComponent): - - """RoboFab wrapper for FL Component object""" - - _title = "FLComponent" - - def __init__(self, flComponent, index): - BaseComponent.__init__(self) - self._object = flComponent - self._index=index - - def _get_index(self): - return self._index - - index = property(_get_index, doc="index of component") - - def _get_baseGlyph(self): - return self._object.parent.parent[self._object.index].name - - baseGlyph = property(_get_baseGlyph, doc="") - - def _get_offset(self): - return (int(self._object.delta.x), int(self._object.delta.y)) - - def _set_offset(self, value): - value = roundPt((value[0], value[1])) - self._object.delta=Point(value[0], value[1]) - - offset = property(_get_offset, _set_offset, doc="the offset of the component") - - def _get_scale(self): - return (self._object.scale.x, self._object.scale.y) - - def _set_scale(self, (x, y)): - self._object.scale=Point(x, y) - - scale = property(_get_scale, _set_scale, doc="the scale of the component") - - def move(self, (x, y)): - """Move the component""" - x, y = roundPt((x, y)) - self._object.delta=Point(self._object.delta.x+x, self._object.delta.y+y) - - def decompose(self): - """Decompose the component""" - self._object.Paste() - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RComponent is not implemented." - - - -class RAnchor(BaseAnchor): - """RoboFab wrapper for FL Anchor object""" - - _title = "FLAnchor" - - def __init__(self, flAnchor, index): - BaseAnchor.__init__(self) - self._object = flAnchor - self._index = index - - def _get_y(self): - return self._object.y - - def _set_y(self, value): - self._object.y = int(round(value)) - - y = property(_get_y, _set_y, doc="y") - - def _get_x(self): - return self._object.x - - def _set_x(self, value): - self._object.x = int(round(value)) - - x = property(_get_x, _set_x, doc="x") - - def _get_name(self): - return self._object.name - - def _set_name(self, value): - self._object.name = value - - name = property(_get_name, _set_name, doc="name") - - def _get_mark(self): - return self._object.mark - - def _set_mark(self, value): - self._object.mark = value - - mark = property(_get_mark, _set_mark, doc="mark") - - def _get_index(self): - return self._index - - index = property(_get_index, doc="index of the anchor") - - def _get_position(self): - return (self._object.x, self._object.y) - - def _set_position(self, value): - value = roundPt((value[0], value[1])) - self._object.x=value[0] - self._object.y=value[1] - - position = property(_get_position, _set_position, doc="position of the anchor") - - - -class RGuide(BaseGuide): - - """RoboFab wrapper for FL Guide object""" - - _title = "FLGuide" - - def __init__(self, flGuide, index): - BaseGuide.__init__(self) - self._object = flGuide - self._index = index - - def __repr__(self): - # this is a doozy! - parent = "unknown_parent" - parentObject = self.getParent() - if parentObject is not None: - # do we have a font? - try: - parent = parentObject.info.postscriptFullName - except AttributeError: - # or do we have a glyph? - try: - parent = parentObject.name - # we must be an orphan - except AttributeError: pass - return "<Robofab guide wrapper for %s>"%parent - - def _get_position(self): - return self._object.position - - def _set_position(self, value): - self._object.position = value - - position = property(_get_position, _set_position, doc="position") - - def _get_angle(self): - return self._object.angle - - def _set_angle(self, value): - self._object.angle = value - - angle = property(_get_angle, _set_angle, doc="angle") - - def _get_index(self): - return self._index - - index = property(_get_index, doc="index of the guide") - - -class RGroups(BaseGroups): - - """RoboFab wrapper for FL group data""" - - _title = "FLGroups" - - def __init__(self, aDict): - self.update(aDict) - - def __setitem__(self, key, value): - # override baseclass so that data is stored in FL classes - if not isinstance(key, str): - raise RoboFabError, 'key must be a string' - if not isinstance(value, list): - raise RoboFabError, 'group must be a list' - super(RGroups, self).__setitem__(key, value) - self._setFLGroups() - - def __delitem__(self, key): - # override baseclass so that data is stored in FL classes - super(RGroups, self).__delitem__(key) - self._setFLGroups() - - def _setFLGroups(self): - # set the group data into the font. - if self.getParent() is not None: - groups = [] - for i in self.keys(): - value = ' '.join(self[i]) - groups.append(': '.join([i, value])) - groups.sort() - self.getParent().naked().classes = groups - - def update(self, aDict): - # override baseclass so that data is stored in FL classes - super(RGroups, self).update(aDict) - self._setFLGroups() - - def clear(self): - # override baseclass so that data is stored in FL classes - super(RGroups, self).clear() - self._setFLGroups() - - def pop(self, key): - # override baseclass so that data is stored in FL classes - i = super(RGroups, self).pop(key) - self._setFLGroups() - return i - - def popitem(self): - # override baseclass so that data is stored in FL classes - i = super(RGroups, self).popitem() - self._setFLGroups() - return i - - def setdefault(self, key, value=None): - # override baseclass so that data is stored in FL classes - i = super(RGroups, self).setdefault(key, value) - self._setFLGroups() - return i - - -class RKerning(BaseKerning): - - """RoboFab wrapper for FL Kerning data""" - - _title = "FLKerning" - - def __setitem__(self, pair, value): - if not isinstance(pair, tuple): - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if len(pair) != 2: - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if value == 0: - if self._kerning.get(pair) is not None: - #see note about setting kerning values to 0 below - self._setFLKerning(pair, 0) - del self._kerning[pair] - else: - #self._kerning[pair] = value - self._setFLKerning(pair, value) - - def _setFLKerning(self, pair, value): - # write a pair back into the font - # - # this is fairly speedy, but setting a pair to 0 is roughly - # 2-3 times slower than setting a real value. this is because - # of all the hoops that must be jumped through to keep FL - # from storing kerning pairs with a value of 0. - parentFont = self.getParent().naked() - left = parentFont[pair[0]] - right = parentFont.FindGlyph(pair[1]) - # the left glyph doesn not exist - if left is None: - return - # the right glyph doesn not exist - if right == -1: - return - self._kerning[pair] = value - leftName = pair[0] - value = int(round(value)) - # pairs set to 0 need to be handled carefully. FL will allow - # for pairs to have a value of 0 (!?), so we must catch them - # when they pop up and make sure that the pair is actually - # removed from the font. - if value == 0: - foundPair = False - # if the value is 0, we don't need to construct a pair - # we just need to make sure that the pair is not in the list - pairs = [] - # so, go through all the pairs and add them to a new list - for flPair in left.kerning: - # we have found the pair. flag it. - if flPair.key == right: - foundPair = True - # not the pair. add it to the list. - else: - pairs.append((flPair.key, flPair.value)) - # if we found it, write it back to the glyph. - if foundPair: - left.kerning = [] - for p in pairs: - new = KerningPair(p[0], p[1]) - left.kerning.append(new) - else: - # non-zero pairs are a bit easier to handle - # we just need to look to see if the pair exists - # if so, change the value and stop the loop. - # if not, add a new pair to the glyph - self._kerning[pair] = value - foundPair = False - for flPair in left.kerning: - if flPair.key == right: - flPair.value = value - foundPair = True - break - if not foundPair: - p = KerningPair(right, value) - left.kerning.append(p) - - def update(self, kerningDict): - """replace kerning data with the data in the given kerningDict""" - # override base class here for speed - parentFont = self.getParent().naked() - # add existing data to the new kerning dict is not being replaced - for pair in self.keys(): - if not kerningDict.has_key(pair): - kerningDict[pair] = self._kerning[pair] - # now clear the existing kerning to make sure that - # all the kerning in residing in the glyphs is gone - self.clear() - self._kerning = kerningDict - kDict = {} - # nest the pairs into a dict keyed by the left glyph - # {'A':{'A':-10, 'B':20, ...}, 'B':{...}, ...} - for left, right in kerningDict.keys(): - value = kerningDict[left, right] - if not left in kDict: - kDict[left] = {} - kDict[left][right] = value - for left in kDict.keys(): - leftGlyph = parentFont[left] - if leftGlyph is not None: - for right in kDict[left].keys(): - value = kDict[left][right] - if value != 0: - rightIndex = parentFont.FindGlyph(right) - if rightIndex != -1: - p = KerningPair(rightIndex, value) - leftGlyph.kerning.append(p) - - def clear(self): - """clear all kerning""" - # override base class here for speed - self._kerning = {} - for glyph in self.getParent().naked().glyphs: - glyph.kerning = [] - - def __add__(self, other): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - from sets import Set - from robofab.objects.objectsRF import RKerning as _RKerning - new = _RKerning() - k = Set(self.keys()) | Set(other.keys()) - for key in k: - new[key] = self.get(key, 0) + other.get(key, 0) - return new - - def __sub__(self, other): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - from sets import Set - from robofab.objects.objectsRF import RKerning as _RKerning - new = _RKerning() - k = Set(self.keys()) | Set(other.keys()) - for key in k: - new[key] = self.get(key, 0) - other.get(key, 0) - return new - - def __mul__(self, factor): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - from robofab.objects.objectsRF import RKerning as _RKerning - new = _RKerning() - for name, value in self.items(): - new[name] = value * factor - return new - - __rmul__ = __mul__ - - def __div__(self, factor): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - if factor == 0: - raise ZeroDivisionError - return self.__mul__(1.0/factor) - - -class RLib(BaseLib): - - """RoboFab wrapper for FL lib""" - - # XXX: As of FL 4.6 the customdata field in glyph objects is busted. - # storing anything there causes the glyph to become uneditable. - # however, the customdata field in font objects is stable. - - def __init__(self, aDict): - self.update(aDict) - - def __setitem__(self, key, value): - # override baseclass so that data is stored in customdata field - super(RLib, self).__setitem__(key, value) - self._stashLib() - - def __delitem__(self, key): - # override baseclass so that data is stored in customdata field - super(RLib, self).__delitem__(key) - self._stashLib() - - def _stashLib(self): - # write the plist into the customdata field of the FL object - if self.getParent() is None: - return - if not self: - data = None - elif len(self) == 1 and "org.robofab.fontlab.customdata" in self: - data = self["org.robofab.fontlab.customdata"].data - else: - f = StringIO() - writePlist(self, f) - data = f.getvalue() - f.close() - parent = self.getParent() - parent.naked().customdata = data - - def update(self, aDict): - # override baseclass so that data is stored in customdata field - super(RLib, self).update(aDict) - self._stashLib() - - def clear(self): - # override baseclass so that data is stored in customdata field - super(RLib, self).clear() - self._stashLib() - - def pop(self, key): - # override baseclass so that data is stored in customdata field - i = super(RLib, self).pop(key) - self._stashLib() - return i - - def popitem(self): - # override baseclass so that data is stored in customdata field - i = super(RLib, self).popitem() - self._stashLib() - return i - - def setdefault(self, key, value=None): - # override baseclass so that data is stored in customdata field - i = super(RLib, self).setdefault(key, value) - self._stashLib() - return i - - -def _infoMapDict(**kwargs): - default = dict( - nakedAttribute=None, - type=None, - requiresSetNum=False, - masterSpecific=False, - libLocation=None, - specialGetSet=False - ) - default.update(kwargs) - return default - -def _flipDict(d): - f = {} - for k, v in d.items(): - f[v] = k - return f - -_styleMapStyleName_fromFL = { - 64 : "regular", - 1 : "italic", - 32 : "bold", - 33 : "bold italic" -} -_styleMapStyleName_toFL = _flipDict(_styleMapStyleName_fromFL) - -_postscriptWindowsCharacterSet_fromFL = { - 0 : 1, - 1 : 2, - 2 : 3, - 77 : 4, - 128 : 5, - 129 : 6, - 130 : 7, - 134 : 8, - 136 : 9, - 161 : 10, - 162 : 11, - 163 : 12, - 177 : 13, - 178 : 14, - 186 : 15, - 200 : 16, - 204 : 17, - 222 : 18, - 238 : 19, - 255 : 20, -} -_postscriptWindowsCharacterSet_toFL = _flipDict(_postscriptWindowsCharacterSet_fromFL) - -_openTypeOS2Type_toFL = { - 1 : 0x0002, - 2 : 0x0004, - 3 : 0x0008, - 8 : 0x0100, - 9 : 0x0200, -} -_openTypeOS2Type_fromFL = _flipDict(_openTypeOS2Type_toFL) - -_openTypeOS2WidthClass_fromFL = { - "Ultra-condensed" : 1, - "Extra-condensed" : 2, - "Condensed" : 3, - "Semi-condensed" : 4, - "Medium (normal)" : 5, - "Semi-expanded" : 6, - "Expanded" : 7, - "Extra-expanded" : 8, - "Ultra-expanded" : 9, -} -_openTypeOS2WidthClass_toFL = _flipDict(_openTypeOS2WidthClass_fromFL) - -_postscriptHintAttributes = set(( - "postscriptBlueValues", - "postscriptOtherBlues", - "postscriptFamilyBlues", - "postscriptFamilyOtherBlues", - "postscriptStemSnapH", - "postscriptStemSnapV", -)) - - -class RInfo(BaseInfo): - - """RoboFab wrapper for FL Font Info""" - - _title = "FLInfo" - - _ufoToFLAttrMapping = { - "familyName" : _infoMapDict(valueType=str, nakedAttribute="family_name"), - "styleName" : _infoMapDict(valueType=str, nakedAttribute="style_name"), - "styleMapFamilyName" : _infoMapDict(valueType=str, nakedAttribute="menu_name"), - "styleMapStyleName" : _infoMapDict(valueType=str, nakedAttribute="font_style", specialGetSet=True), - "versionMajor" : _infoMapDict(valueType=int, nakedAttribute="version_major"), - "versionMinor" : _infoMapDict(valueType=int, nakedAttribute="version_minor"), - "year" : _infoMapDict(valueType=int, nakedAttribute="year"), - "copyright" : _infoMapDict(valueType=str, nakedAttribute="copyright"), - "trademark" : _infoMapDict(valueType=str, nakedAttribute="trademark"), - "unitsPerEm" : _infoMapDict(valueType=int, nakedAttribute="upm"), - "descender" : _infoMapDict(valueType=int, nakedAttribute="descender", masterSpecific=True), - "xHeight" : _infoMapDict(valueType=int, nakedAttribute="x_height", masterSpecific=True), - "capHeight" : _infoMapDict(valueType=int, nakedAttribute="cap_height", masterSpecific=True), - "ascender" : _infoMapDict(valueType=int, nakedAttribute="ascender", masterSpecific=True), - "italicAngle" : _infoMapDict(valueType=float, nakedAttribute="italic_angle"), - "note" : _infoMapDict(valueType=str, nakedAttribute="note"), - "openTypeHeadCreated" : _infoMapDict(valueType=str, nakedAttribute=None, specialGetSet=True), # i can't figure out the ttinfo.head_creation values - "openTypeHeadLowestRecPPEM" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.head_lowest_rec_ppem"), - "openTypeHeadFlags" : _infoMapDict(valueType="intList", nakedAttribute=None), # There is an attribute (ttinfo.head_flags), but no user interface. - "openTypeHheaAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_ascender"), - "openTypeHheaDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_descender"), - "openTypeHheaLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_line_gap"), - "openTypeHheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeHheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeHheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeNameDesigner" : _infoMapDict(valueType=str, nakedAttribute="designer"), - "openTypeNameDesignerURL" : _infoMapDict(valueType=str, nakedAttribute="designer_url"), - "openTypeNameManufacturer" : _infoMapDict(valueType=str, nakedAttribute="source"), - "openTypeNameManufacturerURL" : _infoMapDict(valueType=str, nakedAttribute="vendor_url"), - "openTypeNameLicense" : _infoMapDict(valueType=str, nakedAttribute="license"), - "openTypeNameLicenseURL" : _infoMapDict(valueType=str, nakedAttribute="license_url"), - "openTypeNameVersion" : _infoMapDict(valueType=str, nakedAttribute="tt_version"), - "openTypeNameUniqueID" : _infoMapDict(valueType=str, nakedAttribute="tt_u_id"), - "openTypeNameDescription" : _infoMapDict(valueType=str, nakedAttribute="notice"), - "openTypeNamePreferredFamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_family_name"), - "openTypeNamePreferredSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_style_name"), - "openTypeNameCompatibleFullName" : _infoMapDict(valueType=str, nakedAttribute="mac_compatible"), - "openTypeNameSampleText" : _infoMapDict(valueType=str, nakedAttribute=None), - "openTypeNameWWSFamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), - "openTypeNameWWSSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), - "openTypeOS2WidthClass" : _infoMapDict(valueType=int, nakedAttribute="width"), - "openTypeOS2WeightClass" : _infoMapDict(valueType=int, nakedAttribute="weight_code", specialGetSet=True), - "openTypeOS2Selection" : _infoMapDict(valueType="intList", nakedAttribute=None), # ttinfo.os2_fs_selection only returns 0 - "openTypeOS2VendorID" : _infoMapDict(valueType=str, nakedAttribute="vendor"), - "openTypeOS2Panose" : _infoMapDict(valueType="intList", nakedAttribute="panose", specialGetSet=True), - "openTypeOS2FamilyClass" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_s_family_class", specialGetSet=True), - "openTypeOS2UnicodeRanges" : _infoMapDict(valueType="intList", nakedAttribute="unicoderanges"), - "openTypeOS2CodePageRanges" : _infoMapDict(valueType="intList", nakedAttribute="codepages"), - "openTypeOS2TypoAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_ascender"), - "openTypeOS2TypoDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_descender"), - "openTypeOS2TypoLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_line_gap"), - "openTypeOS2WinAscent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_ascent"), - "openTypeOS2WinDescent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_descent", specialGetSet=True), - "openTypeOS2Type" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_fs_type", specialGetSet=True), - "openTypeOS2SubscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_size"), - "openTypeOS2SubscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_size"), - "openTypeOS2SubscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_offset"), - "openTypeOS2SubscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_offset"), - "openTypeOS2SuperscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_size"), - "openTypeOS2SuperscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_size"), - "openTypeOS2SuperscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_offset"), - "openTypeOS2SuperscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_offset"), - "openTypeOS2StrikeoutSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_size"), - "openTypeOS2StrikeoutPosition" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_position"), - "openTypeVheaVertTypoAscender" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaVertTypoDescender" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaVertTypoLineGap" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), - "postscriptFontName" : _infoMapDict(valueType=str, nakedAttribute="font_name"), - "postscriptFullName" : _infoMapDict(valueType=str, nakedAttribute="full_name"), - "postscriptSlantAngle" : _infoMapDict(valueType=float, nakedAttribute="slant_angle"), - "postscriptUniqueID" : _infoMapDict(valueType=int, nakedAttribute="unique_id"), - "postscriptUnderlineThickness" : _infoMapDict(valueType=int, nakedAttribute="underline_thickness"), - "postscriptUnderlinePosition" : _infoMapDict(valueType=int, nakedAttribute="underline_position"), - "postscriptIsFixedPitch" : _infoMapDict(valueType="boolint", nakedAttribute="is_fixed_pitch"), - "postscriptBlueValues" : _infoMapDict(valueType="intList", nakedAttribute="blue_values", masterSpecific=True, requiresSetNum=True), - "postscriptOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="other_blues", masterSpecific=True, requiresSetNum=True), - "postscriptFamilyBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_blues", masterSpecific=True, requiresSetNum=True), - "postscriptFamilyOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_other_blues", masterSpecific=True, requiresSetNum=True), - "postscriptStemSnapH" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_h", masterSpecific=True, requiresSetNum=True), - "postscriptStemSnapV" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_v", masterSpecific=True, requiresSetNum=True), - "postscriptBlueFuzz" : _infoMapDict(valueType=int, nakedAttribute="blue_fuzz", masterSpecific=True), - "postscriptBlueShift" : _infoMapDict(valueType=int, nakedAttribute="blue_shift", masterSpecific=True), - "postscriptBlueScale" : _infoMapDict(valueType=float, nakedAttribute="blue_scale", masterSpecific=True), - "postscriptForceBold" : _infoMapDict(valueType="boolint", nakedAttribute="force_bold", masterSpecific=True), - "postscriptDefaultWidthX" : _infoMapDict(valueType=int, nakedAttribute="default_width", masterSpecific=True), - "postscriptNominalWidthX" : _infoMapDict(valueType=int, nakedAttribute=None), - "postscriptWeightName" : _infoMapDict(valueType=str, nakedAttribute="weight"), - "postscriptDefaultCharacter" : _infoMapDict(valueType=str, nakedAttribute="default_character"), - "postscriptWindowsCharacterSet" : _infoMapDict(valueType=int, nakedAttribute="ms_charset", specialGetSet=True), - "macintoshFONDFamilyID" : _infoMapDict(valueType=int, nakedAttribute="fond_id"), - "macintoshFONDName" : _infoMapDict(valueType=str, nakedAttribute="apple_name"), - } - _environmentOverrides = ["width", "openTypeOS2WidthClass"] # ugh. - - def __init__(self, font): - super(RInfo, self).__init__() - self._object = font - - def _environmentSetAttr(self, attr, value): - # special fontlab workarounds - if attr == "width": - warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) - attr = "openTypeOS2WidthClass" - if attr == "openTypeOS2WidthClass": - if isinstance(value, basestring) and value not in _openTypeOS2WidthClass_toFL: - print "The openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification. The value will be set into the FontLab file for now." % value - self._object.width = value - else: - self._object.width = _openTypeOS2WidthClass_toFL[value] - return - # get the attribute data - data = self._ufoToFLAttrMapping[attr] - flAttr = data["nakedAttribute"] - valueType = data["valueType"] - masterSpecific = data["masterSpecific"] - requiresSetNum = data["requiresSetNum"] - specialGetSet = data["specialGetSet"] - # warn about setting attributes not supported by FL - if flAttr is None: - print "The attribute %s is not supported by FontLab. This data will not be set." % attr - return - # make sure that the value is the proper type for FL - if valueType == "intList": - value = [int(i) for i in value] - elif valueType == "boolint": - value = int(bool(value)) - elif valueType == str: - if value is None: - value = "" - value = value.encode(LOCAL_ENCODING) - elif valueType == int and not isinstance(value, int): - value = int(round(value)) - elif not isinstance(value, valueType): - value = valueType(value) - # handle postscript hint bug in FL - if attr in _postscriptHintAttributes: - value = self._handlePSHintBug(attr, value) - # handle special cases - if specialGetSet: - attr = "_set_%s" % attr - method = getattr(self, attr) - return method(value) - # set the value - obj = self._object - if len(flAttr.split(".")) > 1: - flAttrList = flAttr.split(".") - for i in flAttrList[:-1]: - obj = getattr(obj, i) - flAttr = flAttrList[-1] - ## set the foo_num attribute if necessary - if requiresSetNum: - numAttr = flAttr + "_num" - setattr(obj, numAttr, len(value)) - ## set master 0 if the data is master specific - if masterSpecific: - subObj = getattr(obj, flAttr) - if valueType == "intList": - for index, v in enumerate(value): - subObj[0][index] = v - else: - subObj[0] = value - ## otherwise use a regular set - else: - setattr(obj, flAttr, value) - - def _environmentGetAttr(self, attr): - # special fontlab workarounds - if attr == "width": - warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) - attr = "openTypeOS2WidthClass" - if attr == "openTypeOS2WidthClass": - value = self._object.width - if value not in _openTypeOS2WidthClass_fromFL: - print "The existing openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification." % value - return - else: - return _openTypeOS2WidthClass_fromFL[value] - # get the attribute data - data = self._ufoToFLAttrMapping[attr] - flAttr = data["nakedAttribute"] - valueType = data["valueType"] - masterSpecific = data["masterSpecific"] - specialGetSet = data["specialGetSet"] - # warn about setting attributes not supported by FL - if flAttr is None: - if not _IN_UFO_EXPORT: - print "The attribute %s is not supported by FontLab." % attr - return - # handle special cases - if specialGetSet: - attr = "_get_%s" % attr - method = getattr(self, attr) - return method() - # get the value - if len(flAttr.split(".")) > 1: - flAttrList = flAttr.split(".") - obj = self._object - for i in flAttrList: - obj = getattr(obj, i) - value = obj - else: - value = getattr(self._object, flAttr) - # grab the first master value if necessary - if masterSpecific: - value = value[0] - # convert if necessary - if valueType == "intList": - value = [int(i) for i in value] - elif valueType == "boolint": - value = bool(value) - elif valueType == str: - if value is None: - pass - else: - value = unicode(value, LOCAL_ENCODING) - elif not isinstance(value, valueType): - value = valueType(value) - return value - - # ------------------------------ - # individual attribute overrides - # ------------------------------ - - # styleMapStyleName - - def _get_styleMapStyleName(self): - return _styleMapStyleName_fromFL[self._object.font_style] - - def _set_styleMapStyleName(self, value): - value = _styleMapStyleName_toFL[value] - self._object.font_style = value - -# # openTypeHeadCreated -# -# # fontlab epoch: 1969-12-31 19:00:00 -# -# def _get_openTypeHeadCreated(self): -# value = self._object.ttinfo.head_creation -# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) -# delta = datetime.timedelta(seconds=value[0]) -# t = epoch - delta -# string = "%s-%s-%s %s:%s:%s" % (str(t.year).zfill(4), str(t.month).zfill(2), str(t.day).zfill(2), str(t.hour).zfill(2), str(t.minute).zfill(2), str(t.second).zfill(2)) -# return string -# -# def _set_openTypeHeadCreated(self, value): -# date, time = value.split(" ") -# year, month, day = [int(i) for i in date.split("-")] -# hour, minute, second = [int(i) for i in time.split(":")] -# value = datetime.datetime(year, month, day, hour, minute, second) -# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) -# delta = epoch - value -# seconds = delta.seconds -# self._object.ttinfo.head_creation[0] = seconds - - # openTypeOS2WeightClass - - def _get_openTypeOS2WeightClass(self): - value = self._object.weight_code - if value == -1: - value = None - return value - - def _set_openTypeOS2WeightClass(self, value): - self._object.weight_code = value - - # openTypeOS2WinDescent - - def _get_openTypeOS2WinDescent(self): - return self._object.ttinfo.os2_us_win_descent - - def _set_openTypeOS2WinDescent(self, value): - if value < 0: - warn("FontLab can only handle positive values for openTypeOS2WinDescent.") - value = abs(value) - self._object.ttinfo.os2_us_win_descent = value - - # openTypeOS2Type - - def _get_openTypeOS2Type(self): - value = self._object.ttinfo.os2_fs_type - intList = [] - for bit, bitNumber in _openTypeOS2Type_fromFL.items(): - if value & bit: - intList.append(bitNumber) - return intList - - def _set_openTypeOS2Type(self, values): - value = 0 - for bitNumber in values: - bit = _openTypeOS2Type_toFL[bitNumber] - value = value | bit - self._object.ttinfo.os2_fs_type = value - - # openTypeOS2Panose - - def _get_openTypeOS2Panose(self): - return [i for i in self._object.panose] - - def _set_openTypeOS2Panose(self, values): - for index, value in enumerate(values): - self._object.panose[index] = value - - # openTypeOS2FamilyClass - - def _get_openTypeOS2FamilyClass(self): - value = self._object.ttinfo.os2_s_family_class - for classID in range(15): - classValue = classID * 256 - if classValue > value: - classID -= 1 - classValue = classID * 256 - break - subclassID = value - classValue - return [classID, subclassID] - - def _set_openTypeOS2FamilyClass(self, values): - classID, subclassID = values - classID = classID * 256 - value = classID + subclassID - self._object.ttinfo.os2_s_family_class = value - - # postscriptWindowsCharacterSet - - def _get_postscriptWindowsCharacterSet(self): - value = self._object.ms_charset - value = _postscriptWindowsCharacterSet_fromFL[value] - return value - - def _set_postscriptWindowsCharacterSet(self, value): - value = _postscriptWindowsCharacterSet_toFL[value] - self._object.ms_charset = value - - # ----------------- - # FL bug workaround - # ----------------- - - def _handlePSHintBug(self, attribute, values): - """Function to handle problems with FontLab not allowing the max number of - alignment zones to be set to the max number. - Input: the name of the zones and the values to be set - Output: a warning when there are too many values to be set - and the max values which FontLab will allow. - """ - originalValues = values - truncatedLength = None - if attribute in ("postscriptStemSnapH", "postscriptStemSnapV"): - if len(values) > 10: - values = values[:10] - truncatedLength = 10 - elif attribute in ("postscriptBlueValues", "postscriptFamilyBlues"): - if len(values) > 12: - values = values[:12] - truncatedLength = 12 - elif attribute in ("postscriptOtherBlues", "postscriptFamilyOtherBlues"): - if len(values) > 8: - values = values[:8] - truncatedLength = 8 - if truncatedLength is not None: - print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s." % (truncatedLength, attribute, str(originalValues[truncatedLength:])) - return values - - -class RFeatures(BaseFeatures): - - _title = "FLFeatures" - - def __init__(self, font): - super(RFeatures, self).__init__() - self._object = font - - def _get_text(self): - naked = self._object - features = [] - if naked.ot_classes: - features.append(_normalizeLineEndings(naked.ot_classes)) - for feature in naked.features: - features.append(_normalizeLineEndings(feature.value)) - return "".join(features) - - def _set_text(self, value): - classes, features = splitFeaturesForFontLab(value) - naked = self._object - naked.ot_classes = classes - naked.features.clean() - for featureName, featureText in features: - f = Feature(featureName, featureText) - naked.features.append(f) - - text = property(_get_text, _set_text, doc="raw feature text.") - diff --git a/misc/pylib/robofab/objects/objectsRF.pyx b/misc/pylib/robofab/objects/objectsRF.pyx deleted file mode 100755 index 50e1f13a7..000000000 --- a/misc/pylib/robofab/objects/objectsRF.pyx +++ /dev/null @@ -1,1233 +0,0 @@ -"""UFO for GlifLib""" - -from robofab import RoboFabError, RoboFabWarning -from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\ - BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \ - relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\ - _interpolate, _interpolatePt, roundPt, addPt,\ - MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ - BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues - -import os - - -__all__ = [ "CurrentFont", - "CurrentGlyph", 'OpenFont', - 'RFont', 'RGlyph', 'RContour', - 'RPoint', 'RBPoint', 'RAnchor', - 'RComponent' - ] - - - -def CurrentFont(): - return None - -def CurrentGlyph(): - return None - -def OpenFont(path=None, note=None): - """Open a font from a path. If path is not given, present the user with a dialog.""" - if not note: - note = 'select a .ufo directory' - if not path: - from robofab.interface.all.dialogs import GetFolder - path = GetFolder(note) - if path: - try: - return RFont(path) - except OSError: - from robofab.interface.all.dialogs import Message - Message("%s is not a valid .UFO font. But considering it's all XML, why don't you have a look inside with a simple text editor."%(path)) - else: - return None - -def NewFont(familyName=None, styleName=None): - """Make a new font""" - new = RFont() - if familyName is not None: - new.info.familyName = familyName - if styleName is not None: - new.info.styleName = styleName - return new - -def AllFonts(): - """AllFonts can't work in plain python usage. It's really up to some sort of application - to keep track of which fonts are open.""" - raise NotImplementedError - - -class PostScriptFontHintValues(BasePostScriptFontHintValues): - """ Font level PostScript hints object for objectsRF usage. - If there are values in the lib, use those. - If there are no values in the lib, use defaults. - - The psHints attribute for objectsRF.RFont is basically just the - data read from the Lib. When the object saves to UFO, the - hints are written back to the lib, which is then saved. - - """ - - def __init__(self, aFont=None, data=None): - self.setParent(aFont) - BasePostScriptFontHintValues.__init__(self) - if aFont is not None: - # in version 1, this data was stored in the lib - # if it is still there, guess that it is correct - # move it to font info and remove it from the lib. - libData = aFont.lib.get(postScriptHintDataLibKey) - if libData is not None: - self.fromDict(libData) - del libData[postScriptHintDataLibKey] - if data is not None: - self.fromDict(data) - -def getPostScriptHintDataFromLib(aFont, fontLib): - hintData = fontLib.get(postScriptHintDataLibKey) - psh = PostScriptFontHintValues(aFont) - psh.fromDict(hintData) - return psh - -class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues): - """ Glyph level PostScript hints object for objectsRF usage. - If there are values in the lib, use those. - If there are no values in the lib, be empty. - - """ - def __init__(self, aGlyph=None, data=None): - # read the data from the glyph.lib, it won't be anywhere else - BasePostScriptGlyphHintValues.__init__(self) - if aGlyph is not None: - self.setParent(aGlyph) - self._loadFromLib(aGlyph.lib) - if data is not None: - self.fromDict(data) - - -class RFont(BaseFont): - """UFO font object which reads and writes glif, and keeps the data in memory in between. - Bahviour: - - comparable to Font - - comparable to GlyphSet so that it can be passed to Glif widgets - """ - - _title = "RoboFabFont" - - def __init__(self, path=None): - BaseFont.__init__(self) - if path is not None: - self._path = os.path.normpath(os.path.abspath(path)) - else: - self._path = None - self._object = {} - - self._glyphSet = None - self._scheduledForDeletion = [] # this is a place for storing glyphs that need to be removed when the font is saved - - self.kerning = RKerning() - self.kerning.setParent(self) - self.info = RInfo() - self.info.setParent(self) - self.features = RFeatures() - self.features.setParent(self) - self.groups = RGroups() - self.groups.setParent(self) - self.lib = RLib() - self.lib.setParent(self) - if path: - self._loadData(path) - else: - self.psHints = PostScriptFontHintValues(self) - self.psHints.setParent(self) - - def __setitem__(self, glyphName, glyph): - """Set a glyph at key.""" - self._object[glyphName] = glyph - - def __cmp__(self, other): - """Compare this font with another, compare if they refer to the same file.""" - if not hasattr(other, '_path'): - return -1 - if self._object._path == other._object._path and self._object._path is not None: - return 0 - else: - return -1 - - def __len__(self): - if self._glyphSet is None: - return 0 - return len(self._glyphSet) - - def _loadData(self, path): - from robofab.ufoLib import UFOReader - reader = UFOReader(path) - fontLib = reader.readLib() - # info - reader.readInfo(self.info) - # kerning - self.kerning.update(reader.readKerning()) - self.kerning.setChanged(False) - # groups - self.groups.update(reader.readGroups()) - # features - if reader.formatVersion == 1: - # migrate features from the lib - features = [] - classes = fontLib.get("org.robofab.opentype.classes") - if classes is not None: - del fontLib["org.robofab.opentype.classes"] - features.append(classes) - splitFeatures = fontLib.get("org.robofab.opentype.features") - if splitFeatures is not None: - order = fontLib.get("org.robofab.opentype.featureorder") - if order is None: - order = splitFeatures.keys() - order.sort() - else: - del fontLib["org.robofab.opentype.featureorder"] - del fontLib["org.robofab.opentype.features"] - for tag in order: - oneFeature = splitFeatures.get(tag) - if oneFeature is not None: - features.append(oneFeature) - features = "\n".join(features) - else: - features = reader.readFeatures() - self.features.text = features - # hint data - self.psHints = PostScriptFontHintValues(self) - if postScriptHintDataLibKey in fontLib: - del fontLib[postScriptHintDataLibKey] - # lib - self.lib.update(fontLib) - # glyphs - self._glyphSet = reader.getGlyphSet() - self._hasNotChanged(doGlyphs=False) - - def _loadGlyph(self, glyphName): - """Load a single glyph from the glyphSet, on request.""" - from robofab.pens.rfUFOPen import RFUFOPointPen - g = RGlyph() - g.name = glyphName - pen = RFUFOPointPen(g) - self._glyphSet.readGlyph(glyphName=glyphName, glyphObject=g, pointPen=pen) - g.setParent(self) - g.psHints._loadFromLib(g.lib) - self._object[glyphName] = g - self._object[glyphName]._hasNotChanged() - return g - - #def _prepareSaveDir(self, dir): - # path = os.path.join(dir, 'glyphs') - # if not os.path.exists(path): - # os.makedirs(path) - - def _hasNotChanged(self, doGlyphs=True): - #set the changed state of the font - if doGlyphs: - for glyph in self: - glyph._hasNotChanged() - self.setChanged(False) - - # - # attributes - # - - def _get_path(self): - return self._path - - path = property(_get_path, doc="path of the font") - - # - # methods for imitating GlyphSet? - # - - def keys(self): - # the keys are the superset of self._objects.keys() and - # self._glyphSet.keys(), minus self._scheduledForDeletion - keys = self._object.keys() - if self._glyphSet is not None: - keys.extend(self._glyphSet.keys()) - d = dict() - for glyphName in keys: - d[glyphName] = None - for glyphName in self._scheduledForDeletion: - if glyphName in d: - del d[glyphName] - return d.keys() - - def has_key(self, glyphName): - # XXX ditto, see above. - if self._glyphSet is not None: - hasGlyph = glyphName in self._object or glyphName in self._glyphSet - else: - hasGlyph = glyphName in self._object - return hasGlyph and not glyphName in self._scheduledForDeletion - - __contains__ = has_key - - def getWidth(self, glyphName): - if self._object.has_key(glyphName): - return self._object[glyphName].width - raise IndexError # or return None? - - def getReverseComponentMapping(self): - """ - Get a reversed map of component references in the font. - { - 'A' : ['Aacute', 'Aring'] - 'acute' : ['Aacute'] - 'ring' : ['Aring'] - etc. - } - """ - # a NON-REVERESED map is stored in the lib. - # this is done because a reveresed map could - # contain faulty data. for example: "Aacute" contains - # a component that references "A". Glyph "Aacute" is - # then deleted. The reverse map would still say that - # "A" is referenced by "Aacute" even though the - # glyph has been deleted. So, the stored lib works like this: - # { - # 'Aacute' : [ - # # the last known mod time of the GLIF - # 1098706856.75, - # # component references in a glyph - # ['A', 'acute'] - # ] - # } - import time - import os - import re - componentSearch_RE = re.compile( - "<component\s+" # <component - "[^>]*?" # anything EXCEPT > - "base\s*=\s*[\"\']" # base=" - "(.*?)" # foo - "[\"\']" # " - ) - rightNow = time.time() - libKey = "org.robofab.componentMapping" - previousMap = None - if self.lib.has_key(libKey): - previousMap = self.lib[libKey] - basicMap = {} - reverseMap = {} - for glyphName in self.keys(): - componentsToMap = None - modTime = None - # get the previous bits of data - previousModTime = None - previousList = None - if previousMap is not None and previousMap.has_key(glyphName): - previousModTime, previousList = previousMap[glyphName] - # the glyph has been loaded. - # simply get the components from it. - if self._object.has_key(glyphName): - componentsToMap = [component.baseGlyph for component in self._object[glyphName].components] - # the glyph has not been loaded. - else: - glyphPath = os.path.join(self._glyphSet.dirName, self._glyphSet.contents[glyphName]) - scanGlyph = True - # test the modified time of the GLIF - fileModTime = os.path.getmtime(glyphPath) - if previousModTime is not None and fileModTime == previousModTime: - # the GLIF almost* certianly has not changed. - # *theoretically, a user could replace a GLIF - # with another GLIF that has precisely the same - # mod time. - scanGlyph = False - componentsToMap = previousList - modTime = previousModTime - else: - # the GLIF is different - modTime = fileModTime - if scanGlyph: - # use regex to extract component - # base glyphs from the file - f = open(glyphPath, 'rb') - data = f.read() - f.close() - componentsToMap = componentSearch_RE.findall(data) - if componentsToMap is not None: - # store the non-reversed map - basicMap[glyphName] = (modTime, componentsToMap) - # reverse the map for the user - if componentsToMap: - for baseGlyphName in componentsToMap: - if not reverseMap.has_key(baseGlyphName): - reverseMap[baseGlyphName] = [] - reverseMap[baseGlyphName].append(glyphName) - # if a glyph has been loaded, we do not store data about it in the lib. - # this is done becuase there is not way to determine the proper mod time - # for a loaded glyph. - if modTime is None: - del basicMap[glyphName] - # store the map in the lib for re-use - self.lib[libKey] = basicMap - return reverseMap - - - def save(self, destDir=None, doProgress=False, formatVersion=2): - """Save the Font in UFO format.""" - # XXX note that when doing "save as" by specifying the destDir argument - # _all_ glyphs get loaded into memory. This could be optimized by either - # copying those .glif files that have not been edited or (not sure how - # well that would work) by simply clearing out self._objects after the - # save. - from robofab.ufoLib import UFOWriter - from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab - # if no destination is given, or if - # the given destination is the current - # path, this is not a save as operation - if destDir is None or destDir == self._path: - saveAs = False - destDir = self._path - else: - saveAs = True - # start a progress bar - nonGlyphCount = 5 - bar = None - if doProgress: - from robofab.interface.all.dialogs import ProgressBar - bar = ProgressBar("Exporting UFO", nonGlyphCount + len(self._object.keys())) - # write - writer = UFOWriter(destDir, formatVersion=formatVersion) - try: - # make a shallow copy of the lib. stuff may be added to it. - fontLib = dict(self.lib) - # info - if bar: - bar.label("Saving info...") - writer.writeInfo(self.info) - if bar: - bar.tick() - # kerning - if self.kerning.changed or saveAs: - if bar: - bar.label("Saving kerning...") - writer.writeKerning(self.kerning.asDict()) - if bar: - bar.tick() - # groups - if bar: - bar.label("Saving groups...") - writer.writeGroups(self.groups) - if bar: - bar.tick() - # features - if bar: - bar.label("Saving features...") - features = self.features.text - if features is None: - features = "" - if formatVersion == 2: - writer.writeFeatures(features) - elif formatVersion == 1: - classes, features = splitFeaturesForFontLab(features) - if classes: - fontLib["org.robofab.opentype.classes"] = classes.strip() + "\n" - if features: - featureDict = {} - for featureName, featureText in features: - featureDict[featureName] = featureText.strip() + "\n" - fontLib["org.robofab.opentype.features"] = featureDict - fontLib["org.robofab.opentype.featureorder"] = [featureName for featureName, featureText in features] - if bar: - bar.tick() - # lib - if formatVersion == 1: - fontLib[postScriptHintDataLibKey] = self.psHints.asDict() - if bar: - bar.label("Saving lib...") - writer.writeLib(fontLib) - if bar: - bar.tick() - # glyphs - glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() - - glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) - if len(self._scheduledForDeletion) != 0: - if bar: - bar.label("Removing deleted glyphs...") - for glyphName in self._scheduledForDeletion: - if glyphSet.has_key(glyphName): - glyphSet.deleteGlyph(glyphName) - if bar: - bar.tick() - if bar: - bar.label("Saving glyphs...") - count = nonGlyphCount - if saveAs: - glyphNames = self.keys() - else: - glyphNames = self._object.keys() - for glyphName in glyphNames: - glyph = self[glyphName] - glyph.psHints._saveToLib(glyph.lib) - glyph._saveToGlyphSet(glyphSet, glyphName=glyphName, force=saveAs) - if bar and not count % 10: - bar.tick(count) - count = count + 1 - glyphSet.writeContents() - self._glyphSet = glyphSet - # only blindly stop if the user says to - except KeyboardInterrupt: - bar.close() - bar = None - # kill the progress bar - if bar: - bar.close() - # reset internal stuff - self._path = destDir - self._scheduledForDeletion = [] - self.setChanged(False) - - def newGlyph(self, glyphName, clear=True): - """Make a new glyph with glyphName - if the glyph exists and clear=True clear the glyph""" - if clear and glyphName in self: - g = self[glyphName] - g.clear() - w = self.info.postscriptDefaultWidthX - if w is None: - w = 0 - g.width = w - return g - g = RGlyph() - g.setParent(self) - g.name = glyphName - w = self.info.postscriptDefaultWidthX - if w is None: - w = 0 - g.width = w - g._hasChanged() - self._object[glyphName] = g - # is the user adding a glyph that has the same - # name as one that was deleted earlier? - if glyphName in self._scheduledForDeletion: - self._scheduledForDeletion.remove(glyphName) - return self.getGlyph(glyphName) - - def insertGlyph(self, glyph, name=None): - """returns a new glyph that has been inserted into the font""" - if name is None: - name = glyph.name - glyph = glyph.copy() - glyph.name = name - glyph.setParent(self) - glyph._hasChanged() - self._object[name] = glyph - # is the user adding a glyph that has the same - # name as one that was deleted earlier? - if name in self._scheduledForDeletion: - self._scheduledForDeletion.remove(name) - return self.getGlyph(name) - - def removeGlyph(self, glyphName): - """remove a glyph from the font""" - # XXX! Potential issue with removing glyphs. - # if a glyph is removed from a font, but it is still referenced - # by a component, it will give pens some trouble. - # where does the resposibility for catching this fall? - # the removeGlyph method? the addComponent method - # of the various pens? somewhere else? hm... tricky. - # - #we won't actually remove it, we will just store it for removal - # but only if the glyph does exist - if self.has_key(glyphName) and glyphName not in self._scheduledForDeletion: - self._scheduledForDeletion.append(glyphName) - # now delete the object - if self._object.has_key(glyphName): - del self._object[glyphName] - self._hasChanged() - - def getGlyph(self, glyphName): - # XXX getGlyph may have to become private, to avoid duplication - # with __getitem__ - n = None - if self._object.has_key(glyphName): - # have we served this glyph before? it should be in _object - n = self._object[glyphName] - else: - # haven't served it before, is it in the glyphSet then? - if self._glyphSet is not None and glyphName in self._glyphSet: - # yes, read the .glif file from disk - n = self._loadGlyph(glyphName) - if n is None: - raise KeyError, glyphName - return n - - -class RGlyph(BaseGlyph): - - _title = "RGlyph" - - def __init__(self): - BaseGlyph.__init__(self) - self.contours = [] - self.components = [] - self.anchors = [] - self._unicodes = [] - self.width = 0 - self.note = None - self._name = "Unnamed Glyph" - self.selected = False - self._properties = None - self._lib = RLib() - self._lib.setParent(self) - self.psHints = PostScriptGlyphHintValues() - self.psHints.setParent(self) - - def __len__(self): - return len(self.contours) - - def __getitem__(self, index): - if index < len(self.contours): - return self.contours[index] - raise IndexError - - def _hasNotChanged(self): - for contour in self.contours: - contour.setChanged(False) - for segment in contour.segments: - segment.setChanged(False) - for point in segment.points: - point.setChanged(False) - for component in self.components: - component.setChanged(False) - for anchor in self.anchors: - anchor.setChanged(False) - self.setChanged(False) - - # - # attributes - # - - def _get_lib(self): - return self._lib - - def _set_lib(self, obj): - self._lib.clear() - self._lib.update(obj) - - lib = property(_get_lib, _set_lib) - - def _get_name(self): - return self._name - - def _set_name(self, value): - prevName = self._name - newName = value - if newName == prevName: - return - self._name = newName - self.setChanged(True) - font = self.getParent() - if font is not None: - # but, this glyph could be linked to a - # FontLab font, because objectsFL.RGlyph.copy() - # creates an objectsRF.RGlyph with the parent - # set to an objectsFL.RFont object. so, check to see - # if this is a legitimate RFont before trying to - # do the objectsRF.RFont glyph name change - if isinstance(font, RFont): - font._object[newName] = self - # is the user changing a glyph's name to the - # name of a glyph that was deleted earlier? - if newName in font._scheduledForDeletion: - font._scheduledForDeletion.remove(newName) - font.removeGlyph(prevName) - - name = property(_get_name, _set_name) - - def _get_unicodes(self): - return self._unicodes - - def _set_unicodes(self, value): - if not isinstance(value, list): - raise RoboFabError, "unicodes must be a list" - self._unicodes = value - self._hasChanged() - - unicodes = property(_get_unicodes, _set_unicodes, doc="all unicode values for the glyph") - - def _get_unicode(self): - if len(self._unicodes) == 0: - return None - return self._unicodes[0] - - def _set_unicode(self, value): - uni = self._unicodes - if value is not None: - if value not in uni: - self.unicodes.insert(0, value) - elif uni.index(value) != 0: - uni.insert(0, uni.pop(uni.index(value))) - self.unicodes = uni - - unicode = property(_get_unicode, _set_unicode, doc="first unicode value for the glyph") - - def getPointPen(self): - from robofab.pens.rfUFOPen import RFUFOPointPen - return RFUFOPointPen(self) - - def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)): - """append a component to the glyph""" - new = RComponent(baseGlyph, offset, scale) - new.setParent(self) - self.components.append(new) - self._hasChanged() - - def appendAnchor(self, name, position, mark=None): - """append an anchor to the glyph""" - new = RAnchor(name, position, mark) - new.setParent(self) - self.anchors.append(new) - self._hasChanged() - - def removeContour(self, index): - """remove a specific contour from the glyph""" - del self.contours[index] - self._hasChanged() - - def removeAnchor(self, anchor): - """remove a specific anchor from the glyph""" - del self.anchors[anchor.index] - self._hasChanged() - - def removeComponent(self, component): - """remove a specific component from the glyph""" - del self.components[component.index] - self._hasChanged() - - def center(self, padding=None): - """Equalise sidebearings, set to padding if wanted.""" - left = self.leftMargin - right = self.rightMargin - if padding: - e_left = e_right = padding - else: - e_left = (left + right)/2 - e_right = (left + right) - e_left - self.leftMargin = e_left - self.rightMargin = e_right - - def decompose(self): - """Decompose all components""" - for i in range(len(self.components)): - self.components[-1].decompose() - self._hasChanged() - - def clear(self, contours=True, components=True, anchors=True, guides=True): - """Clear all items marked as True from the glyph""" - if contours: - self.clearContours() - if components: - self.clearComponents() - if anchors: - self.clearAnchors() - if guides: - self.clearHGuides() - self.clearVGuides() - - def clearContours(self): - """clear all contours""" - self.contours = [] - self._hasChanged() - - def clearComponents(self): - """clear all components""" - self.components = [] - self._hasChanged() - - def clearAnchors(self): - """clear all anchors""" - self.anchors = [] - self._hasChanged() - - def clearHGuides(self): - """clear all horizontal guides""" - self.hGuides = [] - self._hasChanged() - - def clearVGuides(self): - """clear all vertical guides""" - self.vGuides = [] - self._hasChanged() - - def getAnchors(self): - return self.anchors - - def getComponents(self): - return self.components - - # - # stuff related to Glyph Properties - # - - - -class RContour(BaseContour): - - _title = "RoboFabContour" - - def __init__(self, object=None): - #BaseContour.__init__(self) - self.segments = [] - self.selected = False - - def __len__(self): - return len(self.segments) - - def __getitem__(self, index): - if index < len(self.segments): - return self.segments[index] - raise IndexError - - def _get_index(self): - return self.getParent().contours.index(self) - - def _set_index(self, index): - ogIndex = self.index - if index != ogIndex: - contourList = self.getParent().contours - contourList.insert(index, contourList.pop(ogIndex)) - - - index = property(_get_index, _set_index, doc="index of the contour") - - def _get_points(self): - points = [] - for segment in self.segments: - for point in segment.points: - points.append(point) - return points - - points = property(_get_points, doc="view the contour as a list of points") - - def _get_bPoints(self): - bPoints = [] - for segment in self.segments: - segType = segment.type - if segType == MOVE: - bType = CORNER - elif segType == LINE: - bType = CORNER - elif segType == CURVE: - if segment.smooth: - bType = CURVE - else: - bType = CORNER - else: - raise RoboFabError, "encountered unknown segment type" - b = RBPoint() - b.setParent(segment) - bPoints.append(b) - return bPoints - - bPoints = property(_get_bPoints, doc="view the contour as a list of bPoints") - - def appendSegment(self, segmentType, points, smooth=False): - """append a segment to the contour""" - segment = self.insertSegment(index=len(self.segments), segmentType=segmentType, points=points, smooth=smooth) - return segment - - def insertSegment(self, index, segmentType, points, smooth=False): - """insert a segment into the contour""" - segment = RSegment(segmentType, points, smooth) - segment.setParent(self) - self.segments.insert(index, segment) - self._hasChanged() - return segment - - def removeSegment(self, index): - """remove a segment from the contour""" - del self.segments[index] - self._hasChanged() - - def reverseContour(self): - """reverse the contour""" - from robofab.pens.reverseContourPointPen import ReverseContourPointPen - index = self.index - glyph = self.getParent() - pen = glyph.getPointPen() - reversePen = ReverseContourPointPen(pen) - self.drawPoints(reversePen) - # we've drawn the reversed contour onto our parent glyph, - # so it sits at the end of the contours list: - newContour = glyph.contours.pop(-1) - for segment in newContour.segments: - segment.setParent(self) - self.segments = newContour.segments - self._hasChanged() - - def setStartSegment(self, segmentIndex): - """set the first segment on the contour""" - # this obviously does not support open contours - if len(self.segments) < 2: - return - if segmentIndex == 0: - return - if segmentIndex > len(self.segments)-1: - raise IndexError, 'segment index not in segments list' - oldStart = self.segments[0] - oldLast = self.segments[-1] - #check to see if the contour ended with a curve on top of the move - #if we find one delete it, - if oldLast.type == CURVE or oldLast.type == QCURVE: - startOn = oldStart.onCurve - lastOn = oldLast.onCurve - if startOn.x == lastOn.x and startOn.y == lastOn.y: - del self.segments[0] - # since we deleted the first contour, the segmentIndex needs to shift - segmentIndex = segmentIndex - 1 - # if we DO have a move left over, we need to convert it to a line - if self.segments[0].type == MOVE: - self.segments[0].type = LINE - # slice up the segments and reassign them to the contour - segments = self.segments[segmentIndex:] - self.segments = segments + self.segments[:segmentIndex] - # now, draw the contour onto the parent glyph - glyph = self.getParent() - pen = glyph.getPointPen() - self.drawPoints(pen) - # we've drawn the new contour onto our parent glyph, - # so it sits at the end of the contours list: - newContour = glyph.contours.pop(-1) - for segment in newContour.segments: - segment.setParent(self) - self.segments = newContour.segments - self._hasChanged() - - -class RSegment(BaseSegment): - - _title = "RoboFabSegment" - - def __init__(self, segmentType=None, points=[], smooth=False): - BaseSegment.__init__(self) - self.selected = False - self.points = [] - self.smooth = smooth - if points: - #the points in the segment should be RPoints, so create those objects - for point in points[:-1]: - x, y = point - p = RPoint(x, y, pointType=OFFCURVE) - p.setParent(self) - self.points.append(p) - aX, aY = points[-1] - p = RPoint(aX, aY, segmentType) - p.setParent(self) - self.points.append(p) - - def _get_type(self): - return self.points[-1].type - - def _set_type(self, pointType): - onCurve = self.points[-1] - ocType = onCurve.type - if ocType == pointType: - return - #we are converting a cubic line into a cubic curve - if pointType == CURVE and ocType == LINE: - onCurve.type = pointType - parent = self.getParent() - prev = parent._prevSegment(self.index) - p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE) - p1.setParent(self) - p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE) - p2.setParent(self) - self.points.insert(0, p2) - self.points.insert(0, p1) - #we are converting a cubic move to a curve - elif pointType == CURVE and ocType == MOVE: - onCurve.type = pointType - parent = self.getParent() - prev = parent._prevSegment(self.index) - p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE) - p1.setParent(self) - p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE) - p2.setParent(self) - self.points.insert(0, p2) - self.points.insert(0, p1) - #we are converting a quad curve to a cubic curve - elif pointType == CURVE and ocType == QCURVE: - onCurve.type == CURVE - #we are converting a cubic curve into a cubic line - elif pointType == LINE and ocType == CURVE: - p = self.points.pop(-1) - self.points = [p] - onCurve.type = pointType - self.smooth = False - #we are converting a cubic move to a line - elif pointType == LINE and ocType == MOVE: - onCurve.type = pointType - #we are converting a quad curve to a line: - elif pointType == LINE and ocType == QCURVE: - p = self.points.pop(-1) - self.points = [p] - onCurve.type = pointType - self.smooth = False - # we are converting to a quad curve where just about anything is legal - elif pointType == QCURVE: - onCurve.type = pointType - else: - raise RoboFabError, 'unknown segment type' - - type = property(_get_type, _set_type, doc="type of the segment") - - def _get_index(self): - return self.getParent().segments.index(self) - - index = property(_get_index, doc="index of the segment") - - def insertPoint(self, index, pointType, point): - x, y = point - p = RPoint(x, y, pointType=pointType) - p.setParent(self) - self.points.insert(index, p) - self._hasChanged() - - def removePoint(self, index): - del self.points[index] - self._hasChanged() - - -class RBPoint(BaseBPoint): - - _title = "RoboFabBPoint" - - def _setAnchorChanged(self, value): - self._anchorPoint.setChanged(value) - - def _setNextChanged(self, value): - self._nextOnCurve.setChanged(value) - - def _get__parentSegment(self): - return self.getParent() - - _parentSegment = property(_get__parentSegment, doc="") - - def _get__nextOnCurve(self): - pSeg = self._parentSegment - contour = pSeg.getParent() - #could this potentially return an incorrect index? say, if two segments are exactly the same? - return contour.segments[(contour.segments.index(pSeg) + 1) % len(contour.segments)] - - _nextOnCurve = property(_get__nextOnCurve, doc="") - - def _get_index(self): - return self._parentSegment.index - - index = property(_get_index, doc="index of the bPoint on the contour") - - -class RPoint(BasePoint): - - _title = "RoboFabPoint" - - def __init__(self, x=0, y=0, pointType=None, name=None): - self.selected = False - self._type = pointType - self._x = x - self._y = y - self._name = name - - def _get_x(self): - return self._x - - def _set_x(self, value): - self._x = value - self._hasChanged() - - x = property(_get_x, _set_x, doc="") - - def _get_y(self): - return self._y - - def _set_y(self, value): - self._y = value - self._hasChanged() - - y = property(_get_y, _set_y, doc="") - - def _get_type(self): - return self._type - - def _set_type(self, value): - self._type = value - self._hasChanged() - - type = property(_get_type, _set_type, doc="") - - def _get_name(self): - return self._name - - def _set_name(self, value): - self._name = value - self._hasChanged() - - name = property(_get_name, _set_name, doc="") - - -class RAnchor(BaseAnchor): - - _title = "RoboFabAnchor" - - def __init__(self, name=None, position=None, mark=None): - BaseAnchor.__init__(self) - self.selected = False - self.name = name - if position is None: - self.x = self.y = None - else: - self.x, self.y = position - self.mark = mark - - def _get_index(self): - if self.getParent() is None: return None - return self.getParent().anchors.index(self) - - index = property(_get_index, doc="index of the anchor") - - def _get_position(self): - return (self.x, self.y) - - def _set_position(self, value): - self.x = value[0] - self.y = value[1] - self._hasChanged() - - position = property(_get_position, _set_position, doc="position of the anchor") - - def move(self, pt): - """Move the anchor""" - self.x = self.x + pt[0] - self.y = self.y + pt[0] - self._hasChanged() - - -class RComponent(BaseComponent): - - _title = "RoboFabComponent" - - def __init__(self, baseGlyphName=None, offset=(0,0), scale=(1,1), transform=None): - BaseComponent.__init__(self) - self.selected = False - self._baseGlyph = baseGlyphName - self._offset = offset - self._scale = scale - if transform is None: - xx, yy = scale - dx, dy = offset - self.transformation = (xx, 0, 0, yy, dx, dy) - else: - self.transformation = transform - - def _get_index(self): - if self.getParent() is None: return None - return self.getParent().components.index(self) - - index = property(_get_index, doc="index of the component") - - def _get_baseGlyph(self): - return self._baseGlyph - - def _set_baseGlyph(self, glyphName): - # XXXX needs to be implemented in objectsFL for symmetricity's sake. Eventually. - self._baseGlyph = glyphName - self._hasChanged() - - baseGlyph = property(_get_baseGlyph, _set_baseGlyph, doc="") - - def _get_offset(self): - """ Get the offset component of the transformation.=""" - (xx, xy, yx, yy, dx, dy) = self._transformation - return dx, dy - - def _set_offset(self, value): - """ Set the offset component of the transformation.""" - (xx, xy, yx, yy, dx, dy) = self._transformation - self._transformation = (xx, xy, yx, yy, value[0], value[1]) - self._hasChanged() - - offset = property(_get_offset, _set_offset, doc="the offset of the component") - - def _get_scale(self): - """ Return the scale components of the transformation.""" - (xx, xy, yx, yy, dx, dy) = self._transformation - return xx, yy - - def _set_scale(self, scale): - """ Set the scale component of the transformation. - Note: setting this value effectively makes the xy and yx values meaningless. - We're assuming that if you're setting the xy and yx values, you will use - the transformation attribute rather than the scale and offset attributes. - """ - xScale, yScale = scale - (xx, xy, yx, yy, dx, dy) = self._transformation - self._transformation = (xScale, xy, yx, yScale, dx, dy) - self._hasChanged() - - scale = property(_get_scale, _set_scale, doc="the scale of the component") - - def _get_transformation(self): - return self._transformation - - def _set_transformation(self, transformation): - assert len(transformation)==6, "Transformation matrix must have 6 values" - self._transformation = transformation - - transformation = property(_get_transformation, _set_transformation, doc="the transformation matrix of the component") - - def move(self, pt): - """Move the component""" - (xx, xy, yx, yy, dx, dy) = self._transformation - self._transformation = (xx, xy, yx, yy, dx+pt[0], dy+pt[1]) - self._hasChanged() - - def decompose(self): - """Decompose the component""" - baseGlyphName = self.baseGlyph - parentGlyph = self.getParent() - # if there is no parent glyph, there is nothing to decompose to - if baseGlyphName is not None and parentGlyph is not None: - parentFont = parentGlyph.getParent() - # we must have a parent glyph with the baseGlyph - # if not, we will simply remove the component from - # the parent glyph thereby decomposing the component - # to nothing. - if parentFont is not None and parentFont.has_key(baseGlyphName): - from robofab.pens.adapterPens import TransformPointPen - baseGlyph = parentFont[baseGlyphName] - for contour in baseGlyph.contours: - pointPen = parentGlyph.getPointPen() - transPen = TransformPointPen(pointPen, self._transformation) - contour.drawPoints(transPen) - parentGlyph.components.remove(self) - - -class RKerning(BaseKerning): - - _title = "RoboFabKerning" - - -class RGroups(BaseGroups): - - _title = "RoboFabGroups" - -class RLib(BaseLib): - - _title = "RoboFabLib" - - -class RInfo(BaseInfo): - - _title = "RoboFabFontInfo" - -class RFeatures(BaseFeatures): - - _title = "RoboFabFeatures" - diff --git a/misc/pylib/robofab/path/__init__.py b/misc/pylib/robofab/path/__init__.py deleted file mode 100755 index f268df6c9..000000000 --- a/misc/pylib/robofab/path/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - -Directory for modules -which do path stuff. -Maybe it should move somewhere else. - - -""" - - - - diff --git a/misc/pylib/robofab/path/intersect.py b/misc/pylib/robofab/path/intersect.py deleted file mode 100644 index ea0600c19..000000000 --- a/misc/pylib/robofab/path/intersect.py +++ /dev/null @@ -1,108 +0,0 @@ -from robofab.pens.filterPen import flattenGlyph -from robofab.objects.objectsRF import RGlyph as _RGlyph -import math - -_EPSILON = 1e-15 - - -def normalise(a1, a2): - """Normalise this vector to length 1""" - n = math.sqrt((a1*a1)+(a2*a2)) - return (a1/n, a2/n) - -def inbetween((a1, a2), (b1, b2), (c1, c2)): - """Return True if point b is in between points a and c.""" - x = (a1-_EPSILON<=b1<=c1+_EPSILON) or (a1+_EPSILON>=b1>=c1-_EPSILON) - y = (a2-_EPSILON<=b2<=c2+_EPSILON) or (a2+_EPSILON>=b2>=c2-_EPSILON) - return x == y == True - -def sectlines((a1, a2), (p1, p2), (b1, b2), (q1, q2)): - '''Calculate the intersection point of two straight lines. Result in floats.''' - if (a1, a2) == (p1, p2): - return None - r1 = a1-p1 - r2 = a2-p2 - r1, r2 = normalise(r1, r2) - s1 = b1-q1 - s2 = b2-q2 - s1, s2 = normalise(s1, s2) - f = float(s1*r2 - s2*r1) - if f == 0: - return None - mu = (r1*(q2 - p2) + r2*(p1 - q1)) / f - m1 = mu*s1 + q1 - m2 = mu*s2 + q2 - if (m1, m2) == (a1, a2): - return None - if inbetween((a1, a2), (m1, m2), (p1,p2)) and inbetween((b1, b2), (m1, m2), (q1,q2)): - return m1, m2 - else: - return None - -def _makeFlat(aGlyph, segmentLength = 10): - """Helper function to flatten the glyph with a given approximate segment length.""" - return flattenGlyph(aGlyph, segmentLength) - -def intersect(aGlyph, startPt, endPt, segmentLength=10): - """Find the intersections between a glyph and a straight line.""" - flat = _makeFlat(aGlyph) - return _intersect(flat, startPt, endPt, segmentLength) - -def _intersect(flat, startPt, endPt, segmentLength=10): - """Find the intersections between a flattened glyph and a straight line.""" - if len(flat.contours) == 0: - return None - if startPt == endPt: - return None - sect = None - intersections = {} - # new contains the flattened outline - for c in flat: - l =len(c.points) - for i in range(l): - cur = c.points[i] - next = c.points[(i+1)%l] - sect = sectlines((cur.x, cur.y), (next.x, next.y), startPt, endPt) - if sect is None: - continue - intersections[sect] = 1 - return intersections.keys() - -def intersectGlyphs(glyphA, glyphB, segmentLength=10): - """Approximate the intersection points between two glyphs by - flattening both glyphs and checking each tiny segment for - intersections. Slow, but perhaps more realistic then - solving the equasions. - - Seems to work for basic curves and straights, but untested - for edges cases, alsmost hits, near hits, double points, crap like that. - """ - flatA = _makeFlat(glyphA) - flatB = _makeFlat(glyphB) - intersections = [] - for c in flatA: - l =len(c.points) - for i in range(l): - cur = c.points[i] - next = c.points[(i+1)%l] - sect = _intersect(flatB, (cur.x, cur.y), (next.x, next.y)) - if sect is None: - continue - intersections = intersections + sect - return intersections - -def makeTestGlyph(): - g = _RGlyph() - pen = g.getPen() - pen.moveTo((100, 100)) - pen.lineTo((800, 100)) - pen.curveTo((1000, 300), (1000, 600), (800, 800)) - pen.lineTo((100, 800)) - pen.lineTo((100, 100)) - pen.closePath() - return g - -if __name__ == "__main__": - g = makeTestGlyph() - print intersect(g, (-10, 200), (650, 150)) - print intersect(g, (100, 100), (600, 600)) diff --git a/misc/pylib/robofab/pens/__init__.py b/misc/pylib/robofab/pens/__init__.py deleted file mode 100755 index a5afc0dc3..000000000 --- a/misc/pylib/robofab/pens/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -""" - -Directory for all pen modules. -If you make a pen, put it here so that we can keep track of it. - - -""" - - - - diff --git a/misc/pylib/robofab/pens/adapterPens.py b/misc/pylib/robofab/pens/adapterPens.py deleted file mode 100644 index 0cd9ae3f7..000000000 --- a/misc/pylib/robofab/pens/adapterPens.py +++ /dev/null @@ -1,293 +0,0 @@ -import math -from fontTools.pens.basePen import AbstractPen -from robofab.pens.pointPen import AbstractPointPen, BasePointToSegmentPen - - -class FabToFontToolsPenAdapter: - - """Class that covers up the subtle differences between RoboFab - Pens and FontTools Pens. 'Fab should eventually move to FontTools - Pens, this class may help to make the transition smoother. - """ - - # XXX The change to FontTools pens has almost been completed. Any - # usage of this class should be highly suspect. - - def __init__(self, fontToolsPen): - self.fontToolsPen = fontToolsPen - - def moveTo(self, pt, **kargs): - self.fontToolsPen.moveTo(pt) - - def lineTo(self, pt, **kargs): - self.fontToolsPen.lineTo(pt) - - def curveTo(self, *pts, **kargs): - self.fontToolsPen.curveTo(*pts) - - def qCurveTo(self, *pts, **kargs): - self.fontToolsPen.qCurveTo(*pts) - - def closePath(self): - self.fontToolsPen.closePath() - - def endPath(self): - self.fontToolsPen.endPath() - - def addComponent(self, glyphName, offset=(0, 0), scale=(1, 1)): - self.fontToolsPen.addComponent(glyphName, - (scale[0], 0, 0, scale[1], offset[0], offset[1])) - - def setWidth(self, width): - self.width = width - - def setNote(self, note): - pass - - def addAnchor(self, name, pt): - self.fontToolsPen.moveTo(pt) - self.fontToolsPen.endPath() - - def doneDrawing(self): - pass - - -class PointToSegmentPen(BasePointToSegmentPen): - - """Adapter class that converts the PointPen protocol to the - (Segment)Pen protocol. - """ - - def __init__(self, segmentPen, outputImpliedClosingLine=False): - BasePointToSegmentPen.__init__(self) - self.pen = segmentPen - self.outputImpliedClosingLine = outputImpliedClosingLine - - def _flushContour(self, segments): - assert len(segments) >= 1 - pen = self.pen - if segments[0][0] == "move": - # It's an open path. - closed = False - points = segments[0][1] - assert len(points) == 1 - movePt, smooth, name, kwargs = points[0] - del segments[0] - else: - # It's a closed path, do a moveTo to the last - # point of the last segment. - closed = True - segmentType, points = segments[-1] - movePt, smooth, name, kwargs = points[-1] - if movePt is None: - # quad special case: a contour with no on-curve points contains - # one "qcurve" segment that ends with a point that's None. We - # must not output a moveTo() in that case. - pass - else: - pen.moveTo(movePt) - outputImpliedClosingLine = self.outputImpliedClosingLine - nSegments = len(segments) - for i in range(nSegments): - segmentType, points = segments[i] - points = [pt for pt, smooth, name, kwargs in points] - if segmentType == "line": - assert len(points) == 1 - pt = points[0] - if i + 1 != nSegments or outputImpliedClosingLine or not closed: - pen.lineTo(pt) - elif segmentType == "curve": - pen.curveTo(*points) - elif segmentType == "qcurve": - pen.qCurveTo(*points) - else: - assert 0, "illegal segmentType: %s" % segmentType - if closed: - pen.closePath() - else: - pen.endPath() - - def addComponent(self, glyphName, transform): - self.pen.addComponent(glyphName, transform) - - -class SegmentToPointPen(AbstractPen): - - """Adapter class that converts the (Segment)Pen protocol to the - PointPen protocol. - """ - - def __init__(self, pointPen, guessSmooth=True): - if guessSmooth: - self.pen = GuessSmoothPointPen(pointPen) - else: - self.pen = pointPen - self.contour = None - - def _flushContour(self): - pen = self.pen - pen.beginPath() - for pt, segmentType in self.contour: - pen.addPoint(pt, segmentType=segmentType) - pen.endPath() - - def moveTo(self, pt): - self.contour = [] - self.contour.append((pt, "move")) - - def lineTo(self, pt): - self.contour.append((pt, "line")) - - def curveTo(self, *pts): - for pt in pts[:-1]: - self.contour.append((pt, None)) - self.contour.append((pts[-1], "curve")) - - def qCurveTo(self, *pts): - if pts[-1] is None: - self.contour = [] - for pt in pts[:-1]: - self.contour.append((pt, None)) - if pts[-1] is not None: - self.contour.append((pts[-1], "qcurve")) - - def closePath(self): - if len(self.contour) > 1 and self.contour[0][0] == self.contour[-1][0]: - self.contour[0] = self.contour[-1] - del self.contour[-1] - else: - # There's an implied line at the end, replace "move" with "line" - # for the first point - pt, tp = self.contour[0] - if tp == "move": - self.contour[0] = pt, "line" - self._flushContour() - self.contour = None - - def endPath(self): - self._flushContour() - self.contour = None - - def addComponent(self, glyphName, transform): - assert self.contour is None - self.pen.addComponent(glyphName, transform) - - -class TransformPointPen(AbstractPointPen): - - """PointPen that transforms all coordinates, and passes them to another - PointPen. It also transforms the transformation given to addComponent(). - """ - - def __init__(self, outPen, transformation): - if not hasattr(transformation, "transformPoint"): - from fontTools.misc.transform import Transform - transformation = Transform(*transformation) - self._transformation = transformation - self._transformPoint = transformation.transformPoint - self._outPen = outPen - self._stack = [] - - def beginPath(self): - self._outPen.beginPath() - - def endPath(self): - self._outPen.endPath() - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - pt = self._transformPoint(pt) - self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs) - - def addComponent(self, glyphName, transformation): - transformation = self._transformation.transform(transformation) - self._outPen.addComponent(glyphName, transformation) - - -class GuessSmoothPointPen(AbstractPointPen): - - """Filtering PointPen that tries to determine whether an on-curve point - should be "smooth", ie. that it's a "tangent" point or a "curve" point. - """ - - def __init__(self, outPen): - self._outPen = outPen - self._points = None - - def _flushContour(self): - points = self._points - nPoints = len(points) - if not nPoints: - return - if points[0][1] == "move": - # Open path. - indices = range(1, nPoints - 1) - elif nPoints > 1: - # Closed path. To avoid having to mod the contour index, we - # simply abuse Python's negative index feature, and start at -1 - indices = range(-1, nPoints - 1) - else: - # closed path containing 1 point (!), ignore. - indices = [] - for i in indices: - pt, segmentType, dummy, name, kwargs = points[i] - if segmentType is None: - continue - prev = i - 1 - next = i + 1 - if points[prev][1] is not None and points[next][1] is not None: - continue - # At least one of our neighbors is an off-curve point - pt = points[i][0] - prevPt = points[prev][0] - nextPt = points[next][0] - if pt != prevPt and pt != nextPt: - dx1, dy1 = pt[0] - prevPt[0], pt[1] - prevPt[1] - dx2, dy2 = nextPt[0] - pt[0], nextPt[1] - pt[1] - a1 = math.atan2(dx1, dy1) - a2 = math.atan2(dx2, dy2) - if abs(a1 - a2) < 0.05: - points[i] = pt, segmentType, True, name, kwargs - - for pt, segmentType, smooth, name, kwargs in points: - self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs) - - def beginPath(self): - assert self._points is None - self._points = [] - self._outPen.beginPath() - - def endPath(self): - self._flushContour() - self._outPen.endPath() - self._points = None - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - self._points.append((pt, segmentType, False, name, kwargs)) - - def addComponent(self, glyphName, transformation): - assert self._points is None - self._outPen.addComponent(glyphName, transformation) - - -if __name__ == "__main__": - from fontTools.pens.basePen import _TestPen as PSPen - from robofab.pens.pointPen import PrintingPointPen - segmentPen = PSPen(None) -# pen = PointToSegmentPen(SegmentToPointPen(PointToSegmentPen(PSPen(None)))) - pen = PointToSegmentPen(SegmentToPointPen(PrintingPointPen())) -# pen = PrintingPointPen() - pen = PointToSegmentPen(PSPen(None), outputImpliedClosingLine=False) -# pen.beginPath() -# pen.addPoint((50, 50), name="an anchor") -# pen.endPath() - pen.beginPath() - pen.addPoint((-100, 0), segmentType="line") - pen.addPoint((0, 0), segmentType="line") - pen.addPoint((0, 100), segmentType="line") - pen.addPoint((30, 200)) - pen.addPoint((50, 100), name="superbezcontrolpoint!") - pen.addPoint((70, 200)) - pen.addPoint((100, 100), segmentType="curve") - pen.addPoint((100, 0), segmentType="line") - pen.endPath() -# pen.addComponent("a", (1, 0, 0, 1, 100, 200)) diff --git a/misc/pylib/robofab/pens/angledMarginPen.py b/misc/pylib/robofab/pens/angledMarginPen.py deleted file mode 100644 index 49ff8eed3..000000000 --- a/misc/pylib/robofab/pens/angledMarginPen.py +++ /dev/null @@ -1,132 +0,0 @@ -from robofab.world import RFont -from fontTools.pens.basePen import BasePen -from robofab.misc.arrayTools import updateBounds, pointInRect, unionRect -from robofab.misc.bezierTools import calcCubicBounds, calcQuadraticBounds -from robofab.pens.filterPen import _estimateCubicCurveLength, _getCubicPoint -import math - - - -__all__ = ["AngledMarginPen", "getAngledMargins", - "setAngledLeftMargin", "setAngledRightMargin", - "centerAngledMargins"] - - - -class AngledMarginPen(BasePen): - """ - Angled Margin Pen - - Pen to calculate the margins as if the margin lines were slanted - according to the font.info.italicAngle. - - Notes: - - this pen works on the on-curve points, and approximates the distance to curves. - - results will be float. - - when used in FontLab, the resulting margins may be slightly - different from the values originally set, due to rounding errors. - - similar to what RoboFog used to do. - - RoboFog had a special attribute for "italicoffset", horizontal - shift of all glyphs. This is missing in Robofab. - """ - def __init__(self, glyphSet, width, italicAngle): - BasePen.__init__(self, glyphSet) - self.width = width - self._angle = math.radians(90+italicAngle) - self.maxSteps = 100 - self.margin = None - self._left = None - self._right = None - self._start = None - self.currentPt = None - - def _getAngled(self, pt): - r = (g.width + (pt[1] / math.tan(self._angle)))-pt[0] - l = pt[0]-((pt[1] / math.tan(self._angle))) - if self._right is None: - self._right = r - else: - self._right = min(self._right, r) - if self._left is None: - self._left = l - else: - self._left = min(self._left, l) - #print pt, l, r - self.margin = self._left, self._right - - def _moveTo(self, pt): - self._start = self.currentPt = pt - - def _addMoveTo(self): - if self._start is None: - return - self._start = self.currentPt = None - - def _lineTo(self, pt): - self._addMoveTo() - self._getAngled(pt) - - def _curveToOne(self, pt1, pt2, pt3): - step = 1.0/self.maxSteps - factors = range(0, self.maxSteps+1) - for i in factors: - pt = _getCubicPoint(i*step, self.currentPt, pt1, pt2, pt3) - self._getAngled(pt) - self.currentPt = pt3 - - def _qCurveToOne(self, bcp, pt): - self._addMoveTo() - # add curve tracing magic here. - self._getAngled(pt) - self.currentPt = pt3 - -def getAngledMargins(glyph, font): - """Get the angled margins for this glyph.""" - pen = AngledMarginPen(font, glyph.width, font.info.italicAngle) - glyph.draw(pen) - return pen.margin - -def setAngledLeftMargin(glyph, font, value): - """Set the left angled margin to value, adjusted for font.info.italicAngle.""" - pen = AngledMarginPen(font, glyph.width, font.info.italicAngle) - g.draw(pen) - isLeft, isRight = pen.margin - glyph.leftMargin += value-isLeft - -def setAngledRightMargin(glyph, font, value): - """Set the right angled margin to value, adjusted for font.info.italicAngle.""" - pen = AngledMarginPen(font, glyph.width, font.info.italicAngle) - g.draw(pen) - isLeft, isRight = pen.margin - glyph.rightMargin += value-isRight - -def centerAngledMargins(glyph, font): - """Center the glyph on angled margins.""" - pen = AngledMarginPen(font, glyph.width, font.info.italicAngle) - g.draw(pen) - isLeft, isRight = pen.margin - setAngledLeftMargin(glyph, font, (isLeft+isRight)*.5) - setAngledRightMargin(glyph, font, (isLeft+isRight)*.5) - -def guessItalicOffset(glyph, font): - """Guess the italic offset based on the margins of a symetric glyph. - For instance H or I. - """ - l, r = getAngledMargins(glyph, font) - return l - (l+r)*.5 - - -if __name__ == "__main__": - - # example for FontLab, with a glyph open. - from robofab.world import CurrentFont, CurrentGlyph - g = CurrentGlyph() - f = CurrentFont() - - print "margins!", getAngledMargins(g, f) - # set the angled margin to a value - m = 50 - setAngledLeftMargin(g, f, m) - setAngledRightMargin(g, f, m) - g.update() - diff --git a/misc/pylib/robofab/pens/boundsPen.pyx b/misc/pylib/robofab/pens/boundsPen.pyx deleted file mode 100644 index a1a72bd12..000000000 --- a/misc/pylib/robofab/pens/boundsPen.pyx +++ /dev/null @@ -1,95 +0,0 @@ -from fontTools.pens.basePen import BasePen -from robofab.misc.arrayTools import updateBounds, pointInRect, unionRect -from robofab.misc.bezierTools import calcCubicBounds, calcQuadraticBounds - - -__all__ = ["BoundsPen", "ControlBoundsPen"] - - -class ControlBoundsPen(BasePen): - - """Pen to calculate the "control bounds" of a shape. This is the - bounding box of all control points __on closed paths__, so may be larger than the - actual bounding box if there are curves that don't have points - on their extremes. - - Single points, or anchors, are ignored. - - When the shape has been drawn, the bounds are available as the - 'bounds' attribute of the pen object. It's a 4-tuple: - (xMin, yMin, xMax, yMax) - - This replaces fontTools/pens/boundsPen (temporarily?) - The fontTools bounds pen takes lose anchor points into account, - this one doesn't. - """ - - def __init__(self, glyphSet): - BasePen.__init__(self, glyphSet) - self.bounds = None - self._start = None - - def _moveTo(self, pt): - self._start = pt - - def _addMoveTo(self): - if self._start is None: - return - bounds = self.bounds - if bounds: - self.bounds = updateBounds(bounds, self._start) - else: - x, y = self._start - self.bounds = (x, y, x, y) - self._start = None - - def _lineTo(self, pt): - self._addMoveTo() - self.bounds = updateBounds(self.bounds, pt) - - def _curveToOne(self, bcp1, bcp2, pt): - self._addMoveTo() - bounds = self.bounds - bounds = updateBounds(bounds, bcp1) - bounds = updateBounds(bounds, bcp2) - bounds = updateBounds(bounds, pt) - self.bounds = bounds - - def _qCurveToOne(self, bcp, pt): - self._addMoveTo() - bounds = self.bounds - bounds = updateBounds(bounds, bcp) - bounds = updateBounds(bounds, pt) - self.bounds = bounds - - -class BoundsPen(ControlBoundsPen): - - """Pen to calculate the bounds of a shape. It calculates the - correct bounds even when the shape contains curves that don't - have points on their extremes. This is somewhat slower to compute - than the "control bounds". - - When the shape has been drawn, the bounds are available as the - 'bounds' attribute of the pen object. It's a 4-tuple: - (xMin, yMin, xMax, yMax) - """ - - def _curveToOne(self, bcp1, bcp2, pt): - self._addMoveTo() - bounds = self.bounds - bounds = updateBounds(bounds, pt) - if not pointInRect(bcp1, bounds) or not pointInRect(bcp2, bounds): - bounds = unionRect(bounds, calcCubicBounds( - self._getCurrentPoint(), bcp1, bcp2, pt)) - self.bounds = bounds - - def _qCurveToOne(self, bcp, pt): - self._addMoveTo() - bounds = self.bounds - bounds = updateBounds(bounds, pt) - if not pointInRect(bcp, bounds): - bounds = unionRect(bounds, calcQuadraticBounds( - self._getCurrentPoint(), bcp, pt)) - self.bounds = bounds - diff --git a/misc/pylib/robofab/pens/digestPen.py b/misc/pylib/robofab/pens/digestPen.py deleted file mode 100755 index 930daf468..000000000 --- a/misc/pylib/robofab/pens/digestPen.py +++ /dev/null @@ -1,106 +0,0 @@ -"""A couple of point pens which return the glyph as a list of basic values.""" - - -from robofab.pens.pointPen import AbstractPointPen - - -class DigestPointPen(AbstractPointPen): - - """Calculate a digest of all points - AND coordinates - AND components - in a glyph. - """ - - def __init__(self, ignoreSmoothAndName=False): - self._data = [] - self.ignoreSmoothAndName = ignoreSmoothAndName - - def beginPath(self): - self._data.append('beginPath') - - def endPath(self): - self._data.append('endPath') - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - if self.ignoreSmoothAndName: - self._data.append((pt, segmentType)) - else: - self._data.append((pt, segmentType, smooth, name)) - - def addComponent(self, baseGlyphName, transformation): - t = [] - for v in transformation: - if int(v) == v: - t.append(int(v)) - else: - t.append(v) - self._data.append((baseGlyphName, tuple(t))) - - def getDigest(self): - return tuple(self._data) - - def getDigestPointsOnly(self, needSort=True): - """ Return a tuple with all coordinates of all points, - but without smooth info or drawing instructions. - For instance if you want to compare 2 glyphs in shape, - but not interpolatability. - """ - points = [] - from types import TupleType - for item in self._data: - if type(item) == TupleType: - points.append(item[0]) - if needSort: - points.sort() - return tuple(points) - - -class DigestPointStructurePen(DigestPointPen): - - """Calculate a digest of the structure of the glyph - NOT coordinates - NOT values. - """ - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - self._data.append(segmentType) - - def addComponent(self, baseGlyphName, transformation): - self._data.append(baseGlyphName) - -if __name__ == "__main__": - """ - - beginPath - ((112, 651), 'line', False, None) - ((112, 55), 'line', False, None) - ((218, 55), 'line', False, None) - ((218, 651), 'line', False, None) - endPath - - """ - # a test - - from robofab.objects.objectsRF import RGlyph - - g = RGlyph() - p = g.getPen() - p.moveTo((112, 651)) - p.lineTo((112, 55)) - p.lineTo((218, 55)) - p.lineTo((218, 651)) - p.closePath() - - print g, len(g) - - digestPen = DigestPointPen() - g.drawPoints(digestPen) - - print - print "getDigest", digestPen.getDigest() - - print - print "getDigestPointsOnly", digestPen.getDigestPointsOnly() - -
\ No newline at end of file diff --git a/misc/pylib/robofab/pens/filterPen.py b/misc/pylib/robofab/pens/filterPen.py deleted file mode 100755 index f7c84c082..000000000 --- a/misc/pylib/robofab/pens/filterPen.py +++ /dev/null @@ -1,407 +0,0 @@ -"""A couple of point pens to filter contours in various ways.""" - -from fontTools.pens.basePen import AbstractPen, BasePen - -from robofab.pens.pointPen import AbstractPointPen -from robofab.objects.objectsRF import RGlyph as _RGlyph -from robofab.objects.objectsBase import _interpolatePt - -import math - -# -# threshold filtering -# - -def distance(pt1, pt2): - return math.hypot(pt1[0]-pt2[0], pt1[1]-pt2[1]) - -class ThresholdPointPen(AbstractPointPen): - - """ - Rewrite of the ThresholdPen as a PointPen - so that we can preserve named points and other arguments. - This pen will add components from the original glyph, but - but it won't filter those components. - - "move", "line", "curve" or "qcurve" - - """ - def __init__(self, otherPointPen, threshold=10): - self.threshold = threshold - self._lastPt = None - self._offCurveBuffer = [] - self.otherPointPen = otherPointPen - - def beginPath(self): - """Start a new sub path.""" - self.otherPointPen.beginPath() - self._lastPt = None - - def endPath(self): - """End the current sub path.""" - self.otherPointPen.endPath() - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - """Add a point to the current sub path.""" - if segmentType in ['curve', 'qcurve']: - # it's an offcurve, let's buffer them until we get another oncurve - # and we know what to do with them - self._offCurveBuffer.append((pt, segmentType, smooth, name, kwargs)) - return - - elif segmentType == "move": - # start of an open contour - self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs? - self._lastPt = pt - self._offCurveBuffer = [] - - elif segmentType == "line": - if self._lastPt is None: - self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs? - self._lastPt = pt - elif distance(pt, self._lastPt) >= self.threshold: - # we're oncurve and far enough from the last oncurve - if self._offCurveBuffer: - # empty any buffered offcurves - for buf_pt, buf_segmentType, buf_smooth, buf_name, buf_kwargs in self._offCurveBuffer: - self.otherPointPen.addPoint(buf_pt, buf_segmentType, buf_smooth, buf_name) # how to add kwargs? - self._offCurveBuffer = [] - # finally add the oncurve. - self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs? - self._lastPt = pt - else: - # we're too short, so we're not going to make it. - # we need to clear out the offcurve buffer. - self._offCurveBuffer = [] - - def addComponent(self, baseGlyphName, transformation): - """Add a sub glyph. Note: this way components are not filtered.""" - self.otherPointPen.addComponent(baseGlyphName, transformation) - - -class ThresholdPen(AbstractPen): - - """Removes segments shorter in length than the threshold value.""" - - def __init__(self, otherPen, threshold=10): - self.threshold = threshold - self._lastPt = None - self.otherPen = otherPen - - def moveTo(self, pt): - self._lastPt = pt - self.otherPen.moveTo(pt) - - def lineTo(self, pt, smooth=False): - if self.threshold <= distance(pt, self._lastPt): - self.otherPen.lineTo(pt) - self._lastPt = pt - - def curveTo(self, pt1, pt2, pt3): - if self.threshold <= distance(pt3, self._lastPt): - self.otherPen.curveTo(pt1, pt2, pt3) - self._lastPt = pt3 - - def qCurveTo(self, *points): - if self.threshold <= distance(points[-1], self._lastPt): - self.otherPen.qCurveTo(*points) - self._lastPt = points[-1] - - def closePath(self): - self.otherPen.closePath() - - def endPath(self): - self.otherPen.endPath() - - def addComponent(self, glyphName, transformation): - self.otherPen.addComponent(glyphName, transformation) - - -def thresholdGlyph(aGlyph, threshold=10): - """ Convenience function that handles the filtering. """ - from robofab.pens.adapterPens import PointToSegmentPen - new = _RGlyph() - filterpen = ThresholdPen(new.getPen(), threshold) - wrappedPen = PointToSegmentPen(filterpen) - aGlyph.drawPoints(wrappedPen) - aGlyph.clear() - aGlyph.appendGlyph(new) - aGlyph.update() - return aGlyph - -def thresholdGlyphPointPen(aGlyph, threshold=10): - """ Same a thresholdGlyph, but using the ThresholdPointPen, which should respect anchors.""" - from robofab.pens.adapterPens import PointToSegmentPen - new = _RGlyph() - wrappedPen = new.getPointPen() - filterpen = ThresholdPointPen(wrappedPen, threshold) - aGlyph.drawPoints(filterpen) - aGlyph.clear() - new.drawPoints(aGlyph.getPointPen()) - aGlyph.update() - return aGlyph - - -# -# Curve flattening -# - -def _estimateCubicCurveLength(pt0, pt1, pt2, pt3, precision=10): - """Estimate the length of this curve by iterating - through it and averaging the length of the flat bits. - """ - points = [] - length = 0 - step = 1.0/precision - factors = range(0, precision+1) - for i in factors: - points.append(_getCubicPoint(i*step, pt0, pt1, pt2, pt3)) - for i in range(len(points)-1): - pta = points[i] - ptb = points[i+1] - length += distance(pta, ptb) - return length - -def _mid((x0, y0), (x1, y1)): - """(Point, Point) -> Point\nReturn the point that lies in between the two input points.""" - return 0.5 * (x0 + x1), 0.5 * (y0 + y1) - -def _getCubicPoint(t, pt0, pt1, pt2, pt3): - if t == 0: - return pt0 - if t == 1: - return pt3 - if t == 0.5: - a = _mid(pt0, pt1) - b = _mid(pt1, pt2) - c = _mid(pt2, pt3) - d = _mid(a, b) - e = _mid(b, c) - return _mid(d, e) - else: - cx = (pt1[0] - pt0[0]) * 3 - cy = (pt1[1] - pt0[1]) * 3 - bx = (pt2[0] - pt1[0]) * 3 - cx - by = (pt2[1] - pt1[1]) * 3 - cy - ax = pt3[0] - pt0[0] - cx - bx - ay = pt3[1] - pt0[1] - cy - by - t3 = t ** 3 - t2 = t * t - x = ax * t3 + bx * t2 + cx * t + pt0[0] - y = ay * t3 + by * t2 + cy * t + pt0[1] - return x, y - - -class FlattenPen(BasePen): - - """Process the contours into a series of straight lines by flattening the curves. - """ - - def __init__(self, otherPen, approximateSegmentLength=5, segmentLines=False, filterDoubles=True): - self.approximateSegmentLength = approximateSegmentLength - BasePen.__init__(self, {}) - self.otherPen = otherPen - self.currentPt = None - self.firstPt = None - self.segmentLines = segmentLines - self.filterDoubles = filterDoubles - - def _moveTo(self, pt): - self.otherPen.moveTo(pt) - self.currentPt = pt - self.firstPt = pt - - def _lineTo(self, pt): - if self.filterDoubles: - if pt == self.currentPt: - return - if not self.segmentLines: - self.otherPen.lineTo(pt) - self.currentPt = pt - return - d = distance(self.currentPt, pt) - maxSteps = int(round(d / self.approximateSegmentLength)) - if maxSteps < 1: - self.otherPen.lineTo(pt) - self.currentPt = pt - return - step = 1.0/maxSteps - factors = range(0, maxSteps+1) - for i in factors[1:]: - self.otherPen.lineTo(_interpolatePt(self.currentPt, pt, i*step)) - self.currentPt = pt - - def _curveToOne(self, pt1, pt2, pt3): - est = _estimateCubicCurveLength(self.currentPt, pt1, pt2, pt3)/self.approximateSegmentLength - maxSteps = int(round(est)) - falseCurve = (pt1==self.currentPt) and (pt2==pt3) - if maxSteps < 1 or falseCurve: - self.otherPen.lineTo(pt3) - self.currentPt = pt3 - return - step = 1.0/maxSteps - factors = range(0, maxSteps+1) - for i in factors[1:]: - pt = _getCubicPoint(i*step, self.currentPt, pt1, pt2, pt3) - self.otherPen.lineTo(pt) - self.currentPt = pt3 - - def _closePath(self): - self.lineTo(self.firstPt) - self.otherPen.closePath() - self.currentPt = None - - def _endPath(self): - self.otherPen.endPath() - self.currentPt = None - - def addComponent(self, glyphName, transformation): - self.otherPen.addComponent(glyphName, transformation) - - -def flattenGlyph(aGlyph, threshold=10, segmentLines=True): - - """Replace curves with series of straight l ines.""" - - from robofab.pens.adapterPens import PointToSegmentPen - if len(aGlyph.contours) == 0: - return - new = _RGlyph() - writerPen = new.getPen() - filterpen = FlattenPen(writerPen, threshold, segmentLines) - wrappedPen = PointToSegmentPen(filterpen) - aGlyph.drawPoints(wrappedPen) - aGlyph.clear() - aGlyph.appendGlyph(new) - aGlyph.update() - return aGlyph - - -def spikeGlyph(aGlyph, segmentLength=20, spikeLength=40, patternFunc=None): - - """Add narly spikes or dents to the glyph. - patternFunc is an optional function which recalculates the offset.""" - - from math import atan2, sin, cos, pi - - new = _RGlyph() - new.appendGlyph(aGlyph) - new.width = aGlyph.width - - if len(new.contours) == 0: - return - flattenGlyph(new, segmentLength, segmentLines=True) - for contour in new: - l = len(contour.points) - lastAngle = None - for i in range(0, len(contour.points), 2): - prev = contour.points[i-1] - cur = contour.points[i] - next = contour.points[(i+1)%l] - angle = atan2(prev.x - next.x, prev.y - next.y) - lastAngle = angle - if patternFunc is not None: - thisSpikeLength = patternFunc(spikeLength) - else: - thisSpikeLength = spikeLength - cur.x -= sin(angle+.5*pi)*thisSpikeLength - cur.y -= cos(angle+.5*pi)*thisSpikeLength - new.update() - aGlyph.clear() - aGlyph.appendGlyph(new) - aGlyph.update() - return aGlyph - - -def halftoneGlyph(aGlyph, invert=False): - - """Convert the glyph into some sort of halftoning pattern. - Measure a bunch of inside/outside points to simulate grayscale levels. - Slow. - """ - print 'halftoneGlyph is running...' - grid = {} - drawing = {} - dataDistance = 10 - scan = 2 - preload = 0 - cellDistance = dataDistance * 5 - overshoot = dataDistance * 2 - (xMin, yMin, xMax, yMax) = aGlyph.box - for x in range(xMin-overshoot, xMax+overshoot, dataDistance): - print 'scanning..', x - for y in range(yMin-overshoot, yMax+overshoot, dataDistance): - if aGlyph.pointInside((x, y)): - grid[(x, y)] = True - else: - grid[(x, y)] = False - #print 'gathering data', x, y, grid[(x, y)] - print 'analyzing..' - for x in range(xMin-overshoot, xMax+overshoot, cellDistance): - for y in range(yMin-overshoot, yMax+overshoot, cellDistance): - total = preload - for scanx in range(-scan, scan): - for scany in range(-scan, scan): - if grid.get((x+scanx*dataDistance, y+scany*dataDistance)): - total += 1 - if invert: - drawing[(x, y)] = 2*scan**2 - float(total) - else: - drawing[(x, y)] = float(total) - aGlyph.clear() - print drawing - for (x,y) in drawing.keys(): - size = drawing[(x,y)] / float(2*scan**2) * 5 - pen = aGlyph.getPen() - pen.moveTo((x-size, y-size)) - pen.lineTo((x+size, y-size)) - pen.lineTo((x+size, y+size)) - pen.lineTo((x-size, y+size)) - pen.lineTo((x-size, y-size)) - pen.closePath() - aGlyph.update() - - -if __name__ == "__main__": - from robofab.pens.pointPen import PrintingPointPen - pp = PrintingPointPen() - #pp.beginPath() - #pp.addPoint((100, 100)) - #pp.endPath() - - tpp = ThresholdPointPen(pp, threshold=20) - tpp.beginPath() - #segmentType=None, smooth=False, name=None - tpp.addPoint((100, 100), segmentType="line", smooth=True) - # section that should be too small - tpp.addPoint((100, 102), segmentType="line", smooth=True) - tpp.addPoint((200, 200), segmentType="line", smooth=True) - # curve section with final point that's far enough, but with offcurves that are under the threshold - tpp.addPoint((200, 205), segmentType="curve", smooth=True) - tpp.addPoint((300, 295), segmentType="curve", smooth=True) - tpp.addPoint((300, 300), segmentType="line", smooth=True) - # curve section with final point that is not far enough - tpp.addPoint((550, 350), segmentType="curve", smooth=True) - tpp.addPoint((360, 760), segmentType="curve", smooth=True) - tpp.addPoint((310, 310), segmentType="line", smooth=True) - - tpp.addPoint((400, 400), segmentType="line", smooth=True) - tpp.addPoint((100, 100), segmentType="line", smooth=True) - tpp.endPath() - - # couple of single points with names - tpp.beginPath() - tpp.addPoint((500, 500), segmentType="move", smooth=True, name="named point") - tpp.addPoint((600, 500), segmentType="move", smooth=True, name="named point") - tpp.addPoint((601, 501), segmentType="move", smooth=True, name="named point") - tpp.endPath() - - # open path - tpp.beginPath() - tpp.addPoint((500, 500), segmentType="move", smooth=True) - tpp.addPoint((501, 500), segmentType="line", smooth=True) - tpp.addPoint((101, 500), segmentType="line", smooth=True) - tpp.addPoint((101, 100), segmentType="line", smooth=True) - tpp.addPoint((498, 498), segmentType="line", smooth=True) - tpp.endPath() -
\ No newline at end of file diff --git a/misc/pylib/robofab/pens/flPen.py b/misc/pylib/robofab/pens/flPen.py deleted file mode 100755 index d5a867cd9..000000000 --- a/misc/pylib/robofab/pens/flPen.py +++ /dev/null @@ -1,274 +0,0 @@ -"""Pens for creating glyphs in FontLab.""" - - -__all__ = ["FLPen", "FLPointPen", "drawFLGlyphOntoPointPen"] - - -from FL import * - -try: - from fl_cmd import * -except ImportError: - print "The fl_cmd module is not available here. flPen.py" - -from robofab.tools.toolsFL import NewGlyph -from robofab.pens.pointPen import AbstractPointPen -from robofab.pens.adapterPens import SegmentToPointPen - - -def roundInt(x): - return int(round(x)) - - -class FLPen(SegmentToPointPen): - - def __init__(self, glyph): - SegmentToPointPen.__init__(self, FLPointPen(glyph)) - - -class FLPointPen(AbstractPointPen): - - def __init__(self, glyph): - if hasattr(glyph, "isRobofab"): - self.glyph = glyph.naked() - else: - self.glyph = glyph - self.currentPath = None - - def beginPath(self): - self.currentPath = [] - - def endPath(self): - # Love is... abstracting away FL's madness. - path = self.currentPath - self.currentPath = None - glyph = self.glyph - if len(path) == 1 and path[0][3] is not None: - # Single point on the contour, it has a name. Make it an anchor. - x, y = path[0][0] - name = path[0][3] - anchor = Anchor(name, roundInt(x), roundInt(y)) - glyph.anchors.append(anchor) - return - firstOnCurveIndex = None - for i in range(len(path)): - if path[i][1] is not None: - firstOnCurveIndex = i - break - if firstOnCurveIndex is None: - # TT special case: on-curve-less contour. FL doesn't support that, - # so we insert an implied point at the end. - x1, y1 = path[0][0] - x2, y2 = path[-1][0] - impliedPoint = 0.5 * (x1 + x2), 0.5 * (y1 + y2) - path.append((impliedPoint, "qcurve", True, None)) - firstOnCurveIndex = 0 - path = path[firstOnCurveIndex + 1:] + path[:firstOnCurveIndex + 1] - firstPoint, segmentType, smooth, name = path[-1] - closed = True - if segmentType == "move": - path = path[:-1] - closed = False - # XXX The contour is not closed, but I can't figure out how to - # create an open contour in FL. Creating one by hand shows type"0x8011" - # for a move node in an open contour, but I'm not able to access - # that flag. - elif segmentType == "line": - # The contour is closed and ends in a lineto, which is redundant - # as it's implied by closepath. - path = path[:-1] - x, y = firstPoint - node = Node(nMOVE, Point(roundInt(x), roundInt(y))) - if smooth and closed: - if segmentType == "line" or path[0][1] == "line": - node.alignment = nFIXED - else: - node.alignment = nSMOOTH - glyph.Insert(node, len(glyph)) - segment = [] - nPoints = len(path) - for i in range(nPoints): - pt, segmentType, smooth, name = path[i] - segment.append(pt) - if segmentType is None: - continue - if segmentType == "curve": - if len(segment) < 2: - segmentType = "line" - elif len(segment) == 2: - segmentType = "qcurve" - if segmentType == "qcurve": - for x, y in segment[:-1]: - glyph.Insert(Node(nOFF, Point(roundInt(x), roundInt(y))), len(glyph)) - x, y = segment[-1] - node = Node(nLINE, Point(roundInt(x), roundInt(y))) - glyph.Insert(node, len(glyph)) - elif segmentType == "curve": - if len(segment) == 3: - cubicSegments = [segment] - else: - from fontTools.pens.basePen import decomposeSuperBezierSegment - cubicSegments = decomposeSuperBezierSegment(segment) - nSegments = len(cubicSegments) - for i in range(nSegments): - pt1, pt2, pt3 = cubicSegments[i] - x, y = pt3 - node = Node(nCURVE, Point(roundInt(x), roundInt(y))) - node.points[1].x, node.points[1].y = roundInt(pt1[0]), roundInt(pt1[1]) - node.points[2].x, node.points[2].y = roundInt(pt2[0]), roundInt(pt2[1]) - if i != nSegments - 1: - node.alignment = nSMOOTH - glyph.Insert(node, len(self.glyph)) - elif segmentType == "line": - assert len(segment) == 1, segment - x, y = segment[0] - node = Node(nLINE, Point(roundInt(x), roundInt(y))) - glyph.Insert(node, len(glyph)) - else: - assert 0, "unsupported curve type (%s)" % segmentType - if smooth: - if i + 1 < nPoints or closed: - # Can't use existing node, as you can't change node attributes - # AFTER it's been appended to the glyph. - node = glyph[-1] - if segmentType == "line" or path[(i+1) % nPoints][1] == "line": - # tangent - node.alignment = nFIXED - else: - # curve - node.alignment = nSMOOTH - segment = [] - if closed: - # we may have output a node too much - node = glyph[-1] - if node.type == nLINE and (node.x, node.y) == (roundInt(firstPoint[0]), roundInt(firstPoint[1])): - glyph.DeleteNode(len(glyph) - 1) - - def addPoint(self, pt, segmentType=None, smooth=None, name=None, **kwargs): - self.currentPath.append((pt, segmentType, smooth, name)) - - def addComponent(self, baseName, transformation): - assert self.currentPath is None - # make base glyph if needed, Component() needs the index - NewGlyph(self.glyph.parent, baseName, updateFont=False) - baseIndex = self.glyph.parent.FindGlyph(baseName) - if baseIndex == -1: - raise KeyError, "couldn't find or make base glyph" - xx, xy, yx, yy, dx, dy = transformation - # XXX warn when xy or yx != 0 - new = Component(baseIndex, Point(dx, dy), Point(xx, yy)) - self.glyph.components.append(new) - - -def drawFLGlyphOntoPointPen(flGlyph, pen): - """Draw a FontLab glyph onto a PointPen.""" - for anchor in flGlyph.anchors: - pen.beginPath() - pen.addPoint((anchor.x, anchor.y), name=anchor.name) - pen.endPath() - for contour in _getContours(flGlyph): - pen.beginPath() - for pt, segmentType, smooth in contour: - pen.addPoint(pt, segmentType=segmentType, smooth=smooth) - pen.endPath() - for baseGlyph, tranform in _getComponents(flGlyph): - pen.addComponent(baseGlyph, tranform) - - - -class FLPointContourPen(FLPointPen): - """Same as FLPointPen, except that it ignores components.""" - def addComponent(self, baseName, transformation): - pass - - -NODE_TYPES = {nMOVE: "move", nLINE: "line", nCURVE: "curve", - nOFF: None} - -def _getContours(glyph): - contours = [] - for i in range(len(glyph)): - node = glyph[i] - segmentType = NODE_TYPES[node.type] - if segmentType == "move": - contours.append([]) - for pt in node.points[1:]: - contours[-1].append(((pt.x, pt.y), None, False)) - smooth = node.alignment != nSHARP - contours[-1].append(((node.x, node.y), segmentType, smooth)) - - for contour in contours: - # filter out or change the move - movePt, segmentType, smooth = contour[0] - assert segmentType == "move" - lastSegmentType = contour[-1][1] - if movePt == contour[-1][0] and lastSegmentType == "curve": - contour[0] = contour[-1] - contour.pop() - elif lastSegmentType is None: - contour[0] = movePt, "qcurve", smooth - else: - assert lastSegmentType in ("line", "curve") - contour[0] = movePt, "line", smooth - - # change "line" to "qcurve" if appropriate - previousSegmentType = "ArbitraryValueOtherThanNone" - for i in range(len(contour)): - pt, segmentType, smooth = contour[i] - if segmentType == "line" and previousSegmentType is None: - contour[i] = pt, "qcurve", smooth - previousSegmentType = segmentType - - return contours - - -def _simplifyValues(*values): - """Given a set of numbers, convert items to ints if they are - integer float values, eg. 0.0, 1.0.""" - newValues = [] - for v in values: - i = int(v) - if v == i: - v = i - newValues.append(v) - return newValues - - -def _getComponents(glyph): - components = [] - for comp in glyph.components: - baseName = glyph.parent[comp.index].name - dx, dy = comp.delta.x, comp.delta.y - sx, sy = comp.scale.x, comp.scale.y - dx, dy, sx, sy = _simplifyValues(dx, dy, sx, sy) - components.append((baseName, (sx, 0, 0, sy, dx, dy))) - return components - - -def test(): - g = fl.glyph - g.Clear() - - p = PLPen(g) - p.moveTo((50, 50)) - p.lineTo((150,50)) - p.lineTo((170, 200), smooth=2) - p.curveTo((173, 225), (150, 250), (120, 250), smooth=1) - p.curveTo((85, 250), (50, 200), (50, 200)) - p.closePath() - - p.moveTo((300, 300)) - p.lineTo((400, 300)) - p.curveTo((450, 325), (450, 375), (400, 400)) - p.qCurveTo((400, 500), (350, 550), (300, 500), (300, 400)) - p.closePath() - p.setWidth(600) - p.setNote("Hello, this is a note") - p.addAnchor("top", (250, 600)) - - fl.UpdateGlyph(-1) - fl.UpdateFont(-1) - - -if __name__ == "__main__": - test() diff --git a/misc/pylib/robofab/pens/marginPen.py b/misc/pylib/robofab/pens/marginPen.py deleted file mode 100644 index 03f13f917..000000000 --- a/misc/pylib/robofab/pens/marginPen.py +++ /dev/null @@ -1,155 +0,0 @@ -from fontTools.pens.basePen import AbstractPen, BasePen -from robofab.misc.bezierTools import splitLine, splitCubic - - -from sets import Set - -class MarginPen(BasePen): - - """ - Pen to calculate the margins at a given value. - When isHorizontal is True, the margins at <value> are horizontal. - When isHorizontal is False, the margins at <value> are vertical. - - When a glyphset or font is given, MarginPen will also calculate for glyphs with components. - - pen.getMargins() returns the minimum and maximum intersections of the glyph. - pen.getContourMargins() returns the minimum and maximum intersections for each contour. - - - Possible optimisation: - Initialise the pen object with a list of points we want to measure, - then draw the glyph once, but do the splitLine() math for all measure points. - - """ - - def __init__(self, glyphSet, value, isHorizontal=True): - BasePen.__init__(self, glyphSet) - self.value = value - self.hits = {} - self.filterDoubles = True - self.contourIndex = None - self.startPt = None - self.isHorizontal = isHorizontal - - def _moveTo(self, pt): - self.currentPt = pt - self.startPt = pt - if self.contourIndex is None: - self.contourIndex = 0 - else: - self.contourIndex += 1 - - def _lineTo(self, pt): - if self.filterDoubles: - if pt == self.currentPt: - return - hits = splitLine(self.currentPt, pt, self.value, self.isHorizontal) - if len(hits)>1: - # result will be 2 tuples of 2 coordinates - # first two points: start to intersect - # second two points: intersect to end - # so, second point in first tuple is the intersect - # then, the first coordinate of that point is the x. - if not self.contourIndex in self.hits: - self.hits[self.contourIndex] = [] - if self.isHorizontal: - self.hits[self.contourIndex].append(round(hits[0][-1][0], 4)) - else: - self.hits[self.contourIndex].append(round(hits[0][-1][1], 4)) - if self.isHorizontal and pt[1] == self.value: - # it could happen - if not self.contourIndex in self.hits: - self.hits[self.contourIndex] = [] - self.hits[self.contourIndex].append(pt[0]) - elif (not self.isHorizontal) and (pt[0] == self.value): - # it could happen - if not self.contourIndex in self.hits: - self.hits[self.contourIndex] = [] - self.hits[self.contourIndex].append(pt[1]) - self.currentPt = pt - - def _curveToOne(self, pt1, pt2, pt3): - hits = splitCubic(self.currentPt, pt1, pt2, pt3, self.value, self.isHorizontal) - for i in range(len(hits)-1): - # a number of intersections is possible. Just take the - # last point of each segment. - if not self.contourIndex in self.hits: - self.hits[self.contourIndex] = [] - if self.isHorizontal: - self.hits[self.contourIndex].append(round(hits[i][-1][0], 4)) - else: - self.hits[self.contourIndex].append(round(hits[i][-1][1], 4)) - if self.isHorizontal and pt3[1] == self.value: - # it could happen - if not self.contourIndex in self.hits: - self.hits[self.contourIndex] = [] - self.hits[self.contourIndex].append(pt3[0]) - if (not self.isHorizontal) and (pt3[0] == self.value): - # it could happen - if not self.contourIndex in self.hits: - self.hits[self.contourIndex] = [] - self.hits[self.contourIndex].append(pt3[1]) - self.currentPt = pt3 - - def _closePath(self): - if self.currentPt != self.startPt: - self._lineTo(self.startPt) - self.currentPt = self.startPt = None - - def _endPath(self): - self.currentPt = None - - def addComponent(self, baseGlyph, transformation): - from fontTools.pens.transformPen import TransformPen - if self.glyphSet is None: - return - if baseGlyph in self.glyphSet: - glyph = self.glyphSet[baseGlyph] - if glyph is None: - return - tPen = TransformPen(self, transformation) - glyph.draw(tPen) - - def getMargins(self): - """Get the horizontal margins for all contours combined, i.e. the whole glyph.""" - allHits = [] - for index, pts in self.hits.items(): - allHits.extend(pts) - if allHits: - return min(allHits), max(allHits) - return None - - def getContourMargins(self): - """Get the horizontal margins for each contour.""" - allHits = {} - for index, pts in self.hits.items(): - unique = list(Set(pts)) - unique.sort() - allHits[index] = unique - return allHits - - def getAll(self): - """Get all the slices.""" - allHits = [] - for index, pts in self.hits.items(): - allHits.extend(pts) - unique = list(Set(allHits)) - unique = list(unique) - unique.sort() - return unique - - -if __name__ == "__main__": - - from robofab.world import CurrentGlyph, CurrentFont - f = CurrentFont() - g = CurrentGlyph() - - pt = (74, 216) - - pen = MarginPen(f, pt[1], isHorizontal=False) - g.draw(pen) - print 'glyph Y margins', pen.getMargins() - print pen.getContourMargins() - diff --git a/misc/pylib/robofab/pens/mathPens.py b/misc/pylib/robofab/pens/mathPens.py deleted file mode 100755 index 1fe1026e1..000000000 --- a/misc/pylib/robofab/pens/mathPens.py +++ /dev/null @@ -1,185 +0,0 @@ -"""Some pens that are needed during glyph math""" - - -from robofab.pens.pointPen import BasePointToSegmentPen, AbstractPointPen - - -class GetMathDataPointPen(AbstractPointPen): - - """ - Point pen that converts all "line" segments into - curve segments containing two off curve points. - """ - - def __init__(self): - self.contours = [] - self.components = [] - self.anchors = [] - self._points = [] - - def _flushContour(self): - points = self._points - if len(points) == 1: - segmentType, pt, smooth, name = points[0] - self.anchors.append((pt, name)) - else: - self.contours.append([]) - prevOnCurve = None - offCurves = [] - # deal with the first point - segmentType, pt, smooth, name = points[0] - # if it is an offcurve, add it to the offcurve list - if segmentType is None: - offCurves.append((segmentType, pt, smooth, name)) - # if it is a line, change the type to curve and add it to the contour - # create offcurves corresponding with the last oncurve and - # this point and add them to the points list - elif segmentType == "line": - prevOnCurve = pt - self.contours[-1].append(("curve", pt, False, name)) - lastPoint = points[-1][1] - points.append((None, lastPoint, False, None)) - points.append((None, pt, False, None)) - # a move, curve or qcurve. simple append the data. - else: - self.contours[-1].append((segmentType, pt, smooth, name)) - prevOnCurve = pt - # now go through the rest of the points - for segmentType, pt, smooth, name in points[1:]: - # store the off curves - if segmentType is None: - offCurves.append((segmentType, pt, smooth, name)) - continue - # make off curve corresponding the the previous - # on curve an dthis point - if segmentType == "line": - segmentType = "curve" - offCurves.append((None, prevOnCurve, False, None)) - offCurves.append((None, pt, False, None)) - # add the offcurves to the contour - for offCurve in offCurves: - self.contours[-1].append(offCurve) - # add the oncurve to the contour - self.contours[-1].append((segmentType, pt, smooth, name)) - # reset the stored data - prevOnCurve = pt - offCurves = [] - # catch offcurves that belong to the first - if len(offCurves) != 0: - self.contours[-1].extend(offCurves) - - def beginPath(self): - self._points = [] - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - self._points.append((segmentType, pt, smooth, name)) - - def endPath(self): - self._flushContour() - - def addComponent(self, baseGlyphName, transformation): - self.components.append((baseGlyphName, transformation)) - - def getData(self): - return { - 'contours':list(self.contours), - 'components':list(self.components), - 'anchors':list(self.anchors) - } - - -class CurveSegmentFilterPointPen(AbstractPointPen): - - """ - Point pen that turns curve segments that contain - unnecessary anchor points into line segments. - """ - # XXX it would be great if this also checked to see if the - # off curves are on the segment and therefre unneeded - - def __init__(self, anotherPointPen): - self._pen = anotherPointPen - self._points = [] - - def _flushContour(self): - points = self._points - # an anchor - if len(points) == 1: - pt, segmentType, smooth, name = points[0] - self._pen.addPoint(pt, segmentType, smooth, name) - else: - prevOnCurve = None - offCurves = [] - - pointsToDraw = [] - - # deal with the first point - pt, segmentType, smooth, name = points[0] - # if it is an offcurve, add it to the offcurve list - if segmentType is None: - offCurves.append((pt, segmentType, smooth, name)) - else: - # potential redundancy - if segmentType == "curve": - # gather preceding off curves - testOffCurves = [] - lastPoint = None - for i in xrange(len(points)): - i = -i - 1 - testPoint = points[i] - testSegmentType = testPoint[1] - if testSegmentType is not None: - lastPoint = testPoint[0] - break - testOffCurves.append(testPoint[0]) - # if two offcurves exist we can test for redundancy - if len(testOffCurves) == 2: - if testOffCurves[1] == lastPoint and testOffCurves[0] == pt: - segmentType = "line" - # remove the last two points - points = points[:-2] - # add the point to the contour - pointsToDraw.append((pt, segmentType, smooth, name)) - prevOnCurve = pt - for pt, segmentType, smooth, name in points[1:]: - # store offcurves - if segmentType is None: - offCurves.append((pt, segmentType, smooth, name)) - continue - # curves are a potential redundancy - elif segmentType == "curve": - if len(offCurves) == 2: - # test for redundancy - if offCurves[0][0] == prevOnCurve and offCurves[1][0] == pt: - offCurves = [] - segmentType = "line" - # add all offcurves - for offCurve in offCurves: - pointsToDraw.append(offCurve) - # add the on curve - pointsToDraw.append((pt, segmentType, smooth, name)) - # reset the stored data - prevOnCurve = pt - offCurves = [] - # catch any remaining offcurves - if len(offCurves) != 0: - for offCurve in offCurves: - pointsToDraw.append(offCurve) - # draw to the pen - for pt, segmentType, smooth, name in pointsToDraw: - self._pen.addPoint(pt, segmentType, smooth, name) - - def beginPath(self): - self._points = [] - self._pen.beginPath() - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - self._points.append((pt, segmentType, smooth, name)) - - def endPath(self): - self._flushContour() - self._pen.endPath() - - def addComponent(self, baseGlyphName, transformation): - self._pen.addComponent(baseGlyphName, transformation) - diff --git a/misc/pylib/robofab/pens/pointPen.py b/misc/pylib/robofab/pens/pointPen.py deleted file mode 100644 index dc81df38e..000000000 --- a/misc/pylib/robofab/pens/pointPen.py +++ /dev/null @@ -1,173 +0,0 @@ -__all__ = ["AbstractPointPen", "BasePointToSegmentPen", "PrintingPointPen", - "PrintingSegmentPen", "SegmentPrintingPointPen"] - - -class AbstractPointPen: - - def beginPath(self): - """Start a new sub path.""" - raise NotImplementedError - - def endPath(self): - """End the current sub path.""" - raise NotImplementedError - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - """Add a point to the current sub path.""" - raise NotImplementedError - - def addComponent(self, baseGlyphName, transformation): - """Add a sub glyph.""" - raise NotImplementedError - - -class BasePointToSegmentPen(AbstractPointPen): - - """Base class for retrieving the outline in a segment-oriented - way. The PointPen protocol is simple yet also a little tricky, - so when you need an outline presented as segments but you have - as points, do use this base implementation as it properly takes - care of all the edge cases. - """ - - def __init__(self): - self.currentPath = None - - def beginPath(self): - assert self.currentPath is None - self.currentPath = [] - - def _flushContour(self, segments): - """Override this method. - - It will be called for each non-empty sub path with a list - of segments: the 'segments' argument. - - The segments list contains tuples of length 2: - (segmentType, points) - - segmentType is one of "move", "line", "curve" or "qcurve". - "move" may only occur as the first segment, and it signifies - an OPEN path. A CLOSED path does NOT start with a "move", in - fact it will not contain a "move" at ALL. - - The 'points' field in the 2-tuple is a list of point info - tuples. The list has 1 or more items, a point tuple has - four items: - (point, smooth, name, kwargs) - 'point' is an (x, y) coordinate pair. - - For a closed path, the initial moveTo point is defined as - the last point of the last segment. - - The 'points' list of "move" and "line" segments always contains - exactly one point tuple. - """ - raise NotImplementedError - - def endPath(self): - assert self.currentPath is not None - points = self.currentPath - self.currentPath = None - if not points: - return - if len(points) == 1: - # Not much more we can do than output a single move segment. - pt, segmentType, smooth, name, kwargs = points[0] - segments = [("move", [(pt, smooth, name, kwargs)])] - self._flushContour(segments) - return - segments = [] - if points[0][1] == "move": - # It's an open contour, insert a "move" segment for the first - # point and remove that first point from the point list. - pt, segmentType, smooth, name, kwargs = points[0] - segments.append(("move", [(pt, smooth, name, kwargs)])) - points.pop(0) - else: - # It's a closed contour. Locate the first on-curve point, and - # rotate the point list so that it _ends_ with an on-curve - # point. - firstOnCurve = None - for i in range(len(points)): - segmentType = points[i][1] - if segmentType is not None: - firstOnCurve = i - break - if firstOnCurve is None: - # Special case for quadratics: a contour with no on-curve - # points. Add a "None" point. (See also the Pen protocol's - # qCurveTo() method and fontTools.pens.basePen.py.) - points.append((None, "qcurve", None, None, None)) - else: - points = points[firstOnCurve+1:] + points[:firstOnCurve+1] - - currentSegment = [] - for pt, segmentType, smooth, name, kwargs in points: - currentSegment.append((pt, smooth, name, kwargs)) - if segmentType is None: - continue - segments.append((segmentType, currentSegment)) - currentSegment = [] - - self._flushContour(segments) - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - self.currentPath.append((pt, segmentType, smooth, name, kwargs)) - - -class PrintingPointPen(AbstractPointPen): - def __init__(self): - self.havePath = False - def beginPath(self): - self.havePath = True - print "pen.beginPath()" - def endPath(self): - self.havePath = False - print "pen.endPath()" - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - assert self.havePath - args = ["(%s, %s)" % (pt[0], pt[1])] - if segmentType is not None: - args.append("segmentType=%r" % segmentType) - if smooth: - args.append("smooth=True") - if name is not None: - args.append("name=%r" % name) - if kwargs: - args.append("**%s" % kwargs) - print "pen.addPoint(%s)" % ", ".join(args) - def addComponent(self, baseGlyphName, transformation): - assert not self.havePath - print "pen.addComponent(%r, %s)" % (baseGlyphName, tuple(transformation)) - - -from fontTools.pens.basePen import AbstractPen - -class PrintingSegmentPen(AbstractPen): - def moveTo(self, pt): - print "pen.moveTo(%s)" % (pt,) - def lineTo(self, pt): - print "pen.lineTo(%s)" % (pt,) - def curveTo(self, *pts): - print "pen.curveTo%s" % (pts,) - def qCurveTo(self, *pts): - print "pen.qCurveTo%s" % (pts,) - def closePath(self): - print "pen.closePath()" - def endPath(self): - print "pen.endPath()" - def addComponent(self, baseGlyphName, transformation): - print "pen.addComponent(%r, %s)" % (baseGlyphName, tuple(transformation)) - - -class SegmentPrintingPointPen(BasePointToSegmentPen): - def _flushContour(self, segments): - from pprint import pprint - pprint(segments) - - -if __name__ == "__main__": - p = SegmentPrintingPointPen() - from robofab.test.test_pens import TestShapes - TestShapes.onCurveLessQuadShape(p) diff --git a/misc/pylib/robofab/pens/quartzPen.py b/misc/pylib/robofab/pens/quartzPen.py deleted file mode 100644 index dd1947ccf..000000000 --- a/misc/pylib/robofab/pens/quartzPen.py +++ /dev/null @@ -1,21 +0,0 @@ -from fontTools.pens.basePen import BasePen - -class QuartzPen(BasePen): - - """Pen to draw onto a Quartz drawing context (Carbon.CG).""" - - def __init__(self, glyphSet, quartzContext): - BasePen.__init__(self, glyphSet) - self._context = quartzContext - - def _moveTo(self, (x, y)): - self._context.CGContextMoveToPoint(x, y) - - def _lineTo(self, (x, y)): - self._context.CGContextAddLineToPoint(x, y) - - def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)): - self._context.CGContextAddCurveToPoint(x1, y1, x2, y2, x3, y3) - - def _closePath(self): - self._context.closePath() diff --git a/misc/pylib/robofab/pens/reverseContourPointPen.py b/misc/pylib/robofab/pens/reverseContourPointPen.py deleted file mode 100755 index 8ce001b4d..000000000 --- a/misc/pylib/robofab/pens/reverseContourPointPen.py +++ /dev/null @@ -1,125 +0,0 @@ -"""PointPen for reversing the winding direction of contours.""" - - -__all__ = ["ReverseContourPointPen"] - - -from robofab.pens.pointPen import AbstractPointPen - - -class ReverseContourPointPen(AbstractPointPen): - - """This is a PointPen that passes outline data to another PointPen, but - reversing the winding direction of all contours. Components are simply - passed through unchanged. - - Closed contours are reversed in such a way that the first point remains - the first point. - """ - - def __init__(self, outputPointPen): - self.pen = outputPointPen - self.currentContour = None # a place to store the points for the current sub path - - def _flushContour(self): - pen = self.pen - contour = self.currentContour - if not contour: - pen.beginPath() - pen.endPath() - return - - closed = contour[0][1] != "move" - if not closed: - lastSegmentType = "move" - else: - # Remove the first point and insert it at the end. When - # the list of points gets reversed, this point will then - # again be at the start. In other words, the following - # will hold: - # for N in range(len(originalContour)): - # originalContour[N] == reversedContour[-N] - contour.append(contour.pop(0)) - # Find the first on-curve point. - firstOnCurve = None - for i in range(len(contour)): - if contour[i][1] is not None: - firstOnCurve = i - break - if firstOnCurve is None: - # There are no on-curve points, be basically have to - # do nothing but contour.reverse(). - lastSegmentType = None - else: - lastSegmentType = contour[firstOnCurve][1] - - contour.reverse() - if not closed: - # Open paths must start with a move, so we simply dump - # all off-curve points leading up to the first on-curve. - while contour[0][1] is None: - contour.pop(0) - pen.beginPath() - for pt, nextSegmentType, smooth, name in contour: - if nextSegmentType is not None: - segmentType = lastSegmentType - lastSegmentType = nextSegmentType - else: - segmentType = None - pen.addPoint(pt, segmentType=segmentType, smooth=smooth, name=name) - pen.endPath() - - def beginPath(self): - assert self.currentContour is None - self.currentContour = [] - self.onCurve = [] - - def endPath(self): - assert self.currentContour is not None - self._flushContour() - self.currentContour = None - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - self.currentContour.append((pt, segmentType, smooth, name)) - - def addComponent(self, glyphName, transform): - assert self.currentContour is None - self.pen.addComponent(glyphName, transform) - - -if __name__ == "__main__": - from robofab.pens.pointPen import PrintingPointPen - pP = PrintingPointPen() - rP = ReverseContourPointPen(pP) - - rP.beginPath() - rP.addPoint((339, -8), "curve") - rP.addPoint((502, -8)) - rP.addPoint((635, 65)) - rP.addPoint((635, 305), "curve") - rP.addPoint((635, 545)) - rP.addPoint((504, 623)) - rP.addPoint((340, 623), "curve") - rP.addPoint((177, 623)) - rP.addPoint((43, 545)) - rP.addPoint((43, 305), "curve") - rP.addPoint((43, 65)) - rP.addPoint((176, -8)) - rP.endPath() - - rP.beginPath() - rP.addPoint((100, 100), "move", smooth=False, name='a') - rP.addPoint((150, 150)) - rP.addPoint((200, 200)) - rP.addPoint((250, 250), "curve", smooth=True, name='b') - rP.addPoint((300, 300), "line", smooth=False, name='c') - rP.addPoint((350, 350)) - rP.addPoint((400, 400)) - rP.addPoint((450, 450)) - rP.addPoint((500, 500), "curve", smooth=True, name='d') - rP.addPoint((550, 550)) - rP.addPoint((600, 600)) - rP.addPoint((650, 650)) - rP.addPoint((700, 700)) - rP.addPoint((750, 750), "qcurve", smooth=False, name='e') - rP.endPath() diff --git a/misc/pylib/robofab/pens/rfUFOPen.pyx b/misc/pylib/robofab/pens/rfUFOPen.pyx deleted file mode 100755 index 265d7aea0..000000000 --- a/misc/pylib/robofab/pens/rfUFOPen.pyx +++ /dev/null @@ -1,103 +0,0 @@ -"""Pens for creating UFO glyphs.""" - -from robofab.objects.objectsBase import MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE -from robofab.objects.objectsRF import RContour, RSegment, RPoint -from robofab.pens.pointPen import BasePointToSegmentPen -from robofab.pens.adapterPens import SegmentToPointPen - - -class RFUFOPen(SegmentToPointPen): - - def __init__(self, glyph): - SegmentToPointPen.__init__(self, RFUFOPointPen(glyph)) - - -class RFUFOPointPen(BasePointToSegmentPen): - - """Point pen for building objectsRF glyphs""" - - def __init__(self, glyph): - BasePointToSegmentPen.__init__(self) - self.glyph = glyph - - def _flushContour(self, segments): - # - # adapted from robofab.pens.adapterPens.PointToSegmentPen - # - assert len(segments) >= 1 - # if we only have one point and it has a name, we must have an anchor - first = segments[0] - segmentType, points = first - pt, smooth, name, kwargs = points[0] - if len(segments) == 1 and name != None: - self.glyph.appendAnchor(name, pt) - return - # we must have a contour - contour = RContour() - contour.setParent(self.glyph) - if segments[0][0] == "move": - # It's an open path. - closed = False - points = segments[0][1] - assert len(points) == 1 - movePt, smooth, name, kwargs = points[0] - del segments[0] - else: - # It's a closed path, do a moveTo to the last - # point of the last segment. only if it isn't a qcurve - closed = True - segmentType, points = segments[-1] - movePt, smooth, name, kwargs = points[-1] - ## THIS IS STILL UNDECIDED!!! - # since objectsRF currently follows the FL model of not - # allowing open contours, remove the last segment - # since it is being replaced by a move - if segmentType == 'line': - del segments[-1] - # construct a move segment and apply it to the contour if we aren't dealing with a qcurve - segment = RSegment() - segment.setParent(contour) - segment.smooth = smooth - rPoint = RPoint(x=movePt[0], y=movePt[1], pointType=MOVE, name=name) - rPoint.setParent(segment) - segment.points = [rPoint] - contour.segments.append(segment) - # do the rest of the segments - for segmentType, points in segments: - points = [(pt, name) for pt, smooth, name, kwargs in points] - if segmentType == "line": - assert len(points) == 1 - sType = LINE - elif segmentType == "curve": - sType = CURVE - elif segmentType == "qcurve": - sType = QCURVE - else: - assert 0, "illegal segmentType: %s" % segmentType - segment = RSegment() - segment.setParent(contour) - segment.smooth = smooth - rPoints = [] - # handle the offCurves - for point in points[:-1]: - point, name = point - rPoint = RPoint(x=point[0], y=point[1], pointType=OFFCURVE, name=name) - rPoint.setParent(segment) - rPoints.append(rPoint) - # now the onCurve - point, name = points[-1] - rPoint = RPoint(x=point[0], y=point[1], pointType=sType, name=name) - rPoint.setParent(segment) - rPoints.append(rPoint) - # apply them to the segment - segment.points = rPoints - contour.segments.append(segment) - if contour.segments[-1].type == "curve": - contour.segments[-1].points[-1].name = None - self.glyph.contours.append(contour) - - def addComponent(self, glyphName, transform): - xx, xy, yx, yy, dx, dy = transform - self.glyph.appendComponent(baseGlyph=glyphName, offset=(dx, dy), scale=(xx, yy)) - - diff --git a/misc/pylib/robofab/plistFromTree.py b/misc/pylib/robofab/plistFromTree.py deleted file mode 100755 index 5bf608467..000000000 --- a/misc/pylib/robofab/plistFromTree.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Small helper module to parse Plist-formatted data from trees as created -by xmlTreeBuilder. -""" - - -__all__ = "readPlistFromTree" - - -from plistlib import PlistParser - - -def readPlistFromTree(tree): - """Given a (sub)tree created by xmlTreeBuilder, interpret it - as Plist-formatted data, and return the root object. - """ - parser = PlistTreeParser() - return parser.parseTree(tree) - - -class PlistTreeParser(PlistParser): - - def parseTree(self, tree): - element, attributes, children = tree - self.parseElement(element, attributes, children) - return self.root - - def parseElement(self, element, attributes, children): - self.handleBeginElement(element, attributes) - for child in children: - if isinstance(child, tuple): - self.parseElement(child[0], child[1], child[2]) - else: - if not isinstance(child, unicode): - # ugh, xmlTreeBuilder returns utf-8 :-( - child = unicode(child, "utf-8") - self.handleData(child) - self.handleEndElement(element) - - -if __name__ == "__main__": - from xmlTreeBuilder import buildTree - tree = buildTree("xxx.plist", stripData=0) - print readPlistFromTree(tree) diff --git a/misc/pylib/robofab/plistlib.py b/misc/pylib/robofab/plistlib.py deleted file mode 100755 index 282b7ea3e..000000000 --- a/misc/pylib/robofab/plistlib.py +++ /dev/null @@ -1,495 +0,0 @@ -"""plistlib.py -- a tool to generate and parse MacOSX .plist files. - -The PropertList (.plist) file format is a simple XML pickle supporting -basic object types, like dictionaries, lists, numbers and strings. -Usually the top level object is a dictionary. - -To write out a plist file, use the writePlist(rootObject, pathOrFile) -function. 'rootObject' is the top level object, 'pathOrFile' is a -filename or a (writable) file object. - -To parse a plist from a file, use the readPlist(pathOrFile) function, -with a file name or a (readable) file object as the only argument. It -returns the top level object (again, usually a dictionary). - -To work with plist data in strings, you can use readPlistFromString() -and writePlistToString(). - -Values can be strings, integers, floats, booleans, tuples, lists, -dictionaries, Data or datetime.datetime objects. String values (including -dictionary keys) may be unicode strings -- they will be written out as -UTF-8. - -The <data> plist type is supported through the Data class. This is a -thin wrapper around a Python string. - -Generate Plist example: - - pl = dict( - aString="Doodah", - aList=["A", "B", 12, 32.1, [1, 2, 3]], - aFloat = 0.1, - anInt = 728, - aDict=dict( - anotherString="<hello & hi there!>", - aUnicodeValue=u'M\xe4ssig, Ma\xdf', - aTrueValue=True, - aFalseValue=False, - ), - someData = Data("<binary gunk>"), - someMoreData = Data("<lots of binary gunk>" * 10), - aDate = datetime.fromtimestamp(time.mktime(time.gmtime())), - ) - # unicode keys are possible, but a little awkward to use: - pl[u'\xc5benraa'] = "That was a unicode key." - writePlist(pl, fileName) - -Parse Plist example: - - pl = readPlist(pathOrFile) - print pl["aKey"] -""" - - -__all__ = [ - "readPlist", "writePlist", "readPlistFromString", "writePlistToString", - "readPlistFromResource", "writePlistToResource", - "Plist", "Data", "Dict" -] -# Note: the Plist and Dict classes have been deprecated. - -import binascii -from cStringIO import StringIO -import re -try: - from datetime import datetime -except ImportError: - # We're running on Python < 2.3, we don't support dates here, - # yet we provide a stub class so type dispatching works. - class datetime(object): - def __init__(self, *args, **kwargs): - raise ValueError("datetime is not supported") - - -def readPlist(pathOrFile): - """Read a .plist file. 'pathOrFile' may either be a file name or a - (readable) file object. Return the unpacked root object (which - usually is a dictionary). - """ - didOpen = 0 - if isinstance(pathOrFile, (str, unicode)): - pathOrFile = open(pathOrFile) - didOpen = 1 - p = PlistParser() - rootObject = p.parse(pathOrFile) - if didOpen: - pathOrFile.close() - return rootObject - - -def writePlist(rootObject, pathOrFile): - """Write 'rootObject' to a .plist file. 'pathOrFile' may either be a - file name or a (writable) file object. - """ - didOpen = 0 - if isinstance(pathOrFile, (str, unicode)): - pathOrFile = open(pathOrFile, "w") - didOpen = 1 - writer = PlistWriter(pathOrFile) - writer.writeln("<plist version=\"1.0\">") - writer.writeValue(rootObject) - writer.writeln("</plist>") - if didOpen: - pathOrFile.close() - - -def readPlistFromString(data): - """Read a plist data from a string. Return the root object. - """ - return readPlist(StringIO(data)) - - -def writePlistToString(rootObject): - """Return 'rootObject' as a plist-formatted string. - """ - f = StringIO() - writePlist(rootObject, f) - return f.getvalue() - - -def readPlistFromResource(path, restype='plst', resid=0): - """Read plst resource from the resource fork of path. - """ - from Carbon.File import FSRef, FSGetResourceForkName - from Carbon.Files import fsRdPerm - from Carbon import Res - fsRef = FSRef(path) - resNum = Res.FSOpenResourceFile(fsRef, FSGetResourceForkName(), fsRdPerm) - Res.UseResFile(resNum) - plistData = Res.Get1Resource(restype, resid).data - Res.CloseResFile(resNum) - return readPlistFromString(plistData) - - -def writePlistToResource(rootObject, path, restype='plst', resid=0): - """Write 'rootObject' as a plst resource to the resource fork of path. - """ - from Carbon.File import FSRef, FSGetResourceForkName - from Carbon.Files import fsRdWrPerm - from Carbon import Res - plistData = writePlistToString(rootObject) - fsRef = FSRef(path) - resNum = Res.FSOpenResourceFile(fsRef, FSGetResourceForkName(), fsRdWrPerm) - Res.UseResFile(resNum) - try: - Res.Get1Resource(restype, resid).RemoveResource() - except Res.Error: - pass - res = Res.Resource(plistData) - res.AddResource(restype, resid, '') - res.WriteResource() - Res.CloseResFile(resNum) - - -class DumbXMLWriter: - - def __init__(self, file, indentLevel=0, indent="\t"): - self.file = file - self.stack = [] - self.indentLevel = indentLevel - self.indent = indent - - def beginElement(self, element): - self.stack.append(element) - self.writeln("<%s>" % element) - self.indentLevel += 1 - - def endElement(self, element): - assert self.indentLevel > 0 - assert self.stack.pop() == element - self.indentLevel -= 1 - self.writeln("</%s>" % element) - - def simpleElement(self, element, value=None): - if value is not None: - value = _escapeAndEncode(value) - self.writeln("<%s>%s</%s>" % (element, value, element)) - else: - self.writeln("<%s/>" % element) - - def writeln(self, line): - if line: - self.file.write(self.indentLevel * self.indent + line + "\n") - else: - self.file.write("\n") - - -# Contents should conform to a subset of ISO 8601 -# (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. Smaller units may be omitted with -# a loss of precision) -_dateParser = re.compile(r"(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d)(?::(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?)?Z") - -def _dateFromString(s): - order = ('year', 'month', 'day', 'hour', 'minute', 'second') - gd = _dateParser.match(s).groupdict() - lst = [] - for key in order: - val = gd[key] - if val is None: - break - lst.append(int(val)) - return datetime(*lst) - -def _dateToString(d): - return '%04d-%02d-%02dT%02d:%02d:%02dZ' % ( - d.year, d.month, d.day, - d.hour, d.minute, d.second - ) - - -# Regex to find any control chars, except for \t \n and \r -_controlCharPat = re.compile( - r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f" - r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]") - -def _escapeAndEncode(text): - m = _controlCharPat.search(text) - if m is not None: - raise ValueError("strings can't contains control characters; " - "use plistlib.Data instead") - text = text.replace("\r\n", "\n") # convert DOS line endings - text = text.replace("\r", "\n") # convert Mac line endings - text = text.replace("&", "&") # escape '&' - text = text.replace("<", "<") # escape '<' - text = text.replace(">", ">") # escape '>' - return text.encode("utf-8") # encode as UTF-8 - - -PLISTHEADER = """\ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -""" - -class PlistWriter(DumbXMLWriter): - - def __init__(self, file, indentLevel=0, indent="\t", writeHeader=1): - if writeHeader: - file.write(PLISTHEADER) - DumbXMLWriter.__init__(self, file, indentLevel, indent) - - def writeValue(self, value): - if isinstance(value, (str, unicode)): - self.simpleElement("string", value) - elif isinstance(value, bool): - # must switch for bool before int, as bool is a - # subclass of int... - if value: - self.simpleElement("true") - else: - self.simpleElement("false") - elif isinstance(value, (int, long)): - self.simpleElement("integer", "%d" % value) - elif isinstance(value, float): - self.simpleElement("real", repr(value)) - elif isinstance(value, dict): - self.writeDict(value) - elif isinstance(value, Data): - self.writeData(value) - elif isinstance(value, datetime): - self.simpleElement("date", _dateToString(value)) - elif isinstance(value, (tuple, list)): - self.writeArray(value) - else: - raise TypeError("unsuported type: %s" % type(value)) - - def writeData(self, data): - self.beginElement("data") - self.indentLevel -= 1 - maxlinelength = 76 - len(self.indent.replace("\t", " " * 8) * - self.indentLevel) - for line in data.asBase64(maxlinelength).split("\n"): - if line: - self.writeln(line) - self.indentLevel += 1 - self.endElement("data") - - def writeDict(self, d): - self.beginElement("dict") - items = d.items() - items.sort() - for key, value in items: - if not isinstance(key, (str, unicode)): - raise TypeError("keys must be strings") - self.simpleElement("key", key) - self.writeValue(value) - self.endElement("dict") - - def writeArray(self, array): - self.beginElement("array") - for value in array: - self.writeValue(value) - self.endElement("array") - - -class _InternalDict(dict): - - # This class is needed while Dict is scheduled for deprecation: - # we only need to warn when a *user* instantiates Dict or when - # the "attribute notation for dict keys" is used. - - def __getattr__(self, attr): - try: - value = self[attr] - except KeyError: - raise AttributeError, attr - from warnings import warn - warn("Attribute access from plist dicts is deprecated, use d[key] " - "notation instead", PendingDeprecationWarning) - return value - - def __setattr__(self, attr, value): - from warnings import warn - warn("Attribute access from plist dicts is deprecated, use d[key] " - "notation instead", PendingDeprecationWarning) - self[attr] = value - - def __delattr__(self, attr): - try: - del self[attr] - except KeyError: - raise AttributeError, attr - from warnings import warn - warn("Attribute access from plist dicts is deprecated, use d[key] " - "notation instead", PendingDeprecationWarning) - -class Dict(_InternalDict): - - def __init__(self, **kwargs): - from warnings import warn - warn("The plistlib.Dict class is deprecated, use builtin dict instead", - PendingDeprecationWarning) - super(Dict, self).__init__(**kwargs) - - -class Plist(_InternalDict): - - """This class has been deprecated. Use readPlist() and writePlist() - functions instead, together with regular dict objects. - """ - - def __init__(self, **kwargs): - from warnings import warn - warn("The Plist class is deprecated, use the readPlist() and " - "writePlist() functions instead", PendingDeprecationWarning) - super(Plist, self).__init__(**kwargs) - - def fromFile(cls, pathOrFile): - """Deprecated. Use the readPlist() function instead.""" - rootObject = readPlist(pathOrFile) - plist = cls() - plist.update(rootObject) - return plist - fromFile = classmethod(fromFile) - - def write(self, pathOrFile): - """Deprecated. Use the writePlist() function instead.""" - writePlist(self, pathOrFile) - - -def _encodeBase64(s, maxlinelength=76): - # copied from base64.encodestring(), with added maxlinelength argument - maxbinsize = (maxlinelength//4)*3 - pieces = [] - for i in range(0, len(s), maxbinsize): - chunk = s[i : i + maxbinsize] - pieces.append(binascii.b2a_base64(chunk)) - return "".join(pieces) - -class Data: - - """Wrapper for binary data.""" - - def __init__(self, data): - self.data = data - - def fromBase64(cls, data): - # base64.decodestring just calls binascii.a2b_base64; - # it seems overkill to use both base64 and binascii. - return cls(binascii.a2b_base64(data)) - fromBase64 = classmethod(fromBase64) - - def asBase64(self, maxlinelength=76): - return _encodeBase64(self.data, maxlinelength) - - def __cmp__(self, other): - if isinstance(other, self.__class__): - return cmp(self.data, other.data) - elif isinstance(other, str): - return cmp(self.data, other) - else: - return cmp(id(self), id(other)) - - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, repr(self.data)) - - -class PlistParser: - - def __init__(self): - self.stack = [] - self.currentKey = None - self.root = None - - def parse(self, fileobj): - from xml.parsers.expat import ParserCreate - parser = ParserCreate() - parser.StartElementHandler = self.handleBeginElement - parser.EndElementHandler = self.handleEndElement - parser.CharacterDataHandler = self.handleData - parser.ParseFile(fileobj) - return self.root - - def handleBeginElement(self, element, attrs): - self.data = [] - handler = getattr(self, "begin_" + element, None) - if handler is not None: - handler(attrs) - - def handleEndElement(self, element): - handler = getattr(self, "end_" + element, None) - if handler is not None: - handler() - - def handleData(self, data): - self.data.append(data) - - def addObject(self, value): - if self.currentKey is not None: - self.stack[-1][self.currentKey] = value - self.currentKey = None - elif not self.stack: - # this is the root object - self.root = value - else: - self.stack[-1].append(value) - - def getData(self): - data = "".join(self.data) - try: - data = data.encode("ascii") - except UnicodeError: - pass - self.data = [] - return data - - # element handlers - - def begin_dict(self, attrs): - d = _InternalDict() - self.addObject(d) - self.stack.append(d) - def end_dict(self): - self.stack.pop() - - def end_key(self): - self.currentKey = self.getData() - - def begin_array(self, attrs): - a = [] - self.addObject(a) - self.stack.append(a) - def end_array(self): - self.stack.pop() - - def end_true(self): - self.addObject(True) - def end_false(self): - self.addObject(False) - def end_integer(self): - self.addObject(int(self.getData())) - def end_real(self): - self.addObject(float(self.getData())) - def end_string(self): - self.addObject(self.getData()) - def end_data(self): - self.addObject(Data.fromBase64(self.getData())) - def end_date(self): - self.addObject(_dateFromString(self.getData())) - - -# cruft to support booleans in Python <= 2.3 -import sys -if sys.version_info[:2] < (2, 3): - # Python 2.2 and earlier: no booleans - # Python 2.2.x: booleans are ints - class bool(int): - """Imitation of the Python 2.3 bool object.""" - def __new__(cls, value): - return int.__new__(cls, not not value) - def __repr__(self): - if self: - return "True" - else: - return "False" - True = bool(1) - False = bool(0) diff --git a/misc/pylib/robofab/setup.py b/misc/pylib/robofab/setup.py deleted file mode 100755 index 813807a47..000000000 --- a/misc/pylib/robofab/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from distutils.core import setup -from distutils.extension import Extension -from Cython.Distutils import build_ext - -ext_modules = [ - Extension("objects.objectsBase", ["objects/objectsBase.pyx"]), - Extension("objects.objectsRF", ["objects/objectsRF.pyx"]), - Extension("pens.rfUFOPen", ["pens/rfUFOPen.pyx"]), - Extension("pens.boundsPen", ["pens/boundsPen.pyx"]), - Extension("xmlTreeBuilder", ["xmlTreeBuilder.pyx"]), - Extension("misc.arrayTools", ["misc/arrayTools.pyx"]), - Extension("glifLib", ["glifLib.pyx"]), -] - -setup( - name = 'robofab', - cmdclass = {'build_ext': build_ext}, - ext_modules = ext_modules -) diff --git a/misc/pylib/robofab/test/__init__.py b/misc/pylib/robofab/test/__init__.py deleted file mode 100755 index f34415804..000000000 --- a/misc/pylib/robofab/test/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Directory for unit tests. - -Modules here are typically named text_<something>.py, where <something> is -usually a module name, for example "test_flPen.py", but it can also be the name -of an area or concept to be tested, for example "test_drawing.py". - -Testmodules should use the unittest framework. -""" diff --git a/misc/pylib/robofab/test/runAll.py b/misc/pylib/robofab/test/runAll.py deleted file mode 100644 index 28446a7b8..000000000 --- a/misc/pylib/robofab/test/runAll.py +++ /dev/null @@ -1,27 +0,0 @@ -import os -import glob -import unittest - -import robofab.test - -if __name__ == "__main__": - testDir = os.path.dirname(robofab.test.__file__) - testFiles = glob.glob1(testDir, "test_*.py") - - loader = unittest.TestLoader() - suites = [] - for fileName in testFiles: - modName = "robofab.test." + fileName[:-3] - print "importing", fileName - try: - mod = __import__(modName, {}, {}, ["*"]) - except ImportError: - print "*** skipped", fileName - continue - - suites.append(loader.loadTestsFromModule(mod)) - - print "running tests..." - testRunner = unittest.TextTestRunner(verbosity=0) - testSuite = unittest.TestSuite(suites) - testRunner.run(testSuite) diff --git a/misc/pylib/robofab/test/testSupport.py b/misc/pylib/robofab/test/testSupport.py deleted file mode 100755 index f7f96c9ff..000000000 --- a/misc/pylib/robofab/test/testSupport.py +++ /dev/null @@ -1,278 +0,0 @@ -"""Miscellaneous helpers for our test suite.""" - - -import sys -import os -import types -import unittest - - -def getDemoFontPath(): - """Return the path to Data/DemoFont.ufo/.""" - import robofab - root = os.path.dirname(os.path.dirname(os.path.dirname(robofab.__file__))) - return os.path.join(root, "Data", "DemoFont.ufo") - - -def getDemoFontGlyphSetPath(): - """Return the path to Data/DemoFont.ufo/glyphs/.""" - return os.path.join(getDemoFontPath(), "glyphs") - - -def _gatherTestCasesFromCallerByMagic(): - # UGLY magic: fetch TestClass subclasses from the globals of our - # caller's caller. - frame = sys._getframe(2) - return _gatherTestCasesFromDict(frame.f_globals) - - -def _gatherTestCasesFromDict(d): - testCases = [] - for ob in d.values(): - if isinstance(ob, type) and issubclass(ob, unittest.TestCase): - testCases.append(ob) - return testCases - - -def runTests(testCases=None, verbosity=1): - """Run a series of tests.""" - if testCases is None: - testCases = _gatherTestCasesFromCallerByMagic() - loader = unittest.TestLoader() - suites = [] - for testCase in testCases: - suites.append(loader.loadTestsFromTestCase(testCase)) - - testRunner = unittest.TextTestRunner(verbosity=verbosity) - testSuite = unittest.TestSuite(suites) - testRunner.run(testSuite) - -# font info values used by several tests - -fontInfoVersion1 = { - "familyName" : "Some Font (Family Name)", - "styleName" : "Regular (Style Name)", - "fullName" : "Some Font-Regular (Postscript Full Name)", - "fontName" : "SomeFont-Regular (Postscript Font Name)", - "menuName" : "Some Font Regular (Style Map Family Name)", - "fontStyle" : 64, - "note" : "A note.", - "versionMajor" : 1, - "versionMinor" : 0, - "year" : 2008, - "copyright" : "Copyright Some Foundry.", - "notice" : "Some Font by Some Designer for Some Foundry.", - "trademark" : "Trademark Some Foundry", - "license" : "License info for Some Foundry.", - "licenseURL" : "http://somefoundry.com/license", - "createdBy" : "Some Foundry", - "designer" : "Some Designer", - "designerURL" : "http://somedesigner.com", - "vendorURL" : "http://somefoundry.com", - "unitsPerEm" : 1000, - "ascender" : 750, - "descender" : -250, - "capHeight" : 750, - "xHeight" : 500, - "defaultWidth" : 400, - "slantAngle" : -12.5, - "italicAngle" : -12.5, - "widthName" : "Medium (normal)", - "weightName" : "Medium", - "weightValue" : 500, - "fondName" : "SomeFont Regular (FOND Name)", - "otFamilyName" : "Some Font (Preferred Family Name)", - "otStyleName" : "Regular (Preferred Subfamily Name)", - "otMacName" : "Some Font Regular (Compatible Full Name)", - "msCharSet" : 0, - "fondID" : 15000, - "uniqueID" : 4000000, - "ttVendor" : "SOME", - "ttUniqueID" : "OpenType name Table Unique ID", - "ttVersion" : "OpenType name Table Version", -} - -fontInfoVersion2 = { - "familyName" : "Some Font (Family Name)", - "styleName" : "Regular (Style Name)", - "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)", - "styleMapStyleName" : "regular", - "versionMajor" : 1, - "versionMinor" : 0, - "year" : 2008, - "copyright" : "Copyright Some Foundry.", - "trademark" : "Trademark Some Foundry", - "unitsPerEm" : 1000, - "descender" : -250, - "xHeight" : 500, - "capHeight" : 750, - "ascender" : 750, - "italicAngle" : -12.5, - "note" : "A note.", - "openTypeHeadCreated" : "2000/01/01 00:00:00", - "openTypeHeadLowestRecPPEM" : 10, - "openTypeHeadFlags" : [0, 1], - "openTypeHheaAscender" : 750, - "openTypeHheaDescender" : -250, - "openTypeHheaLineGap" : 200, - "openTypeHheaCaretSlopeRise" : 1, - "openTypeHheaCaretSlopeRun" : 0, - "openTypeHheaCaretOffset" : 0, - "openTypeNameDesigner" : "Some Designer", - "openTypeNameDesignerURL" : "http://somedesigner.com", - "openTypeNameManufacturer" : "Some Foundry", - "openTypeNameManufacturerURL" : "http://somefoundry.com", - "openTypeNameLicense" : "License info for Some Foundry.", - "openTypeNameLicenseURL" : "http://somefoundry.com/license", - "openTypeNameVersion" : "OpenType name Table Version", - "openTypeNameUniqueID" : "OpenType name Table Unique ID", - "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.", - "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)", - "openTypeNamePreferredSubfamilyName" : "Regular (Preferred Subfamily Name)", - "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)", - "openTypeNameSampleText" : "Sample Text for Some Font.", - "openTypeNameWWSFamilyName" : "Some Font (WWS Family Name)", - "openTypeNameWWSSubfamilyName" : "Regular (WWS Subfamily Name)", - "openTypeOS2WidthClass" : 5, - "openTypeOS2WeightClass" : 500, - "openTypeOS2Selection" : [3], - "openTypeOS2VendorID" : "SOME", - "openTypeOS2Panose" : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - "openTypeOS2FamilyClass" : [1, 1], - "openTypeOS2UnicodeRanges" : [0, 1], - "openTypeOS2CodePageRanges" : [0, 1], - "openTypeOS2TypoAscender" : 750, - "openTypeOS2TypoDescender" : -250, - "openTypeOS2TypoLineGap" : 200, - "openTypeOS2WinAscent" : 750, - "openTypeOS2WinDescent" : -250, - "openTypeOS2Type" : [], - "openTypeOS2SubscriptXSize" : 200, - "openTypeOS2SubscriptYSize" : 400, - "openTypeOS2SubscriptXOffset" : 0, - "openTypeOS2SubscriptYOffset" : -100, - "openTypeOS2SuperscriptXSize" : 200, - "openTypeOS2SuperscriptYSize" : 400, - "openTypeOS2SuperscriptXOffset" : 0, - "openTypeOS2SuperscriptYOffset" : 200, - "openTypeOS2StrikeoutSize" : 20, - "openTypeOS2StrikeoutPosition" : 300, - "openTypeVheaVertTypoAscender" : 750, - "openTypeVheaVertTypoDescender" : -250, - "openTypeVheaVertTypoLineGap" : 200, - "openTypeVheaCaretSlopeRise" : 0, - "openTypeVheaCaretSlopeRun" : 1, - "openTypeVheaCaretOffset" : 0, - "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)", - "postscriptFullName" : "Some Font-Regular (Postscript Full Name)", - "postscriptSlantAngle" : -12.5, - "postscriptUniqueID" : 4000000, - "postscriptUnderlineThickness" : 20, - "postscriptUnderlinePosition" : -200, - "postscriptIsFixedPitch" : False, - "postscriptBlueValues" : [500, 510], - "postscriptOtherBlues" : [-250, -260], - "postscriptFamilyBlues" : [500, 510], - "postscriptFamilyOtherBlues" : [-250, -260], - "postscriptStemSnapH" : [100, 120], - "postscriptStemSnapV" : [80, 90], - "postscriptBlueFuzz" : 1, - "postscriptBlueShift" : 7, - "postscriptBlueScale" : 0.039625, - "postscriptForceBold" : True, - "postscriptDefaultWidthX" : 400, - "postscriptNominalWidthX" : 400, - "postscriptWeightName" : "Medium", - "postscriptDefaultCharacter" : ".notdef", - "postscriptWindowsCharacterSet" : 1, - "macintoshFONDFamilyID" : 15000, - "macintoshFONDName" : "SomeFont Regular (FOND Name)", -} - -expectedFontInfo1To2Conversion = { - "familyName" : "Some Font (Family Name)", - "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)", - "styleMapStyleName" : "regular", - "styleName" : "Regular (Style Name)", - "unitsPerEm" : 1000, - "ascender" : 750, - "capHeight" : 750, - "xHeight" : 500, - "descender" : -250, - "italicAngle" : -12.5, - "versionMajor" : 1, - "versionMinor" : 0, - "year" : 2008, - "copyright" : "Copyright Some Foundry.", - "trademark" : "Trademark Some Foundry", - "note" : "A note.", - "macintoshFONDFamilyID" : 15000, - "macintoshFONDName" : "SomeFont Regular (FOND Name)", - "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)", - "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.", - "openTypeNameDesigner" : "Some Designer", - "openTypeNameDesignerURL" : "http://somedesigner.com", - "openTypeNameLicense" : "License info for Some Foundry.", - "openTypeNameLicenseURL" : "http://somefoundry.com/license", - "openTypeNameManufacturer" : "Some Foundry", - "openTypeNameManufacturerURL" : "http://somefoundry.com", - "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)", - "openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)", - "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)", - "openTypeNameUniqueID" : "OpenType name Table Unique ID", - "openTypeNameVersion" : "OpenType name Table Version", - "openTypeOS2VendorID" : "SOME", - "openTypeOS2WeightClass" : 500, - "openTypeOS2WidthClass" : 5, - "postscriptDefaultWidthX" : 400, - "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)", - "postscriptFullName" : "Some Font-Regular (Postscript Full Name)", - "postscriptSlantAngle" : -12.5, - "postscriptUniqueID" : 4000000, - "postscriptWeightName" : "Medium", - "postscriptWindowsCharacterSet" : 1 -} - -expectedFontInfo2To1Conversion = { - "familyName" : "Some Font (Family Name)", - "menuName" : "Some Font Regular (Style Map Family Name)", - "fontStyle" : 64, - "styleName" : "Regular (Style Name)", - "unitsPerEm" : 1000, - "ascender" : 750, - "capHeight" : 750, - "xHeight" : 500, - "descender" : -250, - "italicAngle" : -12.5, - "versionMajor" : 1, - "versionMinor" : 0, - "copyright" : "Copyright Some Foundry.", - "trademark" : "Trademark Some Foundry", - "note" : "A note.", - "fondID" : 15000, - "fondName" : "SomeFont Regular (FOND Name)", - "fullName" : "Some Font Regular (Compatible Full Name)", - "notice" : "Some Font by Some Designer for Some Foundry.", - "designer" : "Some Designer", - "designerURL" : "http://somedesigner.com", - "license" : "License info for Some Foundry.", - "licenseURL" : "http://somefoundry.com/license", - "createdBy" : "Some Foundry", - "vendorURL" : "http://somefoundry.com", - "otFamilyName" : "Some Font (Preferred Family Name)", - "otStyleName" : "Regular (Preferred Subfamily Name)", - "otMacName" : "Some Font Regular (Compatible Full Name)", - "ttUniqueID" : "OpenType name Table Unique ID", - "ttVersion" : "OpenType name Table Version", - "ttVendor" : "SOME", - "weightValue" : 500, - "widthName" : "Medium (normal)", - "defaultWidth" : 400, - "fontName" : "SomeFont-Regular (Postscript Font Name)", - "fullName" : "Some Font-Regular (Postscript Full Name)", - "slantAngle" : -12.5, - "uniqueID" : 4000000, - "weightName" : "Medium", - "msCharSet" : 0, - "year" : 2008 -} diff --git a/misc/pylib/robofab/test/test_RInfoFL.py b/misc/pylib/robofab/test/test_RInfoFL.py deleted file mode 100644 index bfbd13477..000000000 --- a/misc/pylib/robofab/test/test_RInfoFL.py +++ /dev/null @@ -1,111 +0,0 @@ -import unittest -from cStringIO import StringIO -import sys -from robofab import ufoLib -from robofab.objects.objectsFL import NewFont -from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2 - - -class RInfoRFTestCase(unittest.TestCase): - - def testRoundTripVersion2(self): - font = NewFont() - infoObject = font.info - for attr, value in fontInfoVersion2.items(): - if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is None: - continue - setattr(infoObject, attr, value) - newValue = getattr(infoObject, attr) - self.assertEqual((attr, newValue), (attr, value)) - font.close() - - def testVersion2UnsupportedSet(self): - saveStderr = sys.stderr - saveStdout = sys.stdout - tempStderr = StringIO() - sys.stderr = tempStderr - sys.stdout = tempStderr - font = NewFont() - infoObject = font.info - requiredWarnings = [] - try: - for attr, value in fontInfoVersion2.items(): - if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is not None: - continue - setattr(infoObject, attr, value) - s = "The attribute %s is not supported by FontLab." % attr - requiredWarnings.append((attr, s)) - finally: - sys.stderr = saveStderr - sys.stdout = saveStdout - tempStderr = tempStderr.getvalue() - for attr, line in requiredWarnings: - self.assertEquals((attr, line in tempStderr), (attr, True)) - font.close() - - def testVersion2UnsupportedGet(self): - saveStderr = sys.stderr - saveStdout = sys.stdout - tempStderr = StringIO() - sys.stderr = tempStderr - sys.stdout = tempStderr - font = NewFont() - infoObject = font.info - requiredWarnings = [] - try: - for attr, value in fontInfoVersion2.items(): - if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is not None: - continue - getattr(infoObject, attr, value) - s = "The attribute %s is not supported by FontLab." % attr - requiredWarnings.append((attr, s)) - finally: - sys.stderr = saveStderr - sys.stdout = saveStdout - tempStderr = tempStderr.getvalue() - for attr, line in requiredWarnings: - self.assertEquals((attr, line in tempStderr), (attr, True)) - font.close() - - def testRoundTripVersion1(self): - font = NewFont() - infoObject = font.info - for attr, value in fontInfoVersion1.items(): - if attr not in ufoLib.deprecatedFontInfoAttributesVersion2: - setattr(infoObject, attr, value) - for attr, expectedValue in fontInfoVersion1.items(): - if attr not in ufoLib.deprecatedFontInfoAttributesVersion2: - value = getattr(infoObject, attr) - self.assertEqual((attr, expectedValue), (attr, value)) - font.close() - - def testVersion1DeprecationRoundTrip(self): - saveStderr = sys.stderr - saveStdout = sys.stdout - tempStderr = StringIO() - sys.stderr = tempStderr - sys.stdout = tempStderr - font = NewFont() - infoObject = font.info - requiredWarnings = [] - try: - for attr, value in fontInfoVersion1.items(): - if attr in ufoLib.deprecatedFontInfoAttributesVersion2: - setattr(infoObject, attr, value) - v = getattr(infoObject, attr) - self.assertEquals((attr, value), (attr, v)) - s = "DeprecationWarning: The %s attribute has been deprecated." % attr - requiredWarnings.append((attr, s)) - finally: - sys.stderr = saveStderr - sys.stdout = saveStdout - tempStderr = tempStderr.getvalue() - for attr, line in requiredWarnings: - self.assertEquals((attr, line in tempStderr), (attr, True)) - font.close() - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - runTests() - diff --git a/misc/pylib/robofab/test/test_RInfoRF.py b/misc/pylib/robofab/test/test_RInfoRF.py deleted file mode 100644 index 3a8747033..000000000 --- a/misc/pylib/robofab/test/test_RInfoRF.py +++ /dev/null @@ -1,56 +0,0 @@ -import unittest -from cStringIO import StringIO -import sys -from robofab import ufoLib -from robofab.objects.objectsRF import RInfo -from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2 - - -class RInfoRFTestCase(unittest.TestCase): - - def testRoundTripVersion2(self): - infoObject = RInfo() - for attr, value in fontInfoVersion2.items(): - setattr(infoObject, attr, value) - newValue = getattr(infoObject, attr) - self.assertEqual((attr, newValue), (attr, value)) - - def testRoundTripVersion1(self): - infoObject = RInfo() - for attr, value in fontInfoVersion1.items(): - if attr not in ufoLib.deprecatedFontInfoAttributesVersion2: - setattr(infoObject, attr, value) - for attr, expectedValue in fontInfoVersion1.items(): - if attr not in ufoLib.deprecatedFontInfoAttributesVersion2: - value = getattr(infoObject, attr) - self.assertEqual((attr, expectedValue), (attr, value)) - - def testVersion1DeprecationRoundTrip(self): - """ - unittest doesn't catch warnings in self.assertRaises, - so some hackery is required to catch the warnings - that are raised when setting deprecated attributes. - """ - saveStderr = sys.stderr - tempStderr = StringIO() - sys.stderr = tempStderr - infoObject = RInfo() - requiredWarnings = [] - try: - for attr, value in fontInfoVersion1.items(): - if attr in ufoLib.deprecatedFontInfoAttributesVersion2: - setattr(infoObject, attr, value) - v = getattr(infoObject, attr) - self.assertEquals((attr, value), (attr, v)) - s = "DeprecationWarning: The %s attribute has been deprecated." % attr - requiredWarnings.append((attr, s)) - finally: - sys.stderr = saveStderr - tempStderr = tempStderr.getvalue() - for attr, line in requiredWarnings: - self.assertEquals((attr, line in tempStderr), (attr, True)) - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - runTests() diff --git a/misc/pylib/robofab/test/test_dialogs.py b/misc/pylib/robofab/test/test_dialogs.py deleted file mode 100644 index c296a6bd7..000000000 --- a/misc/pylib/robofab/test/test_dialogs.py +++ /dev/null @@ -1,218 +0,0 @@ -import robofab.interface.all.dialogs -reload(robofab.interface.all.dialogs) -from robofab.interface.all.dialogs import * - -import unittest - - -__all__ = [ - "AskString", #x - "AskYesNoCancel", #x - "FindGlyph", - "GetFile", #x - "GetFolder", #x - "GetFileOrFolder", #x - "Message", #x - "OneList", - "PutFile", #x - "SearchList", - "SelectFont", - "SelectGlyph", - "TwoChecks", - "TwoFields", - "ProgressBar", -] - -class DialogRunner(object): - def __init__(self): - prompt = "The prompt for %s." - message = "The message for %s." - title = "The title for %s." - informativeText = "The informative text for %s." - fileTypes = ['ufo'] - fileName = "The_filename.txt" - - self.fonts = fonts = [self.makeTestFont(n) for n in range(4)] - - t = "AskString" - try: - print "About to try", t - print "\t>>>", AskString( - message=prompt%t, - value='', - title=title%t - ) - except NotImplementedError: - print t, "is not implemented." - - t = "AskYesNoCancel" - try: - print "About to try", t - print "\t>>>", AskYesNoCancel( - message=prompt%t+" default set to 0", - title=title%t, - default=0, - informativeText=informativeText%t - ) - print "\t>>>", AskYesNoCancel( - message=prompt%t+" default set to 1", - title=title%t, - default=1, - informativeText=informativeText%t - ) - except NotImplementedError: - print t, "is not implemented." - - t = "GetFile" - try: - print "About to try", t - print "\t>>>", GetFile( - message=message%t+" Only fileTypes "+`fileTypes`, - title=title%t, - directory=None, - fileName=fileName, - allowsMultipleSelection=False, - fileTypes=fileTypes - ) - print "\t>>>", GetFile( - message=message%t+" All filetypes, allow multiple selection.", - title=title%t, - directory=None, - fileName=fileName, - allowsMultipleSelection=True, - fileTypes=None - ) - except NotImplementedError: - print t, "is not implemented." - - t = "GetFolder" - try: - print "About to try", t - print "\t>>>", GetFolder( - message=message%t, - title=title%t, - directory=None, - allowsMultipleSelection=False - ) - print "\t>>>", GetFolder( - message=message%t + " Allow multiple selection.", - title=title%t, - directory=None, - allowsMultipleSelection=True - ) - except NotImplementedError: - print t, "is not implemented." - - t = "GetFileOrFolder" - try: - print "About to try", t - print "\t>>>", GetFileOrFolder( - message=message%t+" Only fileTypes "+`fileTypes`, - title=title%t, - directory=None, - fileName=fileName, - allowsMultipleSelection=False, - fileTypes=fileTypes - ) - print "\t>>>", GetFileOrFolder( - message=message%t + " Allow multiple selection.", - title=title%t, - directory=None, - fileName=fileName, - allowsMultipleSelection=True, - fileTypes=None - ) - except NotImplementedError: - print t, "is not implemented." - - t = "Message" - try: - print "About to try", t - print "\t>>>", Message( - message=message%t, - title=title%t, - informativeText=informativeText%t - ) - except NotImplementedError: - print t, "is not implemented." - - t = "PutFile" - try: - print "About to try", t - print "\t>>>", PutFile( - message=message%t, - fileName=fileName, - ) - except NotImplementedError: - print t, "is not implemented." - - # t = "SelectFont" - # try: - #print "About to try", t - # print "\t>>>", SelectFont( - # message=message%t, - # title=title%t, - # allFonts=fonts, - # ) - # except NotImplementedError: - # print t, "is not implemented." - - # t = 'SelectGlyph' - # try: - #print "About to try", t - # print "\t>>>", SelectGlyph( - # font=fonts[0], - # message=message%t, - # title=title%t, - # ) - # except NotImplementedError: - # print t, "is not implemented." - - print 'No more tests.' - - def makeTestFont(self, number): - from robofab.objects.objectsRF import RFont as _RFont - f = _RFont() - f.info.familyName = "TestFamily" - f.info.styleName = "weight%d"%number - f.info.postscriptFullName = "%s %s"%(f.info.familyName, f.info.styleName) - # make some glyphs - for name in ['A', 'B', 'C']: - g = f.newGlyph(name) - pen = g.getPen() - pen.moveTo((0,0)) - pen.lineTo((500, 0)) - pen.lineTo((500, 800)) - pen.lineTo((0, 800)) - pen.closePath() - return f - - -class DialogTests(unittest.TestCase): - def setUp(self): - from robofab.interface.all.dialogs import test - test() - - def tearDown(self): - pass - - def testDialogs(self): - import robofab.interface.all.dialogs - dialogModuleName = robofab.interface.all.dialogs.platformApplicationModuleName - application = robofab.interface.all.dialogs.application - - if application is None and dialogModuleName == "dialogs_mac_vanilla": - # in vanilla, but not in a host application, run with executeVanillaTest - print - print "I'm running these tests with executeVanillaTest" - from vanilla.test.testTools import executeVanillaTest - executeVanillaTest(DialogRunner) - else: - print - print "I'm running these tests natively in" - DialogRunner() - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - runTests() diff --git a/misc/pylib/robofab/test/test_fontLabUFOReadWrite.py b/misc/pylib/robofab/test/test_fontLabUFOReadWrite.py deleted file mode 100644 index 91c072d26..000000000 --- a/misc/pylib/robofab/test/test_fontLabUFOReadWrite.py +++ /dev/null @@ -1,565 +0,0 @@ -import os -import shutil -import unittest -import tempfile -from robofab.plistlib import readPlist -import robofab -from robofab.ufoLib import UFOReader, UFOWriter -from robofab.test.testSupport import fontInfoVersion2, expectedFontInfo1To2Conversion -from robofab.objects.objectsFL import NewFont, OpenFont - -vfbPath = os.path.dirname(robofab.__file__) -vfbPath = os.path.dirname(vfbPath) -vfbPath = os.path.dirname(vfbPath) -vfbPath = os.path.join(vfbPath, "TestData", "TestFont1.vfb") - -ufoPath1 = os.path.dirname(robofab.__file__) -ufoPath1 = os.path.dirname(ufoPath1) -ufoPath1 = os.path.dirname(ufoPath1) -ufoPath1 = os.path.join(ufoPath1, "TestData", "TestFont1 (UFO1).ufo") -ufoPath2 = ufoPath1.replace("TestFont1 (UFO1).ufo", "TestFont1 (UFO2).ufo") - - -expectedFormatVersion1Features = """@myClass = [A B]; - -feature liga { - sub A A by b; -} liga; -""" - -# robofab should remove these from the lib after a load. -removeFromFormatVersion1Lib = [ - "org.robofab.opentype.classes", - "org.robofab.opentype.features", - "org.robofab.opentype.featureorder", - "org.robofab.postScriptHintData" -] - - -class ReadUFOFormatVersion1TestCase(unittest.TestCase): - - def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False): - self.font = NewFont() - self.ufoPath = ufoPath1 - self.font.readUFO(ufoPath1, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures) - self.font.update() - - def tearDownFont(self): - self.font.close() - self.font = None - - def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True): - reader = UFOReader(self.ufoPath) - results = {} - if doInfo: - infoMatches = True - info = self.font.info - for attr, expectedValue in expectedFontInfo1To2Conversion.items(): - writtenValue = getattr(info, attr) - if expectedValue != writtenValue: - infoMatches = False - break - results["info"]= infoMatches - if doKerning: - kerning = self.font.kerning.asDict() - expectedKerning = reader.readKerning() - results["kerning"] = expectedKerning == kerning - if doGroups: - groups = dict(self.font.groups) - expectedGroups = reader.readGroups() - results["groups"] = expectedGroups == groups - if doFeatures: - features = self.font.features.text - expectedFeatures = expectedFormatVersion1Features - # FontLab likes to add lines to the features, so skip blank lines. - features = [line for line in features.splitlines() if line] - expectedFeatures = [line for line in expectedFeatures.splitlines() if line] - results["features"] = expectedFeatures == features - if doLib: - lib = dict(self.font.lib) - expectedLib = reader.readLib() - for key in removeFromFormatVersion1Lib: - if key in expectedLib: - del expectedLib[key] - results["lib"] = expectedLib == lib - return results - - def testFull(self): - self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], True) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - def testInfo(self): - self.setUpFont(doInfo=True) - otherResults = self.compareToUFO(doInfo=False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], False) - info = self.font.info - for attr, expectedValue in expectedFontInfo1To2Conversion.items(): - writtenValue = getattr(info, attr) - self.assertEqual((attr, expectedValue), (attr, writtenValue)) - self.tearDownFont() - - def testFeatures(self): - self.setUpFont(doFeatures=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], False) - self.tearDownFont() - - def testKerning(self): - self.setUpFont(doKerning=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], False) - self.tearDownFont() - - def testGroups(self): - self.setUpFont(doGroups=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], False) - self.tearDownFont() - - def testLib(self): - self.setUpFont(doLib=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - -class ReadUFOFormatVersion2TestCase(unittest.TestCase): - - def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False): - self.font = NewFont() - self.ufoPath = ufoPath2 - self.font.readUFO(ufoPath2, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures) - self.font.update() - - def tearDownFont(self): - self.font.close() - self.font = None - - def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True): - reader = UFOReader(self.ufoPath) - results = {} - if doInfo: - infoMatches = True - info = self.font.info - for attr, expectedValue in fontInfoVersion2.items(): - # cheat by skipping attrs that aren't supported - if info._ufoToFLAttrMapping[attr]["nakedAttribute"] is None: - continue - writtenValue = getattr(info, attr) - if expectedValue != writtenValue: - infoMatches = False - break - results["info"]= infoMatches - if doKerning: - kerning = self.font.kerning.asDict() - expectedKerning = reader.readKerning() - results["kerning"] = expectedKerning == kerning - if doGroups: - groups = dict(self.font.groups) - expectedGroups = reader.readGroups() - results["groups"] = expectedGroups == groups - if doFeatures: - features = self.font.features.text - expectedFeatures = reader.readFeatures() - results["features"] = expectedFeatures == features - if doLib: - lib = dict(self.font.lib) - expectedLib = reader.readLib() - results["lib"] = expectedLib == lib - return results - - def testFull(self): - self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], True) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - def testInfo(self): - self.setUpFont(doInfo=True) - otherResults = self.compareToUFO(doInfo=False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], False) - info = self.font.info - for attr, expectedValue in fontInfoVersion2.items(): - # cheat by skipping attrs that aren't supported - if info._ufoToFLAttrMapping[attr]["nakedAttribute"] is None: - continue - writtenValue = getattr(info, attr) - self.assertEqual((attr, expectedValue), (attr, writtenValue)) - self.tearDownFont() - - def testFeatures(self): - self.setUpFont(doFeatures=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], False) - self.tearDownFont() - - def testKerning(self): - self.setUpFont(doKerning=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], False) - self.tearDownFont() - - def testGroups(self): - self.setUpFont(doGroups=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], False) - self.tearDownFont() - - def testLib(self): - self.setUpFont(doLib=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - -class WriteUFOFormatVersion1TestCase(unittest.TestCase): - - def setUpFont(self, doInfo=False, doKerning=False, doGroups=False): - self.dstDir = tempfile.mktemp() - os.mkdir(self.dstDir) - self.font = OpenFont(vfbPath) - self.font.writeUFO(self.dstDir, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, formatVersion=1) - self.font.close() - - def tearDownFont(self): - shutil.rmtree(self.dstDir) - - def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True): - readerExpected = UFOReader(ufoPath1) - readerWritten = UFOReader(self.dstDir) - results = {} - if doInfo: - matches = True - expectedPath = os.path.join(ufoPath1, "fontinfo.plist") - writtenPath = os.path.join(self.dstDir, "fontinfo.plist") - if not os.path.exists(writtenPath): - matches = False - else: - expected = readPlist(expectedPath) - written = readPlist(writtenPath) - for attr, expectedValue in expected.items(): - if expectedValue != written[attr]: - matches = False - break - results["info"] = matches - if doKerning: - matches = True - expectedPath = os.path.join(ufoPath1, "kerning.plist") - writtenPath = os.path.join(self.dstDir, "kerning.plist") - if not os.path.exists(writtenPath): - matches = False - else: - matches = readPlist(expectedPath) == readPlist(writtenPath) - results["kerning"] = matches - if doGroups: - matches = True - expectedPath = os.path.join(ufoPath1, "groups.plist") - writtenPath = os.path.join(self.dstDir, "groups.plist") - if not os.path.exists(writtenPath): - matches = False - else: - matches = readPlist(expectedPath) == readPlist(writtenPath) - results["groups"] = matches - if doFeatures: - matches = True - featuresPath = os.path.join(self.dstDir, "features.fea") - libPath = os.path.join(self.dstDir, "lib.plist") - if os.path.exists(featuresPath): - matches = False - else: - fontLib = readPlist(libPath) - writtenText = [fontLib.get("org.robofab.opentype.classes", "")] - features = fontLib.get("org.robofab.opentype.features", {}) - featureOrder= fontLib.get("org.robofab.opentype.featureorder", []) - for featureName in featureOrder: - writtenText.append(features.get(featureName, "")) - writtenText = "\n".join(writtenText) - # FontLab likes to add lines to the features, so skip blank lines. - expectedText = [line for line in expectedFormatVersion1Features.splitlines() if line] - writtenText = [line for line in writtenText.splitlines() if line] - matches = "\n".join(expectedText) == "\n".join(writtenText) - results["features"] = matches - if doLib: - matches = True - expectedPath = os.path.join(ufoPath1, "lib.plist") - writtenPath = os.path.join(self.dstDir, "lib.plist") - if not os.path.exists(writtenPath): - matches = False - else: - # the test file doesn't have the glyph order - # so purge it from the written - writtenLib = readPlist(writtenPath) - del writtenLib["org.robofab.glyphOrder"] - matches = readPlist(expectedPath) == writtenLib - results["lib"] = matches - return results - - def testFull(self): - self.setUpFont(doInfo=True, doKerning=True, doGroups=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], True) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - def testInfo(self): - self.setUpFont(doInfo=True) - otherResults = self.compareToUFO(doInfo=False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - expectedPath = os.path.join(ufoPath1, "fontinfo.plist") - writtenPath = os.path.join(self.dstDir, "fontinfo.plist") - expected = readPlist(expectedPath) - written = readPlist(writtenPath) - for attr, expectedValue in expected.items(): - self.assertEqual((attr, expectedValue), (attr, written[attr])) - self.tearDownFont() - - def testFeatures(self): - self.setUpFont() - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], True) - self.tearDownFont() - - def testKerning(self): - self.setUpFont(doKerning=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], False) - self.tearDownFont() - - def testGroups(self): - self.setUpFont(doGroups=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], True) - self.tearDownFont() - - def testLib(self): - self.setUpFont() - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - - -class WriteUFOFormatVersion2TestCase(unittest.TestCase): - - def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False): - self.dstDir = tempfile.mktemp() - os.mkdir(self.dstDir) - self.font = OpenFont(vfbPath) - self.font.writeUFO(self.dstDir, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures) - self.font.close() - - def tearDownFont(self): - shutil.rmtree(self.dstDir) - - def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True): - readerExpected = UFOReader(ufoPath2) - readerWritten = UFOReader(self.dstDir) - results = {} - if doInfo: - matches = True - expectedPath = os.path.join(ufoPath2, "fontinfo.plist") - writtenPath = os.path.join(self.dstDir, "fontinfo.plist") - if not os.path.exists(writtenPath): - matches = False - else: - dummyFont = NewFont() - _ufoToFLAttrMapping = dict(dummyFont.info._ufoToFLAttrMapping) - dummyFont.close() - expected = readPlist(expectedPath) - written = readPlist(writtenPath) - for attr, expectedValue in expected.items(): - # cheat by skipping attrs that aren't supported - if _ufoToFLAttrMapping[attr]["nakedAttribute"] is None: - continue - if expectedValue != written[attr]: - matches = False - break - results["info"] = matches - if doKerning: - matches = True - expectedPath = os.path.join(ufoPath2, "kerning.plist") - writtenPath = os.path.join(self.dstDir, "kerning.plist") - if not os.path.exists(writtenPath): - matches = False - else: - matches = readPlist(expectedPath) == readPlist(writtenPath) - results["kerning"] = matches - if doGroups: - matches = True - expectedPath = os.path.join(ufoPath2, "groups.plist") - writtenPath = os.path.join(self.dstDir, "groups.plist") - if not os.path.exists(writtenPath): - matches = False - else: - matches = readPlist(expectedPath) == readPlist(writtenPath) - results["groups"] = matches - if doFeatures: - matches = True - expectedPath = os.path.join(ufoPath2, "features.fea") - writtenPath = os.path.join(self.dstDir, "features.fea") - if not os.path.exists(writtenPath): - matches = False - else: - f = open(expectedPath, "r") - expectedText = f.read() - f.close() - f = open(writtenPath, "r") - writtenText = f.read() - f.close() - # FontLab likes to add lines to the features, so skip blank lines. - expectedText = [line for line in expectedText.splitlines() if line] - writtenText = [line for line in writtenText.splitlines() if line] - matches = "\n".join(expectedText) == "\n".join(writtenText) - results["features"] = matches - if doLib: - matches = True - expectedPath = os.path.join(ufoPath2, "lib.plist") - writtenPath = os.path.join(self.dstDir, "lib.plist") - if not os.path.exists(writtenPath): - matches = False - else: - # the test file doesn't have the glyph order - # so purge it from the written - writtenLib = readPlist(writtenPath) - del writtenLib["org.robofab.glyphOrder"] - matches = readPlist(expectedPath) == writtenLib - results["lib"] = matches - return results - - def testFull(self): - self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], True) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - def testInfo(self): - self.setUpFont(doInfo=True) - otherResults = self.compareToUFO(doInfo=False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], False) - expectedPath = os.path.join(ufoPath2, "fontinfo.plist") - writtenPath = os.path.join(self.dstDir, "fontinfo.plist") - expected = readPlist(expectedPath) - written = readPlist(writtenPath) - dummyFont = NewFont() - _ufoToFLAttrMapping = dict(dummyFont.info._ufoToFLAttrMapping) - dummyFont.close() - for attr, expectedValue in expected.items(): - # cheat by skipping attrs that aren't supported - if _ufoToFLAttrMapping[attr]["nakedAttribute"] is None: - continue - self.assertEqual((attr, expectedValue), (attr, written[attr])) - self.tearDownFont() - - def testFeatures(self): - self.setUpFont(doFeatures=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], False) - self.tearDownFont() - - def testKerning(self): - self.setUpFont(doKerning=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], False) - self.tearDownFont() - - def testGroups(self): - self.setUpFont(doGroups=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], False) - self.tearDownFont() - - def testLib(self): - self.setUpFont(doLib=True) - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], False) - self.assertEqual(otherResults["kerning"], False) - self.assertEqual(otherResults["groups"], False) - self.assertEqual(otherResults["features"], False) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - runTests() diff --git a/misc/pylib/robofab/test/test_glifLib.py b/misc/pylib/robofab/test/test_glifLib.py deleted file mode 100644 index 13eca3a6a..000000000 --- a/misc/pylib/robofab/test/test_glifLib.py +++ /dev/null @@ -1,150 +0,0 @@ -import os -import tempfile -import shutil -import unittest - -from robofab.test.testSupport import getDemoFontGlyphSetPath -from robofab.glifLib import GlyphSet, glyphNameToFileName, READ_MODE -from robofab.tools.glyphNameSchemes import glyphNameToShortFileName - - -GLYPHSETDIR = getDemoFontGlyphSetPath() - - -class GlyphSetTests(unittest.TestCase): - - def setUp(self): - self.dstDir = tempfile.mktemp() - os.mkdir(self.dstDir) - - def tearDown(self): - shutil.rmtree(self.dstDir) - - def testRoundTrip(self): - srcDir = GLYPHSETDIR - dstDir = self.dstDir - src = GlyphSet(srcDir) - dst = GlyphSet(dstDir) - for glyphName in src.keys(): - g = src[glyphName] - g.drawPoints(None) # load attrs - dst.writeGlyph(glyphName, g, g.drawPoints) - # compare raw file data: - for glyphName in src.keys(): - fileName = src.contents[glyphName] - org = file(os.path.join(srcDir, fileName), READ_MODE).read() - new = file(os.path.join(dstDir, fileName), READ_MODE).read() - self.assertEqual(org, new, "%r .glif file differs after round tripping" % glyphName) - - def testRebuildContents(self): - gset = GlyphSet(GLYPHSETDIR) - contents = gset.contents - gset.rebuildContents() - self.assertEqual(contents, gset.contents) - - def testReverseContents(self): - gset = GlyphSet(GLYPHSETDIR) - d = {} - for k, v in gset.getReverseContents().items(): - d[v] = k - org = {} - for k, v in gset.contents.items(): - org[k] = v.lower() - self.assertEqual(d, org) - - def testReverseContents2(self): - src = GlyphSet(GLYPHSETDIR) - dst = GlyphSet(self.dstDir) - dstMap = dst.getReverseContents() - self.assertEqual(dstMap, {}) - for glyphName in src.keys(): - g = src[glyphName] - g.drawPoints(None) # load attrs - dst.writeGlyph(glyphName, g, g.drawPoints) - self.assertNotEqual(dstMap, {}) - srcMap = dict(src.getReverseContents()) # copy - self.assertEqual(dstMap, srcMap) - del srcMap["a.glif"] - dst.deleteGlyph("a") - self.assertEqual(dstMap, srcMap) - - def testCustomFileNamingScheme(self): - def myGlyphNameToFileName(glyphName, glyphSet): - return "prefix" + glyphNameToFileName(glyphName, glyphSet) - src = GlyphSet(GLYPHSETDIR) - dst = GlyphSet(self.dstDir, myGlyphNameToFileName) - for glyphName in src.keys(): - g = src[glyphName] - g.drawPoints(None) # load attrs - dst.writeGlyph(glyphName, g, g.drawPoints) - d = {} - for k, v in src.contents.items(): - print k, v - d[k] = "prefix" + v - self.assertEqual(d, dst.contents) - - def testGetUnicodes(self): - src = GlyphSet(GLYPHSETDIR) - unicodes = src.getUnicodes() - for glyphName in src.keys(): - g = src[glyphName] - g.drawPoints(None) # load attrs - if not hasattr(g, "unicodes"): - self.assertEqual(unicodes[glyphName], []) - else: - self.assertEqual(g.unicodes, unicodes[glyphName]) - - -class FileNameTests(unittest.TestCase): - - def testDefaultFileNameScheme(self): - self.assertEqual(glyphNameToFileName("a", None), "a.glif") - self.assertEqual(glyphNameToFileName("A", None), "A_.glif") - self.assertEqual(glyphNameToFileName("Aring", None), "Aring_.glif") - self.assertEqual(glyphNameToFileName("F_A_B", None), "F__A__B_.glif") - self.assertEqual(glyphNameToFileName("A.alt", None), "A_.alt.glif") - self.assertEqual(glyphNameToFileName("A.Alt", None), "A_.Alt_.glif") - self.assertEqual(glyphNameToFileName(".notdef", None), "_notdef.glif") - self.assertEqual(glyphNameToFileName("T_H", None), "T__H_.glif") - self.assertEqual(glyphNameToFileName("T_h", None), "T__h.glif") - self.assertEqual(glyphNameToFileName("t_h", None), "t_h.glif") - self.assertEqual(glyphNameToFileName('F_F_I', None), "F__F__I_.glif") - self.assertEqual(glyphNameToFileName('f_f_i', None), "f_f_i.glif") - - - def testShortFileNameScheme(self): - print "testShortFileNameScheme" - self.assertEqual(glyphNameToShortFileName("a", None), "a.glif") - self.assertEqual(glyphNameToShortFileName("A", None), "A_.glif") - self.assertEqual(glyphNameToShortFileName("aE", None), "aE_.glif") - self.assertEqual(glyphNameToShortFileName("AE", None), "A_E_.glif") - self.assertEqual(glyphNameToShortFileName("a.alt", None), "a_alt.glif") - self.assertEqual(glyphNameToShortFileName("A.alt", None), "A__alt.glif") - self.assertEqual(glyphNameToShortFileName("a.alt#swash", None), "a_alt_swash.glif") - self.assertEqual(glyphNameToShortFileName("A.alt", None), "A__alt.glif") - self.assertEqual(glyphNameToShortFileName(".notdef", None), "_notdef.glif") - self.assertEqual(glyphNameToShortFileName("f_f_i", None), "f_f_i.glif") - self.assertEqual(glyphNameToShortFileName("F_F_I", None), "F__F__I_.glif") - self.assertEqual(glyphNameToShortFileName("acircumflexdieresis.swash.alt1", None), "acircumflexdieresi0cfc8352.glif") - self.assertEqual(glyphNameToShortFileName("acircumflexdieresis.swash.alt2", None), "acircumflexdieresi95f5d2e8.glif") - self.assertEqual(glyphNameToShortFileName("Acircumflexdieresis.swash.alt1", None), "A_circumflexdieresed24fb56.glif") - self.assertEqual(glyphNameToShortFileName("F#weight0.800_width0.425", None), "F__weight0_800_width0_425.glif") - self.assertEqual(glyphNameToShortFileName("F#weight0.83245511_width0.425693567", None), "F__weight0_8324551c9a4143c.glif") - self.assertEqual(len(glyphNameToShortFileName("F#weight0.83245511_width0.425693567", None)), 31) - - def testShortFileNameScheme_clashes(self): - # test for the condition in code.robofab.com ticket #5 - name1 = glyphNameToShortFileName('Adieresis', None) - name2 = glyphNameToShortFileName('a_dieresis', None) - self.assertNotEqual(name1, name2) - name1 = glyphNameToShortFileName('AE', None) - name2 = glyphNameToShortFileName('aE', None) - self.assertNotEqual(name1, name2) - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - import sys - if len(sys.argv) > 1 and os.path.isdir(sys.argv[-1]): - GLYPHSETDIR = sys.argv.pop() - runTests() diff --git a/misc/pylib/robofab/test/test_noneLabUFOReadWrite.py b/misc/pylib/robofab/test/test_noneLabUFOReadWrite.py deleted file mode 100644 index 36dcc0582..000000000 --- a/misc/pylib/robofab/test/test_noneLabUFOReadWrite.py +++ /dev/null @@ -1,321 +0,0 @@ -import os -import shutil -import unittest -import tempfile -from robofab.plistlib import readPlist -import robofab -from robofab.test.testSupport import fontInfoVersion2, expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion -from robofab.objects.objectsRF import NewFont, OpenFont -from robofab.ufoLib import UFOReader - -ufoPath1 = os.path.dirname(robofab.__file__) -ufoPath1 = os.path.dirname(ufoPath1) -ufoPath1 = os.path.dirname(ufoPath1) -ufoPath1 = os.path.join(ufoPath1, "TestData", "TestFont1 (UFO1).ufo") -ufoPath2 = ufoPath1.replace("TestFont1 (UFO1).ufo", "TestFont1 (UFO2).ufo") - -# robofab should remove these from the lib after a load. -removeFromFormatVersion1Lib = [ - "org.robofab.opentype.classes", - "org.robofab.opentype.features", - "org.robofab.opentype.featureorder", - "org.robofab.postScriptHintData" -] - - -class ReadUFOFormatVersion1TestCase(unittest.TestCase): - - def setUpFont(self): - self.font = OpenFont(ufoPath1) - self.font.update() - - def tearDownFont(self): - self.font.close() - self.font = None - - def compareToUFO(self, doInfo=True): - reader = UFOReader(ufoPath1) - results = {} - # info - infoMatches = True - info = self.font.info - for attr, expectedValue in expectedFontInfo1To2Conversion.items(): - writtenValue = getattr(info, attr) - if expectedValue != writtenValue: - infoMatches = False - break - results["info"]= infoMatches - # kerning - kerning = self.font.kerning.asDict() - expectedKerning = reader.readKerning() - results["kerning"] = expectedKerning == kerning - # groups - groups = dict(self.font.groups) - expectedGroups = reader.readGroups() - results["groups"] = expectedGroups == groups - # features - features = self.font.features.text - f = open(os.path.join(ufoPath2, "features.fea"), "r") - expectedFeatures = f.read() - f.close() - match = True - features = [line for line in features.splitlines() if line] - expectedFeatures = [line for line in expectedFeatures.splitlines() if line] - if expectedFeatures != features or reader.readFeatures() != "": - match = False - results["features"] = match - # lib - lib = dict(self.font.lib) - expectedLib = reader.readLib() - for key in removeFromFormatVersion1Lib: - if key in expectedLib: - del expectedLib[key] - results["lib"] = expectedLib == lib - return results - - def testFull(self): - self.setUpFont() - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], True) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - def testInfo(self): - self.setUpFont() - info = self.font.info - for attr, expectedValue in expectedFontInfo1To2Conversion.items(): - writtenValue = getattr(info, attr) - self.assertEqual((attr, expectedValue), (attr, writtenValue)) - self.tearDownFont() - - -class ReadUFOFormatVersion2TestCase(unittest.TestCase): - - def setUpFont(self): - self.font = OpenFont(ufoPath2) - self.font.update() - - def tearDownFont(self): - self.font.close() - self.font = None - - def compareToUFO(self, doInfo=True): - reader = UFOReader(ufoPath2) - results = {} - # info - infoMatches = True - info = self.font.info - for attr, expectedValue in fontInfoVersion2.items(): - writtenValue = getattr(info, attr) - if expectedValue != writtenValue: - infoMatches = False - break - results["info"]= infoMatches - # kerning - kerning = self.font.kerning.asDict() - expectedKerning = reader.readKerning() - results["kerning"] = expectedKerning == kerning - # groups - groups = dict(self.font.groups) - expectedGroups = reader.readGroups() - results["groups"] = expectedGroups == groups - # features - features = self.font.features.text - expectedFeatures = reader.readFeatures() - results["features"] = expectedFeatures == features - # lib - lib = dict(self.font.lib) - expectedLib = reader.readLib() - results["lib"] = expectedLib == lib - return results - - def testFull(self): - self.setUpFont() - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], True) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - def testInfo(self): - self.setUpFont() - info = self.font.info - for attr, expectedValue in fontInfoVersion2.items(): - writtenValue = getattr(info, attr) - self.assertEqual((attr, expectedValue), (attr, writtenValue)) - self.tearDownFont() - - -class WriteUFOFormatVersion1TestCase(unittest.TestCase): - - def setUpFont(self): - self.dstDir = tempfile.mktemp() - os.mkdir(self.dstDir) - self.font = OpenFont(ufoPath2) - self.font.save(self.dstDir, formatVersion=1) - - def tearDownFont(self): - shutil.rmtree(self.dstDir) - - def compareToUFO(self): - readerExpected = UFOReader(ufoPath1) - readerWritten = UFOReader(self.dstDir) - results = {} - # info - matches = True - expectedPath = os.path.join(ufoPath1, "fontinfo.plist") - writtenPath = os.path.join(self.dstDir, "fontinfo.plist") - if not os.path.exists(writtenPath): - matches = False - else: - expected = readPlist(expectedPath) - written = readPlist(writtenPath) - for attr, expectedValue in expected.items(): - if expectedValue != written.get(attr): - matches = False - break - results["info"] = matches - # kerning - matches = True - expectedPath = os.path.join(ufoPath1, "kerning.plist") - writtenPath = os.path.join(self.dstDir, "kerning.plist") - if not os.path.exists(writtenPath): - matches = False - else: - matches = readPlist(expectedPath) == readPlist(writtenPath) - results["kerning"] = matches - # groups - matches = True - expectedPath = os.path.join(ufoPath1, "groups.plist") - writtenPath = os.path.join(self.dstDir, "groups.plist") - if not os.path.exists(writtenPath): - matches = False - else: - matches = readPlist(expectedPath) == readPlist(writtenPath) - results["groups"] = matches - # features - matches = True - expectedPath = os.path.join(ufoPath1, "features.fea") - writtenPath = os.path.join(self.dstDir, "features.fea") - if os.path.exists(writtenPath): - matches = False - results["features"] = matches - # lib - matches = True - expectedPath = os.path.join(ufoPath1, "lib.plist") - writtenPath = os.path.join(self.dstDir, "lib.plist") - if not os.path.exists(writtenPath): - matches = False - else: - writtenLib = readPlist(writtenPath) - matches = readPlist(expectedPath) == writtenLib - results["lib"] = matches - return results - - def testFull(self): - self.setUpFont() - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], True) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - -class WriteUFOFormatVersion2TestCase(unittest.TestCase): - - def setUpFont(self): - self.dstDir = tempfile.mktemp() - os.mkdir(self.dstDir) - self.font = OpenFont(ufoPath2) - self.font.save(self.dstDir) - - def tearDownFont(self): - shutil.rmtree(self.dstDir) - - def compareToUFO(self): - readerExpected = UFOReader(ufoPath2) - readerWritten = UFOReader(self.dstDir) - results = {} - # info - matches = True - expectedPath = os.path.join(ufoPath2, "fontinfo.plist") - writtenPath = os.path.join(self.dstDir, "fontinfo.plist") - if not os.path.exists(writtenPath): - matches = False - else: - expected = readPlist(expectedPath) - written = readPlist(writtenPath) - for attr, expectedValue in expected.items(): - if expectedValue != written[attr]: - matches = False - break - results["info"] = matches - # kerning - matches = True - expectedPath = os.path.join(ufoPath2, "kerning.plist") - writtenPath = os.path.join(self.dstDir, "kerning.plist") - if not os.path.exists(writtenPath): - matches = False - else: - matches = readPlist(expectedPath) == readPlist(writtenPath) - results["kerning"] = matches - # groups - matches = True - expectedPath = os.path.join(ufoPath2, "groups.plist") - writtenPath = os.path.join(self.dstDir, "groups.plist") - if not os.path.exists(writtenPath): - matches = False - else: - matches = readPlist(expectedPath) == readPlist(writtenPath) - results["groups"] = matches - # features - matches = True - expectedPath = os.path.join(ufoPath2, "features.fea") - writtenPath = os.path.join(self.dstDir, "features.fea") - if not os.path.exists(writtenPath): - matches = False - else: - f = open(expectedPath, "r") - expectedText = f.read() - f.close() - f = open(writtenPath, "r") - writtenText = f.read() - f.close() - # FontLab likes to add lines to the features, so skip blank lines. - expectedText = [line for line in expectedText.splitlines() if line] - writtenText = [line for line in writtenText.splitlines() if line] - matches = "\n".join(expectedText) == "\n".join(writtenText) - results["features"] = matches - # lib - matches = True - expectedPath = os.path.join(ufoPath2, "lib.plist") - writtenPath = os.path.join(self.dstDir, "lib.plist") - if not os.path.exists(writtenPath): - matches = False - else: - writtenLib = readPlist(writtenPath) - matches = readPlist(expectedPath) == writtenLib - results["lib"] = matches - return results - - def testFull(self): - self.setUpFont() - otherResults = self.compareToUFO() - self.assertEqual(otherResults["info"], True) - self.assertEqual(otherResults["kerning"], True) - self.assertEqual(otherResults["groups"], True) - self.assertEqual(otherResults["features"], True) - self.assertEqual(otherResults["lib"], True) - self.tearDownFont() - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - runTests() diff --git a/misc/pylib/robofab/test/test_objectsFL.py b/misc/pylib/robofab/test/test_objectsFL.py deleted file mode 100755 index 948897097..000000000 --- a/misc/pylib/robofab/test/test_objectsFL.py +++ /dev/null @@ -1,54 +0,0 @@ -"""This test suite for various FontLab-specific tests.""" - - -import FL # needed to quickly raise ImportError if run outside of FL - - -import os -import tempfile -import unittest - -from robofab.world import NewFont -from robofab.test.testSupport import getDemoFontPath, getDemoFontGlyphSetPath -from robofab.tools.glifImport import importAllGlifFiles -from robofab.pens.digestPen import DigestPointPen -from robofab.pens.adapterPens import SegmentToPointPen - - -def getDigests(font): - digests = {} - for glyphName in font.keys(): - pen = DigestPointPen() - font[glyphName].drawPoints(pen) - digests[glyphName] = pen.getDigest() - return digests - - -class FLTestCase(unittest.TestCase): - - def testUFOVersusGlifImport(self): - font = NewFont() - font.readUFO(getDemoFontPath(), doProgress=False) - d1 = getDigests(font) - font.close(False) - font = NewFont() - importAllGlifFiles(font.naked(), getDemoFontGlyphSetPath(), doProgress=False) - d2 = getDigests(font) - self.assertEqual(d1, d2) - font.close(False) - - def testTwoUntitledFonts(self): - font1 = NewFont() - font2 = NewFont() - font1.unitsPerEm = 1024 - font2.unitsPerEm = 2048 - self.assertNotEqual(font1.unitsPerEm, font2.unitsPerEm) - font1.update() - font2.update() - font1.close(False) - font2.close(False) - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - runTests() diff --git a/misc/pylib/robofab/test/test_objectsUFO.py b/misc/pylib/robofab/test/test_objectsUFO.py deleted file mode 100755 index c974a121c..000000000 --- a/misc/pylib/robofab/test/test_objectsUFO.py +++ /dev/null @@ -1,203 +0,0 @@ -"""This test suite for ufo glyph methods""" - - -import unittest -import os -import tempfile -import shutil - -from robofab.objects.objectsRF import RFont -from robofab.test.testSupport import getDemoFontPath -from robofab.pens.digestPen import DigestPointPen -from robofab.pens.adapterPens import SegmentToPointPen, FabToFontToolsPenAdapter - - -class ContourMethodsTestCase(unittest.TestCase): - - def setUp(self): - self.font = RFont(getDemoFontPath()) - - def testReverseContour(self): - for glyph in self.font: - pen = DigestPointPen() - glyph.drawPoints(pen) - digest1 = pen.getDigest() - for contour in glyph: - contour.reverseContour() - contour.reverseContour() - pen = DigestPointPen() - glyph.drawPoints(pen) - digest2 = pen.getDigest() - self.assertEqual(digest1, digest2, "%r not the same after reversing twice" % glyph.name) - - def testStartSegment(self): - for glyph in self.font: - pen = DigestPointPen() - glyph.drawPoints(pen) - digest1 = pen.getDigest() - for contour in glyph: - contour.setStartSegment(2) - contour.setStartSegment(-2) - pen = DigestPointPen() - glyph.drawPoints(pen) - digest2 = pen.getDigest() - self.assertEqual(digest1, digest2, "%r not the same after seting start segment twice" % glyph.name) - - def testAppendSegment(self): - for glyph in self.font: - pen = DigestPointPen() - glyph.drawPoints(pen) - digest1 = pen.getDigest() - for contour in glyph: - contour.insertSegment(2, "curve", [(100, 100), (200, 200), (300, 300)]) - contour.removeSegment(2) - pen = DigestPointPen() - glyph.drawPoints(pen) - digest2 = pen.getDigest() - self.assertEqual(digest1, digest2, "%r not the same after inserting and removing segment" % glyph.name) - - -class GlyphsMethodsTestCase(ContourMethodsTestCase): - - def testCopyGlyph(self): - for glyph in self.font: - pen = DigestPointPen() - glyph.drawPoints(pen) - digest1 = pen.getDigest() - copy = glyph.copy() - pen = DigestPointPen() - copy.drawPoints(pen) - digest2 = pen.getDigest() - self.assertEqual(digest1, digest2, "%r not the same after copying" % glyph.name) - self.assertEqual(glyph.lib, copy.lib, "%r's lib not the same after copying" % glyph.name) - self.assertEqual(glyph.width, copy.width, "%r's width not the same after copying" % glyph.name) - self.assertEqual(glyph.unicodes, copy.unicodes, "%r's unicodes not the same after copying" % glyph.name) - - def testMoveGlyph(self): - for glyph in self.font: - pen = DigestPointPen() - glyph.drawPoints(pen) - digest1 = pen.getDigest() - glyph.move((100, 200)) - glyph.move((-100, -200)) - pen = DigestPointPen() - glyph.drawPoints(pen) - digest2 = pen.getDigest() - self.assertEqual(digest1, digest2, "%r not the same after moving twice" % glyph.name) - - def testScaleGlyph(self): - for glyph in self.font: - pen = DigestPointPen() - glyph.drawPoints(pen) - digest1 = pen.getDigest() - glyph.scale((2, 2)) - glyph.scale((.5, .5)) - pen = DigestPointPen() - glyph.drawPoints(pen) - digest2 = pen.getDigest() - self.assertEqual(digest1, digest2, "%r not the same after scaling twice" % glyph.name) - - def testSegmentPenInterface(self): - for glyph in self.font: - digestPen = DigestPointPen(ignoreSmoothAndName=True) - pen = SegmentToPointPen(digestPen) - glyph.draw(pen) - digest1 = digestPen.getDigest() - digestPen = DigestPointPen(ignoreSmoothAndName=True) - glyph.drawPoints(digestPen) - digest2 = digestPen.getDigest() - self.assertEqual(digest1, digest2, "%r not the same for gl.draw() and gl.drawPoints()" % glyph.name) - - def testFabPenCompatibility(self): - for glyph in self.font: - digestPen = DigestPointPen(ignoreSmoothAndName=True) - pen = FabToFontToolsPenAdapter(SegmentToPointPen(digestPen)) - glyph.draw(pen) - digest1 = digestPen.getDigest() - digestPen = DigestPointPen(ignoreSmoothAndName=True) - glyph.drawPoints(digestPen) - digest2 = digestPen.getDigest() - self.assertEqual(digest1, digest2, "%r not the same for gl.draw() and gl.drawPoints()" % glyph.name) - - def testComponentTransformations(self): - from robofab.objects.objectsRF import RComponent - name = "baseGlyphName" - c = RComponent(name, transform=(1,0,0,1,0,0)) - # get values - assert c.baseGlyph == "baseGlyphName" - assert c.transformation == c.transformation - assert c.scale == (1,1) - assert c.offset == (0,0) - # set values - c.offset = (12,34) - assert c.transformation == (1, 0, 0, 1, 12, 34) - c.offset = (0,0) - assert c.transformation == (1,0,0,1,0,0) - c.scale = (12,34) - assert c.transformation == (12, 0, 0, 34, 0, 0) - - -class SaveTestCase(ContourMethodsTestCase): - - def testSaveAs(self): - path = tempfile.mktemp(".ufo") - try: - keys1 = self.font.keys() - self.font.save(path) - keys2 = self.font.keys() - keys1.sort() - keys2.sort() - self.assertEqual(keys1, keys2) - self.assertEqual(self.font.path, path) - font2 = RFont(path) - keys3 = font2.keys() - keys3.sort() - self.assertEqual(keys1, keys3) - finally: - if os.path.exists(path): - shutil.rmtree(path) - - def testSaveAs2(self): - path = tempfile.mktemp(".ufo") - # copy a glyph - self.font["X"] = self.font["a"].copy() -# self.assertEqual(self.font["X"].name, "X") - # remove a glyph - self.font.removeGlyph("a") - keys1 = self.font.keys() - try: - self.font.save(path) - self.assertEqual(self.font.path, path) - keys2 = self.font.keys() - keys1.sort() - keys2.sort() - self.assertEqual(keys1, keys2) - font2 = RFont(path) - keys3 = font2.keys() - keys3.sort() - self.assertEqual(keys1, keys3) - finally: - if os.path.exists(path): - shutil.rmtree(path) - - def testCustomFileNameScheme(self): - path = tempfile.mktemp(".ufo") - libKey = "org.robofab.glyphNameToFileNameFuncName" - self.font.lib[libKey] = "robofab.test.test_objectsUFO.testGlyphNameToFileName" - try: - self.font.save(path) - self.assertEqual(os.path.exists(os.path.join(path, - "glyphs", "test_a.glif")), True) - finally: - if os.path.exists(path): - shutil.rmtree(path) - - -def testGlyphNameToFileName(glyphName, glyphSet): - from robofab.glifLib import glyphNameToFileName - return "test_" + glyphNameToFileName(glyphName, glyphSet) - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - runTests() diff --git a/misc/pylib/robofab/test/test_pens.py b/misc/pylib/robofab/test/test_pens.py deleted file mode 100755 index a2a773cb2..000000000 --- a/misc/pylib/robofab/test/test_pens.py +++ /dev/null @@ -1,149 +0,0 @@ -"""This test suite test general Pen stuff, it should not contain -FontLab-specific code. -""" - -import unittest - -from robofab.pens.digestPen import DigestPointPen -from robofab.pens.adapterPens import SegmentToPointPen, PointToSegmentPen -from robofab.pens.adapterPens import GuessSmoothPointPen -from robofab.pens.reverseContourPointPen import ReverseContourPointPen -from robofab.test.testSupport import getDemoFontGlyphSetPath -from robofab.glifLib import GlyphSet - - -class TestShapes: - - # Collection of test shapes. It's probably better to add these as - # glyphs to the demo font. - - def square(pen): - # a simple square as a closed path (100, 100, 600, 600) - pen.beginPath() - pen.addPoint((100, 100), "line") - pen.addPoint((100, 600), "line") - pen.addPoint((600, 600), "line") - pen.addPoint((600, 100), "line") - pen.endPath() - square = staticmethod(square) - - def onCurveLessQuadShape(pen): - pen.beginPath() - pen.addPoint((100, 100)) - pen.addPoint((100, 600)) - pen.addPoint((600, 600)) - pen.addPoint((600, 100)) - pen.endPath() - onCurveLessQuadShape = staticmethod(onCurveLessQuadShape) - - def openPath(pen): - # a simple square as a closed path (100, 100, 600, 600) - pen.beginPath() - pen.addPoint((100, 100), "move") - pen.addPoint((100, 600), "line") - pen.addPoint((600, 600), "line") - pen.addPoint((600, 100), "line") - pen.endPath() - openPath = staticmethod(openPath) - - def circle(pen): - pen.beginPath() - pen.addPoint((0, 500), "curve") - pen.addPoint((0, 800)) - pen.addPoint((200, 1000)) - pen.addPoint((500, 1000), "curve") - pen.addPoint((800, 1000)) - pen.addPoint((1000, 800)) - pen.addPoint((1000, 500), "curve") - pen.addPoint((1000, 200)) - pen.addPoint((800, 0)) - pen.addPoint((500, 0), "curve") - pen.addPoint((200, 0)) - pen.addPoint((0, 200)) - pen.endPath() - circle = staticmethod(circle) - - -class RoundTripTestCase(unittest.TestCase): - - def _doTest(self, shapeFunc, shapeName): - pen = DigestPointPen(ignoreSmoothAndName=True) - shapeFunc(pen) - digest1 = pen.getDigest() - - digestPen = DigestPointPen(ignoreSmoothAndName=True) - pen = PointToSegmentPen(SegmentToPointPen(digestPen)) - shapeFunc(pen) - digest2 = digestPen.getDigest() - self.assertEqual(digest1, digest2, "%r failed round tripping" % shapeName) - - def testShapes(self): - for name in dir(TestShapes): - if name[0] != "_": - self._doTest(getattr(TestShapes, name), name) - - def testShapesFromGlyphSet(self): - glyphSet = GlyphSet(getDemoFontGlyphSetPath()) - for name in glyphSet.keys(): - self._doTest(glyphSet[name].drawPoints, name) - - def testGuessSmoothPen(self): - glyphSet = GlyphSet(getDemoFontGlyphSetPath()) - for name in glyphSet.keys(): - digestPen = DigestPointPen() - glyphSet[name].drawPoints(digestPen) - digest1 = digestPen.getDigest() - digestPen = DigestPointPen() - pen = GuessSmoothPointPen(digestPen) - glyphSet[name].drawPoints(pen) - digest2 = digestPen.getDigest() - self.assertEqual(digest1, digest2) - - -class ReverseContourTestCase(unittest.TestCase): - - def testReverseContourClosedPath(self): - digestPen = DigestPointPen() - TestShapes.square(digestPen) - d1 = digestPen.getDigest() - digestPen = DigestPointPen() - pen = ReverseContourPointPen(digestPen) - pen.beginPath() - pen.addPoint((100, 100), "line") - pen.addPoint((600, 100), "line") - pen.addPoint((600, 600), "line") - pen.addPoint((100, 600), "line") - pen.endPath() - d2 = digestPen.getDigest() - self.assertEqual(d1, d2) - - def testReverseContourOpenPath(self): - digestPen = DigestPointPen() - TestShapes.openPath(digestPen) - d1 = digestPen.getDigest() - digestPen = DigestPointPen() - pen = ReverseContourPointPen(digestPen) - pen.beginPath() - pen.addPoint((600, 100), "move") - pen.addPoint((600, 600), "line") - pen.addPoint((100, 600), "line") - pen.addPoint((100, 100), "line") - pen.endPath() - d2 = digestPen.getDigest() - self.assertEqual(d1, d2) - - def testReversContourFromGlyphSet(self): - glyphSet = GlyphSet(getDemoFontGlyphSetPath()) - digestPen = DigestPointPen() - glyphSet["testglyph1"].drawPoints(digestPen) - digest1 = digestPen.getDigest() - digestPen = DigestPointPen() - pen = ReverseContourPointPen(digestPen) - glyphSet["testglyph1.reversed"].drawPoints(pen) - digest2 = digestPen.getDigest() - self.assertEqual(digest1, digest2) - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - runTests() diff --git a/misc/pylib/robofab/test/test_psHints.py b/misc/pylib/robofab/test/test_psHints.py deleted file mode 100644 index 991e9d9cc..000000000 --- a/misc/pylib/robofab/test/test_psHints.py +++ /dev/null @@ -1,110 +0,0 @@ -def test(): - """ - # some tests for the ps Hints operations - >>> from robofab.world import RFont, RGlyph - >>> g = RGlyph() - >>> g.psHints.isEmpty() - True - - >>> h = RGlyph() - >>> i = g + h - >>> i.psHints.isEmpty() - True - - >>> i = g - h - >>> i.psHints.isEmpty() - True - - >>> i = g * 2 - >>> i.psHints.isEmpty() - True - - >>> i = g / 2 - >>> i.psHints.isEmpty() - True - - >>> g.psHints.vHints = [(100, 50), (200, 50)] - >>> g.psHints.hHints = [(100, 50), (200, 5)] - - >>> not g.psHints.isEmpty() - True - - >>> gc = g.copy() - >>> gc.psHints.asDict() == g.psHints.asDict() - True - - # multiplication - >>> v = g.psHints * 2 - >>> v.asDict() == {'vHints': [[200, 100], [400, 100]], 'hHints': [[200, 100], [400, 10]]} - True - - # division - >>> v = g.psHints / 2 - >>> v.asDict() == {'vHints': [[50.0, 25.0], [100.0, 25.0]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]} - True - - # multiplication with x, y, factor - # vertically oriented values should respond different - >>> v = g.psHints * (.5, 10) - >>> v.asDict() == {'vHints': [[1000, 500], [2000, 500]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]} - True - - # division with x, y, factor - # vertically oriented values should respond different - >>> v = g.psHints / (.5, 10) - >>> v.asDict() == {'vHints': [[10.0, 5.0], [20.0, 5.0]], 'hHints': [[200.0, 100.0], [400.0, 10.0]]} - True - - # rounding to integer - >>> v = g.psHints / 2 - >>> v.round() - >>> v.asDict() == {'vHints': [(50, 25), (100, 25)], 'hHints': [(50, 25), (100, 3)]} - True - - # "ps hint values calculating with a glyph" - # ps hint values as part of glyphmath operations. - # multiplication - >>> h = g * 10 - >>> h.psHints.asDict() == {'vHints': [[1000, 500], [2000, 500]], 'hHints': [[1000, 500], [2000, 50]]} - True - - # division - >>> h = g / 2 - >>> h.psHints.asDict() == {'vHints': [[50.0, 25.0], [100.0, 25.0]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]} - True - - # x, y factor multiplication - >>> h = g * (.5, 10) - >>> h.psHints.asDict() == {'vHints': [[1000, 500], [2000, 500]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]} - True - - # x, y factor division - >>> h = g / (.5, 10) - >>> h.psHints.asDict() == {'vHints': [[10.0, 5.0], [20.0, 5.0]], 'hHints': [[200.0, 100.0], [400.0, 10.0]]} - True - - # "font ps hint values" - >>> f = RFont() - >>> f.psHints.isEmpty() - True - - >>> f.psHints.blueScale = .5 - >>> f.psHints.blueShift = 1 - >>> f.psHints.blueFuzz = 1 - >>> f.psHints.forceBold = True - >>> f.psHints.hStems = (100, 90) - >>> f.psHints.vStems = (500, 10) - - >>> not f.psHints.isEmpty() - True - - >>> f.insertGlyph(g, name="new") - <RGlyph for None.new> - >>> f["new"].psHints.asDict() == g.psHints.asDict() - True - """ - -if __name__ == "__main__": - import doctest - doctest.testmod() - diff --git a/misc/pylib/robofab/test/test_ufoLib.py b/misc/pylib/robofab/test/test_ufoLib.py deleted file mode 100644 index e1da807bb..000000000 --- a/misc/pylib/robofab/test/test_ufoLib.py +++ /dev/null @@ -1,1659 +0,0 @@ -import os -import shutil -import unittest -import tempfile -from plistlib import writePlist, readPlist -from robofab.ufoLib import UFOReader, UFOWriter, UFOLibError, \ - convertUFOFormatVersion1ToFormatVersion2, convertUFOFormatVersion2ToFormatVersion1 -from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2, expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion - - -# the format version 1 lib.plist contains some data -# that these tests shouldn't be concerned about. -removeFromFormatVersion1Lib = [ - "org.robofab.opentype.classes", - "org.robofab.opentype.features", - "org.robofab.opentype.featureorder", - "org.robofab.postScriptHintData" -] - - -class TestInfoObject(object): pass - - -class ReadFontInfoVersion1TestCase(unittest.TestCase): - - def setUp(self): - self.dstDir = tempfile.mktemp() - os.mkdir(self.dstDir) - metaInfo = { - "creator": "test", - "formatVersion": 1 - } - path = os.path.join(self.dstDir, "metainfo.plist") - writePlist(metaInfo, path) - - def tearDown(self): - shutil.rmtree(self.dstDir) - - def _writeInfoToPlist(self, info): - path = os.path.join(self.dstDir, "fontinfo.plist") - writePlist(info, path) - - def testRead(self): - originalData = dict(fontInfoVersion1) - self._writeInfoToPlist(originalData) - infoObject = TestInfoObject() - reader = UFOReader(self.dstDir) - reader.readInfo(infoObject) - for attr in dir(infoObject): - if attr not in fontInfoVersion2: - continue - originalValue = fontInfoVersion2[attr] - readValue = getattr(infoObject, attr) - self.assertEqual(originalValue, readValue) - - def testFontStyleConversion(self): - fontStyle1To2 = { - 64 : "regular", - 1 : "italic", - 32 : "bold", - 33 : "bold italic" - } - for old, new in fontStyle1To2.items(): - info = dict(fontInfoVersion1) - info["fontStyle"] = old - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - infoObject = TestInfoObject() - reader.readInfo(infoObject) - self.assertEqual(new, infoObject.styleMapStyleName) - - def testWidthNameConversion(self): - widthName1To2 = { - "Ultra-condensed" : 1, - "Extra-condensed" : 2, - "Condensed" : 3, - "Semi-condensed" : 4, - "Medium (normal)" : 5, - "Semi-expanded" : 6, - "Expanded" : 7, - "Extra-expanded" : 8, - "Ultra-expanded" : 9 - } - for old, new in widthName1To2.items(): - info = dict(fontInfoVersion1) - info["widthName"] = old - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - infoObject = TestInfoObject() - reader.readInfo(infoObject) - self.assertEqual(new, infoObject.openTypeOS2WidthClass) - - -class ReadFontInfoVersion2TestCase(unittest.TestCase): - - def setUp(self): - self.dstDir = tempfile.mktemp() - os.mkdir(self.dstDir) - metaInfo = { - "creator": "test", - "formatVersion": 2 - } - path = os.path.join(self.dstDir, "metainfo.plist") - writePlist(metaInfo, path) - - def tearDown(self): - shutil.rmtree(self.dstDir) - - def _writeInfoToPlist(self, info): - path = os.path.join(self.dstDir, "fontinfo.plist") - writePlist(info, path) - - def testRead(self): - originalData = dict(fontInfoVersion2) - self._writeInfoToPlist(originalData) - infoObject = TestInfoObject() - reader = UFOReader(self.dstDir) - reader.readInfo(infoObject) - readData = {} - for attr in fontInfoVersion2.keys(): - readData[attr] = getattr(infoObject, attr) - self.assertEqual(originalData, readData) - - def testGenericRead(self): - # familyName - info = dict(fontInfoVersion2) - info["familyName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # styleName - info = dict(fontInfoVersion2) - info["styleName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # styleMapFamilyName - info = dict(fontInfoVersion2) - info["styleMapFamilyName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # styleMapStyleName - ## not a string - info = dict(fontInfoVersion2) - info["styleMapStyleName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## out of range - info = dict(fontInfoVersion2) - info["styleMapStyleName"] = "REGULAR" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # versionMajor - info = dict(fontInfoVersion2) - info["versionMajor"] = "1" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # versionMinor - info = dict(fontInfoVersion2) - info["versionMinor"] = "0" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # copyright - info = dict(fontInfoVersion2) - info["copyright"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # trademark - info = dict(fontInfoVersion2) - info["trademark"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # unitsPerEm - info = dict(fontInfoVersion2) - info["unitsPerEm"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # descender - info = dict(fontInfoVersion2) - info["descender"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # xHeight - info = dict(fontInfoVersion2) - info["xHeight"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # capHeight - info = dict(fontInfoVersion2) - info["capHeight"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # ascender - info = dict(fontInfoVersion2) - info["ascender"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # italicAngle - info = dict(fontInfoVersion2) - info["italicAngle"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - - def testHeadRead(self): - # openTypeHeadCreated - ## not a string - info = dict(fontInfoVersion2) - info["openTypeHeadCreated"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## invalid format - info = dict(fontInfoVersion2) - info["openTypeHeadCreated"] = "2000-Jan-01 00:00:00" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeHeadLowestRecPPEM - info = dict(fontInfoVersion2) - info["openTypeHeadLowestRecPPEM"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeHeadFlags - info = dict(fontInfoVersion2) - info["openTypeHeadFlags"] = [-1] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - - def testHheaRead(self): - # openTypeHheaAscender - info = dict(fontInfoVersion2) - info["openTypeHheaAscender"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeHheaDescender - info = dict(fontInfoVersion2) - info["openTypeHheaDescender"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeHheaLineGap - info = dict(fontInfoVersion2) - info["openTypeHheaLineGap"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeHheaCaretSlopeRise - info = dict(fontInfoVersion2) - info["openTypeHheaCaretSlopeRise"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeHheaCaretSlopeRun - info = dict(fontInfoVersion2) - info["openTypeHheaCaretSlopeRun"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeHheaCaretOffset - info = dict(fontInfoVersion2) - info["openTypeHheaCaretOffset"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - - def testNameRead(self): - # openTypeNameDesigner - info = dict(fontInfoVersion2) - info["openTypeNameDesigner"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameDesignerURL - info = dict(fontInfoVersion2) - info["openTypeNameDesignerURL"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameManufacturer - info = dict(fontInfoVersion2) - info["openTypeNameManufacturer"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameManufacturerURL - info = dict(fontInfoVersion2) - info["openTypeNameManufacturerURL"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameLicense - info = dict(fontInfoVersion2) - info["openTypeNameLicense"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameLicenseURL - info = dict(fontInfoVersion2) - info["openTypeNameLicenseURL"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameVersion - info = dict(fontInfoVersion2) - info["openTypeNameVersion"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameUniqueID - info = dict(fontInfoVersion2) - info["openTypeNameUniqueID"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameDescription - info = dict(fontInfoVersion2) - info["openTypeNameDescription"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNamePreferredFamilyName - info = dict(fontInfoVersion2) - info["openTypeNamePreferredFamilyName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNamePreferredSubfamilyName - info = dict(fontInfoVersion2) - info["openTypeNamePreferredSubfamilyName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameCompatibleFullName - info = dict(fontInfoVersion2) - info["openTypeNameCompatibleFullName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameSampleText - info = dict(fontInfoVersion2) - info["openTypeNameSampleText"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameWWSFamilyName - info = dict(fontInfoVersion2) - info["openTypeNameWWSFamilyName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeNameWWSSubfamilyName - info = dict(fontInfoVersion2) - info["openTypeNameWWSSubfamilyName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - - def testOS2Read(self): - # openTypeOS2WidthClass - ## not an int - info = dict(fontInfoVersion2) - info["openTypeOS2WidthClass"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## out or range - info = dict(fontInfoVersion2) - info["openTypeOS2WidthClass"] = 15 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2WeightClass - info = dict(fontInfoVersion2) - ## not an int - info["openTypeOS2WeightClass"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## out of range - info["openTypeOS2WeightClass"] = -50 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2Selection - info = dict(fontInfoVersion2) - info["openTypeOS2Selection"] = [-1] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2VendorID - info = dict(fontInfoVersion2) - info["openTypeOS2VendorID"] = 1234 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2Panose - ## not an int - info = dict(fontInfoVersion2) - info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## too few values - info = dict(fontInfoVersion2) - info["openTypeOS2Panose"] = [0, 1, 2, 3] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## too many values - info = dict(fontInfoVersion2) - info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2FamilyClass - ## not an int - info = dict(fontInfoVersion2) - info["openTypeOS2FamilyClass"] = [1, str(1)] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## too few values - info = dict(fontInfoVersion2) - info["openTypeOS2FamilyClass"] = [1] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## too many values - info = dict(fontInfoVersion2) - info["openTypeOS2FamilyClass"] = [1, 1, 1] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## out of range - info = dict(fontInfoVersion2) - info["openTypeOS2FamilyClass"] = [1, 201] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2UnicodeRanges - ## not an int - info = dict(fontInfoVersion2) - info["openTypeOS2UnicodeRanges"] = ["0"] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## out of range - info = dict(fontInfoVersion2) - info["openTypeOS2UnicodeRanges"] = [-1] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2CodePageRanges - ## not an int - info = dict(fontInfoVersion2) - info["openTypeOS2CodePageRanges"] = ["0"] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## out of range - info = dict(fontInfoVersion2) - info["openTypeOS2CodePageRanges"] = [-1] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2TypoAscender - info = dict(fontInfoVersion2) - info["openTypeOS2TypoAscender"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2TypoDescender - info = dict(fontInfoVersion2) - info["openTypeOS2TypoDescender"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2TypoLineGap - info = dict(fontInfoVersion2) - info["openTypeOS2TypoLineGap"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2WinAscent - info = dict(fontInfoVersion2) - info["openTypeOS2WinAscent"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2WinDescent - info = dict(fontInfoVersion2) - info["openTypeOS2WinDescent"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2Type - ## not an int - info = dict(fontInfoVersion2) - info["openTypeOS2Type"] = ["1"] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - ## out of range - info = dict(fontInfoVersion2) - info["openTypeOS2Type"] = [-1] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2SubscriptXSize - info = dict(fontInfoVersion2) - info["openTypeOS2SubscriptXSize"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2SubscriptYSize - info = dict(fontInfoVersion2) - info["openTypeOS2SubscriptYSize"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2SubscriptXOffset - info = dict(fontInfoVersion2) - info["openTypeOS2SubscriptXOffset"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2SubscriptYOffset - info = dict(fontInfoVersion2) - info["openTypeOS2SubscriptYOffset"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2SuperscriptXSize - info = dict(fontInfoVersion2) - info["openTypeOS2SuperscriptXSize"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2SuperscriptYSize - info = dict(fontInfoVersion2) - info["openTypeOS2SuperscriptYSize"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2SuperscriptXOffset - info = dict(fontInfoVersion2) - info["openTypeOS2SuperscriptXOffset"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2SuperscriptYOffset - info = dict(fontInfoVersion2) - info["openTypeOS2SuperscriptYOffset"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2StrikeoutSize - info = dict(fontInfoVersion2) - info["openTypeOS2StrikeoutSize"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeOS2StrikeoutPosition - info = dict(fontInfoVersion2) - info["openTypeOS2StrikeoutPosition"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - - def testVheaRead(self): - # openTypeVheaVertTypoAscender - info = dict(fontInfoVersion2) - info["openTypeVheaVertTypoAscender"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeVheaVertTypoDescender - info = dict(fontInfoVersion2) - info["openTypeVheaVertTypoDescender"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeVheaVertTypoLineGap - info = dict(fontInfoVersion2) - info["openTypeVheaVertTypoLineGap"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeVheaCaretSlopeRise - info = dict(fontInfoVersion2) - info["openTypeVheaCaretSlopeRise"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeVheaCaretSlopeRun - info = dict(fontInfoVersion2) - info["openTypeVheaCaretSlopeRun"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # openTypeVheaCaretOffset - info = dict(fontInfoVersion2) - info["openTypeVheaCaretOffset"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - - def testFONDRead(self): - # macintoshFONDFamilyID - info = dict(fontInfoVersion2) - info["macintoshFONDFamilyID"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # macintoshFONDName - info = dict(fontInfoVersion2) - info["macintoshFONDName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - - def testPostscriptRead(self): - # postscriptFontName - info = dict(fontInfoVersion2) - info["postscriptFontName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # postscriptFullName - info = dict(fontInfoVersion2) - info["postscriptFullName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # postscriptSlantAngle - info = dict(fontInfoVersion2) - info["postscriptSlantAngle"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) - # postscriptUniqueID - info = dict(fontInfoVersion2) - info["postscriptUniqueID"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptUnderlineThickness - info = dict(fontInfoVersion2) - info["postscriptUnderlineThickness"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptUnderlinePosition - info = dict(fontInfoVersion2) - info["postscriptUnderlinePosition"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptIsFixedPitch - info = dict(fontInfoVersion2) - info["postscriptIsFixedPitch"] = 2 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptBlueValues - ## not a list - info = dict(fontInfoVersion2) - info["postscriptBlueValues"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## uneven value count - info = dict(fontInfoVersion2) - info["postscriptBlueValues"] = [500] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## too many values - info = dict(fontInfoVersion2) - info["postscriptBlueValues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptOtherBlues - ## not a list - info = dict(fontInfoVersion2) - info["postscriptOtherBlues"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## uneven value count - info = dict(fontInfoVersion2) - info["postscriptOtherBlues"] = [500] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## too many values - info = dict(fontInfoVersion2) - info["postscriptOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptFamilyBlues - ## not a list - info = dict(fontInfoVersion2) - info["postscriptFamilyBlues"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## uneven value count - info = dict(fontInfoVersion2) - info["postscriptFamilyBlues"] = [500] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## too many values - info = dict(fontInfoVersion2) - info["postscriptFamilyBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptFamilyOtherBlues - ## not a list - info = dict(fontInfoVersion2) - info["postscriptFamilyOtherBlues"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## uneven value count - info = dict(fontInfoVersion2) - info["postscriptFamilyOtherBlues"] = [500] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## too many values - info = dict(fontInfoVersion2) - info["postscriptFamilyOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptStemSnapH - ## not list - info = dict(fontInfoVersion2) - info["postscriptStemSnapH"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## too many values - info = dict(fontInfoVersion2) - info["postscriptStemSnapH"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptStemSnapV - ## not list - info = dict(fontInfoVersion2) - info["postscriptStemSnapV"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - ## too many values - info = dict(fontInfoVersion2) - info["postscriptStemSnapV"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptBlueFuzz - info = dict(fontInfoVersion2) - info["postscriptBlueFuzz"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptBlueShift - info = dict(fontInfoVersion2) - info["postscriptBlueShift"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptBlueScale - info = dict(fontInfoVersion2) - info["postscriptBlueScale"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptForceBold - info = dict(fontInfoVersion2) - info["postscriptForceBold"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptDefaultWidthX - info = dict(fontInfoVersion2) - info["postscriptDefaultWidthX"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptNominalWidthX - info = dict(fontInfoVersion2) - info["postscriptNominalWidthX"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptWeightName - info = dict(fontInfoVersion2) - info["postscriptWeightName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptDefaultCharacter - info = dict(fontInfoVersion2) - info["postscriptDefaultCharacter"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # postscriptWindowsCharacterSet - info = dict(fontInfoVersion2) - info["postscriptWindowsCharacterSet"] = -1 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # macintoshFONDFamilyID - info = dict(fontInfoVersion2) - info["macintoshFONDFamilyID"] = "abc" - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - # macintoshFONDName - info = dict(fontInfoVersion2) - info["macintoshFONDName"] = 123 - self._writeInfoToPlist(info) - reader = UFOReader(self.dstDir) - self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) - - -class WriteFontInfoVersion1TestCase(unittest.TestCase): - - def setUp(self): - self.dstDir = tempfile.mktemp() - os.mkdir(self.dstDir) - - def tearDown(self): - shutil.rmtree(self.dstDir) - - def makeInfoObject(self): - infoObject = TestInfoObject() - for attr, value in fontInfoVersion2.items(): - setattr(infoObject, attr, value) - return infoObject - - def readPlist(self): - path = os.path.join(self.dstDir, "fontinfo.plist") - return readPlist(path) - - def testWrite(self): - infoObject = self.makeInfoObject() - writer = UFOWriter(self.dstDir, formatVersion=1) - writer.writeInfo(infoObject) - writtenData = self.readPlist() - for attr, originalValue in fontInfoVersion1.items(): - newValue = writtenData[attr] - self.assertEqual(newValue, originalValue) - - def testFontStyleConversion(self): - fontStyle1To2 = { - 64 : "regular", - 1 : "italic", - 32 : "bold", - 33 : "bold italic" - } - for old, new in fontStyle1To2.items(): - infoObject = self.makeInfoObject() - infoObject.styleMapStyleName = new - writer = UFOWriter(self.dstDir, formatVersion=1) - writer.writeInfo(infoObject) - writtenData = self.readPlist() - self.assertEqual(writtenData["fontStyle"], old) - - def testWidthNameConversion(self): - widthName1To2 = { - "Ultra-condensed" : 1, - "Extra-condensed" : 2, - "Condensed" : 3, - "Semi-condensed" : 4, - "Medium (normal)" : 5, - "Semi-expanded" : 6, - "Expanded" : 7, - "Extra-expanded" : 8, - "Ultra-expanded" : 9 - } - for old, new in widthName1To2.items(): - infoObject = self.makeInfoObject() - infoObject.openTypeOS2WidthClass = new - writer = UFOWriter(self.dstDir, formatVersion=1) - writer.writeInfo(infoObject) - writtenData = self.readPlist() - self.assertEqual(writtenData["widthName"], old) - - -class WriteFontInfoVersion2TestCase(unittest.TestCase): - - def setUp(self): - self.dstDir = tempfile.mktemp() - os.mkdir(self.dstDir) - - def tearDown(self): - shutil.rmtree(self.dstDir) - - def makeInfoObject(self): - infoObject = TestInfoObject() - for attr, value in fontInfoVersion2.items(): - setattr(infoObject, attr, value) - return infoObject - - def readPlist(self): - path = os.path.join(self.dstDir, "fontinfo.plist") - return readPlist(path) - - def testWrite(self): - infoObject = self.makeInfoObject() - writer = UFOWriter(self.dstDir) - writer.writeInfo(infoObject) - writtenData = self.readPlist() - for attr, originalValue in fontInfoVersion2.items(): - newValue = writtenData[attr] - self.assertEqual(newValue, originalValue) - - def testGenericWrite(self): - # familyName - infoObject = self.makeInfoObject() - infoObject.familyName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # styleName - infoObject = self.makeInfoObject() - infoObject.styleName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # styleMapFamilyName - infoObject = self.makeInfoObject() - infoObject.styleMapFamilyName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # styleMapStyleName - ## not a string - infoObject = self.makeInfoObject() - infoObject.styleMapStyleName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## out of range - infoObject = self.makeInfoObject() - infoObject.styleMapStyleName = "REGULAR" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # versionMajor - infoObject = self.makeInfoObject() - infoObject.versionMajor = "1" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # versionMinor - infoObject = self.makeInfoObject() - infoObject.versionMinor = "0" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # copyright - infoObject = self.makeInfoObject() - infoObject.copyright = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # trademark - infoObject = self.makeInfoObject() - infoObject.trademark = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # unitsPerEm - infoObject = self.makeInfoObject() - infoObject.unitsPerEm = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # descender - infoObject = self.makeInfoObject() - infoObject.descender = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # xHeight - infoObject = self.makeInfoObject() - infoObject.xHeight = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # capHeight - infoObject = self.makeInfoObject() - infoObject.capHeight = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # ascender - infoObject = self.makeInfoObject() - infoObject.ascender = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # italicAngle - infoObject = self.makeInfoObject() - infoObject.italicAngle = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - - def testHeadWrite(self): - # openTypeHeadCreated - ## not a string - infoObject = self.makeInfoObject() - infoObject.openTypeHeadCreated = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## invalid format - infoObject = self.makeInfoObject() - infoObject.openTypeHeadCreated = "2000-Jan-01 00:00:00" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeHeadLowestRecPPEM - infoObject = self.makeInfoObject() - infoObject.openTypeHeadLowestRecPPEM = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeHeadFlags - infoObject = self.makeInfoObject() - infoObject.openTypeHeadFlags = [-1] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - - def testHheaWrite(self): - # openTypeHheaAscender - infoObject = self.makeInfoObject() - infoObject.openTypeHheaAscender = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeHheaDescender - infoObject = self.makeInfoObject() - infoObject.openTypeHheaDescender = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeHheaLineGap - infoObject = self.makeInfoObject() - infoObject.openTypeHheaLineGap = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeHheaCaretSlopeRise - infoObject = self.makeInfoObject() - infoObject.openTypeHheaCaretSlopeRise = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeHheaCaretSlopeRun - infoObject = self.makeInfoObject() - infoObject.openTypeHheaCaretSlopeRun = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeHheaCaretOffset - infoObject = self.makeInfoObject() - infoObject.openTypeHheaCaretOffset = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - - def testNameWrite(self): - # openTypeNameDesigner - infoObject = self.makeInfoObject() - infoObject.openTypeNameDesigner = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameDesignerURL - infoObject = self.makeInfoObject() - infoObject.openTypeNameDesignerURL = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameManufacturer - infoObject = self.makeInfoObject() - infoObject.openTypeNameManufacturer = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameManufacturerURL - infoObject = self.makeInfoObject() - infoObject.openTypeNameManufacturerURL = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameLicense - infoObject = self.makeInfoObject() - infoObject.openTypeNameLicense = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameLicenseURL - infoObject = self.makeInfoObject() - infoObject.openTypeNameLicenseURL = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameVersion - infoObject = self.makeInfoObject() - infoObject.openTypeNameVersion = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameUniqueID - infoObject = self.makeInfoObject() - infoObject.openTypeNameUniqueID = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameDescription - infoObject = self.makeInfoObject() - infoObject.openTypeNameDescription = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNamePreferredFamilyName - infoObject = self.makeInfoObject() - infoObject.openTypeNamePreferredFamilyName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNamePreferredSubfamilyName - infoObject = self.makeInfoObject() - infoObject.openTypeNamePreferredSubfamilyName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameCompatibleFullName - infoObject = self.makeInfoObject() - infoObject.openTypeNameCompatibleFullName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameSampleText - infoObject = self.makeInfoObject() - infoObject.openTypeNameSampleText = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameWWSFamilyName - infoObject = self.makeInfoObject() - infoObject.openTypeNameWWSFamilyName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeNameWWSSubfamilyName - infoObject = self.makeInfoObject() - infoObject.openTypeNameWWSSubfamilyName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - - def testOS2Write(self): - # openTypeOS2WidthClass - ## not an int - infoObject = self.makeInfoObject() - infoObject.openTypeOS2WidthClass = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## out or range - infoObject = self.makeInfoObject() - infoObject.openTypeOS2WidthClass = 15 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2WeightClass - infoObject = self.makeInfoObject() - ## not an int - infoObject.openTypeOS2WeightClass = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## out of range - infoObject.openTypeOS2WeightClass = -50 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2Selection - infoObject = self.makeInfoObject() - infoObject.openTypeOS2Selection = [-1] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2VendorID - infoObject = self.makeInfoObject() - infoObject.openTypeOS2VendorID = 1234 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2Panose - ## not an int - infoObject = self.makeInfoObject() - infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too few values - infoObject = self.makeInfoObject() - infoObject.openTypeOS2Panose = [0, 1, 2, 3] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too many values - infoObject = self.makeInfoObject() - infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2FamilyClass - ## not an int - infoObject = self.makeInfoObject() - infoObject.openTypeOS2FamilyClass = [0, str(1)] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too few values - infoObject = self.makeInfoObject() - infoObject.openTypeOS2FamilyClass = [1] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too many values - infoObject = self.makeInfoObject() - infoObject.openTypeOS2FamilyClass = [1, 1, 1] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## out of range - infoObject = self.makeInfoObject() - infoObject.openTypeOS2FamilyClass = [1, 20] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2UnicodeRanges - ## not an int - infoObject = self.makeInfoObject() - infoObject.openTypeOS2UnicodeRanges = ["0"] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## out of range - infoObject = self.makeInfoObject() - infoObject.openTypeOS2UnicodeRanges = [-1] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2CodePageRanges - ## not an int - infoObject = self.makeInfoObject() - infoObject.openTypeOS2CodePageRanges = ["0"] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## out of range - infoObject = self.makeInfoObject() - infoObject.openTypeOS2CodePageRanges = [-1] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2TypoAscender - infoObject = self.makeInfoObject() - infoObject.openTypeOS2TypoAscender = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2TypoDescender - infoObject = self.makeInfoObject() - infoObject.openTypeOS2TypoDescender = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2TypoLineGap - infoObject = self.makeInfoObject() - infoObject.openTypeOS2TypoLineGap = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2WinAscent - infoObject = self.makeInfoObject() - infoObject.openTypeOS2WinAscent = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2WinDescent - infoObject = self.makeInfoObject() - infoObject.openTypeOS2WinDescent = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2Type - ## not an int - infoObject = self.makeInfoObject() - infoObject.openTypeOS2Type = ["1"] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## out of range - infoObject = self.makeInfoObject() - infoObject.openTypeOS2Type = [-1] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2SubscriptXSize - infoObject = self.makeInfoObject() - infoObject.openTypeOS2SubscriptXSize = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2SubscriptYSize - infoObject = self.makeInfoObject() - infoObject.openTypeOS2SubscriptYSize = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2SubscriptXOffset - infoObject = self.makeInfoObject() - infoObject.openTypeOS2SubscriptXOffset = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2SubscriptYOffset - infoObject = self.makeInfoObject() - infoObject.openTypeOS2SubscriptYOffset = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2SuperscriptXSize - infoObject = self.makeInfoObject() - infoObject.openTypeOS2SuperscriptXSize = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2SuperscriptYSize - infoObject = self.makeInfoObject() - infoObject.openTypeOS2SuperscriptYSize = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2SuperscriptXOffset - infoObject = self.makeInfoObject() - infoObject.openTypeOS2SuperscriptXOffset = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2SuperscriptYOffset - infoObject = self.makeInfoObject() - infoObject.openTypeOS2SuperscriptYOffset = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2StrikeoutSize - infoObject = self.makeInfoObject() - infoObject.openTypeOS2StrikeoutSize = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeOS2StrikeoutPosition - infoObject = self.makeInfoObject() - infoObject.openTypeOS2StrikeoutPosition = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - - def testVheaWrite(self): - # openTypeVheaVertTypoAscender - infoObject = self.makeInfoObject() - infoObject.openTypeVheaVertTypoAscender = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeVheaVertTypoDescender - infoObject = self.makeInfoObject() - infoObject.openTypeVheaVertTypoDescender = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeVheaVertTypoLineGap - infoObject = self.makeInfoObject() - infoObject.openTypeVheaVertTypoLineGap = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeVheaCaretSlopeRise - infoObject = self.makeInfoObject() - infoObject.openTypeVheaCaretSlopeRise = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeVheaCaretSlopeRun - infoObject = self.makeInfoObject() - infoObject.openTypeVheaCaretSlopeRun = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # openTypeVheaCaretOffset - infoObject = self.makeInfoObject() - infoObject.openTypeVheaCaretOffset = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - - def testFONDWrite(self): - # macintoshFONDFamilyID - infoObject = self.makeInfoObject() - infoObject.macintoshFONDFamilyID = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # macintoshFONDName - infoObject = self.makeInfoObject() - infoObject.macintoshFONDName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - - def testPostscriptWrite(self): - # postscriptFontName - infoObject = self.makeInfoObject() - infoObject.postscriptFontName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptFullName - infoObject = self.makeInfoObject() - infoObject.postscriptFullName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptSlantAngle - infoObject = self.makeInfoObject() - infoObject.postscriptSlantAngle = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptUniqueID - infoObject = self.makeInfoObject() - infoObject.postscriptUniqueID = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptUnderlineThickness - infoObject = self.makeInfoObject() - infoObject.postscriptUnderlineThickness = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptUnderlinePosition - infoObject = self.makeInfoObject() - infoObject.postscriptUnderlinePosition = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptIsFixedPitch - infoObject = self.makeInfoObject() - infoObject.postscriptIsFixedPitch = 2 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptBlueValues - ## not a list - infoObject = self.makeInfoObject() - infoObject.postscriptBlueValues = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## uneven value count - infoObject = self.makeInfoObject() - infoObject.postscriptBlueValues = [500] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too many values - infoObject = self.makeInfoObject() - infoObject.postscriptBlueValues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptOtherBlues - ## not a list - infoObject = self.makeInfoObject() - infoObject.postscriptOtherBlues = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## uneven value count - infoObject = self.makeInfoObject() - infoObject.postscriptOtherBlues = [500] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too many values - infoObject = self.makeInfoObject() - infoObject.postscriptOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptFamilyBlues - ## not a list - infoObject = self.makeInfoObject() - infoObject.postscriptFamilyBlues = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## uneven value count - infoObject = self.makeInfoObject() - infoObject.postscriptFamilyBlues = [500] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too many values - infoObject = self.makeInfoObject() - infoObject.postscriptFamilyBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptFamilyOtherBlues - ## not a list - infoObject = self.makeInfoObject() - infoObject.postscriptFamilyOtherBlues = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## uneven value count - infoObject = self.makeInfoObject() - infoObject.postscriptFamilyOtherBlues = [500] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too many values - infoObject = self.makeInfoObject() - infoObject.postscriptFamilyOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptStemSnapH - ## not list - infoObject = self.makeInfoObject() - infoObject.postscriptStemSnapH = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too many values - infoObject = self.makeInfoObject() - infoObject.postscriptStemSnapH = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptStemSnapV - ## not list - infoObject = self.makeInfoObject() - infoObject.postscriptStemSnapV = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - ## too many values - infoObject = self.makeInfoObject() - infoObject.postscriptStemSnapV = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptBlueFuzz - infoObject = self.makeInfoObject() - infoObject.postscriptBlueFuzz = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptBlueShift - infoObject = self.makeInfoObject() - infoObject.postscriptBlueShift = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptBlueScale - infoObject = self.makeInfoObject() - infoObject.postscriptBlueScale = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptForceBold - infoObject = self.makeInfoObject() - infoObject.postscriptForceBold = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptDefaultWidthX - infoObject = self.makeInfoObject() - infoObject.postscriptDefaultWidthX = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptNominalWidthX - infoObject = self.makeInfoObject() - infoObject.postscriptNominalWidthX = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptWeightName - infoObject = self.makeInfoObject() - infoObject.postscriptWeightName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptDefaultCharacter - infoObject = self.makeInfoObject() - infoObject.postscriptDefaultCharacter = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # postscriptWindowsCharacterSet - infoObject = self.makeInfoObject() - infoObject.postscriptWindowsCharacterSet = -1 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # macintoshFONDFamilyID - infoObject = self.makeInfoObject() - infoObject.macintoshFONDFamilyID = "abc" - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - # macintoshFONDName - infoObject = self.makeInfoObject() - infoObject.macintoshFONDName = 123 - writer = UFOWriter(self.dstDir) - self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) - - - - -class ConversionFunctionsTestCase(unittest.TestCase): - - def tearDown(self): - path = self.getFontPath("TestFont1 (UFO1) converted.ufo") - if os.path.exists(path): - shutil.rmtree(path) - path = self.getFontPath("TestFont1 (UFO2) converted.ufo") - if os.path.exists(path): - shutil.rmtree(path) - - def getFontPath(self, fileName): - import robofab - path = os.path.dirname(robofab.__file__) - path = os.path.dirname(path) - path = os.path.dirname(path) - path = os.path.join(path, "TestData", fileName) - return path - - def compareFileStructures(self, path1, path2, expectedInfoData, testFeatures): - # result - metainfoPath1 = os.path.join(path1, "metainfo.plist") - fontinfoPath1 = os.path.join(path1, "fontinfo.plist") - kerningPath1 = os.path.join(path1, "kerning.plist") - groupsPath1 = os.path.join(path1, "groups.plist") - libPath1 = os.path.join(path1, "lib.plist") - featuresPath1 = os.path.join(path1, "features.plist") - glyphsPath1 = os.path.join(path1, "glyphs") - glyphsPath1_contents = os.path.join(glyphsPath1, "contents.plist") - glyphsPath1_A = os.path.join(glyphsPath1, "A_.glif") - glyphsPath1_B = os.path.join(glyphsPath1, "B_.glif") - # expected result - metainfoPath2 = os.path.join(path2, "metainfo.plist") - fontinfoPath2 = os.path.join(path2, "fontinfo.plist") - kerningPath2 = os.path.join(path2, "kerning.plist") - groupsPath2 = os.path.join(path2, "groups.plist") - libPath2 = os.path.join(path2, "lib.plist") - featuresPath2 = os.path.join(path2, "features.plist") - glyphsPath2 = os.path.join(path2, "glyphs") - glyphsPath2_contents = os.path.join(glyphsPath2, "contents.plist") - glyphsPath2_A = os.path.join(glyphsPath2, "A_.glif") - glyphsPath2_B = os.path.join(glyphsPath2, "B_.glif") - # look for existence - self.assertEqual(os.path.exists(metainfoPath1), True) - self.assertEqual(os.path.exists(fontinfoPath1), True) - self.assertEqual(os.path.exists(kerningPath1), True) - self.assertEqual(os.path.exists(groupsPath1), True) - self.assertEqual(os.path.exists(libPath1), True) - self.assertEqual(os.path.exists(glyphsPath1), True) - self.assertEqual(os.path.exists(glyphsPath1_contents), True) - self.assertEqual(os.path.exists(glyphsPath1_A), True) - self.assertEqual(os.path.exists(glyphsPath1_B), True) - if testFeatures: - self.assertEqual(os.path.exists(featuresPath1), True) - # look for aggrement - data1 = readPlist(metainfoPath1) - data2 = readPlist(metainfoPath2) - self.assertEqual(data1, data2) - data1 = readPlist(fontinfoPath1) - self.assertEqual(sorted(data1.items()), sorted(expectedInfoData.items())) - data1 = readPlist(kerningPath1) - data2 = readPlist(kerningPath2) - self.assertEqual(data1, data2) - data1 = readPlist(groupsPath1) - data2 = readPlist(groupsPath2) - self.assertEqual(data1, data2) - data1 = readPlist(libPath1) - data2 = readPlist(libPath2) - if "UFO1" in libPath1: - for key in removeFromFormatVersion1Lib: - if key in data1: - del data1[key] - if "UFO1" in libPath2: - for key in removeFromFormatVersion1Lib: - if key in data2: - del data2[key] - self.assertEqual(data1, data2) - data1 = readPlist(glyphsPath1_contents) - data2 = readPlist(glyphsPath2_contents) - self.assertEqual(data1, data2) - data1 = readPlist(glyphsPath1_A) - data2 = readPlist(glyphsPath2_A) - self.assertEqual(data1, data2) - data1 = readPlist(glyphsPath1_B) - data2 = readPlist(glyphsPath2_B) - self.assertEqual(data1, data2) - - def test1To2(self): - path1 = self.getFontPath("TestFont1 (UFO1).ufo") - path2 = self.getFontPath("TestFont1 (UFO1) converted.ufo") - path3 = self.getFontPath("TestFont1 (UFO2).ufo") - convertUFOFormatVersion1ToFormatVersion2(path1, path2) - self.compareFileStructures(path2, path3, expectedFontInfo1To2Conversion, False) - - def test2To1(self): - path1 = self.getFontPath("TestFont1 (UFO2).ufo") - path2 = self.getFontPath("TestFont1 (UFO2) converted.ufo") - path3 = self.getFontPath("TestFont1 (UFO1).ufo") - convertUFOFormatVersion2ToFormatVersion1(path1, path2) - self.compareFileStructures(path2, path3, expectedFontInfo2To1Conversion, False) - - -if __name__ == "__main__": - from robofab.test.testSupport import runTests - runTests() diff --git a/misc/pylib/robofab/tools/__init__.py b/misc/pylib/robofab/tools/__init__.py deleted file mode 100755 index e97bd43c2..000000000 --- a/misc/pylib/robofab/tools/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - -Directory for all tool like code. -Stuff that doesn't really belong to objects, pens, compilers etc. -The code is split up into sections. - - -""" - - - - diff --git a/misc/pylib/robofab/tools/accentBuilder.py b/misc/pylib/robofab/tools/accentBuilder.py deleted file mode 100755 index d122222fd..000000000 --- a/misc/pylib/robofab/tools/accentBuilder.py +++ /dev/null @@ -1,348 +0,0 @@ -"""A simple set of tools for building accented glyphs. -# Hey look! A demonstration: -from robofab.accentBuilder import AccentTools, buildRelatedAccentList -font = CurrentFont -# a list of accented glyphs that you want to build -myList=['Aacute', 'aacute'] -# search for glyphs related to glyphs in myList and add them to myList -myList=buildRelatedAccentList(font, myList)+myList -# start the class -at=AccentTools(font, myList) -# clear away any anchors that exist (this is optional) -at.clearAnchors() -# add necessary anchors if you want to -at.buildAnchors(ucXOffset=20, ucYOffset=40, lcXOffset=15, lcYOffset=30) -# print a report of any errors that occured -at.printAnchorErrors() -# build the accented glyphs if you want to -at.buildAccents() -# print a report of any errors that occured -at.printAccentErrors() -""" -#XXX! This is *very* experimental! I think it works, but you never know. - -from robofab.gString import lowercase_plain, accents, uppercase_plain, splitAccent, findAccentBase -from robofab.tools.toolsAll import readGlyphConstructions -import robofab -from robofab.interface.all.dialogs import ProgressBar -from robofab.world import RFWorld -inFontLab = RFWorld().inFontLab - -anchorColor=125 -accentColor=75 - -def stripSuffix(glyphName): - """strip away unnecessary suffixes from a glyph name""" - if glyphName.find('.') != -1: - baseName = glyphName.split('.')[0] - if glyphName.find('.sc') != -1: - baseName = '.'.join([baseName, 'sc']) - return baseName - else: - return glyphName - -def buildRelatedAccentList(font, list): - """build a list of related glyphs suitable for use with AccentTools""" - searchList = [] - baseGlyphs = {} - foundList = [] - for glyphName in list: - splitNames = splitAccent(glyphName) - baseName = splitNames[0] - accentNames = splitNames[1] - if baseName not in searchList: - searchList.append(baseName) - if baseName not in baseGlyphs.keys(): - baseGlyphs[baseName] = [accentNames] - else: - baseGlyphs[baseName].append(accentNames) - foundGlyphs = findRelatedGlyphs(font, searchList, doAccents=0) - for baseGlyph in foundGlyphs.keys(): - for foundGlyph in foundGlyphs[baseGlyph]: - for accentNames in baseGlyphs[baseGlyph]: - foundList.append(makeAccentName(foundGlyph, accentNames)) - return foundList - -def findRelatedGlyphs(font, searchItem, doAccents=True): - """Gather up a bunch of related glyph names. Send it either a - single glyph name 'a', or a list of glyph names ['a', 'x'] and it - returns a dict like: {'a': ['atilde', 'a.alt', 'a.swash']}. if doAccents - is False it will skip accented glyph names. - This is a relatively slow operation!""" - relatedGlyphs = {} - for name in font.keys(): - base = name.split('.')[0] - if name not in relatedGlyphs.keys(): - relatedGlyphs[name] = [] - if base not in relatedGlyphs.keys(): - relatedGlyphs[base] = [] - if doAccents: - accentBase = findAccentBase(name) - if accentBase not in relatedGlyphs.keys(): - relatedGlyphs[accentBase] = [] - baseAccentBase = findAccentBase(base) - if baseAccentBase not in relatedGlyphs.keys(): - relatedGlyphs[baseAccentBase] = [] - if base != name and name not in relatedGlyphs[base]: - relatedGlyphs[base].append(name) - if doAccents: - if accentBase != name and name not in relatedGlyphs[accentBase]: - relatedGlyphs[accentBase].append(name) - if baseAccentBase != name and name not in relatedGlyphs[baseAccentBase]: - relatedGlyphs[baseAccentBase].append(name) - foundGlyphs = {} - if isinstance(searchItem, str): - searchList = [searchItem] - else: - searchList = searchItem - for glyph in searchList: - foundGlyphs[glyph] = relatedGlyphs[glyph] - return foundGlyphs - -def makeAccentName(baseName, accentNames): - """make an accented glyph name""" - if isinstance(accentNames, str): - accentNames = [accentNames] - build = [] - if baseName.find('.') != -1: - base = baseName.split('.')[0] - suffix = baseName.split('.')[1] - else: - base = baseName - suffix = '' - build.append(base) - for accent in accentNames: - build.append(accent) - buildJoin = ''.join(build) - name = '.'.join([buildJoin, suffix]) - return name - -def nameBuster(glyphName, glyphConstruct): - stripedSuffixName = stripSuffix(glyphName) - suffix = None - errors = [] - accentNames = [] - baseName = glyphName - if glyphName.find('.') != -1: - suffix = glyphName.split('.')[1] - if glyphName.find('.sc') != -1: - suffix = glyphName.split('.sc')[1] - if stripedSuffixName not in glyphConstruct.keys(): - errors.append('%s: %s not in glyph construction database'%(glyphName, stripedSuffixName)) - else: - if suffix is None: - baseName = glyphConstruct[stripedSuffixName][0] - else: - if glyphName.find('.sc') != -1: - baseName = ''.join([glyphConstruct[stripedSuffixName][0], suffix]) - else: - baseName = '.'.join([glyphConstruct[stripedSuffixName][0], suffix]) - accentNames = glyphConstruct[stripedSuffixName][1:] - return (baseName, stripedSuffixName, accentNames, errors) - -class AccentTools: - def __init__(self, font, accentList): - """several tools for working with anchors and building accents""" - self.glyphConstructions = readGlyphConstructions() - self.accentList = accentList - self.anchorErrors = ['ANCHOR ERRORS:'] - self.accentErrors = ['ACCENT ERRORS:'] - self.font = font - - def clearAnchors(self, doProgress=True): - """clear all anchors in the font""" - tickCount = len(self.font) - if doProgress: - bar = ProgressBar("Cleaning all anchors...", tickCount) - tick = 0 - for glyphName in self.accentList: - if doProgress: - bar.label(glyphName) - baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions) - existError = False - if len(errors) > 0: - existError = True - if not existError: - toClear = [baseName] - for accent, position in accentNames: - toClear.append(accent) - for glyphName in toClear: - try: - self.font[glyphName].clearAnchors() - except IndexError: pass - if doProgress: - bar.tick(tick) - tick = tick+1 - if doProgress: - bar.close() - - def buildAnchors(self, ucXOffset=0, ucYOffset=0, lcXOffset=0, lcYOffset=0, markGlyph=True, doProgress=True): - """add the necessary anchors to the glyphs if they don't exist - some flag definitions: - uc/lc/X/YOffset=20 offset values for the anchors - markGlyph=1 mark the glyph that is created - doProgress=1 show a progress bar""" - accentOffset = 10 - tickCount = len(self.accentList) - if doProgress: - bar = ProgressBar('Adding anchors...', tickCount) - tick = 0 - for glyphName in self.accentList: - if doProgress: - bar.label(glyphName) - previousPositions = {} - baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions) - existError = False - if len(errors) > 0: - existError = True - for anchorError in errors: - self.anchorErrors.append(anchorError) - if not existError: - existError = False - try: - self.font[baseName] - except IndexError: - self.anchorErrors.append(' '.join([glyphName, ':', baseName, 'does not exist.'])) - existError = True - for accentName, accentPosition in accentNames: - try: - self.font[accentName] - except IndexError: - self.anchorErrors.append(' '.join([glyphName, ':', accentName, 'does not exist.'])) - existError = True - if not existError: - #glyph = self.font.newGlyph(glyphName, clear=True) - for accentName, accentPosition in accentNames: - if baseName.split('.')[0] in lowercase_plain: - xOffset = lcXOffset-accentOffset - yOffset = lcYOffset-accentOffset - else: - xOffset = ucXOffset-accentOffset - yOffset = ucYOffset-accentOffset - # should I add a cedilla and ogonek yoffset override here? - if accentPosition not in previousPositions.keys(): - self._dropAnchor(self.font[baseName], accentPosition, xOffset, yOffset) - if markGlyph: - self.font[baseName].mark = anchorColor - if inFontLab: - self.font[baseName].update() - else: - self._dropAnchor(self.font[previousPositions[accentPosition]], accentPosition, xOffset, yOffset) - self._dropAnchor(self.font[accentName], accentPosition, accentOffset, accentOffset, doAccentPosition=1) - previousPositions[accentPosition] = accentName - if markGlyph: - self.font[accentName].mark = anchorColor - if inFontLab: - self.font[accentName].update() - if inFontLab: - self.font.update() - if doProgress: - bar.tick(tick) - tick = tick+1 - if doProgress: - bar.close() - - def printAnchorErrors(self): - """print errors encounted during buildAnchors""" - if len(self.anchorErrors) == 1: - print 'No anchor errors encountered' - else: - for i in self.anchorErrors: - print i - - def _dropAnchor(self, glyph, positionName, xOffset=0, yOffset=0, doAccentPosition=False): - """anchor adding method. for internal use only.""" - existingAnchorNames = [] - for anchor in glyph.getAnchors(): - existingAnchorNames.append(anchor.name) - if doAccentPosition: - positionName = ''.join(['_', positionName]) - if positionName not in existingAnchorNames: - glyphLeft, glyphBottom, glyphRight, glyphTop = glyph.box - glyphXCenter = glyph.width/2 - if positionName == 'top': - glyph.appendAnchor(positionName, (glyphXCenter, glyphTop+yOffset)) - elif positionName == 'bottom': - glyph.appendAnchor(positionName, (glyphXCenter, glyphBottom-yOffset)) - elif positionName == 'left': - glyph.appendAnchor(positionName, (glyphLeft-xOffset, glyphTop)) - elif positionName == 'right': - glyph.appendAnchor(positionName, (glyphRight+xOffset, glyphTop)) - elif positionName == '_top': - glyph.appendAnchor(positionName, (glyphXCenter, glyphBottom-yOffset)) - elif positionName == '_bottom': - glyph.appendAnchor(positionName, (glyphXCenter, glyphTop+yOffset)) - elif positionName == '_left': - glyph.appendAnchor(positionName, (glyphRight+xOffset, glyphTop)) - elif positionName == '_right': - glyph.appendAnchor(positionName, (glyphLeft-xOffset, glyphTop)) - if inFontLab: - glyph.update() - - def buildAccents(self, clear=True, adjustWidths=True, markGlyph=True, doProgress=True): - """build accented glyphs. some flag definitions: - clear=1 clear the glyphs if they already exist - markGlyph=1 mark the glyph that is created - doProgress=1 show a progress bar - adjustWidths=1 will fix right and left margins when left or right accents are added""" - tickCount = len(self.accentList) - if doProgress: - bar = ProgressBar('Building accented glyphs...', tickCount) - tick = 0 - for glyphName in self.accentList: - if doProgress: - bar.label(glyphName) - existError = False - anchorError = False - - baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions) - if len(errors) > 0: - existError = True - for accentError in errors: - self.accentErrors.append(accentError) - - if not existError: - baseAnchors = [] - try: - self.font[baseName] - except IndexError: - self.accentErrors.append('%s: %s does not exist.'%(glyphName, baseName)) - existError = True - else: - for anchor in self.font[baseName].anchors: - baseAnchors.append(anchor.name) - for accentName, accentPosition in accentNames: - accentAnchors = [] - try: - self.font[accentName] - except IndexError: - self.accentErrors.append('%s: %s does not exist.'%(glyphName, accentName)) - existError = True - else: - for anchor in self.font[accentName].getAnchors(): - accentAnchors.append(anchor.name) - if accentPosition not in baseAnchors: - self.accentErrors.append('%s: %s not in %s anchors.'%(glyphName, accentPosition, baseName)) - anchorError = True - if ''.join(['_', accentPosition]) not in accentAnchors: - self.accentErrors.append('%s: %s not in %s anchors.'%(glyphName, ''.join(['_', accentPosition]), accentName)) - anchorError = True - if not existError and not anchorError: - destination = self.font.compileGlyph(glyphName, baseName, self.glyphConstructions[stripedSuffixName][1:], adjustWidths) - if markGlyph: - destination.mark = accentColor - if doProgress: - bar.tick(tick) - tick = tick+1 - if doProgress: - bar.close() - - def printAccentErrors(self): - """print errors encounted during buildAccents""" - if len(self.accentErrors) == 1: - print 'No accent errors encountered' - else: - for i in self.accentErrors: - print i - - diff --git a/misc/pylib/robofab/tools/fontlabFeatureSplitter.py b/misc/pylib/robofab/tools/fontlabFeatureSplitter.py deleted file mode 100644 index 3e0173dfc..000000000 --- a/misc/pylib/robofab/tools/fontlabFeatureSplitter.py +++ /dev/null @@ -1,85 +0,0 @@ -import re - -featureRE = re.compile( - "^" # start of line - "\s*" # - "feature" # feature - "\s+" # - "(\w{4})" # four alphanumeric characters - "\s*" # - "\{" # { - , re.MULTILINE # run in multiline to preserve line seps -) - -def splitFeaturesForFontLab(text): - """ - >>> result = splitFeaturesForFontLab(testText) - >>> result == expectedTestResult - True - """ - classes = "" - features = [] - while text: - m = featureRE.search(text) - if m is None: - classes = text - text = "" - else: - start, end = m.span() - # if start is not zero, this is the first match - # and all previous lines are part of the "classes" - if start > 0: - assert not classes - classes = text[:start] - # extract the current feature - featureName = m.group(1) - featureText = text[start:end] - text = text[end:] - # grab all text before the next feature definition - # and add it to the current definition - if text: - m = featureRE.search(text) - if m is not None: - start, end = m.span() - featureText += text[:start] - text = text[start:] - else: - featureText += text - text = "" - # store the feature - features.append((featureName, featureText)) - return classes, features - -testText = """ -@class1 = [a b c d]; - -feature liga { - sub f i by fi; -} liga; - -@class2 = [x y z]; - -feature salt { - sub a by a.alt; -} salt; feature ss01 {sub x by x.alt} ss01; - -feature ss02 {sub y by y.alt} ss02; - -# feature calt { -# sub a b' by b.alt; -# } calt; -""" - -expectedTestResult = ( - "\n@class1 = [a b c d];\n", - [ - ("liga", "\nfeature liga {\n sub f i by fi;\n} liga;\n\n@class2 = [x y z];\n"), - ("salt", "\nfeature salt {\n sub a by a.alt;\n} salt; feature ss01 {sub x by x.alt} ss01;\n"), - ("ss02", "\nfeature ss02 {sub y by y.alt} ss02;\n\n# feature calt {\n# sub a b' by b.alt;\n# } calt;\n") - ] -) - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/misc/pylib/robofab/tools/glifExport.py b/misc/pylib/robofab/tools/glifExport.py deleted file mode 100755 index bacb9190a..000000000 --- a/misc/pylib/robofab/tools/glifExport.py +++ /dev/null @@ -1,95 +0,0 @@ -"""Tool for exporting GLIFs from FontLab""" - -import FL -import os -from robofab.interface.all.dialogs import ProgressBar -from robofab.glifLib import GlyphSet -from robofab.tools.glifImport import GlyphPlaceholder -from robofab.pens.flPen import drawFLGlyphOntoPointPen - - -def exportGlyph(glyphName, flGlyph, glyphSet): - """Export a FontLab glyph.""" - - glyph = GlyphPlaceholder() - glyph.width = flGlyph.width - glyph.unicodes = flGlyph.unicodes - if flGlyph.note: - glyph.note = flGlyph.note - customdata = flGlyph.customdata - if customdata: - from cStringIO import StringIO - from robofab.plistlib import readPlist, Data - f = StringIO(customdata) - try: - glyph.lib = readPlist(f) - except: # XXX ugh, plistlib can raise lots of things - # Anyway, customdata does not contain valid plist data, - # but we don't need to toss it! - glyph.lib = {"org.robofab.fontlab.customdata": Data(customdata)} - - def drawPoints(pen): - # whoohoo, nested scopes are cool. - drawFLGlyphOntoPointPen(flGlyph, pen) - - glyphSet.writeGlyph(glyphName, glyph, drawPoints) - - -def exportGlyphs(font, glyphs=None, dest=None, doProgress=True, bar=None): - """Export all glyphs in a FontLab font""" - if dest is None: - dir, base = os.path.split(font.file_name) - base = base.split(".")[0] + ".glyphs" - dest = os.path.join(dir, base) - - if not os.path.exists(dest): - os.makedirs(dest) - - glyphSet = GlyphSet(dest) - - if glyphs is None: - indices = range(len(font)) - else: - indices = [] - for glyphName in glyphs: - indices.append(font.FindGlyph(glyphName)) - barStart = 0 - closeBar = False - if doProgress: - if not bar: - bar = ProgressBar("Exporting Glyphs", len(indices)) - closeBar = True - else: - barStart = bar.getCurrentTick() - else: - bar = None - try: - done = {} - for i in range(len(indices)): - #if not (i % 10) and not bar.tick(i + barStart): - # raise KeyboardInterrupt - index = indices[i] - flGlyph = font[index] - if flGlyph is None: - continue - glyphName = flGlyph.name - if not glyphName: - print "can't dump glyph #%s, it has no glyph name" % i - else: - if glyphName in done: - n = 1 - while ("%s#%s" % (glyphName, n)) in done: - n += 1 - glyphName = "%s#%s" % (glyphName, n) - done[glyphName] = None - exportGlyph(glyphName, flGlyph, glyphSet) - if bar and not i % 10: - bar.tick(barStart + i) - # Write out contents.plist - glyphSet.writeContents() - except KeyboardInterrupt: - if bar: - bar.close() - bar = None - if bar and closeBar: - bar.close() diff --git a/misc/pylib/robofab/tools/glifImport.py b/misc/pylib/robofab/tools/glifImport.py deleted file mode 100755 index 68d14460a..000000000 --- a/misc/pylib/robofab/tools/glifImport.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Tools for importing GLIFs into FontLab""" - -import os -from FL import fl -from robofab.tools.toolsFL import NewGlyph, FontIndex -from robofab.pens.flPen import FLPointPen -from robofab.glifLib import GlyphSet -from robofab.interface.all.dialogs import ProgressBar, GetFolder - - -class GlyphPlaceholder: - pass - - -def importAllGlifFiles(font, dirName=None, doProgress=True, bar=None): - """import all GLIFs into a FontLab font""" - if dirName is None: - if font.file_name: - dir, base = os.path.split(font.file_name) - base = base.split(".")[0] + ".glyphs" - dirName = os.path.join(dir, base) - else: - dirName = GetFolder("Please select a folder with .glif files") - glyphSet = GlyphSet(dirName) - glyphNames = glyphSet.keys() - glyphNames.sort() - barStart = 0 - closeBar = False - if doProgress: - if not bar: - bar = ProgressBar("Importing Glyphs", len(glyphNames)) - closeBar = True - else: - barStart = bar.getCurrentTick() - else: - bar = None - try: - for i in range(len(glyphNames)): - #if not (i % 10) and not bar.tick(barStart + i): - # raise KeyboardInterrupt - glyphName = glyphNames[i] - flGlyph = NewGlyph(font, glyphName, clear=True) - pen = FLPointPen(flGlyph) - glyph = GlyphPlaceholder() - glyphSet.readGlyph(glyphName, glyph, pen) - if hasattr(glyph, "width"): - flGlyph.width = int(round(glyph.width)) - if hasattr(glyph, "unicodes"): - flGlyph.unicodes = glyph.unicodes - if hasattr(glyph, "note"): - flGlyph.note = glyph.note # XXX must encode - if hasattr(glyph, "lib"): - from cStringIO import StringIO - from robofab.plistlib import writePlist - lib = glyph.lib - if lib: - if len(lib) == 1 and "org.robofab.fontlab.customdata" in lib: - data = lib["org.robofab.fontlab.customdata"].data - else: - f = StringIO() - writePlist(glyph.lib, f) - data = f.getvalue() - flGlyph.customdata = data - # XXX the next bit is only correct when font is the current font :-( - fl.UpdateGlyph(font.FindGlyph(glyphName)) - if bar and not i % 10: - bar.tick(barStart + i) - except KeyboardInterrupt: - if bar: - bar.close() - bar = None - fl.UpdateFont(FontIndex(font)) - if bar and closeBar: - bar.close() diff --git a/misc/pylib/robofab/tools/glyphConstruction.py b/misc/pylib/robofab/tools/glyphConstruction.py deleted file mode 100644 index afe098423..000000000 --- a/misc/pylib/robofab/tools/glyphConstruction.py +++ /dev/null @@ -1,565 +0,0 @@ - -_glyphConstruction = """\ -# -# RoboFab Glyph Construction Database -# -# format: -# Glyphname: BaseGlyph Accent.RelativePosition* Accent.RelativePosition* -# *RelativePosition can be top, bottom, left, right -# -# NOTE: this is not a comprehensive, or even accurate, glyph list. -# It was built by Python robots and, in many cases, by tired human hands. -# Please report any omissions, errors or praise to the local RoboFab authorities. -# -##: Uppercase -AEacute: AE acute.top -AEmacron: AE macron.top -Aacute: A acute.top -Abreve: A breve.top -Abreveacute: A breve.top acute.top -Abrevedotaccent: A breve.top dotaccent.bottom -Abrevegrave: A breve.top grave.top -Abrevetilde: A breve.top tilde.top -Acaron: A caron.top -Acircumflex: A circumflex.top -Acircumflexacute: A circumflex.top acute.top -Acircumflexdotaccent: A circumflex.top dotaccent.bottom -Acircumflexgrave: A circumflex.top grave.top -Acircumflextilde: A circumflex.top tilde.top -Adblgrave: A dblgrave.top -Adieresis: A dieresis.top -Adieresismacron: A dieresis.top macron.top -Adotaccent: A dotaccent.top -Adotaccentmacron: A dotaccent.top macron.top -Agrave: A grave.top -Amacron: A macron.top -Aogonek: A ogonek.bottom -Aring: A ring.top -Aringacute: A ring.top acute.top -Atilde: A tilde.top -Bdotaccent: B dotaccent.top -Cacute: C acute.top -Ccaron: C caron.top -Ccedilla: C cedilla.bottom -Ccedillaacute: C cedilla.bottom acute.top -Ccircumflex: C circumflex.top -Cdotaccent: C dotaccent.top -Dcaron: D caron.top -Dcedilla: D cedilla.bottom -Ddotaccent: D dotaccent.top -Eacute: E acute.top -Ebreve: E breve.top -Ecaron: E caron.top -Ecedilla: E cedilla.bottom -Ecedillabreve: E cedilla.bottom breve.top -Ecircumflex: E circumflex.top -Ecircumflexacute: E circumflex.top acute.top -Ecircumflexdotaccent: E circumflex.top dotaccent.bottom -Ecircumflexgrave: E circumflex.top grave.top -Ecircumflextilde: E circumflex.top tilde.top -Edblgrave: E dblgrave.top -Edieresis: E dieresis.top -Edotaccent: E dotaccent.top -Egrave: E grave.top -Emacron: E macron.top -Emacronacute: E macron.top acute.top -Emacrongrave: E macron.top grave.top -Eogonek: E ogonek.bottom -Etilde: E tilde.top -Fdotaccent: F dotaccent.top -Gacute: G acute.top -Gbreve: G breve.top -Gcaron: G caron.top -Gcedilla: G cedilla.bottom -Gcircumflex: G circumflex.top -Gcommaaccent: G commaaccent.bottom -Gdotaccent: G dotaccent.top -Gmacron: G macron.top -Hcaron: H caron.top -Hcedilla: H cedilla.top -Hcircumflex: H circumflex.top -Hdieresis: H dieresis.top -Hdotaccent: H dotaccent.top -Iacute: I acute.top -Ibreve: I breve.top -Icaron: I caron.top -Icircumflex: I circumflex.top -Idblgrave: I dblgrave.top -Idieresis: I dieresis.top -Idieresisacute: I dieresis.top acute.top -Idotaccent: I dotaccent.top -Igrave: I grave.top -Imacron: I macron.top -Iogonek: I ogonek.bottom -Itilde: I tilde.top -Jcircumflex: J circumflex.top -Kacute: K acute.top -Kcaron: K caron.top -Kcedilla: K cedilla.bottom -Kcommaaccent: K commaaccent.bottom -Lacute: L acute.top -Lcaron: L commaaccent.right -Lcedilla: L cedilla.bottom -Lcommaaccent: L commaaccent.bottom -Ldot: L dot.right -Ldotaccent: L dotaccent.bottom -Ldotaccentmacron: L dotaccent.bottom macron.top -Macute: M acute.top -Mdotaccent: M dotaccent.top -Nacute: N acute.top -Ncaron: N caron.top -Ncedilla: N cedilla.bottom -Ncommaaccent: N commaaccent.bottom -Ndotaccent: N dotaccent.top -Ngrave: N grave.top -Ntilde: N tilde.top -Oacute: O acute.top -Obreve: O breve.top -Ocaron: O caron.top -Ocircumflex: O circumflex.top -Ocircumflexacute: O circumflex.top acute.top -Ocircumflexdotaccent: O circumflex.top dotaccent.bottom -Ocircumflexgrave: O circumflex.top grave.top -Ocircumflextilde: O circumflex.top tilde.top -Odblgrave: O dblgrave.top -Odieresis: O dieresis.top -Odieresismacron: O dieresis.top macron.top -Ograve: O grave.top -Ohungarumlaut: O hungarumlaut.top -Omacron: O macron.top -Omacronacute: O macron.top acute.top -Omacrongrave: O macron.top grave.top -Oogonek: O ogonek.bottom -Oogonekmacron: O ogonek.bottom macron.top -Oslashacute: Oslash acute.top -Otilde: O tilde.top -Otildeacute: O tilde.top acute.top -Otildedieresis: O tilde.top dieresis.top -Otildemacron: O tilde.top macron.top -Pacute: P acute.top -Pdotaccent: P dotaccent.top -Racute: R acute.top -Rcaron: R caron.top -Rcedilla: R cedilla.bottom -Rcommaaccent: R commaaccent.bottom -Rdblgrave: R dblgrave.top -Rdotaccent: R dotaccent.top -Rdotaccentmacron: R dotaccent.top macron.top -Sacute: S acute.top -Sacutedotaccent: S acute.top dotaccent.top -Scaron: S caron.top -Scarondotaccent: S caron.top dotaccent.top -Scedilla: S cedilla.bottom -Scircumflex: S circumflex.top -Scommaaccent: S commaaccent.bottom -Sdotaccent: S dotaccent.top -Tcaron: T caron.top -Tcedilla: T cedilla.bottom -Tcommaaccent: T commaaccent.bottom -Tdotaccent: T dotaccent.top -Uacute: U acute.top -Ubreve: U breve.top -Ucaron: U caron.top -Ucircumflex: U circumflex.top -Udblgrave: U dblgrave.top -Udieresis: U dieresis.top -Udieresisacute: U dieresis.top acute.top -Udieresiscaron: U dieresis.top caron.top -Udieresisgrave: U dieresis.top grave.top -Udieresismacron: U dieresis.top macron.top -Ugrave: U grave.top -Uhungarumlaut: U hungarumlaut.top -Umacron: U macron.top -Umacrondieresis: U macron.top dieresis.top -Uogonek: U ogonek.bottom -Uring: U ring.top -Utilde: U tilde.top -Utildeacute: U tilde.top acute.top -Vtilde: V tilde.top -Wacute: W acute.top -Wcircumflex: W circumflex.top -Wdieresis: W dieresis.top -Wdotaccent: W dotaccent.top -Wgrave: W grave.top -Xdieresis: X dieresis.top -Xdotaccent: X dotaccent.top -Yacute: Y acute.top -Ycircumflex: Y circumflex.top -Ydieresis: Y dieresis.top -Ydotaccent: Y dotaccent.top -Ygrave: Y grave.top -Ytilde: Y tilde.top -Zacute: Z acute.top -Zcaron: Z caron.top -Zcircumflex: Z circumflex.top -Zdotaccent: Z dotaccent.top -##: Lowercase -aacute: a acute.top -abreve: a breve.top -abreveacute: a breve.top acute.top -abrevedotaccent: a breve.top dotaccent.bottom -abrevegrave: a breve.top grave.top -abrevetilde: a breve.top tilde.top -acaron: a caron.top -acircumflex: a circumflex.top -acircumflexacute: a circumflex.top acute.top -acircumflexdotaccent: a circumflex.top dotaccent.bottom -acircumflexgrave: a circumflex.top grave.top -acircumflextilde: a circumflex.top tilde.top -adblgrave: a dblgrave.top -adieresis: a dieresis.top -adieresismacron: a dieresis.top macron.top -adotaccent: a dotaccent.top -adotaccentmacron: a dotaccent.top macron.top -aeacute: ae acute.top -aemacron: ae macron.top -agrave: a grave.top -amacron: a macron.top -aogonek: a ogonek.bottom -aring: a ring.top -aringacute: a ring.top acute.top -atilde: a tilde.top -bdotaccent: b dotaccent.top -cacute: c acute.top -ccaron: c caron.top -ccedilla: c cedilla.bottom -ccedillaacute: c cedilla.bottom acute.top -ccircumflex: c circumflex.top -cdotaccent: c dotaccent.top -dcaron: d commaaccent.right -dcedilla: d cedilla.bottom -ddotaccent: d dotaccent.top -dmacron: d macron.top -eacute: e acute.top -ebreve: e breve.top -ecaron: e caron.top -ecedilla: e cedilla.bottom -ecedillabreve: e cedilla.bottom breve.top -ecircumflex: e circumflex.top -ecircumflexacute: e circumflex.top acute.top -ecircumflexdotaccent: e circumflex.top dotaccent.bottom -ecircumflexgrave: e circumflex.top grave.top -ecircumflextilde: e circumflex.top tilde.top -edblgrave: e dblgrave.top -edieresis: e dieresis.top -edotaccent: e dotaccent.top -egrave: e grave.top -emacron: e macron.top -emacronacute: e macron.top acute.top -emacrongrave: e macron.top grave.top -eogonek: e ogonek.bottom -etilde: e tilde.top -fdotaccent: f dotaccent.top -gacute: g acute.top -gbreve: g breve.top -gcaron: g caron.top -gcedilla: g cedilla.top -gcircumflex: g circumflex.top -gcommaaccent: g commaaccent.top -gdotaccent: g dotaccent.top -gmacron: g macron.top -hcaron: h caron.top -hcedilla: h cedilla.bottom -hcircumflex: h circumflex.top -hdieresis: h dieresis.top -hdotaccent: h dotaccent.top -iacute: dotlessi acute.top -ibreve: dotlessi breve.top -icaron: dotlessi caron.top -icircumflex: dotlessi circumflex.top -idblgrave: dotlessi dblgrave.top -idieresis: dotlessi dieresis.top -idieresisacute: dotlessi dieresis.top acute.top -igrave: dotlessi grave.top -imacron: dotlessi macron.top -iogonek: i ogonek.bottom -itilde: dotlessi tilde.top -jcaron: dotlessj caron.top -jcircumflex: dotlessj circumflex.top -jacute: dotlessj acute.top -kacute: k acute.top -kcaron: k caron.top -kcedilla: k cedilla.bottom -kcommaaccent: k commaaccent.bottom -lacute: l acute.top -lcaron: l commaaccent.right -lcedilla: l cedilla.bottom -lcommaaccent: l commaaccent.bottom -ldot: l dot.right -ldotaccent: l dotaccent.bottom -ldotaccentmacron: l dotaccent.bottom macron.top -macute: m acute.top -mdotaccent: m dotaccent.top -nacute: n acute.top -ncaron: n caron.top -ncedilla: n cedilla.bottom -ncommaaccent: n commaaccent.bottom -ndotaccent: n dotaccent.top -ngrave: n grave.top -ntilde: n tilde.top -oacute: o acute.top -obreve: o breve.top -ocaron: o caron.top -ocircumflex: o circumflex.top -ocircumflexacute: o circumflex.top acute.top -ocircumflexdotaccent: o circumflex.top dotaccent.bottom -ocircumflexgrave: o circumflex.top grave.top -ocircumflextilde: o circumflex.top tilde.top -odblgrave: o dblgrave.top -odieresis: o dieresis.top -odieresismacron: o dieresis.top macron.top -ograve: o grave.top -ohungarumlaut: o hungarumlaut.top -omacron: o macron.top -omacronacute: o macron.top acute.top -omacrongrave: o macron.top grave.top -oogonek: o ogonek.bottom -oogonekmacron: o ogonek.bottom macron.top -oslashacute: oslash acute.top -otilde: o tilde.top -otildeacute: o tilde.top acute.top -otildedieresis: o tilde.top dieresis.top -otildemacron: o tilde.top macron.top -pacute: p acute.top -pdotaccent: p dotaccent.top -racute: r acute.top -rcaron: r caron.top -rcedilla: r cedilla.bottom -rcommaaccent: r commaaccent.bottom -rdblgrave: r dblgrave.top -rdotaccent: r dotaccent.top -rdotaccentmacron: r dotaccent.top macron.top -sacute: s acute.top -sacutedotaccent: s acute.top dotaccent.top -scaron: s caron.top -scarondotaccent: s caron.top dotaccent.top -scedilla: s cedilla.bottom -scircumflex: s circumflex.top -scommaaccent: s commaaccent.bottom -sdotaccent: s dotaccent.top -tcaron: t commaaccent.right -tcedilla: t cedilla.bottom -tcommaaccent: t commaaccent.bottom -tdieresis: t dieresis.top -tdotaccent: t dotaccent.top -uacute: u acute.top -ubreve: u breve.top -ucaron: u caron.top -ucircumflex: u circumflex.top -udblgrave: u dblgrave.top -udieresis: u dieresis.top -udieresisacute: u dieresis.top acute.top -udieresiscaron: u dieresis.top caron.top -udieresisgrave: u dieresis.top grave.top -udieresismacron: u dieresis.top macron.top -ugrave: u grave.top -uhungarumlaut: u hungarumlaut.top -umacron: u macron.top -umacrondieresis: u macron.top dieresis.top -uogonek: u ogonek.bottom -uring: u ring.top -utilde: u tilde.top -utildeacute: u tilde.top acute.top -vtilde: v tilde.top -wacute: w acute.top -wcircumflex: w circumflex.top -wdieresis: w dieresis.top -wdotaccent: w dotaccent.top -wgrave: w grave.top -wring: w ring.top -xdieresis: x dieresis.top -xdotaccent: x dotaccent.top -yacute: y acute.top -ycircumflex: y circumflex.top -ydieresis: y dieresis.top -ydotaccent: y dotaccent.top -ygrave: y grave.top -yring: y ring.top -ytilde: y tilde.top -zacute: z acute.top -zcaron: z caron.top -zcircumflex: z circumflex.top -zdotaccent: z dotaccent.top -##: Small: Caps -AEacute.sc: AE.sc acute.top -AEmacron.sc: AE.sc macron.top -Aacute.sc: A.sc acute.top -Abreve.sc: A.sc breve.top -Abreveacute.sc: A.sc breve.top acute.top -Abrevedotaccent.sc: A.sc breve.top dotaccent.bottom -Abrevegrave.sc: A.sc breve.top grave.top -Abrevetilde.sc: A.sc breve.top tilde.top -Acaron.sc: A.sc caron.top -Acircumflex.sc: A.sc circumflex.top -Acircumflexacute.sc: A.sc circumflex.top acute.top -Acircumflexdotaccent.sc: A.sc circumflex.top dotaccent.bottom -Acircumflexgrave.sc: A.sc circumflex.top grave.top -Acircumflextilde.sc: A.sc circumflex.top tilde.top -Adblgrave.sc: A.sc dblgrave.top -Adieresis.sc: A.sc dieresis.top -Adieresismacron.sc: A.sc dieresis.top macron.top -Adotaccent.sc: A.sc dotaccent.top -Adotaccentmacron.sc: A.sc dotaccent.top macron.top -Agrave.sc: A.sc grave.top -Amacron.sc: A.sc macron.top -Aogonek.sc: A.sc ogonek.bottom -Aring.sc: A.sc ring.top -Aringacute.sc: A.sc ring.top acute.top -Atilde.sc: A.sc tilde.top -Bdotaccent.sc: B.sc dotaccent.top -Cacute.sc: C.sc acute.top -Ccaron.sc: C.sc caron.top -Ccedilla.sc: C.sc cedilla.bottom -Ccedillaacute.sc: C.sc cedilla.bottom acute.top -Ccircumflex.sc: C.sc circumflex.top -Cdotaccent.sc: C.sc dotaccent.top -Dcaron.sc: D.sc caron.top -Dcedilla.sc: D.sc cedilla.bottom -Ddotaccent.sc: D.sc dotaccent.top -Eacute.sc: E.sc acute.top -Ebreve.sc: E.sc breve.top -Ecaron.sc: E.sc caron.top -Ecedilla.sc: E.sc cedilla.bottom -Ecedillabreve.sc: E.sc cedilla.bottom breve.top -Ecircumflex.sc: E.sc circumflex.top -Ecircumflexacute.sc: E.sc circumflex.top acute.top -Ecircumflexdotaccent.sc: E.sc circumflex.top dotaccent.bottom -Ecircumflexgrave.sc: E.sc circumflex.top grave.top -Ecircumflextilde.sc: E.sc circumflex.top tilde.top -Edblgrave.sc: E.sc dblgrave.top -Edieresis.sc: E.sc dieresis.top -Edotaccent.sc: E.sc dotaccent.top -Egrave.sc: E.sc grave.top -Emacron.sc: E.sc macron.top -Emacronacute.sc: E.sc macron.top acute.top -Emacrongrave.sc: E.sc macron.top grave.top -Eogonek.sc: E.sc ogonek.bottom -Etilde.sc: E.sc tilde.top -Fdotaccent.sc: F.sc dotaccent.top -Gacute.sc: G.sc acute.top -Gbreve.sc: G.sc breve.top -Gcaron.sc: G.sc caron.top -Gcedilla.sc: G.sc cedilla.bottom -Gcircumflex.sc: G.sc circumflex.top -Gcommaaccent.sc: G.sc commaaccent.bottom -Gdotaccent.sc: G.sc dotaccent.top -Gmacron.sc: G.sc macron.top -Hcaron.sc: H.sc caron.top -Hcedilla.sc: H.sc cedilla.top -Hcircumflex.sc: H.sc circumflex.top -Hdieresis.sc: H.sc dieresis.top -Hdotaccent.sc: H.sc dotaccent.top -Iacute.sc: I.sc acute.top -Ibreve.sc: I.sc breve.top -Icaron.sc: I.sc caron.top -Icircumflex.sc: I.sc circumflex.top -Idblgrave.sc: I.sc dblgrave.top -Idieresis.sc: I.sc dieresis.top -Idieresisacute.sc: I.sc dieresis.top acute.top -Idotaccent.sc: I.sc dotaccent.top -Igrave.sc: I.sc grave.top -Imacron.sc: I.sc macron.top -Iogonek.sc: I.sc ogonek.bottom -Itilde.sc: I.sc tilde.top -Jcircumflex.sc: J.sc circumflex.top -Kacute.sc: K.sc acute.top -Kcaron.sc: K.sc caron.top -Kcedilla.sc: K.sc cedilla.bottom -Kcommaaccent.sc: K.sc commaaccent.bottom -Lacute.sc: L.sc acute.top -Lcaron.sc: L.sc commaaccent.right -Lcedilla.sc: L.sc cedilla.bottom -Lcommaaccent.sc: L.sc commaaccent.bottom -Ldot.sc: L.sc dot.right -Ldotaccent.sc: L.sc dotaccent.bottom -Ldotaccentmacron.sc: L.sc dotaccent.bottom macron.top -Macute.sc: M.sc acute.top -Mdotaccent.sc: M.sc dotaccent.top -Nacute.sc: N.sc acute.top -Ncaron.sc: N.sc caron.top -Ncedilla.sc: N.sc cedilla.bottom -Ncommaaccent.sc: N.sc commaaccent.bottom -Ndotaccent.sc: N.sc dotaccent.top -Ngrave.sc: N.sc grave.top -Ntilde.sc: N.sc tilde.top -Oacute.sc: O.sc acute.top -Obreve.sc: O.sc breve.top -Ocaron.sc: O.sc caron.top -Ocircumflex.sc: O.sc circumflex.top -Ocircumflexacute.sc: O.sc circumflex.top acute.top -Ocircumflexdotaccent.sc: O.sc circumflex.top dotaccent.bottom -Ocircumflexgrave.sc: O.sc circumflex.top grave.top -Ocircumflextilde.sc: O.sc circumflex.top tilde.top -Odblgrave.sc: O.sc dblgrave.top -Odieresis.sc: O.sc dieresis.top -Odieresismacron.sc: O.sc dieresis.top macron.top -Ograve.sc: O.sc grave.top -Ohungarumlaut.sc: O.sc hungarumlaut.top -Omacron.sc: O.sc macron.top -Omacronacute.sc: O.sc macron.top acute.top -Omacrongrave.sc: O.sc macron.top grave.top -Oogonek.sc: O.sc ogonek.bottom -Oogonekmacron.sc: O.sc ogonek.bottom macron.top -Oslashacute.sc: Oslash.sc acute.top -Otilde.sc: O.sc tilde.top -Otildeacute.sc: O.sc tilde.top acute.top -Otildedieresis.sc: O.sc tilde.top dieresis.top -Otildemacron.sc: O.sc tilde.top macron.top -Pacute.sc: P.sc acute.top -Pdotaccent.sc: P.sc dotaccent.top -Racute.sc: R.sc acute.top -Rcaron.sc: R.sc caron.top -Rcedilla.sc: R.sc cedilla.bottom -Rcommaaccent.sc: R.sc commaaccent.bottom -Rdblgrave.sc: R.sc dblgrave.top -Rdotaccent.sc: R.sc dotaccent.top -Rdotaccentmacron.sc: R.sc dotaccent.top macron.top -Sacute.sc: S.sc acute.top -Sacutedotaccent.sc: S.sc acute.top dotaccent.top -Scaron.sc: S.sc caron.top -Scarondotaccent.sc: S.sc caron.top dotaccent.top -Scedilla.sc: S.sc cedilla.bottom -Scircumflex.sc: S.sc circumflex.top -Scommaaccent.sc: S.sc commaaccent.bottom -Sdotaccent.sc: S.sc dotaccent.top -Tcaron.sc: T.sc caron.top -Tcedilla.sc: T.sc cedilla.bottom -Tcommaaccent.sc: T.sc commaaccent.bottom -Tdotaccent.sc: T.sc dotaccent.top -Uacute.sc: U.sc acute.top -Ubreve.sc: U.sc breve.top -Ucaron.sc: U.sc caron.top -Ucircumflex.sc: U.sc circumflex.top -Udblgrave.sc: U.sc dblgrave.top -Udieresis.sc: U.sc dieresis.top -Udieresisacute.sc: U.sc dieresis.top acute.top -Udieresiscaron.sc: U.sc dieresis.top caron.top -Udieresisgrave.sc: U.sc dieresis.top grave.top -Udieresismacron.sc: U.sc dieresis.top macron.top -Ugrave.sc: U.sc grave.top -Uhungarumlaut.sc: U.sc hungarumlaut.top -Umacron.sc: U.sc macron.top -Umacrondieresis.sc: U.sc macron.top dieresis.top -Uogonek.sc: U.sc ogonek.bottom -Uring.sc: U.sc ring.top -Utilde.sc: U.sc tilde.top -Utildeacute.sc: U.sc tilde.top acute.top -Vtilde.sc: V.sc tilde.top -Wacute.sc: W.sc acute.top -Wcircumflex.sc: W.sc circumflex.top -Wdieresis.sc: W.sc dieresis.top -Wdotaccent.sc: W.sc dotaccent.top -Wgrave.sc: W.sc grave.top -Xdieresis.sc: X.sc dieresis.top -Xdotaccent.sc: X.sc dotaccent.top -Yacute.sc: Y.sc acute.top -Ycircumflex.sc: Y.sc circumflex.top -Ydieresis.sc: Y.sc dieresis.top -Ydotaccent.sc: Y.sc dotaccent.top -Ygrave.sc: Y.sc grave.top -Ytilde.sc: Y.sc tilde.top -Zacute.sc: Z.sc acute.top -Zcaron.sc: Z.sc caron.top -Zcircumflex.sc: Z.sc circumflex.top -Zdotaccent.sc: Z.sc dotaccent.top -"""
\ No newline at end of file diff --git a/misc/pylib/robofab/tools/glyphNameSchemes.py b/misc/pylib/robofab/tools/glyphNameSchemes.py deleted file mode 100755 index cb6172b9a..000000000 --- a/misc/pylib/robofab/tools/glyphNameSchemes.py +++ /dev/null @@ -1,41 +0,0 @@ -"""A separate module for glyphname to filename functions. - -glyphNameToShortFileName() generates a non-clashing filename for systems with -filename-length limitations. -""" - -MAXLEN = 31 - -def glyphNameToShortFileName(glyphName, glyphSet): - """Alternative glyphname to filename function. - - Features a garuanteed maximum filename for really long glyphnames, and clash testing. - - all non-ascii characters are converted to "_" (underscore), including "." - - all glyphnames which are too long are truncated and a hash is added at the end - - the hash is generated from the whole glyphname - - finally, the candidate glyphname is checked against the contents.plist - and a incrementing number is added at the end if there is a clash. - """ - import binascii, struct, string - ext = ".glif" - ok = string.ascii_letters + string.digits + " _" - h = binascii.hexlify(struct.pack(">l", binascii.crc32(glyphName))) - n = '' - for c in glyphName: - if c in ok: - if c != c.lower(): - n += c + "_" - else: - n += c - else: - n += "_" - if len(n + ext) < MAXLEN: - return n + ext - count = 0 - candidate = n[:MAXLEN - len(h + ext)] + h + ext - if glyphSet is not None: - names = glyphSet.getReverseContents() - while candidate.lower() in names: - candidate = n[:MAXLEN - len(h + ext + str(count))] + h + str(count) + ext - count += 1 - return candidate diff --git a/misc/pylib/robofab/tools/objectDumper.py b/misc/pylib/robofab/tools/objectDumper.py deleted file mode 100755 index 29110ca53..000000000 --- a/misc/pylib/robofab/tools/objectDumper.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Simple and ugly way to print some attributes and properties of an object to stdout. -FontLab doesn't have an object browser and sometimes we do need to look inside""" - -from pprint import pprint - -def classname(object, modname): - """Get a class name and qualify it with a module name if necessary.""" - name = object.__name__ - if object.__module__ != modname: - name = object.__module__ + '.' + name - return name - -def _objectDumper(object, indent=0, private=False): - """Collect a dict with the contents of the __dict__ as a quick means of peeking inside - an instance. Some RoboFab locations do not support PyBrowser and still need debugging.""" - data = {} - data['__class__'] = "%s at %d"%(classname(object.__class__, object.__module__), id(object)) - for k in object.__class__.__dict__.keys(): - if private and k[0] == "_": - continue - x = object.__class__.__dict__[k] - if hasattr(x, "fget"): #other means of recognising a property? - try: - try: - value = _objectDumper(x.fget(self), 1) - except: - value = x.fget(self) - data[k] = "[property, %s] %s"%(type(x.fget(self)).__name__, value) - except: - data[k] = "[property] (Error getting property value)" - for k in object.__dict__.keys(): - if private and k[0] == "_": - continue - try: - data[k] = "[attribute, %s] %s"%(type(object.__dict__[k]).__name__, `object.__dict__[k]`) - except: - data[k] = "[attribute] (Error getting attribute value)" - return data - -def flattenDict(dict, indent=0): - t = [] - k = dict.keys() - k.sort() - print - print '---RoboFab Object Dump---' - for key in k: - value = dict[key] - t.append(indent*"\t"+"%s: %s"%(key, value)) - t.append('') - return "\r".join(t) - -def dumpObject(object, private=False): - print pprint(_objectDumper(object, private=private)) - - diff --git a/misc/pylib/robofab/tools/otFeatures.py b/misc/pylib/robofab/tools/otFeatures.py deleted file mode 100755 index 8138a2fe7..000000000 --- a/misc/pylib/robofab/tools/otFeatures.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Simple module to write features to font""" - - -import string - - -from types import StringType, ListType, TupleType - -from robofab.world import world -if world.inFontLab: - from FL import * - from fl_cmd import * -from robofab.tools.toolsFL import FontIndex - - #feat = [] - #feat.append('feature smcp {') - #feat.append('\tlookup SMALLCAPS {') - #feat.append('\t\tsub @LETTERS_LC by @LETTERS_LC;') - #feat.append('\t} SMALLCAPS;') - #feat.append('} smcp;') - - -class FeatureWriter: - """Make properly formatted feature code""" - def __init__(self, type): - self.type = type - self.data = [] - - def add(self, src, dst): - """Add a substitution: change src to dst.""" - self.data.append((src, dst)) - - def write(self, group=0): - """Write the whole thing to string""" - t = [] - if len(self.data) == 0: - return None - t.append('feature %s {' % self.type) - for src, dst in self.data: - if isinstance(src, (list, tuple)): - if group: - src = "[%s]" % string.join(src, ' ') - else: - src = string.join(src, ' ') - if isinstance(dst, (list, tuple)): - if group: - dst = "[%s]" % string.join(dst, ' ') - else: - dst = string.join(dst, ' ') - src = string.strip(src) - dst = string.strip(dst) - t.append("\tsub %s by %s;" % (src, dst)) - t.append('}%s;' % self.type) - return string.join(t, '\n') - - -class GlyphName: - """Simple class that splits a glyphname in handy parts, - access the parts as attributes of the name.""" - def __init__(self, name): - self.suffix = [] - self.ligs = [] - self.name = self.base = name - if '.' in name: - self.bits = name.split('.') - self.base = self.bits[0] - self.suffix = self.bits[1:] - if '_' in name: - self.ligs = self.base.split('_') - - -def GetAlternates(font, flavor="alt", match=0): - """Sort the glyphs of this font by the parts of the name. - flavor is the bit to look for, i.e. 'alt' in a.alt - match = 1 if you want a exact match: alt1 != alt - match = 0 if the flavor is a partial match: alt == alt1 - """ - names = {} - for c in font.glyphs: - name = GlyphName(c.name) - if not names.has_key(name.base): - names[name.base] = [] - if match: - # only include if there is an exact match - if flavor in name.suffix: - names[name.base].append(c.name) - else: - # include if there is a partial match - for a in name.suffix: - if a.find(flavor) != -1: - names[name.base].append(c.name) - return names - - -# XXX there should be a more generic glyph finder. - -def MakeCapsFeature(font): - """Build a feature for smallcaps based on .sc glyphnames""" - names = GetAlternates(font, 'sc', match=1) - fw = FeatureWriter('smcp') - k = names.keys() - k.sort() - for p in k: - if names[p]: - fw.add(p, names[p]) - feat = fw.write() - if feat: - font.features.append(Feature('smcp', feat)) - return feat - - -def MakeAlternatesFeature(font): - """Build a aalt feature based on glyphnames""" - names = GetAlternates(font, 'alt', match=0) - fw = FeatureWriter('aalt') - k = names.keys() - k.sort() - for p in k: - if names[p]: - fw.add(p, names[p]) - feat = fw.write(group=1) - if feat: - font.features.append(Feature('aalt', feat)) - return feat - - -def MakeSwashFeature(font): - """Build a swash feature based on glyphnames""" - names = GetAlternates(font, 'swash', match=0) - fw = FeatureWriter('swsh') - k=names.keys() - k.sort() - for p in k: - if names[p]: - l=names[p] - l.sort() - fw.add(p, l[0]) - feat=fw.write() - if feat: - font.features.append(Feature('swsh', feat)) - return feat - - -def MakeLigaturesFeature(font): - """Build a liga feature based on glyphnames""" - from robofab.gString import ligatures - ligCountDict = {} - for glyph in font.glyphs: - if glyph.name in ligatures: - if len(glyph.name) not in ligCountDict.keys(): - ligCountDict[len(glyph.name)] = [glyph.name] - else: - ligCountDict[len(glyph.name)].append(glyph.name) - elif glyph.name.find('_') != -1: - usCounter=1 - for i in glyph.name: - if i =='_': - usCounter=usCounter+1 - if usCounter not in ligCountDict.keys(): - ligCountDict[usCounter] = [glyph.name] - else: - ligCountDict[usCounter].append(glyph.name) - ligCount=ligCountDict.keys() - ligCount.sort() - foundLigs=[] - for i in ligCount: - l = ligCountDict[i] - l.sort() - foundLigs=foundLigs+l - fw=FeatureWriter('liga') - for i in foundLigs: - if i.find('_') != -1: - sub=i.split('_') - else: - sub=[] - for c in i: - sub.append(c) - fw.add(sub, i) - feat=fw.write() - if feat: - font.features.append(Feature('liga', feat)) - return feat - - -if __name__ == "__main__": - fw = FeatureWriter('liga') - fw.add(['f', 'f', 'i'], ['f_f_i']) - fw.add('f f ', 'f_f') - fw.add(['f', 'i'], 'f_i') - print fw.write() diff --git a/misc/pylib/robofab/tools/proof.py b/misc/pylib/robofab/tools/proof.py deleted file mode 100755 index bb3b9d100..000000000 --- a/misc/pylib/robofab/tools/proof.py +++ /dev/null @@ -1,119 +0,0 @@ -"""This is the place for stuff that makes proofs and test text settings etc""" - -import string - - - - -idHeader = """<ASCII-MAC> -<Version:2.000000><FeatureSet:InDesign-Roman><ColorTable:=<Black:COLOR:CMYK:Process:0.000000,0.000000,0.000000,1.000000>>""" - -idColor = """<cColor:COLOR\:%(model)s\:Process\:%(c)f\,%(m)f\,%(y)f\,%(k)f>""" - -idParaStyle = """<ParaStyle:><cTypeface:%(weight)s><cSize:%(size)f><cLeading:%(leading)f><cFont:%(family)s>""" -idGlyphStyle = """<cTypeface:%(weight)s><cSize:%(size)f><cLeading:%(leading)f><cFont:%(family)s>""" - -seperator = '' - -autoLinespaceFactor = 1.2 - - -class IDTaggedText: - - """Export a text as a XML tagged text file for InDesign (2.0?). - The tags can contain information about - - family: font family i.e. "Times" - - weight: font weight "Bold" - - size: typesize in points - - leading: leading in points - - color: a CMYK color, as a 4 tuple of floats between 0 and 1 - - insert special glyphs based on glyphindex - (which is why it only makes sense if you use this in FontLab, - otherwise there is no other way to get the indices) - """ - - def __init__(self, family, weight, size=36, leading=None): - self.family = family - self.weight = weight - self.size = size - if not leading: - self.leading = autoLinespaceFactor*size - self.text = [] - self.data = [] - self.addHeader() - - def add(self, text): - """Method to add text to the file.""" - t = self.charToGlyph(text) - self.data.append(t) - - def charToGlyph(self, text): - return text - - def addHeader(self): - """Add the standard header.""" - # set colors too? - self.data.append(idHeader) - - def replace(self, old, new): - """Replace occurances of 'old' with 'new' in all content.""" - d = [] - for i in self.data: - d.append(i.replace(old, new)) - self.data = d - - def save(self, path): - """Save the tagged text here.""" - f = open(path, 'w') - f.write(string.join(self.data, seperator)) - f.close() - - def addGlyph(self, index): - """Add a special glyph, index is the glyphIndex in an OpenType font.""" - self.addStyle() - self.data.append("<cSpecialGlyph:%d><0xFFFD>"%index) - - def addStyle(self, family=None, weight=None, size=None, leading=None, color=None): - """Set the paragraph style for the following text.""" - if not family: - family = self.family - if not weight: - weight = self.weight - if not size: - size = self.size - if not leading: - leading = autoLinespaceFactor*self.size - self.data.append(idGlyphStyle%({'weight': weight, 'size': size, 'family': family, 'leading':leading})) - if color: - self.data.append(idColor%({'model': 'CMYK', 'c': color[0], 'm': color[1], 'y': color[2], 'k': color[3]})) - - - -if __name__ == "__main__": - from random import randint - id = IDTaggedText("Minion", "Regular", size=40, leading=50) - - id.addStyle(color=(0,0,0,1)) - id.add("Hello") - - id.addStyle(weight="Bold", color=(0,0.5,1,0)) - id.add(" Everybody") - id.addStyle(weight="Regular", size=100, color=(0,1,1,0)) - id.addGlyph(102) - id.addGlyph(202) - - from robofab.interface.all.dialogs import PutFile - path = PutFile("Save the tagged file:", "TaggedText.txt") - if path: - id.save(path) - - # then: open a document in Adobe InDesign - # select "Place" (cmd-D on Mac) - # select the text file you just generated - # place the text - - - - - - #
\ No newline at end of file diff --git a/misc/pylib/robofab/tools/remote.py b/misc/pylib/robofab/tools/remote.py deleted file mode 100755 index 288afcfe7..000000000 --- a/misc/pylib/robofab/tools/remote.py +++ /dev/null @@ -1,175 +0,0 @@ -"""Remote control for MacOS FontLab. -initFontLabRemote() registers a callback for appleevents and -runFontLabRemote() sends the code from a different application, -such as a Mac Python IDE or Python interpreter. -""" - -from robofab.world import world - -if world.inFontLab and world.mac is not None: - from Carbon import AE as _AE - -else: - import sys - from aetools import TalkTo - - class FontLab(TalkTo): - pass - -__all__ = ['initFontLabRemote', 'runFontLabRemote'] - -def _executePython(theAppleEvent, theReply): - import aetools - import cStringIO - import traceback - import sys - parms, attrs = aetools.unpackevent(theAppleEvent) - source = parms.get("----") - if source is None: - return - stdout = cStringIO.StringIO() - #print "<executing remote command>" - save = sys.stdout, sys.stderr - sys.stdout = sys.stderr = stdout - namespace = {} - try: - try: - exec source in namespace - except: - traceback.print_exc() - finally: - sys.stdout, sys.stderr = save - output = stdout.getvalue() - aetools.packevent(theReply, {"----": output}) - -_imported = False - -def initFontLabRemote(): - """Call this in FontLab at startup of the application to switch on the remote.""" - print "FontLabRemote is on." - _AE.AEInstallEventHandler("Rfab", "exec", _executePython) - -if world.inFontLab and world.mac is not None: - initFontLabRemote() - -def runFontLabRemote(code): - """Call this in the MacOS Python IDE to make FontLab execute the code.""" - fl = FontLab("FLab", start=1) - ae, parms, attrs = fl.send("Rfab", "exec", {"----": code}) - output = parms.get("----") - return output - - - -# GlyphTransmit -# Convert a glyph to a string using digestPen, transmit string, unpack string with pointpen. -# - - -def Glyph2String(glyph): - from robofab.pens.digestPen import DigestPointPen - import pickle - p = DigestPointPen(glyph) - glyph.drawPoints(p) - info = {} - info['name'] = glyph.name - info['width'] = glyph.width - info['points'] = p.getDigest() - return str(pickle.dumps(info)) - -def String2Glyph(gString, penClass, font): - import pickle - if gString is None: - return None - info = pickle.loads(gString) - name = info['name'] - if not name in font.keys(): - glyph = font.newGlyph(name) - else: - glyph = font[name] - pen = penClass(glyph) - for p in info['points']: - if p == "beginPath": - pen.beginPath() - elif p == "endPath": - pen.endPath() - else: - pt, type = p - pen.addPoint(pt, type) - glyph.width = info['width'] - glyph.update() - return glyph - -_makeFLGlyph = """ -from robofab.world import CurrentFont -from robofab.tools.remote import receiveGlyph -code = '''%s''' -receiveGlyph(code, CurrentFont()) -""" - -def transmitGlyph(glyph): - from robofab.world import world - if world.inFontLab and world.mac is not None: - # we're in fontlab, on a mac - print Glyph2String(glyph) - pass - else: - remoteProgram = _makeFLGlyph%Glyph2String(glyph) - print "remoteProgram", remoteProgram - return runFontLabRemote(remoteProgram) - -def receiveGlyph(glyphString, font=None): - from robofab.world import world - if world.inFontLab and world.mac is not None: - # we're in fontlab, on a mac - from robofab.pens.flPen import FLPointPen - print String2Glyph(glyphString, FLPointPen, font) - pass - else: - from robofab.pens.rfUFOPen import RFUFOPointPen - print String2Glyph(glyphString, RFUFOPointPen, font) - - -# -# command to tell FontLab to open a UFO and save it as a vfb - -def os9PathConvert(path): - """Attempt to convert a unix style path to a Mac OS9 style path. - No support for relative paths! - """ - if path.find("/Volumes") == 0: - # it's on the volumes list, some sort of external volume - path = path[len("/Volumes")+1:] - elif path[0] == "/": - # a dir on the root volume - path = path[1:] - new = path.replace("/", ":") - return new - - -_remoteUFOImportProgram = """ -from robofab.objects.objectsFL import NewFont -import os.path -destinationPathVFB = "%(destinationPathVFB)s" -font = NewFont() -font.readUFO("%(sourcePathUFO)s", doProgress=True) -font.update() -font.save(destinationPathVFB) -print font, "done" -font.close() -""" - -def makeVFB(sourcePathUFO, destinationPathVFB=None): - """FontLab convenience function to import a UFO and save it as a VFB""" - import os - fl = FontLab("FLab", start=1) - if destinationPathVFB is None: - destinationPathVFB = os.path.splitext(sourcePathUFO)[0]+".vfb" - src9 = os9PathConvert(sourcePathUFO) - dst9 = os9PathConvert(destinationPathVFB) - code = _remoteUFOImportProgram%{'sourcePathUFO': src9, 'destinationPathVFB':dst9} - ae, parms, attrs = fl.send("Rfab", "exec", {"----": code}) - output = parms.get("----") - return output - -
\ No newline at end of file diff --git a/misc/pylib/robofab/tools/rfPrefs.py b/misc/pylib/robofab/tools/rfPrefs.py deleted file mode 100755 index 440984d0d..000000000 --- a/misc/pylib/robofab/tools/rfPrefs.py +++ /dev/null @@ -1,122 +0,0 @@ -"""A simple module for dealing with preferences that are used by scripts. Based almost entirely on MacPrefs. - -To save some preferences: -myPrefs = RFPrefs(drive/directory/directory/myPrefs.plist) -myPrefs.myString = 'xyz' -myPrefs.myInteger = 1234 -myPrefs.myList = ['a', 'b', 'c'] -myPrefs.myDict = {'a':1, 'b':2} -myPrefs.save() - -To retrieve some preferences: -myPrefs = RFPrefs(drive/directory/directory/myPrefs.plist) -myString = myPrefs.myString -myInteger = myPrefs.myInteger -myList = myPrefs.myList -myDict = myPrefs.myDict - -When using this module within FontLab, it is not necessary to -provide the RFPrefs class with a path. If a path is not given, -it will look for a file in FontLab/RoboFab Data/RFPrefs.plist. -If that file does not exist, it will make it. -""" - -from robofab import RoboFabError -from robofab.plistlib import Plist -from cStringIO import StringIO -import os - -class _PrefObject: - - def __init__(self, dict=None): - if not dict: - self._prefs = {} - else: - self._prefs = dict - - def __len__(self): - return len(self._prefs) - - def __delattr__(self, attr): - if self._prefs.has_key(attr): - del self._prefs[attr] - else: - raise AttributeError, 'delete non-existing instance attribute' - - def __getattr__(self, attr): - if attr == '__members__': - keys = self._prefs.keys() - keys.sort() - return keys - try: - return self._prefs[attr] - except KeyError: - raise AttributeError, attr - - def __setattr__(self, attr, value): - if attr[0] != '_': - self._prefs[attr] = value - else: - self.__dict__[attr] = value - - def asDict(self): - return self._prefs - -class RFPrefs(_PrefObject): - - """The main preferences object to call""" - - def __init__(self, path=None): - from robofab.world import world - self.__path = path - self._prefs = {} - if world.inFontLab: - #we don't have a path, but we know where we can put it - if not path: - from robofab.tools.toolsFL import makeDataFolder - settingsPath = makeDataFolder() - path = os.path.join(settingsPath, 'RFPrefs.plist') - self.__path = path - self._makePrefsFile() - #we do have a path, make sure it exists and load it - else: - self._makePrefsFile() - else: - #no path, raise error - if not path: - raise RoboFabError, "no preferences path defined" - #we do have a path, make sure it exists and load it - else: - self._makePrefsFile() - self._prefs = Plist.fromFile(path) - - def _makePrefsFile(self): - if not os.path.exists(self.__path): - self.save() - - def __getattr__(self, attr): - if attr[0] == '__members__': - keys = self._prefs.keys() - keys.sort() - return keys - try: - return self._prefs[attr] - except KeyError: - raise AttributeError, attr - #if attr[0] != '_': - # self._prefs[attr] = _PrefObject() - # return self._prefs[attr] - #else: - # raise AttributeError, attr - - def save(self): - """save the plist file""" - f = StringIO() - pl = Plist() - for i in self._prefs.keys(): - pl[i] = self._prefs[i] - pl.write(f) - data = f.getvalue() - f = open(self.__path, 'wb') - f.write(data) - f.close() diff --git a/misc/pylib/robofab/tools/toolsAll.py b/misc/pylib/robofab/tools/toolsAll.py deleted file mode 100755 index d729fe770..000000000 --- a/misc/pylib/robofab/tools/toolsAll.py +++ /dev/null @@ -1,145 +0,0 @@ -"""A collection of non-environment specific tools""" - - -import sys -import os -from robofab.objects.objectsRF import RInfo - -if sys.platform == "darwin" and sys.version_info[:3] == (2, 2, 0): - # the Mac support of Jaguar's Python 2.2 is broken - have_broken_macsupport = 1 -else: - have_broken_macsupport = 0 - - -def readGlyphConstructions(): - """read GlyphConstruction and turn it into a dict""" - from robofab.tools.glyphConstruction import _glyphConstruction - data = _glyphConstruction.split("\n") - glyphConstructions = {} - for i in data: - if len(i) == 0: continue - if i[0] != '#': - name = i.split(': ')[0] - construction = i.split(': ')[1].split(' ') - build = [construction[0]] - for c in construction[1:]: - accent = c.split('.')[0] - position = c.split('.')[1] - build.append((accent, position)) - glyphConstructions[name] = build - return glyphConstructions - -# -# -# glyph.unicode: ttFont["cmap"].getcmap(3, 1) -# -# - -def guessFileType(fileName): - if not os.path.exists(fileName): - return None - base, ext = os.path.splitext(fileName) - ext = ext.lower() - if not have_broken_macsupport: - try: - import MacOS - except ImportError: - pass - else: - cr, tp = MacOS.GetCreatorAndType(fileName) - if tp in ("sfnt", "FFIL"): - return "TTF" - if tp == "LWFN": - return "Type 1" - if ext == ".dfont": - return "TTF" - if ext in (".otf", ".ttf"): - return "TTF" - if ext in (".pfb", ".pfa"): - return "Type 1" - return None - -def extractTTFFontInfo(font): - # UFO.info attribute name / index. - # name table entries index according to http://www.microsoft.com/typography/otspec/name.htm - attrs = [ - ('copyright', 0), - ('familyName', 1), - ('styleMapStyleName', 2), - ('postscriptFullName', 4), - ('trademark', 7), - ('openTypeNameDesigner', 9), - ('openTypeNameLicenseURL', 14), - ('openTypeNameDesignerURL', 12), - ] - info = RInfo() - names = font['name'] - info.ascender = font['hhea'].ascent - info.descender = font['hhea'].descent - info.unitsPerEm = font['head'].unitsPerEm - for name, index in attrs: - entry = font["name"].getName(index, 3, 1, 0x409) - if entry is not None: - try: - value = unicode(entry.string, "utf_16_be") - if name == "styleMapStyleName": - value = value.lower() - setattr(info, name, value) - except Exception, e: - print "Error importing value %s: %s: %s"%(str(name), value, e.message) - return info - -def extractT1FontInfo(font): - info = RInfo() - src = font.font['FontInfo'] - factor = font.font['FontMatrix'][0] - assert factor > 0 - info.unitsPerEm = int(round(1/factor, 0)) - # assume something for ascender descender - info.ascender = (info.unitsPerEm / 5) * 4 - info.descender = info.ascender - info.unitsPerEm - info.versionMajor = font.font['FontInfo']['version'] - info.fullName = font.font['FontInfo']['FullName'] - info.familyName = font.font['FontInfo']['FullName'].split("-")[0] - info.notice = unicode(font.font['FontInfo']['Notice'], "macroman") - info.italicAngle = font.font['FontInfo']['ItalicAngle'] - info.uniqueID = font['UniqueID'] - return info - -def fontToUFO(src, dst, fileType=None): - from robofab.ufoLib import UFOWriter - from robofab.pens.adapterPens import SegmentToPointPen - if fileType is None: - fileType = guessFileType(src) - if fileType is None: - raise ValueError, "Can't determine input file type" - ufoWriter = UFOWriter(dst) - if fileType == "TTF": - from fontTools.ttLib import TTFont - font = TTFont(src, 0) - elif fileType == "Type 1": - from fontTools.t1Lib import T1Font - font = T1Font(src) - else: - assert 0, "unknown file type: %r" % fileType - inGlyphSet = font.getGlyphSet() - outGlyphSet = ufoWriter.getGlyphSet() - for glyphName in inGlyphSet.keys(): - print "-", glyphName - glyph = inGlyphSet[glyphName] - def drawPoints(pen): - pen = SegmentToPointPen(pen) - glyph.draw(pen) - outGlyphSet.writeGlyph(glyphName, glyph, drawPoints) - outGlyphSet.writeContents() - if fileType == "TTF": - info = extractTTFFontInfo(font) - elif fileType == "Type 1": - info = extractT1FontInfo(font) - ufoWriter.writeInfo(info) - -if __name__ == "__main__": - print readGlyphConstructions() - -
\ No newline at end of file diff --git a/misc/pylib/robofab/tools/toolsFL.py b/misc/pylib/robofab/tools/toolsFL.py deleted file mode 100755 index c8aff19a1..000000000 --- a/misc/pylib/robofab/tools/toolsFL.py +++ /dev/null @@ -1,339 +0,0 @@ -""" -T.O.O.L.S.: Things Other Objects Lack (Sometimes) --assorted raw tools. - -This is an assorted colection of raw tools that do -things inside of FontLab. Many of these functions -form the bedrock of objectsFL. In short, use these -tools only if you need the raw functions and they are -not supported by the objects. - -Object model: -Most of these tools were written before -objectsFL. Some of these tools are used by -objectsFL. That means that if you want to -use functions from robofab.tools you can always -feed them FontLab objects (like Font, Glyps, -etc.). If the functions also accept Robjects from -robofab.objects it is usually mentioned in the -doc string. - -This is a simple way to convert a robofab Font -object back to a FL Font object. Even if you don't -know which particular faith an object belongs to -you can use this: - -font = unwrapFont(font) -""" - - -from FL import * -from warnings import warn - -try: - from fl_cmd import * -except ImportError: - print "The fl_cmd module is not available here. toolsFL.py" - -import os - -from robofab import RoboFabError - -# local encoding -if os.name == "mac": - LOCAL_ENCODING = "macroman" -else: - LOCAL_ENCODING = "latin-1" - - -# -# -# -# stuff for fontlab app -# -# -# - -def AppFolderRenamer(): - """This function will rename the folder that contains the - FontLab application to a more specific name that includes - the version of the application - Warning: it messes with the paths of your app, if you have - items that hardwired to this path you'd be in trouble. - """ - if fl.count > 0: - warn("Close all fonts before running AppFolderRenamer") - return - old = fl.path[:-1] - root = os.path.dirname(old) - new = "FontLab " + fl.version.replace('/', '_') - path = os.path.join(root, new) - if path != old: - try: - os.rename(old, path) - except OSError: - pass - warn("Please quit and restart FontLab") - -# -# -# -# stuff for fonts -# -# -# - -def GetFont(full_name): - """Return fontobjects which match full_name. - Note: result is a list. - Returns: a list of FL Font objects - """ - found = [] - for f in AllFonts(): - if f.full_name == full_name: - found.append(f) - return found - -def AllFonts(): - """Collect a list of all open fonts. - Returns: a list of FL Font objects. - """ - fontcount = len(fl) - af = [] - for i in range(fontcount): - af.append(fl[i]) - return af - -def FontIndex(font): - """return the index of a specified FL Font""" - font = unwrapFont(font) - a = AllFonts() - p = [] - for f in a: - p.append(f.file_name) - if font.file_name in p: - return p.index(font.file_name) - else: - return None - -def unwrapFont(font): - """Unwrap the font if it happens to be a RoboFab Font""" - if hasattr(font, 'isRobofab'): - return font.naked() - return font - -def MakeTempFont(font, dupemark=None, removeOverlap=True, decompose=True): - """Save the current FL Font, - - close the file, - - duplicate the file in the finder (icon looks weird, but it works) - - open the duplicate - - decompose the glyphs - - remove overlaps - - return the fontobject - - font is either a FL Font or RF RFont object. - - Problems: doesn't check if the filename is getting too long. - Note: it will overwrite older files with the same name. - """ - import string - f = unwrapFont(font) - if not dupemark or dupemark == "": - dupemark = "_tmp_" - path = f.file_name - a = f.file_name.split('.') - a.insert(len(a)-1, dupemark) - newpath = string.join(a, '.') - f.Save(path) - fl.Close(FontIndex(f)) - file = open(path, 'rb') - data = file.read() - file.close() - file = open(newpath, 'wb') - file.write(data) - file.close() - fl.Open(newpath, 1) - nf = fl.font - if nf is None: - print 'uh oh, sup?' - return None - else: - for g in nf.glyphs: - if decompose: - g.Decompose() - if removeOverlap: - g.RemoveOverlap() - return nf - -def makePSFontName(name): - """Create a postscript filename out of a regular postscript fontname, - using the old fashioned macintosh 5:3:3 convention. - """ - import string - parts = [] - current = [] - final = [] - notAllowed = '-_+=,-' - index = 0 - for c in name: - if c in notAllowed: - continue - if c in string.uppercase or index == 0: - c = string.upper(c) - if current: - parts.append("".join(current)) - current = [c] - else: - current.append(c) - index = index + 1 - if current: - parts.append("".join(current)) - final.append(parts[0][:5]) - for p in parts[1:]: - final.append(p[:3]) - return "".join(final) - -# -# -# -# stuff for glyphs -# -# -# - -def NewGlyph(font, glyphName, clear=False, updateFont=True): - """Make a new glyph if it doesn't already exist, return the glyph. - font is either a FL Font or RF RFont object. If updateFont is True - the (very slow) fl.UpdateFont function will be called. - """ - font = unwrapFont(font) - if isinstance(glyphName, unicode): - glyphName = glyphName.encode(LOCAL_ENCODING) - glyph = font[glyphName] - if glyph is None: - new = Glyph() - new.name = glyphName - font.glyphs.append(new) - if updateFont: - fl.UpdateFont(FontIndex(font)) - glyph = font[glyphName] - elif clear: - glyph.Clear() - glyph.anchors.clean() - glyph.components.clean() - glyph.note = "" - return glyph - - -def AddToAlias(additions, sep='+'): - """additions is a dict with glyphnames as keys - and glyphConstruction as values. In order to make - a bunch of additions in one go rather than open - and close the file for each name. Add a glyph - to the alias.dat file if it doesn't already exist. - additions = {'Gcircumflex': ['G','circumflex'], } - Returns a list of only the added glyphnames.""" - import string - glyphs = {} - data = [] - new = [] - path = os.path.join(fl.path, 'Mapping', 'alias.dat') - if os.path.exists(path): - file = open(path, 'r') - data = file.read().split('\n') - file.close() - for i in data: - if len(i) == 0: continue - if i[0] != '%': - glyphs[i.split(' ')[0]] = i.split(' ')[1] - for glyphName, glyphConstruction in additions.items(): - if glyphName not in glyphs.keys(): - new.append(glyphName) - glyphs[glyphName] = string.join(glyphConstruction, sep) - newNames = ['%%FONTLAB ALIASES'] - l = glyphs.keys() - l.sort() - for i in l: - newNames.append(string.join([i, glyphs[i]], ' ')) - file = open(path, 'w') - file.write(string.join(newNames, '\n')) - file.close() - return new - - -def GlyphIndexTable(font): - """Make a glyph index table for font""" - font = unwrapFont(font) - idx = {} - for i in range(len(font)): - g = font.glyphs[i] - idx[g.name] = i - return idx - -def MakeReverseCompoMapping(font): - """Return a dict that maps glyph names to lists containing tuples - of the form: - (clientGlyphName, componentIndex) - """ - font = unwrapFont(font) - reverseCompoMapping = {} - for g in font.glyphs: - for i, c in zip(range(len(g.components)), g.components): - base = font[c.index].name - if not base in reverseCompoMapping: - reverseCompoMapping[base] = [] - reverseCompoMapping[base].append((g.name, i)) - return reverseCompoMapping - - -# -# -# -# stuff for text files -# -# -# - -def textPrinter(text, name=None, path=None): - """Write a string to a text file. If no name is given it becomes - Untitled_hour_minute_second.txt . If no path is given it goes - into the FontLab/RoboFab Data directory.""" - if not name: - import time - tm_year,tm_mon,tm_day,tm_hour,tm_min,tm_sec,tm_wday,tm_yday,tm_isdst = time.localtime() - now = '_'.join((`tm_hour`, `tm_min`, `tm_sec`)) - name = 'Untitled_%s.txt'%now - if not path: - path = os.path.join(makeDataFolder(), name) - f = open(path, 'wb') - f.write(text) - f.close() - -def makeDataFolder(): - """Make the RoboFab data folder""" - folderPath = os.path.join(fl.path, "RoboFab Data") - if not os.path.exists(folderPath): - try: - os.makedirs(folderPath) - except: - pass - return folderPath - - -def Log(text=None): - """Make an entry in the default log file.""" - now = str(time.asctime(time.localtime(time.time()))) - if not text: - text = "-" - entry = "%s: %s\r"%(now, text) - path = os.path.join(os.getcwd(), "Logs") - new = 0 - if not os.path.exists(path): - os.makedirs(path) - new = 1 - log = os.path.join(path, "log.txt") - f = open(log, 'a') - if new: - f.write("# log file for FL\r") - f.write(entry) - f.close() diff --git a/misc/pylib/robofab/tools/toolsRF.py b/misc/pylib/robofab/tools/toolsRF.py deleted file mode 100755 index 01624c5ad..000000000 --- a/misc/pylib/robofab/tools/toolsRF.py +++ /dev/null @@ -1,6 +0,0 @@ -""" - -Module for rf specific tool like code. - -""" - diff --git a/misc/pylib/robofab/ufoLib.py b/misc/pylib/robofab/ufoLib.py deleted file mode 100755 index b329fd929..000000000 --- a/misc/pylib/robofab/ufoLib.py +++ /dev/null @@ -1,1084 +0,0 @@ -"""" -A library for importing .ufo files and their descendants. -Refer to http://unifiedfontobject.com for the UFO specification. - -The UFOReader and UFOWriter classes support versions 1 and 2 -of the specification. Up and down conversion functions are also -supplied in this library. These conversion functions are only -necessary if conversion without loading the UFO data into -a set of objects is desired. These functions are: - convertUFOFormatVersion1ToFormatVersion2 - convertUFOFormatVersion2ToFormatVersion1 - -Two sets that list the font info attribute names for the two -fontinfo.plist formats are available for external use. These are: - fontInfoAttributesVersion1 - fontInfoAttributesVersion2 - -A set listing the fontinfo.plist attributes that were deprecated -in version 2 is available for external use: - deprecatedFontInfoAttributesVersion2 - -A function, validateFontInfoVersion2ValueForAttribute, that does -some basic validation on values for a fontinfo.plist value is -available for external use. - -Two value conversion functions are availble for converting -fontinfo.plist values between the possible format versions. - convertFontInfoValueForAttributeFromVersion1ToVersion2 - convertFontInfoValueForAttributeFromVersion2ToVersion1 -""" - - -import os -import shutil -from cStringIO import StringIO -import calendar -from robofab.plistlib import readPlist, writePlist -from robofab.glifLib import GlyphSet, READ_MODE, WRITE_MODE - -try: - set -except NameError: - from sets import Set as set - -__all__ = [ - "makeUFOPath" - "UFOLibError", - "UFOReader", - "UFOWriter", - "convertUFOFormatVersion1ToFormatVersion2", - "convertUFOFormatVersion2ToFormatVersion1", - "fontInfoAttributesVersion1", - "fontInfoAttributesVersion2", - "deprecatedFontInfoAttributesVersion2", - "validateFontInfoVersion2ValueForAttribute", - "convertFontInfoValueForAttributeFromVersion1ToVersion2", - "convertFontInfoValueForAttributeFromVersion2ToVersion1" -] - - -class UFOLibError(Exception): pass - - -# ---------- -# File Names -# ---------- - -GLYPHS_DIRNAME = "glyphs" -METAINFO_FILENAME = "metainfo.plist" -FONTINFO_FILENAME = "fontinfo.plist" -LIB_FILENAME = "lib.plist" -GROUPS_FILENAME = "groups.plist" -KERNING_FILENAME = "kerning.plist" -FEATURES_FILENAME = "features.fea" - -supportedUFOFormatVersions = [1, 2] - - -# --------------------------- -# Format Conversion Functions -# --------------------------- - - -def convertUFOFormatVersion1ToFormatVersion2(inPath, outPath=None): - """ - Function for converting a version format 1 UFO - to version format 2. inPath should be a path - to a UFO. outPath is the path where the new UFO - should be written. If outPath is not given, the - inPath will be used and, therefore, the UFO will - be converted in place. Otherwise, if outPath is - specified, nothing must exist at that path. - """ - if outPath is None: - outPath = inPath - if inPath != outPath and os.path.exists(outPath): - raise UFOLibError("A file already exists at %s." % outPath) - # use a reader for loading most of the data - reader = UFOReader(inPath) - if reader.formatVersion == 2: - raise UFOLibError("The UFO at %s is already format version 2." % inPath) - groups = reader.readGroups() - kerning = reader.readKerning() - libData = reader.readLib() - # read the info data manually and convert - infoPath = os.path.join(inPath, FONTINFO_FILENAME) - if not os.path.exists(infoPath): - infoData = {} - else: - infoData = readPlist(infoPath) - infoData = _convertFontInfoDataVersion1ToVersion2(infoData) - # if the paths are the same, only need to change the - # fontinfo and meta info files. - infoPath = os.path.join(outPath, FONTINFO_FILENAME) - if inPath == outPath: - metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) - metaInfo = dict( - creator="org.robofab.ufoLib", - formatVersion=2 - ) - writePlistAtomically(metaInfo, metaInfoPath) - writePlistAtomically(infoData, infoPath) - # otherwise write everything. - else: - writer = UFOWriter(outPath) - writer.writeGroups(groups) - writer.writeKerning(kerning) - writer.writeLib(libData) - # write the info manually - writePlistAtomically(infoData, infoPath) - # copy the glyph tree - inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) - outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) - if os.path.exists(inGlyphs): - shutil.copytree(inGlyphs, outGlyphs) - -def convertUFOFormatVersion2ToFormatVersion1(inPath, outPath=None): - """ - Function for converting a version format 2 UFO - to version format 1. inPath should be a path - to a UFO. outPath is the path where the new UFO - should be written. If outPath is not given, the - inPath will be used and, therefore, the UFO will - be converted in place. Otherwise, if outPath is - specified, nothing must exist at that path. - """ - if outPath is None: - outPath = inPath - if inPath != outPath and os.path.exists(outPath): - raise UFOLibError("A file already exists at %s." % outPath) - # use a reader for loading most of the data - reader = UFOReader(inPath) - if reader.formatVersion == 1: - raise UFOLibError("The UFO at %s is already format version 1." % inPath) - groups = reader.readGroups() - kerning = reader.readKerning() - libData = reader.readLib() - # read the info data manually and convert - infoPath = os.path.join(inPath, FONTINFO_FILENAME) - if not os.path.exists(infoPath): - infoData = {} - else: - infoData = readPlist(infoPath) - infoData = _convertFontInfoDataVersion2ToVersion1(infoData) - # if the paths are the same, only need to change the - # fontinfo, metainfo and feature files. - infoPath = os.path.join(outPath, FONTINFO_FILENAME) - if inPath == outPath: - metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) - metaInfo = dict( - creator="org.robofab.ufoLib", - formatVersion=1 - ) - writePlistAtomically(metaInfo, metaInfoPath) - writePlistAtomically(infoData, infoPath) - featuresPath = os.path.join(inPath, FEATURES_FILENAME) - if os.path.exists(featuresPath): - os.remove(featuresPath) - # otherwise write everything. - else: - writer = UFOWriter(outPath, formatVersion=1) - writer.writeGroups(groups) - writer.writeKerning(kerning) - writer.writeLib(libData) - # write the info manually - writePlistAtomically(infoData, infoPath) - # copy the glyph tree - inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) - outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) - if os.path.exists(inGlyphs): - shutil.copytree(inGlyphs, outGlyphs) - - -# ---------- -# UFO Reader -# ---------- - - -class UFOReader(object): - - """Read the various components of the .ufo.""" - - def __init__(self, path): - self._path = path - self.readMetaInfo() - - def _get_formatVersion(self): - return self._formatVersion - - formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is determined by reading metainfo.plist during __init__.") - - def _checkForFile(self, path): - if not os.path.exists(path): - return False - else: - return True - - def readMetaInfo(self): - """ - Read metainfo.plist. Only used for internal operations. - """ - path = os.path.join(self._path, METAINFO_FILENAME) - if not self._checkForFile(path): - raise UFOLibError("metainfo.plist is missing in %s. This file is required." % self._path) - # should there be a blind try/except with a UFOLibError - # raised in except here (and elsewhere)? It would be nice to - # provide external callers with a single exception to catch. - data = readPlist(path) - formatVersion = data["formatVersion"] - if formatVersion not in supportedUFOFormatVersions: - raise UFOLibError("Unsupported UFO format (%d) in %s." % (formatVersion, self._path)) - self._formatVersion = formatVersion - - def readGroups(self): - """ - Read groups.plist. Returns a dict. - """ - path = os.path.join(self._path, GROUPS_FILENAME) - if not self._checkForFile(path): - return {} - return readPlist(path) - - def readInfo(self, info): - """ - Read fontinfo.plist. It requires an object that allows - setting attributes with names that follow the fontinfo.plist - version 2 specification. This will write the attributes - defined in the file into the object. - """ - # load the file and return if there is no file - path = os.path.join(self._path, FONTINFO_FILENAME) - if not self._checkForFile(path): - return - infoDict = readPlist(path) - infoDataToSet = {} - # version 1 - if self._formatVersion == 1: - for attr in fontInfoAttributesVersion1: - value = infoDict.get(attr) - if value is not None: - infoDataToSet[attr] = value - infoDataToSet = _convertFontInfoDataVersion1ToVersion2(infoDataToSet) - # version 2 - elif self._formatVersion == 2: - for attr, dataValidationDict in _fontInfoAttributesVersion2ValueData.items(): - value = infoDict.get(attr) - if value is None: - continue - infoDataToSet[attr] = value - # unsupported version - else: - raise NotImplementedError - # validate data - infoDataToSet = _validateInfoVersion2Data(infoDataToSet) - # populate the object - for attr, value in infoDataToSet.items(): - try: - setattr(info, attr, value) - except AttributeError: - raise UFOLibError("The supplied info object does not support setting a necessary attribute (%s)." % attr) - - def readKerning(self): - """ - Read kerning.plist. Returns a dict. - """ - path = os.path.join(self._path, KERNING_FILENAME) - if not self._checkForFile(path): - return {} - kerningNested = readPlist(path) - kerning = {} - for left in kerningNested: - for right in kerningNested[left]: - value = kerningNested[left][right] - kerning[left, right] = value - return kerning - - def readLib(self): - """ - Read lib.plist. Returns a dict. - """ - path = os.path.join(self._path, LIB_FILENAME) - if not self._checkForFile(path): - return {} - return readPlist(path) - - def readFeatures(self): - """ - Read features.fea. Returns a string. - """ - path = os.path.join(self._path, FEATURES_FILENAME) - if not self._checkForFile(path): - return "" - f = open(path, READ_MODE) - text = f.read() - f.close() - return text - - def getGlyphSet(self): - """ - Return the GlyphSet associated with the - glyphs directory in the .ufo. - """ - glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME) - return GlyphSet(glyphsPath) - - def getCharacterMapping(self): - """ - Return a dictionary that maps unicode values (ints) to - lists of glyph names. - """ - glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME) - glyphSet = GlyphSet(glyphsPath) - allUnicodes = glyphSet.getUnicodes() - cmap = {} - for glyphName, unicodes in allUnicodes.iteritems(): - for code in unicodes: - if code in cmap: - cmap[code].append(glyphName) - else: - cmap[code] = [glyphName] - return cmap - - -# ---------- -# UFO Writer -# ---------- - - -class UFOWriter(object): - - """Write the various components of the .ufo.""" - - def __init__(self, path, formatVersion=2, fileCreator="org.robofab.ufoLib"): - if formatVersion not in supportedUFOFormatVersions: - raise UFOLibError("Unsupported UFO format (%d)." % formatVersion) - self._path = path - self._formatVersion = formatVersion - self._fileCreator = fileCreator - self._writeMetaInfo() - # handle down conversion - if formatVersion == 1: - ## remove existing features.fea - featuresPath = os.path.join(path, FEATURES_FILENAME) - if os.path.exists(featuresPath): - os.remove(featuresPath) - - def _get_formatVersion(self): - return self._formatVersion - - formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is set into metainfo.plist during __init__.") - - def _get_fileCreator(self): - return self._fileCreator - - fileCreator = property(_get_fileCreator, doc="The file creator of the UFO. This is set into metainfo.plist during __init__.") - - def _makeDirectory(self, subDirectory=None): - path = self._path - if subDirectory: - path = os.path.join(self._path, subDirectory) - if not os.path.exists(path): - os.makedirs(path) - return path - - def _writeMetaInfo(self): - self._makeDirectory() - path = os.path.join(self._path, METAINFO_FILENAME) - metaInfo = dict( - creator=self._fileCreator, - formatVersion=self._formatVersion - ) - writePlistAtomically(metaInfo, path) - - def writeGroups(self, groups): - """ - Write groups.plist. This method requires a - dict of glyph groups as an argument. - """ - self._makeDirectory() - path = os.path.join(self._path, GROUPS_FILENAME) - groupsNew = {} - for key, value in groups.items(): - groupsNew[key] = list(value) - if groupsNew: - writePlistAtomically(groupsNew, path) - elif os.path.exists(path): - os.remove(path) - - def writeInfo(self, info): - """ - Write info.plist. This method requires an object - that supports getting attributes that follow the - fontinfo.plist version 2 secification. Attributes - will be taken from the given object and written - into the file. - """ - self._makeDirectory() - path = os.path.join(self._path, FONTINFO_FILENAME) - # gather version 2 data - infoData = {} - for attr in _fontInfoAttributesVersion2ValueData.keys(): - try: - value = getattr(info, attr) - except AttributeError: - raise UFOLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr) - if value is None: - continue - infoData[attr] = value - # validate data - infoData = _validateInfoVersion2Data(infoData) - # down convert data to version 1 if necessary - if self._formatVersion == 1: - infoData = _convertFontInfoDataVersion2ToVersion1(infoData) - # write file - writePlistAtomically(infoData, path) - - def writeKerning(self, kerning): - """ - Write kerning.plist. This method requires a - dict of kerning pairs as an argument. - """ - self._makeDirectory() - path = os.path.join(self._path, KERNING_FILENAME) - kerningDict = {} - for left, right in kerning.keys(): - value = kerning[left, right] - if not left in kerningDict: - kerningDict[left] = {} - kerningDict[left][right] = value - if kerningDict: - writePlistAtomically(kerningDict, path) - elif os.path.exists(path): - os.remove(path) - - def writeLib(self, libDict): - """ - Write lib.plist. This method requires a - lib dict as an argument. - """ - self._makeDirectory() - path = os.path.join(self._path, LIB_FILENAME) - if libDict: - writePlistAtomically(libDict, path) - elif os.path.exists(path): - os.remove(path) - - def writeFeatures(self, features): - """ - Write features.fea. This method requires a - features string as an argument. - """ - if self._formatVersion == 1: - raise UFOLibError("features.fea is not allowed in UFO Format Version 1.") - self._makeDirectory() - path = os.path.join(self._path, FEATURES_FILENAME) - writeFileAtomically(features, path) - - def makeGlyphPath(self): - """ - Make the glyphs directory in the .ufo. - Returns the path of the directory created. - """ - glyphDir = self._makeDirectory(GLYPHS_DIRNAME) - return glyphDir - - def getGlyphSet(self, glyphNameToFileNameFunc=None): - """ - Return the GlyphSet associated with the - glyphs directory in the .ufo. - """ - return GlyphSet(self.makeGlyphPath(), glyphNameToFileNameFunc) - -# ---------------- -# Helper Functions -# ---------------- - -def makeUFOPath(path): - """ - Return a .ufo pathname. - - >>> makeUFOPath("/directory/something.ext") - '/directory/something.ufo' - >>> makeUFOPath("/directory/something.another.thing.ext") - '/directory/something.another.thing.ufo' - """ - dir, name = os.path.split(path) - name = ".".join([".".join(name.split(".")[:-1]), "ufo"]) - return os.path.join(dir, name) - -def writePlistAtomically(obj, path): - """ - Write a plist for "obj" to "path". Do this sort of atomically, - making it harder to cause corrupt files, for example when writePlist - encounters an error halfway during write. This also checks to see - if text matches the text that is already in the file at path. - If so, the file is not rewritten so that the modification date - is preserved. - """ - f = StringIO() - writePlist(obj, f) - data = f.getvalue() - writeFileAtomically(data, path) - -def writeFileAtomically(text, path): - """Write text into a file at path. Do this sort of atomically - making it harder to cause corrupt files. This also checks to see - if text matches the text that is already in the file at path. - If so, the file is not rewritten so that the modification date - is preserved.""" - if os.path.exists(path): - f = open(path, READ_MODE) - oldText = f.read() - f.close() - if text == oldText: - return - # if the text is empty, remove the existing file - if not text: - os.remove(path) - if text: - f = open(path, WRITE_MODE) - f.write(text) - f.close() - -# ---------------------- -# fontinfo.plist Support -# ---------------------- - -# Version 1 - -fontInfoAttributesVersion1 = set([ - "familyName", - "styleName", - "fullName", - "fontName", - "menuName", - "fontStyle", - "note", - "versionMajor", - "versionMinor", - "year", - "copyright", - "notice", - "trademark", - "license", - "licenseURL", - "createdBy", - "designer", - "designerURL", - "vendorURL", - "unitsPerEm", - "ascender", - "descender", - "capHeight", - "xHeight", - "defaultWidth", - "slantAngle", - "italicAngle", - "widthName", - "weightName", - "weightValue", - "fondName", - "otFamilyName", - "otStyleName", - "otMacName", - "msCharSet", - "fondID", - "uniqueID", - "ttVendor", - "ttUniqueID", - "ttVersion", -]) - -# Version 2 - -# Validators - -def validateFontInfoVersion2ValueForAttribute(attr, value): - """ - This performs very basic validation of the value for attribute - following the UFO fontinfo.plist specification. The results - of this should not be interpretted as *correct* for the font - that they are part of. This merely indicates that the value - is of the proper type and, where the specification defines - a set range of possible values for an attribute, that the - value is in the accepted range. - """ - dataValidationDict = _fontInfoAttributesVersion2ValueData[attr] - valueType = dataValidationDict.get("type") - validator = dataValidationDict.get("valueValidator") - valueOptions = dataValidationDict.get("valueOptions") - # have specific options for the validator - if valueOptions is not None: - isValidValue = validator(value, valueOptions) - # no specific options - else: - if validator == _fontInfoTypeValidator: - isValidValue = validator(value, valueType) - else: - isValidValue = validator(value) - return isValidValue - -def _validateInfoVersion2Data(infoData): - validInfoData = {} - for attr, value in infoData.items(): - isValidValue = validateFontInfoVersion2ValueForAttribute(attr, value) - if not isValidValue: - raise UFOLibError("Invalid value for attribute %s (%s)." % (attr, repr(value))) - else: - validInfoData[attr] = value - return infoData - -def _fontInfoTypeValidator(value, typ): - return isinstance(value, typ) - -def _fontInfoVersion2IntListValidator(values, validValues): - if not isinstance(values, (list, tuple)): - return False - valuesSet = set(values) - validValuesSet = set(validValues) - if len(valuesSet - validValuesSet) > 0: - return False - for value in values: - if not isinstance(value, int): - return False - return True - -def _fontInfoVersion2StyleMapStyleNameValidator(value): - options = ["regular", "italic", "bold", "bold italic"] - return value in options - -def _fontInfoVersion2OpenTypeHeadCreatedValidator(value): - # format: 0000/00/00 00:00:00 - if not isinstance(value, (str, unicode)): - return False - # basic formatting - if not len(value) == 19: - return False - if value.count(" ") != 1: - return False - date, time = value.split(" ") - if date.count("/") != 2: - return False - if time.count(":") != 2: - return False - # date - year, month, day = date.split("/") - if len(year) != 4: - return False - if len(month) != 2: - return False - if len(day) != 2: - return False - try: - year = int(year) - month = int(month) - day = int(day) - except ValueError: - return False - if month < 1 or month > 12: - return False - monthMaxDay = calendar.monthrange(year, month) - if month > monthMaxDay: - return False - # time - hour, minute, second = time.split(":") - if len(hour) != 2: - return False - if len(minute) != 2: - return False - if len(second) != 2: - return False - try: - hour = int(hour) - minute = int(minute) - second = int(second) - except ValueError: - return False - if hour < 0 or hour > 23: - return False - if minute < 0 or minute > 59: - return False - if second < 0 or second > 59: - return True - # fallback - return True - -def _fontInfoVersion2OpenTypeOS2WeightClassValidator(value): - if not isinstance(value, int): - return False - if value < 0: - return False - return True - -def _fontInfoVersion2OpenTypeOS2WidthClassValidator(value): - if not isinstance(value, int): - return False - if value < 1: - return False - if value > 9: - return False - return True - -def _fontInfoVersion2OpenTypeOS2PanoseValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) != 10: - return False - for value in values: - if not isinstance(value, int): - return False - # XXX further validation? - return True - -def _fontInfoVersion2OpenTypeOS2FamilyClassValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) != 2: - return False - for value in values: - if not isinstance(value, int): - return False - classID, subclassID = values - if classID < 0 or classID > 14: - return False - if subclassID < 0 or subclassID > 15: - return False - return True - -def _fontInfoVersion2PostscriptBluesValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) > 14: - return False - if len(values) % 2: - return False - for value in values: - if not isinstance(value, (int, float)): - return False - return True - -def _fontInfoVersion2PostscriptOtherBluesValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) > 10: - return False - if len(values) % 2: - return False - for value in values: - if not isinstance(value, (int, float)): - return False - return True - -def _fontInfoVersion2PostscriptStemsValidator(values): - if not isinstance(values, (list, tuple)): - return False - if len(values) > 12: - return False - for value in values: - if not isinstance(value, (int, float)): - return False - return True - -def _fontInfoVersion2PostscriptWindowsCharacterSetValidator(value): - validValues = range(1, 21) - if value not in validValues: - return False - return True - -# Attribute Definitions -# This defines the attributes, types and, in some -# cases the possible values, that can exist is -# fontinfo.plist. - -_fontInfoVersion2OpenTypeHeadFlagsOptions = range(0, 14) -_fontInfoVersion2OpenTypeOS2SelectionOptions = [1, 2, 3, 4, 7, 8, 9] -_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions = range(0, 128) -_fontInfoVersion2OpenTypeOS2CodePageRangesOptions = range(0, 64) -_fontInfoVersion2OpenTypeOS2TypeOptions = [0, 1, 2, 3, 8, 9] - -_fontInfoAttributesVersion2ValueData = { - "familyName" : dict(type=(str, unicode)), - "styleName" : dict(type=(str, unicode)), - "styleMapFamilyName" : dict(type=(str, unicode)), - "styleMapStyleName" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2StyleMapStyleNameValidator), - "versionMajor" : dict(type=int), - "versionMinor" : dict(type=int), - "year" : dict(type=int), - "copyright" : dict(type=(str, unicode)), - "trademark" : dict(type=(str, unicode)), - "unitsPerEm" : dict(type=(int, float)), - "descender" : dict(type=(int, float)), - "xHeight" : dict(type=(int, float)), - "capHeight" : dict(type=(int, float)), - "ascender" : dict(type=(int, float)), - "italicAngle" : dict(type=(float, int)), - "note" : dict(type=(str, unicode)), - "openTypeHeadCreated" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2OpenTypeHeadCreatedValidator), - "openTypeHeadLowestRecPPEM" : dict(type=(int, float)), - "openTypeHeadFlags" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeHeadFlagsOptions), - "openTypeHheaAscender" : dict(type=(int, float)), - "openTypeHheaDescender" : dict(type=(int, float)), - "openTypeHheaLineGap" : dict(type=(int, float)), - "openTypeHheaCaretSlopeRise" : dict(type=int), - "openTypeHheaCaretSlopeRun" : dict(type=int), - "openTypeHheaCaretOffset" : dict(type=(int, float)), - "openTypeNameDesigner" : dict(type=(str, unicode)), - "openTypeNameDesignerURL" : dict(type=(str, unicode)), - "openTypeNameManufacturer" : dict(type=(str, unicode)), - "openTypeNameManufacturerURL" : dict(type=(str, unicode)), - "openTypeNameLicense" : dict(type=(str, unicode)), - "openTypeNameLicenseURL" : dict(type=(str, unicode)), - "openTypeNameVersion" : dict(type=(str, unicode)), - "openTypeNameUniqueID" : dict(type=(str, unicode)), - "openTypeNameDescription" : dict(type=(str, unicode)), - "openTypeNamePreferredFamilyName" : dict(type=(str, unicode)), - "openTypeNamePreferredSubfamilyName" : dict(type=(str, unicode)), - "openTypeNameCompatibleFullName" : dict(type=(str, unicode)), - "openTypeNameSampleText" : dict(type=(str, unicode)), - "openTypeNameWWSFamilyName" : dict(type=(str, unicode)), - "openTypeNameWWSSubfamilyName" : dict(type=(str, unicode)), - "openTypeOS2WidthClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WidthClassValidator), - "openTypeOS2WeightClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WeightClassValidator), - "openTypeOS2Selection" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2SelectionOptions), - "openTypeOS2VendorID" : dict(type=(str, unicode)), - "openTypeOS2Panose" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2PanoseValidator), - "openTypeOS2FamilyClass" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2FamilyClassValidator), - "openTypeOS2UnicodeRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions), - "openTypeOS2CodePageRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2CodePageRangesOptions), - "openTypeOS2TypoAscender" : dict(type=(int, float)), - "openTypeOS2TypoDescender" : dict(type=(int, float)), - "openTypeOS2TypoLineGap" : dict(type=(int, float)), - "openTypeOS2WinAscent" : dict(type=(int, float)), - "openTypeOS2WinDescent" : dict(type=(int, float)), - "openTypeOS2Type" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2TypeOptions), - "openTypeOS2SubscriptXSize" : dict(type=(int, float)), - "openTypeOS2SubscriptYSize" : dict(type=(int, float)), - "openTypeOS2SubscriptXOffset" : dict(type=(int, float)), - "openTypeOS2SubscriptYOffset" : dict(type=(int, float)), - "openTypeOS2SuperscriptXSize" : dict(type=(int, float)), - "openTypeOS2SuperscriptYSize" : dict(type=(int, float)), - "openTypeOS2SuperscriptXOffset" : dict(type=(int, float)), - "openTypeOS2SuperscriptYOffset" : dict(type=(int, float)), - "openTypeOS2StrikeoutSize" : dict(type=(int, float)), - "openTypeOS2StrikeoutPosition" : dict(type=(int, float)), - "openTypeVheaVertTypoAscender" : dict(type=(int, float)), - "openTypeVheaVertTypoDescender" : dict(type=(int, float)), - "openTypeVheaVertTypoLineGap" : dict(type=(int, float)), - "openTypeVheaCaretSlopeRise" : dict(type=int), - "openTypeVheaCaretSlopeRun" : dict(type=int), - "openTypeVheaCaretOffset" : dict(type=(int, float)), - "postscriptFontName" : dict(type=(str, unicode)), - "postscriptFullName" : dict(type=(str, unicode)), - "postscriptSlantAngle" : dict(type=(float, int)), - "postscriptUniqueID" : dict(type=int), - "postscriptUnderlineThickness" : dict(type=(int, float)), - "postscriptUnderlinePosition" : dict(type=(int, float)), - "postscriptIsFixedPitch" : dict(type=bool), - "postscriptBlueValues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), - "postscriptOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), - "postscriptFamilyBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), - "postscriptFamilyOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), - "postscriptStemSnapH" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), - "postscriptStemSnapV" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), - "postscriptBlueFuzz" : dict(type=(int, float)), - "postscriptBlueShift" : dict(type=(int, float)), - "postscriptBlueScale" : dict(type=(float, int)), - "postscriptForceBold" : dict(type=bool), - "postscriptDefaultWidthX" : dict(type=(int, float)), - "postscriptNominalWidthX" : dict(type=(int, float)), - "postscriptWeightName" : dict(type=(str, unicode)), - "postscriptDefaultCharacter" : dict(type=(str, unicode)), - "postscriptWindowsCharacterSet" : dict(type=int, valueValidator=_fontInfoVersion2PostscriptWindowsCharacterSetValidator), - "macintoshFONDFamilyID" : dict(type=int), - "macintoshFONDName" : dict(type=(str, unicode)), -} -fontInfoAttributesVersion2 = set(_fontInfoAttributesVersion2ValueData.keys()) - -# insert the type validator for all attrs that -# have no defined validator. -for attr, dataDict in _fontInfoAttributesVersion2ValueData.items(): - if "valueValidator" not in dataDict: - dataDict["valueValidator"] = _fontInfoTypeValidator - -# Version Conversion Support -# These are used from converting from version 1 -# to version 2 or vice-versa. - -def _flipDict(d): - flipped = {} - for key, value in d.items(): - flipped[value] = key - return flipped - -_fontInfoAttributesVersion1To2 = { - "menuName" : "styleMapFamilyName", - "designer" : "openTypeNameDesigner", - "designerURL" : "openTypeNameDesignerURL", - "createdBy" : "openTypeNameManufacturer", - "vendorURL" : "openTypeNameManufacturerURL", - "license" : "openTypeNameLicense", - "licenseURL" : "openTypeNameLicenseURL", - "ttVersion" : "openTypeNameVersion", - "ttUniqueID" : "openTypeNameUniqueID", - "notice" : "openTypeNameDescription", - "otFamilyName" : "openTypeNamePreferredFamilyName", - "otStyleName" : "openTypeNamePreferredSubfamilyName", - "otMacName" : "openTypeNameCompatibleFullName", - "weightName" : "postscriptWeightName", - "weightValue" : "openTypeOS2WeightClass", - "ttVendor" : "openTypeOS2VendorID", - "uniqueID" : "postscriptUniqueID", - "fontName" : "postscriptFontName", - "fondID" : "macintoshFONDFamilyID", - "fondName" : "macintoshFONDName", - "defaultWidth" : "postscriptDefaultWidthX", - "slantAngle" : "postscriptSlantAngle", - "fullName" : "postscriptFullName", - # require special value conversion - "fontStyle" : "styleMapStyleName", - "widthName" : "openTypeOS2WidthClass", - "msCharSet" : "postscriptWindowsCharacterSet" -} -_fontInfoAttributesVersion2To1 = _flipDict(_fontInfoAttributesVersion1To2) -deprecatedFontInfoAttributesVersion2 = set(_fontInfoAttributesVersion1To2.keys()) - -_fontStyle1To2 = { - 64 : "regular", - 1 : "italic", - 32 : "bold", - 33 : "bold italic" -} -_fontStyle2To1 = _flipDict(_fontStyle1To2) -# Some UFO 1 files have 0 -_fontStyle1To2[0] = "regular" - -_widthName1To2 = { - "Ultra-condensed" : 1, - "Extra-condensed" : 2, - "Condensed" : 3, - "Semi-condensed" : 4, - "Medium (normal)" : 5, - "Semi-expanded" : 6, - "Expanded" : 7, - "Extra-expanded" : 8, - "Ultra-expanded" : 9 -} -_widthName2To1 = _flipDict(_widthName1To2) -# FontLab's default width value is "Normal". -# Many format version 1 UFOs will have this. -_widthName1To2["Normal"] = 5 -# FontLab has an "All" width value. In UFO 1 -# move this up to "Normal". -_widthName1To2["All"] = 5 -# "medium" appears in a lot of UFO 1 files. -_widthName1To2["medium"] = 5 -# "Medium" appears in a lot of UFO 1 files. -_widthName1To2["Medium"] = 5 - -_msCharSet1To2 = { - 0 : 1, - 1 : 2, - 2 : 3, - 77 : 4, - 128 : 5, - 129 : 6, - 130 : 7, - 134 : 8, - 136 : 9, - 161 : 10, - 162 : 11, - 163 : 12, - 177 : 13, - 178 : 14, - 186 : 15, - 200 : 16, - 204 : 17, - 222 : 18, - 238 : 19, - 255 : 20 -} -_msCharSet2To1 = _flipDict(_msCharSet1To2) - -def convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value): - """ - Convert value from version 1 to version 2 format. - Returns the new attribute name and the converted value. - If the value is None, None will be returned for the new value. - """ - # convert floats to ints if possible - if isinstance(value, float): - if int(value) == value: - value = int(value) - if value is not None: - if attr == "fontStyle": - v = _fontStyle1To2.get(value) - if v is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) - value = v - elif attr == "widthName": - v = _widthName1To2.get(value) - if v is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) - value = v - elif attr == "msCharSet": - v = _msCharSet1To2.get(value) - if v is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) - value = v - attr = _fontInfoAttributesVersion1To2.get(attr, attr) - return attr, value - -def convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value): - """ - Convert value from version 2 to version 1 format. - Returns the new attribute name and the converted value. - If the value is None, None will be returned for the new value. - """ - if value is not None: - if attr == "styleMapStyleName": - value = _fontStyle2To1.get(value) - elif attr == "openTypeOS2WidthClass": - value = _widthName2To1.get(value) - elif attr == "postscriptWindowsCharacterSet": - value = _msCharSet2To1.get(value) - attr = _fontInfoAttributesVersion2To1.get(attr, attr) - return attr, value - -def _convertFontInfoDataVersion1ToVersion2(data): - converted = {} - for attr, value in data.items(): - # FontLab gives -1 for the weightValue - # for fonts wil no defined value. Many - # format version 1 UFOs will have this. - if attr == "weightValue" and value == -1: - continue - newAttr, newValue = convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) - # skip if the attribute is not part of version 2 - if newAttr not in fontInfoAttributesVersion2: - continue - # catch values that can't be converted - if value is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) - # store - converted[newAttr] = newValue - return converted - -def _convertFontInfoDataVersion2ToVersion1(data): - converted = {} - for attr, value in data.items(): - newAttr, newValue = convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) - # only take attributes that are registered for version 1 - if newAttr not in fontInfoAttributesVersion1: - continue - # catch values that can't be converted - if value is None: - raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) - # store - converted[newAttr] = newValue - return converted - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/misc/pylib/robofab/world.py b/misc/pylib/robofab/world.py deleted file mode 100644 index 203753d6c..000000000 --- a/misc/pylib/robofab/world.py +++ /dev/null @@ -1,108 +0,0 @@ -import os, sys -from robofab import RoboFabError, version, numberVersion - - -class RFWorld: - - """All parameters about platforms, versions and environments included in one object.""" - - def __init__(self): - self.mac = None - self.pc = None - self.platform = sys.platform - self.applicationName = None # name of the application we're running in - self.name = os.name - self.version = version # the robofab version - self.numberVersion = numberVersion - self.run = True - - # get some platform information - if self.name == 'mac' or self.name == 'posix': - if self.platform == "darwin": - self.mac = "X" - else: - self.mac = "pre-X" - elif self.name == 'nt': - # if you know more about PC & win stuff, add it here! - self.pc = True - else: - raise RoboFabError, "We're running on an unknown platform." - - # collect versions - self.pyVersion = sys.version[:3] - self.inPython = False - self.inFontLab = False - self.flVersion = None - self.inGlyphs = False - self.glyphsVersion = None - self.inRoboFont = False - self.roboFontVersion = None - - # are we in Glyphs? - try: - import objectsGS - from AppKit import NSBundle - bundle = NSBundle.mainBundle() - self.applicationName = bundle.infoDictionary()["CFBundleName"] - self.inGlyphs = True - self.glyphsVersion = bundle.infoDictionary()["CFBundleVersion"] - except ImportError: - # are we in RoboFont - try: - import mojo - from AppKit import NSBundle - bundle = NSBundle.mainBundle() - self.applicationName = bundle.infoDictionary()["CFBundleName"] - self.inRoboFont = True - self.roboFontVersion = bundle.infoDictionary()["CFBundleVersion"] - except ImportError: - # are we in FontLab? - try: - from FL import fl - self.applicationName = fl.filename - self.inFontLab = True - self.flVersion = fl.version - except ImportError: pass - # we are in NoneLab - if not self.inFontLab: - self.inPython = True - - # see if we have DialogKit - self.supportsDialogKit = False - - def __repr__(self): - s = [ - "Robofab is running on %s" % self.platform, - "Python version: %s" % self.pyVersion, - "Mac stuff: %s" % self.mac, - "PC stuff: %s" % self.pc, - "FontLab stuff: %s" % self.inFontLab, - "FLversion: %s" % self.flVersion, - "Glyphs stuff: %s" % self.inGlyphs, - "Glyphs version: %s" % self.glyphsVersion, - "RoboFont stuff: %s" %self.inRoboFont, - "RoboFont version: %s" %self.roboFontVersion, - ] - return ", ".join(s) - - -world = RFWorld() - -lineBreak = os.linesep - -if world.inFontLab: - from robofab.interface.all.dialogs import SelectFont, SelectGlyph - from robofab.objects.objectsFL import CurrentFont, CurrentGlyph, RFont, RGlyph, OpenFont, NewFont, AllFonts - lineBreak = "\n" -elif world.inRoboFont: - from mojo.roboFont import CurrentFont, CurrentGlyph, RFont, RGlyph, OpenFont, NewFont, AllFonts -elif world.inGlyphs: - from objectsGS import CurrentFont, CurrentGlyph, RFont, RGlyph, OpenFont, NewFont, AllFonts -elif world.inPython: - from robofab.objects.objectsRF import CurrentFont, CurrentGlyph, RFont, RGlyph, OpenFont, NewFont, AllFonts - - - -if __name__ == "__main__": - f = RFWorld() - print f diff --git a/misc/pylib/robofab/xmlTreeBuilder.pyx b/misc/pylib/robofab/xmlTreeBuilder.pyx deleted file mode 100644 index be621e14f..000000000 --- a/misc/pylib/robofab/xmlTreeBuilder.pyx +++ /dev/null @@ -1,116 +0,0 @@ -import os -try: - from xml.parsers.expat import ParserCreate -except ImportError: - _haveExpat = 0 - from xml.parsers.xmlproc.xmlproc import XMLProcessor -else: - _haveExpat = 1 - - -class XMLParser: - - def __init__(self): - self.root = [] - self.current = (self.root, None) - - def getRoot(self): - assert len(self.root) == 1 - return self.root[0] - - def startElementHandler(self, name, attrs): - children = [] - self.current = (children, name, attrs, self.current) - - def endElementHandler(self, name): - children, name, attrs, previous = self.current - previous[0].append((name, attrs, children)) - self.current = previous - - def characterDataHandler(self, data): - nodes = self.current[0] - if nodes and type(nodes[-1]) == type(data): - nodes[-1] = nodes[-1] + data - else: - nodes.append(data) - - def _expatParseFile(self, pathOrFile): - parser = ParserCreate() - parser.returns_unicode = 0 # XXX, Don't remember why. It sucks, though. - parser.StartElementHandler = self.startElementHandler - parser.EndElementHandler = self.endElementHandler - parser.CharacterDataHandler = self.characterDataHandler - if isinstance(pathOrFile, (str, unicode)): - f = open(pathOrFile) - didOpen = 1 - else: - didOpen = 0 - f = pathOrFile - parser.ParseFile(f) - if didOpen: - f.close() - return self.getRoot() - - def _xmlprocDataHandler(self, data, begin, end): - self.characterDataHandler(data[begin:end]) - - def _xmlprocParseFile(self, pathOrFile): - proc = XMLProcessor() - proc.app.handle_start_tag = self.startElementHandler - proc.app.handle_end_tag = self.endElementHandler - proc.app.handle_data = self._xmlprocDataHandler - if isinstance(pathOrFile, (str, unicode)): - f = open(pathOrFile) - didOpen = 1 - else: - didOpen = 0 - f = pathOrFile - proc.parseStart() - proc.read_from(f) - proc.flush() - proc.parseEnd() - proc.deref() - if didOpen: - f.close() - return self.getRoot() - - if _haveExpat: - parseFile = _expatParseFile - else: - parseFile = _xmlprocParseFile - - -def stripCharacterData(nodes, recursive=True): - i = 0 - while 1: - try: - node = nodes[i] - except IndexError: - break - if isinstance(node, tuple): - if recursive: - stripCharacterData(node[2]) - i = i + 1 - else: - node = node.strip() - if node: - nodes[i] = node - i = i + 1 - else: - del nodes[i] - - -def buildTree(pathOrFile, stripData=1): - parser = XMLParser() - tree = parser.parseFile(pathOrFile) - if stripData: - stripCharacterData(tree[2]) - return tree - - -if __name__ == "__main__": - from pprint import pprint - import sys - strip = bool(sys.argv[2:]) - tree = buildTree(sys.argv[1], strip) - pprint(tree) |