meta: remove robofab files
This commit is contained in:
parent
5b43ab5b42
commit
e345fec96f
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,6 @@
|
||||
__pycache__/
|
||||
build/
|
||||
dist/
|
||||
.DS_Store
|
||||
*.egg-info
|
||||
*.pyc
|
||||
|
@ -40,7 +40,7 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'RoboFab'
|
||||
project = u'ufoLib'
|
||||
copyright = u'2011, Tal Leming, Erik van Blokland, Just van Rossum'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
@ -164,7 +164,7 @@ html_static_path = ['_static']
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'RoboFabdoc'
|
||||
htmlhelp_basename = 'ufoLib-doc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
@ -183,7 +183,7 @@ latex_elements = {
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'RoboFab.tex', u'RoboFab Documentation',
|
||||
('index', 'ufoLib.tex', u'ufoLib Documentation',
|
||||
u'Tal Leming, Erik van Blokland, Just van Rossum', 'manual'),
|
||||
]
|
||||
|
||||
@ -213,7 +213,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'robofab', u'RoboFab Documentation',
|
||||
('index', 'ufoLib', u'ufoLib Documentation',
|
||||
[u'Tal Leming, Erik van Blokland, Just van Rossum'], 1)
|
||||
]
|
||||
|
||||
@ -227,8 +227,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'RoboFab', u'RoboFab Documentation', u'Tal Leming, Erik van Blokland, Just van Rossum',
|
||||
'RoboFab', 'One line description of project.', 'Miscellaneous'),
|
||||
('index', 'ufoLib', u'ufoLib Documentation', u'Tal Leming, Erik van Blokland, Just van Rossum',
|
||||
'ufoLib', 'One line description of project.', 'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
@ -3,50 +3,6 @@
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to the RoboFab documentation!
|
||||
=====================================
|
||||
|
||||
Welcome to the RoboFab documentation. At the time of this writing, the UFO3 implementation is in full swing. We're using this opportunity to remove some cruft and clean up.
|
||||
|
||||
RoboFab
|
||||
=======
|
||||
|
||||
Objects
|
||||
^^^^^^^
|
||||
|
||||
The Robofab object model.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
objects/objectsBase
|
||||
objects/objectsRF
|
||||
|
||||
Pens
|
||||
^^^^
|
||||
|
||||
Pen objects for all sorts of use.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
pens/rfUFOPen
|
||||
pens/digestPen
|
||||
pens/mathPens
|
||||
pens/filterPen
|
||||
pens/adapterPens
|
||||
pens/boundsPen
|
||||
pens/marginPen
|
||||
pens/angledMarginPen
|
||||
|
||||
Interface
|
||||
^^^^^^^^^
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
interface/dialogs
|
||||
|
||||
UFOLib
|
||||
======
|
||||
|
||||
@ -62,11 +18,9 @@ Tools for accessing, validating and making UFO and glif.
|
||||
ufoLib/converters
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
.. highlight:: python
|
||||
|
||||
=======
|
||||
Dialogs
|
||||
=======
|
||||
|
||||
The dialogs module has a couple of basic dialogs and alerts that allow simple interactions.
|
||||
|
||||
.. automodule:: robofab.interface.all.dialogs
|
||||
:inherited-members:
|
||||
:members:
|
@ -1,9 +0,0 @@
|
||||
.. highlight:: python
|
||||
|
||||
===========
|
||||
objectsBase
|
||||
===========
|
||||
|
||||
.. automodule:: robofab.objects.objectsBase
|
||||
:inherited-members:
|
||||
:members:
|
@ -1,9 +0,0 @@
|
||||
.. highlight:: python
|
||||
|
||||
=========
|
||||
objectsRF
|
||||
=========
|
||||
|
||||
.. automodule:: robofab.objects.objectsRF
|
||||
:inherited-members:
|
||||
:members:
|
@ -1,9 +0,0 @@
|
||||
.. highlight:: python
|
||||
|
||||
===========
|
||||
adapterPens
|
||||
===========
|
||||
|
||||
.. automodule:: robofab.pens.adapterPens
|
||||
:inherited-members:
|
||||
:members:
|
@ -1,9 +0,0 @@
|
||||
.. highlight:: python
|
||||
|
||||
=========
|
||||
digestPen
|
||||
=========
|
||||
|
||||
.. automodule:: robofab.pens.digestPen
|
||||
:inherited-members:
|
||||
:members:
|
@ -1,9 +0,0 @@
|
||||
.. highlight:: python
|
||||
|
||||
=========
|
||||
filterPen
|
||||
=========
|
||||
|
||||
.. automodule:: robofab.pens.filterPen
|
||||
:inherited-members:
|
||||
:members:
|
@ -1,9 +0,0 @@
|
||||
.. highlight:: python
|
||||
|
||||
========
|
||||
mathPens
|
||||
========
|
||||
|
||||
.. automodule:: robofab.pens.mathPens
|
||||
:inherited-members:
|
||||
:members:
|
@ -1,9 +0,0 @@
|
||||
.. highlight:: python
|
||||
|
||||
========
|
||||
rfUFOPen
|
||||
========
|
||||
|
||||
.. automodule:: robofab.pens.rfUFOPen
|
||||
:inherited-members:
|
||||
:members:
|
@ -1,86 +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) 2005-2012, 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]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class RoboFabError(Exception): pass
|
||||
|
||||
class RoboFabWarning(Warning): pass
|
||||
|
||||
|
||||
numberVersion = (1, 3, "developer", 0)
|
||||
version = "1.3.0"
|
@ -1,11 +0,0 @@
|
||||
"""
|
||||
|
||||
Directory for contributed packages.
|
||||
Packages stored here can be imported from
|
||||
robofab.contrib.<packagename>
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
@ -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()
|
@ -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
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
@ -1,240 +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
|
||||
# | | | |
|
||||
#('mac', 'fontlab', (2,5,4), "dialogs_fontlab_legacy1"),
|
||||
('mac', 'fontlab', (2,5,4), "dialogs_fontlab_legacy2"),
|
||||
# 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_fontlab_legacy2 import AskString
|
||||
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 prints the basic parameters of the environment we're in, and the code we've loaded."""
|
||||
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()
|
||||
|
@ -1,81 +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.
|
||||
|
||||
"""
|
||||
|
||||
__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'):
|
||||
"""Prototype for AskString dialog. Should show a prompt, a text input box and OK button."""
|
||||
raise NotImplementedError
|
||||
|
||||
def AskYesNoCancel(message, title='RoboFab', default=0):
|
||||
"""Prototype for AskYesNoCancel dialog. Should show a prompt, Yes, No, Cancel buttons."""
|
||||
raise NotImplementedError
|
||||
|
||||
def FindGlyph(font, message="Search for a glyph:", title='RoboFab'):
|
||||
"""Prototype for FindGlyph dialog. Should show a list of glyph names of the current font, OK and Cancel buttons"""
|
||||
raise NotImplementedError
|
||||
|
||||
def GetFile(message=None):
|
||||
"""Prototype for GetFile dialog. Should offer a standard OS get file dialog."""
|
||||
raise NotImplementedError
|
||||
|
||||
def GetFolder(message=None):
|
||||
"""Prototype for GetFolder dialog. Should offer a standard OS get folder dialog."""
|
||||
raise NotImplementedError
|
||||
|
||||
def GetFileOrFolder(message=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def Message(message, title='RoboFab'):
|
||||
"""Prototype for Message dialog. Should offer a window with the message, OK button."""
|
||||
raise NotImplementedError
|
||||
|
||||
def OneList(list, message="Select an item:", title='RoboFab'):
|
||||
"""Prototype for OneList dialog."""
|
||||
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 PendingDeprecationWarning
|
||||
|
||||
def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'):
|
||||
raise PendingDeprecationWarning
|
||||
|
||||
class ProgressBar(object):
|
||||
pass
|
||||
|
@ -1,155 +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 AskString(message, value='', title='RoboFab'):
|
||||
dialogInstance = _AskStringDialog(message, value, title)
|
||||
return dialogInstance.getValue()
|
||||
|
||||
class _AskStringDialog(object):
|
||||
def __init__(self, message, value, title):
|
||||
self.w = ModalDialog((360, 140), title)
|
||||
self.w.button1 = Button((-100, -300, 80, 24), 'OK', callback=self.buttonCallback)
|
||||
self.w.t = TextBox((5, 10, -5, 27), message)
|
||||
self.w.inputValue = EditText((5, 35, -5, 50), value)
|
||||
self.w.open()
|
||||
|
||||
def getValue(self):
|
||||
return self.w.inputValue.get()
|
||||
|
||||
def buttonCallback(self, sender):
|
||||
self.w.close()
|
||||
|
||||
def AskYesNoCancel(message, title='RoboFab', default=1, informativeText=None):
|
||||
dialogInstance = _AskYesNoCancelDialog(message, title=title, default=default)
|
||||
result = dialogInstance.getValue()
|
||||
if result is None and default is not None:
|
||||
return default
|
||||
return result
|
||||
|
||||
class _AskYesNoCancelDialog(object):
|
||||
def __init__(self, message, default=None, title="RoboFab"):
|
||||
# default is ignord?
|
||||
self.answer = -1
|
||||
self.w = ModalDialog((360, 140), title, okCallback=self.buttonOKCallback)
|
||||
self.w.noButton = Button((10, -35, 80, 24), 'No', callback=self.buttonNoCallback)
|
||||
self.w.t = TextBox((5, 10, -5, 27), message)
|
||||
self.w.open()
|
||||
|
||||
def getValue(self):
|
||||
return self.answer
|
||||
|
||||
def buttonNoCallback(self, sender):
|
||||
self.answer = 0
|
||||
self.w.close()
|
||||
|
||||
def buttonOKCallback(self, sender):
|
||||
self.answer = 1
|
||||
self.w.close()
|
||||
|
||||
def FindGlyph(aFont, message="Search for a glyph:", title='RoboFab'):
|
||||
raise NotImplementedError
|
||||
|
||||
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 Message(message, title='RoboFab', informativeText=None):
|
||||
""" Display a standard message dialog.
|
||||
Note: informativeText is not supported on this platform.
|
||||
"""
|
||||
if informativeText is not None:
|
||||
message = "%s\n%s"%(message, informativeText)
|
||||
_MessageDialog(message, title)
|
||||
|
||||
class _MessageDialog(object):
|
||||
def __init__(self, message, title):
|
||||
self.w = ModalDialog((360, 100), title)
|
||||
self.w.t = TextBox((5, 10, -5, -40), message)
|
||||
self.w.open()
|
||||
|
||||
|
||||
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, '')
|
||||
|
||||
def OneList(list, message="Select an item:", title='RoboFab'):
|
||||
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
|
||||
|
||||
|
||||
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()
|
||||
|
@ -1,380 +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
|
||||
|
||||
"""
|
||||
|
||||
#__import__("FL")
|
||||
from FL import *
|
||||
#from dialogKit import ModalDialog
|
||||
|
||||
#import objc
|
||||
#from objc import selector
|
||||
|
||||
from Foundation import NSObject
|
||||
from AppKit import *
|
||||
|
||||
# is this the magic that will make this work in FL 5.1
|
||||
# however, what are the side effects?
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
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()
|
||||
|
||||
|
@ -1,721 +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')
|
||||
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)
|
||||
#
|
@ -1,262 +0,0 @@
|
||||
"""
|
||||
|
||||
Dialogs for environments that support cocao / vanilla.
|
||||
|
||||
"""
|
||||
|
||||
import vanilla
|
||||
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), [])
|
||||
|
||||
def okCallback(self, sender):
|
||||
self.w.close()
|
||||
|
||||
def closeCallback(self, sender):
|
||||
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):
|
||||
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
|
||||
"""
|
||||
import vanilla.dialogs
|
||||
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(list, message="Select an item:", title='RoboFab'):
|
||||
raise NotImplementedError
|
||||
|
||||
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
|
@ -1,10 +0,0 @@
|
||||
"""
|
||||
|
||||
Directory for interface related modules.
|
||||
Stuff for MacOSX, widgets, quartz
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
@ -1,10 +0,0 @@
|
||||
"""
|
||||
|
||||
Directory for interface related modules.
|
||||
Stuff for Windows
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
@ -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, (x, y), min=min, max=max):
|
||||
"""Return the bounding recangle of rectangle bounds and point (x, y)."""
|
||||
xMin, yMin, xMax, yMax = bounds
|
||||
return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
|
||||
|
||||
def pointInRect((x, y), rect):
|
||||
"""Return True when point (x, y) is inside rect."""
|
||||
xMin, yMin, xMax, yMax = rect
|
||||
return (xMin <= x <= xMax) and (yMin <= y <= 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((xMin, yMin, xMax, yMax)):
|
||||
"""Normalize the rectangle so that the following holds:
|
||||
xMin <= xMax and yMin <= yMax
|
||||
"""
|
||||
return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
|
||||
|
||||
def scaleRect((xMin, yMin, xMax, yMax), x, y):
|
||||
"""Scale the rectangle by x, y."""
|
||||
return xMin * x, yMin * y, xMax * x, yMax * y
|
||||
|
||||
def offsetRect((xMin, yMin, xMax, yMax), dx, dy):
|
||||
"""Offset the rectangle by dx, dy."""
|
||||
return xMin+dx, yMin+dy, xMax+dx, yMax+dy
|
||||
|
||||
def insetRect((xMin, yMin, xMax, yMax), dx, dy):
|
||||
"""Inset the rectangle by dx, dy on all sides."""
|
||||
return xMin+dx, yMin+dy, xMax-dx, yMax-dy
|
||||
|
||||
def sectRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)):
|
||||
"""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(xMin1, xMin2), max(yMin1, yMin2),
|
||||
min(xMax1, xMax2), min(yMax1, yMax2))
|
||||
if xMin >= xMax or yMin >= yMax:
|
||||
return 0, (0, 0, 0, 0)
|
||||
return 1, (xMin, yMin, xMax, yMax)
|
||||
|
||||
def unionRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)):
|
||||
"""Return the smallest rectangle in which both input rectangles are fully
|
||||
enclosed. In other words, return the total bounding rectangle of both input
|
||||
rectangles.
|
||||
"""
|
||||
xMin, yMin, xMax, yMax = (min(xMin1, xMin2), min(yMin1, yMin2),
|
||||
max(xMax1, xMax2), max(yMax1, yMax2))
|
||||
return (xMin, yMin, xMax, yMax)
|
||||
|
||||
def rectCenter((xMin, yMin, xMax, yMax)):
|
||||
"""Return the center of the rectangle as an (x, y) coordinate."""
|
||||
return (xMin+xMax)/2, (yMin+yMax)/2
|
||||
|
||||
def intRect((xMin, yMin, xMax, yMax)):
|
||||
"""Return the rectangle, rounded off to integer values, but guaranteeing that
|
||||
the resulting rectangle is NOT smaller than the original.
|
||||
"""
|
||||
import math
|
||||
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()
|
@ -1,410 +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, 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 ax == 0:
|
||||
return [(pt1, pt2)]
|
||||
|
||||
t = float(where - (bx, by)[isHorizontal]) / ax
|
||||
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()
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
@ -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()
|
@ -1,15 +0,0 @@
|
||||
"""
|
||||
|
||||
Directory for modules supporting
|
||||
|
||||
Unified
|
||||
|
||||
Font
|
||||
|
||||
Objects
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
@ -1,245 +0,0 @@
|
||||
import math
|
||||
from fontTools.pens.basePen import AbstractPen
|
||||
from ufoLib.pointPen import AbstractPointPen
|
||||
from robofab.pens.pointPen import BasePointToSegmentPen
|
||||
|
||||
|
||||
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.printingPens 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))
|
@ -1,144 +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):
|
||||
"""
|
||||
Pen to calculate the margins according to a slanted coordinate system. Slant angle comes from font.info.italicAngle.
|
||||
|
||||
- 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.
|
||||
|
||||
Notes:
|
||||
|
||||
- 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):
|
||||
print "_getAngled", pt
|
||||
r = (self.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)
|
||||
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()
|
||||
print "_lineTo"
|
||||
self._getAngled(pt)
|
||||
|
||||
def _curveToOne(self, pt1, pt2, pt3):
|
||||
step = 1.0/self.maxSteps
|
||||
factors = range(0, self.maxSteps+1)
|
||||
for i in factors:
|
||||
print "_curveToOne", i
|
||||
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.
|
||||
print "_qCurveToOne"
|
||||
self._getAngled(pt)
|
||||
self.currentPt = pt3
|
||||
|
||||
def getAngledMargins(glyph, font):
|
||||
"""Convenience function, returns the angled margins for this glyph. Adjusted for font.info.italicAngle."""
|
||||
pen = AngledMarginPen(font, glyph.width, font.info.italicAngle)
|
||||
glyph.draw(pen)
|
||||
return pen.margin
|
||||
|
||||
def setAngledLeftMargin(glyph, font, value):
|
||||
"""Convenience function, sets 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):
|
||||
"""Convenience function, sets 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):
|
||||
"""Convenience function, centers 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__":
|
||||
|
||||
def makeTestGlyph():
|
||||
# make a simple glyph that we can test the pens with.
|
||||
from robofab.objects.objectsRF import RGlyph
|
||||
testGlyph = RGlyph()
|
||||
testGlyph.name = "testGlyph"
|
||||
testGlyph.width = 1000
|
||||
pen = testGlyph.getPen()
|
||||
pen.moveTo((100, 100))
|
||||
pen.lineTo((900, 100))
|
||||
pen.lineTo((900, 800))
|
||||
pen.lineTo((100, 800))
|
||||
# a curve
|
||||
pen.curveTo((120, 700), (120, 300), (100, 100))
|
||||
pen.closePath()
|
||||
return testGlyph
|
||||
|
||||
def angledMarginPenTest():
|
||||
testGlyph = makeTestGlyph()
|
||||
glyphSet = {}
|
||||
testPen = AngledMarginPen(glyphSet, width=0, italicAngle=10)
|
||||
testGlyph.draw(testPen)
|
||||
print testPen.margin
|
||||
|
||||
angledMarginPenTest()
|
@ -1,122 +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
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
def makeTestGlyph():
|
||||
# make a simple glyph that we can test the pens with.
|
||||
from robofab.objects.objectsRF import RGlyph
|
||||
testGlyph = RGlyph()
|
||||
testGlyph.name = "testGlyph"
|
||||
testGlyph.width = 1000
|
||||
pen = testGlyph.getPen()
|
||||
pen.moveTo((100, 100))
|
||||
pen.lineTo((900, 100))
|
||||
pen.lineTo((900, 800))
|
||||
pen.lineTo((100, 800))
|
||||
# a curve
|
||||
pen.curveTo((120, 700), (120, 300), (100, 100))
|
||||
pen.closePath()
|
||||
return testGlyph
|
||||
|
||||
def controlBoundsPenTest():
|
||||
testGlyph = makeTestGlyph()
|
||||
glyphSet = {}
|
||||
testPen = ControlBoundsPen(glyphSet)
|
||||
testGlyph.draw(testPen)
|
||||
assert testPen.bounds == (100, 100, 900, 800)
|
||||
|
||||
|
||||
controlBoundsPenTest()
|
@ -1,86 +0,0 @@
|
||||
from ufoLib.pointPen import AbstractPointPen
|
||||
|
||||
class DigestPointPen(AbstractPointPen):
|
||||
|
||||
"""This calculates a tuple representing the structure and values in a glyph:
|
||||
|
||||
- including coordinates
|
||||
- including components
|
||||
|
||||
>>> from robofab.pens.digestPen import DigestPointPen
|
||||
>>> pen = DigestPointPen()
|
||||
>>> g = CurrentGlyph()
|
||||
>>> g.drawPoints(pen)
|
||||
>>> pen.getDigest()
|
||||
('beginPath', ((25, 425), 'line', False, None),((25, 0), 'line', False, None),((95, 0), 'line', False, None),((95, 425), 'line', False, None), 'endPath',
|
||||
'beginPath', ((25, 595), 'line', False, None),((25, 491), 'line', False, None),((95, 491), 'line', False, None),((95, 595), 'line', False, None), 'endPath')
|
||||
|
||||
"""
|
||||
|
||||
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 the digest as a tuple with all coordinates of all points."""
|
||||
return tuple(self._data)
|
||||
|
||||
def getDigestPointsOnly(self, needSort=True):
|
||||
""" Return the digest as 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):
|
||||
|
||||
"""This calculates a tuple representing the structure and values in a glyph:
|
||||
|
||||
- excluding coordinates
|
||||
- excluding components
|
||||
|
||||
>>> from robofab.pens.digestPen import DigestPointStructurePen
|
||||
>>> pen = DigestPointStructurePen()
|
||||
>>> g = CurrentGlyph()
|
||||
>>> g.drawPoints(pen)
|
||||
>>> pen.getDigest()
|
||||
('beginPath', 'line', 'line', 'line', 'line', 'endPath', 'beginPath', 'line', 'line', 'line', 'line', 'endPath')
|
||||
|
||||
"""
|
||||
|
||||
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
|
||||
self._data.append(segmentType)
|
||||
|
||||
def addComponent(self, baseGlyphName, transformation):
|
||||
self._data.append(baseGlyphName)
|
||||
|
@ -1,228 +0,0 @@
|
||||
"""
|
||||
=============================
|
||||
Filters: chaining pen objects
|
||||
=============================
|
||||
|
||||
By chaining two (or more) pen objects together, the first pen can function as a filter to the second. The first pen receives data from (for instance) glyph.draw() or glyph.drawPoints(). The filter pen then processes the data and passes new, fresh data to the second pen. In turn the second pen can draw in a glyph.
|
||||
|
||||
Filterpens need to be initialised with a second pen object that will receive the processed data. The examples in this module, ThresholdPen and FlattenPen, have proven useful in a number of projects. For convenienve, each pen has a wrapper function that takes care of making a new glyph to dump the processed data in, and making the appropriate pen instances. But these are non-normative examples.
|
||||
|
||||
All pens in this module subclass from fontTools.pens.basePen.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from fontTools.pens.basePen import AbstractPen, BasePen
|
||||
|
||||
|
||||
|
||||
from ufoLib.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.sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)
|
||||
|
||||
|
||||
class ThresholdPen(AbstractPen):
|
||||
|
||||
"""This pen only draws segments longer in length than the threshold value.
|
||||
|
||||
- otherPen: a different segment pen object this filter should draw the results with.
|
||||
- threshold: the minimum length of a segment
|
||||
"""
|
||||
|
||||
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 applies the **ThresholdPen** to a glyph. Returns a new glyph object (from objectsRF.RGlyph)."""
|
||||
|
||||
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
|
||||
|
||||
#
|
||||
# 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):
|
||||
|
||||
"""This filter pen processes the contours into a series of straight lines by flattening the curves.
|
||||
|
||||
- otherPen: a different segment pen object this filter should draw the results with.
|
||||
- approximateSegmentLength: the length you want the flattened segments to be (roughly).
|
||||
- segmentLines: whether to cut straight lines into segments as well.
|
||||
- filterDoubles: don't draw if a segment goes to the same coordinate.
|
||||
"""
|
||||
|
||||
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):
|
||||
|
||||
"""Convenience function that applies the **FlattenPen** pen to a glyph. Returns a new glyph object."""
|
||||
|
||||
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
|
||||
|
@ -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 ufoLib.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()
|
@ -1,183 +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 height or width.
|
||||
|
||||
- isHorizontal = True: slice the glyph at y=value.
|
||||
- isHorizontal = False: slice the glyph at x=value.
|
||||
|
||||
>>> f = CurrentFont()
|
||||
>>> g = CurrentGlyph()
|
||||
>>> pen = MarginPen(f, 200, isHorizontal=True)
|
||||
>>> g.draw(pen)
|
||||
>>> print pen.getMargins()
|
||||
(75.7881, 181.9713)
|
||||
|
||||
>>> pen = MarginPen(f, 200, isHorizontal=False)
|
||||
>>> g.draw(pen)
|
||||
>>> print pen.getMargins()
|
||||
(26.385, 397.4469)
|
||||
>>> print pen.getAll()
|
||||
[75.7881, 181.9713]
|
||||
|
||||
>>> pen = MarginPen(f, 200, isHorizontal=False)
|
||||
>>> g.draw(pen)
|
||||
>>> print pen.getMargins()
|
||||
(26.385, 397.4469)
|
||||
>>> print pen.getAll()
|
||||
[26.385, 171.6137, 268.0, 397.4469]
|
||||
|
||||
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):
|
||||
if self.glyphSet is None:
|
||||
return
|
||||
if baseGlyph in self.glyphSet:
|
||||
glyph = self.glyphSet[baseGlyph]
|
||||
if glyph is not None:
|
||||
glyph.draw(self)
|
||||
|
||||
def getMargins(self):
|
||||
"""Return the extremes of the slice 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):
|
||||
"""Return the extremes of the slice 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):
|
||||
"""Return 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__":
|
||||
|
||||
def makeTestGlyph():
|
||||
# make a simple glyph that we can test the pens with.
|
||||
from robofab.objects.objectsRF import RGlyph
|
||||
testGlyph = RGlyph()
|
||||
testGlyph.name = "testGlyph"
|
||||
testGlyph.width = 1000
|
||||
pen = testGlyph.getPen()
|
||||
pen.moveTo((100, 100))
|
||||
pen.lineTo((900, 100))
|
||||
pen.lineTo((900, 800))
|
||||
pen.lineTo((100, 800))
|
||||
# a curve
|
||||
pen.curveTo((120, 700), (120, 300), (100, 100))
|
||||
pen.closePath()
|
||||
return testGlyph
|
||||
|
||||
def controlBoundsPenTest():
|
||||
testGlyph = makeTestGlyph()
|
||||
glyphSet = {}
|
||||
value = 200
|
||||
isHorizontal = True
|
||||
testPen = MarginPen(glyphSet, value, isHorizontal)
|
||||
testGlyph.draw(testPen)
|
||||
assert testPen.getAll() == [107.5475, 200.0, 300.0]
|
||||
|
||||
controlBoundsPenTest()
|
@ -1,186 +0,0 @@
|
||||
"""Some pens that are needed during glyph math"""
|
||||
|
||||
|
||||
from ufoLib.pointPen import AbstractPointPen
|
||||
from robofab.pens.pointPen import BasePointToSegmentPen
|
||||
|
||||
|
||||
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):
|
||||
|
||||
"""
|
||||
Filtering 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)
|
||||
|
@ -1,172 +0,0 @@
|
||||
from fontTools.pens.basePen import AbstractPen
|
||||
from ufoLib.pointPen import AbstractPointPen
|
||||
|
||||
__all__ = ["AbstractPointPen", "BasePointToSegmentPen", "PrintingPointPen",
|
||||
"PrintingSegmentPen", "SegmentPrintingPointPen"]
|
||||
|
||||
|
||||
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, **kwargs):
|
||||
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, identifier=None, **kwargs):
|
||||
self.havePath = True
|
||||
args = []
|
||||
if identifier is not None:
|
||||
args.append("identifier=%r" % identifier)
|
||||
if kwargs:
|
||||
args.append("**%s" % kwargs)
|
||||
print "pen.beginPath(%s)" % ", ".join(args)
|
||||
|
||||
def endPath(self):
|
||||
self.havePath = False
|
||||
print "pen.endPath()"
|
||||
|
||||
def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=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 identifier is not None:
|
||||
args.append("identifier=%r" % identifier)
|
||||
if kwargs:
|
||||
args.append("**%s" % kwargs)
|
||||
print "pen.addPoint(%s)" % ", ".join(args)
|
||||
|
||||
def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
|
||||
assert not self.havePath
|
||||
args = [baseGlyphName, str(tuple(transformation))]
|
||||
if identifier is not None:
|
||||
args.append("identifier=%r" % identifier)
|
||||
if kwargs:
|
||||
args.append("**%s" % kwargs)
|
||||
print "pen.addComponent(%s)" % ", ".join(args)
|
||||
|
||||
|
||||
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)
|
@ -1,84 +0,0 @@
|
||||
from robofab.pens.pointPen import BasePointToSegmentPen
|
||||
from ufoLib.pointPen import AbstractPointPen
|
||||
|
||||
"""
|
||||
|
||||
Printing pens print their data. Useful for demos and debugging.
|
||||
|
||||
"""
|
||||
|
||||
__all__ = ["PrintingPointPen", "PrintingSegmentPen", "SegmentPrintingPointPen"]
|
||||
|
||||
class PrintingPointPen(AbstractPointPen):
|
||||
"""A PointPen that prints every step.
|
||||
"""
|
||||
|
||||
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):
|
||||
"""A SegmentPen that prints every step.
|
||||
"""
|
||||
|
||||
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):
|
||||
"""A SegmentPen that pprints every step.
|
||||
"""
|
||||
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)
|
@ -1,25 +0,0 @@
|
||||
from fontTools.pens.basePen import BasePen
|
||||
|
||||
class QuartzPen(BasePen):
|
||||
|
||||
"""Pen to draw onto a Quartz drawing context (Carbon.CG).
|
||||
|
||||
- OSX specific.
|
||||
|
||||
"""
|
||||
|
||||
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()
|
@ -1,125 +0,0 @@
|
||||
"""PointPen for reversing the winding direction of contours."""
|
||||
|
||||
|
||||
__all__ = ["ReverseContourPointPen"]
|
||||
|
||||
|
||||
from ufoLib.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.printingPens 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()
|
@ -1,100 +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 contou 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 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]:
|
||||
rPoint = RPoint(x=point[0], y=point[1], pointType=OFFCURVE, name=name)
|
||||
rPoint.setParent(segment)
|
||||
rPoints.append(rPoint)
|
||||
# now the onCurve
|
||||
point = 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)
|
||||
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))
|
||||
|
||||
|
@ -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.
|
||||
"""
|
@ -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)
|
@ -1,566 +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)",
|
||||
}
|
||||
|
||||
fontInfoVersion3 = {
|
||||
"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.",
|
||||
"openTypeGaspRangeRecords" : [
|
||||
dict(rangeMaxPPEM=10, rangeGaspBehavior=[0]),
|
||||
dict(rangeMaxPPEM=20, rangeGaspBehavior=[1]),
|
||||
dict(rangeMaxPPEM=30, rangeGaspBehavior=[2]),
|
||||
dict(rangeMaxPPEM=40, rangeGaspBehavior=[3]),
|
||||
dict(rangeMaxPPEM=50, rangeGaspBehavior=[0, 1, 2, 3]),
|
||||
dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0])
|
||||
],
|
||||
"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)",
|
||||
"openTypeNameRecords" : [
|
||||
dict(nameID=1, platformID=1, encodingID=1, languageID=1, string="Name Record."),
|
||||
dict(nameID=2, platformID=1, encodingID=1, languageID=1, string="Name Record.")
|
||||
],
|
||||
"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)",
|
||||
"woffMajorVersion" : 1,
|
||||
"woffMinorVersion" : 0,
|
||||
"woffMetadataUniqueID" : dict(id="string"),
|
||||
"woffMetadataVendor" : dict(name="Some Foundry", url="http://somefoundry.com"),
|
||||
"woffMetadataCredits" : dict(
|
||||
credits=[
|
||||
dict(name="Some Designer"),
|
||||
dict(name=""),
|
||||
dict(name="Some Designer", url="http://somedesigner.com"),
|
||||
dict(name="Some Designer", url=""),
|
||||
dict(name="Some Designer", role="Designer"),
|
||||
dict(name="Some Designer", role=""),
|
||||
dict(name="Some Designer", dir="ltr"),
|
||||
dict(name="rengiseD emoS", dir="rtl"),
|
||||
{"name" : "Some Designer", "class" : "hello"},
|
||||
{"name" : "Some Designer", "class" : ""},
|
||||
]
|
||||
),
|
||||
"woffMetadataDescription" : dict(
|
||||
url="http://somefoundry.com/foo/description",
|
||||
text=[
|
||||
dict(text="foo"),
|
||||
dict(text=""),
|
||||
dict(text="foo", language="bar"),
|
||||
dict(text="foo", language=""),
|
||||
dict(text="foo", dir="ltr"),
|
||||
dict(text="foo", dir="rtl"),
|
||||
{"text" : "foo", "class" : "foo"},
|
||||
{"text" : "foo", "class" : ""},
|
||||
]
|
||||
),
|
||||
"woffMetadataLicense" : dict(
|
||||
url="http://somefoundry.com/foo/license",
|
||||
id="foo",
|
||||
text=[
|
||||
dict(text="foo"),
|
||||
dict(text=""),
|
||||
dict(text="foo", language="bar"),
|
||||
dict(text="foo", language=""),
|
||||
dict(text="foo", dir="ltr"),
|
||||
dict(text="foo", dir="rtl"),
|
||||
{"text" : "foo", "class" : "foo"},
|
||||
{"text" : "foo", "class" : ""},
|
||||
]
|
||||
),
|
||||
"woffMetadataCopyright" : dict(
|
||||
text=[
|
||||
dict(text="foo"),
|
||||
dict(text=""),
|
||||
dict(text="foo", language="bar"),
|
||||
dict(text="foo", language=""),
|
||||
dict(text="foo", dir="ltr"),
|
||||
dict(text="foo", dir="rtl"),
|
||||
{"text" : "foo", "class" : "foo"},
|
||||
{"text" : "foo", "class" : ""},
|
||||
]
|
||||
),
|
||||
"woffMetadataTrademark" : dict(
|
||||
text=[
|
||||
dict(text="foo"),
|
||||
dict(text=""),
|
||||
dict(text="foo", language="bar"),
|
||||
dict(text="foo", language=""),
|
||||
dict(text="foo", dir="ltr"),
|
||||
dict(text="foo", dir="rtl"),
|
||||
{"text" : "foo", "class" : "foo"},
|
||||
{"text" : "foo", "class" : ""},
|
||||
]
|
||||
),
|
||||
"woffMetadataLicensee" : dict(
|
||||
name="Some Licensee"
|
||||
),
|
||||
"woffMetadataExtensions" : [
|
||||
dict(
|
||||
# everything
|
||||
names=[
|
||||
dict(text="foo"),
|
||||
dict(text=""),
|
||||
dict(text="foo", language="bar"),
|
||||
dict(text="foo", language=""),
|
||||
dict(text="foo", dir="ltr"),
|
||||
dict(text="foo", dir="rtl"),
|
||||
{"text" : "foo", "class" : "hello"},
|
||||
{"text" : "foo", "class" : ""},
|
||||
],
|
||||
items=[
|
||||
# everything
|
||||
dict(
|
||||
id="foo",
|
||||
names=[
|
||||
dict(text="foo"),
|
||||
dict(text=""),
|
||||
dict(text="foo", language="bar"),
|
||||
dict(text="foo", language=""),
|
||||
dict(text="foo", dir="ltr"),
|
||||
dict(text="foo", dir="rtl"),
|
||||
{"text" : "foo", "class" : "hello"},
|
||||
{"text" : "foo", "class" : ""},
|
||||
],
|
||||
values=[
|
||||
dict(text="foo"),
|
||||
dict(text=""),
|
||||
dict(text="foo", language="bar"),
|
||||
dict(text="foo", language=""),
|
||||
dict(text="foo", dir="ltr"),
|
||||
dict(text="foo", dir="rtl"),
|
||||
{"text" : "foo", "class" : "hello"},
|
||||
{"text" : "foo", "class" : ""},
|
||||
]
|
||||
),
|
||||
# no id
|
||||
dict(
|
||||
names=[
|
||||
dict(text="foo")
|
||||
],
|
||||
values=[
|
||||
dict(text="foo")
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
# no names
|
||||
dict(
|
||||
items=[
|
||||
dict(
|
||||
id="foo",
|
||||
names=[
|
||||
dict(text="foo")
|
||||
],
|
||||
values=[
|
||||
dict(text="foo")
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
],
|
||||
"firstKerningGroupPrefix" : "@kern1",
|
||||
"secondKerningGroupPrefix" : "@kern2",
|
||||
"guidelines" : [
|
||||
# ints
|
||||
dict(x=100, y=200, angle=45),
|
||||
# floats
|
||||
dict(x=100.5, y=200.5, angle=45.5),
|
||||
# edges
|
||||
dict(x=0, y=0, angle=0),
|
||||
dict(x=0, y=0, angle=360),
|
||||
dict(x=0, y=0, angle=360.0),
|
||||
# no y
|
||||
dict(x=100),
|
||||
# no x
|
||||
dict(y=200),
|
||||
# name
|
||||
dict(x=100, y=200, angle=45, name="foo"),
|
||||
dict(x=100, y=200, angle=45, name=""),
|
||||
# identifier
|
||||
dict(x=100, y=200, angle=45, identifier="guide1"),
|
||||
dict(x=100, y=200, angle=45, identifier="guide2"),
|
||||
dict(x=100, y=200, angle=45, identifier=u"\x20"),
|
||||
dict(x=100, y=200, angle=45, identifier=u"\x7E"),
|
||||
# colors
|
||||
dict(x=100, y=200, angle=45, color="0,0,0,0"),
|
||||
dict(x=100, y=200, angle=45, color="1,0,0,0"),
|
||||
dict(x=100, y=200, angle=45, color="1,1,1,1"),
|
||||
dict(x=100, y=200, angle=45, color="0,1,0,0"),
|
||||
dict(x=100, y=200, angle=45, color="0,0,1,0"),
|
||||
dict(x=100, y=200, angle=45, color="0,0,0,1"),
|
||||
dict(x=100, y=200, angle=45, color="1, 0, 0, 0"),
|
||||
dict(x=100, y=200, angle=45, color="0, 1, 0, 0"),
|
||||
dict(x=100, y=200, angle=45, color="0, 0, 1, 0"),
|
||||
dict(x=100, y=200, angle=45, color="0, 0, 0, 1"),
|
||||
dict(x=100, y=200, angle=45, color=".5,0,0,0"),
|
||||
dict(x=100, y=200, angle=45, color="0,.5,0,0"),
|
||||
dict(x=100, y=200, angle=45, color="0,0,.5,0"),
|
||||
dict(x=100, y=200, angle=45, color="0,0,0,.5"),
|
||||
dict(x=100, y=200, angle=45, color=".5,1,1,1"),
|
||||
dict(x=100, y=200, angle=45, color="1,.5,1,1"),
|
||||
dict(x=100, y=200, angle=45, color="1,1,.5,1"),
|
||||
dict(x=100, y=200, angle=45, color="1,1,1,.5"),
|
||||
],
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
import unittest
|
||||
from cStringIO import StringIO
|
||||
import sys
|
||||
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()
|
||||
|
@ -1,56 +0,0 @@
|
||||
import unittest
|
||||
from cStringIO import StringIO
|
||||
import sys
|
||||
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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -1,175 +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)
|
||||
|
||||
|
||||
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()
|
@ -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 ufoLib.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()
|
@ -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()
|
||||
|
@ -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.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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
|
||||
"""
|
@ -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
|
@ -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
|
||||
|
||||
|
@ -1,142 +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),
|
||||
('fontStyle', 1),
|
||||
('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)
|
||||
if entry is not None:
|
||||
try:
|
||||
setattr(info, name, unicode(entry.string, "utf16"))
|
||||
except:
|
||||
print "Error importing value %s: %s"%(str(name), str(info))
|
||||
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()
|
||||
|
||||
|
@ -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()
|
@ -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 FontLab?
|
||||
try:
|
||||
from FL import fl
|
||||
self.applicationName = fl.filename
|
||||
self.inFontLab = True
|
||||
self.flVersion = fl.version
|
||||
except ImportError: pass
|
||||
# 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: pass
|
||||
# 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: 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
|
@ -30,7 +30,7 @@ are available for external use. These are
|
||||
validateFontInfoVersion2ValueForAttribute
|
||||
validateFontInfoVersion3ValueForAttribute
|
||||
|
||||
Value conversion functions are availble for converting
|
||||
Value conversion functions are available for converting
|
||||
fontinfo.plist values between the possible format versions.
|
||||
convertFontInfoValueForAttributeFromVersion1ToVersion2
|
||||
convertFontInfoValueForAttributeFromVersion2ToVersion1
|
||||
|
@ -1,5 +1,6 @@
|
||||
from __future__ import absolute_import
|
||||
"""Small helper module to parse Plist-formatted data from trees as created
|
||||
"""
|
||||
Small helper module to parse Plist-formatted data from trees as created
|
||||
by xmlTreeBuilder.
|
||||
"""
|
||||
|
||||
@ -8,7 +9,8 @@ __all__ = "readPlistFromTree"
|
||||
from ufoLib.plistlib import PlistParser
|
||||
|
||||
def readPlistFromTree(tree):
|
||||
"""Given a (sub)tree created by xmlTreeBuilder, interpret it
|
||||
"""
|
||||
Given a (sub)tree created by xmlTreeBuilder, interpret it
|
||||
as Plist-formatted data, and return the root object.
|
||||
"""
|
||||
parser = PlistTreeParser()
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
|
||||
"""
|
||||
plistlib.py -- a tool to generate and parse MacOSX .plist files.
|
||||
|
||||
The PropertList (.plist) file format is a simple XML pickle supporting
|
||||
The PropertyList (.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.
|
||||
|
||||
@ -50,7 +51,6 @@ Parse Plist example:
|
||||
print pl["aKey"]
|
||||
"""
|
||||
|
||||
|
||||
__all__ = [
|
||||
"readPlist", "writePlist", "readPlistFromString", "writePlistToString",
|
||||
"readPlistFromResource", "writePlistToResource",
|
||||
|
@ -1,84 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""FontLab Tokenize
|
||||
|
||||
Tokenize FontLab’s preview/metrics text into single characters
|
||||
respecting escaped glyph names (eg. “/A.smcp”) and providing a
|
||||
lossless reverse function. Sample usage (and actual test suite):
|
||||
|
||||
>>> tokenize('/A/B/C')
|
||||
['/A', '/B', '/C']
|
||||
>>> tokenize('abcde/B/C')
|
||||
['a', 'b', 'c', 'd', 'e', '/B', '/C']
|
||||
>>> tokenize('foo/A.smcp/B.smcp abc')
|
||||
['f', 'o', 'o', '/A.smcp', '/B.smcp', 'a', 'b', 'c']
|
||||
>>> p = ['f', 'o', 'o', '/A.smcp', '/B.smcp', 'a', 'b', 'c']
|
||||
>>> serialize(p)
|
||||
'foo/A.smcp/B.smcp abc'
|
||||
>>> tokenize('/a /b /c')
|
||||
['/a', '/b', '/c']
|
||||
>>> tokenize('/a/b c')
|
||||
['/a', '/b', 'c']
|
||||
>>> tokenize('@a@b@')
|
||||
['@', 'a', '@', 'b', '@']
|
||||
>>> tokenize('abc def ghi ')
|
||||
['a', 'b', 'c', ' ', 'd', 'e', 'f', ' ', 'g', 'h', 'i', ' ']
|
||||
>>> p = ['a', 'b', 'c', ' ', 'd', 'e', 'f', ' ', 'g', 'h', 'i', ' ']
|
||||
>>> serialize(p)
|
||||
'abc def ghi '
|
||||
>>> serialize(['/a', 'b', '/c', 'd'])
|
||||
'/a b/c d'
|
||||
"""
|
||||
|
||||
__author__ = 'Antonio Cavedoni <http://cavedoni.com/>'
|
||||
__version__ = '0.1'
|
||||
__svnid__ = '$Id$'
|
||||
__license__ = 'Python'
|
||||
|
||||
def tokenize(input):
|
||||
tokens = []
|
||||
escaped = []
|
||||
for i in range(len(input)):
|
||||
x = input[i]
|
||||
if x != '/' and not escaped:
|
||||
tokens.append(x)
|
||||
else:
|
||||
if x == '/' and not escaped:
|
||||
# append the slash so the escaped list is no longer
|
||||
# false: starts capturing elements
|
||||
escaped.append(x)
|
||||
elif x != '/' and escaped:
|
||||
if i == (len(input) - 1):
|
||||
escaped.append(x)
|
||||
tokens.append("".join(escaped))
|
||||
else:
|
||||
if x == ' ':
|
||||
tokens.append("".join(escaped))
|
||||
escaped = []
|
||||
else:
|
||||
escaped.append(x)
|
||||
elif x == '/' and escaped:
|
||||
# starts a new sequence so, flush the escaped buffer
|
||||
# and start anew
|
||||
tokens.append("".join(escaped))
|
||||
escaped = [x]
|
||||
|
||||
return tokens
|
||||
|
||||
def serialize(tokens):
|
||||
series = []
|
||||
for i in range(len(tokens)):
|
||||
t = tokens[i]
|
||||
if t.startswith('/') and i != (len(tokens) - 1):
|
||||
if not tokens[i+1].startswith('/'):
|
||||
series.append(t + ' ')
|
||||
else:
|
||||
series.append(t)
|
||||
else:
|
||||
series.append(t)
|
||||
|
||||
return "".join(series)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
@ -1,28 +0,0 @@
|
||||
|
||||
The code in the Contributions folder is covered, like the rest of RoboFab,
|
||||
by the BSD license, unless stated otherwise in the code itself.
|
||||
|
||||
|
||||
RoboFab License Agreement
|
||||
|
||||
Copyright (c) 2005-2012, 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
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
RoboFab Contributed Scripts
|
||||
|
||||
This is a folder with contributed scripts.
|
@ -1,3 +0,0 @@
|
||||
RoboFab Scripts
|
||||
|
||||
These are some folders with scripts that can be run in FontLab.
|
@ -1,72 +0,0 @@
|
||||
#FLM: Align Two Nodes
|
||||
|
||||
"""Align bPoints horizontally, vertically or both."""
|
||||
|
||||
from robofab.world import CurrentGlyph
|
||||
from robofab.interface.all.dialogs import TwoChecks
|
||||
|
||||
glyph = CurrentGlyph()
|
||||
|
||||
sel = []
|
||||
|
||||
#gather selected bPoints
|
||||
for contour in glyph.contours:
|
||||
if contour.selected:
|
||||
for bPoint in contour.bPoints:
|
||||
if bPoint.selected:
|
||||
sel.append(bPoint)
|
||||
|
||||
if len(sel) != 0:
|
||||
xL = []
|
||||
yL = []
|
||||
|
||||
#store up all coordinates for use later
|
||||
for bPoint in sel:
|
||||
x, y = bPoint.anchor
|
||||
xL.append(x)
|
||||
yL.append(y)
|
||||
|
||||
if len(xL) > 1:
|
||||
w = TwoChecks("Horizontal", "Vertical", 0, 0)
|
||||
if w == None or w == 0:
|
||||
#the user doesn't want to align anything
|
||||
pass
|
||||
else:
|
||||
#find the center among all those bPoints
|
||||
minX = min(xL)
|
||||
maxX = max(xL)
|
||||
minY = min(yL)
|
||||
maxY = max(yL)
|
||||
cX = int(round((minX + maxX)/2))
|
||||
cY = int(round((minY + maxY)/2))
|
||||
|
||||
#set the undo
|
||||
fl.SetUndo()
|
||||
|
||||
#determine what the user wants to do
|
||||
noY = False
|
||||
noX = False
|
||||
if w == 1:
|
||||
#the user wants to align y
|
||||
noX = True
|
||||
elif w == 2:
|
||||
#the user wants to align x
|
||||
noY = True
|
||||
elif w == 3:
|
||||
#the user wants to align x and y
|
||||
pass
|
||||
|
||||
|
||||
for bPoint in sel:
|
||||
#get the move value for the bPoint
|
||||
aX, aY = bPoint.anchor
|
||||
mX = cX - aX
|
||||
mY = cY - aY
|
||||
if noY:
|
||||
#don't move the y
|
||||
mY = 0
|
||||
if noX:
|
||||
#don't move the x
|
||||
mX = 0
|
||||
bPoint.move((mX, mY))
|
||||
glyph.update()
|
@ -1,14 +0,0 @@
|
||||
"""Correct contour direction for all glyphs in the font"""
|
||||
|
||||
from robofab.world import OpenFont
|
||||
from robofab.interface.all.dialogs import ProgressBar
|
||||
|
||||
font = OpenFont()
|
||||
bar = ProgressBar('Correcting contour direction...', len(font))
|
||||
for glyph in font:
|
||||
bar.label(glyph.name)
|
||||
glyph.correctDirection()
|
||||
glyph.update()
|
||||
bar.tick()
|
||||
font.update()
|
||||
bar.close()
|
@ -1,27 +0,0 @@
|
||||
#
|
||||
#
|
||||
# make a list of which glyphs in this font could theoretically
|
||||
# interpolate with each other.
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
from robofab.world import CurrentFont
|
||||
from robofab.pens.digestPen import DigestPointPen, DigestPointStructurePen
|
||||
|
||||
compatibles = {}
|
||||
|
||||
f = CurrentFont()
|
||||
for c in f:
|
||||
p = DigestPointStructurePen()
|
||||
c.drawPoints(p)
|
||||
d = p.getDigest()
|
||||
if not compatibles.has_key(d):
|
||||
compatibles[d] = []
|
||||
compatibles[d].append(c.name)
|
||||
|
||||
print
|
||||
print 'In %s, these glyphs could interpolate:'%(f.info.postscriptFullName)
|
||||
for d, names in compatibles.items():
|
||||
if len(names) > 1:
|
||||
print ", ".join(names)
|
@ -1,22 +0,0 @@
|
||||
#FLM: Glyph Appender
|
||||
|
||||
"""Add a glyph to the current glyph"""
|
||||
|
||||
from robofab.world import CurrentFont, CurrentGlyph
|
||||
from robofab.interface.all.dialogs import SelectGlyph
|
||||
|
||||
glyph = CurrentGlyph()
|
||||
font = CurrentFont()
|
||||
|
||||
# select a glyph to add
|
||||
selected = SelectGlyph(font)
|
||||
# make sure that we are not trying add the current glyph to itself
|
||||
if selected.name != glyph.name:
|
||||
# preserve the current state
|
||||
fl.SetUndo()
|
||||
# add the selected glyph to the current glyph
|
||||
glyph.appendGlyph(selected)
|
||||
# always update the glyph!
|
||||
glyph.update()
|
||||
# and, just to be safe, update the font...
|
||||
font.update()
|
@ -1,24 +0,0 @@
|
||||
#FLM: Fun with GlyphMath
|
||||
|
||||
# this example is meant to run with the RoboFab Demo Font
|
||||
# as the Current Font. So, if you're doing this in FontLab
|
||||
# import the Demo Font UFO first.
|
||||
|
||||
from robofab.world import CurrentFont
|
||||
from random import random
|
||||
|
||||
f = CurrentFont()
|
||||
condensedLight = f["a#condensed_light"]
|
||||
wideLight = f["a#wide_light"]
|
||||
wideBold = f["a#wide_bold"]
|
||||
|
||||
diff = wideLight - condensedLight
|
||||
|
||||
destination = f.newGlyph("a#deltaexperiment")
|
||||
destination.clear()
|
||||
x = wideBold + (condensedLight-wideLight)*random()
|
||||
|
||||
destination.appendGlyph( x)
|
||||
destination.width = x.width
|
||||
destination.update()
|
||||
f.update()
|
@ -1,50 +0,0 @@
|
||||
#FLM: Interpol Preview
|
||||
|
||||
"""This script draws all incremental interpolations
|
||||
between 1% and 99% of a selected glyph into a new font.
|
||||
It requires two open source fonts in FontLab."""
|
||||
|
||||
from robofab.interface.all.dialogs import SelectFont, OneList, ProgressBar
|
||||
from robofab.world import NewFont
|
||||
|
||||
src1 = SelectFont('Select source font one:')
|
||||
if src1:
|
||||
src2 = SelectFont('Select source font two:')
|
||||
if src2:
|
||||
# collect a list of all compatible glyphs
|
||||
common = []
|
||||
for glyphName in src1.keys():
|
||||
if src2.has_key(glyphName):
|
||||
if src1[glyphName].isCompatible(src2[glyphName]):
|
||||
common.append(glyphName)
|
||||
common.sort()
|
||||
selName = OneList(common, 'Select a glyph:')
|
||||
if selName:
|
||||
dest = NewFont()
|
||||
g1 = src1[selName]
|
||||
g2 = src2[selName]
|
||||
count = 1
|
||||
bar = ProgressBar('Interpolating...', 100)
|
||||
# add the sourec one glyph for reference
|
||||
dest.newGlyph(selName + '_000')
|
||||
dest[selName + '_000'].width = src1[selName].width
|
||||
dest[selName + '_000'].appendGlyph(src1[selName])
|
||||
dest[selName + '_000'].mark = 1
|
||||
dest[selName + '_000'].update()
|
||||
# add a new glyph and interpolate it
|
||||
while count != 100:
|
||||
factor = count * .01
|
||||
newName = selName + '_' + `count`.zfill(3)
|
||||
gD = dest.newGlyph(newName)
|
||||
gD.interpolate(factor, g1, g2)
|
||||
gD.update()
|
||||
bar.tick()
|
||||
count = count + 1
|
||||
# add the source two glyph for reference
|
||||
dest.newGlyph(selName + '_100')
|
||||
dest[selName + '_100'].width = src2[selName].width
|
||||
dest[selName + '_100'].appendGlyph(src2[selName])
|
||||
dest[selName + '_100'].mark = 1
|
||||
dest[selName + '_100'].update()
|
||||
dest.update()
|
||||
bar.close()
|
@ -1,16 +0,0 @@
|
||||
#FLM: Invert Selection
|
||||
|
||||
"""Invert the selected segments in the current glyph"""
|
||||
|
||||
from robofab.world import CurrentGlyph
|
||||
|
||||
glyph = CurrentGlyph()
|
||||
for contour in glyph.contours:
|
||||
notSelected = []
|
||||
for segment in contour.segments:
|
||||
if not segment.selected:
|
||||
notSelected.append(segment.index)
|
||||
contour.selected = False
|
||||
for index in notSelected:
|
||||
contour[index].selected = True
|
||||
glyph.update()
|
@ -1,50 +0,0 @@
|
||||
# FLM: Make Cameo Font
|
||||
|
||||
"""Make a cameo font. Pretty simple."""
|
||||
|
||||
from robofab.world import CurrentFont
|
||||
from robofab.interface.all.dialogs import Message
|
||||
|
||||
buffer = 30
|
||||
scaleValue = .9
|
||||
|
||||
f = CurrentFont()
|
||||
# clear all kerning
|
||||
f.kerning.clear()
|
||||
#determine top and bottom of the box
|
||||
t = f.info.unitsPerEm + f.info.descender + buffer
|
||||
b = f.info.descender - buffer
|
||||
#first decompose any components
|
||||
for g in f:
|
||||
g.decompose()
|
||||
#then proceed with the cameo operation
|
||||
for g in f:
|
||||
#catch negative sidebearings
|
||||
if g.leftMargin < 0:
|
||||
g.leftMargin = 0
|
||||
if g.rightMargin < 0:
|
||||
g.rightMargin = 0
|
||||
#scale the glyph and sidebearings
|
||||
leftMargin = int(round((g.rightMargin * scaleValue) + buffer))
|
||||
rightMargin = int(round((g.rightMargin * scaleValue) + buffer))
|
||||
g.scale((scaleValue, scaleValue), (int(round(g.width/2)), 0))
|
||||
g.leftMargin = leftMargin
|
||||
g.rightMargin = rightMargin
|
||||
#determine the left and the right of the box
|
||||
l = 0
|
||||
r = g.width
|
||||
#draw the box using flPen
|
||||
p = g.getPen()
|
||||
p.moveTo((l, b))
|
||||
p.lineTo((l, t))
|
||||
p.lineTo((r, t))
|
||||
p.lineTo((r, b))
|
||||
p.closePath()
|
||||
#correct path direction
|
||||
g.correctDirection()
|
||||
#update the glyph
|
||||
g.update()
|
||||
#update the font
|
||||
f.update()
|
||||
#tell me when it is over
|
||||
Message('The highly complex "Cameo Operation" is now complete. Please examine the results and be thankful that RoboFab is on your side.')
|
@ -1,12 +0,0 @@
|
||||
#FLM: Kerning Counter
|
||||
|
||||
"""print kerning counts for glyphs selected in the font window"""
|
||||
|
||||
from robofab.world import CurrentFont
|
||||
|
||||
font = CurrentFont()
|
||||
selectedGlyphs = font.selection
|
||||
kerning = font.kerning
|
||||
counts = kerning.occurrenceCount(selectedGlyphs)
|
||||
for glyphName in selectedGlyphs:
|
||||
print "%s: %s pairs"%(glyphName, counts[glyphName])
|
@ -1,29 +0,0 @@
|
||||
#FLM: Print Measurments
|
||||
|
||||
"""print the distance and angle between two selected points"""
|
||||
|
||||
from robofab.world import CurrentGlyph
|
||||
import math
|
||||
|
||||
glyph = CurrentGlyph()
|
||||
|
||||
selectedPoints = []
|
||||
|
||||
for contour in glyph.contours:
|
||||
if contour.selected:
|
||||
for segment in contour.segments:
|
||||
if segment.selected:
|
||||
onCurve = segment.onCurve
|
||||
point = (onCurve.x, onCurve.y)
|
||||
if point not in selectedPoints:
|
||||
selectedPoints.append(point)
|
||||
|
||||
if len(selectedPoints) == 2:
|
||||
xList = [x for x, y in selectedPoints]
|
||||
yList = [y for x, y in selectedPoints]
|
||||
xList.sort()
|
||||
yList.sort()
|
||||
xDiff = xList[1] - xList[0]
|
||||
yDiff = yList[1] - yList[0]
|
||||
ang = round(math.atan2(yDiff, xDiff)*180/math.pi, 3)
|
||||
print "x:%s y:%s a:%s"%(xDiff, yDiff, ang)
|
@ -1,14 +0,0 @@
|
||||
"""round all kerning values to increments of a specified value"""
|
||||
|
||||
value = 100
|
||||
|
||||
from robofab.world import CurrentFont
|
||||
|
||||
font = CurrentFont()
|
||||
kerning = font.kerning
|
||||
startCount = len(kerning)
|
||||
kerning.round(value)
|
||||
font.update()
|
||||
print 'finished rounding kerning by %s.'%value
|
||||
print 'you started with %s kerning pairs.'%startCount
|
||||
print 'you now have %s kerning pairs.'%len(kerning)
|
@ -1,31 +0,0 @@
|
||||
#FLM: UFO Remove Overlap
|
||||
|
||||
"""
|
||||
Remove overlap on all glyphs in a .ufo font.
|
||||
|
||||
This script sis more than a little silly, but it
|
||||
demonstrates how objectsRF and objectsFL can
|
||||
work hand in hand.
|
||||
"""
|
||||
|
||||
from robofab.objects.objectsRF import OpenFont
|
||||
from robofab.objects.objectsFL import NewFont
|
||||
from robofab.interface.all.dialogs import ProgressBar
|
||||
|
||||
ufoFont = OpenFont(note="Select a .ufo")
|
||||
if ufoFont:
|
||||
bar = ProgressBar('Removing Overlap...', len(ufoFont))
|
||||
flFont = NewFont()
|
||||
flGlyph = flFont.newGlyph('OverlapRemover')
|
||||
for ufoGlyph in ufoFont:
|
||||
flPen = flGlyph.getPointPen()
|
||||
ufoGlyph.drawPoints(flPen)
|
||||
flGlyph.removeOverlap()
|
||||
ufoPen = ufoGlyph.getPointPen()
|
||||
ufoGlyph.clear()
|
||||
flGlyph.drawPoints(ufoPen)
|
||||
flGlyph.clear()
|
||||
bar.tick()
|
||||
flFont.close(save=0)
|
||||
bar.close()
|
||||
ufoFont.save(doProgress=True)
|
@ -1,32 +0,0 @@
|
||||
#FLM: 010 FontLab to RoboFab and Back
|
||||
|
||||
# In which an adventurous glyph of your choice
|
||||
# makes a trip into RoboFab land,
|
||||
# and returns safely home after various inspections
|
||||
# and modifications.
|
||||
|
||||
from robofab.world import CurrentGlyph, CurrentFont
|
||||
|
||||
c = CurrentGlyph()
|
||||
f = CurrentFont()
|
||||
|
||||
from robofab.objects.objectsRF import RGlyph
|
||||
d = RGlyph()
|
||||
|
||||
# woa! d is now a rf version of a fl glyph!
|
||||
d.appendGlyph(c)
|
||||
d.width = 100
|
||||
|
||||
c.printDump()
|
||||
d.printDump()
|
||||
|
||||
e = f.newGlyph('copyTest')
|
||||
|
||||
# dump the rf glyph back to a fl glyph!
|
||||
e.appendGlyph(d)
|
||||
|
||||
# see, it still takes its own kind as well
|
||||
e.appendGlyph(f['a'])
|
||||
e.printDump()
|
||||
|
||||
|
@ -1,60 +0,0 @@
|
||||
#FLM: RoboFab Intro, Building Accented Glyphs
|
||||
|
||||
#
|
||||
#
|
||||
# demo building accented glyphs with robofab
|
||||
#
|
||||
#
|
||||
|
||||
# RoboFab sports very simple, yet very simple automatic
|
||||
# accented glyph compiling. The accentBuilder module has
|
||||
# several tools that handle these operations splendidly.
|
||||
# Why don't we have a look?
|
||||
|
||||
# (you will need to have a font open in FontLab)
|
||||
from robofab.world import CurrentFont
|
||||
|
||||
# accentBuilder lives in robofab.tools
|
||||
from robofab.tools.accentBuilder import AccentTools
|
||||
|
||||
font = CurrentFont()
|
||||
|
||||
# The first thing that you need is a list of accented glyphs
|
||||
# that you want to compile. This is a very short one for
|
||||
# demonstration purposes. There are some more extensive
|
||||
# lists located in robofab.gString
|
||||
myList = ['aacute', 'agrave', 'acircumflex', 'adieresis', 'atilde', 'aring']
|
||||
|
||||
# AccentTools is the class that contains the most important methods.
|
||||
# It takes a font and the list of accented glyphs as arguments.
|
||||
accentTool = AccentTools(font, myList)
|
||||
|
||||
# These accented glyphs are compiled using anchors. Anchors are
|
||||
# simple reference points in the glyph. These anchors are used
|
||||
# to align the various components when the accented glyph is compiled.
|
||||
# AccentTools looks at the glyph names in your list of accented glyphs
|
||||
# and references an internal glyph construction database to determine
|
||||
# where the anchors need to be placed. So, we need to tell AccentTools
|
||||
# to position the anchors appropriately. We have very flexible control
|
||||
# of how the anchors are positioned in relation to the glyph.
|
||||
# So, build the needed anchors!
|
||||
accentTool.buildAnchors(ucXOffset=30, ucYOffset=70, lcXOffset=20, lcYOffset=50, doProgress=True)
|
||||
|
||||
# AccentTools may encounter some problems, for example if some
|
||||
# necessary glyphs are not present, when adding the anchors.
|
||||
# We can print these errors by calling:
|
||||
accentTool.printAnchorErrors()
|
||||
|
||||
# Now that we have the anchors in place, we can compile the glyphs.
|
||||
accentTool.buildAccents(doProgress=True)
|
||||
|
||||
# It may also run into some problems, for example if necessary
|
||||
# anchors are not prest. You can print these errors out as well.
|
||||
accentTool.printAccentErrors()
|
||||
|
||||
# That's it! Now, update the font.
|
||||
font.update()
|
||||
|
||||
# See how easy that is? And, this is just the tip of the iceburg
|
||||
# with AccentTools. Read the source!
|
||||
|
@ -1,67 +0,0 @@
|
||||
#FLM: RoboFab Intro, Contours, Segments and Points
|
||||
|
||||
#
|
||||
#
|
||||
# demo of RoboFab contour, segments and points
|
||||
#
|
||||
#
|
||||
|
||||
# In RoboFab, glyphs are constructed mainly from a list
|
||||
# of contours. These contours are constructed from lists
|
||||
# of segments, which are construted from a list of points.
|
||||
# Kind of makes sense, doesn't it? This demo will briefly
|
||||
# run through printing some info about the contours in
|
||||
# a glyph and it will show how to do a few things to contours.
|
||||
# You should really check out the documentation for a complete
|
||||
# list of everything that can be done with and to contours,
|
||||
# segments and points. There are far too many things to list
|
||||
# in this intro. Ok. Here we go!
|
||||
|
||||
from robofab.world import CurrentGlyph
|
||||
|
||||
# (you will need t have a font open in FontLab for this demo)
|
||||
glyph = CurrentGlyph()
|
||||
|
||||
# Before we get into changing any of these objects,
|
||||
# let's print a little info about them.
|
||||
|
||||
print "This glyph has %s contours"%len(glyph.contours)
|
||||
# a glyph has a list of contours
|
||||
for contour in glyph.contours:
|
||||
# every contour has an index,
|
||||
print "-contour index %s"%contour.index
|
||||
# a direction,
|
||||
print "--clockwise: %s"%contour.clockwise
|
||||
# and a list of segments
|
||||
print "--%s segments"%len(contour.segments)
|
||||
for segment in contour.segments:
|
||||
# every segment has an index,
|
||||
print "---segment %s"%segment.index
|
||||
# a type,
|
||||
print "---type: %s"%segment.type
|
||||
# a list of points,
|
||||
print "---%s points"%len(segment.points)
|
||||
# which includes one on curve point,
|
||||
onCurve = segment.onCurve
|
||||
print "---onCurve point at: (%s, %s)"%(onCurve.x, onCurve.y)
|
||||
# and possibly some off curve points
|
||||
print "---%s offCurve points"%len(segment.offCurve)
|
||||
|
||||
# Now, contours, segments and points all have
|
||||
# unique methods of their own, but for now let's
|
||||
# just look at contour methods.
|
||||
|
||||
for contour in glyph.contours:
|
||||
# you can move the contour
|
||||
contour.move((314, 159))
|
||||
# you can scale the contour
|
||||
# and you can even set the center
|
||||
# of the scale
|
||||
contour.scale((2, .5), center=(100, 100))
|
||||
# you can reverse the direction
|
||||
contour.reverseContour()
|
||||
# and there are many more!
|
||||
|
||||
# as always, update the glyph
|
||||
glyph.update()
|
||||
|
@ -1,94 +0,0 @@
|
||||
#FLM: RoboFab Intro, Font and Glyph Lib
|
||||
|
||||
#
|
||||
#
|
||||
# demo of font.lib and glyph.lib
|
||||
#
|
||||
#
|
||||
|
||||
from robofab.world import CurrentFont, CurrentGlyph
|
||||
|
||||
# font.lib and glyph.lib are an idea inherited from RoboFog.
|
||||
# They are spaces to store custom data. font.lib is for
|
||||
# font-level custom data, glyph.lib is for glyph-level data.
|
||||
# They're both Python dictionaries.
|
||||
#
|
||||
# Now let's talk about your slice of the lib pie.
|
||||
# Everyone has their own little address space in the lib object.
|
||||
# This is done by advising to use a naming convention for the
|
||||
# top level keys in the lib dictionary.
|
||||
# Your address is defined by inversing your domain name.
|
||||
# For example, keys used by RoboFab start with "org.robofab",
|
||||
# LettError keys start with "com.letterror".
|
||||
# It's pretty simple, and best of all it avoids the problems
|
||||
# associated with maintaining a registry. This approach
|
||||
# to naming was pioneered by Java and has been adopted
|
||||
# by industry luminaries like Apple and RoboFab.
|
||||
# What you store in your section is completely up to you.
|
||||
# Enough of that, let's take a look at how to use the thing.
|
||||
|
||||
# Make sure you have a font open.
|
||||
font = CurrentFont()
|
||||
|
||||
# Essentially the lib is a dict object, and it has all properties
|
||||
# of a dict. So, following the naming scheme above, this is how
|
||||
# you access your section of the lib (but use your address!):
|
||||
font.lib['org.robofab'] = 'our address in the lib'
|
||||
font.lib['org.robofab.keepout'] = 'this also belongs to us'
|
||||
|
||||
# Like a dict, you can simply store a string in the dict
|
||||
font.lib['org.robofab'] = 'Hello World'
|
||||
print font.lib['org.robofab']
|
||||
|
||||
# But that is really boring! Let's store a bunch of stuff.
|
||||
# Here we can go in two directions: we can either store a
|
||||
# dict right at our address:
|
||||
font.lib['org.robofab'] = {'An Int':1, 'A Str':'Howdy!', 'A List':['X', 'Y', 'Z'], 'A Dict':{'Robo':'Fab'}}
|
||||
# Now because we have a nested dict, and we can access
|
||||
# it like any other dict let's print the stuff we just stored:
|
||||
print font.lib['org.robofab']['An Int']
|
||||
print font.lib['org.robofab']['A Str']
|
||||
print font.lib['org.robofab']['A List']
|
||||
print font.lib['org.robofab']['A Dict']
|
||||
|
||||
# ...or we can avoid deeper nesting, and use our address as a
|
||||
# key prefix:
|
||||
font.lib['org.robofab.A'] = "A"
|
||||
font.lib['org.robofab.B'] = "B"
|
||||
font.lib['org.robofab.aList'] = [1, 2, 3]
|
||||
print font.lib['org.robofab.A']
|
||||
print font.lib['org.robofab.B']
|
||||
print font.lib['org.robofab.aList']
|
||||
|
||||
|
||||
# It is all sooo easy!
|
||||
|
||||
# Every glyph has it's very own lib as well
|
||||
# and it works just like the font lib
|
||||
glyph = CurrentGlyph()
|
||||
glyph.lib['org.robofab'] = {'My glyph is totally':'Awesome'}
|
||||
print glyph.lib['org.robofab']['My glyph is totally']
|
||||
|
||||
# The type of data that can be stored in lib dictionaries is
|
||||
# limited. Dictionary keys must be strings (or unicode strings),
|
||||
# values may be dicts, lists, ints, floats, (unicode) strings and
|
||||
# booleans. There is also a special type to store arbitrary binary
|
||||
# data: robofab.plistlib.Data. Don't use plain strings for that!
|
||||
|
||||
# So, as you can see, this can be a very powerful thing
|
||||
# to use and you are only limited by your imagination.
|
||||
# Have fun!
|
||||
|
||||
# A Technical Note:
|
||||
# You "under the hood" type folks out there may be
|
||||
# wondering where this data is being stored in the
|
||||
# FontLab file. Well, in all environments, it's stored
|
||||
# as a Property List* (.plist), which is a simple XML
|
||||
# format defined by Apple.
|
||||
# In FontLab, libs are stored in the font.customdata
|
||||
# and glyph.customdata fields and it is parsed whenever
|
||||
# the lib is requested. So, don't try to put anything
|
||||
# else in those fields, otherwise the lib won't work.
|
||||
# When using UFO/GLIF, font.lib is stored in a file called
|
||||
# lib.plist in the .ufo folder, glyph.lib is stored as
|
||||
# part of the .glif file.
|
@ -1,43 +0,0 @@
|
||||
#FLM: RoboFab Intro, The Font Object
|
||||
|
||||
#
|
||||
#
|
||||
# demo of the RoboFab font object
|
||||
#
|
||||
#
|
||||
|
||||
import robofab
|
||||
|
||||
# Let's talk to some of the font objects.
|
||||
# CurrentFont and CurrentGlyph are similar to
|
||||
# the RoboFog functions. They return a font
|
||||
# or Glyph object respectively. It will be the
|
||||
# front most font or the front most glyph.
|
||||
from robofab.world import CurrentFont, CurrentGlyph
|
||||
|
||||
# This is a brief intro into Robofabs all singing and
|
||||
# dancing dialog class. It will produce simple
|
||||
# dialogs in almost any environment, FontLab, Python IDE, W.
|
||||
from robofab.interface.all.dialogs import Message
|
||||
|
||||
# (make sure you have a font opened in FontLab)
|
||||
|
||||
f = CurrentFont()
|
||||
# so now f is the name of a font object for the current font.
|
||||
|
||||
if f == None:
|
||||
# let's see what dialog can do, a warning
|
||||
Message("You should open a font first, there's nothing to look at now!")
|
||||
else:
|
||||
# and another dialog.
|
||||
Message("The current font is %s"%(f.info.postscriptFullName))
|
||||
|
||||
# let's have a look at some of the attributes a RoboFab Font object has
|
||||
print "the number of glyphs:", len(f)
|
||||
|
||||
# some of the attributes map straight to the FontLab Font class
|
||||
# We just straightened the camelCase here and there
|
||||
print "full name of this font:", f.info.postscriptFullName
|
||||
print "list of glyph names:", f.keys()
|
||||
print 'ascender:', f.info.ascender
|
||||
print 'descender:', f.info.descender
|
@ -1,75 +0,0 @@
|
||||
#FLM: RoboFab Intro, FoundrySettings.plist
|
||||
|
||||
#
|
||||
#
|
||||
# demo FoundrySettings.plist
|
||||
#
|
||||
#
|
||||
|
||||
# Setting all the data strings in the FontLab font header can be a repetitive and
|
||||
# tedious exercise. RoboFab to the rescue! RoboFab features some nifty tools
|
||||
# that help automate this process. These tools read a .plist file that you are free
|
||||
# to edit to include your own standard settings. Currently, the .plist file contains
|
||||
# these bits of data. We reserve the right to add more in the future.
|
||||
# -copyright
|
||||
# -trademark
|
||||
# -license
|
||||
# -licenseurl
|
||||
# -notice
|
||||
# -ttvendor
|
||||
# -vendorurl
|
||||
# -designer
|
||||
# -designerurl
|
||||
#
|
||||
# The foundry settings tools parse this .plist file into a python dictionary that
|
||||
# can be used to apply the data to fonts. It's really easy to use. Let's check it out!
|
||||
|
||||
|
||||
from robofab.world import CurrentFont
|
||||
from robofab.tools.toolsFL import makeDataFolder
|
||||
# all the foundry settings tools live here:
|
||||
from robofab.tools.toolsAll import readFoundrySettings, getFoundrySetting, setFoundrySetting
|
||||
import time
|
||||
import os
|
||||
|
||||
# You will need a font open in fontlab for this demo
|
||||
font = CurrentFont()
|
||||
|
||||
# We need to know where the .plist file lives. In the FontLab environment
|
||||
# it can live in the "RoboFab Data" folder with its friends. makeDataFolder()
|
||||
# will make the data folder if it doesn't exist and it will return the path
|
||||
settingsPath = os.path.join(makeDataFolder(), 'FoundrySettings.plist')
|
||||
|
||||
# Now, let's load those settings up
|
||||
# readFoundrySettings(path) will return the data from the .plist as dictionary
|
||||
mySettings = readFoundrySettings(settingsPath)
|
||||
|
||||
# Let's get the current year so that the year string is always up to date
|
||||
font.info.year = time.gmtime(time.time())[0]
|
||||
|
||||
# Apply those settings that we just loaded
|
||||
font.info.copyright = mySettings['copyright']
|
||||
font.info.trademark = mySettings['trademark']
|
||||
font.info.openTypeNameLicense = mySettings['license']
|
||||
font.info.openTypeNameLicenseURL = mySettings['licenseurl']
|
||||
font.info.openTypeNameDescription = mySettings['notice']
|
||||
font.info.openTypeOS2VendorID = mySettings['ttvendor']
|
||||
font.info.openTypeNameManufacturerURL = mySettings['vendorurl']
|
||||
font.info.openTypeNameDesigner = mySettings['designer']
|
||||
font.info.openTypeNameDesignerURL = mySettings['designerurl']
|
||||
|
||||
# and call the update method
|
||||
font.update()
|
||||
|
||||
# But, Prof. RoboFab, what if I want to change the settings in the .plist file?
|
||||
# Good question. You can always edit the .plist data by hand, or you can
|
||||
# do it via a script. It would go a little something like this:
|
||||
setFoundrySetting('trademark', 'This font is a trademark of Me, Myself and I', settingsPath)
|
||||
# If you are on OSX, and you have the Apple developers tools installed, you
|
||||
# can also edit it with /Developer/Applications/Property List Editor.
|
||||
|
||||
# And to read only only one setting from the file you can use this handly little method.
|
||||
font.info.trademark = getFoundrySetting('trademark', settingsPath)
|
||||
font.update()
|
||||
|
||||
# It's that easy!
|
@ -1,55 +0,0 @@
|
||||
#FLM: RoboFab Intro, Generating Fonts
|
||||
|
||||
#
|
||||
#
|
||||
# demo generating fonts with robofab
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
# Generating fonts with RoboFab is super easy! Let's have a look.
|
||||
# (you will need to have a font open in FontLab)
|
||||
|
||||
from robofab.world import CurrentFont
|
||||
import os
|
||||
|
||||
# A little function for making folders. we'll need it later.
|
||||
def makeFolder(path):
|
||||
#if the path doesn't exist, make it!
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
# We need to have a font open for this demo to work
|
||||
font = CurrentFont()
|
||||
# This will tell us what folder the font is in
|
||||
fontPath = os.path.dirname(font.path)
|
||||
|
||||
# We'll put the fonts into a folder called "FabFonts" next the .vfb file
|
||||
macPath = os.path.join(fontPath, 'FabFonts', 'ForMac')
|
||||
pcPath = os.path.join(fontPath, 'FabFonts', 'ForPC')
|
||||
bothPath = os.path.join(fontPath, 'FabFonts', 'ForBoth')
|
||||
|
||||
# Now, we'll use that little function we made earlier to make the folders
|
||||
makeFolder(macPath)
|
||||
makeFolder(pcPath)
|
||||
makeFolder(bothPath)
|
||||
|
||||
# A dict of all the font types we want to output
|
||||
fontTypes = { 'mac' : ['mactype1', 'macttf', 'macttdfont'],
|
||||
'pc' : ['pctype1', 'pcmm'],
|
||||
'both' : ['otfcff', 'otfttf']
|
||||
}
|
||||
|
||||
# Finally, let's generate the fonts!
|
||||
for macType in fontTypes['mac']:
|
||||
print "generating %s..."%macType
|
||||
font.generate(macType, macPath)
|
||||
for pcType in fontTypes['pc']:
|
||||
print "generating %s..."%pcType
|
||||
font.generate(pcType, pcPath)
|
||||
for bothType in fontTypes['both']:
|
||||
print "generating %s..."%bothType
|
||||
font.generate(bothType, bothPath)
|
||||
print 'Done!'
|
||||
|
||||
# Wow! Could it be any easier than that?
|
@ -1,34 +0,0 @@
|
||||
#FLM: RoboFab Intro, The Glyph Object
|
||||
|
||||
#
|
||||
#
|
||||
# demo of the RoboFab glyph object
|
||||
#
|
||||
#
|
||||
|
||||
import robofab
|
||||
from robofab.world import CurrentFont, CurrentGlyph
|
||||
from robofab.interface.all.dialogs import Message
|
||||
|
||||
# (make sure you have a font opened in FontLab)
|
||||
|
||||
|
||||
|
||||
# this code starts out the same as intro_FontObject
|
||||
f = CurrentFont()
|
||||
if f == None:
|
||||
Message("You should open a font first, there's nothing to look at now!")
|
||||
else:
|
||||
for g in f:
|
||||
print "glyphname:", g.name, ", glyph width:", g.width
|
||||
# so now g is a RoboFab Glyph object
|
||||
print "this glyph has %d contours" % len(g.contours)
|
||||
print "this glyph has %d components" % len(g.components)
|
||||
print "this glyph has %d anchors" % len(g.anchors)
|
||||
print
|
||||
|
||||
# easy huh?
|
||||
# There are many more attributes and methods.
|
||||
# Most of these can be used to edit the font data.
|
||||
# Which makes them not suited for a simple intro as this
|
||||
# because we don't want to mess up your font.
|
@ -1,24 +0,0 @@
|
||||
#FLM: RoboFab Intro, Font and Glyph Lib
|
||||
|
||||
#
|
||||
#
|
||||
# demo of glyph properties
|
||||
#
|
||||
#
|
||||
|
||||
from robofab.world import OpenFont
|
||||
|
||||
# This is a specific test of some features which are probably
|
||||
# still only supported in one glyph in the DemoFont.ufo,
|
||||
# which you can find the robofab/Data/ folder. Here is goes.
|
||||
|
||||
f = OpenFont(None, "")
|
||||
|
||||
for c in f:
|
||||
if not c.properties.isEmpty():
|
||||
print c.properties.dump()
|
||||
|
||||
# This prints the available GlyphProperties objects.
|
||||
# Not very impressive at the moment, but it means
|
||||
# that at least they're getting stored with the glyphs
|
||||
# and that they can be read and interpreted.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user