diff options
| -rw-r--r-- | .gitignore | 21 | ||||
| -rw-r--r-- | COPYING | 350 | ||||
| -rw-r--r-- | Makefile | 244 | ||||
| -rw-r--r-- | NEWS | 29 | ||||
| -rw-r--r-- | README | 1 | ||||
| -rw-r--r-- | doc/Makefile | 58 | ||||
| -rw-r--r-- | doc/filegraph.dot (renamed from filegraph.dot) | 12 | ||||
| -rw-r--r-- | doc/luaotfload-context.tex | 485 | ||||
| -rw-r--r-- | doc/luaotfload-latex.tex | 448 | ||||
| -rw-r--r-- | doc/luaotfload-main.tex | 1591 | ||||
| -rw-r--r-- | doc/luaotfload-tool.rst (renamed from luaotfload-tool.rst) | 105 | ||||
| -rw-r--r-- | doc/luaotfload.conf.example | 30 | ||||
| -rw-r--r-- | doc/luaotfload.conf.rst | 347 | ||||
| -rw-r--r-- | luaotfload-blacklist.cnf | 12 | ||||
| -rw-r--r-- | luaotfload-legacy-attributes.lua | 27 | ||||
| -rw-r--r-- | luaotfload-legacy-database.lua | 724 | ||||
| -rw-r--r-- | luaotfload-legacy-merged.lua | 8157 | ||||
| -rwxr-xr-x | luaotfload-legacy-tool.lua | 105 | ||||
| -rw-r--r-- | luaotfload-legacy.lua | 402 | ||||
| -rw-r--r-- | luaotfload.dtx | 2685 | ||||
| -rw-r--r-- | misc/luaotfload-blacklist.cnf | 4 | ||||
| -rw-r--r-- | misc/valgrind-kpse-suppression.sup | 140 | ||||
| -rwxr-xr-x | scripts/mkcharacters (renamed from mkcharacters) | 6 | ||||
| -rwxr-xr-x | scripts/mkglyphlist (renamed from mkglyphlist) | 13 | ||||
| -rwxr-xr-x | scripts/mkstatus (renamed from mkstatus) | 84 | ||||
| -rwxr-xr-x | scripts/mktests (renamed from mktests) | 88 | ||||
| -rw-r--r-- | src/luaotfload-auxiliary.lua (renamed from luaotfload-auxiliary.lua) | 231 | ||||
| -rw-r--r-- | src/luaotfload-basics-gen.lua (renamed from luaotfload-basics-gen.lua) | 25 | ||||
| -rw-r--r-- | src/luaotfload-basics-nod.lua (renamed from luaotfload-basics-nod.lua) | 15 | ||||
| -rw-r--r-- | src/luaotfload-colors.lua (renamed from luaotfload-colors.lua) | 39 | ||||
| -rw-r--r-- | src/luaotfload-configuration.lua | 704 | ||||
| -rw-r--r-- | src/luaotfload-database.lua (renamed from luaotfload-database.lua) | 1412 | ||||
| -rw-r--r-- | src/luaotfload-diagnostics.lua (renamed from luaotfload-diagnostics.lua) | 49 | ||||
| -rw-r--r-- | src/luaotfload-features.lua (renamed from luaotfload-features.lua) | 389 | ||||
| -rw-r--r-- | src/luaotfload-fontloader.lua (renamed from luaotfload-fontloader.lua) | 1522 | ||||
| -rw-r--r-- | src/luaotfload-fonts-cbk.lua (renamed from luaotfload-fonts-cbk.lua) | 0 | ||||
| -rw-r--r-- | src/luaotfload-fonts-def.lua (renamed from luaotfload-fonts-def.lua) | 0 | ||||
| -rw-r--r-- | src/luaotfload-fonts-enc.lua (renamed from luaotfload-fonts-enc.lua) | 0 | ||||
| -rw-r--r-- | src/luaotfload-fonts-ext.lua (renamed from luaotfload-fonts-ext.lua) | 0 | ||||
| -rw-r--r-- | src/luaotfload-fonts-inj.lua | 526 | ||||
| -rw-r--r-- | src/luaotfload-fonts-lua.lua (renamed from luaotfload-fonts-lua.lua) | 0 | ||||
| -rw-r--r-- | src/luaotfload-fonts-otn.lua | 2848 | ||||
| -rw-r--r-- | src/luaotfload-fonts-tfm.lua (renamed from luaotfload-fonts-tfm.lua) | 0 | ||||
| -rw-r--r-- | src/luaotfload-letterspace.lua (renamed from luaotfload-letterspace.lua) | 15 | ||||
| -rw-r--r-- | src/luaotfload-loaders.lua (renamed from luaotfload-loaders.lua) | 4 | ||||
| -rw-r--r-- | src/luaotfload-log.lua (renamed from luaotfload-override.lua) | 65 | ||||
| -rw-r--r-- | src/luaotfload-main.lua | 708 | ||||
| -rw-r--r-- | src/luaotfload-override.lua | 52 | ||||
| -rw-r--r-- | src/luaotfload-parsers.lua | 701 | ||||
| -rwxr-xr-x | src/luaotfload-tool.lua (renamed from luaotfload-tool.lua) | 699 | ||||
| -rw-r--r-- | src/luaotfload.sty | 45 | ||||
| -rw-r--r-- | tests/alternate_sub.tex | 15 | ||||
| -rw-r--r-- | tests/anum.tex | 17 | ||||
| -rw-r--r-- | tests/caseinsensitive.tex | 6 | ||||
| -rw-r--r-- | tests/color.tex | 10 | ||||
| -rw-r--r-- | tests/fallback.tex | 6 | ||||
| -rw-r--r-- | tests/featurefiles.tex | 5 | ||||
| -rw-r--r-- | tests/font_patch.tex | 28 | ||||
| -rw-r--r-- | tests/fontconfig_conf_reading.tex | 8 | ||||
| -rw-r--r-- | tests/fontencoding.tex | 4 | ||||
| -rw-r--r-- | tests/fonts.conf.test | 25 | ||||
| -rw-r--r-- | tests/fontspec_lookup.ltx | 41 | ||||
| -rw-r--r-- | tests/frac.tex | 4 | ||||
| -rw-r--r-- | tests/fullname.tex | 15 | ||||
| -rw-r--r-- | tests/itlc.tex | 5 | ||||
| -rw-r--r-- | tests/lookups.tex | 20 | ||||
| -rw-r--r-- | tests/marks.tex | 10 | ||||
| -rw-r--r-- | tests/math.tex | 56 | ||||
| -rw-r--r-- | tests/microtypography.tex | 36 | ||||
| -rw-r--r-- | tests/opbd.fea | 187 | ||||
| -rw-r--r-- | tests/opbd.tex | 35 | ||||
| -rw-r--r-- | tests/opticalsize.tex | 13 | ||||
| -rw-r--r-- | tests/pln-aux-1.tex | 55 | ||||
| -rw-r--r-- | tests/pln-aux-2.tex | 102 | ||||
| -rw-r--r-- | tests/pln-aux-3.tex | 39 | ||||
| -rw-r--r-- | tests/pln-aux-4.tex | 41 | ||||
| -rw-r--r-- | tests/pln-request-4-slashed.tex | 12 | ||||
| -rw-r--r-- | tests/pln-request-5-cached.tex | 18 | ||||
| -rw-r--r-- | tests/pln-subfont-1.tex | 12 | ||||
| -rw-r--r-- | tests/pln-tfm.tex | 10 | ||||
| -rw-r--r-- | tests/sanitize_color.tex | 8 | ||||
| -rw-r--r-- | tests/systemfonts.tex | 50 | ||||
| -rw-r--r-- | tests/texligatures.tex | 7 | ||||
| -rw-r--r-- | tests/tfmofm.ltx | 6 | ||||
| -rw-r--r-- | tests/tkrn.fea | 8 | ||||
| -rw-r--r-- | tests/weirdfonts.tex | 15 | ||||
| -rw-r--r-- | tests/zero_width_marks_lig.tex | 16 | 
87 files changed, 11814 insertions, 15348 deletions
@@ -18,9 +18,7 @@ luaotfload-tool.1  luaotfload/*  # Files generated by 'make world' and removed by 'make mrproper' -luaotfload.lua  luaotfload.pdf -luaotfload.sty  luaotfload.tds.zip  luaotfload.zip @@ -41,3 +39,22 @@ tests/*.ovf  tests/*.sty  tests/luaotfload* +# temporary directories +build/* +testing/* +tests/* +tmp/* + +# tex side-effects +doc/*.aux +doc/*.log +doc/*.out +doc/*.pdf +doc/*.tmp +doc/*.toc +doc/*.tuc +doc/*.vimout + +# valgrind log +src/*.log + @@ -0,0 +1,350 @@ +Context, where the fontloader Luaotfload is built around originates, is +licensed under the GPL version 2.0 (exactly). As a derived work, anything +Luaotflaod adds to that is also subject to the same license at the same +version. The “any later version” clause as used by the FSF in the license text +*does not apply* to either Context or Luaotfload, despite being kept around in +the license text given below. + +------------------------------------------------------------------------------- +                               LICENSE TEXT BELOW +------------------------------------------------------------------------------- + +                    GNU GENERAL PUBLIC LICENSE +                       Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +                            Preamble + +  The licenses for most software are designed to take away your +freedom to share and change it.  By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users.  This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it.  (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.)  You can apply it to +your programs, too. + +  When we speak of free software, we are referring to freedom, not +price.  Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + +  To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + +  For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have.  You must make sure that they, too, receive or can get the +source code.  And you must show them these terms so they know their +rights. + +  We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +  Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software.  If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + +  Finally, any free program is threatened constantly by software +patents.  We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary.  To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + +  The precise terms and conditions for copying, distribution and +modification follow. + +                    GNU GENERAL PUBLIC LICENSE +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +  0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License.  The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language.  (Hereinafter, translation is included without limitation in +the term "modification".)  Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope.  The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + +  1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +  2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +    a) You must cause the modified files to carry prominent notices +    stating that you changed the files and the date of any change. + +    b) You must cause any work that you distribute or publish, that in +    whole or in part contains or is derived from the Program or any +    part thereof, to be licensed as a whole at no charge to all third +    parties under the terms of this License. + +    c) If the modified program normally reads commands interactively +    when run, you must cause it, when started running for such +    interactive use in the most ordinary way, to print or display an +    announcement including an appropriate copyright notice and a +    notice that there is no warranty (or else, saying that you provide +    a warranty) and that users may redistribute the program under +    these conditions, and telling the user how to view a copy of this +    License.  (Exception: if the Program itself is interactive but +    does not normally print such an announcement, your work based on +    the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole.  If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works.  But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +  3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + +    a) Accompany it with the complete corresponding machine-readable +    source code, which must be distributed under the terms of Sections +    1 and 2 above on a medium customarily used for software interchange; or, + +    b) Accompany it with a written offer, valid for at least three +    years, to give any third party, for a charge no more than your +    cost of physically performing source distribution, a complete +    machine-readable copy of the corresponding source code, to be +    distributed under the terms of Sections 1 and 2 above on a medium +    customarily used for software interchange; or, + +    c) Accompany it with the information you received as to the offer +    to distribute corresponding source code.  (This alternative is +    allowed only for noncommercial distribution and only if you +    received the program in object code or executable form with such +    an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it.  For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable.  However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +  4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License.  Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + +  5. You are not required to accept this License, since you have not +signed it.  However, nothing else grants you permission to modify or +distribute the Program or its derivative works.  These actions are +prohibited by law if you do not accept this License.  Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +  6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions.  You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +  7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License.  If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all.  For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices.  Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +  8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded.  In such case, this License incorporates +the limitation as if written in the body of this License. + +  9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time.  Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number.  If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation.  If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + +  10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission.  For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this.  Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + +                            NO WARRANTY + +  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + +  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +                     END OF TERMS AND CONDITIONS + +            How to Apply These Terms to Your New Programs + +  If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +  To do so, attach the following notices to the program.  It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + +    <one line to give the program's name and a brief idea of what it does.> +    Copyright (C) <year>  <name of author> + +    This program is free software; you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation; either version 2 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License along +    with this program; if not, write to the Free Software Foundation, Inc., +    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + +    Gnomovision version 69, Copyright (C) year name of author +    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. +    This is free software, and you are welcome to redistribute it +    under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License.  Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary.  Here is a sample; alter the names: + +  Yoyodyne, Inc., hereby disclaims all copyright interest in the program +  `Gnomovision' (which makes passes at compilers) written by James Hacker. + +  <signature of Ty Coon>, 1 April 1989 +  Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs.  If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library.  If this is what you want to do, use the GNU Lesser General +Public License instead of this License. @@ -1,89 +1,94 @@  # Makefile for luaotfload -NAME         = luaotfload -DOC          = $(NAME).pdf -DTX          = $(NAME).dtx -OTFL         = $(wildcard luaotfload-*.lua) luaotfload-blacklist.cnf +NAME		= luaotfload -GLYPHSCRIPT  = mkglyphlist -GLYPHSOURCE  = glyphlist.txt -CHARSCRIPT   = mkcharacters -STATUSSCRIPT = mkstatus +DOCSRCDIR	= ./doc +SCRIPTSRCDIR	= ./scripts +SRCSRCDIR	= ./src +BUILDDIR	= ./build +MISCDIR		= ./misc + +SRC		= $(wildcard $(SRCSRCDIR)/luaotfload-*.lua) +SRC		+= $(SRCSRCDIR)/luaotfload.sty +SRC		+= $(MISCDIR)/luaotfload-blacklist.cnf + +VGND		= $(MISCDIR)/valgrind-kpse-suppression.sup + +GLYPHSCRIPT	= $(SCRIPTSRCDIR)/mkglyphlist +CHARSCRIPT	= $(SCRIPTSRCDIR)/mkcharacters +STATUSSCRIPT	= $(SCRIPTSRCDIR)/mkstatus + +GLYPHSOURCE	= $(BUILDDIR)/glyphlist.txt  RESOURCESCRIPTS = $(GLYPHSCRIPT) $(CHARSCRIPT) $(STATUSSCRIPT) -SCRIPTNAME   = luaotfload-tool -SCRIPT       = $(SCRIPTNAME).lua -MANSOURCE	 = $(SCRIPTNAME).rst -MANPAGE   	 = $(SCRIPTNAME).1 -OLDSCRIPT    = luaotfload-legacy-tool.lua +TOOLNAME	= luaotfload-tool +TOOL		= $(SRCSRCDIR)/$(TOOLNAME).lua + +CONFNAME	= luaotfload.conf -GRAPH  		 = filegraph -DOTPDF 		 = $(GRAPH).pdf -DOT    		 = $(GRAPH).dot +GRAPH		= filegraph +DOCSRC		= $(addprefix $(DOCSRCDIR)/$(NAME), -main.tex -latex.tex) +GRAPHSRC	= $(DOCSRCDIR)/$(GRAPH).dot +MANSRC		= $(DOCSRCDIR)/$(TOOLNAME).rst $(DOCSRCDIR)/$(CONFNAME).rst + +DOCPDF		= $(DOCSRCDIR)/$(NAME).pdf +DOTPDF		= $(DOCSRCDIR)/$(GRAPH).pdf +TOOLMAN 	= $(DOCSRCDIR)/$(TOOLNAME).1 +CONFMAN		= $(DOCSRCDIR)/$(CONFNAME).5 +MANPAGES	= $(TOOLMAN) $(CONFMAN) + +DOCS		= $(DOCPDF) $(DOTPDF) $(MANPAGES)  # Files grouped by generation mode -GLYPHS      = luaotfload-glyphlist.lua -CHARS       = luaotfload-characters.lua -STATUS      = luaotfload-status.lua +GLYPHS		= $(BUILDDIR)/$(NAME)-glyphlist.lua +CHARS		= $(BUILDDIR)/$(NAME)-characters.lua +STATUS		= $(BUILDDIR)/$(NAME)-status.lua  RESOURCES	= $(GLYPHS) $(CHARS) $(STATUS) -GRAPHED     = $(DOTPDF) -MAN			= $(MANPAGE) -COMPILED    = $(DOC) -UNPACKED    = luaotfload.sty luaotfload.lua -GENERATED   = $(GRAPHED) $(UNPACKED) $(COMPILED) $(RESOURCES) $(MAN) -SOURCE 		= $(DTX) $(MANSOURCE) $(OTFL) README Makefile NEWS $(RESOURCESCRIPTS) - -# test files -TESTDIR 		= tests -TESTSTATUS 		= $(wildcard $(TESTDIR)/*.tex $(TESTDIR)/*.ltx) -TESTSTATUS_SYS 	= $(TESTDIR)/systemfonts.tex $(TESTDIR)/fontconfig_conf_reading.tex -TESTSTATUS_TL 	= $(filter-out $(TESTSTATUS_SYS), $(TESTSTATUS)) +SOURCE		= $(DOCSRC) $(MANSRC) $(SRC) README COPYING Makefile NEWS $(RESOURCESCRIPTS)  # Files grouped by installation location -SCRIPTSTATUS = $(SCRIPT) $(OLDSCRIPT) $(RESOURCESCRIPTS) -RUNSTATUS    = $(UNPACKED) $(filter-out $(SCRIPTSTATUS),$(OTFL)) -DOCSTATUS    = $(DOC) $(DOTPDF) README NEWS -MANSTATUS	= $(MANPAGE) -SRCSTATUS    = $(DTX) Makefile +SCRIPTSTATUS	= $(TOOL) $(RESOURCESCRIPTS) +RUNSTATUS	= $(filter-out $(SCRIPTSTATUS),$(SRC)) +DOCSTATUS	= $(DOCPDF) $(DOTPDF) README NEWS COPYING +SRCSTATUS	= $(DOCSRC) $(MANSRC) $(GRAPHSRC) Makefile  # The following definitions should be equivalent  # ALL_STATUS = $(RUNSTATUS) $(DOCSTATUS) $(SRCSTATUS) -ALL_STATUS = $(GENERATED) $(SOURCE) +ALL_STATUS 	= $(RESOURCES) $(SOURCE)  # Installation locations -FORMAT = luatex -SCRIPTDIR = $(TEXMFROOT)/scripts/$(NAME) -RUNDIR    = $(TEXMFROOT)/tex/$(FORMAT)/$(NAME) -DOCDIR    = $(TEXMFROOT)/doc/$(FORMAT)/$(NAME) -MANDIR    = $(TEXMFROOT)/doc/man/man1/ -SRCDIR    = $(TEXMFROOT)/source/$(FORMAT)/$(NAME) -TEXMFROOT = $(shell kpsewhich --var-value TEXMFHOME) +FORMAT 		= luatex +SCRIPTDIR	= $(TEXMFROOT)/scripts/$(NAME) +RUNDIR		= $(TEXMFROOT)/tex/$(FORMAT)/$(NAME) +DOCDIR		= $(TEXMFROOT)/doc/$(FORMAT)/$(NAME) +MAN1DIR		= $(TEXMFROOT)/doc/man/man1/ +MAN5DIR		= $(TEXMFROOT)/doc/man/man5/ +SRCDIR		= $(TEXMFROOT)/source/$(FORMAT)/$(NAME) +TEXMFROOT	= $(shell kpsewhich --var-value TEXMFHOME)  # CTAN-friendly subdirectory for packaging -DISTDIR	  = ./luaotfload +DISTDIR		= $(BUILDDIR)/$(NAME) + +CTAN_ZIPFILE	= $(NAME).zip +TDS_ZIPFILE	= $(NAME).tds.zip +CTAN_ZIP	= $(BUILDDIR)/$(CTAN_ZIPFILE) +TDS_ZIP		= $(BUILDDIR)/$(TDS_ZIPFILE) +ZIPS		= $(CTAN_ZIP) $(TDS_ZIP) -CTAN_ZIP = $(NAME).zip -TDS_ZIP  = $(NAME).tds.zip -ZIPS 	 = $(CTAN_ZIP) $(TDS_ZIP) +LUA		= texlua -LUA	= texlua +## For now the $(BUILDDIR) is hardcoded in the scripts +## but we might just as well pass it to them by as environment +## variables. +DO_GLYPHS	= $(LUA) $(GLYPHSCRIPT) > /dev/null +DO_CHARS	= $(LUA) $(CHARSCRIPT)  > /dev/null +DO_STATUS	= $(LUA) $(STATUSSCRIPT)  > /dev/null -DO_TEX 		  	= luatex --interaction=batchmode $< >/dev/null -DO_LATEXMK	 	= latexmk -e '$$max_repeat = 5' -pdf -lualatex -silent $< >/dev/null -# latexmk does only one run on my machine, so we’re not going to rely on it -DO_LATEX  	 	= lualatex -interaction=batchmode $< >/dev/null -DO_GRAPHVIZ 	= dot -Tpdf -o $@ $< > /dev/null -DO_GLYPHS 		= $(LUA) $(GLYPHSCRIPT) > /dev/null -DO_CHARS 		= $(LUA) $(CHARSCRIPT)  > /dev/null -DO_STATUS 		= $(LUA) $(STATUSSCRIPT)  > /dev/null -DO_DOCUTILS 	= rst2man $< >$@ 2>/dev/null +show: showtargets  all: $(GENERATED) -graph: $(GRAPHED) -doc: $(GRAPHED) $(COMPILED) $(MAN) -manual: $(MAN) -unpack: $(UNPACKED) +builddir: $(BUILDDIR)  resources: $(RESOURCES)  chars: $(CHARS)  status: $(STATUS) @@ -91,87 +96,110 @@ ctan: $(CTAN_ZIP)  tds: $(TDS_ZIP)  world: all ctan -$(GLYPHS): /dev/null -	$(DO_GLYPHS) +graph: $(DOTPDF) +doc: $(DOCS) +pdf: $(DOCPDF) +manual: $(MANPAGES) -$(CHARS): /dev/null -	$(DO_CHARS) +$(DOTPDF): +	@$(MAKE) -C $(DOCSRCDIR) graph -$(STATUS): /dev/null -	$(DO_STATUS) +$(DOCPDF): +	@$(MAKE) -C $(DOCSRCDIR) doc -$(GRAPHED): $(DOT) -	$(DO_GRAPHVIZ) +$(MANPAGES): +	@$(MAKE) -C $(DOCSRCDIR) manuals -$(COMPILED): $(DTX) -	$(DO_LATEX) -	$(DO_LATEX) +$(GLYPHS): builddir +	$(DO_GLYPHS) -$(UNPACKED): $(DTX) -	$(DO_TEX) +$(CHARS): builddir +	$(DO_CHARS) + +$(STATUS): builddir +	$(DO_STATUS) -$(MAN): $(MANSOURCE) -	$(DO_DOCUTILS) +$(BUILDDIR): /dev/null +	mkdir -p $(BUILDDIR)  define make-ctandir  @$(RM) -rf $(DISTDIR) -@mkdir -p $(DISTDIR) && cp $(SOURCE) $(COMPILED) $(DISTDIR) +@mkdir -p $(DISTDIR) && cp $(VGND) $(SOURCE) $(COMPILED) $(DISTDIR)  endef -$(CTAN_ZIP): $(SOURCE) $(COMPILED) $(TDS_ZIP) +$(CTAN_ZIP): $(DOCS) $(SOURCE) $(COMPILED) $(TDS_ZIP)  	@echo "Making $@ for CTAN upload."  	@$(RM) -- $@  	$(make-ctandir) -	@zip -r -9 $@ $(TDS_ZIP) $(DISTDIR) >/dev/null +	cd $(BUILDDIR) && zip -r -9 $(CTAN_ZIPFILE) $(TDS_ZIPFILE) $(NAME) >/dev/null + +define run-install-doc +@mkdir -p $(DOCDIR) && cp -- $(DOCSTATUS) $(VGND) $(DOCDIR) +@mkdir -p $(SRCDIR) && cp -- $(SRCSTATUS) $(SRCDIR) +@mkdir -p $(MAN1DIR) && cp -- $(TOOLMAN) $(MAN1DIR) +@mkdir -p $(MAN5DIR) && cp -- $(CONFMAN) $(MAN5DIR) +endef  define run-install -@mkdir -p $(SCRIPTDIR) && cp $(SCRIPTSTATUS) $(SCRIPTDIR) -@mkdir -p $(RUNDIR) && cp $(RUNSTATUS) $(RUNDIR) -@mkdir -p $(DOCDIR) && cp $(DOCSTATUS) $(DOCDIR) -@mkdir -p $(SRCDIR) && cp $(SRCSTATUS) $(SRCDIR) -@mkdir -p $(MANDIR) && cp $(MANSTATUS) $(MANDIR) +@mkdir -p $(SCRIPTDIR) && cp -- $(SCRIPTSTATUS) $(SCRIPTDIR) +@mkdir -p $(RUNDIR)    && cp -- $(RESOURCES) $(RUNSTATUS) $(RUNDIR)  endef  $(TDS_ZIP): TEXMFROOT=./tmp-texmf -$(TDS_ZIP): $(ALL_STATUS) +$(TDS_ZIP): $(DOCS) $(ALL_STATUS)  	@echo "Making TDS-ready archive $@."  	@$(RM) -- $@ +	$(run-install-doc)  	$(run-install)  	@cd $(TEXMFROOT) && zip -9 ../$@ -r . >/dev/null  	@$(RM) -r -- $(TEXMFROOT) -.PHONY: install manifest clean mrproper +.PHONY: install manifest clean mrproper show showtargets  install: $(ALL_STATUS)  	@echo "Installing in '$(TEXMFROOT)'." +	$(run-install-docs)  	$(run-install) -check: $(RUNSTATUS) $(TESTSTATUS_TL) -	@rm -rf var -	@for f in $(TESTSTATUS_TL); do \ -	    echo "check: luatex $$f"; \ -	    luatex --interaction=batchmode $$f \ -	    > /dev/null || exit $$?; \ -	    done - -check-all: $(TESTSTATUS_SYS) check -	@cd $(TESTDIR); for f in $(TESTSTATUS_SYS); do \ -	    echo "check: luatex $$f"; \ -	    $(TESTENV) luatex --interaction=batchmode ../$$f \ -	    > /dev/null || exit $$?; \ -	    done - -manifest:  +manifest:  	@echo "Source files:"  	@for f in $(SOURCE); do echo $$f; done  	@echo ""  	@echo "Derived files:"  	@for f in $(GENERATED); do echo $$f; done -clean:  -	@$(RM) -- *.log *.aux *.toc *.idx *.ind *.ilg *.out $(TESTDIR)/*.log +CLEANEXTS	= log aux toc idx ind ilg out +CLEANME		= $(foreach ext,$(CLEANEXTS),$(wildcard *.$(ext))) +CLEANME		+= $(foreach ext,$(CLEANEXTS),$(wildcard $(BUILDDIR)/*$(ext))) -mrproper: clean -	@$(RM) -- $(GENERATED) $(ZIPS) $(GLYPHSOURCE) $(TESTDIR)/*.pdf -	@$(RM) -r -- $(DISTDIR) +clean: +	$(MAKE) -C $(DOCSRCDIR) $@ +	@$(RM) -- $(CLEANME) +mrproper: clean +	$(MAKE) -C $(DOCSRCDIR) $@ +	@$(RM) -- $(GENERATED) $(ZIPS) $(GLYPHSOURCE) +	@$(RM) -r -- $(BUILDDIR) + +############################################################################### +showtargets: +	@echo "Available targets:" +	@echo +	@echo "       all         build everything: documentation, resources," +	@echo "       world       build everything and package zipballs" +	@echo "       doc         compile PDF documentation" +	@echo "       resources   generate resource files (chars, glyphs)" +	@echo +	@echo "       pdf         build luaotfload.pdf" +	@echo "       manual      crate manpages for luaotfload-tool(1) and" +	@echo "                   luaotfload.conf(5) (requires Docutils)" +	@echo "       graph       generate file graph (requires GraphViz)" +	@echo +	@echo "       chars       import char-def.lua as luaotfload-characters.lua" +	@echo "       status      create repository info (luaotfload-status.lua)" +	@echo +	@echo "       tds         package a zipball according to the TDS" +	@echo "       ctan        package a zipball for uploading to CTAN" +	@echo + +# vim:noexpandtab:tabstop=8:shiftwidth=2 @@ -1,14 +1,27 @@  Change History  -------------- -2014/05/18, luaotfload v2.4-4 -    * Fix incorrect handling of font file formats (backport of commit -      828a69ef... to the 2.5 branch). - -2014/02/05, luaotfload v2.4-3 -    * Add contact info to --version output of luaotfload-tool (backport from -      2.5). -    * Fix bug with broken font names (backport from 2.5). +2014/**/**, luaotfload v2.5 +    * Remove legacy code. +    * Remove compatibility with the old mkluatexfontdb script. +    * Remove test directory. Use https://bitbucket.org/phg/lua-la-tex-tests +      instead. +    * Remove luaotfload.lua from luaotfload.dtx; it is now a separate file +      luaotfload-main.lua. +    * Standard source tree structure: the code is now located in the ./doc, +      ./scripts, ./src, ./build, and ./misc directories. +    * Move the heavier LPEG parsers from luaotfload-features (syntax) and +      luaotfload-database (fontconfig) into the new file +      luaotfload-parsers.lua. +    * Move logging routines from luaotfload-override in to luaotfload-log. +    * Scan local font files (``--local`` flag to luaotfload-tool, flag +      ``scan_local`` during TeX run). +    * Add bisection mode (``--bisect``) to luaotfload-tool. +    * Add functions for accessing the database: ``aux.font_index()`` and +      ``aux.read_font_index()``. +    * Distinguish XDG configuration paths (Reuben Thomas) +    * Optional configuration via rc files. +    * Configure default features via configuration file.  2013/12/31, luaotfload v2.4      * Additional self-tests, now in separate file (luaotfload-diagnostics.lua) @@ -32,6 +32,7 @@ Elie Roux                <elie.roux@telecom-bretagne.eu>  Will Robertson           <will.robertson@latex-project.org>  Philipp Gesang           <philipp.gesang@alumni.uni-heidelberg.de>  Dohyun Kim               <nomosnomos@gmail.com> +Reuben Thomas            <https://github.com/rrthomas>  Installation diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..ed340a4 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,58 @@ +NAME		= luaotfload +DOCPDF		= $(NAME).pdf +DOCSRC		= $(NAME)-latex.tex + +SCRIPTNAME	= luaotfload-tool +TOOLMANSRC	= $(SCRIPTNAME).rst +TOOLMAN		= $(SCRIPTNAME).1 + +CONFNAME	= luaotfload.conf +CONFMANSRC	= $(CONFNAME).rst +CONFMAN		= $(CONFNAME).5 + +MANPAGES	= $(TOOLMAN) $(CONFMAN) + +GRAPH		= filegraph +DOTPDF		= $(GRAPH).pdf +DOT		= $(GRAPH).dot + +DOCS		= $(DOTPDF) $(DOCPDF) $(MANPAGES) + +DO_LATEXMK	= @latexmk -e '$$max_repeat = 5' -pdf -lualatex -silent $< >/dev/null +# latexmk does only one run on my machine, so we’re not going to rely on it +DO_LATEX	= @lualatex -interaction=batchmode $< >/dev/null +DO_GRAPHVIZ	= @dot -Tpdf -o $@ $< > /dev/null +DO_DOCUTILS	= @rst2man $< >$@ 2>/dev/null + +doc: graph $(DOCPDF) +all: manuals doc +graph: $(DOTPDF) +manuals: $(TOOLMAN) $(CONFMAN) + +$(DOCPDF): $(DOCSRC) +	@echo "creating PDF documentation ($@)" +	$(DO_LATEX) +	$(DO_LATEX) +	mv -f -- $(<:tex=pdf) $@ + +$(TOOLMAN): $(TOOLMANSRC) +	@echo "creating man page ($(TOOLMAN))" +	$(DO_DOCUTILS) + +$(CONFMAN): $(CONFMANSRC) +	@echo "creating man page ($(CONFMAN))" +	$(DO_DOCUTILS) + +$(DOTPDF): $(DOT) +	@echo "creating file graph ($(DOTPDF))" +	$(DO_GRAPHVIZ) + +.PHONY: clean mrproper + +clean: +	@$(RM) -- *.log *.aux *.toc *.idx *.ind *.ilg *.out + +mrproper: clean +	@$(RM) -- $(DOCS) + +# vim:noexpandtab:tabstop=8:shiftwidth=2 diff --git a/filegraph.dot b/doc/filegraph.dot index 9eccd80..47db9ea 100644 --- a/filegraph.dot +++ b/doc/filegraph.dot @@ -122,7 +122,7 @@ strict digraph luaotfload_files { //looks weird with circo ...                         style  = "filled,rounded",                         penwidth=2] -    luaotfload        [label  = "luaotfload.lua", +    luaotfload        [label  = "luaotfload-main.lua",                         shape  = rect,                         width  = "3.2cm",                         height = "1.2cm", @@ -200,8 +200,9 @@ strict digraph luaotfload_files { //looks weird with circo ...                  <th> <td colspan="2"> <font point-size="12" face="Iwona Italic">Luaotfload Libraries</font> </td> </th>                  <tr> <td>luaotfload-auxiliary.lua</td>    <td>luaotfload-features.lua</td>    </tr>                  <tr> <td>luaotfload-override.lua</td>     <td>luaotfload-loaders.lua</td>     </tr> -                <tr> <td>luaotfload-database.lua</td>     <td>luaotfload-color.lua</td>       </tr> -                <tr> <td>luaotfload-letterspace.lua</td>                                      </tr> +                <tr> <td>luaotfload-log.lua</td>          <td>luaotfload-letterspace.lua</td> </tr> +                <tr> <td>luaotfload-parsers.lua</td>      <td>luaotfload-database.lua</td>    </tr> +                <tr> <td>luaotfload-color.lua</td>                                            </tr>              </table>          >,      ] @@ -256,6 +257,7 @@ strict digraph luaotfload_files { //looks weird with circo ...                  <tr> <td>luatex-font-tfm.lua</td>   <td>luatex-font-afm.lua</td>   </tr>                  <tr> <td>luatex-font-afk.lua</td>   <td>luatex-fonts-tfm.lua</td>  </tr>                  <tr> <td>luatex-fonts-chr.lua</td>  <td>luatex-fonts-lua.lua</td>  </tr> +                <tr> <td>luatex-fonts-inj.lua</td>  <td>luatex-fonts-otn.lua</td>  </tr>                  <tr> <td>luatex-fonts-def.lua</td>  <td>luatex-fonts-ext.lua</td>  </tr>                  <tr> <td>luatex-fonts-cbk.lua</td>                                 </tr> @@ -275,8 +277,8 @@ strict digraph luaotfload_files { //looks weird with circo ...                  <th> <td colspan="3"> <font point-size="12" face="Iwona Italic"> Font and Node Libraries from Context </font> </td> </th>                  <tr> <td>data-con.lua</td> <td>font-ini.lua</td> <td>font-con.lua</td> </tr>                  <tr> <td>font-cid.lua</td> <td>font-map.lua</td> <td>font-oti.lua</td> </tr> -                <tr> <td>font-otf.lua</td> <td>font-otb.lua</td> <td>node-inj.lua</td> </tr> -                <tr> <td>font-ota.lua</td> <td>font-otn.lua</td> <td>font-def.lua</td> </tr> +                <tr> <td>font-otf.lua</td> <td>font-otb.lua</td> <td>font-ota.lua</td> </tr> +                <tr> <td>font-def.lua</td> </tr>              </table>          >,      ] diff --git a/doc/luaotfload-context.tex b/doc/luaotfload-context.tex new file mode 100644 index 0000000..6c8d4b2 --- /dev/null +++ b/doc/luaotfload-context.tex @@ -0,0 +1,485 @@ +% macros=mkvi +%% Copyright (C) 2009-2014 +%% +%%      by  Elie Roux      <elie.roux@telecom-bretagne.eu> +%%      and Khaled Hosny   <khaledhosny@eglug.org> +%%      and Philipp Gesang <philipp.gesang@alumni.uni-heidelberg.de> +%% +%% This file is part of Luaotfload. +%% +%%      Home:      https://github.com/lualatex/luaotfload +%%      Support:   <lualatex-dev@tug.org>. +%% +%% Luaotfload is under the GPL v2.0 (exactly) license. +%% +%% ---------------------------------------------------------------------------- +%% +%% Luaotfload is free software; you can redistribute it and/or +%% modify it under the terms of the GNU General Public License +%% as published by the Free Software Foundation; version 2 +%% of the License. +%% +%% Luaotfload is distributed in the hope that it will be useful, +%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%% GNU General Public License for more details. +%% +%% You should have received a copy of the GNU General Public License +%% along with Luaotfload; if not, see <http://www.gnu.org/licenses/>. +%% +%% ---------------------------------------------------------------------------- +%% + +\unprotect + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% layout and paper +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\setuppapersize [A5] [A5] %% 148×210 + +\definelayout [mainlayout] [ +  backspace=15mm, %% 133 +  textwidth=103mm, +  topspace=15mm, +] + +\setuplayout [mainlayout] + +\setuppagenumbering [location=,alternative=doublesided] + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% colors +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\usecolors [x11] +\definecolor [primarycolor]   [dodgerblue4] +\definecolor [secondarycolor] [goldenrod4] + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% interaction +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\setupinteraction [ +  state=start, +  page=no, +  click=yes, +  style=italic, +  color=primarycolor, +  contrastcolor=secondarycolor, +  title={The Luaotfload package}, +  subtitle={OpenType layout system for Plain TeX and LaTeX}, +  author={Elie Roux & Khaled Hosny & Philipp Gesang}, +  keywords={luatex, lualatex, unicode, opentype}, +] + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% fonts +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\usemodule [simplefonts] + +\definefontfeature [default]   [default] [mode=base,liga=yes,dlig=yes,tlig=yes,onum=yes] +\definefontfeature [monospace]           [liga=no,tlig=no,onum=no] + +\definefontfamily [mainface] [serif] [Linux Libertine O] [features=default] +%definefontfamily [mainface] [serif] [Liberation Serif] [feature=default] +%definefontfamily [mainface] [sans]  [Iwona]             [feature=default] +\definefontfamily [mainface] [sans]  [Iwona Medium] [ +  feature=default, +  it=file:IwonaMedium-Italic.otf, +  tf=file:IwonaMedium-Regular.otf, +  bf=file:Iwona-Bold.otf, +  bi=file:Iwona-BoldItalic.otf, +] +%definefontfamily [mainface] [sans]  [DejaVu Sans]       [feature=default] +\definefontfamily [mainface] [mono]  [Liberation Mono]   [scale=0.85,features=monospace] + +\setupbodyfont [mainface,10pt] + +\def \LUA      {Lua} +\def \LUALATEX {Lua\LATEX} +\def \OpenType {\identifier{Open\kern-.25ex Type}} + +\definealternativestyle [emphasis:texmacro]        [\ss \it \letterbackslash]       [\ss \it \letterbackslash] +\definealternativestyle [emphasis:identifier]      [\ss]                            [\ss] +\definealternativestyle [emphasis:normal]          [\sl]                            [\sl] +\definealternativestyle [emphasis:abbrev]          [{\feature [+][smallcaps]}]      [{\feature [+][smallcaps]}] +\definealternativestyle [emphasis:Largefont]       [{\switchtobodyfont[14pt]}]      [{\switchtobodyfont[14pt]}] +\definealternativestyle [emphasis:smallcaps]       [{\feature [+][smallcaps]}]      [{\feature [+][smallcaps]}] +%definealternativestyle [emphasis:nonproportional] [\mono]                          [\mono] +\definealternativestyle [emphasis:nonproportional] [\tt]                            [\tt] +\definealternativestyle [head:section]             [{\roman\feature[+][smallcaps]}] [{\roman\feature[+][smallcaps]}] +\definealternativestyle [head:subsection]          [{\roman\feature[+][smallcaps]}] [{\roman\feature[+][smallcaps]}] +\definealternativestyle [head:subsubsection]       [{\roman\feature[+][smallcaps]}] [{\roman\feature[+][smallcaps]}] +\definealternativestyle [typing:luafunction]       [\italic]                        [\italic] +\definealternativestyle [typing:fileent]           [\tt]                            [\tt] + +\definehighlight        [texmacro] [style=emphasis:texmacro]        %% cs +\definehighlight      [identifier] [style=emphasis:identifier]      %% names +\definehighlight          [abbrev] [style=emphasis:abbrev]          %% acronyms +\definehighlight        [emphasis] [style=emphasis:normal]          %% level 1 emph + +\definehighlight       [Largefont] [style=emphasis:Largefont]       %% font size +\definehighlight       [smallcaps] [style=emphasis:smallcaps]       %% font feature +\definehighlight [nonproportional] [style=emphasis:nonproportional] %% font switch + +\definetype [fileent]     [style=typing:fileent] +\definetype [luafunction] [style=typing:luafunction] +\setuptyping [style=ttx] + +\definebodyfontenvironment  [8pt] +\definebodyfontenvironment [10pt] +\definebodyfontenvironment [12pt] + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% headings +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\setuphead [section]       [style=head:section,      alternative=inmargin] +\setuphead [subsection]    [style=head:subsection,   alternative=inmargin] +\setuphead [subsubsection] [style=head:subsubsection,alternative=inmargin] + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% running headers +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\setupheadertexts +  [{\tfx \getmarking[section]}] [pagenumber] +  [pagenumber] [{\tfx \fileent{Luaotfload} Manual}] + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% structurals +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% section +\def \beginsection {\dosingleempty \section_begin_indeed} + +\def \section_begin_indeed [#ref]#title{% +  \iffirstargument +    \startsection [reference=#ref,title=#title]% +  \else +    \startsection [title=#title]% +  \fi +} + +\let \endsection \stopsection + +%% subsection +\def \beginsubsection {\dosingleempty \section_begin_indeed} + +\def \subsection_begin_indeed [#ref]#title{% +  \iffirstargument +    \startsubsection [reference=#ref,title=#title]% +  \else +    \startsubsection [title=#title]% +  \fi +} + +\let \endsubsection \stopsection + +%% subsubsection +\def \beginsubsubsection {\dosingleempty \section_begin_indeed} + +\def \subsubsection_begin_indeed [#ref]#title{% +  \iffirstargument +    \startsubsubsection [reference=#ref,title=#title]% +  \else +    \startsubsubsection [title=#title]% +  \fi +} + +\let \endsubsubsection \stopsection + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% inline verbatim +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Context offers both \type{…} and \type<<…>>, but not an unbalanced +%% one that we could map directly onto Latex’s \verb|…|. + +\definetype [inlinecode_indeed] [style=emphasis:nonproportional] + +%% The listings macros don’t seem to handle backslashes and braces +%% well. We emulate this behavior by handling the escaping in Lua. + +\startluacode +  local lpeg           = require "lpeg" +  local Cs, P, S       = lpeg.Cs, lpeg.P, lpeg.S +  local lpegmatch      = lpeg.match +  local unescape_char  = S[[\letterbackslash\letterleftbrace\letterrightbrace]] +  local backslash      = P[[\letterbackslash]] +  local unescape       = Cs (((backslash / "" * unescape_char) + 1)^0) +  commands.unescape_things = function (str) +    context.type (lpegmatch (unescape, str)) +  end +\stopluacode + +\unexpanded \def \inlinecode #content{% +  \ctxcommand {unescape_things \!!bs \detokenize {#content}\!!es}% +} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% codelistings +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Now *that’s* what I call easy. + +\unexpanded \def \beginlisting {% +  \grabbufferdatadirect{listing}{beginlisting}{endlisting}% +} + +\unexpanded \def \endlisting {\typebuffer [listing]} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% enumerations and lists +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\definedescription [descriptionitem] [ +  align=right, +  alternative=hanging, +  width=2em, +] + +\def \begindescriptions {% +  \begingroup +    \def \beginnormalitem ##1\endnormalitem{% +      \startitem##1\stopitem +    } +    \let \endnormalitem   \relax +    \let \beginaltitem    \startdescriptionitem +    \let \endaltitem      \stopdescriptionitem +} + +\let \enddescriptions \endgroup + + +\definedescription [definitionitem] [ +  align=right, +  alternative=hanging, +] + +\def \begindefinitions {% +  \begingroup +    \def \beginnormalitem ##1\endnormalitem{% +      \startitem##1\stopitem +    } +    \let \endnormalitem   \relax +    \let \beginaltitem    \startdefinitionitem +    \let \endaltitem      \stopdefinitionitem +} + +\let \enddefinitions \endgroup + + +\definedescription [filelistitem] [ +  align=normal, +  alternative=hanging, +  headstyle=typing:fileent, +  width=4cm, +] + +\def \beginfilelist {% +  \begingroup +    \def \beginnormalitem ##1\endnormalitem{% +      \startitem##1\stopitem +    } +    \let \endnormalitem   \relax +    \let \beginaltitem    \startfilelistitem +    \let \endaltitem      \stopfilelistitem +} + +\let \endfilelist \endgroup + +\definedescription [functionlistitem] [ +  align=normal, +  alternative=hanging, +  headstyle=typing:luafunction, +  width=4cm, +] + +\def \beginfunctionlist {% +  \begingroup +    \def \beginnormalitem ##1\endnormalitem{% +      \startitem##1\stopitem +    } +    \let \endnormalitem   \relax +    \let \beginaltitem    \startfunctionlistitem +    \let \endaltitem      \stopfunctionlistitem +} + +\let \endfunctionlist \endgroup + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% columns +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\def \begindoublecolumns {\startcolumns [2]} +\let \enddoublecolumns   \stopcolumns + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% alignment +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\setupnarrower [before={\blank[line]},after={\blank[line]}] +\let \beginnarrower \startnarrower +\let \endnarrower   \stopnarrower + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% special elements +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\definefont [lmromantenregular] [file:lmroman10-regular.otf*default] + +\def \meta #1{% +  {\lmromantenregular<}% +  {\italic #1}% +  {\lmromantenregular>}% +} + +\def \beginabstractcontent {% +  \grabbufferdatadirect{abstractcontent}{beginabstractcontent}{endabstractcontent}% +} + +\let \endabstractcontent \relax + +\def \setdocumenttitle   #1{\setvalue  {document_title}{#1}} +\def \setdocumentdate    #1{\setvalue   {document_date}{#1}} +\def \setdocumentauthor  #1{\setvalue {document_author}{#1}} + +\let \typesetdocumenttitle \relax +\let \beginfrontmatter \relax + +\def \endfrontmatter { +  \startstandardmakeup +    \vfill +    \strut \hfill +      \startframed [frame=off,align=middle,width=.5\textwidth] +        \Largefont{\getvalue {document_title}} +      \stopframed +    \hfill \strut \par + +    \blank [2*big] + +    \strut \hfill +      \startframed [frame=off,align=middle,width=.65\textwidth] +        \setuplocalinterlinespace [18pt] +        \getvalue {document_author} +      \stopframed +    \hfill \strut \par + +    \vfill +    \strut \hfill \getvalue {document_date} \hfill \strut +    \blank [2*big] + +    \strut \hfill +    \startframed [width=.7\textwidth,align=normal,style=tfx,frame=off]% +      \getbuffer [abstractcontent] +    \stopframed +    \hfill \strut  +  \stopstandardmakeup +} + +\let \typesetcontent \completecontent + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% floats +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% XXX we can improve on this part later + +\usemodule [vim] +\definevimtyping [bnf] [syntax=bnf] +\definefloat [syntax] [figure] + +\def \beginsyntaxfloat #reference#caption{% +  \begingroup +    \edef \currentreference {#reference}% +    \edef \currentcaption   {#caption}% +    \grabbufferdatadirect{rawsyntaxdata}{beginsyntaxfloat}{endsyntaxfloat}% +} + +\def \endsyntaxfloat {% +    \savebuffer [rawsyntaxdata] [rawsyntaxdata] +    \startplacesyntax [ +      reference=\currentreference, +      title={\currentcaption}, +    ] +      %% there’s no \typebnfbuffer in t-vim :( +      \typebnffile {\jobname-rawsyntaxdata.tmp} +    \stopplacesyntax +  \endgroup% +} + +\def \figurefloat #reference#caption#file{% +  \startplacefigure [ +    reference=#reference, +    title={#caption}, +  ] +    \externalfigure [#file] [width=\textwidth] +  \stopplacefigure +} + + +\def \tablefloat #reference#caption#content{% +  \startplacetable [ +    reference=#reference, +    title={#caption}, +  ] +    #content +  \stopplacetable +} + + +%% tables + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% tables +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\setupxtable [frame=off,option=stretch,textwidth=\dimexpr(\textwidth/2)] + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% hyperlinks and references +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\unexpanded \def \hyperlink{% +  \dosingleempty \hyperlink_indeed% +} + +\def \hyperlink_indeed [#text]#url{% +  \iffirstargument +    \useURL [temporary_url] [#url] [] [#text]% +  \else +    \useURL [temporary_url] [#url]% +  \fi% +  \from [temporary_url]% +} + + +\def \email #1{\goto{#1}[url(mailto:#1)]} + +\def \label #tag{\reference [#tag]\empty} +\def \pageref #tag{\at{page}{#tag}} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% escaped characters +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\let \charpercent   \letterpercent +\let \charbackslash \letterbackslash +\let \chartilde     \lettertilde + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% main +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\protect + +\newif \ifcontextmkiv \contextmkivtrue + +\starttext +  \input luaotfload-main.tex +\stoptext + diff --git a/doc/luaotfload-latex.tex b/doc/luaotfload-latex.tex new file mode 100644 index 0000000..34c494d --- /dev/null +++ b/doc/luaotfload-latex.tex @@ -0,0 +1,448 @@ +\luatexsuppresslongerror1%% sigh ... +%% Copyright (C) 2009-2014 +%% +%%      by  Elie Roux      <elie.roux@telecom-bretagne.eu> +%%      and Khaled Hosny   <khaledhosny@eglug.org> +%%      and Philipp Gesang <philipp.gesang@alumni.uni-heidelberg.de> +%% +%% This file is part of Luaotfload. +%% +%%      Home:      https://github.com/lualatex/luaotfload +%%      Support:   <lualatex-dev@tug.org>. +%% +%% Luaotfload is under the GPL v2.0 (exactly) license. +%% +%% ---------------------------------------------------------------------------- +%% +%% Luaotfload is free software; you can redistribute it and/or +%% modify it under the terms of the GNU General Public License +%% as published by the Free Software Foundation; version 2 +%% of the License. +%% +%% Luaotfload is distributed in the hope that it will be useful, +%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%% GNU General Public License for more details. +%% +%% You should have received a copy of the GNU General Public License +%% along with Luaotfload; if not, see <http://www.gnu.org/licenses/>. +%% +%% ---------------------------------------------------------------------------- +%% + +\documentclass{ltxdoc} + +\makeatletter + +\usepackage {metalogo,multicol,fancyvrb,xspace} +\usepackage [x11names] {xcolor} + +\def \primarycolor   {DodgerBlue4} %%-> rgb  16  78 139 | #104e8b +\def \secondarycolor {Goldenrod4}  %%-> rgb 139 105 200 | #8b6914 + +\usepackage[ +    bookmarks=true, +   colorlinks=true, +    linkcolor=\primarycolor, +     urlcolor=\secondarycolor, +    citecolor=\primarycolor, +     pdftitle={The Luaotfload package}, +   pdfsubject={OpenType layout system for Plain TeX and LaTeX}, +    pdfauthor={Elie Roux & Khaled Hosny & Philipp Gesang}, +  pdfkeywords={luatex, lualatex, unicode, opentype} +]{hyperref} + +\usepackage {fontspec} +\usepackage {unicode-math} + +\setmainfont[ +% Numbers     = OldStyle, %% buggy with font cache +  Ligatures   = TeX, +  BoldFont    = {Linux Libertine O Bold}, +  ItalicFont  = {Linux Libertine O Italic}, +  SlantedFont = {Linux Libertine O Italic}, +]{Linux Libertine O} +\setmonofont[Ligatures=TeX,Scale=MatchLowercase]{Liberation Mono} +%setsansfont[Ligatures=TeX]{Linux Biolinum O} +\setsansfont[Ligatures=TeX,Scale=MatchLowercase]{Iwona Medium} +%setmathfont{XITS Math} + +\usepackage{hologo} + +\newcommand\TEX      {\TeX\xspace} +\newcommand\LUA      {Lua\xspace} +\newcommand\PDFTEX   {pdf\TeX\xspace} +\newcommand\LUATEX   {Lua\TeX\xspace} +\newcommand\XETEX    {\XeTeX\xspace} +\newcommand\LATEX    {\LaTeX\xspace} +\newcommand\LUALATEX {Lua\LaTeX\xspace} +\newcommand\CONTEXT  {Con\TeX t\xspace} +\newcommand\OpenType {\identifier{Open\kern-.25ex Type}\xspace} + +%% \groupedcommand, with some omissions taken from syst-aux.mkiv +\let \handlegroupnormalbefore \relax +\let \handlegroupnormalafter  \relax + +\protected \def \handlegroupnormal #1#2{% +  \bgroup % 1 +  \def \handlegroupbefore {#1}% +  \def \handlegroupafter  {#2}% +  \afterassignment \handlegroupnormalbefore +  \let \next = +} + +\def \handlegroupnormalbefore {% +  \bgroup % 2 +  \handlegroupbefore +  \bgroup % 3 +  \aftergroup \handlegroupnormalafter% +} + +\def \handlegroupnormalafter {% +  \handlegroupafter +  \egroup % 3 +  \egroup % 2 +} + +\let \groupedcommand \handlegroupnormal %% only the two arg version + +\def \definehighlight [#1][#2]{% +  \ifcsname #1\endcsname\else +    \expandafter\def\csname #1\endcsname{% +      \leavevmode +      \groupedcommand {#2}\empty% +    } +  \fi% +} + +%% old, simplistic definition: obsolete now that we have +%% \groupedcommand +%\def\definehighlight[#1][#2]% + %{\ifcsname #1\endcsname\else +    %\expandafter\def\csname #1\endcsname% +      %{\bgroup#2\csname #1_indeed\endcsname} +    %\expandafter\def\csname #1_indeed\endcsname##1% +      %{##1\egroup}% +  %\fi} + +\def\restoreunderscore{\catcode`\_=12\relax} + +\definehighlight         [fileent][\ttfamily\restoreunderscore]         %% files, dirs +\definehighlight        [texmacro][\sffamily\itshape\textbackslash]     %% cs +\definehighlight     [luafunction][\sffamily\itshape\restoreunderscore] %% lua identifiers +\definehighlight      [identifier][\sffamily]                           %% names +\definehighlight          [abbrev][\rmfamily\scshape]                   %% acronyms +\definehighlight        [emphasis][\rmfamily\slshape]                   %% level 1 emph + +\definehighlight       [Largefont][\Large]                              %% font size +\definehighlight       [smallcaps][\sc]                                 %% font feature +\definehighlight [nonproportional][\tt]                                 %% font switch + +\newcommand*\email[1]{\href{mailto:#1}{#1}} + +\renewcommand\partname{Part}%% gets rid of the stupid “file” heading + +\usepackage{syntax}%% bnf for font request syntax + +\usepackage{titlesec} + +\def\movecountertomargin#1{\llap{\rmfamily\upshape#1\hskip2em}} +\def\zeropoint{0pt} +\titleformat \part +             {\normalsize\rmfamily\bfseries} +             {\movecountertomargin\thepart} \zeropoint {} +\titleformat \section +             {\normalsize\rmfamily\scshape} +             {\movecountertomargin\thesection} \zeropoint {} +\titleformat \subsection +             {\small\rmfamily\itshape} +             {\movecountertomargin\thesubsection} \zeropoint {} +\titleformat \subsubsection +             {\normalsize\rmfamily\upshape} +             {\movecountertomargin\thesubsubsection} \zeropoint {} + +\usepackage{tocloft} +\renewcommand \cftpartfont   {\rmfamily\upshape} +\renewcommand \cftsecfont    {\rmfamily\upshape} +\renewcommand \cftsubsecfont {\rmfamily\upshape} +\setlength \cftbeforepartskip {1ex} +\setlength \cftbeforesecskip  {1ex} + +\VerbatimFootnotes + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% structurals +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\def \definestructural #1{% +  \expandafter \let \csname end#1\endcsname \relax + +  \expandafter \def \csname begin#1\endcsname {% +    \@ifnextchar[{\csname begin#1indeed\endcsname} +                 {\csname begin#1indeed\endcsname[]}% +  } + +  \expandafter \def \csname begin#1indeed\endcsname [##1]##2{% +    \edef \first {##1}% +    \ifx \first \empty +      \csname #1\endcsname [##2]{##2}% +    \else +      \csname #1\endcsname [\first]{##2}% +    \fi +  } +} + +\definestructural {section} +\definestructural {subsection} +\definestructural {subsubsection} + +\def \fakesection #1{\section*{#1}} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% inline verbatim +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Context offers both \type{…} and \type<<…>>, but not an unbalanced +%% one that we could map directly onto Latex’s \verb|…|. + +\usepackage {listings} +\lstset { +  basicstyle=\ttfamily, +} + +%\let \inlinecode \lstinline +\protected \def \inlinecode {\lstinline} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% codelistings; this sucks hard since we lack access to buffers +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\newcount \othercatcode  \othercatcode 12 +\newcount \activecatcode \othercatcode 13 + +\newluatexcatcodetable \vrbcatcodes +\setluatexcatcodetable \vrbcatcodes {% +  \luatexcatcodetable \CatcodeTableIniTeX +  \catcode  9 \othercatcode %% \tabasciicode +  \catcode 13 \othercatcode %% \endoflineasciicode +  \catcode 12 \othercatcode %% \formfeedasciicode +  \catcode 26 \othercatcode %% \endoffileasciicode +  \catcode 32 \othercatcode %% \spaceasciicode +} + +\newluatexcatcodetable \literalcatcodes +\setluatexcatcodetable \literalcatcodes {% +  \luatexcatcodetable \CatcodeTableString +  \catcode 32 \activecatcode %% \spaceasciicode +} + +\def \beginlisting {% +  \begingroup +    \luatexcatcodetable \vrbcatcodes +    \beginlistingindeed% +} + +\directlua { +  local texprint   = tex.print +  local stringsub  = string.sub +  local backslash  = string.byte (0x5c) +  document         = document or { } +  document.printlines = function (buffer) +    for _, line in next, string.explode (buffer, "\noexpand\n") do +      if stringsub (line, 1, 1) == " " then +        line = backslash .. line +      end +      texprint (-1, line) +      texprint (-1, "") +    end +  end +} + +\def \beginlistingindeed#1\endlisting{% +  \endgroup +  \begingroup +    \ttfamily +    \small +    \begin {quote} +      \bgroup +        \addfontfeature {RawFeature=-tlig;-liga}%% So one can’t just turn them all off at once using the ``Ligatures`` key? +        \luatexcatcodetable \literalcatcodes +        \obeyspaces +        \obeylines +        \directlua{document.printlines ([==[\detokenize {#1}]==])} +      \egroup +    \end {quote} +  \endgroup +} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% enumerations and lists +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\def \definelist [#1]#2{% name, itemcode +  \expandafter \def \csname begin#1\endcsname {% +    \begin {itemize} +      \let \normalitem = \item +      \def \altitem ####1{% +        \def \first {####1}% +        #2 +      } +      \let \beginnormalitem \item +      \let \endnormalitem   \relax +      \let \beginaltitem    \altitem +      \let \endaltitem      \relax +  } + +  \expandafter \def \csname end#1\endcsname {% +    \end {itemize} +  } +} + +\definelist  [descriptions]{\normalitem {\textbf \first}\hfill\break} +\definelist   [definitions]{\normalitem {\fileent {\first}}} +\definelist      [filelist]{\normalitem {\fileent {\first}}\space--\hskip 1em} +\definelist  [functionlist]{\normalitem {\luafunction {\first}}\hfill\break} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% columns +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\def \begindoublecolumns {\begin {multicols} {2}} +\def \enddoublecolumns   {\end {multicols}} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% alignment +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\def \begincentered {\begin {center}} +\def \endcentered   {\end {center}} + +\def \beginnarrower {\begin {quote}} +\def \endnarrower   {\end {quote}} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% special elements +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\let \beginfrontmatter \relax +\let \endfrontmatter   \relax + +\def \beginabstractcontent {\begin {abstract}} +\def \endabstractcontent   {\end {abstract}} + +\let \setdocumenttitle      \title +\let \setdocumentdate       \date +\let \setdocumentauthor     \author +\let \typesetdocumenttitle  \maketitle + +\AtBeginDocument {%% seriously? +  \let \typesetcontent \tableofcontents% +} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% floats +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% syntax definition +\def \beginsyntaxfloat #1#2{%% #1:label #2:caption +  \begin {figure} [b] +    \edef \syntaxlabel  {#1}% +    \def \syntaxcaption {#2}% +    \setlength\grammarparsep{12pt plus 2pt minus 2pt}% +    \setlength\grammarindent{5cm}% +    \begingroup +      \small +      \begin {grammar} +} + +\def \endsyntaxfloat {% +      \end {grammar} +    \endgroup +    \caption \syntaxcaption +    \label   \syntaxlabel +  \end {figure} +} + +%% figures, e.g. the file graph +\def \figurefloat #1#2#3{%% #1:label #2:caption #3:file +  \begin {figure} [b] +    \caption {#2}% +    \includegraphics[width=\textwidth]{#3}% +    \label {#1} +  \end {figure} +} + +%% tables +\def \tablefloat #1#2{%% #1:label #2:caption +  \begin {table} [t] +    \hrule +    \caption {#2}% +    \label {#1} +    \hrule +  \end {table} +} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% hyperlinks +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\protected \def \hyperlink{% +  \@ifnextchar[{\hyperlinkindeed}% +               {\hyperlinkindeed[]}% +} + +\def \hyperlinkindeed [#1]#2{% +  \def \first {#1}% +  \ifx \first \empty +    \url {#2}% +  \else +    \href {#2}{#1}% +  \fi% +} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% tables +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Our tables aren’t anything special so we stick with “tabular” on the +%% Latex end. +%% +%% This is going to be largely incompatible with Context since format +%% specifications work quite differently (even between different +%% Context table variants). + +\def \begintabulate [#1]#2\endtabulate{% +  \begingroup +    \let \beginrow  = \relax %% -> \NC in Context +    \let \newcell   = &      %% -> \NC +    \let \endrow    = \cr    %% -> \NC \NR +    \begin {tabular}{#1}% +      #2 +    \end {tabular} +  \endgroup +} + +\let \endtabulate \relax + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% escaped characters +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\let \charpercent   \textpercent +\let \charbackslash \textbackslash +\let \chartilde     \textasciitilde + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% main +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\makeatother + +\newif \ifcontextmkiv \contextmkivfalse + +\begin {document} +  \input {luaotfload-main.tex} +\end {document} + + diff --git a/doc/luaotfload-main.tex b/doc/luaotfload-main.tex new file mode 100644 index 0000000..034b704 --- /dev/null +++ b/doc/luaotfload-main.tex @@ -0,0 +1,1591 @@ +%% Copyright (C) 2009-2014 +%% +%%      by  Elie Roux      <elie.roux@telecom-bretagne.eu> +%%      and Khaled Hosny   <khaledhosny@eglug.org> +%%      and Philipp Gesang <philipp.gesang@alumni.uni-heidelberg.de> +%% +%% This file is part of Luaotfload. +%% +%%      Home:      https://github.com/lualatex/luaotfload +%%      Support:   <lualatex-dev@tug.org>. +%% +%% Luaotfload is under the GPL v2.0 (exactly) license. +%% +%% ---------------------------------------------------------------------------- +%% +%% Luaotfload is free software; you can redistribute it and/or +%% modify it under the terms of the GNU General Public License +%% as published by the Free Software Foundation; version 2 +%% of the License. +%% +%% Luaotfload is distributed in the hope that it will be useful, +%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%% GNU General Public License for more details. +%% +%% You should have received a copy of the GNU General Public License +%% along with Luaotfload; if not, see <http://www.gnu.org/licenses/>. +%% +%% ---------------------------------------------------------------------------- +%% + +\beginfrontmatter + +  \setdocumenttitle  {The \identifier{luaotfload} package} +  \setdocumentdate   {2014/**/** v2.5} +  \setdocumentauthor {Elie Roux · Khaled Hosny · Philipp Gesang\\ +                      Home:    \hyperlink {https://github.com/lualatex/luaotfload}\\ +                      Support: \email     {lualatex-dev@tug.org}} + +  \typesetdocumenttitle + +  \beginabstractcontent +    This package is an adaptation of the \CONTEXT font loading system. +    It allows for loading \OpenType fonts with an extended syntax and adds +    support for a variety of font features. +  \endabstractcontent + +\endfrontmatter + +\typesetcontent + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\beginsection {Introduction} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +Font management and installation has always been painful with \TEX.  A +lot of files are needed for one font (\abbrev{tfm}, \abbrev{pfb}, +\abbrev{map}, \abbrev{fd}, \abbrev{vf}), and due to the 8-Bit encoding +each font is limited to 256 characters. + +But the font world has evolved since the original \TEX, and new +typographic systems have appeared, most notably the so called +\emphasis{smart font} technologies like \OpenType fonts (\abbrev{otf}). + +These fonts can contain many more characters than \TEX fonts, as well +as additional functionality like ligatures, old-style numbers, small +capitals, etc., and support more complex writing systems like Arabic +and Indic\footnote{% +  Unfortunately, \identifier{luaotfload} doesn‘t support many Indic +  scripts right now. +  Assistance in implementing the prerequisites is greatly +  appreciated. +} +scripts. + +\OpenType fonts are widely deployed and available for all modern +operating systems. + +As of 2013 they have become the de facto standard for advanced text +layout. + +However, until recently the only way to use them directly in the \TEX +world was with the \XETEX engine. + +Unlike \XETEX, \LUATEX has no built-in support for \OpenType or +technologies other than the original \TEX fonts. + +Instead, it provides hooks for executing \LUA code during the \TEX run +that allow implementing extensions for loading fonts and manipulating +how input text is processed without modifying the underlying engine. + +This is where \identifier{luaotfload} comes into play: +Based on code from \CONTEXT, it extends \LUATEX with functionality necessary +for handling \OpenType fonts. + +Additionally, it provides means for accessing fonts known to the operating +system conveniently by indexing the metadata. + +\endsection + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\beginsection {Thanks} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\identifier{Luaotfload} is part of \LUALATEX, the community-driven +project to provide a foundation for using the \LATEX format with the +full capabilites of the \LUATEX engine. +% +As such, the distinction between end users, contributors, and project +maintainers is intentionally kept less strict, lest we unduly +personalize the common effort. + +Nevertheless, the current maintainers would like to express their +gratitude to Khaled Hosny, Akira Kakuto, Hironori Kitagawa and Dohyun +Kim. +% +Their contributions -- be it patches, advice, or systematic +testing -- made the switch from version 1.x to 2.2 possible. +% +Also, Hans Hagen, the author of the font loader, made porting the +code to \LATEX a breeze due to the extra effort he invested into +isolating it from the rest of \CONTEXT, not to mention his assistance +in the task and willingness to respond to our suggestions. + +\endsection + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\beginsection {Loading Fonts} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\identifier{luaotfload} supports an extended font request syntax: + +\beginnarrower +      \nonproportional{\string\font\string\foo\space= \string{}% +      \meta{prefix}\nonproportional{:}% +      \meta{font name}\nonproportional{:}% +      \meta{font features}\nonproportional{\string}}% +      \meta{\TEX font features} +\endnarrower + +\noindent +The curly brackets are optional and escape the spaces in the enclosed +font name. +% +Alternatively, double quotes serve the same purpose. +% +A selection of individual parts of the syntax are discussed below; +for a more formal description see figure \ref{font-syntax}. + +\beginsyntaxfloat +  {font-syntax} +  {Font request syntax. +   Braces or double quotes around the +   \emphasis{specification} rule will +   preserve whitespace in file names. +   In addition to the font style modifiers +   (\emphasis{slash-notation}) given above, there +   are others that are recognized but will be silently +   ignored: \nonproportional{aat}, +            \nonproportional{icu}, and +            \nonproportional{gr}. +   The special terminals are: +   \smallcaps {feature\textunderscore id} for a valid font +      feature name and +   \smallcaps {feature\textunderscore value} for the corresponding +      value. +   \smallcaps {tfmname} is the name of a \abbrev{tfm} file. +   \smallcaps {digit}  again refers to bytes 48--57, and +   \smallcaps {all\textunderscore characters} to all byte values. +   \smallcaps {csname} and \smallcaps {dimension} are the \TEX concepts.} +% +      <definition>      ::= `\\font', {\sc csname}, `=', <font request>, [ <size> ] ; + +      <size>            ::= `at', {\sc dimension} ; + +      <font request>    ::= `"', <unquoted font request> `"' +      \alt                  `{', <unquoted font request> `}' +      \alt                  <unquoted font request> ; + +      <unquoted font request> ::= <specification>, [`:', <feature list> ] +      \alt                        `[', <path lookup> `]', [ [`:'], <feature list> ] ; + +      <specification>    ::= <prefixed spec>, [ <subfont no> ], \{ <modifier> \} +      \alt                   <anon lookup>, \{ <modifier> \} ; + +      <prefixed spec>    ::= `file:', <file lookup> +      \alt                   `name:', <name lookup> ; + +      <file lookup>      ::= \{ <name character> \} ; + +      <name lookup>      ::= \{ <name character> \} ; + +      <anon lookup>      ::= {\sc tfmname} | <name lookup> ; + +      <path lookup>      ::= \{ {\sc all_characters} - `]' \} ; + +      <modifier>         ::= `/', (`I' | `B' | `BI' | `IB' | `S=', \{ {\sc digit} \} ) ; + +      <subfont no>       ::= `(', \{ {\sc digit} \}, `)' ; + +      <feature list>     ::= <feature expr>, \{ `;', <feature expr> \} ; + +      <feature expr>     ::= {\sc feature_id}, `=', {\sc feature_value} +      \alt                   <feature switch>, {\sc feature_id} ; + +      <feature switch>   ::= `+' | `-' ; + +      <name character>   ::= {\sc all_characters} - ( `(' | `/' | `:' ) ; +\endsyntaxfloat + +\beginsubsection{Prefix -- the \identifier{luaotfload}{ }Way} + +In \identifier{luaotfload}, the canonical syntax for font requests +requires a \emphasis{prefix}: +% +\beginnarrower +  \nonproportional{\string\font\string\fontname\space= }% +  \meta{prefix}% +  \nonproportional{:}% +  \meta{fontname}% +  \dots +\endnarrower +% +where \meta{prefix} is either \inlinecode{file:} or \inlinecode {name:}.\footnote{% +  The development version also knows two further prefixes, +  \inlinecode {kpse:} and \inlinecode {my:}. +  % +  A \inlinecode {kpse} lookup is restricted to files that can be found by +  \identifier{kpathsea} and +  will not attempt to locate system fonts. +  % +  This behavior can be of value when an extra degree of encapsulation is +  needed, for instance when supplying a customized tex distribution. + +  The \inlinecode {my} lookup takes this a step further: it lets you define +  a custom resolver function and hook it into the \luafunction{resolve_font} +  callback. +  % +  This ensures full control over how a file is located. +  % +  For a working example see the +  \hyperlink [test repo]{https://bitbucket.org/phg/lua-la-tex-tests/src/5f6a535d/pln-lookup-callback-1.tex}. +} +% +It determines whether the font loader should interpret the request as +a \emphasis{file name} or +  \emphasis{font name}, respectively, +which again influences how it will attempt to locate the font. +% +Examples for font names are +            “Latin Modern Italic”, +            “GFS Bodoni Rg”, and +            “PT Serif Caption” +-- they are the human readable identifiers +usually listed in drop-down menus and the like.\footnote{% +  Font names may appear like a great choice at first because they +  offer seemingly more intuitive identifiers in comparison to arguably +  cryptic file names: +  % +  “PT Sans Bold” is a lot more descriptive than \fileent{PTS75F.ttf}. +  On the other hand, font names are quite arbitrary and there is no +  universal method to determine their meaning. +  % +  While \identifier{luaotfload} provides fairly sophisticated heuristic +  to figure out a matching font style, weight, and optical size, it +  cannot be relied upon to work satisfactorily for all font files. +  % +  For an in-depth analysis of the situation and how broken font names +  are, please refer to +  \hyperlink [this post]{http://www.ntg.nl/pipermail/ntg-context/2013/073889.html} +  by Hans Hagen, the author of the font loader. +  % +  If in doubt, use filenames. +  % +  \fileent{luaotfload-tool} can perform the matching for you with the +  option \inlinecode {--find=<name>}, and you can use the file name it returns +  in your font definition. +} +% +In order for fonts installed both in system locations and in your +\fileent{texmf} to be accessible by font name, \identifier{luaotfload} must +first collect the metadata included in the files. +% +Please refer to section~\ref{sec:fontdb} below for instructions on how to +create the database. + +File names are whatever your file system allows them to be, except +that that they may not contain the characters +  \inlinecode {(}, +  \inlinecode {:}, and +  \inlinecode {/}. +% +As is obvious from the last exception, the \inlinecode {file:} lookup will +not process paths to the font location -- only those +files found when generating the database are addressable this way. +% +Continue below in the \XETEX section if you need to load your fonts +by path. +% +The file names corresponding to the example font names above are +  \fileent{lmroman12-italic.otf}, +  \fileent{GFSBodoni.otf}, and +  \fileent{PTZ56F.ttf}. + +\endsubsection + +\beginsubsection {Compatibility Layer} + +In addition to the regular prefixed requests, \identifier{luaotfload} +accepts loading fonts the \XETEX way. +% +There are again two modes: bracketed and unbracketed. +A bracketed request looks as follows. + +\beginnarrower +  \nonproportional{\string\font\string\fontname\space = [}% +  \meta{/path/to/file}% +  \nonproportional{]} +\endnarrower + +\noindent +Inside the square brackets, every character except for a closing +bracket is permitted, allowing for specifying paths to a font file. +% +Naturally, path-less file names are equally valid and processed the +same way as an ordinary \inlinecode {file:} lookup. + +\beginnarrower +  \nonproportional{\string\font\string\fontname\space= }% +  \meta{font name} +  \dots +\endnarrower + +Unbracketed (or, for lack of a better word: \emphasis{anonymous}) +font requests resemble the conventional \TEX syntax. +% +However, they have a broader spectrum of possible interpretations: +before anything else, \identifier{luaotfload} attempts to load a +traditional \TEX Font Metric (\abbrev{tfm} or \abbrev{ofm}). +% +If this fails, it performs a \inlinecode {name:} lookup, which itself will +fall back to a \inlinecode {file:} lookup if no database entry matches +\meta{font name}. + +Furthermore, \identifier{luaotfload} supports the slashed (shorthand) +font style notation from \XETEX. + +\beginnarrower +  \nonproportional{\string\font\string\fontname\space= }% +  \meta{font name}% +  \nonproportional{/}% +  \meta{modifier} +  \dots +\endnarrower + +\noindent +Currently, four style modifiers are supported: +  \inlinecode {I} for italic shape, +  \inlinecode {B} for bold   weight, +  \inlinecode {BI} or \inlinecode {IB} for the combination of both. +% +Other “slashed” modifiers are too specific to the \XETEX engine and +have no meaning in \LUATEX. + +\endsubsection + +\beginsubsection{Examples} + +\beginsubsubsection{Loading by File Name} + +For example, conventional \abbrev{type1} font can be loaded with a +\inlinecode {file:} request like so: + +\beginlisting +  \font \lmromanten = {file:ec-lmr10} at 10pt +\endlisting + +The \OpenType version of Janusz Nowacki’s font \emphasis{Antykwa +Półtawskiego}\footnote{% +  \hyperlink {http://jmn.pl/antykwa-poltawskiego/}, also available in +  in \TEX Live. +} +in its condensed variant can be loaded as follows: + +\beginlisting +  \font \apcregular = file:antpoltltcond-regular.otf at 42pt +\endlisting + +The next example shows how to load the \emphasis{Porson} font digitized by +the Greek Font Society using \XETEX-style syntax and an absolute path from a +non-standard directory: + +\beginlisting +  \font \gfsporson = "[/tmp/GFSPorson.otf]" at 12pt +\endlisting + +\endsubsubsection + +\beginsubsubsection{Loading by Font Name} + +The \inlinecode {name:} lookup does not depend on cryptic filenames: + +\beginlisting +  \font \pagellaregular = {name:TeX Gyre Pagella} at 9pt +\endlisting + +A bit more specific but essentially the same lookup would be: + +\beginlisting +  \font \pagellaregular = {name:TeX Gyre Pagella Regular} at 9pt +\endlisting + +\noindent +Which fits nicely with the whole set: + +\beginlisting +  \font\pagellaregular    = {name:TeX Gyre Pagella Regular}    at 9pt +  \font\pagellaitalic     = {name:TeX Gyre Pagella Italic}     at 9pt +  \font\pagellabold       = {name:TeX Gyre Pagella Bold}       at 9pt +  \font\pagellabolditalic = {name:TeX Gyre Pagella Bolditalic} at 9pt + +  {\pagellaregular     foo bar baz\endgraf} +  {\pagellaitalic      foo bar baz\endgraf} +  {\pagellabold        foo bar baz\endgraf} +  {\pagellabolditalic  foo bar baz\endgraf} + +  ... +\endlisting + +\endsubsubsection + +\beginsubsubsection{Modifiers} + +If the entire \emphasis{Iwona} family\footnote{% +  \hyperlink {http://jmn.pl/kurier-i-iwona/}, +  also in \TEX Live. +} +is installed in some location accessible by \identifier{luaotfload}, +the regular shape can be loaded as follows: + +\beginlisting +  \font \iwona = Iwona at 20pt +\endlisting + +\noindent +To load the most common of the other styles, the slash notation can +be employed as shorthand: + +\beginlisting +  \font \iwonaitalic     = Iwona/I    at 20pt +  \font \iwonabold       = Iwona/B    at 20pt +  \font \iwonabolditalic = Iwona/BI   at 20pt +\endlisting + +\noindent +which is equivalent to these full names: + +\beginlisting +  \font \iwonaitalic     = "Iwona Italic"       at 20pt +  \font \iwonabold       = "Iwona Bold"         at 20pt +  \font \iwonabolditalic = "Iwona BoldItalic"   at 20pt +\endlisting + +\endsubsubsection +\endsubsection +\endsection + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\beginsection {Font features} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\emphasis{Font features} are the second to last component in the +general scheme for font requests: + +\beginnarrower +  \nonproportional{\string\font\string\foo\space= "}% +  \meta{prefix}% +  \nonproportional{:}% +  \meta{font name}% +  \nonproportional{:}% +  \meta{font features}% +  \meta{\TEX font features}% +  \nonproportional{"} +\endnarrower + +\noindent +If style modifiers are present (\XETEX style), they must precede +\meta{font features}. + +The element \meta{font features} is a semicolon-separated list of feature +tags\footnote{% +  Cf. \hyperlink {http://www.microsoft.com/typography/otspec/featurelist.htm}. +} +and font options. +% +Prepending a font feature with a \inlinecode{+} (plus sign) enables it, +whereas a \inlinecode{-} (minus) disables it. For instance, the request + +\beginlisting +  \font \test = LatinModernRoman:+clig;-kern +\endlisting + +\noindent activates contextual ligatures (\inlinecode{clig}) and +disables kerning (\inlinecode{kern}). +% +Alternatively the options \inlinecode{true} or \inlinecode{false} can +be passed to the feature in a key/value expression. +% +The following request has the same meaning as the last one: + +\beginlisting +  \font \test = LatinModernRoman:clig=true;kern=false +\endlisting + +\noindent +Furthermore, this second syntax is required should a font feature +accept other options besides a true/false switch. +% +For example, \emphasis{stylistic alternates} (\inlinecode{salt}) are +variants of given glyphs. +% +They can be selected either explicitly by supplying the variant +index (starting from one), or randomly by setting the value to, +obviously, \inlinecode{random}. + +%% TODO   verify that this actually works with a font that supports +%%        the salt/random feature!\fi +\beginlisting +  \font \librmsaltfirst = LatinModernRoman:salt=1 +\endlisting + +\beginsubsection {Basic font features} + +\begindescriptions + +  \beginaltitem {mode} +         \identifier{luaotfload} has two \OpenType processing +         \emphasis{modes}: +         \identifier{base} and \identifier{node}. + +         \identifier{base} mode works by mapping \OpenType +         features to traditional \TEX ligature and kerning mechanisms. +         % +         Supporting only non-contextual substitutions and kerning +         pairs, it is the slightly faster, albeit somewhat limited, variant. +         % +         \identifier{node} mode works by processing \TeX’s internal +         node list directly at the \LUA end and supports +         a wider range of \OpenType features. +         % +         The downside is that the intricate operations required for +         \identifier{node} mode may slow down typesetting especially +         with complex fonts and it does not work in math mode. + +         By default \identifier{luaotfload} is in \identifier{node} +         mode, and \identifier{base} mode has to be requested where needed, +         e.~g. for math fonts. +  \endaltitem + +  \beginaltitem {script} \label{script-tag} +         An \OpenType script tag;\footnote{% +           See \hyperlink {http://www.microsoft.com/typography/otspec/scripttags.htm} +           for a list of valid values. +           % +           For scripts derived from the Latin alphabet the value +           \inlinecode{latn} is good choice. +         } +         the default value is \inlinecode{dlft}. +         % +         Some fonts, including very popular ones by foundries like Adobe, +         do not assign features to the \inlinecode{dflt} script, in +         which case the script needs to be set explicitly. +  \endaltitem + +  \beginaltitem {language} +         An \OpenType language system identifier,\footnote{% +           Cf. \hyperlink {http://www.microsoft.com/typography/otspec/languagetags.htm}. +         } +         defaulting to \inlinecode{dflt}. +  \endaltitem + +  \beginaltitem {featurefile} +         A comma-separated list of feature files to be applied to the +         font. +         % +         Feature files contain a textual representation of +         \OpenType tables and extend the features of a font +         on fly. +         % +         After they are applied to a font, features defined in a +         feature file can be enabled or disabled just like any +         other font feature. +         % +         The syntax is documented in \identifier{Adobe}’s +         \OpenType Feature File Specification.\footnote{% +           Cf. \hyperlink {http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html}. +           Feature file support is part of the engine which at the +           time of this writing (2014) implements the spec only +           partially. +           See the +           \hyperlink [\LUATEX tracker]{http://tracker.luatex.org/view.php?id=231} +            for details. +         } + +         For a demonstration of how to set a \inlinecode{tkrn} feature consult +         the file \inlinecode{tkrn.fea} that is part of \identifier{luaotfload}. +         It can be read and applied as follows: + +         \inlinecode{\\font \\test = Latin Modern Roman:featurefile=tkrn.fea;+tkrn} +  \endaltitem + +  \beginaltitem {color} +         A font color, defined as a triplet of two-digit hexadecimal +         \abbrev{rgb} values, with an optional fourth value for +         transparency +         (where \inlinecode{00} is completely transparent and +         \inlinecode{FF} is opaque). + +         For example, in order to set text in semitransparent red: + +         \beginlisting +\font \test = "Latin Modern Roman:color=FF0000BB" +         \endlisting +  \endaltitem + +  \beginaltitem {kernfactor \& letterspace} +         Define a font with letterspacing (tracking) enabled. +         % +         In \identifier{luaotfload}, letterspacing is implemented by +         inserting additional kerning between glyphs. + +         This approach is derived from and still quite similar to the +         \emphasis{character kerning} (\texmacro{setcharacterkerning} / +         \texmacro{definecharacterkerning} \& al.) functionality of +         Context, see the file \fileent{typo-krn.lua} there. +         % +         The main difference is that \identifier{luaotfload} does not +         use \LUATEX attributes to assign letterspacing to regions, +         but defines virtual letterspaced versions of a font. + +         The option \identifier{kernfactor} accepts a numeric value that +         determines the letterspacing factor to be applied to the font +         size. +         % +         E.~g. a kern factor of $0.42$ applied to a $10$ pt font +         results in $4.2$ pt of additional kerning applied to each +         pair of glyphs. +         % +         Ligatures are split into their component glyphs unless +         explicitly ignored (see below). + +         For compatibility with \XETEX an alternative +         \identifier{letterspace} option is supplied that interprets the +         supplied value as a \emphasis{percentage} of the font size but +         is otherwise identical to \identifier{kernfactor}. +         % +         Consequently, both definitions in below snippet yield the same +         letterspacing width: + +         \beginlisting +\font \iwonakernedA = "file:Iwona-Regular.otf:kernfactor=0.125" +\font \iwonakernedB = "file:Iwona-Regular.otf:letterspace=12.5" +         \endlisting + +         Specific pairs of letters and ligatures may be exempt from +         letterspacing by defining the \LUA functions +         \luafunction{keeptogether} and \luafunction{keepligature}, +         respectively, inside the namespace \inlinecode {luaotfload.letterspace}. +         % +         Both functions are called whenever the letterspacing callback +         encounters an appropriate node or set of nodes. +         % +         If they return a true-ish value, no extra kern is inserted at +         the current position. +         % +         \luafunction{keeptogether} receives a pair of consecutive +         glyph nodes in order of their appearance in the node list. +         % +         \luafunction{keepligature} receives a single node which can be +         analyzed into components. +         % +         (For details refer to the \emphasis{glyph nodes} section in the +         \LUATEX reference manual.) +         % +         The implementation of both functions is left entirely to the +         user. +  \endaltitem + +\ifcontextmkiv +  \startbuffer [printvectors] +  \directlua{inspect(fonts.protrusions.setups.default) +             inspect(fonts.expansions.setups.default)} +  \stopbuffer +\fi + +  \beginaltitem {protrusion \& expansion} +         These keys control microtypographic features of the font, +         namely \emphasis{character protrusion} and \emphasis{font +         expansion}. +         % +         Their arguments are names of \LUA tables that contain +         values for the respective features.\footnote{% +            For examples of the table layout please refer to the +            section of the file \fileent{luaotfload-fonts-ext.lua} where the +            default values are defined. +            % +            Alternatively and with loss of information, you can dump +            those tables into your terminal by issuing +            \unless \ifcontextmkiv +              \beginlisting + \directlua{inspect(fonts.protrusions.setups.default) +            inspect(fonts.expansions.setups.default)} +              \endlisting +            \else +              \typebuffer [printvectors] +            \fi +            at some point after loading \fileent{luaotfload.sty}. +         } +         % +         For both, only the set \identifier{default} is predefined. + +         For example, to define a font with the default +         protrusion vector applied\footnote{% +           You also need to set +               \inlinecode {pdfprotrudechars=2} and +               \inlinecode {pdfadjustspacing=2} +           to activate protrusion and expansion, respectively. +           See the +           \hyperlink [\PDFTEX manual]{http://mirrors.ctan.org/systems/pdftex/manual/pdftex-a.pdf}% +           for details. +         }: + +         \beginlisting +\font \test = LatinModernRoman:protrusion=default +         \endlisting +  \endaltitem +\enddescriptions + +\endsubsection + +\beginsubsection {Non-standard font features} +\identifier{luaotfload} adds a number of features that are not defined +in the original \OpenType specification, most of them +aiming at emulating the behavior familiar from other \TEX engines. +% +Currently (2014) there are three of them: + +\begindescriptions + +  \beginaltitem {anum} +          Substitutes the glyphs in the \abbrev{ascii} number range +          with their counterparts from eastern Arabic or Persian, +          depending on the value of \identifier{language}. +  \endaltitem + +  \beginaltitem {tlig} +          Applies legacy \TEX ligatures\footnote{% +            These contain the feature set \inlinecode {trep} of earlier +            versions of \identifier{luaotfload}. + +            Note to \XETEX users: this is the equivalent of the +            assignment \inlinecode {mapping=text-tex} using \XETEX's input +            remapping feature. +          }: + +          \unless \ifcontextmkiv +            %% Using braced arg syntax with inline code appears to be +            %% impossible within Latex tables -- just ignore the weird +            %% exclamation points below. +            \begintabulate [rlrl] +              \beginrow ``  \newcell  {\inlinecode !``! } \newcell  ''  \newcell  {\inlinecode !''!} \endrow +              \beginrow `   \newcell  {\inlinecode !`!  } \newcell  '   \newcell  {\inlinecode !'! } \endrow +              \beginrow "   \newcell  {\inlinecode !"!  } \newcell  --  \newcell  {\inlinecode !--!} \endrow +              \beginrow --- \newcell  {\inlinecode !---!} \newcell  !`  \newcell  {\inlinecode ?!`?} \endrow +              \beginrow ?`  \newcell  {\inlinecode !?`! } \newcell      \newcell                     \endrow +            \endtabulate +          \else +            %% XXX find a way to wrap these in the tabulate environment +            \startframed [frame=off,width=broad,align=middle] +              \startframed [frame=off,width=\dimexpr(\textwidth/2)] +                \startxtable [align=middle] +                    \startxrow \startxcell ``  \stopxcell \startxcell  \inlinecode {``}  \stopxcell \startxcell  ''  \stopxcell \startxcell  \inlinecode {''}  \stopxcell \stopxrow +                    \startxrow \startxcell `   \stopxcell \startxcell  \inlinecode {`}   \stopxcell \startxcell  '   \stopxcell \startxcell  \inlinecode {'}   \stopxcell \stopxrow +                    \startxrow \startxcell "   \stopxcell \startxcell  \inlinecode {"}   \stopxcell \startxcell  --  \stopxcell \startxcell  \inlinecode {--}  \stopxcell \stopxrow +                    \startxrow \startxcell --- \stopxcell \startxcell  \inlinecode {---} \stopxcell \startxcell  !`  \stopxcell \startxcell  \inlinecode {!`}  \stopxcell \stopxrow +                    \startxrow \startxcell ?`  \stopxcell \startxcell  \inlinecode {?`}  \stopxcell \startxcell      \stopxcell \startxcell                    \stopxcell \stopxrow +                \stopxtable +              \stopframed +            \stopframed +          \fi +  \endaltitem + +  \beginaltitem {itlc} +          Computes italic correction values (active by default). +  \endaltitem + +\enddescriptions + +\endsubsection +\endsection + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\beginsection {Font names database} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\label{sec:fontdb} + +As mentioned above, \identifier{luaotfload} keeps track of which +fonts are available to \LUATEX by means of a \emphasis{database}. +% +This allows referring to fonts not only by explicit filenames but +also by the proper names contained in the metadata which is often +more accessible to humans.\footnote{% +  The tool \hyperlink[\fileent{otfinfo}]{http://www.lcdf.org/type/} +  (comes with \TEX Live), when invoked on a font file with the +  \inlinecode {-i} option, lists the variety of name fields defined for +  it. +} + +When \identifier{luaotfload} is asked to load a font by a font name, +it will check if the database exists and load it, or else generate a +fresh one. +% +Should it then fail to locate the font, an update to the database is +performed in case the font has been added to the system only +recently. +% +As soon as the database is updated, the resolver will try +and look up the font again, all without user intervention. +% +The goal is for \identifier{luaotfload} to act in the background and +behave as unobtrusively as possible, while providing a convenient +interface to the fonts installed on the system. + +Generating the database for the first time may take a while since it +inspects every font file on your computer. +% +This is particularly noticeable if it occurs during a typesetting run. +In any case, subsequent updates to the database will be quite fast. + +\beginsubsection[luaotfload-tool] +                {\fileent{luaotfload-tool}} + +It can still be desirable at times to do some of these steps +manually, and without having to compile a document. +% +To this end, \identifier{luaotfload} comes with the utility +\fileent{luaotfload-tool} that offers an interface to the database +functionality. +% +Being a \LUA script, there are two ways to run it: +either make it executable (\inlinecode {chmod +x} on unixoid systems) or +pass it as an argument to \fileent{texlua}.\footnote{% +  Tests by the maintainer show only marginal performance gain by +  running with Luigi Scarso’s +  \hyperlink [\identifier{Luajit\kern-.25ex\TEX}]{https://foundry.supelec.fr/projects/luajittex/}, +  which is probably due to the fact that most of the time is spent +  on file system operations. + +  \emphasis{Note}: +  On \abbrev{MS} \identifier{Windows} systems, the script can be run +  either by calling the wrapper application +  \fileent{luaotfload-tool.exe} or as +  \inlinecode {texlua.exe luaotfload-tool.lua}. +} +% +Invoked with the argument \inlinecode {--update} it will perform a database +update, scanning for fonts not indexed. + +\beginlisting +  luaotfload-tool --update +\endlisting + +Adding the \inlinecode {--force} switch will initiate a complete +rebuild of the database. + +\beginlisting +  luaotfload-tool --update --force +\endlisting + +\endsubsection + +\beginsubsection{Search Paths} + +\identifier{luaotfload} scans those directories where fonts are +expected to be located on a given system. +% +On a Linux machine it follows the paths listed in the +\identifier{Fontconfig} configuration files; +consult \inlinecode {man 5 fonts.conf} for further information. +% +On \identifier{Windows} systems, the standard location is +\inlinecode {Windows\\Fonts}, +% +while \identifier{Mac OS~X} requires a multitude of paths to +be examined. +% +The complete list is is given in table \ref{table-searchpaths}. +Other paths can be specified by setting the environment variable +\inlinecode {OSFONTDIR}. +% +If it is non-empty, then search will be extended to the included +directories. + +\tablefloat {table-searchpaths} +  {List of paths searched for each supported operating system.} +  {% +    \unless \ifcontextmkiv +      \begincentered +        \begintabulate [lp{.5\textwidth}] +          \beginrow +            Windows   \newcell \inlinecode !\% WINDIR\%\\ Fonts! +          \endrow +          \beginrow +            Linux     \newcell \fileent{/usr/local/etc/fonts/fonts.conf} and\hfill\break +                               \fileent{/etc/fonts/fonts.conf} +          \endrow +          \beginrow +            Mac       \newcell \fileent{\textasciitilde/Library/Fonts},\break +                               \fileent{/Library/Fonts},\break +                               \fileent{/System/Library/Fonts}, and\hfill\break +                               \fileent{/Network/Library/Fonts} +          \endrow +        \endtabulate +      \endcentered +    \else +      \setuplocalinterlinespace [14pt] +      \starttabulate [|l|p(.5\textwidth)|] +        \NC Windows   \NC \inlinecode {\% WINDIR\%\\ Fonts} \NC \NR +        \NC Linux     \NC \fileent{/usr/local/etc/fonts/fonts.conf} and\crlf +                          \fileent{/etc/fonts/fonts.conf} \NC \NR +        \NC +          Mac         \NC \fileent{\textasciitilde/Library/Fonts},\crlf +                          \fileent{/Library/Fonts},\break +                          \fileent{/System/Library/Fonts}, and\crlf +                          \fileent{/Network/Library/Fonts} \NC \NR +      \stoptabulate +    \fi% +  } + +\endsubsection + +\beginsubsection{Querying from Outside} + +\fileent{luaotfload-tool} also provides rudimentary means of +accessing the information collected in the font database. +% +If the option \inlinecode {--find=}\emphasis{name} is given, the script will +try and search the fonts indexed by \identifier{luaotfload} for a +matching name. +% +For instance, the invocation + +\beginlisting +  luaotfload-tool  --find="Iwona Regular" +\endlisting + +\noindent +will verify if “Iwona Regular” is found in the database and can be +readily requested in a document. + +If you are unsure about the actual font name, then add the +\inlinecode {-F} (or \inlinecode {--fuzzy}) switch to the command line to enable +approximate matching. +% +Suppose you cannot precisely remember if the variant of +\identifier{Iwona} you are looking for was “Bright” or “Light”. +The query + +\beginlisting +  luaotfload-tool  -F --find="Iwona Bright" +\endlisting + +\noindent +will tell you that indeed the latter name is correct. + +Basic information about fonts in the database can be displayed +using the \inlinecode {-i} option (\inlinecode {--info}). +% +\beginlisting +  luaotfload-tool  -i --find="Iwona Light Italic" +\endlisting +% +\noindent +The meaning of the printed values is described in section 4.4 of the +\LUATEX reference manual.\footnote{% +  In \TEX Live: \fileent{texmf-dist/doc/luatex/base/luatexref-t.pdf}. +} + +For a much more detailed report about a given font try the +\inlinecode {-I} option instead (\inlinecode {--inspect}). +\beginlisting +  luaotfload-tool  -I --find="Iwona Light Italic" +\endlisting + +\inlinecode {luaotfload-tool --help} will list the available command line +switches, including some not discussed in detail here. +% +For a full documentation of \identifier{luaotfload-tool} and its +capabilities refer to the manpage +(\inlinecode {man 1 luaotfload-tool}).\footnote{% +  Or see \inlinecode {luaotfload-tool.rst} in the source directory. +} + +\endsubsection + +\beginsubsection {Blacklisting Fonts} +\label{font-blacklist} + +Some fonts are problematic in general, or just in \LUATEX. +% +If you find that compiling your document takes far too long or eats +away all your system’s memory, you can track down the culprit by +running \inlinecode {luaotfload-tool -v} to increase verbosity. +% +Take a note of the \emphasis{filename} of the font that database +creation fails with and append it to the file +\fileent{luaotfload-blacklist.cnf}. + +A blacklist file is a list of font filenames, one per line. +Specifying the full path to where the file is located is optional, the +plain filename should suffice. +% +File extensions (\fileent{.otf}, \fileent{.ttf}, etc.) may be omitted. +% +Anything after a percent (\inlinecode {\%}) character until the end of the line +is ignored, so use this to add comments. +% +Place this file to some location where the \identifier{kpse} +library can find it, e.~g. +\fileent{texmf-local/tex/luatex/luaotfload} if you are running +\identifier{\TEX Live},\footnote{% +  You may have to run \inlinecode {mktexlsr} if you created a new file in +  your \fileent{texmf} tree. +} +or just leave it in the working directory of your document. +% +\identifier{luaotfload} reads all files named +\fileent{luaotfload-blacklist.cnf} it finds, so the fonts in +\fileent{./luaotfload-blacklist.cnf} extend the global blacklist. + +Furthermore, a filename prepended with a dash character (\inlinecode{-}) is +removed from the blacklist, causing it to be temporarily whitelisted +without modifying the global file. +% +An example with explicit paths: + +\beginlisting +% example otf-blacklist.cnf +/Library/Fonts/GillSans.ttc  % Luaotfload ignores this font. +-/Library/Fonts/Optima.ttc   % This one is usable again, even if +                             % blacklisted somewhere else. +\endlisting + +\endsubsection +\endsection + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\beginsection {Files from \CONTEXT and \LUATEX-Fonts} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\identifier{luaotfload} relies on code originally written by Hans +Hagen for the \hyperlink[\identifier{\CONTEXT}]{http://wiki.contextgarden.net} +format. +% +It integrates the font loader as distributed in +the \identifier{\LUATEX-Fonts} package. +% +The original \LUA source files have been combined using the +\fileent{mtx-package} script into a single, self-contained blob. +In this form the font loader has no further dependencies\footnote{% +  It covers, however, to some extent the functionality of the +  \identifier{lualibs} package. +} +and requires only minor adaptions to integrate into +\identifier{luaotfload}. +% +The guiding principle is to let \CONTEXT/\LUATEX-Fonts take care of +the implementation, and update the imported code from time to time. +% +As maintainers, we aim at importing files from upstream essentially +\emphasis{unmodified}, except for renaming them to prevent name +clashes. +% +This job has been greatly alleviated since the advent of +\LUATEX-Fonts, prior to which the individual dependencies had to be +manually spotted and extracted from the \CONTEXT source code in a +complicated and error-prone fashion. + +Below is a commented list of the files distributed with +\identifier{luaotfload} in one way or the other. +% +See figure \ref{file-graph} on page \pageref{file-graph} for a +graphical representation of the dependencies. +% +From \LUATEX-Fonts, only the file \fileent{luatex-fonts-merged.lua} +has been imported as \fileent{luaotfload-fontloader.lua}. +% +It is generated by \fileent{mtx-package}, a \LUA source code merging +too developed by Hans Hagen.\footnote{% +  \fileent{mtx-package} is +  \hyperlink [part of \CONTEXT]{http://repo.or.cz/w/context.git/blob_plain/refs/heads/origin:/scripts/context/lua/mtx-package.lua} +  and requires \fileent{mtxrun}. +  Run +  \inlinecode {mtxrun --script package --help} +  to display further information. +  For the actual merging code see the file +  \fileent{util-mrg.lua} that is part of \CONTEXT. +} +It houses several \LUA files that can be classed in three +categories. + +\begindefinitions +  \beginnormalitem +    \emphasis{\LUA utility libraries}, a subset +    of what is provided by the \identifier{lualibs} +    package. + +    \begindoublecolumns +      \begindefinitions +          \beginaltitem {l-lua.lua}       \endaltitem +          \beginaltitem {l-lpeg.lua}      \endaltitem +          \beginaltitem {l-function.lua}  \endaltitem +          \beginaltitem {l-string.lua}    \endaltitem +          \beginaltitem {l-table.lua}     \endaltitem +          \beginaltitem {l-io.lua}        \endaltitem +          \beginaltitem {l-file.lua}      \endaltitem +          \beginaltitem {l-boolean.lua}   \endaltitem +          \beginaltitem {l-math.lua}      \endaltitem +          \beginaltitem {util-str.lua}    \endaltitem +      \enddefinitions +    \enddoublecolumns +  \endnormalitem + +  \beginnormalitem +    The \emphasis{font loader} itself. +    These files have been written for +    \LUATEX-Fonts and they are distributed along +    with \identifier{luaotfload}. +    \begindoublecolumns +      \begindefinitions +        \beginaltitem{luatex-basics-gen.lua} \endaltitem +        \beginaltitem{luatex-basics-nod.lua} \endaltitem +        \beginaltitem{luatex-fonts-enc.lua}  \endaltitem +        \beginaltitem{luatex-fonts-syn.lua}  \endaltitem +        \beginaltitem{luatex-fonts-tfm.lua}  \endaltitem +        \beginaltitem{luatex-fonts-chr.lua}  \endaltitem +        \beginaltitem{luatex-fonts-lua.lua}  \endaltitem +        \beginaltitem{luatex-fonts-inj.lua}  \endaltitem +        \beginaltitem{luatex-fonts-otn.lua}  \endaltitem +        \beginaltitem{luatex-fonts-def.lua}  \endaltitem +        \beginaltitem{luatex-fonts-ext.lua}  \endaltitem +        \beginaltitem{luatex-fonts-cbk.lua}  \endaltitem +      \enddefinitions +    \enddoublecolumns +  \endnormalitem + +  \beginnormalitem +    Code related to \emphasis{font handling and +    node processing}, taken directly from +    \CONTEXT. +    \begindoublecolumns +      \begindefinitions +        \beginaltitem{data-con.lua} \endaltitem +        \beginaltitem{font-ini.lua} \endaltitem +        \beginaltitem{font-con.lua} \endaltitem +        \beginaltitem{font-cid.lua} \endaltitem +        \beginaltitem{font-map.lua} \endaltitem +        \beginaltitem{font-oti.lua} \endaltitem +        \beginaltitem{font-otf.lua} \endaltitem +        \beginaltitem{font-otb.lua} \endaltitem +        \beginaltitem{font-ota.lua} \endaltitem +        \beginaltitem{font-def.lua} \endaltitem +        \beginaltitem{font-otp.lua} \endaltitem +      \enddefinitions +    \enddoublecolumns +  \endnormalitem +\enddefinitions + +Note that if \identifier{luaotfload} cannot locate the +merged file, it will load the individual \LUA libraries +instead. +% +Their names remain the same as in \CONTEXT (without the +\inlinecode {otfl}-prefix) since we imported the relevant section of +\fileent{luatex-fonts.lua} unmodified into \fileent{luaotfload-main.lua}. +Thus if you prefer running bleeding edge code from the +\CONTEXT beta, all you have to do is remove +\fileent{luaotfload-merged.lua} from the search path. + +Also, the merged file at some point loads the Adobe Glyph List from a +\LUA table that is contained in \fileent{luaotfload-glyphlist.lua}, +which is automatically generated by the script +\fileent{mkglyphlist}.\footnote{% +  See \fileent{luaotfload-font-enc.lua}. +  The hard-coded file name is why we have to replace the procedure +  that loads the file in \fileent{luaotfload-override.lua}. +} +There is a make target \identifier{glyphs} that will create a fresh +glyph list so we don’t need to import it from \CONTEXT any longer. + +In addition to these, \identifier{luaotfload} requires a number of +files not contained in the merge. Some of these have no equivalent in +\LUATEX-Fonts or \CONTEXT, some were taken unmodified from the latter. + + +\beginfilelist +    \beginaltitem {luaotfload-features.lua} +      font feature handling; incorporates some of the code from +      \fileent{font-otc} from \CONTEXT; +    \endaltitem +    \beginaltitem {luaotfload-override.lua} +      overrides the \CONTEXT logging functionality. +    \endaltitem +    \beginaltitem {luaotfload-loaders.lua} +      registers the \OpenType font reader as handler for Postscript +      fonts (\abbrev{pfa}, \abbrev{pfb}). +    \endaltitem +    \beginaltitem {luaotfload-parsers.lua} +      various \abbrev{lpeg}-based parsers. +    \endaltitem +    \beginaltitem {luaotfload-database.lua} +      font names database. +    \endaltitem +    \beginaltitem {luaotfload-colors.lua} +      color handling. +    \endaltitem +    \beginaltitem {luaotfload-auxiliary.lua} +      access to internal functionality for package authors (proposals +      for additions welcome). +    \endaltitem +    \beginaltitem {luaotfload-letterspace.lua} +      font-based letterspacing. +    \endaltitem +\endfilelist + +\figurefloat +  {file-graph} +  {Schematic of the files in \identifier{Luaotfload}} +  {filegraph.pdf} + +\endsection + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\beginsection {Auxiliary Functions} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +With release version 2.2, \identifier{luaotfload} received +additional functions for package authors to call from outside +(see the file \fileent{luaotfload-auxiliary.lua} for details). +% +The purpose of this addition twofold. +% +Firstly, \identifier{luaotfload} failed to provide a stable interface +to internals in the past which resulted in an unmanageable situation +of different packages abusing the raw access to font objects by means +of the \luafunction{patch_font} callback. +% +When the structure of the font object changed due to an update, all +of these imploded and several packages had to be fixed while +simultaneously providing fallbacks for earlier versions. +% +Now the patching is done on the \identifier{luaotfload} side and can +be adapted with future modifications to font objects without touching +the packages that depend on it. +% +Second, some the capabilities of the font loader and the names +database are not immediately relevant in \identifier{luaotfload} +itself but might nevertheless be of great value to package authors or +end users. + +Note that the current interface is not yet set in stone and the +development team is open to suggestions for improvements or +additions. + +\beginsubsection {Callback Functions} + +The \luafunction{patch_font} callback is inserted in the wrapper +\identifier{luaotfload} provides for the font definition callback +(see below, page \pageref{define-font}). +% +At this place it allows manipulating the font object immediately after +the font loader is done creating it. +% +For a short demonstration of its usefulness, here is a snippet that +writes an entire font object to the file \fileent{fontdump.lua}: + +\beginlisting +  \input luaotfload.sty +  \directlua{ +    local dumpfile    = "fontdump.lua" +    local dump_font   = function (tfmdata) +      local data = table.serialize(tfmdata) +      io.savedata(dumpfile, data) +    end + +    luatexbase.add_to_callback( +      "luaotfload.patch_font", +      dump_font, +      "my_private_callbacks.dump_font" +    ) +  } +  \font \dumpme = name:Iwona +  \bye +\endlisting + +\emphasis{Beware}: this creates a Lua file of around 150,000 lines of +code, taking up 3~\abbrev{mb} of disk space. +% +By inspecting the output you can get a first impression of how a font +is structured in \LUATEX’s memory, what elements it is composed of, +and in what ways it can be rearranged. + +\beginsubsubsection {Compatibility with Earlier Versions} + +As has been touched on in the preface to this section, the structure +of the object as returned by the fontloader underwent rather drastic +changes during different stages of its development, and not all +packages that made use of font patching have kept up with every one +of it. +% +To ensure compatibility with these as well as older versions of +some packages, \identifier{luaotfload} sets up copies of or references +to data in the font table where it used to be located. +% +For instance, important parameters like the requested point size, the +units factor, and the font name have again been made accessible from +the toplevel of the table even though they were migrated to different +subtables in the meantime. + +\endsubsubsection + +\beginsubsubsection{Patches} + +These are mostly concerned with establishing compatibility with \XETEX. + +\beginfunctionlist + +  \beginaltitem  {set_sscale_dimens} +    Calculate \texmacro{fontdimen}s 10 and 11 to emulate \XETEX. +  \endaltitem + +  \beginaltitem  {set_capheight} +    Calculates \texmacro{fontdimen} 8 like \XETEX. +  \endaltitem + +  \beginaltitem  {patch_cambria_domh} +    Correct some values of the font \emphasis{Cambria Math}. +  \endaltitem + +\endfunctionlist + +\endsubsection + +\beginsubsection {Package Author’s Interface} + +As \LUATEX release 1.0 is nearing, the demand for a reliable interface +for package authors increases. + +\endsubsubsection + +\beginsubsubsection{Font Properties} + +Below functions mostly concern querying the different components of a +font like for instance the glyphs it contains, or what font features +are defined for which scripts. + +\beginfunctionlist + +  \beginaltitem  {aux.font_has_glyph (id : int, index : int)} +            Predicate that returns true if the font \luafunction{id} +            has glyph \luafunction{index}. +  \endaltitem + +  \beginaltitem  {aux.slot_of_name(name : string)} +            Translates an Adobe Glyph name to the corresponding glyph +            slot. +  \endaltitem + +  \beginaltitem  {aux.name_of_slot(slot : int)} +            The inverse of \luafunction{slot_of_name}; note that this +            might be incomplete as multiple glyph names may map to the +            same codepoint, only one of which is returned by +            \luafunction{name_of_slot}. +  \endaltitem + +  \beginaltitem  {aux.provides_script(id : int, script : string)} +            Test if a font supports \luafunction{script}. +  \endaltitem + +  \beginaltitem  {aux.provides_language(id : int, script : string, language : string)} +            Test if a font defines \luafunction{language} for a given +            \luafunction{script}. +  \endaltitem + +  \beginaltitem  {aux.provides_feature(id : int, script : string, +             language : string, feature : string)} +            Test if a font defines \luafunction{feature} for +            \luafunction{language} for a given \luafunction{script}. +  \endaltitem + +  \beginaltitem  {aux.get_math_dimension(id : int, dimension : string)} +            Get the dimension \luafunction{dimension} of font \luafunction{id}. +  \endaltitem + +  \beginaltitem  {aux.sprint_math_dimension(id : int, dimension : string)} +            Same as \luafunction{get_math_dimension()}, but output the value +            in scaled points at the \TEX end. +  \endaltitem + +\endfunctionlist + +\endsubsubsection + +\beginsubsubsection{Database} + +%% not implemented, may come back later +\beginfunctionlist +%   \beginaltitem  {aux.scan_external_dir(dir : string)} +%             Include fonts in directory \luafunction{dir} in font lookups without +%             adding them to the database. +% +  \beginaltitem  {aux.read_font_index (void)} +            Read the index file from the appropriate location (usually +            the bytecode file \fileent{luaotfload-names.luc} somewhere +            in the \fileent{texmf-var} tree) and return the result as a +            table. The file is processed with each call so it is up to +            the user to store the result for later access. +  \endaltitem + +  \beginaltitem  {aux.font_index (void)} +            Return a reference of the font names table used internally +            by \identifier{luaotfload}. The index will be read if it +            has not been loaded up to this point. Also a font scan that +            overwrites the current index file might be triggered. Since +            the return value points to the actual index, any +            modifications to the table might influence runtime behavior +            of \identifier{luaotfload}. +  \endaltitem + +\endfunctionlist + +\endsubsubsection + +\endsubsection +\endsection + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\beginsection {Troubleshooting} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\beginsubsection {Database Generation} + +If you encounter problems with some fonts, please first update to the +latest version of this package before reporting a bug, as +\identifier{luaotfload} is under active development and still a moving +target. +% +The development takes place on \identifier{github} at +\hyperlink {https://github.com/lualatex/luaotfload} where there is an issue +tracker for submitting bug reports, feature requests and the likes. + +Bug reports are more likely to be addressed if they contain the output +of + +\beginlisting +    luaotfload-tool --diagnose=environment,files,permissions +\endlisting + +\noindent Consult the man page for a description of these options. + +Errors during database generation can be traced by increasing the +verbosity level and redirecting log output to \fileent{stdout}: + +\beginlisting +    luaotfload-tool -fuvvv --log=stdout +\endlisting + +\noindent or to a file in \fileent{/tmp}: + +\beginlisting +    luaotfload-tool -fuvvv --log=file +\endlisting + +\noindent In the latter case, invoke the \inlinecode {tail(1)} utility on the +file for live monitoring of the progress. + +If database generation fails, the font last printed to the terminal or +log file is likely to be the culprit. +% +Please specify it when reporting a bug, and blacklist it for the time +being (see above, page \pageref{font-blacklist}). + +\endsubsection + +\beginsubsection {Font Features} + +A common problem is the lack of features for some +\OpenType fonts even when specified. +% +This can be related to the fact that some fonts do not provide features +for the \inlinecode {dflt} script (see above on page \pageref{script-tag}), +which is the default one in this package. +% +If this happens, assigning a noth script when the font is defined should +fix it. +% +For example with \inlinecode {latn}: + +\beginlisting +    \font \test = file:MyFont.otf:script=latn;+liga; +\endlisting + +You can get a list of features that a font defines for scripts and +languages by querying it in \fileent{luaotfload-tool}: + +\beginlisting +    luaotfload-tool --find="Iwona" --inspect +\endlisting + +\endsubsection + +\beginsubsection {\LUATEX Programming} + +Another strategy that helps avoiding problems is to not access raw +\LUATEX internals directly. +% +Some of them, even though they are dangerous to access, have not been +overridden or disabled. +% +Thus, whenever possible prefer the functions in the \luafunction{aux} +namespace over direct manipulation of font objects. For example, raw +access to the \luafunction{font.fonts} table like: + +\beginlisting +    local somefont = font.fonts[2] +\endlisting + +\noindent can render already defined fonts unusable. +% +Instead, the function \luafunction{font.getfont()} should be used +because it has been replaced by a safe variant. + +However, \luafunction{font.getfont()} only covers fonts handled by the +font loader, e.~g. \identifier{OpenType} and \identifier{TrueType} +fonts, but not \abbrev{tfm} or \abbrev{ofm}. +% +Should you absolutely require access to all fonts known to \LUATEX, +including the virtual and autogenerated ones, then you need to query +both \luafunction{font.getfont()} and \luafunction{font.fonts}. +% +In this case, best define you own accessor: + +\beginlisting +    local unsafe_getfont = function (id) +        local tfmdata = font.getfont (id) +        if not tfmdata then +            tfmdata = font.fonts[id] +        end +        return tfmdata +    end + +    --- use like getfont() +    local somefont = unsafe_getfont (2) +\endlisting + +\endsubsection +\endsection + +\beginsection {License} + +\identifier {luaotfload} is licensed under the terms of the +\hyperlink [GNU General Public License version 2.0]% +           {https://www.gnu.org/licenses/old-licenses/gpl-2.0.html}. +Following the underlying fontloader code \identifier {luaotfload} +recognizes only that exact version as its license. +The „any later version” clause of the original license text as +copyrighted by the \hyperlink [Free Software Foundation]{http://www.fsf.org/} +\emphasis {does not apply} to either \identifier {luaotfload} or the +code imported from \CONTEXT. + +The complete text of the license is given as a separate file \fileent +{COPYING} in the toplevel directory of the +\hyperlink [\fileent {Luaotfload} Git repository]{https://github.com/lualatex/luaotfload/blob/master/COPYING}. +Distributions probably package it as \fileent +{doc/luatex/luaotfload/COPYING} in the relevant \fileent {texmf} tree. + +\endsection + +\endinput + diff --git a/luaotfload-tool.rst b/doc/luaotfload-tool.rst index 08acb6c..4b1a934 100644 --- a/luaotfload-tool.rst +++ b/doc/luaotfload-tool.rst @@ -6,21 +6,22 @@           generate and query the Luaotfload font names database  ----------------------------------------------------------------------- -:Date:      2014-05-18 -:Copyright: GPL v2.0 -:Version:   2.4-4 -:Manual section: 1 -:Manual group: text processing +:Date:                  2014-03-30 +:Copyright:             GPL v2.0 +:Version:               2.5 +:Manual section:        1 +:Manual group:          text processing  SYNOPSIS  ======================================================================= -**luaotfload-tool** [ -bDfFiIlnpquvVhw ] +**luaotfload-tool** [ -bcDfFiIlLnpqRSuvVhw ]  **luaotfload-tool** --update [ --force ] [ --quiet ] [ --verbose ]                               [ --prefer-texmf ] [ --dry-run ]                               [ --formats=[+|-]EXTENSIONS ]                               [ --no-compress ] [ --no-strip ] +                             [ --local ] [ --max-fonts=N ]  **luaotfload-tool** --find=FONTNAME [ --fuzzy ] [ --info ] [ --inspect ]                                      [ --no-reload ] @@ -31,6 +32,8 @@ SYNOPSIS  **luaotfload-tool** --list=CRITERION[:VALUE] [ --fields=F1,F2,...,Fn ] +**luaotfload-tool** --bisect=DIRECTIVE +  **luaotfload-tool** --help  **luaotfload-tool** --version @@ -49,10 +52,6 @@ the *Luaotfload* package. There are two general modes: **update** and  + **update**:  update the database or rebuild it entirely;  + **query**:   resolve a font name or display close matches. -Note that if the script is named ``mkluatexfontdb`` it will behave like -earlier versions (<=1.3) and always update the database first. Also, -the verbosity level will be set to 2. -  OPTIONS  ======================================================================= @@ -61,12 +60,15 @@ update mode  --update, -u            Update the database; indexes new fonts.  --force, -f             Force rebuilding of the database; re-indexes                          all fonts. +--local, -L             Include font files in ``$PWD``. This option +                        will cause large parts of the database to be +                        rebuilt. Thus it is quite inefficient. +                        Additionally, if local font files are found, +                        the database is prevented from being saved +                        to disk, so the local fonts need to be parsed +                        with every invocation of ``luaotfload-tool``.  --no-reload, -n         Suppress auto-updates to the database (e.g.                          when ``--find`` is passed an unknown name). ---no-strip              Do not strip redundant information after -                        building the database. Warning: this will -                        inflate the index to about two to three times -                        the normal size.  --no-compress, -c       Do not filter the plain text version of the                          font index through gzip. Useful for debugging                          if your editor is built without zlib. @@ -74,8 +76,6 @@ update mode  --prefer-texmf, -p      Organize the file name database in a way so                          that it prefer fonts in the *TEXMF* tree over                          system fonts if they are installed in both. ---max-fonts=N           Process at most *N* font files, including fonts -                        already indexed in the count.  --formats=EXTENSIONS    Extensions of the font files to index.                          Where *EXTENSIONS* is a comma-separated list of                          supported file extensions (otf, ttf, ttc, @@ -95,9 +95,6 @@ update mode                             grow the database considerably and slow down                             font indexing. ---dry-run, -D           Don’t load fonts, scan directories only. -                        (For debugging file system related issues.) -  query mode  -----------------------------------------------------------------------  --find=NAME             Resolve a font name; this looks up <name> in @@ -121,7 +118,6 @@ query mode                          library (assumes ``-I``). Automatically enabled                          if the verbosity level exceeds 2. ---show-blacklist, -b    Show blacklisted files (not directories).  --list=CRITERION        Show entries, where *CRITERION* is one of the                          following: @@ -183,6 +179,68 @@ font and lookup caches                             cache;                          3) ``show``  -> print stats. +debugging methods +----------------------------------------------------------------------- +--show-blacklist, -b    Show blacklisted files (not directories). +--dry-run, -D           Don’t load fonts when updating the database; +                        scan directories only. +                        (For debugging file system related issues.) +--no-strip              Do not strip redundant information after +                        building the database. Warning: this will +                        inflate the index to about two to three times +                        the normal size. +--max-fonts=N           Process at most *N* font files, including fonts +                        already indexed in the count. +--bisect=DIRECTIVE      Bisection of the font database. +                        This mode is intended as assistance in +                        debugging the Luatex engine, especially when +                        tracking memleaks or buggy fonts. + +                        *DIRECTIVE* can be one of the following: + +                        1) ``run`` -> Make ``luaotfload-tool`` respect +                           the bisection progress when running. +                           Combined with ``--update`` and possibly +                           ``--force`` this will only process the files +                           from the start up until the pivot and ignore +                           the rest. +                        2) ``start`` -> Start bisection: create a +                           bisection state file and initialize the low, +                           high, and pivot indices. +                        3) ``stop`` -> Terminate the current bisection +                           session by deleting the state file. +                        4) ``good`` | ``bad`` -> Mark the section +                           processed last as “good” or “bad”, +                           respectively. The next bisection step will +                           continue with the bad section. +                        5) ``status`` -> Print status information about +                           the current bisection session. Hint: Use +                           with higher verbosity settings for more +                           output. + +                        A bisection session is initiated by issuing the +                        ``start`` directive. This sets the pivot to the +                        middle of the list of available font files. +                        Now run *luaotfload-tool* with the ``--update`` +                        flag set as well as ``--bisect=run``: only the +                        fonts up to the pivot will be considered. If +                        that task exhibited the issue you are tracking, +                        then tell Luaotfload using ``--bisect=bad``. +                        The next step of ``--bisect=run`` will continue +                        bisection with the part of the files below the +                        pivot. +                        Likewise, issue ``--bisect=good`` in order to +                        continue with the fonts above the pivot, +                        assuming the tested part of the list did not +                        trigger the bug. + +                        Once the culprit font is tracked down, ``good`` +                        or ``bad`` will have no effect anymore. ``run`` +                        will always end up processing the single font +                        file that was left. +                        Use ``--bisect=stop`` to clear the bisection +                        state. +  miscellaneous  -----------------------------------------------------------------------  --verbose=N, -v         Set verbosity level to *n* or the number of @@ -192,12 +250,13 @@ miscellaneous                          troubleshooting), where *CHANNEL* can be                          1) ``stdout`` -> all output will be -                           dumped to the terminal; or +                           dumped to the terminal (default); or                          2) ``file`` -> write to a file to the temporary                             directory (the name will be chosen -                           automatically (**experimental!**). +                           automatically. ---version, -V           Show version info of components and exit. +--version, -V           Show version numbers of components as well as +                        some basic information and exit.  --help, -h              Show help message and exit.  --diagnose=CHECK        Run the diagnostic procedure *CHECK*. Available diff --git a/doc/luaotfload.conf.example b/doc/luaotfload.conf.example new file mode 100644 index 0000000..2756d62 --- /dev/null +++ b/doc/luaotfload.conf.example @@ -0,0 +1,30 @@ +;; Example configuration file for Luaotfload. This file contains the +;; defaults only, see luaotfload.conf(5) for more information. + +[db] +    compress        = true +    formats         = otf, ttf, ttc, dfont +    max-fonts       = 2.2517998136852e15 +    scan-local      = false +    skip-read       = false +    strip           = true +    update-live     = true + +[misc] +    statistics      = false +    termwidth       = nil + +[paths] +    cache-dir       = fonts +    names-dir       = names +    index-file      = luaotfload-names.lua +    lookups-file    = luaotfload-lookup-cache.lua + +[run] +    color-callback  = pre_linebreak_filter +    definer         = patch +    log-level       = 0 +    resolver        = cached + +;; vim:ft=dosini:et:sw=4:ts=8 + diff --git a/doc/luaotfload.conf.rst b/doc/luaotfload.conf.rst new file mode 100644 index 0000000..774095b --- /dev/null +++ b/doc/luaotfload.conf.rst @@ -0,0 +1,347 @@ +======================================================================= +                            luaotfload.conf +======================================================================= + +----------------------------------------------------------------------- +                     Luaotfload configuration file +----------------------------------------------------------------------- + +:Date:                  2014-06-09 +:Copyright:             GPL v2.0 +:Version:               2.5 +:Manual section:        5 +:Manual group:          text processing + +SYNOPSIS +======================================================================= + +- **./luaotfload{.conf,rc}** +- **XDG_CONFIG_HOME/luaotfload/luaotfload{.conf,rc}** +- **~/.luaotfloadrc** + +DESCRIPTION +======================================================================= + +The file ``luaotfload.conf`` contains configuration options for +*Luaotfload*, a font loading and font management component for LuaTeX. + + +EXAMPLE +======================================================================= + +A small Luaotfload configuration file with few customizations could +look as follows: :: + +    [db] +        formats = afm, pfa, pfb +        compress = false +     +    [misc] +        termwidth = 60 +     +    [run] +        log-level = 6 + +This will make Luaotfload ignore all font files except for PostScript +formats. NB: With a default Tex Live install the PS fonts will take +much longer to index than OpenType or TrueType ones. Also, an +uncompressed index file will be dumped which is going to be much larger +due to the huge amount of PostScript fonts indexed. The terminal width +is truncated to 60 characters which influences the verbose output +during indexing. Finally, the verbosity is increased greatly: each font +file being processed will be printed to the stdout on a separate line, +along with lots of other information. + +To observe the difference in behavior, save above snippet to +``./luaotfload.conf`` and update the font index: :: + +    luaotfload --update --force + + +SYNTAX +======================================================================= + +The configuration file syntax follows the common INI format. For a more +detailed description please refer to the section “CONFIGURATION FILE” +of **git-config**\(1). A brief list of rules is given below: + +  * Blank lines and lines starting with a semicolon (``;``) are ignored. + +  * A configuration file is partitioned into sections that are declared +    by specifying the section title in brackets on a separate line: :: + +        [some-section] +        ... section content ... + +  * Sections consist of one or more variable assignments of the form +    ``variable-name = value``  E. g.:: + +        [foo] +            bar = baz +            quux = xyzzy +            ... + +  * Section and variable names may contain only uppercase and lowercase +    letters as well as dashes (``-``). + + +VARIABLES +======================================================================= + +Variables in belong into a configuration section and their values must +be of a certain type. Some of them have further constraints. For +example, the “color callback” must be a string of one of the values +``pre_linebreak_filter`` or ``pre_output_filter``, defined in the +section *run*. + +Currently, the configuration is organized into four sections: + +db +    Options relating to the font index. + +misc +    Options without a clearly defined category. + +paths +    Path and file name settings. + +run +    Options controlling runtime behavior of Luaotfload. + +The list of valid variables, the sections they are part of and their +type is given below. Types represent Lua types that the values must be +convertible to; they are abbreviated as follows: ``s`` for the *string* +type, ``n`` for *number*, ``b`` for *boolean*. A value of ``nil`` means +the variable is unset. + + +Section ``db`` +----------------------------------------------------------------------- + ++---------------+--------+---------------------------+ +|  variable     |  type  |  default                  | ++---------------+--------+---------------------------+ +|  compress     |   b    |  ``true``                 | ++---------------+--------+---------------------------+ +|  formats      |   s    |  ``"otf,ttf,ttc,dfont"``  | ++---------------+--------+---------------------------+ +|  max-fonts    |   n    |  ``2^51``                 | ++---------------+--------+---------------------------+ +|  scan-local   |   b    |  ``false``                | ++---------------+--------+---------------------------+ +|  skip-read    |   b    |  ``false``                | ++---------------+--------+---------------------------+ +|  strip        |   b    |  ``true``                 | ++---------------+--------+---------------------------+ +|  update-live  |   b    |  ``true``                 | ++---------------+--------+---------------------------+ + +The flag ``compress`` determines whether the font index (usually +``luaotfload-names.lua[.gz]`` will be stored in compressed forms. +If unset it is equivalent of passing ``--no-compress`` to +**luaotfload-tool**. Since the file is only created for convenience +and has no effect on the runtime behavior of Luaotfload, the flag +should remain set. Most editors come with zlib support anyways. + +The list of ``formats`` must be a comma separated sequence of strings +containing one or more of these elements: + +* ``otf``               (OpenType format), +* ``ttf`` and ``ttc``   (TrueType format), +* ``dfont``             (Macintosh TrueType format), +* ``afm``               (Adobe Font Metrics), +* ``pfb`` and ``pfa``   (PostScript format). + +It corresponds loosely to the ``--formats`` option to +**luaotfload-tool**. Invalid or duplicate members are ignored; if the +list does not contain any useful identifiers, the default list +``"otf,ttf,ttc,dfont"`` will be used. + +The variable ``max-fonts`` determines after processing how many font +files the font scanner will terminate the search. This is useful for +debugging issues with the font index and has the same effect as the +option ``--max-fonts`` to **luaotfload-tools**. + +The ``scan-local`` flag, if set, will incorporate the current working +directory as a font search location. NB: This will potentially slow +down document processing because a font index with local fonts will not +be saved to disk, so these fonts will have to be re-indexed whenever +the document is built. + +The ``skip-read`` flag is only useful for debugging: It makes +Luaotfload skip reading fonts. The font information for rebuilding the +index is taken from the presently existing one. + +Unsetting the ``strip`` flag prevents Luaotfload from removing data +from the index that is only useful when processing font files. NB: this +can increase the size of the index files significantly and has no +effect on the runtime behavior. + +If ``update-live`` is set, Luaotfload will reload the database if it +cannot find a requested font. Those who prefer to update manually using +**luaotfload-tool** should unset this flag. + + +Section ``default-features`` +----------------------------------------------------------------------- + +By default Luaotfload enables ``node`` mode and picks the default font +features that are prescribed in the OpenType standard. This behavior +may be overridden in the ``default-features`` section. Global defaults +that will be applied for all scripts can be set via the ``global`` +option, others by the script they are to be applied to. For example, +a setting of :: + +  [default-features] +      global = mode=base,color=0000FF +      dflt   = smcp,onum + +would force *base* mode, tint all fonts blue and activate small +capitals and text figures globally. Features are specified as a comma +separated list of variables or variable-value pairs. Variables without +an explicit value are set to ``true``. + + +Section ``misc`` +----------------------------------------------------------------------- + ++---------------+--------+-------------------------+ +|  variable     |  type  |  default                | ++---------------+--------+-------------------------+ +|  statistics   |   b    |  ``false``              | ++---------------+--------+-------------------------+ +|  termwidth    |   n    |  ``nil``                | ++---------------+--------+-------------------------+ +|  version      |   s    |  <Luaotfload version>   | ++---------------+--------+-------------------------+ + +With ``statistics`` enabled, extra statistics will be collected during +index creation and appended to the index file. It may then be queried +at the Lua end or inspected by reading the file itself. + +The value of ``termwidth``, if set, overrides the value retrieved by +querying the properties of the terminal in which Luatex runs. This is +useful if the engine runs with ``shell_escape`` disabled and the actual +terminal dimensions cannot be retrieved. + +The value of ``version`` is derived from the version string hard-coded +in the Luaotfload source. Override at your own risk. + + +Section ``paths`` +----------------------------------------------------------------------- + ++------------------+--------+------------------------------------+ +|  variable        |  type  |  default                           | ++------------------+--------+------------------------------------+ +|  cache-dir       |   s    |  ``"fonts"``                       | ++------------------+--------+------------------------------------+ +|  names-dir       |   s    |  ``"names"``                       | ++------------------+--------+------------------------------------+ +|  index-file      |   s    |  ``"luaotfload-names.lua"``        | ++------------------+--------+------------------------------------+ +|  lookups-file    |   s    |  ``"luaotfload-lookup-cache.lua"`` | ++------------------+--------+------------------------------------+ + +The paths ``cache-dir`` and ``names-dir`` determine the subdirectory +inside the Luaotfload subtree of the ``luatex-cache`` directory where +the font cache and the font index will be stored, respectively. + +Inside the index directory, the names of the index file and the font +lookup cache will be derived from the respective values of +``index-file`` and ``lookups-file``. This is the filename base for the +bytecode compiled version as well as -- for the index -- the gzipped +version. + + +Section ``run`` +----------------------------------------------------------------------- + ++------------------+--------+------------------------------+ +|  variable        |  type  |  default                     | ++------------------+--------+------------------------------+ +|  color-callback  |   s    |  ``"pre_linebreak_filter"``  | ++------------------+--------+------------------------------+ +|  definer         |   s    |  ``"patch"``                 | ++------------------+--------+------------------------------+ +|  log-level       |   n    |  ``0``                       | ++------------------+--------+------------------------------+ +|  resolver        |   s    |  ``"cached"``                | ++------------------+--------+------------------------------+ + +The ``color-callback`` option determines the stage at which fonts that +defined with a ``color=xxyyzz`` feature will be colorized. By default +this happens in a ``pre_linebreak_filter`` but alternatively the +``pre_output_filter`` may be chosen, which is faster but might produce +inconsistent output. The latter also was the default in the 1.x series +of Luaotfload. + +The ``definer`` allows for switching the ``define_font`` callback. +Apart from the default ``patch`` one may also choose the ``generic`` +one that comes with the vanilla fontloader. Beware that this might +break tools like Fontspect that rely on the ``patch_font`` callback +provided by Luaotfload to perform important corrections on font data. + +The value of ``log-level`` sets the default verbosity of messages +printed by Luaotfload. Only messages defined with a verbosity of less +than or equal to the supplied value will be output on the terminal. +At a log level of five Luaotfload can be very noisy. Also, printing too +many messages will slow down the interpreter due to line buffering +being disabled (see **setbuf**\(3)). + +The ``resolver`` setting allows choosing the font name resolution +function: With the default value ``cached`` Luaotfload saves the result +of a successful font name request to a cache file to speed up +subsequent lookups. The alternative, ``normal`` circumvents the cache +and resolves every request individually. (Since to the restructuring of +the font name index in Luaotfload 2.4 the performance difference +between the cached and uncached lookups should be marginal.) + + +FILES +======================================================================= + +Luaotfload only processes the first configuration file it encounters at +one of the search locations. The file name may be either +``luaotfload.conf`` or ``luaotfloadrc``, except for the dotfile in the +user’s home directory which is expected at ``~/.luaotfloadrc``. + +Configuration files are located following a series of steps. The search +terminates as soon as a suitable file is encountered. The sequence of +locations that Luaotfload looks at is + +i.    The current working directory of the LuaTeX process. +ii.   The subdirectory ``luaotfload/`` inside the XDG configuration +      tree, e. g. ``/home/oenothea/config/luaotfload/``. +iii.  The dotfile. +iv.   The *TEXMF* (using kpathsea). + + +SEE ALSO +======================================================================= + +**luaotfload-tool**\(1), **luatex**\(1), **lua**\(1) + +* ``texdoc luaotfload`` to display the PDF manual for the *Luaotfload* +  package +* Luaotfload development `<https://github.com/lualatex/luaotfload>`_ +* LuaLaTeX mailing list  `<http://tug.org/pipermail/lualatex-dev/>`_ +* LuaTeX                 `<http://luatex.org/>`_ +* Luaotfload on CTAN     `<http://ctan.org/pkg/luaotfload>`_ + + +REFERENCES +======================================================================= + +* The XDG base specification +  `<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_. + +AUTHORS +======================================================================= + +*Luaotfload* is maintained by the LuaLaTeX dev team +(`<https://github.com/lualatex/>`_). + +This manual page was written by Philipp Gesang +<philipp.gesang@alumni.uni-heidelberg.de>. + diff --git a/luaotfload-blacklist.cnf b/luaotfload-blacklist.cnf deleted file mode 100644 index 5c03dc2..0000000 --- a/luaotfload-blacklist.cnf +++ /dev/null @@ -1,12 +0,0 @@ -% Takes ages to load -LastResort.ttf % a MacOSX font, but also available for free from unicode.org  -% Segfaults under LuaTeX 0.76 -lingoes.ttf -% http://tug.org/pipermail/luatex/2013-May/004239.html -Diablindall.ttf -spltfgbd.ttf -spltfgbi.ttf -spltfgit.ttf -spltfgrg.ttf -% Buggy Max OS font, see https://github.com/lualatex/luaotfload/issues/139 -Skia.ttf diff --git a/luaotfload-legacy-attributes.lua b/luaotfload-legacy-attributes.lua deleted file mode 100644 index c6130b4..0000000 --- a/luaotfload-legacy-attributes.lua +++ /dev/null @@ -1,27 +0,0 @@ ------------------------------------------------------------------------ ---         FILE:  otfl-luat-att.lua ---        USAGE:  with old luaotfload ---  DESCRIPTION:  setting attributes abide luatexbase rules --- REQUIREMENTS:  some old luatex ---       AUTHOR:  Philipp Gesang (Phg), <phg42.2a@gmail.com> ---      CREATED:  2013-05-10 20:37:19+0200 ------------------------------------------------------------------------ --- - -if not modules then modules = { } end modules ['otfl-luat-att'] = { -    version   = math.pi/42, -    comment   = "companion to luaotfload.lua", -    author    = "Philipp Gesang", -    copyright = "Luaotfload Development Team", -    license   = "GNU GPL v2" -} - -function attributes.private(name) -    local attr   = "otfl@" .. name -    local number = luatexbase.attributes[attr] -    if not number then -        number = luatexbase.new_attribute(attr) -    end -    return number -end - diff --git a/luaotfload-legacy-database.lua b/luaotfload-legacy-database.lua deleted file mode 100644 index b31fe88..0000000 --- a/luaotfload-legacy-database.lua +++ /dev/null @@ -1,724 +0,0 @@ -if not modules then modules = { } end modules ['font-nms'] = { -    version   = "old", -    comment   = "companion to luaotfload.lua", -    author    = "Khaled Hosny and Elie Roux", -    copyright = "Luaotfload Development Team", -    license   = "GNU GPL v2" -} - -fonts                = fonts       or { } -fonts.names          = fonts.names or { } - -local names          = fonts.names -local names_dir      = "luatex-cache/generic/names" -names.version        = "old" -- not the same as in context -names.data           = nil -names.path           = { -    basename  = "otfl-names.lua", --- different from current -    localdir  = file.join(kpse.expand_var("$TEXMFVAR"),    names_dir), -    systemdir = file.join(kpse.expand_var("$TEXMFSYSVAR"), names_dir), -} - - -local splitpath, expandpath = file.split_path, kpse.expand_path -local glob, basename        = dir.glob, file.basename -local upper, lower, format  = string.upper, string.lower, string.format -local gsub, match, rpadd    = string.gsub, string.match, string.rpadd -local gmatch, sub, find     = string.gmatch, string.sub, string.find -local utfgsub               = unicode.utf8.gsub - -local trace_short    = false --tracing adapted to rebuilding of the database inside a document -local trace_search   = false --trackers.register("names.search",   function(v) trace_search   = v end) -local trace_loading  = false --trackers.register("names.loading",  function(v) trace_loading  = v end) - -local function sanitize(str) -    if str then -        return utfgsub(lower(str), "[^%a%d]", "") -    else -        return str -- nil -    end -end - -local function fontnames_init() -    return { -        mappings  = { }, -        status    = { }, -        version   = names.version, -    } -end - -local function load_names() -    local localpath  = file.join(names.path.localdir, names.path.basename) -    local systempath = file.join(names.path.systemdir, names.path.basename) -    local kpsefound  = kpse.find_file(names.path.basename) -    local foundname -    local data -    if kpsefound and file.isreadable(kpsefound) then -        data = dofile(kpsefound) -	foundname = kpsefound -    elseif file.isreadable(localpath)  then -        data = dofile(localpath) -	foundname = localpath -    elseif file.isreadable(systempath) then -        data = dofile(systempath) -	foundname = systempath -    end -    if data then -        logs.info("Font names database loaded: " .. foundname) -    else -        logs.info([[Font names database not found, generating new one. -             This can take several minutes; please be patient.]]) -        data = names.update(fontnames_init()) -        names.save(data) -    end -    return data -end - -local synonyms = { -    regular    = { "normal", "roman", "plain", "book", "medium" }, -    -- boldregular was for old versions of Linux Libertine, is it still useful? -    -- semibold is in new versions of Linux Libertine, but there is also a bold, -    -- not sure it's useful here... -    bold       = { "demi", "demibold", "semibold", "boldregular" }, -    italic     = { "regularitalic", "normalitalic", "oblique", "slanted" }, -    bolditalic = { "boldoblique", "boldslanted", "demiitalic", "demioblique", "demislanted", "demibolditalic", "semibolditalic" }, -} - -local loaded   = false -local reloaded = false - -function names.resolve(specification) -    local name  = sanitize(specification.name) -    local style = sanitize(specification.style) or "regular" - -    local size -    if specification.optsize then -        size = tonumber(specification.optsize) -    elseif specification.size then -        size = specification.size / 65536 -    end - - -    if not loaded then -        names.data = names.load() -        loaded     = true -    end - -    local data = names.data -    if type(data) == "table" and data.version == names.version then -        if data.mappings then -            local found = { } -            for _,face in next, data.mappings do -                local family, subfamily, fullname, psname -                local optsize, dsnsize, maxsize, minsize - -                if face.names then -                    family    = sanitize(face.names.family) -                    subfamily = sanitize(face.names.subfamily) -                    fullname  = sanitize(face.names.fullname) -                    psname    = sanitize(face.names.psname) -                end -                local fontname  = sanitize(face.fontname) -                local pfullname = sanitize(face.fullname) -                if #face.size > 0 then -                    optsize = face.size -                    dsnsize = optsize[1] and optsize[1] / 10 -                    -- can be nil -                    maxsize = optsize[2] and optsize[2] / 10 or dsnsize -                    minsize = optsize[3] and optsize[3] / 10 or dsnsize -                end -                if name == family then -                    if subfamily == style then -                        if optsize then -                            if dsnsize == size -                            or (size > minsize and size <= maxsize) then -                                found[1] = face -                                break -                            else -                                found[#found+1] = face -                            end -                        else -                            found[1] = face -                            break -                        end -                    elseif synonyms[style] and -                           table.contains(synonyms[style], subfamily) then -                        if optsize then -                            if dsnsize == size -                            or (size > minsize and size <= maxsize) then -                                found[1] = face -                                break -                            else -                                found[#found+1] = face -                            end -                        else -                            found[1] = face -                            break -                        end -                    elseif subfamily == "regular" or -                           table.contains(synonyms.regular, subfamily) then -                        found.fallback = face -                    elseif name == fullname -                    or name == pfullname -                    or name == fontname -                    or name == psname then -                        if optsize then -                            if dsnsize == size -                            or (size > minsize and size <= maxsize) then -                                found[1] = face -                                break -                            else -                                found[#found+1] = face -                            end -                        else -                            found[1] = face -                            break -                        end -                    end -                else -                    if name == fullname -                    or name == pfullname -                    or name == fontname -                    or name == psname then -                        if optsize then -                            if dsnsize == size -                            or (size > minsize and size <= maxsize) then -                                found[1] = face -                                break -                            else -                                found[#found+1] = face -                            end -                        else -                            found[1] = face -                            break -                        end -                    end -                end -            end -            if #found == 1 then -                if kpse.lookup(found[1].filename[1]) then -                    logs.report("load font", -                                "font family='%s', subfamily='%s' found: %s", -                                name, style, found[1].filename[1]) -                    return found[1].filename[1], found[1].filename[2] -                elseif lfs.isfile(found[1].found_at) then -                    logs.report("load font", -                                "font family='%s', subfamily='%s' found: %s", -                                name, style, found[1].found_at) -                    return found[1].found_at, found[1].filename[2] -                end -            elseif #found > 1 then -                -- we found matching font(s) but not in the requested optical -                -- sizes, so we loop through the matches to find the one with -                -- least difference from the requested size. -                local closest -                local least = math.huge -- initial value is infinity -                for i,face in next, found do -                    local dsnsize    = face.size[1]/10 -                    local difference = math.abs(dsnsize-size) -                    if difference < least then -                        closest = face -                        least   = difference -                    end -                end -                if kpse.lookup(closest.filename[1]) then -                    logs.report("load font", -                                "font family='%s', subfamily='%s' found: %s", -                                name, style, closest.filename[1]) -                    return closest.filename[1], closest.filename[2] -                elseif lfs.isfile(closest.found_at) then -                    logs.report("load font", -                                "font family='%s', subfamily='%s' found: %s", -                                name, style, closest.found_at) -                    return closest.found_at, closest.filename[2] -                end -            elseif found.fallback then -                return found.fallback.filename[1], found.fallback.filename[2] -            end -            -- no font found so far -            if not reloaded then -                -- try reloading the database -                names.data = names.update(names.data) -                names.save(names.data) -                reloaded   = true -                return names.resolve(specification) -            else -                -- else, fallback to filename -                return specification.name, false -            end -        end -    else -        if not reloaded then -            names.data = names.update() -            names.save(names.data) -            reloaded   = true -            return names.resolve(specification) -        else -            return specification.name, false -        end -    end -end - -names.resolvespec = names.resolve - -function names.set_log_level(level) -    if level == 2 then -        trace_loading = true -    elseif level >= 3 then -        trace_loading = true -        trace_search = true -    end -end - -local lastislog = 0 - -local function log(fmt, ...) -    lastislog = 1 -    texio.write_nl(format("luaotfload | %s", format(fmt,...))) -    io.flush() -end - -logs        = logs or { } -logs.report = logs.report or log -logs.info   = logs.info or log - -local function font_fullinfo(filename, subfont, texmf) -    local found_at = filename -    local t = { } -    local f = fontloader.open(filename, subfont) -    if not f then -	    if trace_loading then -        	logs.report("error: failed to open %s", filename) -	    end -        return -    end -    local m = fontloader.to_table(f) -    fontloader.close(f) -    collectgarbage('collect') -    -- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size -    if m.fontstyle_name then -        for _,v in next, m.fontstyle_name do -            if v.lang == 1033 then -                t.fontstyle_name = v.name -            end -        end -    end -    if m.names then -        for _,v in next, m.names do -            if v.lang == "English (US)" then -                t.names = { -                    -- see -                    -- http://developer.apple.com/textfonts/ -                    -- TTRefMan/RM06/Chap6name.html -                    fullname = v.names.compatfull     or v.names.fullname, -                    family   = v.names.preffamilyname or v.names.family, -                    subfamily= t.fontstyle_name       or v.names.prefmodifiers  or v.names.subfamily, -                    psname   = v.names.postscriptname -                } -            end -        end -    else -        -- no names table, propably a broken font -        if trace_loading then -            logs.report("broken font rejected: %s", basefile) -        end -        return -    end -    t.fontname    = m.fontname -    t.fullname    = m.fullname -    t.familyname  = m.familyname -    t.filename    = { texmf and basename(filename) or filename, subfont } -    t.weight      = m.pfminfo.weight -    t.width       = m.pfminfo.width -    t.slant       = m.italicangle -    -- don't waste the space with zero values -    t.size = { -        m.design_size         ~= 0 and m.design_size         or nil, -        m.design_range_top    ~= 0 and m.design_range_top    or nil, -        m.design_range_bottom ~= 0 and m.design_range_bottom or nil, -    } -    -- rather, waste space on paths -    t.found_at = found_at -    return t -end - -local function load_font(filename, fontnames, newfontnames, texmf) -    local newmappings = newfontnames.mappings -    local newstatus   = newfontnames.status -    local mappings    = fontnames.mappings -    local status      = fontnames.status -    local basefile    = texmf and basename(filename) or filename -    if filename then -        if table.contains(names.blacklist, filename) or -           table.contains(names.blacklist, basename(filename)) then -            if trace_search then -                logs.report("ignoring font '%s'", filename) -            end -            return -        end -        local timestamp, db_timestamp -        db_timestamp        = status[basefile] and status[basefile].timestamp -        timestamp           = lfs.attributes(filename, "modification") - -        local index_status = newstatus[basefile] or (not texmf and newstatus[basename(filename)]) -        if index_status and index_status.timestamp == timestamp then -            -- already indexed this run -            return -        end - -        newstatus[basefile] = newstatus[basefile] or { } -        newstatus[basefile].timestamp = timestamp -        newstatus[basefile].index     = newstatus[basefile].index or { } - -        if db_timestamp == timestamp and not newstatus[basefile].index[1] then -            for _,v in next, status[basefile].index do -                local index = #newstatus[basefile].index -                newmappings[#newmappings+1]        = mappings[v] -                newstatus[basefile].index[index+1] = #newmappings -            end -            if trace_loading then -                logs.report("font already indexed: %s", basefile) -            end -            return -        end -        local info = fontloader.info(filename) -        if info then -            if type(info) == "table" and #info > 1 then -                for i in next, info do -                    local fullinfo = font_fullinfo(filename, i-1, texmf) -                    if not fullinfo then -                        return -                    end -                    local index = newstatus[basefile].index[i] -                    if not index then -                        index = #newmappings+1 -                    end -                    newmappings[index]           = fullinfo -                    newstatus[basefile].index[i] = index -                end -            else -                local fullinfo = font_fullinfo(filename, false, texmf) -                if not fullinfo then -                    return -                end -                local index = newstatus[basefile].index[1] -                if not index then -                    index = #newmappings+1 -                end -                newmappings[index]           = fullinfo -                newstatus[basefile].index[1] = index -            end -        else -            if trace_loading then -               logs.report("failed to load %s", basefile) -            end -        end -    end -end - -local function path_normalize(path) -    --[[ -        path normalization: -        - a\b\c  -> a/b/c -        - a/../b -> b -        - /cygdrive/a/b -> a:/b -        - reading symlinks under non-Win32 -        - using kpse.readable_file on Win32 -    ]] -    if os.type == "windows" or os.type == "msdos" then -        path = path:gsub('\\', '/') -        path = path:lower() -        path = path:gsub('^/cygdrive/(%a)/', '%1:/') -    end -    if os.type ~= "windows" and os.type ~= "msdos" then -        local dest = lfs.readlink(path) -        if dest then -            if kpse.readable_file(dest) then -                path = dest -            elseif kpse.readable_file(file.join(file.dirname(path), dest)) then -                path = file.join(file.dirname(path), dest) -            else -                -- broken symlink? -            end -        end -    end -    path = file.collapse_path(path) -    return path -end - -fonts.path_normalize = path_normalize - -names.blacklist = { } - -local function read_blacklist() -    local files = { -        kpse.lookup("otfl-blacklist.cnf", {all=true, format="tex"}) -    } -    local blacklist = names.blacklist - -    if files and type(files) == "table" then -        for _,v in next, files do -            for line in io.lines(v) do -                line = line:strip() -- to get rid of lines like " % foo" -                if line:find("^%%") or line:is_empty() then -                    -- comment or empty line -                else -                    line = line:split("%")[1] -                    line = line:strip() -                    if trace_search then -                        logs.report("blacklisted file: %s", line) -                    end -                    blacklist[#blacklist+1] = line -                end -            end -        end -    end -end - -local font_extensions = { "otf", "ttf", "ttc", "dfont" } - -local function scan_dir(dirname, fontnames, newfontnames, texmf) -    --[[ -    This function scans a directory and populates the list of fonts -    with all the fonts it finds. -    - dirname is the name of the directory to scan -    - names is the font database to fill -    - texmf is a boolean saying if we are scanning a texmf directory -    ]] -    local list, found = { }, { } -    local nbfound = 0 -    if trace_search then -        logs.report("scanning '%s'", dirname) -    end -    for _,i in next, font_extensions do -        for _,ext in next, { i, upper(i) } do -            found = glob(format("%s/**.%s$", dirname, ext)) -            -- note that glob fails silently on broken symlinks, which happens -            -- sometimes in TeX Live. -            if trace_search then -                logs.report("%s '%s' fonts found", #found, ext) -            end -            nbfound = nbfound + #found -            table.append(list, found) -        end -    end -    if trace_search then -        logs.report("%d fonts found in '%s'", nbfound, dirname) -    end - -    for _,file in next, list do -        file = path_normalize(file) -        if trace_loading then -            logs.report("loading font: %s", file) -        end -        load_font(file, fontnames, newfontnames, texmf) -    end -end - -local function scan_texmf_fonts(fontnames, newfontnames) -    --[[ -    This function scans all fonts in the texmf tree, through kpathsea -    variables OPENTYPEFONTS and TTFONTS of texmf.cnf -    ]] -    if expandpath("$OSFONTDIR"):is_empty() then -        logs.info("Scanning TEXMF fonts...") -    else -        logs.info("Scanning TEXMF and OS fonts...") -    end -    local fontdirs = expandpath("$OPENTYPEFONTS"):gsub("^%.", "") -    fontdirs = fontdirs .. expandpath("$TTFONTS"):gsub("^%.", "") -    if not fontdirs:is_empty() then -        for _,d in next, splitpath(fontdirs) do -            scan_dir(d, fontnames, newfontnames, true) -        end -    end -end - ---[[ -  For the OS fonts, there are several options: -   - if OSFONTDIR is set (which is the case under windows by default but -     not on the other OSs), it scans it at the same time as the texmf tree, -     in the scan_texmf_fonts. -   - in addition: -     - under Windows and Mac OSX, we take a look at some hardcoded directories -     - under Unix, we read /etc/fonts/fonts.conf and read the directories in it - -  This means that if you have fonts in fancy directories, you need to set them -  in OSFONTDIR if they cannot be found by fontconfig. -]] - -local function read_fonts_conf(path, results, passed_paths) -    --[[ -    This function parses /etc/fonts/fonts.conf and returns all the dir it finds. -    The code is minimal, please report any error it may generate. -    ]] -    local f = io.open(path) -    table.insert(passed_paths, path) -    if not f then -        logs.info("Warning: unable to read "..path.. ", skipping...") -        return results -    end -    local incomments = false -    for line in f:lines() do -        while line and line ~= "" do -            -- spaghetti code... hmmm... -            if incomments then -                local tmp = find(line, '-->') -                if tmp then -                    incomments = false -                    line = sub(line, tmp+3) -                else -                    line = nil -                end -            else -                local tmp = find(line, '<!--') -                local newline = line -                if tmp then -                    -- for the analysis, we take everything that is before the -                    -- comment sign -                    newline = sub(line, 1, tmp-1) -                    -- and we loop again with the comment -                    incomments = true -                    line = sub(line, tmp+4) -                else -                    -- if there is no comment start, the block after that will -                    -- end the analysis, we exit the while loop -                    line = nil -                end -                for dir in gmatch(newline, '<dir>([^<]+)</dir>') do -                    -- now we need to replace ~ by kpse.expand_path('~') -                    if sub(dir, 1, 1) == '~' then -                        dir = file.join(kpse.expand_path('~'), sub(dir, 2)) -                    end -                    -- we exclude paths with texmf in them, as they should be -                    -- found anyway -                    if not find(dir, 'texmf') then -                        results[#results+1] = dir -                    end -                end -                for include in gmatch(newline, '<include[^<]*>([^<]+)</include>') do -                    -- include here can be four things: a directory or a file, -                    -- in absolute or relative path. -                    if sub(include, 1, 1) == '~' then -                        include = file.join(kpse.expand_path('~'),sub(include, 2)) -                        -- First if the path is relative, we make it absolute: -                    elseif not lfs.isfile(include) and not lfs.isdir(include) then -                        include = file.join(file.dirname(path), include) -                    end -                    if lfs.isfile(include) and kpse.readable_file(include) and not table.contains(passed_paths, include) then -                        -- we exclude path with texmf in them, as they should -                        -- be found otherwise -                        read_fonts_conf(include, results, passed_paths) -                    elseif lfs.isdir(include) then -                        for _,f in next, glob(file.join(include, "*.conf")) do -                            if not table.contains(passed_paths, f) then -                                read_fonts_conf(f, results, passed_paths) -                            end -                        end -                    end -                end -            end -        end -    end -    f:close() -    return results -end - --- for testing purpose -names.read_fonts_conf = read_fonts_conf - -local function get_os_dirs() -    if os.name == 'macosx' then -        return { -            file.join(kpse.expand_path('~'), "Library/Fonts"), -            "/Library/Fonts", -            "/System/Library/Fonts", -            "/Network/Library/Fonts", -        } -    elseif os.type == "windows" or os.type == "msdos" then -        local windir = os.getenv("WINDIR") -        return { file.join(windir, 'Fonts') } -    else -        return read_fonts_conf("/etc/fonts/fonts.conf", {}, {}) -    end -end - -local function scan_os_fonts(fontnames, newfontnames) -    --[[ -    This function scans the OS fonts through -      - fontcache for Unix (reads the fonts.conf file and scans the directories) -      - a static set of directories for Windows and MacOSX -    ]] -    logs.info("Scanning OS fonts...") -    if trace_search then -        logs.info("Searching in static system directories...") -    end -    for _,d in next, get_os_dirs() do -        scan_dir(d, fontnames, newfontnames, false) -    end -end - -local function update_names(fontnames, force) -    --[[ -    The main function, scans everything -    - fontnames is the final table to return -    - force is whether we rebuild it from scratch or not -    ]] -    logs.info("Updating the font names database:") - -    if force then -        fontnames = fontnames_init() -    else -        if not fontnames then -            fontnames = names.load() -        end -        if fontnames.version ~= names.version then -            fontnames = fontnames_init() -            if trace_search then -                logs.report("No font names database or old one found; " -                          .."generating new one") -            end -        end -    end -    local newfontnames = fontnames_init() -    read_blacklist() -    scan_texmf_fonts(fontnames, newfontnames) -    scan_os_fonts(fontnames, newfontnames) -    return newfontnames -end - -local function save_names(fontnames) -    local savepath  = names.path.localdir -    if not lfs.isdir(savepath) then -        dir.mkdirs(savepath) -    end -    savepath = file.join(savepath, names.path.basename) -    if file.iswritable(savepath) then -        table.tofile(savepath, fontnames, true) -        logs.info("Font names database saved: %s \n", savepath) -        return savepath -    else -        logs.info("Failed to save names database\n") -        return nil -    end -end - -local function scan_external_dir(dir) -    local old_names, new_names -    if loaded then -        old_names = names.data -    else -        old_names = names.load() -        loaded    = true -    end -    new_names = table.copy(old_names) -    scan_dir(dir, old_names, new_names) -    names.data = new_names -end - -names.scan   = scan_external_dir -names.load   = load_names -names.update = update_names -names.save   = save_names - --- vim:ft=lua:sw=4:ts=4:expandtab diff --git a/luaotfload-legacy-merged.lua b/luaotfload-legacy-merged.lua deleted file mode 100644 index 9bec298..0000000 --- a/luaotfload-legacy-merged.lua +++ /dev/null @@ -1,8157 +0,0 @@ --- merged file : luaotfload-legacy-merged.lua --- parent file : luaotfload-legacy.lua --- merge date  : Fri May 10 20:57:35 2013 - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['luat-dum']={ -  version=1.100, -  comment="companion to luatex-*.tex", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local dummyfunction=function() end -statistics={ -  register=dummyfunction, -  starttiming=dummyfunction, -  stoptiming=dummyfunction, -} -directives={ -  register=dummyfunction, -  enable=dummyfunction, -  disable=dummyfunction, -} -trackers={ -  register=dummyfunction, -  enable=dummyfunction, -  disable=dummyfunction, -} -experiments={ -  register=dummyfunction, -  enable=dummyfunction, -  disable=dummyfunction, -} -storage={ -  register=dummyfunction, -  shared={}, -} -logs={ -  report=dummyfunction, -  simple=dummyfunction, -} -tasks={ -  new=dummyfunction, -  actions=dummyfunction, -  appendaction=dummyfunction, -  prependaction=dummyfunction, -} -callbacks={ -  register=function(n,f) return callback.register(n,f) end, -} -texconfig.kpse_init=true -resolvers=resolvers or {}  -local remapper={ -  otf="opentype fonts", -  ttf="truetype fonts", -  ttc="truetype fonts", -  dfont="truetype fonts", -  cid="cid maps", -  fea="font feature files", -} -function resolvers.find_file(name,kind) -  name=string.gsub(name,"\\","/") -  kind=string.lower(kind) -  return kpse.find_file(name,(kind and kind~="" and (remapper[kind] or kind)) or file.extname(name,"tex")) -end -function resolvers.findbinfile(name,kind) -  if not kind or kind=="" then -    kind=file.extname(name)  -  end -  return resolvers.find_file(name,(kind and remapper[kind]) or kind) -end -caches={} -local writable,readables=nil,{} -if not caches.namespace or caches.namespace=="" or caches.namespace=="context" then -  caches.namespace='generic' -end -do -  local cachepaths -  if kpse.expand_var('$TEXMFCACHE')~='$TEXMFCACHE' then -    cachepaths=kpse.expand_var('$TEXMFCACHE') -  elseif kpse.expand_var('$TEXMFVAR')~='$TEXMFVAR' then -    cachepaths=kpse.expand_var('$TEXMFVAR') -  end -  if not cachepaths then -    cachepaths="." -  end -  cachepaths=string.split(cachepaths,os.type=="windows" and ";" or ":") -  for i=1,#cachepaths do -    local done -    writable=file.join(cachepaths[i],"luatex-cache") -    writable=file.join(writable,caches.namespace) -    writable,done=dir.mkdirs(writable) -    if done then -      break -    end -  end -  for i=1,#cachepaths do -    if file.isreadable(cachepaths[i]) then -      readables[#readables+1]=file.join(cachepaths[i],"luatex-cache",caches.namespace) -    end -  end -  if not writable then -    texio.write_nl("quiting: fix your writable cache path\n") -    os.exit() -  elseif #readables==0 then -    texio.write_nl("quiting: fix your readable cache path\n") -    os.exit() -  elseif #readables==1 and readables[1]==writable then -    texio.write(string.format("(using cache: %s)",writable)) -  else -    texio.write(string.format("(using write cache: %s)",writable)) -    texio.write(string.format("(using read cache: %s)",table.concat(readables," "))) -  end -end -function caches.getwritablepath(category,subcategory) -  local path=file.join(writable,category) -  lfs.mkdir(path) -  path=file.join(path,subcategory) -  lfs.mkdir(path) -  return path -end -function caches.getreadablepaths(category,subcategory) -  local t={} -  for i=1,#readables do -    t[i]=file.join(readables[i],category,subcategory) -  end -  return t -end -local function makefullname(path,name) -  if path and path~="" then -    name="temp-"..name  -    return file.addsuffix(file.join(path,name),"lua") -  end -end -function caches.iswritable(path,name) -  local fullname=makefullname(path,name) -  return fullname and file.iswritable(fullname) -end -function caches.loaddata(paths,name) -  for i=1,#paths do -    local fullname=makefullname(paths[i],name) -    if fullname then -      texio.write(string.format("(load: %s)",fullname)) -      local data=loadfile(fullname) -      return data and data() -    end -  end -end -function caches.savedata(path,name,data) -  local fullname=makefullname(path,name) -  if fullname then -    texio.write(string.format("(save: %s)",fullname)) -    table.tofile(fullname,data,'return',false,true,false) -  end -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['luat-ovr']={ -  version=1.001, -  comment="companion to luatex-*.tex", -  author="Khaled Hosny and Elie Roux", -  copyright="Luaotfload Development Team", -  license="GNU GPL v2" -} -local write_nl,format,name=texio.write_nl,string.format,"luaotfload" -local dummyfunction=function() end -callbacks={ -  register=dummyfunction, -} -function logs.report(category,fmt,...) -  if fmt then -    write_nl('log',format("%s | %s: %s",name,category,format(fmt,...))) -  elseif category then -    write_nl('log',format("%s | %s",name,category)) -  else -    write_nl('log',format("%s |",name)) -  end -end -function logs.info(category,fmt,...) -  if fmt then -    write_nl(format("%s | %s: %s",name,category,format(fmt,...))) -  elseif category then -    write_nl(format("%s | %s",name,category)) -  else -    write_nl(format("%s |",name)) -  end -  io.flush() -end -function logs.simple(fmt,...) -  if fmt then -    write_nl('log',format("%s | %s",name,format(fmt,...))) -  else -    write_nl('log',format("%s |",name)) -  end -end -tex.attribute[0]=0 -tex.ctxcatcodes=luatexbase.catcodetables.latex - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['data-con']={ -  version=1.100, -  comment="companion to luat-lib.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local format,lower,gsub=string.format,string.lower,string.gsub -local trace_cache=false trackers.register("resolvers.cache",function(v) trace_cache=v end) -local trace_containers=false trackers.register("resolvers.containers",function(v) trace_containers=v end) -local trace_storage=false trackers.register("resolvers.storage",function(v) trace_storage=v end) -containers=containers or {} -containers.usecache=true -local function report(container,tag,name) -  if trace_cache or trace_containers then -    logs.report(format("%s cache",container.subcategory),"%s: %s",tag,name or 'invalid') -  end -end -local allocated={} -local mt={ -  __index=function(t,k) -    if k=="writable" then -      local writable=caches.getwritablepath(t.category,t.subcategory) or { "." } -      t.writable=writable -      return writable -    elseif k=="readables" then -      local readables=caches.getreadablepaths(t.category,t.subcategory) or { "." } -      t.readables=readables -      return readables -    end -  end -} -function containers.define(category,subcategory,version,enabled) -  if category and subcategory then -    local c=allocated[category] -    if not c then -      c={} -      allocated[category]=c -    end -    local s=c[subcategory] -    if not s then -      s={ -        category=category, -        subcategory=subcategory, -        storage={}, -        enabled=enabled, -        version=version or math.pi, -        trace=false, -      } -      setmetatable(s,mt) -      c[subcategory]=s -    end -    return s -  end -end -function containers.is_usable(container,name) -  return container.enabled and caches and caches.iswritable(container.writable,name) -end -function containers.is_valid(container,name) -  if name and name~="" then -    local storage=container.storage[name] -    return storage and storage.cache_version==container.version -  else -    return false -  end -end -function containers.read(container,name) -  local storage=container.storage -  local stored=storage[name] -  if not stored and container.enabled and caches and containers.usecache then -    stored=caches.loaddata(container.readables,name) -    if stored and stored.cache_version==container.version then -      report(container,"loaded",name) -    else -      stored=nil -    end -    storage[name]=stored -  elseif stored then -    report(container,"reusing",name) -  end -  return stored -end -function containers.write(container,name,data) -  if data then -    data.cache_version=container.version -    if container.enabled and caches then -      local unique,shared=data.unique,data.shared -      data.unique,data.shared=nil,nil -      caches.savedata(container.writable,name,data) -      report(container,"saved",name) -      data.unique,data.shared=unique,shared -    end -    report(container,"stored",name) -    container.storage[name]=data -  end -  return data -end -function containers.content(container,name) -  return container.storage[name] -end -function containers.cleanname(name) -  return (gsub(lower(name),"[^%w%d]+","-")) -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-ini']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local utf=unicode.utf8 -local format,serialize=string.format,table.serialize -local write_nl=texio.write_nl -local lower=string.lower -if not fontloader then fontloader=fontforge end -fontloader.totable=fontloader.to_table -fonts=fonts   or {} -fonts.ids=fonts.ids or {} fonts.identifiers=fonts.ids  -fonts.chr=fonts.chr or {} fonts.characters=fonts.chr  -fonts.qua=fonts.qua or {} fonts.quads=fonts.qua  -fonts.tfm=fonts.tfm or {} -fonts.mode='base' -fonts.private=0xF0000  -fonts.verbose=false  -fonts.ids[0]={  -  characters={}, -  descriptions={}, -  name="nullfont", -} -fonts.chr[0]={} -fonts.methods=fonts.methods or { -  base={ tfm={},afm={},otf={},vtf={},fix={} }, -  node={ tfm={},afm={},otf={},vtf={},fix={} }, -} -fonts.initializers=fonts.initializers or { -  base={ tfm={},afm={},otf={},vtf={},fix={} }, -  node={ tfm={},afm={},otf={},vtf={},fix={} } -} -fonts.triggers=fonts.triggers or { -  'mode', -  'language', -  'script', -  'strategy', -} -fonts.processors=fonts.processors or {} -fonts.manipulators=fonts.manipulators or {} -fonts.define=fonts.define         or {} -fonts.define.specify=fonts.define.specify     or {} -fonts.define.specify.synonyms=fonts.define.specify.synonyms or {} -if not fonts.color then -  fonts.color={ -    set=function() end, -    reset=function() end, -  } -end -fonts.formats={} -function fonts.fontformat(filename,default) -  local extname=lower(file.extname(filename)) -  local format=fonts.formats[extname] -  if format then -    return format -  else -    logs.report("fonts define","unable to determine font format for '%s'",filename) -    return default -  end -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['node-dum']={ -  version=1.001, -  comment="companion to luatex-*.tex", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -nodes=nodes   or {} -fonts=fonts   or {} -attributes=attributes or {} -local traverse_id=node.traverse_id -local free_node=node.free -local remove_node=node.remove -local new_node=node.new -local glyph=node.id('glyph') -local fontdata=fonts.ids or {} -function nodes.simple_font_handler(head) -  head=nodes.process_characters(head) -  nodes.inject_kerns(head) -  nodes.protect_glyphs(head) -  head=node.ligaturing(head) -  head=node.kerning(head) -  return head -end -if tex.attribute[0]~=0 then -  texio.write_nl("log","!") -  texio.write_nl("log","! Attribute 0 is reserved for ConTeXt's font feature management and has to be") -  texio.write_nl("log","! set to zero. Also, some attributes in the range 1-255 are used for special") -  texio.write_nl("log","! purposed so setting them at the TeX end might break the font handler.") -  texio.write_nl("log","!") -  tex.attribute[0]=0  -end -nodes.protect_glyphs=node.protect_glyphs -nodes.unprotect_glyphs=node.unprotect_glyphs -function nodes.process_characters(head) -  local usedfonts,done,prevfont={},false,nil -  for n in traverse_id(glyph,head) do -    local font=n.font -    if font~=prevfont then -      prevfont=font -      local used=usedfonts[font] -      if not used then -        local tfmdata=fontdata[font] -        if tfmdata then -          local shared=tfmdata.shared  -          if shared then -            local processors=shared.processes -            if processors and #processors>0 then -              usedfonts[font]=processors -              done=true -            end -          end -        end -      end -    end -  end -  if done then -    for font,processors in next,usedfonts do -      for i=1,#processors do -        local h,d=processors[i](head,font,0) -        head,done=h or head,done or d -      end -    end -  end -  return head,true -end -function nodes.kern(k) -  local n=new_node("kern",1) -  n.kern=k -  return n -end -function nodes.remove(head,current,free_too) -  local t=current -  head,current=remove_node(head,current) -  if t then -    if free_too then -      free_node(t) -      t=nil -    else -      t.next,t.prev=nil,nil -    end -  end -  return head,current,t -end -function nodes.delete(head,current) -  return nodes.remove(head,current,true) -end -nodes.before=node.insert_before -nodes.after=node.insert_after -attributes.unsetvalue=-0x7FFFFFFF -local numbers,last={},127 -function attributes.private(name) -  local number=numbers[name] -  if not number then -    if last<255 then -      last=last+1 -    end -    number=last -    numbers[name]=number -  end -  return number -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['node-inj']={ -  version=1.001, -  comment="companion to node-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local next=next -local trace_injections=false trackers.register("nodes.injections",function(v) trace_injections=v end) -fonts=fonts   or {} -fonts.tfm=fonts.tfm or {} -fonts.ids=fonts.ids or {} -local fontdata=fonts.ids -local glyph=node.id('glyph') -local kern=node.id('kern') -local traverse_id=node.traverse_id -local unset_attribute=node.unset_attribute -local has_attribute=node.has_attribute -local set_attribute=node.set_attribute -local insert_node_before=node.insert_before -local insert_node_after=node.insert_after -local newkern=nodes.kern -local markbase=attributes.private('markbase') -local markmark=attributes.private('markmark') -local markdone=attributes.private('markdone') -local cursbase=attributes.private('cursbase') -local curscurs=attributes.private('curscurs') -local cursdone=attributes.private('cursdone') -local kernpair=attributes.private('kernpair') -local cursives={} -local marks={} -local kerns={} -function nodes.set_cursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmnext) -  local dx,dy=factor*(exit[1]-entry[1]),factor*(exit[2]-entry[2]) -  local ws,wn=tfmstart.width,tfmnext.width -  local bound=#cursives+1 -  set_attribute(start,cursbase,bound) -  set_attribute(nxt,curscurs,bound) -  cursives[bound]={ rlmode,dx,dy,ws,wn } -  return dx,dy,bound -end -function nodes.set_pair(current,factor,rlmode,r2lflag,spec,tfmchr) -  local x,y,w,h=factor*spec[1],factor*spec[2],factor*spec[3],factor*spec[4] -  if x~=0 or w~=0 or y~=0 or h~=0 then -    local bound=has_attribute(current,kernpair) -    if bound then -      local kb=kerns[bound] -      kb[2],kb[3],kb[4],kb[5]=(kb[2] or 0)+x,(kb[3] or 0)+y,(kb[4] or 0)+w,(kb[5] or 0)+h -    else -      bound=#kerns+1 -      set_attribute(current,kernpair,bound) -      kerns[bound]={ rlmode,x,y,w,h,r2lflag,tfmchr.width } -    end -    return x,y,w,h,bound -  end -  return x,y,w,h  -end -function nodes.set_kern(current,factor,rlmode,x,tfmchr) -  local dx=factor*x -  if dx~=0 then -    local bound=#kerns+1 -    set_attribute(current,kernpair,bound) -    kerns[bound]={ rlmode,dx } -    return dx,bound -  else -    return 0,0 -  end -end -function nodes.set_mark(start,base,factor,rlmode,ba,ma,index)  -  local dx,dy=factor*(ba[1]-ma[1]),factor*(ba[2]-ma[2]) -  local bound=has_attribute(base,markbase) -  if bound then -    local mb=marks[bound] -    if mb then -      if not index then index=#mb+1 end -      mb[index]={ dx,dy } -      set_attribute(start,markmark,bound) -      set_attribute(start,markdone,index) -      return dx,dy,bound -    else -      logs.report("nodes mark","possible problem, U+%04X is base without data (id: %s)",base.char,bound) -    end -  end -  index=index or 1 -  bound=#marks+1 -  set_attribute(base,markbase,bound) -  set_attribute(start,markmark,bound) -  set_attribute(start,markdone,index) -  marks[bound]={ [index]={ dx,dy,rlmode } } -  return dx,dy,bound -end -function nodes.trace_injection(head) -  local function dir(n) -    return (n and n<0 and "r-to-l") or (n and n>0 and "l-to-r") or ("unset") -  end -  local function report(...) -    logs.report("nodes finisher",...) -  end -  report("begin run") -  for n in traverse_id(glyph,head) do -    if n.subtype<256 then -      local kp=has_attribute(n,kernpair) -      local mb=has_attribute(n,markbase) -      local mm=has_attribute(n,markmark) -      local md=has_attribute(n,markdone) -      local cb=has_attribute(n,cursbase) -      local cc=has_attribute(n,curscurs) -      report("char U+%05X, font=%s",n.char,n.font) -      if kp then -        local k=kerns[kp] -        if k[3] then -          report("  pairkern: dir=%s, x=%s, y=%s, w=%s, h=%s",dir(k[1]),k[2] or "?",k[3] or "?",k[4] or "?",k[5] or "?") -        else -          report("  kern: dir=%s, dx=%s",dir(k[1]),k[2] or "?") -        end -      end -      if mb then -        report("  markbase: bound=%s",mb) -      end -      if mm then -        local m=marks[mm] -        if mb then -          local m=m[mb] -          if m then -            report("  markmark: bound=%s, index=%s, dx=%s, dy=%s",mm,md or "?",m[1] or "?",m[2] or "?") -          else -            report("  markmark: bound=%s, missing index",mm) -          end -        else -          m=m[1] -          report("  markmark: bound=%s, dx=%s, dy=%s",mm,m[1] or "?",m[2] or "?") -        end -      end -      if cb then -        report("  cursbase: bound=%s",cb) -      end -      if cc then -        local c=cursives[cc] -        report("  curscurs: bound=%s, dir=%s, dx=%s, dy=%s",cc,dir(c[1]),c[2] or "?",c[3] or "?") -      end -    end -  end -  report("end run") -end -function nodes.inject_kerns(head,where,keep) -  local has_marks,has_cursives,has_kerns=next(marks),next(cursives),next(kerns) -  if has_marks or has_cursives then -    if trace_injections then -      nodes.trace_injection(head) -    end -    local done,ky,rl,valid,cx,wx,mk=false,{},{},{},{},{},{} -    if has_kerns then  -      local nf,tm=nil,nil -      for n in traverse_id(glyph,head) do -        if n.subtype<256 then -          valid[#valid+1]=n -          if n.font~=nf then -            nf=n.font -            tm=fontdata[nf].marks -          end -          mk[n]=tm[n.char] -          local k=has_attribute(n,kernpair) -          if k then -            local kk=kerns[k] -            if kk then -              local x,y,w,h=kk[2] or 0,kk[3] or 0,kk[4] or 0,kk[5] or 0 -              local dy=y-h -              if dy~=0 then -                ky[n]=dy -              end -              if w~=0 or x~=0 then -                wx[n]=kk -              end -              rl[n]=kk[1]  -            end -          end -        end -      end -    else -      local nf,tm=nil,nil -      for n in traverse_id(glyph,head) do -        if n.subtype<256 then -          valid[#valid+1]=n -          if n.font~=nf then -            nf=n.font -            tm=fontdata[nf].marks -          end -          mk[n]=tm[n.char] -        end -      end -    end -    if #valid>0 then -      local cx={} -      if has_kerns and next(ky) then -        for n,k in next,ky do -          n.yoffset=k -        end -      end -      if has_cursives then -        local p_cursbase,p=nil,nil -        local t,d,maxt={},{},0 -        for i=1,#valid do  -          local n=valid[i] -          if not mk[n] then -            local n_cursbase=has_attribute(n,cursbase) -            if p_cursbase then -              local n_curscurs=has_attribute(n,curscurs) -              if p_cursbase==n_curscurs then -                local c=cursives[n_curscurs] -                if c then -                  local rlmode,dx,dy,ws,wn=c[1],c[2],c[3],c[4],c[5] -                  if rlmode>=0 then -                    dx=dx-ws -                  else -                    dx=dx+wn -                  end -                  if dx~=0 then -                    cx[n]=dx -                    rl[n]=rlmode -                  end -                    dy=-dy -                  maxt=maxt+1 -                  t[maxt]=p -                  d[maxt]=dy -                else -                  maxt=0 -                end -              end -            elseif maxt>0 then -              local ny=n.yoffset -              for i=maxt,1,-1 do -                ny=ny+d[i] -                local ti=t[i] -                ti.yoffset=ti.yoffset+ny -              end -              maxt=0 -            end -            if not n_cursbase and maxt>0 then -              local ny=n.yoffset -              for i=maxt,1,-1 do -                ny=ny+d[i] -                local ti=t[i] -                ti.yoffset=ny -              end -              maxt=0 -            end -            p_cursbase,p=n_cursbase,n -          end -        end -        if maxt>0 then -          local ny=n.yoffset -          for i=maxt,1,-1 do -            ny=ny+d[i] -            local ti=t[i] -            ti.yoffset=ny -          end -          maxt=0 -        end -        if not keep then -          cursives={} -        end -      end -      if has_marks then -        for i=1,#valid do -          local p=valid[i] -          local p_markbase=has_attribute(p,markbase) -          if p_markbase then -            local mrks=marks[p_markbase] -            for n in traverse_id(glyph,p.next) do -              local n_markmark=has_attribute(n,markmark) -              if p_markbase==n_markmark then -                local index=has_attribute(n,markdone) or 1 -                local d=mrks[index] -                if d then -                  local rlmode=d[3] -                  if rlmode and rlmode>0 then -                    local k=wx[p] -                    if k then  -                      n.xoffset=p.xoffset-(p.width-d[1])-k[2] -                    else -                      n.xoffset=p.xoffset-(p.width-d[1]) -                    end -                  else -                    local k=wx[p] -                    if k then -                      n.xoffset=p.xoffset-d[1]-k[2] -                    else -                      n.xoffset=p.xoffset-d[1] -                    end -                  end -                  if mk[p] then -                    n.yoffset=p.yoffset+d[2] -                  else -                    n.yoffset=n.yoffset+p.yoffset+d[2] -                  end -                end -              else -                break -              end -            end -          end -        end -        if not keep then -          marks={} -        end -      end -      if next(wx) then -        for n,k in next,wx do -          local rl,x,w,r2l=k[1],k[2] or 0,k[4] or 0,k[6] -          local wx=w-x -          if r2l then -            if wx~=0 then -              insert_node_before(head,n,newkern(wx)) -            end -            if x~=0 then -              insert_node_after (head,n,newkern(x)) -            end -          else -            if x~=0 then -              insert_node_before(head,n,newkern(x)) -            end -            if wx~=0 then -              insert_node_after(head,n,newkern(wx)) -            end -          end -        end -      end -      if next(cx) then -        for n,k in next,cx do -          if k~=0 then -            local rln=rl[n] -            if rln and rln<0 then -              insert_node_before(head,n,newkern(-k)) -            else -              insert_node_before(head,n,newkern(k)) -            end -          end -        end -      end -      if not keep then -        kerns={} -      end -      return head,true -    elseif not keep then -      kerns,cursives,marks={},{},{} -    end -  elseif has_kerns then -    if trace_injections then -      nodes.trace_injection(head) -    end -    for n in traverse_id(glyph,head) do -      if n.subtype<256 then -        local k=has_attribute(n,kernpair) -        if k then -          local kk=kerns[k] -          if kk then -            local rl,x,y,w=kk[1],kk[2] or 0,kk[3],kk[4] -            if y and y~=0 then -              n.yoffset=y  -            end -            if w then -              local r2l=kk[6] -              local wx=w-x -              if r2l then -                if wx~=0 then -                  insert_node_before(head,n,newkern(wx)) -                end -                if x~=0 then -                  insert_node_after (head,n,newkern(x)) -                end -              else -                if x~=0 then -                  insert_node_before(head,n,newkern(x)) -                end -                if wx~=0 then -                  insert_node_after(head,n,newkern(wx)) -                end -              end -            else -              if x~=0 then -                insert_node_before(head,n,newkern(x)) -              end -            end -          end -        end -      end -    end -    if not keep then -      kerns={} -    end -    return head,true -  else -  end -  return head,false -end - -end -- closure - -do -- begin closure to overcome local limits and interference - - -if not modules then modules={} end modules ['otfl-luat-att']={ -  version=math.pi/42, -  comment="companion to luaotfload.lua", -  author="Philipp Gesang", -  copyright="Luaotfload Development Team", -  license="GNU GPL v2" -} -function attributes.private(name) -  local attr="otfl@"..name -  local number=luatexbase.attributes[attr] -  if not number then -    number=luatexbase.new_attribute(attr) -  end -  return number -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-tfm']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local utf=unicode.utf8 -local next,format,match,lower,gsub=next,string.format,string.match,string.lower,string.gsub -local concat,sortedkeys,utfbyte,serialize=table.concat,table.sortedkeys,utf.byte,table.serialize -local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) -local trace_scaling=false trackers.register("fonts.scaling",function(v) trace_scaling=v end) -fonts=fonts   or {} -fonts.tfm=fonts.tfm or {} -fonts.ids=fonts.ids or {} -local tfm=fonts.tfm -fonts.loaded=fonts.loaded  or {} -fonts.dontembed=fonts.dontembed or {} -fonts.triggers=fonts.triggers or {}  -fonts.initializers=fonts.initializers    or {} -fonts.initializers.common=fonts.initializers.common or {} -local fontdata=fonts.ids -local disc=node.id('disc') -local glyph=node.id('glyph') -local set_attribute=node.set_attribute -tfm.resolve_vf=true  -tfm.share_base_kerns=false  -tfm.mathactions={} -tfm.fontname_mode="fullpath" -tfm.enhance=tfm.enhance or function() end -fonts.formats.tfm="type1"  -function tfm.read_from_tfm(specification) -  local fname,tfmdata=specification.filename or "",nil -  if fname~="" then -    if trace_defining then -      logs.report("define font","loading tfm file %s at size %s",fname,specification.size) -    end -    tfmdata=font.read_tfm(fname,specification.size)  -    if tfmdata then -      tfmdata.descriptions=tfmdata.descriptions or {} -      if tfm.resolve_vf then -        fonts.logger.save(tfmdata,file.extname(fname),specification)  -        fname=resolvers.findbinfile(specification.name,'ovf') -        if fname and fname~="" then -          local vfdata=font.read_vf(fname,specification.size)  -          if vfdata then -            local chars=tfmdata.characters -            for k,v in next,vfdata.characters do -              chars[k].commands=v.commands -            end -            tfmdata.type='virtual' -            tfmdata.fonts=vfdata.fonts -          end -        end -      end -      tfm.enhance(tfmdata,specification) -    end -  elseif trace_defining then -    logs.report("define font","loading tfm with name %s fails",specification.name) -  end -  return tfmdata -end -local factors={ -  pt=65536.0, -  bp=65781.8, -} -function tfm.setfactor(f) -  tfm.factor=factors[f or 'pt'] or factors.pt -end -tfm.setfactor() -function tfm.scaled(scaledpoints,designsize)  -  if scaledpoints<0 then -    if designsize then -      if designsize>tfm.factor then  -        return (- scaledpoints/1000)*designsize  -      else -        return (- scaledpoints/1000)*designsize*tfm.factor -      end -    else -      return (- scaledpoints/1000)*10*tfm.factor -    end -  else -    return scaledpoints -  end -end -function tfm.get_virtual_id(tfmdata) -  if not tfmdata.fonts then -    tfmdata.type="virtual" -    tfmdata.fonts={ { id=0 } } -    return 1 -  else -    tfmdata.fonts[#tfmdata.fonts+1]={ id=0 } -    return #tfmdata.fonts -  end -end -function tfm.check_virtual_id(tfmdata,id) -  if tfmdata and tfmdata.type=="virtual" then -    if not tfmdata.fonts or #tfmdata.fonts==0 then -      tfmdata.type,tfmdata.fonts="real",nil -    else -      local vfonts=tfmdata.fonts -      for f=1,#vfonts do -        local fnt=vfonts[f] -        if fnt.id and fnt.id==0 then -          fnt.id=id -        end -      end -    end -  end -end -fonts.trace_scaling=false -local charactercache={} -function tfm.calculate_scale(tfmtable,scaledpoints,relativeid) -  if scaledpoints<0 then -    scaledpoints=(- scaledpoints/1000)*tfmtable.designsize  -  end -  local units=tfmtable.units or 1000 -  local delta=scaledpoints/units  -  return scaledpoints,delta,units -end -function tfm.do_scale(tfmtable,scaledpoints,relativeid) -  local t={}  -  local scaledpoints,delta,units=tfm.calculate_scale(tfmtable,scaledpoints,relativeid) -  t.units_per_em=units or 1000 -  local hdelta,vdelta=delta,delta -  for k,v in next,tfmtable do -    if type(v)=="table" then -    else -      t[k]=v -    end -  end -  local extend_factor=tfmtable.extend_factor or 0 -  if extend_factor~=0 and extend_factor~=1 then -    hdelta=hdelta*extend_factor -    t.extend=extend_factor*1000 -  else -    t.extend=1000 -  end -  local slant_factor=tfmtable.slant_factor or 0 -  if slant_factor~=0 then -    t.slant=slant_factor*1000 -  else -    t.slant=0 -  end -  local isvirtual=tfmtable.type=="virtual" or tfmtable.virtualized -  local hasmath=(tfmtable.math_parameters~=nil and next(tfmtable.math_parameters)~=nil) or (tfmtable.MathConstants~=nil and next(tfmtable.MathConstants)~=nil) -  local nodemode=tfmtable.mode=="node" -  local hasquality=tfmtable.auto_expand or tfmtable.auto_protrude -  local hasitalic=tfmtable.has_italic -  t.parameters={} -  t.characters={} -  t.MathConstants={} -  local descriptions=tfmtable.descriptions or {} -  t.unicodes=tfmtable.unicodes -  t.indices=tfmtable.indices -  t.marks=tfmtable.marks -t.goodies=tfmtable.goodies -t.colorscheme=tfmtable.colorscheme -  t.descriptions=descriptions -  if tfmtable.fonts then -    t.fonts=table.fastcopy(tfmtable.fonts)  -  end -  local tp=t.parameters -  local mp=t.math_parameters -  local tfmp=tfmtable.parameters -  tp.slant=(tfmp.slant     or tfmp[1] or 0) -  tp.space=(tfmp.space     or tfmp[2] or 0)*hdelta -  tp.space_stretch=(tfmp.space_stretch or tfmp[3] or 0)*hdelta -  tp.space_shrink=(tfmp.space_shrink or tfmp[4] or 0)*hdelta -  tp.x_height=(tfmp.x_height   or tfmp[5] or 0)*vdelta -  tp.quad=(tfmp.quad     or tfmp[6] or 0)*hdelta -  tp.extra_space=(tfmp.extra_space  or tfmp[7] or 0)*hdelta -  local protrusionfactor=(tp.quad~=0 and 1000/tp.quad) or 0 -  local tc=t.characters -  local characters=tfmtable.characters -  local nameneeded=not tfmtable.shared.otfdata  -  local changed=tfmtable.changed or {}  -  local ischanged=changed and next(changed) -  local indices=tfmtable.indices -  local luatex=tfmtable.luatex -  local tounicode=luatex and luatex.tounicode -  local defaultwidth=luatex and luatex.defaultwidth or 0 -  local defaultheight=luatex and luatex.defaultheight or 0 -  local defaultdepth=luatex and luatex.defaultdepth or 0 -  local scaledwidth=defaultwidth*hdelta -  local scaledheight=defaultheight*vdelta -  local scaleddepth=defaultdepth*vdelta -  local stackmath=tfmtable.ignore_stack_math~=true -  local private=fonts.private -  local sharedkerns={} -  for k,v in next,characters do -    local chr,description,index -    if ischanged then -      local c=changed[k] -      if c then -        description=descriptions[c] or v -        v=characters[c] or v -        index=(indices and indices[c]) or c -      else -        description=descriptions[k] or v -        index=(indices and indices[k]) or k -      end -    else -      description=descriptions[k] or v -      index=(indices and indices[k]) or k -    end -    local width=description.width -    local height=description.height -    local depth=description.depth -    if width then width=hdelta*width else width=scaledwidth end -    if height then height=vdelta*height else height=scaledheight end -    if depth and depth~=0 then -      depth=delta*depth -      if nameneeded then -        chr={ -          name=description.name, -          index=index, -          height=height, -          depth=depth, -          width=width, -        } -      else -        chr={ -          index=index, -          height=height, -          depth=depth, -          width=width, -        } -      end -    else -      if nameneeded then -        chr={ -          name=description.name, -          index=index, -          height=height, -          width=width, -        } -      else -        chr={ -          index=index, -          height=height, -          width=width, -        } -      end -    end -    if tounicode then -      local tu=tounicode[index]  -      if tu then -        chr.tounicode=tu -      end -    end -    if hasquality then -      local ve=v.expansion_factor -      if ve then -        chr.expansion_factor=ve*1000  -      end -      local vl=v.left_protruding -      if vl then -        chr.left_protruding=protrusionfactor*width*vl -      end -      local vr=v.right_protruding -      if vr then -        chr.right_protruding=protrusionfactor*width*vr -      end -    end -    if hasitalic then -      local vi=description.italic or v.italic -      if vi and vi~=0 then -        chr.italic=vi*hdelta -      end -    end -    if hasmath then -      local vn=v.next -      if vn then -        chr.next=vn -      else -        local vv=v.vert_variants -        if vv then -          local t={} -          for i=1,#vv do -            local vvi=vv[i] -            t[i]={ -              ["start"]=(vvi["start"]  or 0)*vdelta, -              ["end"]=(vvi["end"]   or 0)*vdelta, -              ["advance"]=(vvi["advance"] or 0)*vdelta, -              ["extender"]=vvi["extender"], -              ["glyph"]=vvi["glyph"], -            } -          end -          chr.vert_variants=t -        else -          local hv=v.horiz_variants -          if hv then -            local t={} -            for i=1,#hv do -              local hvi=hv[i] -              t[i]={ -                ["start"]=(hvi["start"]  or 0)*hdelta, -                ["end"]=(hvi["end"]   or 0)*hdelta, -                ["advance"]=(hvi["advance"] or 0)*hdelta, -                ["extender"]=hvi["extender"], -                ["glyph"]=hvi["glyph"], -              } -            end -            chr.horiz_variants=t -          end -        end -      end -      local vt=description.top_accent -      if vt then -        chr.top_accent=vdelta*vt -      end -      if stackmath then -        local mk=v.mathkerns -        if mk then -          local kerns={} -          local v=mk.top_right  if v then local k={} for i=1,#v do local vi=v[i] -            k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } -          end   kerns.top_right=k end -          local v=mk.top_left   if v then local k={} for i=1,#v do local vi=v[i] -            k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } -          end   kerns.top_left=k end -          local v=mk.bottom_left if v then local k={} for i=1,#v do local vi=v[i] -            k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } -          end   kerns.bottom_left=k end -          local v=mk.bottom_right if v then local k={} for i=1,#v do local vi=v[i] -            k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } -          end   kerns.bottom_right=k end -          chr.mathkern=kerns  -        end -      end -    end -    if not nodemode then -      local vk=v.kerns -      if vk then -        local s=sharedkerns[vk] -        if not s then -          s={} -          for k,v in next,vk do s[k]=v*hdelta end -          sharedkerns[vk]=s -        end -        chr.kerns=s -      end -      local vl=v.ligatures -      if vl then -        if true then -          chr.ligatures=vl  -        else -          local tt={} -          for i,l in next,vl do -            tt[i]=l -          end -          chr.ligatures=tt -        end -      end -    end -    if isvirtual then -      local vc=v.commands -      if vc then -        local ok=false -        for i=1,#vc do -          local key=vc[i][1] -          if key=="right" or key=="down" then -            ok=true -            break -          end -        end -        if ok then -          local tt={} -          for i=1,#vc do -            local ivc=vc[i] -            local key=ivc[1] -            if key=="right" then -              tt[#tt+1]={ key,ivc[2]*hdelta } -            elseif key=="down" then -              tt[#tt+1]={ key,ivc[2]*vdelta } -            elseif key=="rule" then -              tt[#tt+1]={ key,ivc[2]*vdelta,ivc[3]*hdelta } -            else  -              tt[#tt+1]=ivc  -            end -          end -          chr.commands=tt -        else -          chr.commands=vc -        end -      end -    end -    tc[k]=chr -  end -  t.size=scaledpoints -  t.factor=delta -  t.hfactor=hdelta -  t.vfactor=vdelta -  if t.fonts then -    t.fonts=table.fastcopy(t.fonts)  -  end -  if hasmath then -    local ma=tfm.mathactions -    for i=1,#ma do -      ma[i](t,tfmtable,delta,hdelta,vdelta)  -    end -  end -  local tpx=tp.x_height -  if hasmath then -    if not tp[13] then tp[13]=.86*tpx end  -    if not tp[14] then tp[14]=.86*tpx end  -    if not tp[15] then tp[15]=.86*tpx end  -    if not tp[16] then tp[16]=.48*tpx end  -    if not tp[17] then tp[17]=.48*tpx end  -    if not tp[22] then tp[22]=0   end  -    if t.MathConstants then t.MathConstants.AccentBaseHeight=nil end  -  end -  t.tounicode=1 -  t.cidinfo=tfmtable.cidinfo -  if hasmath then -    if trace_defining then -      logs.report("define font","math enabled for: name '%s', fullname: '%s', filename: '%s'",t.name or "noname",t.fullname or "nofullname",t.filename or "nofilename") -    end -  else -    if trace_defining then -      logs.report("define font","math disabled for: name '%s', fullname: '%s', filename: '%s'",t.name or "noname",t.fullname or "nofullname",t.filename or "nofilename") -    end -    t.nomath,t.MathConstants=true,nil -  end -  if not t.psname then -    t.psname=t.fontname or (t.fullname and fonts.names.cleanname(t.fullname)) -  end -  if trace_defining then -    logs.report("define font","used for accesing subfont: '%s'",t.psname or "nopsname") -    logs.report("define font","used for subsetting: '%s'",t.fontname or "nofontname") -  end -  return t,delta -end -tfm.auto_cleanup=true -local lastfont=nil -function tfm.cleanup_table(tfmdata)  -  if tfm.auto_cleanup then  -    if tfmdata.type=='virtual' or tfmdata.virtualized then -      for k,v in next,tfmdata.characters do -        if v.commands then v.commands=nil end -      end -    else -    end -  end -end -function tfm.cleanup(tfmdata)  -end -function tfm.scale(tfmtable,scaledpoints,relativeid) -  local t,factor=tfm.do_scale(tfmtable,scaledpoints,relativeid) -  t.factor=factor -  t.ascender=factor*(tfmtable.ascender or 0) -  t.descender=factor*(tfmtable.descender or 0) -  t.shared=tfmtable.shared or {} -  t.unique=table.fastcopy(tfmtable.unique or {}) -  tfm.cleanup(t) -  return t -end -fonts.analyzers=fonts.analyzers       or {} -fonts.analyzers.aux=fonts.analyzers.aux     or {} -fonts.analyzers.methods=fonts.analyzers.methods   or {} -fonts.analyzers.initializers=fonts.analyzers.initializers or {} -local state=attributes.private('state') -function fonts.analyzers.aux.setstate(head,font) -  local tfmdata=fontdata[font] -  local characters=tfmdata.characters -  local descriptions=tfmdata.descriptions -  local first,last,current,n,done=nil,nil,head,0,false  -  while current do -    local id=current.id -    if id==glyph and current.font==font then -      local d=descriptions[current.char] -      if d then -        if d.class=="mark" then -          done=true -          set_attribute(current,state,5)  -        elseif n==0 then -          first,last,n=current,current,1 -          set_attribute(current,state,1)  -        else -          last,n=current,n+1 -          set_attribute(current,state,2)  -        end -      else  -        if first and first==last then -          set_attribute(last,state,4)  -        elseif last then -          set_attribute(last,state,3)  -        end -        first,last,n=nil,nil,0 -      end -    elseif id==disc then -      set_attribute(current,state,2)  -      last=current -    else  -      if first and first==last then -        set_attribute(last,state,4)  -      elseif last then -        set_attribute(last,state,3)  -      end -      first,last,n=nil,nil,0 -    end -    current=current.next -  end -  if first and first==last then -    set_attribute(last,state,4)  -  elseif last then -    set_attribute(last,state,3)  -  end -  return head,done -end -function tfm.replacements(tfm,value) -  tfm.characters[0x0027]=tfm.characters[0x2019] -end -function tfm.checked_filename(metadata,whatever) -  local foundfilename=metadata.foundfilename -  if not foundfilename then -    local askedfilename=metadata.filename or "" -    if askedfilename~="" then -      foundfilename=resolvers.findbinfile(askedfilename,"") or "" -      if foundfilename=="" then -        logs.report("fonts","source file '%s' is not found",askedfilename) -        foundfilename=resolvers.findbinfile(file.basename(askedfilename),"") or "" -        if foundfilename~="" then -          logs.report("fonts","using source file '%s' (cache mismatch)",foundfilename) -        end -      end -    elseif whatever then -      logs.report("fonts","no source file for '%s'",whatever) -      foundfilename="" -    end -    metadata.foundfilename=foundfilename -  end -  return foundfilename -end -statistics.register("fonts load time",function() -  return statistics.elapsedseconds(fonts) -end) - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-cid']={ -  version=1.001, -  comment="companion to font-otf.lua (cidmaps)", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local format,match,lower=string.format,string.match,string.lower -local tonumber=tonumber -local lpegmatch=lpeg.match -local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) -fonts=fonts     or {} -fonts.cid=fonts.cid   or {} -fonts.cid.map=fonts.cid.map or {} -fonts.cid.max=fonts.cid.max or 10 -local number=lpeg.C(lpeg.R("09","af","AF")^1) -local space=lpeg.S(" \n\r\t") -local spaces=space^0 -local period=lpeg.P(".") -local periods=period*period -local name=lpeg.P("/")*lpeg.C((1-space)^1) -local unicodes,names={},{} -local function do_one(a,b) -  unicodes[tonumber(a)]=tonumber(b,16) -end -local function do_range(a,b,c) -  c=tonumber(c,16) -  for i=tonumber(a),tonumber(b) do -    unicodes[i]=c -    c=c+1 -  end -end -local function do_name(a,b) -  names[tonumber(a)]=b -end -local grammar=lpeg.P { "start", -  start=number*spaces*number*lpeg.V("series"), -  series=(spaces*(lpeg.V("one")+lpeg.V("range")+lpeg.V("named")) )^1, -  one=(number*spaces*number)/do_one, -  range=(number*periods*number*spaces*number)/do_range, -  named=(number*spaces*name)/do_name -} -function fonts.cid.load(filename) -  local data=io.loaddata(filename) -  if data then -    unicodes,names={},{} -    lpegmatch(grammar,data) -    local supplement,registry,ordering=match(filename,"^(.-)%-(.-)%-()%.(.-)$") -    return { -      supplement=supplement, -      registry=registry, -      ordering=ordering, -      filename=filename, -      unicodes=unicodes, -      names=names -    } -  else -    return nil -  end -end -local template="%s-%s-%s.cidmap" -local function locate(registry,ordering,supplement) -  local filename=format(template,registry,ordering,supplement) -  local hashname=lower(filename) -  local cidmap=fonts.cid.map[hashname] -  if not cidmap then -    if trace_loading then -      logs.report("load otf","checking cidmap, registry: %s, ordering: %s, supplement: %s, filename: %s",registry,ordering,supplement,filename) -    end -    local fullname=resolvers.find_file(filename,'cid') or "" -    if fullname~="" then -      cidmap=fonts.cid.load(fullname) -      if cidmap then -        if trace_loading then -          logs.report("load otf","using cidmap file %s",filename) -        end -        fonts.cid.map[hashname]=cidmap -        cidmap.usedname=file.basename(filename) -        return cidmap -      end -    end -  end -  return cidmap -end -function fonts.cid.getmap(registry,ordering,supplement) -  local supplement=tonumber(supplement) -  if trace_loading then -    logs.report("load otf","needed cidmap, registry: %s, ordering: %s, supplement: %s",registry,ordering,supplement) -  end -  local cidmap=locate(registry,ordering,supplement) -  if not cidmap then -    local cidnum=nil -    if supplement<fonts.cid.max then -      for supplement=supplement+1,fonts.cid.max do -        local c=locate(registry,ordering,supplement) -        if c then -          cidmap,cidnum=c,supplement -          break -        end -      end -    end -    if not cidmap and supplement>0 then -      for supplement=supplement-1,0,-1 do -        local c=locate(registry,ordering,supplement) -        if c then -          cidmap,cidnum=c,supplement -          break -        end -      end -    end -    if cidmap and cidnum>0 then -      for s=0,cidnum-1 do -        filename=format(template,registry,ordering,s) -        if not fonts.cid.map[filename] then -          fonts.cid.map[filename]=cidmap  -        end -      end -    end -  end -  return cidmap -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-otf']={ -  version=1.001, -  comment="companion to font-otf.lua (tables)", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local type,next,tonumber,tostring=type,next,tonumber,tostring -local gsub,lower=string.gsub,string.lower -fonts=fonts   or {} -fonts.otf=fonts.otf or {} -local otf=fonts.otf -otf.tables=otf.tables  or {} -otf.meanings=otf.meanings or {} -otf.tables.scripts={ -  ['dflt']='Default', -  ['arab']='Arabic', -  ['armn']='Armenian', -  ['bali']='Balinese', -  ['beng']='Bengali', -  ['bopo']='Bopomofo', -  ['brai']='Braille', -  ['bugi']='Buginese', -  ['buhd']='Buhid', -  ['byzm']='Byzantine Music', -  ['cans']='Canadian Syllabics', -  ['cher']='Cherokee', -  ['copt']='Coptic', -  ['cprt']='Cypriot Syllabary', -  ['cyrl']='Cyrillic', -  ['deva']='Devanagari', -  ['dsrt']='Deseret', -  ['ethi']='Ethiopic', -  ['geor']='Georgian', -  ['glag']='Glagolitic', -  ['goth']='Gothic', -  ['grek']='Greek', -  ['gujr']='Gujarati', -  ['guru']='Gurmukhi', -  ['hang']='Hangul', -  ['hani']='CJK Ideographic', -  ['hano']='Hanunoo', -  ['hebr']='Hebrew', -  ['ital']='Old Italic', -  ['jamo']='Hangul Jamo', -  ['java']='Javanese', -  ['kana']='Hiragana and Katakana', -  ['khar']='Kharosthi', -  ['khmr']='Khmer', -  ['knda']='Kannada', -  ['lao' ]='Lao', -  ['latn']='Latin', -  ['limb']='Limbu', -  ['linb']='Linear B', -  ['math']='Mathematical Alphanumeric Symbols', -  ['mlym']='Malayalam', -  ['mong']='Mongolian', -  ['musc']='Musical Symbols', -  ['mymr']='Myanmar', -  ['nko' ]="N'ko", -  ['ogam']='Ogham', -  ['orya']='Oriya', -  ['osma']='Osmanya', -  ['phag']='Phags-pa', -  ['phnx']='Phoenician', -  ['runr']='Runic', -  ['shaw']='Shavian', -  ['sinh']='Sinhala', -  ['sylo']='Syloti Nagri', -  ['syrc']='Syriac', -  ['tagb']='Tagbanwa', -  ['tale']='Tai Le', -  ['talu']='Tai Lu', -  ['taml']='Tamil', -  ['telu']='Telugu', -  ['tfng']='Tifinagh', -  ['tglg']='Tagalog', -  ['thaa']='Thaana', -  ['thai']='Thai', -  ['tibt']='Tibetan', -  ['ugar']='Ugaritic Cuneiform', -  ['xpeo']='Old Persian Cuneiform', -  ['xsux']='Sumero-Akkadian Cuneiform', -  ['yi' ]='Yi', -} -otf.tables.languages={ -  ['dflt']='Default', -  ['aba']='Abaza', -  ['abk']='Abkhazian', -  ['ady']='Adyghe', -  ['afk']='Afrikaans', -  ['afr']='Afar', -  ['agw']='Agaw', -  ['als']='Alsatian', -  ['alt']='Altai', -  ['amh']='Amharic', -  ['ara']='Arabic', -  ['ari']='Aari', -  ['ark']='Arakanese', -  ['asm']='Assamese', -  ['ath']='Athapaskan', -  ['avr']='Avar', -  ['awa']='Awadhi', -  ['aym']='Aymara', -  ['aze']='Azeri', -  ['bad']='Badaga', -  ['bag']='Baghelkhandi', -  ['bal']='Balkar', -  ['bau']='Baule', -  ['bbr']='Berber', -  ['bch']='Bench', -  ['bcr']='Bible Cree', -  ['bel']='Belarussian', -  ['bem']='Bemba', -  ['ben']='Bengali', -  ['bgr']='Bulgarian', -  ['bhi']='Bhili', -  ['bho']='Bhojpuri', -  ['bik']='Bikol', -  ['bil']='Bilen', -  ['bkf']='Blackfoot', -  ['bli']='Balochi', -  ['bln']='Balante', -  ['blt']='Balti', -  ['bmb']='Bambara', -  ['bml']='Bamileke', -  ['bos']='Bosnian', -  ['bre']='Breton', -  ['brh']='Brahui', -  ['bri']='Braj Bhasha', -  ['brm']='Burmese', -  ['bsh']='Bashkir', -  ['bti']='Beti', -  ['cat']='Catalan', -  ['ceb']='Cebuano', -  ['che']='Chechen', -  ['chg']='Chaha Gurage', -  ['chh']='Chattisgarhi', -  ['chi']='Chichewa', -  ['chk']='Chukchi', -  ['chp']='Chipewyan', -  ['chr']='Cherokee', -  ['chu']='Chuvash', -  ['cmr']='Comorian', -  ['cop']='Coptic', -  ['cos']='Corsican', -  ['cre']='Cree', -  ['crr']='Carrier', -  ['crt']='Crimean Tatar', -  ['csl']='Church Slavonic', -  ['csy']='Czech', -  ['dan']='Danish', -  ['dar']='Dargwa', -  ['dcr']='Woods Cree', -  ['deu']='German', -  ['dgr']='Dogri', -  ['div']='Divehi', -  ['djr']='Djerma', -  ['dng']='Dangme', -  ['dnk']='Dinka', -  ['dri']='Dari', -  ['dun']='Dungan', -  ['dzn']='Dzongkha', -  ['ebi']='Ebira', -  ['ecr']='Eastern Cree', -  ['edo']='Edo', -  ['efi']='Efik', -  ['ell']='Greek', -  ['eng']='English', -  ['erz']='Erzya', -  ['esp']='Spanish', -  ['eti']='Estonian', -  ['euq']='Basque', -  ['evk']='Evenki', -  ['evn']='Even', -  ['ewe']='Ewe', -  ['fan']='French Antillean', -  ['far']='Farsi', -  ['fin']='Finnish', -  ['fji']='Fijian', -  ['fle']='Flemish', -  ['fne']='Forest Nenets', -  ['fon']='Fon', -  ['fos']='Faroese', -  ['fra']='French', -  ['fri']='Frisian', -  ['frl']='Friulian', -  ['fta']='Futa', -  ['ful']='Fulani', -  ['gad']='Ga', -  ['gae']='Gaelic', -  ['gag']='Gagauz', -  ['gal']='Galician', -  ['gar']='Garshuni', -  ['gaw']='Garhwali', -  ['gez']="Ge'ez", -  ['gil']='Gilyak', -  ['gmz']='Gumuz', -  ['gon']='Gondi', -  ['grn']='Greenlandic', -  ['gro']='Garo', -  ['gua']='Guarani', -  ['guj']='Gujarati', -  ['hai']='Haitian', -  ['hal']='Halam', -  ['har']='Harauti', -  ['hau']='Hausa', -  ['haw']='Hawaiin', -  ['hbn']='Hammer-Banna', -  ['hil']='Hiligaynon', -  ['hin']='Hindi', -  ['hma']='High Mari', -  ['hnd']='Hindko', -  ['ho']='Ho', -  ['hri']='Harari', -  ['hrv']='Croatian', -  ['hun']='Hungarian', -  ['hye']='Armenian', -  ['ibo']='Igbo', -  ['ijo']='Ijo', -  ['ilo']='Ilokano', -  ['ind']='Indonesian', -  ['ing']='Ingush', -  ['inu']='Inuktitut', -  ['iri']='Irish', -  ['irt']='Irish Traditional', -  ['isl']='Icelandic', -  ['ism']='Inari Sami', -  ['ita']='Italian', -  ['iwr']='Hebrew', -  ['jan']='Japanese', -  ['jav']='Javanese', -  ['jii']='Yiddish', -  ['jud']='Judezmo', -  ['jul']='Jula', -  ['kab']='Kabardian', -  ['kac']='Kachchi', -  ['kal']='Kalenjin', -  ['kan']='Kannada', -  ['kar']='Karachay', -  ['kat']='Georgian', -  ['kaz']='Kazakh', -  ['keb']='Kebena', -  ['kge']='Khutsuri Georgian', -  ['kha']='Khakass', -  ['khk']='Khanty-Kazim', -  ['khm']='Khmer', -  ['khs']='Khanty-Shurishkar', -  ['khv']='Khanty-Vakhi', -  ['khw']='Khowar', -  ['kik']='Kikuyu', -  ['kir']='Kirghiz', -  ['kis']='Kisii', -  ['kkn']='Kokni', -  ['klm']='Kalmyk', -  ['kmb']='Kamba', -  ['kmn']='Kumaoni', -  ['kmo']='Komo', -  ['kms']='Komso', -  ['knr']='Kanuri', -  ['kod']='Kodagu', -  ['koh']='Korean Old Hangul', -  ['kok']='Konkani', -  ['kon']='Kikongo', -  ['kop']='Komi-Permyak', -  ['kor']='Korean', -  ['koz']='Komi-Zyrian', -  ['kpl']='Kpelle', -  ['kri']='Krio', -  ['krk']='Karakalpak', -  ['krl']='Karelian', -  ['krm']='Karaim', -  ['krn']='Karen', -  ['krt']='Koorete', -  ['ksh']='Kashmiri', -  ['ksi']='Khasi', -  ['ksm']='Kildin Sami', -  ['kui']='Kui', -  ['kul']='Kulvi', -  ['kum']='Kumyk', -  ['kur']='Kurdish', -  ['kuu']='Kurukh', -  ['kuy']='Kuy', -  ['kyk']='Koryak', -  ['lad']='Ladin', -  ['lah']='Lahuli', -  ['lak']='Lak', -  ['lam']='Lambani', -  ['lao']='Lao', -  ['lat']='Latin', -  ['laz']='Laz', -  ['lcr']='L-Cree', -  ['ldk']='Ladakhi', -  ['lez']='Lezgi', -  ['lin']='Lingala', -  ['lma']='Low Mari', -  ['lmb']='Limbu', -  ['lmw']='Lomwe', -  ['lsb']='Lower Sorbian', -  ['lsm']='Lule Sami', -  ['lth']='Lithuanian', -  ['ltz']='Luxembourgish', -  ['lub']='Luba', -  ['lug']='Luganda', -  ['luh']='Luhya', -  ['luo']='Luo', -  ['lvi']='Latvian', -  ['maj']='Majang', -  ['mak']='Makua', -  ['mal']='Malayalam Traditional', -  ['man']='Mansi', -  ['map']='Mapudungun', -  ['mar']='Marathi', -  ['maw']='Marwari', -  ['mbn']='Mbundu', -  ['mch']='Manchu', -  ['mcr']='Moose Cree', -  ['mde']='Mende', -  ['men']="Me'en", -  ['miz']='Mizo', -  ['mkd']='Macedonian', -  ['mle']='Male', -  ['mlg']='Malagasy', -  ['mln']='Malinke', -  ['mlr']='Malayalam Reformed', -  ['mly']='Malay', -  ['mnd']='Mandinka', -  ['mng']='Mongolian', -  ['mni']='Manipuri', -  ['mnk']='Maninka', -  ['mnx']='Manx Gaelic', -  ['moh']='Mohawk', -  ['mok']='Moksha', -  ['mol']='Moldavian', -  ['mon']='Mon', -  ['mor']='Moroccan', -  ['mri']='Maori', -  ['mth']='Maithili', -  ['mts']='Maltese', -  ['mun']='Mundari', -  ['nag']='Naga-Assamese', -  ['nan']='Nanai', -  ['nas']='Naskapi', -  ['ncr']='N-Cree', -  ['ndb']='Ndebele', -  ['ndg']='Ndonga', -  ['nep']='Nepali', -  ['new']='Newari', -  ['ngr']='Nagari', -  ['nhc']='Norway House Cree', -  ['nis']='Nisi', -  ['niu']='Niuean', -  ['nkl']='Nkole', -  ['nko']="N'ko", -  ['nld']='Dutch', -  ['nog']='Nogai', -  ['nor']='Norwegian', -  ['nsm']='Northern Sami', -  ['nta']='Northern Tai', -  ['nto']='Esperanto', -  ['nyn']='Nynorsk', -  ['oci']='Occitan', -  ['ocr']='Oji-Cree', -  ['ojb']='Ojibway', -  ['ori']='Oriya', -  ['oro']='Oromo', -  ['oss']='Ossetian', -  ['paa']='Palestinian Aramaic', -  ['pal']='Pali', -  ['pan']='Punjabi', -  ['pap']='Palpa', -  ['pas']='Pashto', -  ['pgr']='Polytonic Greek', -  ['pil']='Pilipino', -  ['plg']='Palaung', -  ['plk']='Polish', -  ['pro']='Provencal', -  ['ptg']='Portuguese', -  ['qin']='Chin', -  ['raj']='Rajasthani', -  ['rbu']='Russian Buriat', -  ['rcr']='R-Cree', -  ['ria']='Riang', -  ['rms']='Rhaeto-Romanic', -  ['rom']='Romanian', -  ['roy']='Romany', -  ['rsy']='Rusyn', -  ['rua']='Ruanda', -  ['rus']='Russian', -  ['sad']='Sadri', -  ['san']='Sanskrit', -  ['sat']='Santali', -  ['say']='Sayisi', -  ['sek']='Sekota', -  ['sel']='Selkup', -  ['sgo']='Sango', -  ['shn']='Shan', -  ['sib']='Sibe', -  ['sid']='Sidamo', -  ['sig']='Silte Gurage', -  ['sks']='Skolt Sami', -  ['sky']='Slovak', -  ['sla']='Slavey', -  ['slv']='Slovenian', -  ['sml']='Somali', -  ['smo']='Samoan', -  ['sna']='Sena', -  ['snd']='Sindhi', -  ['snh']='Sinhalese', -  ['snk']='Soninke', -  ['sog']='Sodo Gurage', -  ['sot']='Sotho', -  ['sqi']='Albanian', -  ['srb']='Serbian', -  ['srk']='Saraiki', -  ['srr']='Serer', -  ['ssl']='South Slavey', -  ['ssm']='Southern Sami', -  ['sur']='Suri', -  ['sva']='Svan', -  ['sve']='Swedish', -  ['swa']='Swadaya Aramaic', -  ['swk']='Swahili', -  ['swz']='Swazi', -  ['sxt']='Sutu', -  ['syr']='Syriac', -  ['tab']='Tabasaran', -  ['taj']='Tajiki', -  ['tam']='Tamil', -  ['tat']='Tatar', -  ['tcr']='TH-Cree', -  ['tel']='Telugu', -  ['tgn']='Tongan', -  ['tgr']='Tigre', -  ['tgy']='Tigrinya', -  ['tha']='Thai', -  ['tht']='Tahitian', -  ['tib']='Tibetan', -  ['tkm']='Turkmen', -  ['tmn']='Temne', -  ['tna']='Tswana', -  ['tne']='Tundra Nenets', -  ['tng']='Tonga', -  ['tod']='Todo', -  ['trk']='Turkish', -  ['tsg']='Tsonga', -  ['tua']='Turoyo Aramaic', -  ['tul']='Tulu', -  ['tuv']='Tuvin', -  ['twi']='Twi', -  ['udm']='Udmurt', -  ['ukr']='Ukrainian', -  ['urd']='Urdu', -  ['usb']='Upper Sorbian', -  ['uyg']='Uyghur', -  ['uzb']='Uzbek', -  ['ven']='Venda', -  ['vit']='Vietnamese', -  ['wa' ]='Wa', -  ['wag']='Wagdi', -  ['wcr']='West-Cree', -  ['wel']='Welsh', -  ['wlf']='Wolof', -  ['xbd']='Tai Lue', -  ['xhs']='Xhosa', -  ['yak']='Yakut', -  ['yba']='Yoruba', -  ['ycr']='Y-Cree', -  ['yic']='Yi Classic', -  ['yim']='Yi Modern', -  ['zhh']='Chinese Hong Kong', -  ['zhp']='Chinese Phonetic', -  ['zhs']='Chinese Simplified', -  ['zht']='Chinese Traditional', -  ['znd']='Zande', -  ['zul']='Zulu' -} -otf.tables.features={ -  ['aalt']='Access All Alternates', -  ['abvf']='Above-Base Forms', -  ['abvm']='Above-Base Mark Positioning', -  ['abvs']='Above-Base Substitutions', -  ['afrc']='Alternative Fractions', -  ['akhn']='Akhands', -  ['blwf']='Below-Base Forms', -  ['blwm']='Below-Base Mark Positioning', -  ['blws']='Below-Base Substitutions', -  ['c2pc']='Petite Capitals From Capitals', -  ['c2sc']='Small Capitals From Capitals', -  ['calt']='Contextual Alternates', -  ['case']='Case-Sensitive Forms', -  ['ccmp']='Glyph Composition/Decomposition', -  ['cjct']='Conjunct Forms', -  ['clig']='Contextual Ligatures', -  ['cpsp']='Capital Spacing', -  ['cswh']='Contextual Swash', -  ['curs']='Cursive Positioning', -  ['dflt']='Default Processing', -  ['dist']='Distances', -  ['dlig']='Discretionary Ligatures', -  ['dnom']='Denominators', -  ['dtls']='Dotless Forms', -  ['expt']='Expert Forms', -  ['falt']='Final glyph Alternates', -  ['fin2']='Terminal Forms #2', -  ['fin3']='Terminal Forms #3', -  ['fina']='Terminal Forms', -  ['flac']='Flattened Accents Over Capitals', -  ['frac']='Fractions', -  ['fwid']='Full Width', -  ['half']='Half Forms', -  ['haln']='Halant Forms', -  ['halt']='Alternate Half Width', -  ['hist']='Historical Forms', -  ['hkna']='Horizontal Kana Alternates', -  ['hlig']='Historical Ligatures', -  ['hngl']='Hangul', -  ['hojo']='Hojo Kanji Forms', -  ['hwid']='Half Width', -  ['init']='Initial Forms', -  ['isol']='Isolated Forms', -  ['ital']='Italics', -  ['jalt']='Justification Alternatives', -  ['jp04']='JIS2004 Forms', -  ['jp78']='JIS78 Forms', -  ['jp83']='JIS83 Forms', -  ['jp90']='JIS90 Forms', -  ['kern']='Kerning', -  ['lfbd']='Left Bounds', -  ['liga']='Standard Ligatures', -  ['ljmo']='Leading Jamo Forms', -  ['lnum']='Lining Figures', -  ['locl']='Localized Forms', -  ['mark']='Mark Positioning', -  ['med2']='Medial Forms #2', -  ['medi']='Medial Forms', -  ['mgrk']='Mathematical Greek', -  ['mkmk']='Mark to Mark Positioning', -  ['mset']='Mark Positioning via Substitution', -  ['nalt']='Alternate Annotation Forms', -  ['nlck']='NLC Kanji Forms', -  ['nukt']='Nukta Forms', -  ['numr']='Numerators', -  ['onum']='Old Style Figures', -  ['opbd']='Optical Bounds', -  ['ordn']='Ordinals', -  ['ornm']='Ornaments', -  ['palt']='Proportional Alternate Width', -  ['pcap']='Petite Capitals', -  ['pnum']='Proportional Figures', -  ['pref']='Pre-base Forms', -  ['pres']='Pre-base Substitutions', -  ['pstf']='Post-base Forms', -  ['psts']='Post-base Substitutions', -  ['pwid']='Proportional Widths', -  ['qwid']='Quarter Widths', -  ['rand']='Randomize', -  ['rkrf']='Rakar Forms', -  ['rlig']='Required Ligatures', -  ['rphf']='Reph Form', -  ['rtbd']='Right Bounds', -  ['rtla']='Right-To-Left Alternates', -  ['rtlm']='Right To Left Math', -  ['ruby']='Ruby Notation Forms', -  ['salt']='Stylistic Alternates', -  ['sinf']='Scientific Inferiors', -  ['size']='Optical Size', -  ['smcp']='Small Capitals', -  ['smpl']='Simplified Forms', -  ['ss01']='Stylistic Set 1', -  ['ss02']='Stylistic Set 2', -  ['ss03']='Stylistic Set 3', -  ['ss04']='Stylistic Set 4', -  ['ss05']='Stylistic Set 5', -  ['ss06']='Stylistic Set 6', -  ['ss07']='Stylistic Set 7', -  ['ss08']='Stylistic Set 8', -  ['ss09']='Stylistic Set 9', -  ['ss10']='Stylistic Set 10', -  ['ss11']='Stylistic Set 11', -  ['ss12']='Stylistic Set 12', -  ['ss13']='Stylistic Set 13', -  ['ss14']='Stylistic Set 14', -  ['ss15']='Stylistic Set 15', -  ['ss16']='Stylistic Set 16', -  ['ss17']='Stylistic Set 17', -  ['ss18']='Stylistic Set 18', -  ['ss19']='Stylistic Set 19', -  ['ss20']='Stylistic Set 20', -  ['ssty']='Script Style', -  ['subs']='Subscript', -  ['sups']='Superscript', -  ['swsh']='Swash', -  ['titl']='Titling', -  ['tjmo']='Trailing Jamo Forms', -  ['tnam']='Traditional Name Forms', -  ['tnum']='Tabular Figures', -  ['trad']='Traditional Forms', -  ['twid']='Third Widths', -  ['unic']='Unicase', -  ['valt']='Alternate Vertical Metrics', -  ['vatu']='Vattu Variants', -  ['vert']='Vertical Writing', -  ['vhal']='Alternate Vertical Half Metrics', -  ['vjmo']='Vowel Jamo Forms', -  ['vkna']='Vertical Kana Alternates', -  ['vkrn']='Vertical Kerning', -  ['vpal']='Proportional Alternate Vertical Metrics', -  ['vrt2']='Vertical Rotation', -  ['zero']='Slashed Zero', -  ['trep']='Traditional TeX Replacements', -  ['tlig']='Traditional TeX Ligatures', -} -otf.tables.baselines={ -  ['hang']='Hanging baseline', -  ['icfb']='Ideographic character face bottom edge baseline', -  ['icft']='Ideographic character face tope edige baseline', -  ['ideo']='Ideographic em-box bottom edge baseline', -  ['idtp']='Ideographic em-box top edge baseline', -  ['math']='Mathmatical centered baseline', -  ['romn']='Roman baseline' -} -function otf.tables.to_tag(id) -  return stringformat("%4s",lower(id)) -end -local function resolve(tab,id) -  if tab and id then -    id=lower(id) -    return tab[id] or tab[gsub(id," ","")] or tab['dflt'] or '' -  else -    return "unknown" -  end -end -function otf.meanings.script(id) -  return resolve(otf.tables.scripts,id) -end -function otf.meanings.language(id) -  return resolve(otf.tables.languages,id) -end -function otf.meanings.feature(id) -  return resolve(otf.tables.features,id) -end -function otf.meanings.baseline(id) -  return resolve(otf.tables.baselines,id) -end -otf.tables.to_scripts=table.reverse_hash(otf.tables.scripts ) -otf.tables.to_languages=table.reverse_hash(otf.tables.languages) -otf.tables.to_features=table.reverse_hash(otf.tables.features ) -local scripts=otf.tables.scripts -local languages=otf.tables.languages -local features=otf.tables.features -local to_scripts=otf.tables.to_scripts -local to_languages=otf.tables.to_languages -local to_features=otf.tables.to_features -for k,v in next,to_features do -  local stripped=gsub(k,"%-"," ") -  to_features[stripped]=v -  local stripped=gsub(k,"[^a-zA-Z0-9]","") -  to_features[stripped]=v -end -for k,v in next,to_features do -  to_features[lower(k)]=v -end -otf.meanings.checkers={ -  rand=function(v) -    return v and "random" -  end -} -local checkers=otf.meanings.checkers -function otf.meanings.normalize(features) -  local h={} -  for k,v in next,features do -    k=lower(k) -    if k=="language" or k=="lang" then -      v=gsub(lower(v),"[^a-z0-9%-]","") -      if not languages[v] then -        h.language=to_languages[v] or "dflt" -      else -        h.language=v -      end -    elseif k=="script" then -      v=gsub(lower(v),"[^a-z0-9%-]","") -      if not scripts[v] then -        h.script=to_scripts[v] or "dflt" -      else -        h.script=v -      end -    else -      if type(v)=="string" then -        local b=v:is_boolean() -        if type(b)=="nil" then -          v=tonumber(v) or lower(v) -        else -          v=b -        end -      end -      k=to_features[k] or k -      local c=checkers[k] -      h[k]=c and c(v) or v -    end -  end -  return h -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-map']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local utf=unicode.utf8 -local match,format,find,concat,gsub,lower=string.match,string.format,string.find,table.concat,string.gsub,string.lower -local lpegmatch=lpeg.match -local utfbyte=utf.byte -local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) -local trace_unimapping=false trackers.register("otf.unimapping",function(v) trace_unimapping=v end) -local ctxcatcodes=tex and tex.ctxcatcodes -fonts=fonts   or {} -fonts.map=fonts.map or {} -local function load_lum_table(filename)  -  local lumname=file.replacesuffix(file.basename(filename),"lum") -  local lumfile=resolvers.find_file(lumname,"map") or "" -  if lumfile~="" and lfs.isfile(lumfile) then -    if trace_loading or trace_unimapping then -      logs.report("load otf","enhance: loading %s ",lumfile) -    end -    lumunic=dofile(lumfile) -    return lumunic,lumfile -  end -end -local hex=lpeg.R("AF","09") -local hexfour=(hex*hex*hex*hex)/function(s) return tonumber(s,16) end -local hexsix=(hex^1)/function(s) return tonumber(s,16) end -local dec=(lpeg.R("09")^1)/tonumber -local period=lpeg.P(".") -local unicode=lpeg.P("uni")*(hexfour*(period+lpeg.P(-1))*lpeg.Cc(false)+lpeg.Ct(hexfour^1)*lpeg.Cc(true)) -local ucode=lpeg.P("u")*(hexsix*(period+lpeg.P(-1))*lpeg.Cc(false)+lpeg.Ct(hexsix^1)*lpeg.Cc(true)) -local index=lpeg.P("index")*dec*lpeg.Cc(false) -local parser=unicode+ucode+index -local parsers={} -local function make_name_parser(str) -  if not str or str=="" then -    return parser -  else -    local p=parsers[str] -    if not p then -      p=lpeg.P(str)*period*dec*lpeg.Cc(false) -      parsers[str]=p -    end -    return p -  end -end -local function tounicode16(unicode) -  if unicode<0x10000 then -    return format("%04X",unicode) -  else -    return format("%04X%04X",unicode/1024+0xD800,unicode%1024+0xDC00) -  end -end -local function tounicode16sequence(unicodes) -  local t={} -  for l=1,#unicodes do -    local unicode=unicodes[l] -    if unicode<0x10000 then -      t[l]=format("%04X",unicode) -    else -      t[l]=format("%04X%04X",unicode/1024+0xD800,unicode%1024+0xDC00) -    end -  end -  return concat(t) -end -fonts.map.load_lum_table=load_lum_table -fonts.map.make_name_parser=make_name_parser -fonts.map.tounicode16=tounicode16 -fonts.map.tounicode16sequence=tounicode16sequence -local separator=lpeg.S("_.") -local other=lpeg.C((1-separator)^1) -local ligsplitter=lpeg.Ct(other*(separator*other)^0) -fonts.map.add_to_unicode=function(data,filename) -  local unicodes=data.luatex and data.luatex.unicodes -  if not unicodes then -    return -  end -  unicodes['space']=unicodes['space'] or 32 -  unicodes['hyphen']=unicodes['hyphen'] or 45 -  unicodes['zwj']=unicodes['zwj']  or 0x200D -  unicodes['zwnj']=unicodes['zwnj']  or 0x200C -  local tounicode,originals,ns,nl,private,unknown={},{},0,0,fonts.private,format("%04X",utfbyte("?")) -  data.luatex.tounicode,data.luatex.originals=tounicode,originals -  local lumunic,uparser,oparser -  if false then  -    lumunic=load_lum_table(filename) -    lumunic=lumunic and lumunic.tounicode -  end -  local cidinfo,cidnames,cidcodes=data.cidinfo -  local usedmap=cidinfo and cidinfo.usedname -  usedmap=usedmap and lower(usedmap) -  usedmap=usedmap and fonts.cid.map[usedmap] -  if usedmap then -    oparser=usedmap and make_name_parser(cidinfo.ordering) -    cidnames=usedmap.names -    cidcodes=usedmap.unicodes -  end -  uparser=make_name_parser() -  local aglmap=fonts.map and fonts.map.agl_to_unicode -  for index,glyph in next,data.glyphs do -    local name,unic=glyph.name,glyph.unicode or -1  -    if unic==-1 or unic>=private or (unic>=0xE000 and unic<=0xF8FF) or unic==0xFFFE or unic==0xFFFF then -      local unicode=(lumunic and lumunic[name]) or (aglmap and aglmap[name]) -      if unicode then -        originals[index],tounicode[index],ns=unicode,tounicode16(unicode),ns+1 -      end -      if (not unicode) and usedmap then -        local foundindex=lpegmatch(oparser,name) -        if foundindex then -          unicode=cidcodes[foundindex]  -          if unicode then -            originals[index],tounicode[index],ns=unicode,tounicode16(unicode),ns+1 -          else -            local reference=cidnames[foundindex]  -            if reference then -              local foundindex=lpegmatch(oparser,reference) -              if foundindex then -                unicode=cidcodes[foundindex] -                if unicode then -                  originals[index],tounicode[index],ns=unicode,tounicode16(unicode),ns+1 -                end -              end -              if not unicode then -                local foundcodes,multiple=lpegmatch(uparser,reference) -                if foundcodes then -                  if multiple then -                    originals[index],tounicode[index],nl,unicode=foundcodes,tounicode16sequence(foundcodes),nl+1,true -                  else -                    originals[index],tounicode[index],ns,unicode=foundcodes,tounicode16(foundcodes),ns+1,foundcodes -                  end -                end -              end -            end -          end -        end -      end -      if not unicode then -        local split=lpegmatch(ligsplitter,name) -        local nplit=(split and #split) or 0 -        if nplit==0 then -        elseif nplit==1 then -          local base=split[1] -          unicode=unicodes[base] or (aglmap and aglmap[base]) -          if unicode then -            if type(unicode)=="table" then -              unicode=unicode[1] -            end -            originals[index],tounicode[index],ns=unicode,tounicode16(unicode),ns+1 -          end -        else -          local t={} -          for l=1,nplit do -            local base=split[l] -            local u=unicodes[base] or (aglmap and aglmap[base]) -            if not u then -              break -            elseif type(u)=="table" then -              t[#t+1]=u[1] -            else -              t[#t+1]=u -            end -          end -          if #t>0 then  -            originals[index],tounicode[index],nl,unicode=t,tounicode16sequence(t),nl+1,true -          end -        end -      end -      if not unicode then -        local foundcodes,multiple=lpegmatch(uparser,name) -        if foundcodes then -          if multiple then -            originals[index],tounicode[index],nl,unicode=foundcodes,tounicode16sequence(foundcodes),nl+1,true -          else -            originals[index],tounicode[index],ns,unicode=foundcodes,tounicode16(foundcodes),ns+1,foundcodes -          end -        end -      end -      if not unicode then -        originals[index],tounicode[index]=0xFFFD,"FFFD" -      end -    end -  end -  if trace_unimapping then -    for index,glyph in table.sortedhash(data.glyphs) do -      local toun,name,unic=tounicode[index],glyph.name,glyph.unicode or -1  -      if toun then -        logs.report("load otf","internal: 0x%05X, name: %s, unicode: 0x%05X, tounicode: %s",index,name,unic,toun) -      else -        logs.report("load otf","internal: 0x%05X, name: %s, unicode: 0x%05X",index,name,unic) -      end -    end -  end -  if trace_loading and (ns>0 or nl>0) then -    logs.report("load otf","enhance: %s tounicode entries added (%s ligatures)",nl+ns,ns) -  end -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-otf']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local utf=unicode.utf8 -local concat,utfbyte=table.concat,utf.byte -local format,gmatch,gsub,find,match,lower,strip=string.format,string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip -local type,next,tonumber,tostring=type,next,tonumber,tostring -local abs=math.abs -local getn=table.getn -local lpegmatch=lpeg.match -local trace_private=false trackers.register("otf.private",function(v) trace_private=v end) -local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) -local trace_features=false trackers.register("otf.features",function(v) trace_features=v end) -local trace_dynamics=false trackers.register("otf.dynamics",function(v) trace_dynamics=v end) -local trace_sequences=false trackers.register("otf.sequences",function(v) trace_sequences=v end) -local trace_math=false trackers.register("otf.math",function(v) trace_math=v end) -local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) -fonts=fonts   or {} -fonts.otf=fonts.otf or {} -fonts.tfm=fonts.tfm or {} -local otf=fonts.otf -local tfm=fonts.tfm -local fontdata=fonts.ids -otf.tables=otf.tables      or {}  -otf.meanings=otf.meanings     or {}  -otf.tables.features=otf.tables.features or {}  -otf.tables.languages=otf.tables.languages or {}  -otf.tables.scripts=otf.tables.scripts  or {}  -otf.features=otf.features     or {} -otf.features.list=otf.features.list  or {} -otf.features.default=otf.features.default or {} -otf.enhancers=otf.enhancers    or {} -otf.glists={ "gsub","gpos" } -otf.version=2.653  -otf.pack=true  -otf.syncspace=true -otf.notdef=false -otf.cache=containers.define("fonts","otf",otf.version,true) -otf.cleanup_aat=false  -local wildcard="*" -local default="dflt" -otf.tables.global_fields=table.tohash { -  "lookups", -  "glyphs", -  "subfonts", -  "luatex", -  "pfminfo", -  "cidinfo", -  "tables", -  "names", -  "unicodes", -  "names", -  "anchor_classes", -  "kern_classes", -  "gpos", -  "gsub" -} -otf.tables.valid_fields={ -  "anchor_classes", -  "ascent", -  "cache_version", -  "cidinfo", -  "copyright", -  "creationtime", -  "descent", -  "design_range_bottom", -  "design_range_top", -  "design_size", -  "encodingchanged", -  "extrema_bound", -  "familyname", -  "fontname", -  "fontstyle_id", -  "fontstyle_name", -  "fullname", -  "glyphs", -  "hasvmetrics", -  "head_optimized_for_cleartype", -  "horiz_base", -  "issans", -  "isserif", -  "italicangle", -  "kerns", -  "lookups", -  "macstyle", -  "modificationtime", -  "onlybitmaps", -  "origname", -  "os2_version", -  "pfminfo", -  "private", -  "serifcheck", -  "sfd_version", -  "strokedfont", -  "strokewidth", -  "subfonts", -  "table_version", -  "ttf_tables", -  "uni_interp", -  "uniqueid", -  "units_per_em", -  "upos", -  "use_typo_metrics", -  "uwidth", -  "validation_state", -  "verbose", -  "version", -  "vert_base", -  "weight", -  "weight_width_slope_only", -  "xuid", -} -local function load_featurefile(ff,featurefile) -  if featurefile then -    featurefile=resolvers.find_file(file.addsuffix(featurefile,'fea'),'fea') -    if featurefile and featurefile~="" then -      if trace_loading then -        logs.report("load otf","featurefile: %s",featurefile) -      end -      fontloader.apply_featurefile(ff,featurefile) -    end -  end -end -function otf.enhance(name,data,filename,verbose) -  local enhancer=otf.enhancers[name] -  if enhancer then -    if (verbose~=nil and verbose) or trace_loading then -      logs.report("load otf","enhance: %s (%s)",name,filename) -    end -    enhancer(data,filename) -  end -end -local enhancers={ -  "patch bugs", -  "merge cid fonts","prepare unicode","cleanup ttf tables","compact glyphs","reverse coverage", -  "cleanup aat","enrich with features","add some missing characters", -  "reorganize mark classes", -  "reorganize kerns", -  "flatten glyph lookups","flatten anchor tables","flatten feature tables", -  "simplify glyph lookups", -  "prepare luatex tables", -  "analyse features","rehash features", -  "analyse anchors","analyse marks","analyse unicodes","analyse subtables", -  "check italic correction","check math", -  "share widths", -  "strip not needed data", -  "migrate metadata", -  "check math parameters", -} -function otf.load(filename,format,sub,featurefile) -  local name=file.basename(file.removesuffix(filename)) -  local attr=lfs.attributes(filename) -  local size,time=attr.size or 0,attr.modification or 0 -  if featurefile then -    local fattr=lfs.attributes(featurefile) -    local fsize,ftime=fattr and fattr.size or 0,fattr and fattr.modification or 0 -    name=name.."@"..file.removesuffix(file.basename(featurefile))..ftime..fsize -  end -  if sub=="" then sub=false end -  local hash=name -  if sub then -    hash=hash.."-"..sub -  end -  hash=containers.cleanname(hash) -  local data=containers.read(otf.cache,hash) -  if not data or data.verbose~=fonts.verbose or data.size~=size or data.time~=time then -    logs.report("load otf","loading: %s (hash: %s)",filename,hash) -    local ff,messages -    if sub then -      ff,messages=fontloader.open(filename,sub) -    else -      ff,messages=fontloader.open(filename) -    end -    if trace_loading and messages and #messages>0 then -      if type(messages)=="string" then -        logs.report("load otf","warning: %s",messages) -      else -        for m=1,#messages do -          logs.report("load otf","warning: %s",tostring(messages[m])) -        end -      end -    else -      logs.report("load otf","font loaded okay") -    end -    if ff then -      load_featurefile(ff,featurefile) -      data=fontloader.to_table(ff) -      fontloader.close(ff) -      if data then -        logs.report("load otf","file size: %s",size) -        logs.report("load otf","enhancing ...") -        for e=1,#enhancers do -          otf.enhance(enhancers[e],data,filename) -          io.flush()  -        end -        if otf.pack and not fonts.verbose then -          otf.enhance("pack",data,filename) -        end -        data.size=size -        data.time=time -        data.verbose=fonts.verbose -        logs.report("load otf","saving in cache: %s",filename) -        data=containers.write(otf.cache,hash,data) -        collectgarbage("collect") -        data=containers.read(otf.cache,hash)  -        collectgarbage("collect") -      else -        logs.report("load otf","loading failed (table conversion error)") -      end -    else -      logs.report("load otf","loading failed (file read error)") -    end -  end -  if data then -    if trace_defining then -      logs.report("define font","loading from cache: %s",hash) -    end -    otf.enhance("unpack",data,filename,false)  -    otf.add_dimensions(data) -    if trace_sequences then -      otf.show_feature_order(data,filename) -    end -  end -  return data -end -function otf.add_dimensions(data) -  if data then -    local force=otf.notdef -    local luatex=data.luatex -    local defaultwidth=luatex.defaultwidth or 0 -    local defaultheight=luatex.defaultheight or 0 -    local defaultdepth=luatex.defaultdepth or 0 -    for _,d in next,data.glyphs do -      local bb,wd=d.boundingbox,d.width -      if not wd then -        d.width=defaultwidth -      elseif wd~=0 and d.class=="mark" then -        d.width=-wd -      end -      if force and not d.name then -        d.name=".notdef" -      end -      if bb then -        local ht,dp=bb[4],-bb[2] -        if ht==0 or ht<0 then -        else -          d.height=ht -        end -        if dp==0 or dp<0 then -        else -          d.depth=dp -        end -      end -    end -  end -end -function otf.show_feature_order(otfdata,filename) -  local sequences=otfdata.luatex.sequences -  if sequences and #sequences>0 then -    if trace_loading then -      logs.report("otf check","font %s has %s sequences",filename,#sequences) -      logs.report("otf check"," ") -    end -    for nos=1,#sequences do -      local sequence=sequences[nos] -      local typ=sequence.type or "no-type" -      local name=sequence.name or "no-name" -      local subtables=sequence.subtables or { "no-subtables" } -      local features=sequence.features -      if trace_loading then -        logs.report("otf check","%3i  %-15s  %-20s  [%s]",nos,name,typ,concat(subtables,",")) -      end -      if features then -        for feature,scripts in next,features do -          local tt={} -          for script,languages in next,scripts do -            local ttt={} -            for language,_ in next,languages do -              ttt[#ttt+1]=language -            end -            tt[#tt+1]=format("[%s: %s]",script,concat(ttt," ")) -          end -          if trace_loading then -            logs.report("otf check","       %s: %s",feature,concat(tt," ")) -          end -        end -      end -    end -    if trace_loading then -      logs.report("otf check","\n") -    end -  elseif trace_loading then -    logs.report("otf check","font %s has no sequences",filename) -  end -end -otf.enhancers["reorganize mark classes"]=function(data,filename) -  if data.mark_classes then -    local unicodes=data.luatex.unicodes -    local reverse={} -    for name,class in next,data.mark_classes do -      local t={} -      for s in gmatch(class,"[^ ]+") do -        local us=unicodes[s] -        if type(us)=="table" then -          for u=1,#us do -            t[us[u]]=true -          end -        else -          t[us]=true -        end -      end -      reverse[name]=t -    end -    data.luatex.markclasses=reverse -    data.mark_classes=nil -  end -end -otf.enhancers["prepare luatex tables"]=function(data,filename) -  data.luatex=data.luatex or {} -  local luatex=data.luatex -  luatex.filename=filename -  luatex.version=otf.version -  luatex.creator="context mkiv" -end -otf.enhancers["cleanup aat"]=function(data,filename) -  if otf.cleanup_aat then -  end -end -local function analyze_features(g,features) -  if g then -    local t,done={},{} -    for k=1,#g do -      local f=features or g[k].features -      if f then -        for k=1,#f do -          local tag=f[k].tag -          if not done[tag] then -            t[#t+1]=tag -            done[tag]=true -          end -        end -      end -    end -    if #t>0 then -      return t -    end -  end -  return nil -end -otf.enhancers["analyse features"]=function(data,filename) -end -otf.enhancers["rehash features"]=function(data,filename) -  local features={} -  data.luatex.features=features -  for k,what in next,otf.glists do -    local dw=data[what] -    if dw then -      local f={} -      features[what]=f -      for i=1,#dw do -        local d=dw[i] -        local dfeatures=d.features -        if dfeatures then -          for i=1,#dfeatures do -            local df=dfeatures[i] -            local tag=strip(lower(df.tag)) -            local ft=f[tag] if not ft then ft={} f[tag]=ft end -            local dscripts=df.scripts -            for script,languages in next,dscripts do -              script=strip(lower(script)) -              local fts=ft[script] if not fts then fts={} ft[script]=fts end -              for i=1,#languages do -                fts[strip(lower(languages[i]))]=true -              end -            end -          end -        end -      end -    end -  end -end -otf.enhancers["analyse anchors"]=function(data,filename) -  local classes=data.anchor_classes -  local luatex=data.luatex -  local anchor_to_lookup,lookup_to_anchor={},{} -  luatex.anchor_to_lookup,luatex.lookup_to_anchor=anchor_to_lookup,lookup_to_anchor -  if classes then -    for c=1,#classes do -      local class=classes[c] -      local anchor=class.name -      local lookups=class.lookup -      if type(lookups)~="table" then -        lookups={ lookups } -      end -      local a=anchor_to_lookup[anchor] -      if not a then a={} anchor_to_lookup[anchor]=a end -      for l=1,#lookups do -        local lookup=lookups[l] -        local l=lookup_to_anchor[lookup] -        if not l then l={} lookup_to_anchor[lookup]=l end -        l[anchor]=true -        a[lookup]=true -      end -    end -  end -end -otf.enhancers["analyse marks"]=function(data,filename) -  local glyphs=data.glyphs -  local marks={} -  data.luatex.marks=marks -  for unicode,index in next,data.luatex.indices do -    local glyph=glyphs[index] -    if glyph.class=="mark" then -      marks[unicode]=true -    end -  end -end -otf.enhancers["analyse unicodes"]=fonts.map.add_to_unicode -otf.enhancers["analyse subtables"]=function(data,filename) -  data.luatex=data.luatex or {} -  local luatex=data.luatex -  local sequences={} -  local lookups={} -  luatex.sequences=sequences -  luatex.lookups=lookups -  for _,g in next,{ data.gsub,data.gpos } do -    for k=1,#g do -      local gk=g[k] -      local typ=gk.type -      if typ=="gsub_contextchain" or typ=="gpos_contextchain" then -        gk.chain=1 -      elseif typ=="gsub_reversecontextchain" or typ=="gpos_reversecontextchain" then -        gk.chain=-1 -      else -        gk.chain=0 -      end -      local features=gk.features -      if features then -        sequences[#sequences+1]=gk -        local t={} -        for f=1,#features do -          local feature=features[f] -          local hash={} -          for s,languages in next,feature.scripts do -            s=lower(s) -            local h=hash[s] -            if not h then h={} hash[s]=h end -            for l=1,#languages do -              h[strip(lower(languages[l]))]=true -            end -          end -          t[feature.tag]=hash -        end -        gk.features=t -      else -        lookups[gk.name]=gk -        gk.name=nil -      end -      local subtables=gk.subtables -      if subtables then -        local t={} -        for s=1,#subtables do -          local subtable=subtables[s] -          local name=subtable.name -          t[#t+1]=name -        end -        gk.subtables=t -      end -      local flags=gk.flags -      if flags then -        gk.flags={  -          (flags.ignorecombiningmarks and "mark")   or false, -          (flags.ignoreligatures   and "ligature") or false, -          (flags.ignorebaseglyphs   and "base")   or false, -           flags.r2l                 or false, -        } -        if flags.mark_class then -          gk.markclass=luatex.markclasses[flags.mark_class] -        end -      end -    end -  end -end -otf.enhancers["merge cid fonts"]=function(data,filename) -  if data.subfonts then -    if data.glyphs and next(data.glyphs) then -      logs.report("load otf","replacing existing glyph table due to subfonts") -    end -    local cidinfo=data.cidinfo -    local verbose=fonts.verbose -    if cidinfo.registry then -      local cidmap,cidname=fonts.cid.getmap(cidinfo.registry,cidinfo.ordering,cidinfo.supplement) -      if cidmap then -        cidinfo.usedname=cidmap.usedname -        local glyphs,uni_to_int,int_to_uni,nofnames,nofunicodes={},{},{},0,0 -        local unicodes,names=cidmap.unicodes,cidmap.names -        for n,subfont in next,data.subfonts do -          for index,g in next,subfont.glyphs do -            if not next(g) then -            else -              local unicode,name=unicodes[index],names[index] -              g.cidindex=n -              g.boundingbox=g.boundingbox  -              g.name=g.name or name or "unknown" -              if unicode then -                uni_to_int[unicode]=index -                int_to_uni[index]=unicode -                nofunicodes=nofunicodes+1 -                g.unicode=unicode -              elseif name then -                nofnames=nofnames+1 -                g.unicode=-1 -              end -              glyphs[index]=g -            end -          end -          subfont.glyphs=nil -        end -        if trace_loading then -          logs.report("load otf","cid font remapped, %s unicode points, %s symbolic names, %s glyphs",nofunicodes,nofnames,nofunicodes+nofnames) -        end -        data.glyphs=glyphs -        data.map=data.map or {} -        data.map.map=uni_to_int -        data.map.backmap=int_to_uni -      elseif trace_loading then -        logs.report("load otf","unable to remap cid font, missing cid file for %s",filename) -      end -    elseif trace_loading then -      logs.report("load otf","font %s has no glyphs",filename) -    end -  end -end -otf.enhancers["prepare unicode"]=function(data,filename) -  local luatex=data.luatex -  if not luatex then luatex={} data.luatex=luatex end -  local indices,unicodes,multiples,internals={},{},{},{} -  local glyphs=data.glyphs -  local mapmap=data.map -  if not mapmap then -    logs.report("load otf","no map in %s",filename) -    mapmap={} -    data.map={ map=mapmap } -  elseif not mapmap.map then -    logs.report("load otf","no unicode map in %s",filename) -    mapmap={} -    data.map.map=mapmap -  else -    mapmap=mapmap.map -  end -  local criterium=fonts.private -  local private=fonts.private -  for index,glyph in next,glyphs do -    if index>0 then -      local name=glyph.name -      if name then -        local unicode=glyph.unicode -        if unicode==-1 or unicode>=criterium then -          glyph.unicode=private -          indices[private]=index -          unicodes[name]=private -          internals[index]=true -          if trace_private then -            logs.report("load otf","enhance: glyph %s at index U+%04X is moved to private unicode slot U+%04X",name,index,private) -          end -          private=private+1 -        else -          indices[unicode]=index -          unicodes[name]=unicode -        end -      end -    end -  end -  for unicode,index in next,mapmap do -    if not internals[index] then -      local name=glyphs[index].name -      if name then -        local un=unicodes[name] -        if not un then -          unicodes[name]=unicode  -        elseif type(un)=="number" then -          if un~=unicode then -            multiples[#multiples+1]=name -            unicodes[name]={ un,unicode } -            indices[unicode]=index -          end -        else -          local ok=false -          for u=1,#un do -            if un[u]==unicode then -              ok=true -              break -            end -          end -          if not ok then -            multiples[#multiples+1]=name -            un[#un+1]=unicode -            indices[unicode]=index -          end -        end -      end -    end -  end -  if trace_loading then -    if #multiples>0 then -      logs.report("load otf","%s glyph are reused: %s",#multiples,concat(multiples," ")) -    else -      logs.report("load otf","no glyph are reused") -    end -  end -  luatex.indices=indices -  luatex.unicodes=unicodes -  luatex.private=private -end -otf.enhancers["cleanup ttf tables"]=function(data,filename) -  local ttf_tables=data.ttf_tables -  if ttf_tables then -    for k=1,#ttf_tables do -      if ttf_tables[k].data then ttf_tables[k].data="deleted" end -    end -  end -  data.ttf_tab_saved=nil -end -otf.enhancers["compact glyphs"]=function(data,filename) -  table.compact(data.glyphs)  -  if data.subfonts then -    for _,subfont in next,data.subfonts do -      table.compact(subfont.glyphs)  -    end -  end -end -otf.enhancers["reverse coverage"]=function(data,filename) -  if data.lookups then -    for _,v in next,data.lookups do -      if v.rules then -        for _,vv in next,v.rules do -          local c=vv.coverage -          if c and c.before then -            c.before=table.reverse(c.before) -          end -        end -      end -    end -  end -end -otf.enhancers["check italic correction"]=function(data,filename) -  local glyphs=data.glyphs -  local ok=false -  for index,glyph in next,glyphs do -    local ic=glyph.italic_correction -    if ic then -      if ic~=0 then -        glyph.italic=ic -      end -      glyph.italic_correction=nil -      ok=true -    end -  end -  otf.tables.valid_fields[#otf.tables.valid_fields+1]="has_italic" -  data.has_italic=true -end -otf.enhancers["check math"]=function(data,filename) -  if data.math then -    local glyphs=data.glyphs -    local unicodes=data.luatex.unicodes -    for index,glyph in next,glyphs do -      local mk=glyph.mathkern -      local hv=glyph.horiz_variants -      local vv=glyph.vert_variants -      if mk or hv or vv then -        local math={} -        glyph.math=math -        if mk then -          for k,v in next,mk do -            if not next(v) then -              mk[k]=nil -            end -          end -          math.kerns=mk -          glyph.mathkern=nil -        end -        if hv then -          math.horiz_variants=hv.variants -          local p=hv.parts -          if p and #p>0 then -            for i=1,#p do -              local pi=p[i] -              pi.glyph=unicodes[pi.component] or 0 -            end -            math.horiz_parts=p -          end -          local ic=hv.italic_correction -          if ic and ic~=0 then -            math.horiz_italic_correction=ic -          end -          glyph.horiz_variants=nil -        end -        if vv then -          local uc=unicodes[index] -          math.vert_variants=vv.variants -          local p=vv.parts -          if p and #p>0 then -            for i=1,#p do -              local pi=p[i] -              pi.glyph=unicodes[pi.component] or 0 -            end -            math.vert_parts=p -          end -          local ic=vv.italic_correction -          if ic and ic~=0 then -            math.vert_italic_correction=ic -          end -          glyph.vert_variants=nil -        end -        local ic=glyph.italic_correction -        if ic then -          if ic~=0 then -            math.italic_correction=ic -          end -          glyph.italic_correction=nil -        end -      end -    end -  end -end -otf.enhancers["share widths"]=function(data,filename) -  local glyphs=data.glyphs -  local widths={} -  for index,glyph in next,glyphs do -    local width=glyph.width -    widths[width]=(widths[width] or 0)+1 -  end -  local wd,most=0,1 -  for k,v in next,widths do -    if v>most then -      wd,most=k,v -    end -  end -  if most>1000 then -    if trace_loading then -      logs.report("load otf","most common width: %s (%s times), sharing (cjk font)",wd,most) -    end -    for k,v in next,glyphs do -      if v.width==wd then -        v.width=nil -      end -    end -    data.luatex.defaultwidth=wd -  end -end -otf.enhancers["reorganize kerns"]=function(data,filename) -  local glyphs,mapmap,unicodes=data.glyphs,data.luatex.indices,data.luatex.unicodes -  local mkdone=false -  local function do_it(lookup,first_unicode,kerns) -    local glyph=glyphs[mapmap[first_unicode]] -    if glyph then -      local mykerns=glyph.mykerns -      if not mykerns then -        mykerns={}  -        glyph.mykerns=mykerns -      end -      local lookupkerns=mykerns[lookup] -      if not lookupkerns then -        lookupkerns={} -        mykerns[lookup]=lookupkerns -      end -      for second_unicode,kern in next,kerns do -        lookupkerns[second_unicode]=kern -      end -    elseif trace_loading then -      logs.report("load otf","no glyph data for U+%04X",first_unicode) -    end -  end -  for index,glyph in next,glyphs do -    if glyph.kerns then -      local mykerns={} -      for k,v in next,glyph.kerns do -        local vc,vo,vl=v.char,v.off,v.lookup -        if vc and vo and vl then  -          local uvc=unicodes[vc] -          if not uvc then -            if trace_loading then -              logs.report("load otf","problems with unicode %s of kern %s at glyph %s",vc,k,index) -            end -          else -            if type(vl)~="table" then -              vl={ vl } -            end -            for l=1,#vl do -              local vll=vl[l] -              local mkl=mykerns[vll] -              if not mkl then -                mkl={} -                mykerns[vll]=mkl -              end -              if type(uvc)=="table" then -                for u=1,#uvc do -                  mkl[uvc[u]]=vo -                end -              else -                mkl[uvc]=vo -              end -            end -          end -        end -      end -      glyph.mykerns=mykerns -      glyph.kerns=nil  -      mkdone=true -    end -  end -  if trace_loading and mkdone then -    logs.report("load otf","replacing 'kerns' tables by 'mykerns' tables") -  end -  if data.kerns then -    if trace_loading then -      logs.report("load otf","removing global 'kern' table") -    end -    data.kerns=nil -  end -  local dgpos=data.gpos -  if dgpos then -    local separator=lpeg.P(" ") -    local other=((1-separator)^0)/unicodes -    local splitter=lpeg.Ct(other*(separator*other)^0) -    for gp=1,#dgpos do -      local gpos=dgpos[gp] -      local subtables=gpos.subtables -      if subtables then -        for s=1,#subtables do -          local subtable=subtables[s] -          local kernclass=subtable.kernclass  -          if kernclass then  -            local split={}  -            for k=1,#kernclass do -              local kcl=kernclass[k] -              local firsts,seconds,offsets,lookups=kcl.firsts,kcl.seconds,kcl.offsets,kcl.lookup  -              if type(lookups)~="table" then -                lookups={ lookups } -              end -              local maxfirsts,maxseconds=#firsts,#seconds -              for _,s in next,firsts do -                split[s]=split[s] or lpegmatch(splitter,s) -              end -              for _,s in next,seconds do -                split[s]=split[s] or lpegmatch(splitter,s) -              end -              for l=1,#lookups do -                local lookup=lookups[l] -                for fk=1,#firsts do -                  local fv=firsts[fk] -                  local splt=split[fv] -                  if splt then -                    local kerns,baseoffset={},(fk-1)*maxseconds -                    for sk=2,maxseconds do -                      local sv=seconds[sk] -                      local splt=split[sv] -                      if splt then -                        local offset=offsets[baseoffset+sk] -                        if offset then -                          for i=1,#splt do -                            local second_unicode=splt[i] -                            if tonumber(second_unicode) then -                              kerns[second_unicode]=offset -                            else for s=1,#second_unicode do -                              kerns[second_unicode[s]]=offset -                            end end -                          end -                        end -                      end -                    end -                    for i=1,#splt do -                      local first_unicode=splt[i] -                      if tonumber(first_unicode) then -                        do_it(lookup,first_unicode,kerns) -                      else for f=1,#first_unicode do -                        do_it(lookup,first_unicode[f],kerns) -                      end end -                    end -                  end -                end -              end -            end -            subtable.comment="The kernclass table is merged into mykerns in the indexed glyph tables." -            subtable.kernclass={} -          end -        end -      end -    end -  end -end -otf.enhancers["strip not needed data"]=function(data,filename) -  local verbose=fonts.verbose -  local int_to_uni=data.luatex.unicodes -  for k,v in next,data.glyphs do -    local d=v.dependents -    if d then v.dependents=nil end -    local a=v.altuni -    if a then v.altuni=nil end -    if verbose then -      local code=int_to_uni[k] -      if code then -        local vu=v.unicode -        if not vu then -          v.unicode=code -        elseif type(vu)=="table" then -          if vu[#vu]==code then -          else -            vu[#vu+1]=code -          end -        elseif vu~=code then -          v.unicode={ vu,code } -        end -      end -    else -      v.unicode=nil -      v.index=nil -    end -  end -  data.luatex.comment="Glyph tables have their original index. When present, mykern tables are indexed by unicode." -  data.map=nil -  data.names=nil  -  data.glyphcnt=nil -  data.glyphmax=nil -  if true then -    data.gpos=nil -    data.gsub=nil -    data.anchor_classes=nil -  end -end -otf.enhancers["migrate metadata"]=function(data,filename) -  local global_fields=otf.tables.global_fields -  local metadata={} -  for k,v in next,data do -    if not global_fields[k] then -      metadata[k]=v -      data[k]=nil -    end -  end -  data.metadata=metadata -  local pfminfo=data.pfminfo -  metadata.isfixedpitch=metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose["proportion"]=="Monospaced") -  metadata.charwidth=pfminfo and pfminfo.avgwidth -end -local private_math_parameters={ -  "FractionDelimiterSize", -  "FractionDelimiterDisplayStyleSize", -} -otf.enhancers["check math parameters"]=function(data,filename) -  local mathdata=data.metadata.math -  if mathdata then -    for m=1,#private_math_parameters do -      local pmp=private_math_parameters[m] -      if not mathdata[pmp] then -        if trace_loading then -          logs.report("load otf","setting math parameter '%s' to 0",pmp) -        end -        mathdata[pmp]=0 -      end -    end -  end -end -otf.enhancers["flatten glyph lookups"]=function(data,filename) -  for k,v in next,data.glyphs do -    local lookups=v.lookups -    if lookups then -      for kk,vv in next,lookups do -        for kkk=1,#vv do -          local vvv=vv[kkk] -          local s=vvv.specification -          if s then -            local t=vvv.type -            if t=="ligature" then -              vv[kkk]={ "ligature",s.components,s.char } -            elseif t=="alternate" then -              vv[kkk]={ "alternate",s.components } -            elseif t=="substitution" then -              vv[kkk]={ "substitution",s.variant } -            elseif t=="multiple" then -              vv[kkk]={ "multiple",s.components } -            elseif t=="position" then -              vv[kkk]={ "position",{ s.x or 0,s.y or 0,s.h or 0,s.v or 0 } } -            elseif t=="pair" then -              local one,two,paired=s.offsets[1],s.offsets[2],s.paired or "" -              if one then -                if two then -                  vv[kkk]={ "pair",paired,{ one.x or 0,one.y or 0,one.h or 0,one.v or 0 },{ two.x or 0,two.y or 0,two.h or 0,two.v or 0 } } -                else -                  vv[kkk]={ "pair",paired,{ one.x or 0,one.y or 0,one.h or 0,one.v or 0 } } -                end -              else -                if two then -                  vv[kkk]={ "pair",paired,{},{ two.x or 0,two.y or 0,two.h or 0,two.v or 0} }  -                else -                  vv[kkk]={ "pair",paired } -                end -              end -            else -              if trace_loading then -                logs.report("load otf","flattening needed, report to context list") -              end -              for a,b in next,s do -                if trace_loading and vvv[a] then -                  logs.report("load otf","flattening conflict, report to context list") -                end -                vvv[a]=b -              end -              vvv.specification=nil -            end -          end -        end -      end -    end -  end -end -otf.enhancers["simplify glyph lookups"]=function(data,filename) -  for k,v in next,data.glyphs do -    local lookups=v.lookups -    if lookups then -      local slookups,mlookups -      for kk,vv in next,lookups do -        if #vv==1 then -          if not slookups then -            slookups={} -            v.slookups=slookups -          end -          slookups[kk]=vv[1] -        else -          if not mlookups then -            mlookups={} -            v.mlookups=mlookups -          end -          mlookups[kk]=vv -        end -      end -      v.lookups=nil -    end -  end -end -otf.enhancers["flatten anchor tables"]=function(data,filename) -  for k,v in next,data.glyphs do -    if v.anchors then -      for kk,vv in next,v.anchors do -        for kkk,vvv in next,vv do -          if vvv.x or vvv.y then -            vv[kkk]={ vvv.x or 0,vvv.y or 0 } -          else -            for kkkk=1,#vvv do -              local vvvv=vvv[kkkk] -              vvv[kkkk]={ vvvv.x or 0,vvvv.y or 0 } -            end -          end -        end -      end -    end -  end -end -otf.enhancers["flatten feature tables"]=function(data,filename) -  for _,tag in next,otf.glists do -    if data[tag] then -      if trace_loading then -        logs.report("load otf","flattening %s table",tag) -      end -      for k,v in next,data[tag] do -        local features=v.features -        if features then -          for kk=1,#features do -            local vv=features[kk] -            local t={} -            local scripts=vv.scripts -            for kkk=1,#scripts do -              local vvv=scripts[kkk] -              t[vvv.script]=vvv.langs -            end -            vv.scripts=t -          end -        end -      end -    end -  end -end -otf.enhancers.patches=otf.enhancers.patches or {} -otf.enhancers["patch bugs"]=function(data,filename) -  local basename=file.basename(lower(filename)) -  for pattern,action in next,otf.enhancers.patches do -    if find(basename,pattern) then -      action(data,filename) -    end -  end -end -fonts.otf.enhancers["enrich with features"]=function(data,filename) -end -function otf.features.register(name,default) -  otf.features.list[#otf.features.list+1]=name -  otf.features.default[name]=default -end -function otf.set_features(tfmdata,features) -  local processes={} -  if features and next(features) then -    local lists={  -      fonts.triggers, -      fonts.processors, -      fonts.manipulators, -    } -    local mode=tfmdata.mode or fonts.mode  -    local initializers=fonts.initializers -    local fi=initializers[mode] -    if fi then -      local fiotf=fi.otf -      if fiotf then -        local done={} -        for l=1,4 do -          local list=lists[l] -          if list then -            for i=1,#list do -              local f=list[i] -              local value=features[f] -              if value and fiotf[f] then  -                if not done[f] then  -                  if trace_features then -                    logs.report("define otf","initializing feature %s to %s for mode %s for font %s",f,tostring(value),mode or 'unknown',tfmdata.fullname or 'unknown') -                  end -                  fiotf[f](tfmdata,value)  -                  mode=tfmdata.mode or fonts.mode  -                  local im=initializers[mode] -                  if im then -                    fiotf=initializers[mode].otf -                  end -                  done[f]=true -                end -              end -            end -          end -        end -      end -    end -    local fm=fonts.methods[mode]  -    if fm then -      local fmotf=fm.otf -      if fmotf then -        for l=1,4 do -          local list=lists[l] -          if list then -            for i=1,#list do -              local f=list[i] -              if fmotf[f] then  -                if trace_features then -                  logs.report("define otf","installing feature handler %s for mode %s for font %s",f,mode or 'unknown',tfmdata.fullname or 'unknown') -                end -                processes[#processes+1]=fmotf[f] -              end -            end -          end -        end -      end -    else -    end -  end -  return processes,features -end -function otf.otf_to_tfm(specification) -  local name=specification.name -  local sub=specification.sub -  local filename=specification.filename -  local format=specification.format -  local features=specification.features.normal -  local cache_id=specification.hash -  local tfmdata=containers.read(tfm.cache,cache_id) -  if not tfmdata then -    local otfdata=otf.load(filename,format,sub,features and features.featurefile) -    if otfdata and next(otfdata) then -      otfdata.shared=otfdata.shared or { -        featuredata={}, -        anchorhash={}, -        initialized=false, -      } -      tfmdata=otf.copy_to_tfm(otfdata,cache_id) -      if tfmdata and next(tfmdata) then -        tfmdata.unique=tfmdata.unique or {} -        tfmdata.shared=tfmdata.shared or {}  -        local shared=tfmdata.shared -        shared.otfdata=otfdata -        shared.features=features  -        shared.dynamics={} -        shared.processes={} -        shared.set_dynamics=otf.set_dynamics -        tfmdata.luatex=otfdata.luatex -        tfmdata.indices=otfdata.luatex.indices -        tfmdata.unicodes=otfdata.luatex.unicodes -        tfmdata.marks=otfdata.luatex.marks -        tfmdata.originals=otfdata.luatex.originals -        tfmdata.changed={} -        tfmdata.has_italic=otfdata.metadata.has_italic -        if not tfmdata.language then tfmdata.language='dflt' end -        if not tfmdata.script  then tfmdata.script='dflt' end -        shared.processes,shared.features=otf.set_features(tfmdata,fonts.define.check(features,otf.features.default)) -      end -    end -    containers.write(tfm.cache,cache_id,tfmdata) -  end -  return tfmdata -end -fonts.formats.dfont="truetype" -fonts.formats.ttc="truetype" -fonts.formats.ttf="truetype" -fonts.formats.otf="opentype" -function otf.copy_to_tfm(data,cache_id)  -  if data then -    local glyphs,pfminfo,metadata=data.glyphs or {},data.pfminfo or {},data.metadata or {} -    local luatex=data.luatex -    local unicodes=luatex.unicodes  -    local indices=luatex.indices -    local characters,parameters,math_parameters,descriptions={},{},{},{} -    local designsize=metadata.designsize or metadata.design_size or 100 -    if designsize==0 then -      designsize=100 -    end -    local spaceunits,spacer=500,"space" -    for u,i in next,indices do -      characters[u]={}  -      descriptions[u]=glyphs[i] -    end -    if metadata.math then -      for name,value in next,metadata.math do -        math_parameters[name]=value -      end -      for u,char in next,characters do -        local d=descriptions[u] -        local m=d.math -        if m then -          local variants,parts,c,uc=m.horiz_variants,m.horiz_parts,char,u -          if variants then -            for n in gmatch(variants,"[^ ]+") do -              local un=unicodes[n] -              if un and uc~=un then -                c.next=un -                c=characters[un] -				uc=un -              end -            end -            c.horiz_variants=parts -          elseif parts then -            c.horiz_variants=parts -          end -          local variants,parts,c,uc=m.vert_variants,m.vert_parts,char,u -          if variants then -            for n in gmatch(variants,"[^ ]+") do -              local un=unicodes[n] -              if un and uc~=un then -                c.next=un -                c=characters[un] -				uc=un -              end -            end  -            c.vert_variants=parts -          elseif parts then -            c.vert_variants=parts -          end -          local italic_correction=m.vert_italic_correction -          if italic_correction then -            c.vert_italic_correction=italic_correction -          end -          local kerns=m.kerns -          if kerns then -            char.mathkerns=kerns -          end -        end -      end -    end -    local endash,emdash,space=0x20,0x2014,"space"  -    if metadata.isfixedpitch then -      if descriptions[endash] then -        spaceunits,spacer=descriptions[endash].width,"space" -      end -      if not spaceunits and descriptions[emdash] then -        spaceunits,spacer=descriptions[emdash].width,"emdash" -      end -      if not spaceunits and metadata.charwidth then -        spaceunits,spacer=metadata.charwidth,"charwidth" -      end -    else -      if descriptions[endash] then -        spaceunits,spacer=descriptions[endash].width,"space" -      end -      if not spaceunits and descriptions[emdash] then -        spaceunits,spacer=descriptions[emdash].width/2,"emdash/2" -      end -      if not spaceunits and metadata.charwidth then -        spaceunits,spacer=metadata.charwidth,"charwidth" -      end -    end -    spaceunits=tonumber(spaceunits) or tfm.units/2 -    local filename=fonts.tfm.checked_filename(luatex) -    local fontname=metadata.fontname -    local fullname=metadata.fullname or fontname -    local cidinfo=data.cidinfo -    local units=metadata.units_per_em or 1000 -    cidinfo.registry=cidinfo and cidinfo.registry or "" -    parameters.slant=0 -    parameters.space=spaceunits      -    parameters.space_stretch=units/2   -    parameters.space_shrink=1*units/3  -    parameters.x_height=2*units/5  -    parameters.quad=units    -    if spaceunits<2*units/5 then -    end -    local italicangle=metadata.italicangle -    if italicangle then  -      parameters.slant=parameters.slant-math.round(math.tan(italicangle*math.pi/180)) -    end -    if metadata.isfixedpitch then -      parameters.space_stretch=0 -      parameters.space_shrink=0 -    elseif otf.syncspace then  -      parameters.space_stretch=spaceunits/2 -      parameters.space_shrink=spaceunits/3 -    end -    parameters.extra_space=parameters.space_shrink  -    if pfminfo.os2_xheight and pfminfo.os2_xheight>0 then -      parameters.x_height=pfminfo.os2_xheight -    else -      local x=0x78  -      if x then -        local x=descriptions[x] -        if x then -          parameters.x_height=x.height -        end -      end -    end -    return { -      characters=characters, -      parameters=parameters, -      math_parameters=math_parameters, -      descriptions=descriptions, -      indices=indices, -      unicodes=unicodes, -      type="real", -      direction=0, -      boundarychar_label=0, -      boundarychar=65536, -      designsize=(designsize/10)*65536, -      spacer="500 units", -      encodingbytes=2, -      filename=filename, -      fontname=fontname, -      fullname=fullname, -      psname=fontname or fullname, -      name=filename or fullname, -      units=units, -      format=fonts.fontformat(filename,"opentype"), -      cidinfo=cidinfo, -      ascender=abs(metadata.ascent or 0), -      descender=abs(metadata.descent or 0), -      spacer=spacer, -      italicangle=italicangle, -    } -  else -    return nil -  end -end -otf.features.register('mathsize') -function tfm.read_from_open_type(specification) -  local tfmtable=otf.otf_to_tfm(specification) -  if tfmtable then -    local otfdata=tfmtable.shared.otfdata -    tfmtable.name=specification.name -    tfmtable.sub=specification.sub -    local s=specification.size -    local m=otfdata.metadata.math -    if m then -      local f=specification.features -      if f then -        local f=f.normal -        if f and f.mathsize then -          local mathsize=specification.mathsize or 0 -          if mathsize==2 then -            local p=m.ScriptPercentScaleDown -            if p then -              local ps=p*specification.textsize/100 -              if trace_math then -                logs.report("define font","asked script size: %s, used: %s (%2.2f %%)",s,ps,(ps/s)*100) -              end -              s=ps -            end -          elseif mathsize==3 then -            local p=m.ScriptScriptPercentScaleDown -            if p then -              local ps=p*specification.textsize/100 -              if trace_math then -                logs.report("define font","asked scriptscript size: %s, used: %s (%2.2f %%)",s,ps,(ps/s)*100) -              end -              s=ps -            end -          end -        end -      end -    end -    tfmtable=tfm.scale(tfmtable,s,specification.relativeid) -    if tfm.fontname_mode=="specification" then -      local specname=specification.specification -      if specname then -        tfmtable.name=specname -        if trace_defining then -          logs.report("define font","overloaded fontname: '%s'",specname) -        end -      end -    end -    fonts.logger.save(tfmtable,file.extname(specification.filename),specification) -  end -  return tfmtable -end -function otf.collect_lookups(otfdata,kind,script,language) -  local sequences=otfdata.luatex.sequences -  if sequences then -    local featuremap,featurelist={},{} -    for s=1,#sequences do -      local sequence=sequences[s] -      local features=sequence.features -      features=features and features[kind] -      features=features and (features[script]  or features[default] or features[wildcard]) -      features=features and (features[language] or features[default] or features[wildcard]) -      if features then -        local subtables=sequence.subtables -        if subtables then -          for s=1,#subtables do -            local ss=subtables[s] -            if not featuremap[s] then -              featuremap[ss]=true -              featurelist[#featurelist+1]=ss -            end -          end -        end -      end -    end -    if #featurelist>0 then -      return featuremap,featurelist -    end -  end -  return nil,nil -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-otd']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local trace_dynamics=false trackers.register("otf.dynamics",function(v) trace_dynamics=v end) -fonts=fonts   or {} -fonts.otf=fonts.otf or {} -local otf=fonts.otf -local fontdata=fonts.ids -otf.features=otf.features     or {} -otf.features.default=otf.features.default or {} -local context_setups=fonts.define.specify.context_setups -local context_numbers=fonts.define.specify.context_numbers -local a_to_script={} otf.a_to_script=a_to_script -local a_to_language={} otf.a_to_language=a_to_language -function otf.set_dynamics(font,dynamics,attribute) -  local features=context_setups[context_numbers[attribute]]  -  if features then -    local script=features.script  or 'dflt' -    local language=features.language or 'dflt' -    local ds=dynamics[script] -    if not ds then -      ds={} -      dynamics[script]=ds -    end -    local dsl=ds[language] -    if not dsl then -      dsl={} -      ds[language]=dsl -    end -    local dsla=dsl[attribute] -    if dsla then -      return dsla -    else -      local tfmdata=fontdata[font] -      a_to_script [attribute]=script -      a_to_language[attribute]=language -      local saved={ -        script=tfmdata.script, -        language=tfmdata.language, -        mode=tfmdata.mode, -        features=tfmdata.shared.features -      } -      tfmdata.mode="node" -      tfmdata.language=language -      tfmdata.script=script -      tfmdata.shared.features={} -      local set=fonts.define.check(features,otf.features.default) -      dsla=otf.set_features(tfmdata,set) -      if trace_dynamics then -        logs.report("otf define","setting dynamics %s: attribute %s, script %s, language %s, set: %s",context_numbers[attribute],attribute,script,language,table.sequenced(set)) -      end -      tfmdata.script=saved.script -      tfmdata.language=saved.language -      tfmdata.mode=saved.mode -      tfmdata.shared.features=saved.features -      dynamics[script][language][attribute]=dsla  -      return dsla -    end -  end -  return nil  -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-oti']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local lower=string.lower -local otf=fonts.otf -otf.default_language='latn' -otf.default_script='dflt' -local languages=otf.tables.languages -local scripts=otf.tables.scripts -function otf.features.language(tfmdata,value) -  if value then -    value=lower(value) -    if languages[value] then -      tfmdata.language=value -    end -  end -end -function otf.features.script(tfmdata,value) -  if value then -    value=lower(value) -    if scripts[value] then -      tfmdata.script=value -    end -  end -end -function otf.features.mode(tfmdata,value) -  if value then -    tfmdata.mode=lower(value) -  end -end -fonts.initializers.base.otf.language=otf.features.language -fonts.initializers.base.otf.script=otf.features.script -fonts.initializers.base.otf.mode=otf.features.mode -fonts.initializers.base.otf.method=otf.features.mode -fonts.initializers.node.otf.language=otf.features.language -fonts.initializers.node.otf.script=otf.features.script -fonts.initializers.node.otf.mode=otf.features.mode -fonts.initializers.node.otf.method=otf.features.mode -otf.features.register("features",true)    -table.insert(fonts.processors,"features")  - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-otb']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local concat=table.concat -local format,gmatch,gsub,find,match,lower,strip=string.format,string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip -local type,next,tonumber,tostring=type,next,tonumber,tostring -local lpegmatch=lpeg.match -local otf=fonts.otf -local tfm=fonts.tfm -local trace_baseinit=false trackers.register("otf.baseinit",function(v) trace_baseinit=v end) -local trace_singles=false trackers.register("otf.singles",function(v) trace_singles=v end) -local trace_multiples=false trackers.register("otf.multiples",function(v) trace_multiples=v end) -local trace_alternatives=false trackers.register("otf.alternatives",function(v) trace_alternatives=v end) -local trace_ligatures=false trackers.register("otf.ligatures",function(v) trace_ligatures=v end) -local trace_kerns=false trackers.register("otf.kerns",function(v) trace_kerns=v end) -local trace_preparing=false trackers.register("otf.preparing",function(v) trace_preparing=v end) -local wildcard="*" -local default="dflt" -local split_at_space=lpeg.Ct(lpeg.splitat(" "))  -local pcache,fcache={},{}  -local function gref(descriptions,n) -  if type(n)=="number" then -    local name=descriptions[n].name -    if name then -      return format("U+%04X (%s)",n,name) -    else -      return format("U+%04X") -    end -  elseif n then -    local num,nam={},{} -    for i=1,#n do -      local ni=n[i] -      num[i]=format("U+%04X",ni) -      nam[i]=descriptions[ni].name or "?" -    end -    return format("%s (%s)",concat(num," "),concat(nam," ")) -  else -    return "?" -  end -end -local function cref(kind,lookupname) -  if lookupname then -    return format("feature %s, lookup %s",kind,lookupname) -  else -    return format("feature %s",kind) -  end -end -local function resolve_ligatures(tfmdata,ligatures,kind) -  kind=kind or "unknown" -  local unicodes=tfmdata.unicodes -  local characters=tfmdata.characters -  local descriptions=tfmdata.descriptions -  local changed=tfmdata.changed -  local done={} -  while true do -    local ok=false -    for k,v in next,ligatures do -      local lig=v[1] -      if not done[lig] then -        local ligs=lpegmatch(split_at_space,lig) -        if #ligs==2 then -          local uc=v[2] -          local c,f,s=characters[uc],ligs[1],ligs[2] -          local uft,ust=unicodes[f] or 0,unicodes[s] or 0 -          if not uft or not ust then -            logs.report("define otf","%s: unicode problem with base ligature %s = %s + %s",cref(kind),gref(descriptions,uc),gref(descriptions,uft),gref(descriptions,ust)) -          else -            if type(uft)=="number" then uft={ uft } end -            if type(ust)=="number" then ust={ ust } end -            for ufi=1,#uft do -              local uf=uft[ufi] -              for usi=1,#ust do -                local us=ust[usi] -                if changed[uf] or changed[us] then -                  if trace_baseinit and trace_ligatures then -                    logs.report("define otf","%s: base ligature %s + %s ignored",cref(kind),gref(descriptions,uf),gref(descriptions,us)) -                  end -                else -                  local first,second=characters[uf],us -                  if first and second then -                    local t=first.ligatures -                    if not t then -                      t={} -                      first.ligatures=t -                    end -                    if type(uc)=="number" then -                      t[second]={ type=0,char=uc } -                    else -                      t[second]={ type=0,char=uc[1] }  -                    end -                    if trace_baseinit and trace_ligatures then -                      logs.report("define otf","%s: base ligature %s + %s => %s",cref(kind),gref(descriptions,uf),gref(descriptions,us),gref(descriptions,uc)) -                    end -                  end -                end -              end -            end -          end -          ok,done[lig]=true,descriptions[uc].name -        end -      end -    end -    if ok then -      for d,n in next,done do -        local pattern=pcache[d] if not pattern then pattern="^("..d..") "       pcache[d]=pattern end -        local fnc=fcache[n] if not fnc   then fnc=function() return n.." " end fcache[n]=fnc   end -        for k,v in next,ligatures do -          v[1]=gsub(v[1],pattern,fnc) -        end -      end -    else -      break -    end -  end -end -local splitter=lpeg.splitat(" ") -local function prepare_base_substitutions(tfmdata,kind,value)  -  if value then -    local otfdata=tfmdata.shared.otfdata -    local validlookups,lookuplist=otf.collect_lookups(otfdata,kind,tfmdata.script,tfmdata.language) -    if validlookups then -      local ligatures={} -      local unicodes=tfmdata.unicodes  -      local indices=tfmdata.indices -      local characters=tfmdata.characters -      local descriptions=tfmdata.descriptions -      local changed=tfmdata.changed -      local actions={ -        substitution=function(p,lookup,k,glyph,unicode) -          local pv=p[2]  -          if pv then -            local upv=unicodes[pv] -            if upv then -              if type(upv)=="table" then -                upv=upv[1] -              end -              if characters[upv] then -                if trace_baseinit and trace_singles then -                  logs.report("define otf","%s: base substitution %s => %s",cref(kind,lookup),gref(descriptions,k),gref(descriptions,upv)) -                end -                changed[k]=upv -              end -            end -          end -        end, -        alternate=function(p,lookup,k,glyph,unicode) -          local pc=p[2]  -          if pc then -            if value==1 then -              pc=lpegmatch(splitter,pc) -            elseif value==2 then -              local a,b=lpegmatch(splitter,pc) -              pc=b or a -            else -              pc={ lpegmatch(splitter,pc) } -              pc=pc[value] or pc[#pc] -            end -            if pc then -              local upc=unicodes[pc] -              if upc then -                if type(upc)=="table" then -                  upc=upc[1] -                end -                if characters[upc] then -                  if trace_baseinit and trace_alternatives then -                    logs.report("define otf","%s: base alternate %s %s => %s",cref(kind,lookup),tostring(value),gref(descriptions,k),gref(descriptions,upc)) -                  end -                  changed[k]=upc -                end -              end -            end -          end -        end, -        ligature=function(p,lookup,k,glyph,unicode) -          local pc=p[2] -          if pc then -            if trace_baseinit and trace_ligatures then -              local upc={ lpegmatch(splitter,pc) } -              for i=1,#upc do upc[i]=unicodes[upc[i]] end -              logs.report("define otf","%s: base ligature %s => %s",cref(kind,lookup),gref(descriptions,upc),gref(descriptions,k)) -            end -            ligatures[#ligatures+1]={ pc,k } -          end -        end, -      } -      for k,c in next,characters do -        local glyph=descriptions[k] -        local lookups=glyph.slookups -        if lookups then -          for l=1,#lookuplist do -            local lookup=lookuplist[l] -            local p=lookups[lookup] -            if p then -              local a=actions[p[1]] -              if a then -                a(p,lookup,k,glyph,unicode) -              end -            end -          end -        end -        local lookups=glyph.mlookups -        if lookups then -          for l=1,#lookuplist do -            local lookup=lookuplist[l] -            local ps=lookups[lookup] -            if ps then -              for i=1,#ps do -                local p=ps[i] -                local a=actions[p[1]] -                if a then -                  a(p,lookup,k,glyph,unicode) -                end -              end -            end -          end -        end -      end -      resolve_ligatures(tfmdata,ligatures,kind) -    end -  else -    tfmdata.ligatures=tfmdata.ligatures or {}  -  end -end -local function prepare_base_kerns(tfmdata,kind,value)  -  if value then -    local otfdata=tfmdata.shared.otfdata -    local validlookups,lookuplist=otf.collect_lookups(otfdata,kind,tfmdata.script,tfmdata.language) -    if validlookups then -      local unicodes=tfmdata.unicodes  -      local indices=tfmdata.indices -      local characters=tfmdata.characters -      local descriptions=tfmdata.descriptions -      local sharedkerns={} -      for u,chr in next,characters do -        local d=descriptions[u] -        if d then -          local dk=d.mykerns  -          if dk then -            local s=sharedkerns[dk] -            if s==false then -            elseif s then -              chr.kerns=s -            else -              local t,done=chr.kerns or {},false -              for l=1,#lookuplist do -                local lookup=lookuplist[l] -                local kerns=dk[lookup] -                if kerns then -                  for k,v in next,kerns do -                    if v~=0 and not t[k] then  -                      t[k],done=v,true -                      if trace_baseinit and trace_kerns then -                        logs.report("define otf","%s: base kern %s + %s => %s",cref(kind,lookup),gref(descriptions,u),gref(descriptions,k),v) -                      end -                    end -                  end -                end -              end -              if done then -                sharedkerns[dk]=t -                chr.kerns=t  -              else -                sharedkerns[dk]=false -              end -            end -          end -        end -      end -    end -  end -end -local supported_gsub={ -  'liga','dlig','rlig','hlig', -  'pnum','onum','tnum','lnum', -  'zero', -  'smcp','cpsp','c2sc','ornm','aalt', -  'hwid','fwid', -  'ssty','rtlm', -} -local supported_gpos={ -  'kern' -} -function otf.features.register_base_substitution(tag) -  supported_gsub[#supported_gsub+1]=tag -end -function otf.features.register_base_kern(tag) -  supported_gsub[#supported_gpos+1]=tag -end -local basehash,basehashes={},1 -function fonts.initializers.base.otf.features(tfmdata,value) -  if true then -    local t=trace_preparing and os.clock() -    local features=tfmdata.shared.features -    if features then -      local h={} -      for f=1,#supported_gsub do -        local feature=supported_gsub[f] -        local value=features[feature] -        prepare_base_substitutions(tfmdata,feature,value) -        if value then -          h[#h+1]=feature.."="..tostring(value) -        end -      end -      for f=1,#supported_gpos do -        local feature=supported_gpos[f] -        local value=features[feature] -        prepare_base_kerns(tfmdata,feature,features[feature]) -        if value then -          h[#h+1]=feature.."="..tostring(value) -        end -      end -      local hash=concat(h," ") -      local base=basehash[hash] -      if not base then -        basehashes=basehashes+1 -        base=basehashes -        basehash[hash]=base -      end -      tfmdata.fullname=tfmdata.fullname.."-"..base -    end -    if trace_preparing then -      logs.report("otf define","preparation time is %0.3f seconds for %s",os.clock()-t,tfmdata.fullname or "?") -    end -  end -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-otn']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local concat,insert,remove=table.concat,table.insert,table.remove -local format,gmatch,gsub,find,match,lower,strip=string.format,string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip -local type,next,tonumber,tostring=type,next,tonumber,tostring -local lpegmatch=lpeg.match -local otf=fonts.otf -local tfm=fonts.tfm -local trace_lookups=false trackers.register("otf.lookups",function(v) trace_lookups=v end) -local trace_singles=false trackers.register("otf.singles",function(v) trace_singles=v end) -local trace_multiples=false trackers.register("otf.multiples",function(v) trace_multiples=v end) -local trace_alternatives=false trackers.register("otf.alternatives",function(v) trace_alternatives=v end) -local trace_ligatures=false trackers.register("otf.ligatures",function(v) trace_ligatures=v end) -local trace_contexts=false trackers.register("otf.contexts",function(v) trace_contexts=v end) -local trace_marks=false trackers.register("otf.marks",function(v) trace_marks=v end) -local trace_kerns=false trackers.register("otf.kerns",function(v) trace_kerns=v end) -local trace_cursive=false trackers.register("otf.cursive",function(v) trace_cursive=v end) -local trace_preparing=false trackers.register("otf.preparing",function(v) trace_preparing=v end) -local trace_bugs=false trackers.register("otf.bugs",function(v) trace_bugs=v end) -local trace_details=false trackers.register("otf.details",function(v) trace_details=v end) -local trace_applied=false trackers.register("otf.applied",function(v) trace_applied=v end) -local trace_steps=false trackers.register("otf.steps",function(v) trace_steps=v end) -local trace_skips=false trackers.register("otf.skips",function(v) trace_skips=v end) -local trace_directions=false trackers.register("otf.directions",function(v) trace_directions=v end) -trackers.register("otf.verbose_chain",function(v) otf.setcontextchain(v and "verbose") end) -trackers.register("otf.normal_chain",function(v) otf.setcontextchain(v and "normal") end) -trackers.register("otf.replacements","otf.singles,otf.multiples,otf.alternatives,otf.ligatures") -trackers.register("otf.positions","otf.marks,otf.kerns,otf.cursive") -trackers.register("otf.actions","otf.replacements,otf.positions") -trackers.register("otf.injections","nodes.injections") -trackers.register("*otf.sample","otf.steps,otf.actions,otf.analyzing") -local insert_node_after=node.insert_after -local delete_node=nodes.delete -local copy_node=node.copy -local find_node_tail=node.tail or node.slide -local set_attribute=node.set_attribute -local has_attribute=node.has_attribute -local zwnj=0x200C -local zwj=0x200D -local wildcard="*" -local default="dflt" -local split_at_space=lpeg.splitters[" "] or lpeg.Ct(lpeg.splitat(" "))  -local glyph=node.id('glyph') -local glue=node.id('glue') -local kern=node.id('kern') -local disc=node.id('disc') -local whatsit=node.id('whatsit') -local state=attributes.private('state') -local markbase=attributes.private('markbase') -local markmark=attributes.private('markmark') -local markdone=attributes.private('markdone') -local cursbase=attributes.private('cursbase') -local curscurs=attributes.private('curscurs') -local cursdone=attributes.private('cursdone') -local kernpair=attributes.private('kernpair') -local set_mark=nodes.set_mark -local set_cursive=nodes.set_cursive -local set_kern=nodes.set_kern -local set_pair=nodes.set_pair -local markonce=true -local cursonce=true -local kernonce=true -local fontdata=fonts.ids -otf.features.process={} -local tfmdata=false -local otfdata=false -local characters=false -local descriptions=false -local marks=false -local indices=false -local unicodes=false -local currentfont=false -local lookuptable=false -local anchorlookups=false -local handlers={} -local rlmode=0 -local featurevalue=false -local context_setups=fonts.define.specify.context_setups -local context_numbers=fonts.define.specify.context_numbers -local context_merged=fonts.define.specify.context_merged -local special_attributes={ -  init=1, -  medi=2, -  fina=3, -  isol=4 -} -local checkstep=(nodes and nodes.tracers and nodes.tracers.steppers.check)  or function() end -local registerstep=(nodes and nodes.tracers and nodes.tracers.steppers.register) or function() end -local registermessage=(nodes and nodes.tracers and nodes.tracers.steppers.message) or function() end -local function logprocess(...) -  if trace_steps then -    registermessage(...) -  end -  logs.report("otf direct",...) -end -local function logwarning(...) -  logs.report("otf direct",...) -end -local function gref(n) -  if type(n)=="number" then -    local description=descriptions[n] -    local name=description and description.name -    if name then -      return format("U+%04X (%s)",n,name) -    else -      return format("U+%04X",n) -    end -  elseif not n then -    return "<error in tracing>" -  else -    local num,nam={},{} -    for i=1,#n do -      local ni=n[i] -      num[#num+1]=format("U+%04X",ni) -      local dni=descriptions[ni] -      nam[#num]=(dni and dni.name) or "?" -    end -    return format("%s (%s)",concat(num," "),concat(nam," ")) -  end -end -local function cref(kind,chainname,chainlookupname,lookupname,index) -  if index then -    return format("feature %s, chain %s, sub %s, lookup %s, index %s",kind,chainname,chainlookupname,lookupname,index) -  elseif lookupname then -    return format("feature %s, chain %s, sub %s, lookup %s",kind,chainname or "?",chainlookupname or "?",lookupname) -  elseif chainlookupname then -    return format("feature %s, chain %s, sub %s",kind,chainname or "?",chainlookupname) -  elseif chainname then -    return format("feature %s, chain %s",kind,chainname) -  else -    return format("feature %s",kind) -  end -end -local function pref(kind,lookupname) -  return format("feature %s, lookup %s",kind,lookupname) -end -local function markstoligature(kind,lookupname,start,stop,char) -  local n=copy_node(start) -  local keep=start -  local current -  current,start=insert_node_after(start,start,n) -  local snext=stop.next -  current.next=snext -  if snext then -    snext.prev=current -  end -  start.prev,stop.next=nil,nil -  current.char,current.subtype,current.components=char,2,start -  return keep -end -local function toligature(kind,lookupname,start,stop,char,markflag,discfound)  -  if start~=stop then -    if discfound then -      local lignode=copy_node(start) -      lignode.font,lignode.char,lignode.subtype=start.font,char,2 -      local next,prev=stop.next,start.prev -      stop.next=nil -      lignode=node.do_ligature_n(start,stop,lignode) -      prev.next=lignode -      if next then -        next.prev=lignode -      end -      lignode.next,lignode.prev=next,prev -      start=lignode -    else  -      local deletemarks=markflag~="mark" -      local n=copy_node(start) -      local current -      current,start=insert_node_after(start,start,n) -      local snext=stop.next -      current.next=snext -      if snext then -        snext.prev=current -      end -      start.prev,stop.next=nil,nil -      current.char,current.subtype,current.components=char,2,start -      local head=current -      if deletemarks then -        if trace_marks then -          while start do -            if marks[start.char] then -              logwarning("%s: remove mark %s",pref(kind,lookupname),gref(start.char)) -            end -            start=start.next -          end -        end -      else -        local i=0 -        while start do -          if marks[start.char] then -            set_attribute(start,markdone,i) -            if trace_marks then -              logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(start.char),i) -            end -            head,current=insert_node_after(head,current,copy_node(start)) -          else -            i=i+1 -          end -          start=start.next -        end -        start=current.next -        while start and start.id==glyph do -          if marks[start.char] then -            set_attribute(start,markdone,i) -            if trace_marks then -              logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(start.char),i) -            end -          else -            break -          end -          start=start.next -        end -      end -      return head -    end -  else -    start.char=char -  end -  return start -end -function handlers.gsub_single(start,kind,lookupname,replacement) -  if trace_singles then -    logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(start.char),gref(replacement)) -  end -  start.char=replacement -  return start,true -end -local function alternative_glyph(start,alternatives,kind,chainname,chainlookupname,lookupname)  -  local value,choice,n=featurevalue or tfmdata.shared.features[kind],nil,#alternatives  -  if value=="random" then -    local r=math.random(1,n) -    value,choice=format("random, choice %s",r),alternatives[r] -  elseif value=="first" then -    value,choice=format("first, choice %s",1),alternatives[1] -  elseif value=="last" then -    value,choice=format("last, choice %s",n),alternatives[n] -  else -    value=tonumber(value) -    if type(value)~="number" then -      value,choice="default, choice 1",alternatives[1] -    elseif value>n then -      value,choice=format("no %s variants, taking %s",value,n),alternatives[n] -    elseif value==0 then -      value,choice=format("choice %s (no change)",value),start.char -    elseif value<1 then -      value,choice=format("no %s variants, taking %s",value,1),alternatives[1] -    else -      value,choice=format("choice %s",value),alternatives[value] -    end -  end -  if not choice then -    logwarning("%s: no variant %s for %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(start.char)) -    choice,value=start.char,format("no replacement instead of %s",value) -  end -  return choice,value -end -function handlers.gsub_alternate(start,kind,lookupname,alternative,sequence) -  local choice,index=alternative_glyph(start,alternative,kind,lookupname) -  if trace_alternatives then -    logprocess("%s: replacing %s by alternative %s (%s)",pref(kind,lookupname),gref(start.char),gref(choice),index) -  end -  start.char=choice -  return start,true -end -function handlers.gsub_multiple(start,kind,lookupname,multiple) -  if trace_multiples then -    logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(start.char),gref(multiple)) -  end -  start.char=multiple[1] -  if #multiple>1 then -    for k=2,#multiple do -      local n=copy_node(start) -      n.char=multiple[k] -      local sn=start.next -      n.next=sn -      n.prev=start -      if sn then -        sn.prev=n -      end -      start.next=n -      start=n -    end -  end -  return start,true -end -function handlers.gsub_ligature(start,kind,lookupname,ligature,sequence)  -  local s,stop,discfound=start.next,nil,false -  local startchar=start.char -  if marks[startchar] then -    while s do -      local id=s.id -      if id==glyph and s.subtype<256 then -        if s.font==currentfont then -          local char=s.char -          local lg=ligature[1][char] -          if not lg then -            break -          else -            stop=s -            ligature=lg -            s=s.next -          end -        else -          break -        end -      else -        break -      end -    end -    if stop and ligature[2] then -      if trace_ligatures then -        local stopchar=stop.char -        start=markstoligature(kind,lookupname,start,stop,ligature[2]) -        logprocess("%s: replacing %s upto %s by ligature %s",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) -      else -        start=markstoligature(kind,lookupname,start,stop,ligature[2]) -      end -      return start,true -    end -  else -    local skipmark=sequence.flags[1] -    while s do -      local id=s.id -      if id==glyph and s.subtype<256 then -        if s.font==currentfont then -          local char=s.char -          if skipmark and marks[char] then -            s=s.next -          else -            local lg=ligature[1][char] -            if not lg then -              break -            else -              stop=s -              ligature=lg -              s=s.next -            end -          end -        else -          break -        end -      elseif id==disc then -        discfound=true -        s=s.next -      else -        break -      end -    end -    if stop and ligature[2] then -      if trace_ligatures then -        local stopchar=stop.char -        start=toligature(kind,lookupname,start,stop,ligature[2],skipmark,discfound) -        logprocess("%s: replacing %s upto %s by ligature %s",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) -      else -        start=toligature(kind,lookupname,start,stop,ligature[2],skipmark,discfound) -      end -      return start,true -    end -  end -  return start,false -end -function handlers.gpos_mark2base(start,kind,lookupname,markanchors,sequence) -  local markchar=start.char -  if marks[markchar] then -    local base=start.prev  -    if base and base.id==glyph and base.subtype<256 and base.font==currentfont then -      local basechar=base.char -      if marks[basechar] then -        while true do -          base=base.prev -          if base and base.id==glyph and base.subtype<256 and base.font==currentfont then -            basechar=base.char -            if not marks[basechar] then -              break -            end -          else -            if trace_bugs then -              logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) -            end -            return start,false -          end -        end -      end -      local baseanchors=descriptions[basechar] -      if baseanchors then -        baseanchors=baseanchors.anchors -      end -      if baseanchors then -        local baseanchors=baseanchors['basechar'] -        if baseanchors then -          local al=anchorlookups[lookupname] -          for anchor,ba in next,baseanchors do -            if al[anchor] then -              local ma=markanchors[anchor] -              if ma then -                local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma) -                if trace_marks then -                  logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%s,%s)", -                    pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) -                end -                return start,true -              end -            end -          end -          if trace_bugs then -            logwarning("%s, no matching anchors for mark %s and base %s",pref(kind,lookupname),gref(markchar),gref(basechar)) -          end -        end -      else -        fonts.register_message(currentfont,basechar,"no base anchors") -      end -    elseif trace_bugs then -      logwarning("%s: prev node is no char",pref(kind,lookupname)) -    end -  elseif trace_bugs then -    logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) -  end -  return start,false -end -function handlers.gpos_mark2ligature(start,kind,lookupname,markanchors,sequence) -  local markchar=start.char -  if marks[markchar] then -    local base=start.prev  -    local index=1 -    if base and base.id==glyph and base.subtype<256 and base.font==currentfont then -      local basechar=base.char -      if marks[basechar] then -        index=index+1 -        while true do -          base=base.prev -          if base and base.id==glyph and base.subtype<256 and base.font==currentfont then -            basechar=base.char -            if marks[basechar] then -              index=index+1 -            else -              break -            end -          else -            if trace_bugs then -              logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) -            end -            return start,false -          end -        end -      end -      local i=has_attribute(start,markdone) -      if i then index=i end -      local baseanchors=descriptions[basechar] -      if baseanchors then -        baseanchors=baseanchors.anchors -        if baseanchors then -          local baseanchors=baseanchors['baselig'] -          if baseanchors then -            local al=anchorlookups[lookupname] -            for anchor,ba in next,baseanchors do -              if al[anchor] then -                local ma=markanchors[anchor] -                if ma then -                  ba=ba[index] -                  if ba then -                    local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma,index) -                    if trace_marks then -                      logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%s,%s)", -                        pref(kind,lookupname),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy) -                    end -                    return start,true -                  end -                end -              end -            end -            if trace_bugs then -              logwarning("%s: no matching anchors for mark %s and baselig %s",pref(kind,lookupname),gref(markchar),gref(basechar)) -            end -          end -        end -      else -        fonts.register_message(currentfont,basechar,"no base anchors") -      end -    elseif trace_bugs then -      logwarning("%s: prev node is no char",pref(kind,lookupname)) -    end -  elseif trace_bugs then -    logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) -  end -  return start,false -end -function handlers.gpos_mark2mark(start,kind,lookupname,markanchors,sequence) -  local markchar=start.char -  if marks[markchar] then -      local base=start.prev  -      if base and base.id==glyph and base.subtype<256 and base.font==currentfont then  -        local basechar=base.char -        local baseanchors=descriptions[basechar] -        if baseanchors then -          baseanchors=baseanchors.anchors -          if baseanchors then -            baseanchors=baseanchors['basemark'] -            if baseanchors then -              local al=anchorlookups[lookupname] -              for anchor,ba in next,baseanchors do -                if al[anchor] then -                  local ma=markanchors[anchor] -                  if ma then -                    local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma) -                    if trace_marks then -                      logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%s,%s)", -                        pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) -                    end -                    return start,true -                  end -                end -              end -              if trace_bugs then -                logwarning("%s: no matching anchors for mark %s and basemark %s",pref(kind,lookupname),gref(markchar),gref(basechar)) -              end -            end -          end -        else -          fonts.register_message(currentfont,basechar,"no base anchors") -        end -      elseif trace_bugs then -        logwarning("%s: prev node is no mark",pref(kind,lookupname)) -      end -  elseif trace_bugs then -    logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) -  end -  return start,false -end -function handlers.gpos_cursive(start,kind,lookupname,exitanchors,sequence)  -  local alreadydone=cursonce and has_attribute(start,cursbase) -  if not alreadydone then -    local done=false -    local startchar=start.char -    if marks[startchar] then -      if trace_cursive then -        logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) -      end -    else -      local nxt=start.next -      while not done and nxt and nxt.id==glyph and nxt.subtype<256 and nxt.font==currentfont do -        local nextchar=nxt.char -        if marks[nextchar] then -          nxt=nxt.next -        else -          local entryanchors=descriptions[nextchar] -          if entryanchors then -            entryanchors=entryanchors.anchors -            if entryanchors then -              entryanchors=entryanchors['centry'] -              if entryanchors then -                local al=anchorlookups[lookupname] -                for anchor,entry in next,entryanchors do -                  if al[anchor] then -                    local exit=exitanchors[anchor] -                    if exit then -                      local dx,dy,bound=set_cursive(start,nxt,tfmdata.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) -                      if trace_cursive then -                        logprocess("%s: moving %s to %s cursive (%s,%s) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) -                      end -                      done=true -                      break -                    end -                  end -                end -              end -            end -          else -            fonts.register_message(currentfont,startchar,"no entry anchors") -          end -          break -        end -      end -    end -    return start,done -  else -    if trace_cursive and trace_details then -      logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(start.char),alreadydone) -    end -    return start,false -  end -end -function handlers.gpos_single(start,kind,lookupname,kerns,sequence) -  local startchar=start.char -  local dx,dy,w,h=set_pair(start,tfmdata.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) -  if trace_kerns then -    logprocess("%s: shifting single %s by (%s,%s) and correction (%s,%s)",pref(kind,lookupname),gref(startchar),dx,dy,w,h) -  end -  return start,false -end -function handlers.gpos_pair(start,kind,lookupname,kerns,sequence) -  local snext=start.next -  if not snext then -    return start,false -  else -    local prev,done=start,false -    local factor=tfmdata.factor -    while snext and snext.id==glyph and snext.subtype<256 and snext.font==currentfont do -      local nextchar=snext.char -local krn=kerns[nextchar] -      if not krn and marks[nextchar] then -        prev=snext -        snext=snext.next -      else -        local krn=kerns[nextchar] -        if not krn then -        elseif type(krn)=="table" then -          if krn[1]=="pair" then -            local a,b=krn[3],krn[4] -            if a and #a>0 then -              local startchar=start.char -              local x,y,w,h=set_pair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) -              if trace_kerns then -                logprocess("%s: shifting first of pair %s and %s by (%s,%s) and correction (%s,%s)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) -              end -            end -            if b and #b>0 then -              local startchar=start.char -              local x,y,w,h=set_pair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) -              if trace_kerns then -                logprocess("%s: shifting second of pair %s and %s by (%s,%s) and correction (%s,%s)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) -              end -            end -          else -            logs.report("%s: check this out (old kern stuff)",pref(kind,lookupname)) -            local a,b=krn[3],krn[7] -            if a and a~=0 then -              local k=set_kern(snext,factor,rlmode,a) -              if trace_kerns then -                logprocess("%s: inserting first kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar)) -              end -            end -            if b and b~=0 then -              logwarning("%s: ignoring second kern xoff %s",pref(kind,lookupname),b*factor) -            end -          end -          done=true -        elseif krn~=0 then -          local k=set_kern(snext,factor,rlmode,krn) -          if trace_kerns then -            logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar)) -          end -          done=true -        end -        break -      end -    end -    return start,done -  end -end -local chainmores={} -local chainprocs={} -local function logprocess(...) -  if trace_steps then -    registermessage(...) -  end -  logs.report("otf subchain",...) -end -local function logwarning(...) -  logs.report("otf subchain",...) -end -function chainmores.chainsub(start,stop,kind,chainname,currentcontext,cache,lookuplist,chainlookupname,n) -  logprocess("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) -  return start,false -end -function chainmores.gsub_multiple(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) -  logprocess("%s: gsub_multiple not yet supported",cref(kind,chainname,chainlookupname)) -  return start,false -end -function chainmores.gsub_alternate(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) -  logprocess("%s: gsub_alternate not yet supported",cref(kind,chainname,chainlookupname)) -  return start,false -end -local function logprocess(...) -  if trace_steps then -    registermessage(...) -  end -  logs.report("otf chain",...) -end -local function logwarning(...) -  logs.report("otf chain",...) -end -function chainprocs.chainsub(start,stop,kind,chainname,currentcontext,cache,lookuplist,chainlookupname) -  logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) -  return start,false -end -function chainprocs.reversesub(start,stop,kind,chainname,currentcontext,cache,replacements) -  local char=start.char -  local replacement=replacements[char] -  if replacement then -    if trace_singles then -      logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement)) -    end -    start.char=replacement -    return start,true -  else -    return start,false -  end -end -local function delete_till_stop(start,stop,ignoremarks) -  if start~=stop then -    local done=false -    while not done do -      done=start==stop -      delete_node(start,start.next) -    end -  end -end -function chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex) -  if not chainindex then -    delete_till_stop(start,stop)  -  end -  local current=start -  local subtables=currentlookup.subtables -  while current do -    if current.id==glyph then -      local currentchar=current.char -      local lookupname=subtables[1] -      local replacement=cache.gsub_single[lookupname] -      if not replacement then -        if trace_bugs then -          logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) -        end -      else -        replacement=replacement[currentchar] -        if not replacement then -          if trace_bugs then -            logwarning("%s: no single for %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar)) -          end -        else -          if trace_singles then -            logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement)) -          end -          current.char=replacement -        end -      end -      return start,true -    elseif current==stop then -      break -    else -      current=current.next -    end -  end -  return start,false -end -chainmores.gsub_single=chainprocs.gsub_single -function chainprocs.gsub_multiple(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) -  delete_till_stop(start,stop) -  local startchar=start.char -  local subtables=currentlookup.subtables -  local lookupname=subtables[1] -  local replacements=cache.gsub_multiple[lookupname] -  if not replacements then -    if trace_bugs then -      logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname)) -    end -  else -    replacements=replacements[startchar] -    if not replacements then -      if trace_bugs then -        logwarning("%s: no multiple for %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar)) -      end -    else -      if trace_multiples then -        logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements)) -      end -      local sn=start.next -      for k=1,#replacements do -        if k==1 then -          start.char=replacements[k] -        else -          local n=copy_node(start)  -          n.char=replacements[k] -          n.next,n.prev=sn,start -          if sn then -            sn.prev=n -          end -          start.next,start=n,n -        end -      end -      return start,true -    end -  end -  return start,false -end -function chainprocs.gsub_alternate(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) -  delete_till_stop(start,stop) -  local current=start -  local subtables=currentlookup.subtables -  while current do -    if current.id==glyph then -      local currentchar=current.char -      local lookupname=subtables[1] -      local alternatives=cache.gsub_alternate[lookupname] -      if not alternatives then -        if trace_bugs then -          logwarning("%s: no alternative hits",cref(kind,chainname,chainlookupname,lookupname)) -        end -      else -        alternatives=alternatives[currentchar] -        if not alternatives then -          if trace_bugs then -            logwarning("%s: no alternative for %s",cref(kind,chainname,chainlookupname,lookupname),gref(currentchar)) -          end -        else -          local choice,index=alternative_glyph(current,alternatives,kind,chainname,chainlookupname,lookupname) -          current.char=choice -          if trace_alternatives then -            logprocess("%s: replacing single %s by alternative %s (%s)",cref(kind,chainname,chainlookupname,lookupname),index,gref(currentchar),gref(choice),index) -          end -        end -      end -      return start,true -    elseif current==stop then -      break -    else -      current=current.next -    end -  end -  return start,false -end -function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex) -  local startchar=start.char -  local subtables=currentlookup.subtables -  local lookupname=subtables[1] -  local ligatures=cache.gsub_ligature[lookupname] -  if not ligatures then -    if trace_bugs then -      logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) -    end -  else -    ligatures=ligatures[startchar] -    if not ligatures then -      if trace_bugs then -        logwarning("%s: no ligatures starting with %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) -      end -    else -      local s,discfound,last,nofreplacements=start.next,false,stop,0 -      while s do -        local id=s.id -        if id==disc then -          s=s.next -          discfound=true -        else -          local schar=s.char -          if marks[schar] then  -            s=s.next -          else -            local lg=ligatures[1][schar] -            if not lg then -              break -            else -              ligatures,last,nofreplacements=lg,s,nofreplacements+1 -              if s==stop then -                break -              else -                s=s.next -              end -            end -          end -        end -      end -      local l2=ligatures[2] -      if l2 then -        if chainindex then -          stop=last -        end -        if trace_ligatures then -          if start==stop then -            logprocess("%s: replacing character %s by ligature %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(l2)) -          else -            logprocess("%s: replacing character %s upto %s by ligature %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(stop.char),gref(l2)) -          end -        end -        start=toligature(kind,lookupname,start,stop,l2,currentlookup.flags[1],discfound) -        return start,true,nofreplacements -      elseif trace_bugs then -        if start==stop then -          logwarning("%s: replacing character %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) -        else -          logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(stop.char)) -        end -      end -    end -  end -  return start,false,0 -end -chainmores.gsub_ligature=chainprocs.gsub_ligature -function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) -  local markchar=start.char -  if marks[markchar] then -    local subtables=currentlookup.subtables -    local lookupname=subtables[1] -    local markanchors=cache.gpos_mark2base[lookupname] -    if markanchors then -      markanchors=markanchors[markchar] -    end -    if markanchors then -      local base=start.prev  -      if base and base.id==glyph and base.subtype<256 and base.font==currentfont then -        local basechar=base.char -        if marks[basechar] then -          while true do -            base=base.prev -            if base and base.id==glyph and base.subtype<256 and base.font==currentfont then -              basechar=base.char -              if not marks[basechar] then -                break -              end -            else -              if trace_bugs then -                logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) -              end -              return start,false -            end -          end -        end -        local baseanchors=descriptions[basechar].anchors -        if baseanchors then -          local baseanchors=baseanchors['basechar'] -          if baseanchors then -            local al=anchorlookups[lookupname] -            for anchor,ba in next,baseanchors do -              if al[anchor] then -                local ma=markanchors[anchor] -                if ma then -                  local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma) -                  if trace_marks then -                    logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%s,%s)", -                      cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) -                  end -                  return start,true -                end -              end -            end -            if trace_bugs then -              logwarning("%s, no matching anchors for mark %s and base %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) -            end -          end -        end -      elseif trace_bugs then -        logwarning("%s: prev node is no char",cref(kind,chainname,chainlookupname,lookupname)) -      end -    elseif trace_bugs then -      logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) -    end -  elseif trace_bugs then -    logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) -  end -  return start,false -end -function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) -  local markchar=start.char -  if marks[markchar] then -    local subtables=currentlookup.subtables -    local lookupname=subtables[1] -    local markanchors=cache.gpos_mark2ligature[lookupname] -    if markanchors then -      markanchors=markanchors[markchar] -    end -    if markanchors then -      local base=start.prev  -      local index=1 -      if base and base.id==glyph and base.subtype<256 and base.font==currentfont then -        local basechar=base.char -        if marks[basechar] then -          index=index+1 -          while true do -            base=base.prev -            if base and base.id==glyph and base.subtype<256 and base.font==currentfont then -              basechar=base.char -              if marks[basechar] then -                index=index+1 -              else -                break -              end -            else -              if trace_bugs then -                logwarning("%s: no base for mark %s",cref(kind,chainname,chainlookupname,lookupname),markchar) -              end -              return start,false -            end -          end -        end -        local i=has_attribute(start,markdone) -        if i then index=i end -        local baseanchors=descriptions[basechar].anchors -        if baseanchors then -          local baseanchors=baseanchors['baselig'] -          if baseanchors then -            local al=anchorlookups[lookupname] -            for anchor,ba in next,baseanchors do -              if al[anchor] then -                local ma=markanchors[anchor] -                if ma then -                  ba=ba[index] -                  if ba then -                    local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma,index) -                    if trace_marks then -                      logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%s,%s)", -                        cref(kind,chainname,chainlookupname,lookupname),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy) -                    end -                    return start,true -                  end -                end -              end -            end -            if trace_bugs then -              logwarning("%s: no matching anchors for mark %s and baselig %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) -            end -          end -        end -      elseif trace_bugs then -        logwarning("feature %s, lookup %s: prev node is no char",kind,lookupname) -      end -    elseif trace_bugs then -      logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) -    end -  elseif trace_bugs then -    logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) -  end -  return start,false -end -function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) -  local markchar=start.char -  if marks[markchar] then -      local subtables=currentlookup.subtables -      local lookupname=subtables[1] -      local markanchors=cache.gpos_mark2mark[lookupname] -      if markanchors then -        markanchors=markanchors[markchar] -      end -      if markanchors then -        local base=start.prev  -        if base and base.id==glyph and base.subtype<256 and base.font==currentfont then  -          local basechar=base.char -          local baseanchors=descriptions[basechar].anchors -          if baseanchors then -            baseanchors=baseanchors['basemark'] -            if baseanchors then -              local al=anchorlookups[lookupname] -              for anchor,ba in next,baseanchors do -                if al[anchor] then -                  local ma=markanchors[anchor] -                  if ma then -                    local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma) -                    if trace_marks then -                      logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%s,%s)", -                        cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) -                    end -                    return start,true -                  end -                end -              end -              if trace_bugs then -                logwarning("%s: no matching anchors for mark %s and basemark %s",gref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) -              end -            end -          end -        elseif trace_bugs then -          logwarning("%s: prev node is no mark",cref(kind,chainname,chainlookupname,lookupname)) -        end -      elseif trace_bugs then -        logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) -      end -  elseif trace_bugs then -    logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) -  end -  return start,false -end -function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) -  local alreadydone=cursonce and has_attribute(start,cursbase) -  if not alreadydone then -    local startchar=start.char -    local subtables=currentlookup.subtables -    local lookupname=subtables[1] -    local exitanchors=cache.gpos_cursive[lookupname] -    if exitanchors then -      exitanchors=exitanchors[startchar] -    end -    if exitanchors then -      local done=false -      if marks[startchar] then -        if trace_cursive then -          logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) -        end -      else -        local nxt=start.next -        while not done and nxt and nxt.id==glyph and nxt.subtype<256 and nxt.font==currentfont do -          local nextchar=nxt.char -          if marks[nextchar] then -            nxt=nxt.next -          else -            local entryanchors=descriptions[nextchar] -            if entryanchors then -              entryanchors=entryanchors.anchors -              if entryanchors then -                entryanchors=entryanchors['centry'] -                if entryanchors then -                  local al=anchorlookups[lookupname] -                  for anchor,entry in next,entryanchors do -                    if al[anchor] then -                      local exit=exitanchors[anchor] -                      if exit then -                        local dx,dy,bound=set_cursive(start,nxt,tfmdata.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) -                        if trace_cursive then -                          logprocess("%s: moving %s to %s cursive (%s,%s) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) -                        end -                        done=true -                        break -                      end -                    end -                  end -                end -              end -            else -              fonts.register_message(currentfont,startchar,"no entry anchors") -            end -            break -          end -        end -      end -      return start,done -    else -      if trace_cursive and trace_details then -        logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(start.char),alreadydone) -      end -      return start,false -    end -  end -  return start,false -end -function chainprocs.gpos_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex,sequence) -  local startchar=start.char -  local subtables=currentlookup.subtables -  local lookupname=subtables[1] -  local kerns=cache.gpos_single[lookupname] -  if kerns then -    kerns=kerns[startchar] -    if kerns then -      local dx,dy,w,h=set_pair(start,tfmdata.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) -      if trace_kerns then -        logprocess("%s: shifting single %s by (%s,%s) and correction (%s,%s)",cref(kind,chainname,chainlookupname),gref(startchar),dx,dy,w,h) -      end -    end -  end -  return start,false -end -function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex,sequence) -  local snext=start.next -  if snext then -    local startchar=start.char -    local subtables=currentlookup.subtables -    local lookupname=subtables[1] -    local kerns=cache.gpos_pair[lookupname] -    if kerns then -      kerns=kerns[startchar] -      if kerns then -        local prev,done=start,false -        local factor=tfmdata.factor -        while snext and snext.id==glyph and snext.subtype<256 and snext.font==currentfont do -          local nextchar=snext.char -          local krn=kerns[nextchar] -          if not krn and marks[nextchar] then -            prev=snext -            snext=snext.next -          else -            if not krn then -            elseif type(krn)=="table" then -              if krn[1]=="pair" then -                local a,b=krn[3],krn[4] -                if a and #a>0 then -                  local startchar=start.char -                  local x,y,w,h=set_pair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) -                  if trace_kerns then -                    logprocess("%s: shifting first of pair %s and %s by (%s,%s) and correction (%s,%s)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) -                  end -                end -                if b and #b>0 then -                  local startchar=start.char -                  local x,y,w,h=set_pair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) -                  if trace_kerns then -                    logprocess("%s: shifting second of pair %s and %s by (%s,%s) and correction (%s,%s)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) -                  end -                end -              else -                logs.report("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname)) -                local a,b=krn[3],krn[7] -                if a and a~=0 then -                  local k=set_kern(snext,factor,rlmode,a) -                  if trace_kerns then -                    logprocess("%s: inserting first kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar)) -                  end -                end -                if b and b~=0 then -                  logwarning("%s: ignoring second kern xoff %s",cref(kind,chainname,chainlookupname),b*factor) -                end -              end -              done=true -            elseif krn~=0 then -              local k=set_kern(snext,factor,rlmode,krn) -              if trace_kerns then -                logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar)) -              end -              done=true -            end -            break -          end -        end -        return start,done -      end -    end -  end -  return start,false -end -local function show_skip(kind,chainname,char,ck,class) -  if ck[9] then -    logwarning("%s: skipping char %s (%s) in rule %s, lookuptype %s (%s=>%s)",cref(kind,chainname),gref(char),class,ck[1],ck[2],ck[9],ck[10]) -  else -    logwarning("%s: skipping char %s (%s) in rule %s, lookuptype %s",cref(kind,chainname),gref(char),class,ck[1],ck[2]) -  end -end -local function normal_handle_contextchain(start,kind,chainname,contexts,sequence,cache) -  local flags,done=sequence.flags,false -  local skipmark,skipligature,skipbase=flags[1],flags[2],flags[3] -  local someskip=skipmark or skipligature or skipbase  -  local markclass=sequence.markclass  -  local skipped=false -  for k=1,#contexts do -    local match,current,last=true,start,start -    local ck=contexts[k] -    local seq=ck[3] -    local s=#seq -    if s==1 then -      match=current.id==glyph and current.subtype<256 and current.font==currentfont and seq[1][current.char] -    else -      local f,l=ck[4],ck[5] -      if f==l then -        match=true -      else -        local n=f+1 -        last=last.next -          while n<=l do -            if last then -              local id=last.id -              if id==glyph then -                if last.subtype<256 and last.font==currentfont then -                  local char=last.char -                  local ccd=descriptions[char] -                  if ccd then -                    local class=ccd.class -                    if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then -                      skipped=true -                      if trace_skips then -                        show_skip(kind,chainname,char,ck,class) -                      end -                      last=last.next -                    elseif seq[n][char] then -                      if n<l then -                        last=last.next -                      end -                      n=n+1 -                    else -                      match=false break -                    end -                  else -                    match=false break -                  end -                else -                  match=false break -                end -              elseif id==disc then  -                last=last.next -              else -                match=false break -              end -            else -              match=false break -            end -          end -      end -      if match and f>1 then -        local prev=start.prev -        if prev then -          local n=f-1 -          while n>=1 do -            if prev then -              local id=prev.id -              if id==glyph then -                if prev.subtype<256 and prev.font==currentfont then  -                  local char=prev.char -                  local ccd=descriptions[char] -                  if ccd then -                    local class=ccd.class -                    if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then -                      skipped=true -                      if trace_skips then -                        show_skip(kind,chainname,char,ck,class) -                      end -                    elseif seq[n][char] then -                      n=n -1 -                    else -                      match=false break -                    end -                  else -                    match=false break -                  end -                else -                  match=false break -                end -              elseif id==disc then -              elseif seq[n][32] then -                n=n -1 -              else -                match=false break -              end -              prev=prev.prev -            elseif seq[n][32] then -              n=n -1 -            else -              match=false break -            end -          end -        elseif f==2 then -          match=seq[1][32] -        else -          for n=f-1,1 do -            if not seq[n][32] then -              match=false break -            end -          end -        end -      end -      if match and s>l then -        local current=last.next -        if current then -          local n=l+1 -          while n<=s do -            if current then -              local id=current.id -              if id==glyph then -                if current.subtype<256 and current.font==currentfont then  -                  local char=current.char -                  local ccd=descriptions[char] -                  if ccd then -                    local class=ccd.class -                    if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then -                      skipped=true -                      if trace_skips then -                        show_skip(kind,chainname,char,ck,class) -                      end -                    elseif seq[n][char] then -                      n=n+1 -                    else -                      match=false break -                    end -                  else -                    match=false break -                  end -                else -                  match=false break -                end -              elseif id==disc then -              elseif seq[n][32] then  -                n=n+1 -              else -                match=false break -              end -              current=current.next -            elseif seq[n][32] then -              n=n+1 -            else -              match=false break -            end -          end -        elseif s-l==1 then -          match=seq[s][32] -        else -          for n=l+1,s do -            if not seq[n][32] then -              match=false break -            end -          end -        end -      end -    end -    if match then -      if trace_contexts then -        local rule,lookuptype,f,l=ck[1],ck[2],ck[4],ck[5] -        local char=start.char -        if ck[9] then -          logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %s (%s=>%s)",cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10]) -        else -          logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %s",cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype) -        end -      end -      local chainlookups=ck[6] -      if chainlookups then -        local nofchainlookups=#chainlookups -        if nofchainlookups==1 then -          local chainlookupname=chainlookups[1] -          local chainlookup=lookuptable[chainlookupname] -          local cp=chainprocs[chainlookup.type] -          if cp then -            start,done=cp(start,last,kind,chainname,ck,cache,chainlookup,chainlookupname,nil,sequence) -          else -            logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) -          end -         else -          local i=1 -          repeat -if skipped then -  while true do -    local char=start.char -    local ccd=descriptions[char] -    if ccd then -      local class=ccd.class -      if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then -        start=start.next -      else -        break -      end -    else -      break -    end -  end -end -            local chainlookupname=chainlookups[i] -            local chainlookup=lookuptable[chainlookupname] -            local cp=chainmores[chainlookup.type] -            if cp then -              local ok,n -              start,ok,n=cp(start,last,kind,chainname,ck,cache,chainlookup,chainlookupname,i,sequence) -              if ok then -                done=true -                i=i+(n or 1) -              else -                i=i+1 -              end -            else -              logprocess("%s: multiple subchains for %s are not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) -              i=i+1 -            end -            start=start.next -          until i>nofchainlookups -        end -      else -        local replacements=ck[7] -        if replacements then -          start,done=chainprocs.reversesub(start,last,kind,chainname,ck,cache,replacements)  -        else -          done=true  -          if trace_contexts then -            logprocess("%s: skipping match",cref(kind,chainname)) -          end -        end -      end -    end -  end -  return start,done -end -local verbose_handle_contextchain=function(font,...) -  logwarning("no verbose handler installed, reverting to 'normal'") -  otf.setcontextchain() -  return normal_handle_contextchain(...) -end -otf.chainhandlers={ -  normal=normal_handle_contextchain, -  verbose=verbose_handle_contextchain, -} -function otf.setcontextchain(method) -  if not method or method=="normal" or not otf.chainhandlers[method] then -    if handlers.contextchain then  -      logwarning("installing normal contextchain handler") -    end -    handlers.contextchain=normal_handle_contextchain -  else -    logwarning("installing contextchain handler '%s'",method) -    local handler=otf.chainhandlers[method] -    handlers.contextchain=function(...) -      return handler(currentfont,...)  -    end -  end -  handlers.gsub_context=handlers.contextchain -  handlers.gsub_contextchain=handlers.contextchain -  handlers.gsub_reversecontextchain=handlers.contextchain -  handlers.gpos_contextchain=handlers.contextchain -  handlers.gpos_context=handlers.contextchain -end -otf.setcontextchain() -local missing={}  -local function logprocess(...) -  if trace_steps then -    registermessage(...) -  end -  logs.report("otf process",...) -end -local function logwarning(...) -  logs.report("otf process",...) -end -local function report_missing_cache(typ,lookup) -  local f=missing[currentfont] if not f then f={} missing[currentfont]=f end -  local t=f[typ]        if not t then t={} f[typ]=t end -  if not t[lookup] then -    t[lookup]=true -    logwarning("missing cache for lookup %s of type %s in font %s (%s)",lookup,typ,currentfont,tfmdata.fullname) -  end -end -local resolved={} -function fonts.methods.node.otf.features(head,font,attr) -  if trace_steps then -    checkstep(head) -  end -  tfmdata=fontdata[font] -  local shared=tfmdata.shared -  otfdata=shared.otfdata -  local luatex=otfdata.luatex -  descriptions=tfmdata.descriptions -  characters=tfmdata.characters -  indices=tfmdata.indices -  unicodes=tfmdata.unicodes -  marks=tfmdata.marks -  anchorlookups=luatex.lookup_to_anchor -  currentfont=font -  rlmode=0 -  local featuredata=otfdata.shared.featuredata  -  local sequences=luatex.sequences -  lookuptable=luatex.lookups -  local done=false -  local script,language,s_enabled,a_enabled,dyn -  local attribute_driven=attr and attr~=0 -  if attribute_driven then -    local features=context_setups[context_numbers[attr]]  -    dyn=context_merged[attr] or 0 -    language,script=features.language or "dflt",features.script or "dflt" -    a_enabled=features  -    if dyn==2 or dyn==-2 then -      s_enabled=shared.features -    end -  else -    language,script=tfmdata.language or "dflt",tfmdata.script or "dflt" -    s_enabled=shared.features  -    dyn=0 -  end -  local res=resolved[font]   if not res  then res={} resolved[font]=res end -  local rs=res   [script]  if not rs  then rs={} res   [script]=rs end -  local rl=rs   [language] if not rl  then rl={} rs   [language]=rl end -  local ra=rl   [attr]   if ra==nil then ra={} rl   [attr]=ra end -  for s=1,#sequences do -    local pardir,txtdir,success=0,{},false -    local sequence=sequences[s] -    local r=ra[s]  -    if r==nil then -      local chain=sequence.chain or 0 -      local features=sequence.features -      if not features then -        r=false  -      else -        local valid,attribute,kind,what=false,false -        for k,v in next,features do -          local s_e=s_enabled and s_enabled[k] -          local a_e=a_enabled and a_enabled[k] -          if s_e or a_e then -            local l=v[script] or v[wildcard] -            if l then -              if l[language] then -                valid,what=s_e or a_e,language -              elseif l[wildcard] then -                valid,what=s_e or a_e,wildcard -              end -              if valid then -                kind,attribute=k,special_attributes[k] or false -                if a_e and dyn<0 then -                  valid=false -                end -                if trace_applied then -                  local typ,action=match(sequence.type,"(.*)_(.*)") -                  logs.report("otf node mode", -                    "%s font: %03i, dynamic: %03i, kind: %s, lookup: %3i, script: %-4s, language: %-4s (%-4s), type: %s, action: %s, name: %s", -                    (valid and "+") or "-",font,attr or 0,kind,s,script,language,what,typ,action,sequence.name) -                end -                break -              end -            end -          end -        end -        if valid then -          r={ valid,attribute,chain,kind } -        else -          r=false  -        end -      end -      ra[s]=r -    end -    featurevalue=r and r[1]  -    if featurevalue then -      local attribute,chain,typ,subtables=r[2],r[3],sequence.type,sequence.subtables -      if chain<0 then -        local handler=handlers[typ] -        local thecache=featuredata[typ] or {} -        local start=find_node_tail(head)  -        while start do -          local id=start.id -          if id==glyph then -            if start.subtype<256 and start.font==font then -              local a=has_attribute(start,0) -              if a then -                a=a==attr -              else -                a=true -              end -              if a then -                for i=1,#subtables do -                  local lookupname=subtables[i] -                  local lookupcache=thecache[lookupname] -                  if lookupcache then -                    local lookupmatch=lookupcache[start.char] -                    if lookupmatch then -                      start,success=handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,i) -                      if success then -                        break -                      end -                    end -                  else -                    report_missing_cache(typ,lookupname) -                  end -                end -                if start then start=start.prev end -              else -                start=start.prev -              end -            else -              start=start.prev -            end -          else -            start=start.prev -          end -        end -      else -        local handler=handlers[typ] -        local ns=#subtables -        local thecache=featuredata[typ] or {} -        local start=head  -        rlmode=0  -        if ns==1 then -          local lookupname=subtables[1] -          local lookupcache=thecache[lookupname] -          if not lookupcache then -            report_missing_cache(typ,lookupname) -          else -            while start do -              local id=start.id -              if id==glyph then -                if start.subtype<256 and start.font==font then -                  local a=has_attribute(start,0) -                  if a then -                    a=(a==attr) and (not attribute or has_attribute(start,state,attribute)) -                  else -                    a=not attribute or has_attribute(start,state,attribute) -                  end -                  if a then -                    local lookupmatch=lookupcache[start.char] -                    if lookupmatch then -                      local ok -                      start,ok=handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,1) -                      if ok then -                        success=true -                      end -                    end -                    if start then start=start.next end -                  else -                    start=start.next -                  end -                else -                  start=start.next -                end -              elseif id==whatsit then -                local subtype=start.subtype -                if subtype==7 then -                  local dir=start.dir -                  if   dir=="+TRT" or dir=="+TLT" then -                    insert(txtdir,dir) -                  elseif dir=="-TRT" or dir=="-TLT" then -                    remove(txtdir) -                  end -                  local d=txtdir[#txtdir] -                  if d=="+TRT" then -                    rlmode=-1 -                  elseif d=="+TLT" then -                    rlmode=1 -                  else -                    rlmode=pardir -                  end -                  if trace_directions then -                    logs.report("fonts","directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) -                  end -                elseif subtype==6 then -                  local dir=start.dir -                  if dir=="TRT" then -                    pardir=-1 -                  elseif dir=="TLT" then -                    pardir=1 -                  else -                    pardir=0 -                  end -                  rlmode=pardir -                  if trace_directions then -                    logs.report("fonts","directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) -                  end -                end -                start=start.next -              else -                start=start.next -              end -            end -          end -        else -          while start do -            local id=start.id -            if id==glyph then -              if start.subtype<256 and start.font==font then -                local a=has_attribute(start,0) -                if a then -                  a=(a==attr) and (not attribute or has_attribute(start,state,attribute)) -                else -                  a=not attribute or has_attribute(start,state,attribute) -                end -                if a then -                  for i=1,ns do -                    local lookupname=subtables[i] -                    local lookupcache=thecache[lookupname] -                    if lookupcache then -                      local lookupmatch=lookupcache[start.char] -                      if lookupmatch then -                        local ok -                        start,ok=handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,i) -                        if ok then -                          success=true -                          break -                        end -                      end -                    else -                      report_missing_cache(typ,lookupname) -                    end -                  end -                  if start then start=start.next end -                else -                  start=start.next -                end -              else -                start=start.next -              end -            elseif id==whatsit then -              local subtype=start.subtype -              if subtype==7 then -                local dir=start.dir -                if   dir=="+TRT" or dir=="+TLT" then -                  insert(txtdir,dir) -                elseif dir=="-TRT" or dir=="-TLT" then -                  remove(txtdir) -                end -                local d=txtdir[#txtdir] -                if d=="+TRT" then -                  rlmode=-1 -                elseif d=="+TLT" then -                  rlmode=1 -                else -                  rlmode=pardir -                end -                if trace_directions then -                  logs.report("fonts","directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) -                end -              elseif subtype==6 then -                local dir=start.dir -                if dir=="TRT" then -                  pardir=-1 -                elseif dir=="TLT" then -                  pardir=1 -                else -                  pardir=0 -                end -                rlmode=pardir -                if trace_directions then -                  logs.report("fonts","directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) -                end -              end -              start=start.next -            else -              start=start.next -            end -          end -        end -      end -      if success then -        done=true -      end -      if trace_steps then  -        registerstep(head) -      end -    end -  end -  return head,done -end -otf.features.prepare={} -local function split(replacement,original,cache,unicodes) -  local o,t,n={},{},0 -  for s in gmatch(original,"[^ ]+") do -    local us=unicodes[s] -    if type(us)=="number" then  -      o[#o+1]=us -    else -      o[#o+1]=us[1] -    end -  end -  for s in gmatch(replacement,"[^ ]+") do -    n=n+1 -    local us=unicodes[s] -    if type(us)=="number" then  -      t[o[n]]=us -    else -      t[o[n]]=us[1] -    end -  end -  return t -end -local function uncover(covers,result,cache,unicodes) -  for n=1,#covers do -    local c=covers[n] -    local cc=cache[c] -    if not cc then -      local t={} -      for s in gmatch(c,"[^ ]+") do -        local us=unicodes[s] -        if type(us)=="number" then -          t[us]=true -        else -          for i=1,#us do -            t[us[i]]=true -          end -        end -      end -      cache[c]=t -      result[#result+1]=t -    else -      result[#result+1]=cc -    end -  end -end -local function prepare_lookups(tfmdata) -  local otfdata=tfmdata.shared.otfdata -  local featuredata=otfdata.shared.featuredata -  local anchor_to_lookup=otfdata.luatex.anchor_to_lookup -  local lookup_to_anchor=otfdata.luatex.lookup_to_anchor -  local multiple=featuredata.gsub_multiple -  local alternate=featuredata.gsub_alternate -  local single=featuredata.gsub_single -  local ligature=featuredata.gsub_ligature -  local pair=featuredata.gpos_pair -  local position=featuredata.gpos_single -  local kerns=featuredata.gpos_pair -  local mark=featuredata.gpos_mark2mark -  local cursive=featuredata.gpos_cursive -  local unicodes=tfmdata.unicodes  -  local indices=tfmdata.indices -  local descriptions=tfmdata.descriptions -  local action={ -    substitution=function(p,lookup,glyph,unicode) -      local old,new=unicode,unicodes[p[2]] -      if type(new)=="table" then -        new=new[1] -      end -      local s=single[lookup] -      if not s then s={} single[lookup]=s end -      s[old]=new -    end, -    multiple=function (p,lookup,glyph,unicode) -      local old,new=unicode,{} -      local m=multiple[lookup] -      if not m then m={} multiple[lookup]=m end -      m[old]=new -      for pc in gmatch(p[2],"[^ ]+") do -        local upc=unicodes[pc] -        if type(upc)=="number" then -          new[#new+1]=upc -        else -          new[#new+1]=upc[1] -        end -      end -    end, -    alternate=function(p,lookup,glyph,unicode) -      local old,new=unicode,{} -      local a=alternate[lookup] -      if not a then a={} alternate[lookup]=a end -      a[old]=new -      for pc in gmatch(p[2],"[^ ]+") do -        local upc=unicodes[pc] -        if type(upc)=="number" then -          new[#new+1]=upc -        else -          new[#new+1]=upc[1] -        end -      end -    end, -    ligature=function (p,lookup,glyph,unicode) -      local first=true -      local t=ligature[lookup] -      if not t then t={} ligature[lookup]=t end -      for s in gmatch(p[2],"[^ ]+") do -        if first then -          local u=unicodes[s] -          if not u then -            logs.report("define otf","lookup %s: ligature %s => %s ignored due to invalid unicode",lookup,p[2],glyph.name) -            break -          elseif type(u)=="number" then -            if not t[u] then -              t[u]={ {} } -            end -            t=t[u] -          else -            local tt=t -            local tu -            for i=1,#u do -              local u=u[i] -              if i==1 then -                if not t[u] then -                  t[u]={ {} } -                end -                tu=t[u] -                t=tu -              else -                if not t[u] then -                  tt[u]=tu -                end -              end -            end -          end -          first=false -        else -          s=unicodes[s] -          local t1=t[1] -          if not t1[s] then -            t1[s]={ {} } -          end -          t=t1[s] -        end -      end -      t[2]=unicode -    end, -    position=function(p,lookup,glyph,unicode) -      local s=position[lookup] -      if not s then s={} position[lookup]=s end -      s[unicode]=p[2]  -    end, -    pair=function(p,lookup,glyph,unicode) -      local s=pair[lookup] -      if not s then s={} pair[lookup]=s end -      local others=s[unicode] -      if not others then others={} s[unicode]=others end -      local two=p[2] -      local upc=unicodes[two] -      if not upc then -        for pc in gmatch(two,"[^ ]+") do -          local upc=unicodes[pc] -          if type(upc)=="number" then -            others[upc]=p  -          else -            for i=1,#upc do -              others[upc[i]]=p  -            end -          end -        end -      elseif type(upc)=="number" then -        others[upc]=p  -      else -        for i=1,#upc do -          others[upc[i]]=p  -        end -      end -    end, -  } -  for unicode,glyph in next,descriptions do -    local lookups=glyph.slookups -    if lookups then -      for lookup,p in next,lookups do -        action[p[1]](p,lookup,glyph,unicode) -      end -    end -    local lookups=glyph.mlookups -    if lookups then -      for lookup,whatever in next,lookups do -        for i=1,#whatever do  -          local p=whatever[i] -          action[p[1]](p,lookup,glyph,unicode) -        end -      end -    end -    local list=glyph.mykerns -    if list then -      for lookup,krn in next,list do -        local k=kerns[lookup] -        if not k then k={} kerns[lookup]=k end -        k[unicode]=krn -      end -    end -    local oanchor=glyph.anchors -    if oanchor then -      for typ,anchors in next,oanchor do  -        if typ=="mark" then -          for name,anchor in next,anchors do -            local lookups=anchor_to_lookup[name] -            if lookups then -              for lookup,_ in next,lookups do -                local f=mark[lookup] -                if not f then f={} mark[lookup]=f end -                f[unicode]=anchors -              end -            end -          end -        elseif typ=="cexit" then  -          for name,anchor in next,anchors do -            local lookups=anchor_to_lookup[name] -            if lookups then -              for lookup,_ in next,lookups do -                local f=cursive[lookup] -                if not f then f={} cursive[lookup]=f end -                f[unicode]=anchors -              end -            end -          end -        end -      end -    end -  end -end -luatex=luatex or {}  -local function prepare_contextchains(tfmdata) -  local otfdata=tfmdata.shared.otfdata -  local lookups=otfdata.lookups -  if lookups then -    local featuredata=otfdata.shared.featuredata -    local contextchain=featuredata.gsub_contextchain  -    local reversecontextchain=featuredata.gsub_reversecontextchain  -    local characters=tfmdata.characters -    local unicodes=tfmdata.unicodes -    local indices=tfmdata.indices -    local cache=luatex.covers -    if not cache then -      cache={} -      luatex.covers=cache -    end -    for lookupname,lookupdata in next,otfdata.lookups do -      local lookuptype=lookupdata.type -      if not lookuptype then -        logs.report("otf process","missing lookuptype for %s",lookupname) -      else -        local rules=lookupdata.rules -        if rules then -          local fmt=lookupdata.format -          if fmt=="coverage" then -            if lookuptype~="chainsub" and lookuptype~="chainpos" then -              logs.report("otf process","unsupported coverage %s for %s",lookuptype,lookupname) -            else -              local contexts=contextchain[lookupname] -              if not contexts then -                contexts={} -                contextchain[lookupname]=contexts -              end -              local t={} -              for nofrules=1,#rules do  -                local rule=rules[nofrules] -                local coverage=rule.coverage -                if coverage and coverage.current then -                  local current,before,after,sequence=coverage.current,coverage.before,coverage.after,{} -                  if before then -                    uncover(before,sequence,cache,unicodes) -                  end -                  local start=#sequence+1 -                  uncover(current,sequence,cache,unicodes) -                  local stop=#sequence -                  if after then -                    uncover(after,sequence,cache,unicodes) -                  end -                  if sequence[1] then -                    t[#t+1]={ nofrules,lookuptype,sequence,start,stop,rule.lookups } -                    for unic,_ in next,sequence[start] do -                      local cu=contexts[unic] -                      if not cu then -                        contexts[unic]=t -                      end -                    end -                  end -                end -              end -            end -          elseif fmt=="reversecoverage" then -            if lookuptype~="reversesub" then -              logs.report("otf process","unsupported reverse coverage %s for %s",lookuptype,lookupname) -            else -              local contexts=reversecontextchain[lookupname] -              if not contexts then -                contexts={} -                reversecontextchain[lookupname]=contexts -              end -              local t={} -              for nofrules=1,#rules do -                local rule=rules[nofrules] -                local reversecoverage=rule.reversecoverage -                if reversecoverage and reversecoverage.current then -                  local current,before,after,replacements,sequence=reversecoverage.current,reversecoverage.before,reversecoverage.after,reversecoverage.replacements,{} -                  if before then -                    uncover(before,sequence,cache,unicodes) -                  end -                  local start=#sequence+1 -                  uncover(current,sequence,cache,unicodes) -                  local stop=#sequence -                  if after then -                    uncover(after,sequence,cache,unicodes) -                  end -                  if replacements then -                    replacements=split(replacements,current[1],cache,unicodes) -                  end -                  if sequence[1] then -                    t[#t+1]={ nofrules,lookuptype,sequence,start,stop,rule.lookups,replacements } -                    for unic,_ in next,sequence[start] do -                      local cu=contexts[unic] -                      if not cu then -                        contexts[unic]=t -                      end -                    end -                  end -                end -              end -            end -          elseif fmt=="glyphs" then -            if lookuptype~="chainsub" and lookuptype~="chainpos" then -              logs.report("otf process","unsupported coverage %s for %s",lookuptype,lookupname) -            else -              local contexts=contextchain[lookupname] -              if not contexts then -                contexts={} -                contextchain[lookupname]=contexts -              end -              local t={} -              for nofrules=1,#rules do -                local rule=rules[nofrules] -                local glyphs=rule.glyphs -                if glyphs and glyphs.names then -                  local fore,back,names,sequence=glyphs.fore,glyphs.back,glyphs.names,{} -                  if fore and fore~="" then -                    fore=lpegmatch(split_at_space,fore) -                    uncover(fore,sequence,cache,unicodes) -                  end -                  local start=#sequence+1 -                  names=lpegmatch(split_at_space,names) -                  uncover(names,sequence,cache,unicodes) -                  local stop=#sequence -                  if back and back~="" then -                    back=lpegmatch(split_at_space,back) -                    uncover(back,sequence,cache,unicodes) -                  end -                  if sequence[1] then -                    t[#t+1]={ nofrules,lookuptype,sequence,start,stop,rule.lookups } -                    for unic,_ in next,sequence[start] do -                      local cu=contexts[unic] -                      if not cu then -                        contexts[unic]=t -                      end -                    end -                  end -                end -              end -            end -          end -        end -      end -    end -  end -end -function fonts.initializers.node.otf.features(tfmdata,value) -  if true then  -    if not tfmdata.shared.otfdata.shared.initialized then -      local t=trace_preparing and os.clock() -      local otfdata=tfmdata.shared.otfdata -      local featuredata=otfdata.shared.featuredata -      featuredata.gsub_multiple={} -      featuredata.gsub_alternate={} -      featuredata.gsub_single={} -      featuredata.gsub_ligature={} -      featuredata.gsub_contextchain={} -      featuredata.gsub_reversecontextchain={} -      featuredata.gpos_pair={} -      featuredata.gpos_single={} -      featuredata.gpos_mark2base={} -      featuredata.gpos_mark2ligature=featuredata.gpos_mark2base -      featuredata.gpos_mark2mark=featuredata.gpos_mark2base -      featuredata.gpos_cursive={} -      featuredata.gpos_contextchain=featuredata.gsub_contextchain -      featuredata.gpos_reversecontextchain=featuredata.gsub_reversecontextchain -      prepare_contextchains(tfmdata) -      prepare_lookups(tfmdata) -      otfdata.shared.initialized=true -      if trace_preparing then -        logs.report("otf process","preparation time is %0.3f seconds for %s",os.clock()-t,tfmdata.fullname or "?") -      end -    end -  end -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-ota']={ -  version=1.001, -  comment="companion to font-otf.lua (analysing)", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local type,tostring,match,format,concat=type,tostring,string.match,string.format,table.concat -if not trackers then trackers={ register=function() end } end -local trace_analyzing=false trackers.register("otf.analyzing",function(v) trace_analyzing=v end) -local trace_cjk=false trackers.register("cjk.injections",function(v) trace_cjk=v end) -trackers.register("cjk.analyzing","otf.analyzing") -fonts=fonts            or {} -fonts.analyzers=fonts.analyzers       or {} -fonts.analyzers.initializers=fonts.analyzers.initializers or { node={ otf={} } } -fonts.analyzers.methods=fonts.analyzers.methods   or { node={ otf={} } } -local otf=fonts.otf -local tfm=fonts.tfm -local initializers=fonts.analyzers.initializers -local methods=fonts.analyzers.methods -local glyph=node.id('glyph') -local glue=node.id('glue') -local penalty=node.id('penalty') -local set_attribute=node.set_attribute -local has_attribute=node.has_attribute -local traverse_id=node.traverse_id -local traverse_node_list=node.traverse -local fontdata=fonts.ids -local state=attributes.private('state') -local fcs=(fonts.color and fonts.color.set)  or function() end -local fcr=(fonts.color and fonts.color.reset) or function() end -local a_to_script=otf.a_to_script -local a_to_language=otf.a_to_language -function fonts.initializers.node.otf.analyze(tfmdata,value,attr) -  local script,language -  if attr and attr>0 then -    script,language=a_to_script[attr],a_to_language[attr] -  else -    script,language=tfmdata.script,tfmdata.language -  end -  local action=initializers[script] -  if action then -    if type(action)=="function" then -      return action(tfmdata,value) -    else -      local action=action[language] -      if action then -        return action(tfmdata,value) -      end -    end -  end -  return nil -end -function fonts.methods.node.otf.analyze(head,font,attr) -  local tfmdata=fontdata[font] -  local script,language -  if attr and attr>0 then -    script,language=a_to_script[attr],a_to_language[attr] -  else -    script,language=tfmdata.script,tfmdata.language -  end -  local action=methods[script] -  if action then -    if type(action)=="function" then -      return action(head,font,attr) -    else -      action=action[language] -      if action then -        return action(head,font,attr) -      end -    end -  end -  return head,false -end -otf.features.register("analyze",true)   -table.insert(fonts.triggers,"analyze") -fonts.analyzers.methods.latn=fonts.analyzers.aux.setstate -local zwnj=0x200C -local zwj=0x200D -local isol={ -  [0x0600]=true,[0x0601]=true,[0x0602]=true,[0x0603]=true, -  [0x0608]=true,[0x060B]=true,[0x0621]=true,[0x0674]=true, -  [0x06DD]=true,[zwnj]=true, -} -local isol_fina={ -  [0x0622]=true,[0x0623]=true,[0x0624]=true,[0x0625]=true, -  [0x0627]=true,[0x0629]=true,[0x062F]=true,[0x0630]=true, -  [0x0631]=true,[0x0632]=true,[0x0648]=true,[0x0671]=true, -  [0x0672]=true,[0x0673]=true,[0x0675]=true,[0x0676]=true, -  [0x0677]=true,[0x0688]=true,[0x0689]=true,[0x068A]=true, -  [0x068B]=true,[0x068C]=true,[0x068D]=true,[0x068E]=true, -  [0x068F]=true,[0x0690]=true,[0x0691]=true,[0x0692]=true, -  [0x0693]=true,[0x0694]=true,[0x0695]=true,[0x0696]=true, -  [0x0697]=true,[0x0698]=true,[0x0699]=true,[0x06C0]=true, -  [0x06C3]=true,[0x06C4]=true,[0x06C5]=true,[0x06C6]=true, -  [0x06C7]=true,[0x06C8]=true,[0x06C9]=true,[0x06CA]=true, -  [0x06CB]=true,[0x06CD]=true,[0x06CF]=true,[0x06D2]=true, -  [0x06D3]=true,[0x06D5]=true,[0x06EE]=true,[0x06EF]=true, -  [0x0759]=true,[0x075A]=true,[0x075B]=true,[0x076B]=true, -  [0x076C]=true,[0x0771]=true,[0x0773]=true,[0x0774]=true, -	[0x0778]=true,[0x0779]=true,[0xFEF5]=true,[0xFEF7]=true, -	[0xFEF9]=true,[0xFEFB]=true, -} -local isol_fina_medi_init={ -  [0x0626]=true,[0x0628]=true,[0x062A]=true,[0x062B]=true, -  [0x062C]=true,[0x062D]=true,[0x062E]=true,[0x0633]=true, -  [0x0634]=true,[0x0635]=true,[0x0636]=true,[0x0637]=true, -  [0x0638]=true,[0x0639]=true,[0x063A]=true,[0x063B]=true, -  [0x063C]=true,[0x063D]=true,[0x063E]=true,[0x063F]=true, -  [0x0640]=true,[0x0641]=true,[0x0642]=true,[0x0643]=true, -  [0x0644]=true,[0x0645]=true,[0x0646]=true,[0x0647]=true, -  [0x0649]=true,[0x064A]=true,[0x066E]=true,[0x066F]=true, -  [0x0678]=true,[0x0679]=true,[0x067A]=true,[0x067B]=true, -  [0x067C]=true,[0x067D]=true,[0x067E]=true,[0x067F]=true, -  [0x0680]=true,[0x0681]=true,[0x0682]=true,[0x0683]=true, -  [0x0684]=true,[0x0685]=true,[0x0686]=true,[0x0687]=true, -  [0x069A]=true,[0x069B]=true,[0x069C]=true,[0x069D]=true, -  [0x069E]=true,[0x069F]=true,[0x06A0]=true,[0x06A1]=true, -  [0x06A2]=true,[0x06A3]=true,[0x06A4]=true,[0x06A5]=true, -  [0x06A6]=true,[0x06A7]=true,[0x06A8]=true,[0x06A9]=true, -  [0x06AA]=true,[0x06AB]=true,[0x06AC]=true,[0x06AD]=true, -  [0x06AE]=true,[0x06AF]=true,[0x06B0]=true,[0x06B1]=true, -  [0x06B2]=true,[0x06B3]=true,[0x06B4]=true,[0x06B5]=true, -  [0x06B6]=true,[0x06B7]=true,[0x06B8]=true,[0x06B9]=true, -  [0x06BA]=true,[0x06BB]=true,[0x06BC]=true,[0x06BD]=true, -  [0x06BE]=true,[0x06BF]=true,[0x06C1]=true,[0x06C2]=true, -  [0x06CC]=true,[0x06CE]=true,[0x06D0]=true,[0x06D1]=true, -  [0x06FA]=true,[0x06FB]=true,[0x06FC]=true,[0x06FF]=true, -  [0x0750]=true,[0x0751]=true,[0x0752]=true,[0x0753]=true, -  [0x0754]=true,[0x0755]=true,[0x0756]=true,[0x0757]=true, -  [0x0758]=true,[0x075C]=true,[0x075D]=true,[0x075E]=true, -  [0x075F]=true,[0x0760]=true,[0x0761]=true,[0x0762]=true, -  [0x0763]=true,[0x0764]=true,[0x0765]=true,[0x0766]=true, -  [0x0767]=true,[0x0768]=true,[0x0769]=true,[0x076A]=true, -  [0x076D]=true,[0x076E]=true,[0x076F]=true,[0x0770]=true, -  [0x0772]=true,[0x0775]=true,[0x0776]=true,[0x0777]=true, -  [0x077A]=true,[0x077B]=true,[0x077C]=true,[0x077D]=true, -  [0x077E]=true,[0x077F]=true,[zwj]=true, -} -local arab_warned={} -local function warning(current,what) -  local char=current.char -  if not arab_warned[char] then -    log.report("analyze","arab: character %s (U+%04X) has no %s class",char,char,what) -    arab_warned[char]=true -  end -end -function fonts.analyzers.methods.nocolor(head,font,attr) -  for n in traverse_node_list(head,glyph) do -    if not font or n.font==font then -      fcr(n) -    end -  end -  return head,true -end -local function finish(first,last) -  if last then -    if first==last then -      local fc=first.char -      if isol_fina_medi_init[fc] or isol_fina[fc] then -        set_attribute(first,state,4)  -        if trace_analyzing then fcs(first,"font:isol") end -      else -        warning(first,"isol") -        set_attribute(first,state,0)  -        if trace_analyzing then fcr(first) end -      end -    else -      local lc=last.char -      if isol_fina_medi_init[lc] or isol_fina[lc] then -        set_attribute(last,state,3)  -        if trace_analyzing then fcs(last,"font:fina") end -      else -        warning(last,"fina") -        set_attribute(last,state,0)  -        if trace_analyzing then fcr(last) end -      end -    end -    first,last=nil,nil -  elseif first then -    local fc=first.char -    if isol_fina_medi_init[fc] or isol_fina[fc] then -      set_attribute(first,state,4)  -      if trace_analyzing then fcs(first,"font:isol") end -    else -      warning(first,"isol") -      set_attribute(first,state,0)  -      if trace_analyzing then fcr(first) end -    end -    first=nil -  end -  return first,last -end -function fonts.analyzers.methods.arab(head,font,attr)  -  local tfmdata=fontdata[font] -  local marks=tfmdata.marks -  local first,last,current,done=nil,nil,head,false -  while current do -    if current.id==glyph and current.subtype<256 and current.font==font and not has_attribute(current,state) then -      done=true -      local char=current.char -      if marks[char] then -        set_attribute(current,state,5)  -        if trace_analyzing then fcs(current,"font:mark") end -      elseif isol[char] then  -        first,last=finish(first,last) -        set_attribute(current,state,4)  -        if trace_analyzing then fcs(current,"font:isol") end -        first,last=nil,nil -      elseif not first then -        if isol_fina_medi_init[char] then -          set_attribute(current,state,1)  -          if trace_analyzing then fcs(current,"font:init") end -          first,last=first or current,current -        elseif isol_fina[char] then -          set_attribute(current,state,4)  -          if trace_analyzing then fcs(current,"font:isol") end -          first,last=nil,nil -        else  -          first,last=finish(first,last) -        end -      elseif isol_fina_medi_init[char] then -        first,last=first or current,current -        set_attribute(current,state,2)  -        if trace_analyzing then fcs(current,"font:medi") end -      elseif isol_fina[char] then -        if not has_attribute(last,state,1) then -          set_attribute(last,state,2)  -          if trace_analyzing then fcs(last,"font:medi") end -        end -        set_attribute(current,state,3)  -        if trace_analyzing then fcs(current,"font:fina") end -        first,last=nil,nil -      elseif char>=0x0600 and char<=0x06FF then -        if trace_analyzing then fcs(current,"font:rest") end -        first,last=finish(first,last) -      else  -        first,last=finish(first,last) -      end -    else -      first,last=finish(first,last) -    end -    current=current.next -  end -  first,last=finish(first,last) -  return head,done -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-otc']={ -  version=1.001, -  comment="companion to font-otf.lua (context)", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local format,insert=string.format,table.insert -local type,next=type,next -local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) -local otf=fonts.otf -local tfm=fonts.tfm -local extra_lists={ -  tlig={ -    { -      endash="hyphen hyphen", -      emdash="hyphen hyphen hyphen", -      quotedblleft="quoteleft quoteleft", -      quotedblright="quoteright quoteright", -      quotedblleft="grave grave", -      quotedblright="quotesingle quotesingle", -      quotedblbase="comma comma", -      exclamdown="exclam grave", -      questiondown="question grave", -      guillemotleft="less less", -      guillemotright="greater greater", -    }, -  }, -  trep={ -    { -      [0x0022]=0x201D, -      [0x0027]=0x2019, -      [0x0060]=0x2018, -    }, -  }, -  anum={ -    {  -      [0x0030]=0x0660, -      [0x0031]=0x0661, -      [0x0032]=0x0662, -      [0x0033]=0x0663, -      [0x0034]=0x0664, -      [0x0035]=0x0665, -      [0x0036]=0x0666, -      [0x0037]=0x0667, -      [0x0038]=0x0668, -      [0x0039]=0x0669, -    }, -    {  -      [0x0030]=0x06F0, -      [0x0031]=0x06F1, -      [0x0032]=0x06F2, -      [0x0033]=0x06F3, -      [0x0034]=0x06F4, -      [0x0035]=0x06F5, -      [0x0036]=0x06F6, -      [0x0037]=0x06F7, -      [0x0038]=0x06F8, -      [0x0039]=0x06F9, -    }, -  }, -} -local extra_features={  -  tlig={ -    { -      features={ { scripts={ { script="*",langs={ "*" },} },tag="tlig",comment="added bij mkiv" },}, -      name="ctx_tlig_1", -      subtables={ { name="ctx_tlig_1_s" } }, -      type="gsub_ligature", -      flags={}, -    }, -  }, -  trep={ -    { -      features={ { scripts={ { script="*",langs={ "*" },} },tag="trep",comment="added bij mkiv" },}, -      name="ctx_trep_1", -      subtables={ { name="ctx_trep_1_s" } }, -      type="gsub_single", -      flags={}, -    }, -  }, -  anum={ -    { -      features={ { scripts={ { script="arab",langs={ "dflt","ARA" },} },tag="anum",comment="added bij mkiv" },}, -      name="ctx_anum_1", -      subtables={ { name="ctx_anum_1_s" } }, -      type="gsub_single", -      flags={}, -    }, -    { -      features={ { scripts={ { script="arab",langs={ "FAR" },} },tag="anum",comment="added bij mkiv" },}, -      name="ctx_anum_2", -      subtables={ { name="ctx_anum_2_s" } }, -      type="gsub_single", -      flags={}, -    }, -  }, -} -fonts.otf.enhancers["add some missing characters"]=function(data,filename) -end -fonts.otf.enhancers["enrich with features"]=function(data,filename) -  local used={} -  for i=1,#otf.glists do -    local g=data[otf.glists[i]] -    if g then -      for i=1,#g do -        local f=g[i].features -        if f then -          for i=1,#f do -            local t=f[i].tag -            if t then used[t]=true end -          end -        end -      end -    end -  end -  local glyphs=data.glyphs -  local indices=data.map.map -  data.gsub=data.gsub or {} -  for kind,specifications in next,extra_features do -    if not used[kind] then -      local done=0 -      for s=1,#specifications do -        local added=false -        local specification=specifications[s] -        local list=extra_lists[kind][s] -        local name=specification.name.."_s" -        if specification.type=="gsub_ligature" then -          for unicode,index in next,indices do -            local glyph=glyphs[index] -            local ligature=list[glyph.name] -            if ligature then -              local o=glyph.lookups or {} -              o[name]={ -                { -                  ["type"]="ligature", -                  ["specification"]={ -                    char=glyph.name, -                    components=ligature, -                  } -                } -              } -              glyph.lookups,done,added=o,done+1,true -            end -          end -        elseif specification.type=="gsub_single" then -          for unicode,index in next,indices do -            local glyph=glyphs[index] -            local r=list[unicode] -            if r then -              local replacement=indices[r] -              if replacement and glyphs[replacement] then -                local o=glyph.lookups or {} -                o[name]={ -                  { -                    ["type"]="substitution", -                    ["specification"]={ -                      variant=glyphs[replacement].name, -                    } -                  } -                } -                glyph.lookups,done,added=o,done+1,true -              end -            end -          end -        end -        if added then -          insert(data.gsub,s,table.fastcopy(specification))  -        end -      end -      if done>0 then -        if trace_loading then -          logs.report("load otf","enhance: registering %s feature (%s glyphs affected)",kind,done) -        end -      end -    end -  end -end -otf.tables.features['tlig']='TeX Ligatures' -otf.tables.features['trep']='TeX Replacements' -otf.tables.features['anum']='Arabic Digits' -otf.features.register_base_substitution('tlig') -otf.features.register_base_substitution('trep') -otf.features.register_base_substitution('anum') -fonts.initializers.base.otf.equaldigits=fonts.initializers.common.equaldigits -fonts.initializers.node.otf.equaldigits=fonts.initializers.common.equaldigits -fonts.initializers.base.otf.lineheight=fonts.initializers.common.lineheight -fonts.initializers.node.otf.lineheight=fonts.initializers.common.lineheight -fonts.initializers.base.otf.compose=fonts.initializers.common.compose -fonts.initializers.node.otf.compose=fonts.initializers.common.compose - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-def']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local format,concat,gmatch,match,find,lower=string.format,table.concat,string.gmatch,string.match,string.find,string.lower -local tostring,next=tostring,next -local lpegmatch=lpeg.match -local trace_defining=false trackers .register("fonts.defining",function(v) trace_defining=v end) -local directive_embedall=false directives.register("fonts.embedall",function(v) directive_embedall=v end) -trackers.register("fonts.loading","fonts.defining","otf.loading","afm.loading","tfm.loading") -trackers.register("fonts.all","fonts.*","otf.*","afm.*","tfm.*") -fonts=fonts    or {} -fonts.define=fonts.define or {} -fonts.tfm=fonts.tfm  or {} -fonts.ids=fonts.ids  or {} -fonts.vf=fonts.vf   or {} -fonts.used=fonts.used  or {} -local tfm=fonts.tfm -local vf=fonts.vf -local define=fonts.define -tfm.version=1.01 -tfm.cache=containers.define("fonts","tfm",tfm.version,false)  -define.method="afm or tfm"  -define.specify=fonts.define.specify or {} -define.methods=fonts.define.methods or {} -tfm.fonts=tfm.fonts    or {} -tfm.readers=tfm.readers   or {} -tfm.internalized=tfm.internalized or {}  -tfm.readers.sequence={ 'otf','ttf','afm','tfm' } -tfm.auto_afm=true -local readers=tfm.readers -local sequence=readers.sequence -fonts.version=1.05 -fonts.cache=containers.define("fonts","def",fonts.version,false) -local splitter,specifiers=nil,"" -local P,C,S,Cc=lpeg.P,lpeg.C,lpeg.S,lpeg.Cc -local left=P("(") -local right=P(")") -local colon=P(":") -local space=P(" ") -define.defaultlookup="file" -local prefixpattern=P(false) -function define.add_specifier(symbol) -  specifiers=specifiers..symbol -  local method=S(specifiers) -  local lookup=C(prefixpattern)*colon -  local sub=left*C(P(1-left-right-method)^1)*right -  local specification=C(method)*C(P(1)^1) -  local name=C((1-sub-specification)^1) -  splitter=P((lookup+Cc(""))*name*(sub+Cc(""))*(specification+Cc(""))) -end -function define.add_lookup(str,default) -  prefixpattern=prefixpattern+P(str) -end -define.add_lookup("file") -define.add_lookup("name") -define.add_lookup("spec") -function define.get_specification(str) -  return lpegmatch(splitter,str) -end -function define.register_split(symbol,action) -  define.add_specifier(symbol) -  define.specify[symbol]=action -end -function define.makespecification(specification,lookup,name,sub,method,detail,size) -  size=size or 655360 -  if trace_defining then -    logs.report("define font","%s -> lookup: %s, name: %s, sub: %s, method: %s, detail: %s", -      specification,(lookup~="" and lookup) or "[file]",(name~="" and name) or "-", -      (sub~="" and sub) or "-",(method~="" and method) or "-",(detail~="" and detail) or "-") -  end -  if not lookup or lookup=="" then -    lookup=define.defaultlookup -  end -  local t={ -    lookup=lookup, -    specification=specification, -    size=size, -    name=name, -    sub=sub, -    method=method, -    detail=detail, -    resolved="", -    forced="", -    features={}, -  } -  return t -end -function define.analyze(specification,size) -  local lookup,name,sub,method,detail=define.get_specification(specification or "") -  return define.makespecification(specification,lookup,name,sub,method,detail,size) -end -local sortedhashkeys=table.sortedhashkeys -function tfm.hash_features(specification) -  local features=specification.features -  if features then -    local t={} -    local normal=features.normal -    if normal and next(normal) then -      local f=sortedhashkeys(normal) -      for i=1,#f do -        local v=f[i] -        if v~="number" and v~="features" then  -          t[#t+1]=v..'='..tostring(normal[v]) -        end -      end -    end -    local vtf=features.vtf -    if vtf and next(vtf) then -      local f=sortedhashkeys(vtf) -      for i=1,#f do -        local v=f[i] -        t[#t+1]=v..'='..tostring(vtf[v]) -      end -    end -    if #t>0 then -      return concat(t,"+") -    end -  end -  return "unknown" -end -fonts.designsizes={} -function tfm.hash_instance(specification,force) -  local hash,size,fallbacks=specification.hash,specification.size,specification.fallbacks -  if force or not hash then -    hash=tfm.hash_features(specification) -    specification.hash=hash -  end -  if size<1000 and fonts.designsizes[hash] then -    size=math.round(tfm.scaled(size,fonts.designsizes[hash])) -    specification.size=size -  end -    if fallbacks then -      return hash..' @ '..tostring(size)..' @ '..fallbacks -    else -      return hash..' @ '..tostring(size) -    end -end -define.resolvers=resolvers -function define.resolvers.file(specification) -  local suffix=file.suffix(specification.name) -  if fonts.formats[suffix] then -    specification.forced=suffix -    specification.name=file.removesuffix(specification.name) -  end -end -function define.resolvers.name(specification) -  local resolve=fonts.names.resolve -  if resolve then -    local resolved,sub=fonts.names.resolve(specification) -    specification.resolved,specification.sub=resolved,sub -    if resolved then -      local suffix=file.suffix(resolved) -      if fonts.formats[suffix] then -        specification.forced=suffix -        specification.name=file.removesuffix(resolved) -      else -        specification.name=resolved -      end -    end -  else -    define.resolvers.file(specification) -  end -end -function define.resolvers.spec(specification) -  local resolvespec=fonts.names.resolvespec -  if resolvespec then -    specification.resolved,specification.sub=fonts.names.resolvespec(specification) -    if specification.resolved then -      specification.forced=file.extname(specification.resolved) -      specification.name=file.removesuffix(specification.resolved) -    end -  else -    define.resolvers.name(specification) -  end -end -function define.resolve(specification) -  if not specification.resolved or specification.resolved=="" then  -    local r=define.resolvers[specification.lookup] -    if r then -      r(specification) -    end -  end -  if specification.forced=="" then -    specification.forced=nil -  else -    specification.forced=specification.forced -  end -  specification.hash=lower(specification.name..' @ '..tfm.hash_features(specification)) -  if specification.sub and specification.sub~="" then -    specification.hash=specification.sub..' @ '..specification.hash -  end -  return specification -end -function tfm.read(specification) -  local hash=tfm.hash_instance(specification) -  local tfmtable=tfm.fonts[hash]  -  if not tfmtable then -    local forced=specification.forced or "" -    if forced~="" then -      tfmtable=readers[lower(forced)](specification) -      if not tfmtable then -        logs.report("define font","forced type %s of %s not found",forced,specification.name) -      end -    else -      for s=1,#sequence do  -        local reader=sequence[s] -        if readers[reader] then  -          if trace_defining then -            logs.report("define font","trying (reader sequence driven) type %s for %s with file %s",reader,specification.name,specification.filename or "unknown") -          end -          tfmtable=readers[reader](specification) -          if tfmtable then -            break -          else -            specification.filename=nil -          end -        end -      end -    end -    if tfmtable then -      if directive_embedall then -        tfmtable.embedding="full" -      elseif tfmtable.filename and fonts.dontembed[tfmtable.filename] then -        tfmtable.embedding="no" -      else -        tfmtable.embedding="subset" -      end -      tfm.fonts[hash]=tfmtable -      fonts.designsizes[specification.hash]=tfmtable.designsize -    end -  end -  if not tfmtable then -    logs.report("define font","font with name %s is not found",specification.name) -  end -  return tfmtable -end -function tfm.read_and_define(name,size)  -  local specification=define.analyze(name,size) -  local method=specification.method -  if method and define.specify[method] then -    specification=define.specify[method](specification) -  end -  specification=define.resolve(specification) -  local hash=tfm.hash_instance(specification) -  local id=define.registered(hash) -  if not id then -    local fontdata=tfm.read(specification) -    if fontdata then -      fontdata.hash=hash -      id=font.define(fontdata) -      define.register(fontdata,id) -      tfm.cleanup_table(fontdata) -    else -      id=0  -    end -  end -  return fonts.ids[id],id -end -local function check_tfm(specification,fullname) -  local foundname=resolvers.findbinfile(fullname,'tfm') or ""  -  if foundname=="" then -    foundname=resolvers.findbinfile(fullname,'ofm') or ""  -  end -  if foundname~="" then -    specification.filename,specification.format=foundname,"ofm" -    return tfm.read_from_tfm(specification) -  end -end -local function check_afm(specification,fullname) -  local foundname=resolvers.findbinfile(fullname,'afm') or ""  -  if foundname=="" and tfm.auto_afm then -    local encoding,shortname=match(fullname,"^(.-)%-(.*)$")  -    if encoding and shortname and fonts.enc.known[encoding] then -      shortname=resolvers.findbinfile(shortname,'afm') or ""  -      if shortname~="" then -        foundname=shortname -        if trace_loading then -          logs.report("load afm","stripping encoding prefix from filename %s",afmname) -        end -      end -    end -  end -  if foundname~="" then -    specification.filename,specification.format=foundname,"afm" -    return tfm.read_from_afm(specification) -  end -end -function readers.tfm(specification) -  local fullname,tfmtable=specification.filename or "",nil -  if fullname=="" then -    local forced=specification.forced or "" -    if forced~="" then -      tfmtable=check_tfm(specification,specification.name.."."..forced) -    end -    if not tfmtable then -      tfmtable=check_tfm(specification,specification.name) -    end -  else -    tfmtable=check_tfm(specification,fullname) -  end -  return tfmtable -end -function readers.afm(specification,method) -  local fullname,tfmtable=specification.filename or "",nil -  if fullname=="" then -    local forced=specification.forced or "" -    if forced~="" then -      tfmtable=check_afm(specification,specification.name.."."..forced) -    end -    if not tfmtable then -      method=method or define.method or "afm or tfm" -      if method=="tfm" then -        tfmtable=check_tfm(specification,specification.name) -      elseif method=="afm" then -        tfmtable=check_afm(specification,specification.name) -      elseif method=="tfm or afm" then -        tfmtable=check_tfm(specification,specification.name) or check_afm(specification,specification.name) -      else  -        tfmtable=check_afm(specification,specification.name) or check_tfm(specification,specification.name) -      end -    end -  else -    tfmtable=check_afm(specification,fullname) -  end -  return tfmtable -end -local function check_otf(forced,specification,suffix,what) -  local name=specification.name -  if forced then -    name=file.addsuffix(name,suffix,true) -  end -  local fullname,tfmtable=resolvers.findbinfile(name,suffix) or "",nil  -  if fullname=="" then -    local fb=fonts.names.old_to_new[name] -    if fb then -      fullname=resolvers.findbinfile(fb,suffix) or "" -    end -  end -  if fullname=="" then -    local fb=fonts.names.new_to_old[name] -    if fb then -      fullname=resolvers.findbinfile(fb,suffix) or "" -    end -  end -  if fullname~="" then -    specification.filename,specification.format=fullname,what  -    tfmtable=tfm.read_from_open_type(specification)        -  end -  return tfmtable -end -function readers.opentype(specification,suffix,what) -  local forced=specification.forced or "" -  if forced=="otf" then -    return check_otf(true,specification,forced,"opentype") -  elseif forced=="ttf" or forced=="ttc" or forced=="dfont" then -    return check_otf(true,specification,forced,"truetype") -  else -    return check_otf(false,specification,suffix,what) -  end -end -function readers.otf (specification) return readers.opentype(specification,"otf","opentype") end -function readers.ttf (specification) return readers.opentype(specification,"ttf","truetype") end -function readers.ttc (specification) return readers.opentype(specification,"ttf","truetype") end  -function readers.dfont(specification) return readers.opentype(specification,"ttf","truetype") end -function define.check(features,defaults)  -  local done=false -  if features and next(features) then -    for k,v in next,defaults do -      if features[k]==nil then -        features[k],done=v,true -      end -    end -  else -    features,done=table.fastcopy(defaults),true -  end -  return features,done  -end -define.last=nil -function define.register(fontdata,id) -  if fontdata and id then -    local hash=fontdata.hash -    if not tfm.internalized[hash] then -      if trace_defining then -        logs.report("define font","loading at 2 id %s, hash: %s",id or "?",hash or "?") -      end -      fonts.identifiers[id]=fontdata -      fonts.characters [id]=fontdata.characters -      fonts.quads   [id]=fontdata.parameters.quad -      tfm.internalized[hash]=id -    end -  end -end -function define.registered(hash) -  local id=tfm.internalized[hash] -  return id,id and fonts.ids[id] -end -local cache_them=false -function tfm.make(specification) -  local fvm=define.methods[specification.features.vtf.preset] -  if fvm then -    return fvm(specification) -  else -    return nil -  end -end -function define.read(specification,size,id)  -  statistics.starttiming(fonts) -  if type(specification)=="string" then -    specification=define.analyze(specification,size) -  end -  local method=specification.method -  if method and define.specify[method] then -    specification=define.specify[method](specification) -  end -  specification=define.resolve(specification) -  local hash=tfm.hash_instance(specification) -  if cache_them then -    local fontdata=containers.read(fonts.cache,hash)  -  end -  local fontdata=define.registered(hash)  -  if not fontdata then -    if specification.features.vtf and specification.features.vtf.preset then -      fontdata=tfm.make(specification) -    else -      fontdata=tfm.read(specification) -      if fontdata then -        tfm.check_virtual_id(fontdata) -      end -    end -    if cache_them then -      fontdata=containers.write(fonts.cache,hash,fontdata)  -    end -    if fontdata then -      fontdata.hash=hash -      fontdata.cache="no" -      if id then -        define.register(fontdata,id) -      end -    end -  end -  define.last=fontdata or id  -  if not fontdata then -    logs.report("define font","unknown font %s, loading aborted",specification.name) -  elseif trace_defining and type(fontdata)=="table" then -    logs.report("define font","using %s font with id %s, name:%s size:%s bytes:%s encoding:%s fullname:%s filename:%s", -      fontdata.type     or "unknown", -      id           or "?", -      fontdata.name     or "?", -      fontdata.size     or "default", -      fontdata.encodingbytes or "?", -      fontdata.encodingname or "unicode", -      fontdata.fullname   or "?", -      file.basename(fontdata.filename or "?")) -  end -  statistics.stoptiming(fonts) -  return fontdata -end -function vf.find(name) -  name=file.removesuffix(file.basename(name)) -  if tfm.resolve_vf then -    local format=fonts.logger.format(name) -    if format=='tfm' or format=='ofm' then -      if trace_defining then -        logs.report("define font","locating vf for %s",name) -      end -      return resolvers.findbinfile(name,"ovf") -    else -      if trace_defining then -        logs.report("define font","vf for %s is already taken care of",name) -      end -      return nil  -    end -  else -    if trace_defining then -      logs.report("define font","locating vf for %s",name) -    end -    return resolvers.findbinfile(name,"ovf") -  end -end -callbacks.register('define_font',define.read,"definition of fonts (tfmtable preparation)") -callbacks.register('find_vf_file',vf.find,"locating virtual fonts, insofar needed")  - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-xtx']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local texsprint,count=tex.sprint,tex.count -local format,concat,gmatch,match,find,lower=string.format,table.concat,string.gmatch,string.match,string.find,string.lower -local tostring,next=tostring,next -local lpegmatch=lpeg.match -local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) -local list={} -fonts.define.specify.colonized_default_lookup="file" -local function isstyle(s) -  local style=string.lower(s):split("/") -  for _,v in ipairs(style) do -    if v=="b" then -      list.style="bold" -    elseif v=="i" then -      list.style="italic" -    elseif v=="bi" or v=="ib" then -      list.style="bolditalic" -    elseif v:find("^s=") then -      list.optsize=v:split("=")[2] -    elseif v=="aat" or v=="icu" or v=="gr" then -      logs.report("load font","unsupported font option: %s",v) -    elseif not v:is_empty() then -      list.style=v:gsub("[^%a%d]","") -    end -  end -end -fonts=fonts   or {} -fonts.otf=fonts.otf or {} -local otf=fonts.otf -otf.tables=otf.tables or {} -otf.tables.defaults={ -  dflt={ -    "ccmp","locl","rlig","liga","clig", -    "kern","mark","mkmk","itlc", -  }, -  arab={ -    "ccmp","locl","isol","fina","fin2", -    "fin3","medi","med2","init","rlig", -    "calt","liga","cswh","mset","curs", -    "kern","mark","mkmk", -  }, -  deva={ -    "ccmp","locl","init","nukt","akhn", -    "rphf","blwf","half","pstf","vatu", -    "pres","blws","abvs","psts","haln", -    "calt","blwm","abvm","dist","kern", -    "mark","mkmk", -  }, -  khmr={ -    "ccmp","locl","pref","blwf","abvf", -    "pstf","pres","blws","abvs","psts", -    "clig","calt","blwm","abvm","dist", -    "kern","mark","mkmk", -  }, -  thai={ -    "ccmp","locl","liga","kern","mark", -    "mkmk", -  }, -  hang={ -    "ccmp","ljmo","vjmo","tjmo", -  }, -} -otf.tables.defaults.beng=otf.tables.defaults.deva -otf.tables.defaults.guru=otf.tables.defaults.deva -otf.tables.defaults.gujr=otf.tables.defaults.deva -otf.tables.defaults.orya=otf.tables.defaults.deva -otf.tables.defaults.taml=otf.tables.defaults.deva -otf.tables.defaults.telu=otf.tables.defaults.deva -otf.tables.defaults.knda=otf.tables.defaults.deva -otf.tables.defaults.mlym=otf.tables.defaults.deva -otf.tables.defaults.sinh=otf.tables.defaults.deva -otf.tables.defaults.syrc=otf.tables.defaults.arab -otf.tables.defaults.mong=otf.tables.defaults.arab -otf.tables.defaults.nko=otf.tables.defaults.arab -otf.tables.defaults.tibt=otf.tables.defaults.khmr -otf.tables.defaults.lao=otf.tables.defaults.thai -local function parse_script(script) -  if otf.tables.scripts[script] then -    local dflt -    if otf.tables.defaults[script] then -      logs.report("load font","auto-selecting default features for script: %s",script) -      dflt=otf.tables.defaults[script] -    else -      logs.report("load font","auto-selecting default features for script: dflt (was %s)",script) -      dflt=otf.tables.defaults["dflt"] -    end -    for _,v in next,dflt do -      list[v]="yes" -    end -  else -    logs.report("load font","unknown script: %s",script) -  end -end -local function issome ()  list.lookup=fonts.define.specify.colonized_default_lookup end -local function isfile ()  list.lookup='file' end -local function isname ()  list.lookup='name' end -local function thename(s)  list.name=s end -local function issub (v)  list.sub=v end -local function iskey (k,v) -  if k=="script" then -    parse_script(v) -  end -  list[k]=v -end -local function istrue (s)  list[s]=true end -local function isfalse(s)  list[s]=false end -local spaces=lpeg.P(" ")^0 -local namespec=(1-lpeg.S("/:("))^0  -local filespec=(lpeg.R("az","AZ")*lpeg.P(":"))^-1*(1-lpeg.S(":("))^1 -local crapspec=spaces*lpeg.P("/")*(((1-lpeg.P(":"))^0)/isstyle)*spaces -local filename=(lpeg.P("file:")/isfile*(filespec/thename))+(lpeg.P("[")*lpeg.P(true)/isfile*(((1-lpeg.P("]"))^0)/thename)*lpeg.P("]")) -local fontname=(lpeg.P("name:")/isname*(namespec/thename))+lpeg.P(true)/issome*(namespec/thename) -local sometext=(lpeg.R("az","AZ","09")+lpeg.S("+-."))^1 -local truevalue=lpeg.P("+")*spaces*(sometext/istrue) -local falsevalue=lpeg.P("-")*spaces*(sometext/isfalse) -local keyvalue=lpeg.P("+")+(lpeg.C(sometext)*spaces*lpeg.P("=")*spaces*lpeg.C(sometext))/iskey -local somevalue=sometext/istrue -local subvalue=lpeg.P("(")*(lpeg.C(lpeg.P(1-lpeg.S("()"))^1)/issub)*lpeg.P(")")  -local option=spaces*(keyvalue+falsevalue+truevalue+somevalue)*spaces -local options=lpeg.P(":")*spaces*(lpeg.P(";")^0*option)^0 -local pattern=(filename+fontname)*subvalue^0*crapspec^0*options^0 -local normalize_meanings=fonts.otf.meanings.normalize -function fonts.define.specify.colonized(specification)  -  list={} -  lpegmatch(pattern,specification.specification) -  if list.style then -    specification.style=list.style -    list.style=nil -  end -  if list.optsize then -    specification.optsize=list.optsize -    list.optsize=nil -  end -  if list.name then -    if resolvers.find_file(list.name,"tfm") then -      list.lookup="file" -      list.name=file.addsuffix(list.name,"tfm") -    elseif resolvers.find_file(list.name,"ofm") then -      list.lookup="file" -      list.name=file.addsuffix(list.name,"ofm") -    end -    specification.name=list.name -    list.name=nil -  end -  if list.lookup then -    specification.lookup=list.lookup -    list.lookup=nil -  end -  if list.sub then -    specification.sub=list.sub -    list.sub=nil -  end -  specification.features.normal=normalize_meanings(list) -  return specification -end -fonts.define.register_split(":",fonts.define.specify.colonized) - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-dum']={ -  version=1.001, -  comment="companion to luatex-*.tex", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -fonts=fonts or {} -fonts.otf.pack=false -fonts.tfm.resolve_vf=false  -fonts.tfm.fontname_mode="specification" -fonts.tfm.readers=fonts.tfm.readers or {} -fonts.tfm.readers.sequence={ 'otf','ttf','tfm' } -fonts.tfm.readers.afm=nil -fonts.define=fonts.define or {} -fonts.define.specify.colonized_default_lookup="name" -function fonts.define.get_specification(str) -  return "",str,"",":",str -end -fonts.logger=fonts.logger or {} -function fonts.logger.save() -end -fonts.names=fonts.names or {} -fonts.names.version=1.001  -fonts.names.basename="luatex-fonts-names.lua" -fonts.names.new_to_old={} -fonts.names.old_to_new={} -local data,loaded=nil,false -local fileformats={ "lua","tex","other text files" } -function fonts.names.resolve(name,sub) -  if not loaded then -    local basename=fonts.names.basename -    if basename and basename~="" then -      for i=1,#fileformats do -        local format=fileformats[i] -        local foundname=resolvers.find_file(basename,format) or "" -        if foundname~="" then -          data=dofile(foundname) -          break -        end -      end -    end -    loaded=true -  end -  if type(data)=="table" and data.version==fonts.names.version then -    local condensed=string.gsub(string.lower(name),"[^%a%d]","") -    local found=data.mappings and data.mappings[condensed] -    if found then -      local fontname,filename,subfont=found[1],found[2],found[3] -      if subfont then -        return filename,fontname -      else -        return filename,false -      end -    else -      return name,false  -    end -  end -end -fonts.names.resolvespec=fonts.names.resolve -table.insert(fonts.triggers,"itlc") -local function itlc(tfmdata,value) -  if value then -    local metadata=tfmdata.shared.otfdata.metadata -    if metadata then -      local italicangle=metadata.italicangle -      if italicangle and italicangle~=0 then -        local uwidth=(metadata.uwidth or 40)/2 -        for unicode,d in next,tfmdata.descriptions do -          local it=d.boundingbox[3]-d.width+uwidth -          if it~=0 then -            d.italic=it -          end -        end -        tfmdata.has_italic=true -      end -    end -  end -end -fonts.initializers.base.otf.itlc=itlc -fonts.initializers.node.otf.itlc=itlc -function fonts.initializers.common.slant(tfmdata,value) -  value=tonumber(value) -  if not value then -    value=0 -  elseif value>1 then -    value=1 -  elseif value<-1 then -    value=-1 -  end -  tfmdata.slant_factor=value -end -function fonts.initializers.common.extend(tfmdata,value) -  value=tonumber(value) -  if not value then -    value=0 -  elseif value>10 then -    value=10 -  elseif value<-10 then -    value=-10 -  end -  tfmdata.extend_factor=value -end -table.insert(fonts.triggers,"slant") -table.insert(fonts.triggers,"extend") -fonts.initializers.base.otf.slant=fonts.initializers.common.slant -fonts.initializers.node.otf.slant=fonts.initializers.common.slant -fonts.initializers.base.otf.extend=fonts.initializers.common.extend -fonts.initializers.node.otf.extend=fonts.initializers.common.extend -fonts.protrusions=fonts.protrusions    or {} -fonts.protrusions.setups=fonts.protrusions.setups or {} -local setups=fonts.protrusions.setups -local function map_opbd_onto_protrusion(tfmdata,value,opbd) -  local characters,descriptions=tfmdata.characters,tfmdata.descriptions -  local otfdata=tfmdata.shared.otfdata -  local singles=otfdata.shared.featuredata.gpos_single -  local script,language=tfmdata.script,tfmdata.language -  local done,factor,left,right=false,1,1,1 -  local setup=setups[value] -  if setup then -    factor=setup.factor or 1 -    left=setup.left  or 1 -    right=setup.right or 1 -  else -    factor=tonumber(value) or 1 -  end -  if opbd~="right" then -    local validlookups,lookuplist=fonts.otf.collect_lookups(otfdata,"lfbd",script,language) -    if validlookups then -      for i=1,#lookuplist do -        local lookup=lookuplist[i] -        local data=singles[lookup] -        if data then -          if trace_protrusion then -            logs.report("fonts","set left protrusion using lfbd lookup '%s'",lookup) -          end -          for k,v in next,data do -            local p=- (v[1]/1000)*factor*left -            characters[k].left_protruding=p -            if trace_protrusion then -              logs.report("opbd","lfbd -> %s -> 0x%05X (%s) -> %0.03f (%s)",lookup,k,utfchar(k),p,concat(v," ")) -            end -          end -          done=true -        end -      end -    end -  end -  if opbd~="left" then -    local validlookups,lookuplist=fonts.otf.collect_lookups(otfdata,"rtbd",script,language) -    if validlookups then -      for i=1,#lookuplist do -        local lookup=lookuplist[i] -        local data=singles[lookup] -        if data then -          if trace_protrusion then -            logs.report("fonts","set right protrusion using rtbd lookup '%s'",lookup) -          end -          for k,v in next,data do -            local p=(v[1]/1000)*factor*right -            characters[k].right_protruding=p -            if trace_protrusion then -              logs.report("opbd","rtbd -> %s -> 0x%05X (%s) -> %0.03f (%s)",lookup,k,utfchar(k),p,concat(v," ")) -            end -          end -        end -        done=true -      end -    end -  end -  tfmdata.auto_protrude=done -end -function fonts.initializers.common.protrusion(tfmdata,value) -  if value then -    local opbd=tfmdata.shared.features.opbd -    if opbd then -      map_opbd_onto_protrusion(tfmdata,value,opbd) -    elseif value then -      local setup=setups[value] -      if setup then -        local factor,left,right=setup.factor or 1,setup.left or 1,setup.right or 1 -        local emwidth=tfmdata.parameters.quad -        tfmdata.auto_protrude=true -        for i,chr in next,tfmdata.characters do -          local v,pl,pr=setup[i],nil,nil -          if v then -            pl,pr=v[1],v[2] -          end -          if pl and pl~=0 then chr.left_protruding=left*pl*factor end -          if pr and pr~=0 then chr.right_protruding=right*pr*factor end -        end -      end -    end -  end -end -fonts.expansions=fonts.expansions    or {} -fonts.expansions.setups=fonts.expansions.setups or {} -local setups=fonts.expansions.setups -function fonts.initializers.common.expansion(tfmdata,value) -  if value then -    local setup=setups[value] -    if setup then -      local stretch,shrink,step,factor=setup.stretch or 0,setup.shrink or 0,setup.step or 0,setup.factor or 1 -      tfmdata.stretch,tfmdata.shrink,tfmdata.step,tfmdata.auto_expand=stretch*10,shrink*10,step*10,true -      for i,chr in next,tfmdata.characters do -        local v=setup[i] -        if v and v~=0 then -          chr.expansion_factor=v*factor -        else  -          chr.expansion_factor=factor -        end -      end -    end -  end -end -table.insert(fonts.manipulators,"protrusion") -table.insert(fonts.manipulators,"expansion") -fonts.initializers.base.otf.protrusion=fonts.initializers.common.protrusion -fonts.initializers.node.otf.protrusion=fonts.initializers.common.protrusion -fonts.initializers.base.otf.expansion=fonts.initializers.common.expansion -fonts.initializers.node.otf.expansion=fonts.initializers.common.expansion -function fonts.register_message() -end -local byte=string.byte -fonts.expansions.setups['default']={ -  stretch=2,shrink=2,step=.5,factor=1, -  [byte('A')]=0.5,[byte('B')]=0.7,[byte('C')]=0.7,[byte('D')]=0.5,[byte('E')]=0.7, -  [byte('F')]=0.7,[byte('G')]=0.5,[byte('H')]=0.7,[byte('K')]=0.7,[byte('M')]=0.7, -  [byte('N')]=0.7,[byte('O')]=0.5,[byte('P')]=0.7,[byte('Q')]=0.5,[byte('R')]=0.7, -  [byte('S')]=0.7,[byte('U')]=0.7,[byte('W')]=0.7,[byte('Z')]=0.7, -  [byte('a')]=0.7,[byte('b')]=0.7,[byte('c')]=0.7,[byte('d')]=0.7,[byte('e')]=0.7, -  [byte('g')]=0.7,[byte('h')]=0.7,[byte('k')]=0.7,[byte('m')]=0.7,[byte('n')]=0.7, -  [byte('o')]=0.7,[byte('p')]=0.7,[byte('q')]=0.7,[byte('s')]=0.7,[byte('u')]=0.7, -  [byte('w')]=0.7,[byte('z')]=0.7, -  [byte('2')]=0.7,[byte('3')]=0.7,[byte('6')]=0.7,[byte('8')]=0.7,[byte('9')]=0.7, -} -fonts.protrusions.setups['default']={ -  factor=1,left=1,right=1, -  [0x002C]={ 0,1  }, -  [0x002E]={ 0,1  }, -  [0x003A]={ 0,1  }, -  [0x003B]={ 0,1  }, -  [0x002D]={ 0,1  }, -  [0x2013]={ 0,0.50 }, -  [0x2014]={ 0,0.33 }, -  [0x3001]={ 0,1  }, -  [0x3002]={ 0,1  }, -  [0x060C]={ 0,1  }, -  [0x061B]={ 0,1  }, -  [0x06D4]={ 0,1  }, -} -fonts.otf.meanings=fonts.otf.meanings or {} -fonts.otf.meanings.normalize=fonts.otf.meanings.normalize or function(t) -  if t.rand then -    t.rand="random" -  end -end -function fonts.otf.name_to_slot(name) -  local tfmdata=fonts.ids[font.current()] -  if tfmdata and tfmdata.shared then -    local otfdata=tfmdata.shared.otfdata -    local unicode=otfdata.luatex.unicodes[name] -    return unicode and (type(unicode)=="number" and unicode or unicode[1]) -  end -end -function fonts.otf.char(n) -  if type(n)=="string" then -    n=fonts.otf.name_to_slot(n) -  end -  if type(n)=="number" then -    tex.sprint("\\char"..n) -  end -end -fonts.strippables=table.tohash { -  0x000AD,0x017B4,0x017B5,0x0200B,0x0200C,0x0200D,0x0200E,0x0200F,0x0202A,0x0202B, -  0x0202C,0x0202D,0x0202E,0x02060,0x02061,0x02062,0x02063,0x0206A,0x0206B,0x0206C, -  0x0206D,0x0206E,0x0206F,0x0FEFF,0x1D173,0x1D174,0x1D175,0x1D176,0x1D177,0x1D178, -  0x1D179,0x1D17A,0xE0001,0xE0020,0xE0021,0xE0022,0xE0023,0xE0024,0xE0025,0xE0026, -  0xE0027,0xE0028,0xE0029,0xE002A,0xE002B,0xE002C,0xE002D,0xE002E,0xE002F,0xE0030, -  0xE0031,0xE0032,0xE0033,0xE0034,0xE0035,0xE0036,0xE0037,0xE0038,0xE0039,0xE003A, -  0xE003B,0xE003C,0xE003D,0xE003E,0xE003F,0xE0040,0xE0041,0xE0042,0xE0043,0xE0044, -  0xE0045,0xE0046,0xE0047,0xE0048,0xE0049,0xE004A,0xE004B,0xE004C,0xE004D,0xE004E, -  0xE004F,0xE0050,0xE0051,0xE0052,0xE0053,0xE0054,0xE0055,0xE0056,0xE0057,0xE0058, -  0xE0059,0xE005A,0xE005B,0xE005C,0xE005D,0xE005E,0xE005F,0xE0060,0xE0061,0xE0062, -  0xE0063,0xE0064,0xE0065,0xE0066,0xE0067,0xE0068,0xE0069,0xE006A,0xE006B,0xE006C, -  0xE006D,0xE006E,0xE006F,0xE0070,0xE0071,0xE0072,0xE0073,0xE0074,0xE0075,0xE0076, -  0xE0077,0xE0078,0xE0079,0xE007A,0xE007B,0xE007C,0xE007D,0xE007E,0xE007F, -} - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-clr']={ -  version=1.001, -  comment="companion to font-otf.lua (font color)", -  author="Khaled Hosny and Elie Roux", -  copyright="Luaotfload Development Team", -  license="GPL" -} -fonts.triggers=fonts.triggers      or {} -fonts.initializers=fonts.initializers    or {} -fonts.initializers.common=fonts.initializers.common or {} -local initializers,format=fonts.initializers,string.format -table.insert(fonts.triggers,"color") -function initializers.common.color(tfmdata,value) -  local sanitized -  if value then -    value=tostring(value) -    if #value==6 or #value==8 then -      sanitized=value -    elseif #value==7 then -      _,_,sanitized=value:find("(......)") -    elseif #value>8 then -      _,_,sanitized=value:find("(........)") -    else -    end -  end -  if sanitized then -    tfmdata.color=sanitized -    add_color_callback() -  end -end -initializers.base.otf.color=initializers.common.color -initializers.node.otf.color=initializers.common.color -local function hex2dec(hex,one) -  if one then -    return format("%.1g",tonumber(hex,16)/255) -  else -    return format("%.3g",tonumber(hex,16)/255) -  end -end -local res -local function pageresources(a) -  local res2 -  if not res then -    res="/TransGs1<</ca 1/CA 1>>" -  end -  res2=format("/TransGs%s<</ca %s/CA %s>>",a,a,a) -  res=format("%s%s",res,res:find(res2) and "" or res2) -end -local function hex_to_rgba(hex) -  local r,g,b,a,push,pop,res3 -  if hex then -    if #hex==6 then -      _,_,r,g,b=hex:find('(..)(..)(..)') -    elseif #hex==8 then -      _,_,r,g,b,a=hex:find('(..)(..)(..)(..)') -      a=hex2dec(a,true) -      pageresources(a) -    end -  else -    return nil -  end -  r=hex2dec(r) -  g=hex2dec(g) -  b=hex2dec(b) -  if a then -    push=format('/TransGs%g gs %s %s %s rg',a,r,g,b) -    pop='0 g /TransGs1 gs' -  else -    push=format('%s %s %s rg',r,g,b) -    pop='0 g' -  end -  return push,pop -end -local glyph=node.id('glyph') -local hlist=node.id('hlist') -local vlist=node.id('vlist') -local whatsit=node.id('whatsit') -local pgi=node.id('page_insert') -local sbox=node.id('sub_box') -local function lookup_next_color(head) -  for n in node.traverse(head) do -    if n.id==glyph then -      if fonts.ids[n.font] and fonts.ids[n.font].color then -        return fonts.ids[n.font].color -      else -        return -1 -      end -    elseif n.id==vlist or n.id==hlist or n.id==sbox then -      local r=lookup_next_color(n.list) -      if r==-1 then -        return -1 -      elseif r then -        return r -      end -    elseif n.id==whatsit or n.id==pgi then -      return -1 -    end -  end -  return nil -end -local function node_colorize(head,current_color,next_color) -  for n in node.traverse(head) do -    if n.id==hlist or n.id==vlist or n.id==sbox then -      local next_color_in=lookup_next_color(n.next) or next_color -      n.list,current_color=node_colorize(n.list,current_color,next_color_in) -    elseif n.id==glyph then -      local tfmdata=fonts.ids[n.font] -      if tfmdata and tfmdata.color then -        if tfmdata.color~=current_color then -          local pushcolor=hex_to_rgba(tfmdata.color) -          local push=node.new(whatsit,8) -          push.mode=1 -          push.data=pushcolor -          head=node.insert_before(head,n,push) -          current_color=tfmdata.color -        end -        local next_color_in=lookup_next_color (n.next) or next_color -        if next_color_in~=tfmdata.color then -          local _,popcolor=hex_to_rgba(tfmdata.color) -          local pop=node.new(whatsit,8) -          pop.mode=1 -          pop.data=popcolor -          head=node.insert_after(head,n,pop) -          current_color=nil -        end -      end -    end -  end -  return head,current_color -end -local function font_colorize(head) -  if res then -   local r="/ExtGState<<"..res..">>" -   tex.pdfpageresources=tex.pdfpageresources:gsub(r,"") -  end -  local h=node_colorize(head,nil,nil) -  if res and res:find("%S") then  -   local r="/ExtGState<<"..res..">>" -   tex.pdfpageresources=tex.pdfpageresources..r -  end -  return h -end -local color_callback_activated=0 -function add_color_callback() -  if color_callback_activated==0 then -    luatexbase.add_to_callback("pre_output_filter",font_colorize,"loaotfload.colorize") -    color_callback_activated=1 -  end -end - -end -- closure diff --git a/luaotfload-legacy-tool.lua b/luaotfload-legacy-tool.lua deleted file mode 100755 index 30ab13b..0000000 --- a/luaotfload-legacy-tool.lua +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env texlua ---[[ -This file is copyright 2010 Elie Roux and Khaled Hosny and is under CC0 -license (see http://creativecommons.org/publicdomain/zero/1.0/legalcode). - -This file is a wrapper for the luaotfload's font names module. It is part of the -luaotfload bundle, please see the luaotfload documentation for more info. ---]] - -kpse.set_program_name("luatex") - -require("lualibs")                    ---> current -require("luaotfload-legacy-database") ---> old -require("alt_getopt")                 ---> ? - -local name      = "mkluatexfontdb" -local version   = "1.31 (legacy)" - -local names    = fonts.names - -local function help_msg() -    texio.write(string.format([[ -Usage: %s [OPTION]... - -================================================================================ -                        please update your luatex binary -             this version is unsupported and likely to break things -================================================================================ -     -Rebuild the LuaTeX font database. - -Valid options: -  -f --force                   force re-indexing all fonts -  -q --quiet                   don't output anything -  -v --verbose=LEVEL           be more verbose (print the searched directories) -  -vv                          print the loaded fonts -  -vvv                         print all steps of directory searching -  -V --version                 print version and exit -  -h --help                    print this message - -The output database file is named otfl-names.lua and is placed under: - -   %s - -contact: https://github.com/lualatex/luaotfload - -]], name, names.path.localdir)) -end - -local function version_msg() -    texio.write(string.format( -        "%s version %s, database version %s.\n", name, version, names.version)) -end - ---[[ -Command-line processing. -Here we fill cmdargs with the good values, and then analyze it. ---]] - -local long_opts = { -    force            = "f", -    quiet            = "q", -    help             = "h", -    verbose          = 1  , -    version          = "V", -} - -local short_opts = "fqpvVh" - -local force_reload = nil - -local function process_cmdline() -    local opts, optind, optarg = alt_getopt.get_ordered_opts (arg, short_opts, long_opts) -    local log_level = 1 -    for i,v in ipairs(opts) do -        if     v == "q" then -            log_level = 0 -        elseif v == "v" then -            if log_level > 0 then -                log_level = log_level + 1 -            else -                log_level = 2 -            end -        elseif v == "V" then -            version_msg() -            os.exit(0) -        elseif v == "h" then -            help_msg() -            os.exit(0) -        elseif v == "f" then -            force_reload = 1 -        end -    end -    names.set_log_level(log_level) -end - -local function generate(force) -    local fontnames, saved -    fontnames = names.update(fontnames, force) -    logs.report("%s fonts in the database", #fontnames.mappings) -    saved = names.save(fontnames) -end - -process_cmdline() -generate(force_reload) diff --git a/luaotfload-legacy.lua b/luaotfload-legacy.lua deleted file mode 100644 index 8bb1790..0000000 --- a/luaotfload-legacy.lua +++ /dev/null @@ -1,402 +0,0 @@ -module("luaotfload", package.seeall) - -luaotfload.module = { -    name          = "luaotfload-legacy", -    version       = 1.31, -    date          = "2013/04/25", -    description   = "Unsupported Luaotfload", -    author        = "Elie Roux & Hans Hagen", -    copyright     = "Elie Roux", -    license       = "GPL v2" -} - -local error, warning, info, log = luatexbase.provides_module(luaotfload.module) - ---[[doc-- - -   This used to be a necessary initalization in order not to rebuild an -   existing font.  Maybe 600 should be replaced by |\pdfpkresolution| -   or |texconfig.pk_dpi| (and it should be replaced dynamically), but -   we don't have access (yet) to the |texconfig| table, so we let it be -   600. Anyway, it does still work fine even if |\pdfpkresolution| is -   changed. - ---doc]]-- - -kpse.init_prog("", 600, "/") - ---[[doc-- - -   The minimal required \luatex version. -   We are tolerant folks. - ---doc]]-- - -local luatex_version = 60 -if tex.luatexversion < luatex_version then -    warning("LuaTeX v%.2f is old, v%.2f is required, v0.76 recommended.", -             tex.luatexversion/100, -             luatex_version   /100) -end - ---[[doc-- - -    \subsection{Module loading} -    We load the outdated \context files with this function. It -    automatically adds the |otfl-| prefix to it, so that we call it with -    the actual \context name. - ---doc]]-- - -function luaotfload.loadmodule(tofind) -    local found = kpse.find_file(tofind,"tex") -    if found then -        log("loading file %s.", found) -        dofile(found) -    else -        error("file %s not found.", tofind) -    end -end -local loadmodule = luaotfload.loadmodule - ---[[doc-- - -    Keep away from these lines! - ---doc]]-- -loadmodule"luaotfload-legacy-merged.lua" - -if not fonts then -  loadmodule("otfl-luat-dum.lua") -- not used in context at all -  loadmodule("otfl-luat-ovr.lua") -- override some luat-dum functions -  loadmodule("otfl-data-con.lua") -- maybe some day we don't need this one -  loadmodule("otfl-font-ini.lua") -  loadmodule("otfl-node-dum.lua") -  loadmodule("otfl-node-inj.lua") ---[[doc-- -   By default \context takes some private attributes for internal use. To -   avoide attribute clashes with other packages, we override the function -   that allocates new attributes, making it a wraper around -   |luatexbase.new_attribute()|. We also prefix attributes with |otfl@| to -   avoid possiple name clashes. ---doc]]-- -  loadmodule("luaotfload-legacy-attributes.lua") -- patch attributes ---[[doc-- -   Font handling modules. ---doc]]-- -  loadmodule("otfl-font-tfm.lua") -  loadmodule("otfl-font-cid.lua") -  loadmodule("otfl-font-ott.lua") -  loadmodule("otfl-font-map.lua") -  loadmodule("otfl-font-otf.lua") -  loadmodule("otfl-font-otd.lua") -  loadmodule("otfl-font-oti.lua") -  loadmodule("otfl-font-otb.lua") -  loadmodule("otfl-font-otn.lua") -  loadmodule("otfl-font-ota.lua") -  loadmodule("otfl-font-otc.lua") -  loadmodule("otfl-font-def.lua") ---[[doc-- -   \textsf{old luaotfload} specific modules. ---doc]]-- -  loadmodule("otfl-font-xtx.lua") -  loadmodule("otfl-font-dum.lua") -  loadmodule("otfl-font-clr.lua") -end -loadmodule"luaotfload-legacy-database.lua" --- unmerged coz needed in db script - ---[[doc-- - -    This is a patch for |otfl-font-def.lua|, that defines a reader for ofm -    fonts, this is necessary if we set the forced field of the specification -    to |ofm|. - ---doc]]-- - -if fonts and fonts.tfm and fonts.tfm.readers then -    fonts.tfm.readers.ofm = fonts.tfm.readers.tfm -end - ---[[doc-- - -    \subsection{Post-processing TFM table} -    Here we do some final touches to the loaded TFM table before passing it -    to the \tex end. -    First we create a callback for patching fonts on the fly, to be used by -    other packages. - ---doc]]-- - -luatexbase.create_callback("luaotfload.patch_font", "simple", function() end) - ---[[doc-- - -    then define a function where font manipulation will take place. - ---doc]]-- - -local function def_font(...) -    local fontdata = fonts.define.read(...) -    if type(fontdata) == "table" and fontdata.shared then ---[[doc-- - -    Then we populate |MathConstants| table, which is required for -    OpenType math. - -    Note: actually it isn’t, but you’re asking for it by using outdated -    code. - ---doc]]-- -        local otfdata = fontdata.shared.otfdata -        if otfdata.metadata.math then -            local mc = { } -            for k,v in next, otfdata.metadata.math do -                if k:find("Percent") then -                    -- keep percent values as is -                    mc[k] = v -                else -                    mc[k] = v / fontdata.units * fontdata.size -                end -            end -            -- for \overwithdelims -            mc.FractionDelimiterSize             = 1.01 * fontdata.size -            mc.FractionDelimiterDisplayStyleSize = 2.39 * fontdata.size - -            fontdata.MathConstants = mc -        end ---[[doc-- - -    Execute any registered font patching callbacks. - ---doc]]-- -        luatexbase.call_callback("luaotfload.patch_font", fontdata) -    end -    return fontdata -end - ---[[doc-- -\subsection{\context override} - -    We have a unified function for both file and name resolver. This -    line is commented as it makes database reload too often. This means -    that in some cases, a font in the database will not be found if -    it's not in the texmf tree. A similar thing will reappear in next -    version. - ---doc]]-- - ---fonts.define.resolvers.file = fonts.define.resolvers.name - ---[[doc-- - -    Overriding some defaults set in \context code. - ---doc]]-- - -fonts.mode = "node" - ---[[doc-- - -    The following features are useful in math (e.g. in XITS Math font), -    but \textsf{luaotfload} does not recognize them in |base| mode. - ---doc]]-- - -local register_base_sub = fonts.otf.features.register_base_substitution -local gsubs = { -    "ss01", "ss02", "ss03", "ss04", "ss05", -    "ss06", "ss07", "ss08", "ss09", "ss10", -    "ss11", "ss12", "ss13", "ss14", "ss15", -    "ss16", "ss17", "ss18", "ss19", "ss20", -} - -for _,v in next, gsubs do -    register_base_sub(v) -end - ---[[doc-- - -    Finally we register the callbacks - ---doc]]-- - -luatexbase.add_to_callback("pre_linebreak_filter", -                            nodes.simple_font_handler, -                           "luaotfload.pre_linebreak_filter") -luatexbase.add_to_callback("hpack_filter", -                            nodes.simple_font_handler, -                           "luaotfload.hpack_filter") -luatexbase.reset_callback("define_font") -luatexbase.add_to_callback("define_font", -                            def_font, -                           "luaotfload.define_font", 1) -luatexbase.add_to_callback("find_vf_file", -                            fonts.vf.find, -                           "luaotfload.find_vf_file") ---[[doc-- - -    XXX: see https://github.com/wspr/unicode-math/issues/185 -    \luatex does not provide interface to accessing -    |(Script)ScriptPercentScaleDown| math constants, so we -    emulate \xetex behaviour by setting |\fontdimen10| and -    |\fontdimen11|. - -    Note: actually, it does now, but not unless you update. - ---doc]]-- - -local function set_sscale_diments(fontdata) -    local mc = fontdata.MathConstants -    if mc then -        if mc["ScriptPercentScaleDown"] then -            fontdata.parameters[10] = mc.ScriptPercentScaleDown -        else -- resort to plain TeX default -            fontdata.parameters[10] = 70 -        end -        if mc["ScriptScriptPercentScaleDown"] then -            fontdata.parameters[11] = mc.ScriptScriptPercentScaleDown -        else -- resort to plain TeX default -            fontdata.parameters[11] = 50 -        end -    end -end - -luatexbase.add_to_callback("luaotfload.patch_font", set_sscale_diments, "unicodemath.set_sscale_diments") - ---[[doc-- -  Version 2.3c of fontspec dropped a couple features that are now -  provided in the luaotfload auxiliary libraries. To avoid breaking -  Mik\TEX (again), which is sorta the entire point of distributing the -  legacy codebase, we temporarily restore those functions here. - -  Note that apart from cosmetic changes these are still the same as in -  pre-TL2013 fontspec, relying on pairs() and other inefficient methods. ---doc]]-- - -luaotfload.aux      = luaotfload.aux or { } -local aux           = luaotfload.aux - -local stringlower   = string.lower -local fontid        = font.id - -local identifiers   = fonts.identifiers - -local check_script = function (id, script) -  local s = stringlower(script) -  if id and id > 0 then -    local tfmdata = identifiers[id] -    local otfdata = tfmdata.shared and tfmdata.shared.otfdata -    if otfdata then -      local features = otfdata.luatex.features -      for i, _ in pairs(features) do -        for j, _ in pairs(features[i]) do -          if features[i][j][s] then -            fontspec.log("script '%s' exists in font '%s'", -                         script, tfmdata.fullname) -            return true -          end -        end -      end -    end -  end -end - -local check_language = function (id, script, language) -  local s = stringlower(script) -  local l = stringlower(language) -  if id and id > 0 then -    local tfmdata = identifiers[id] -    local otfdata = tfmdata.shared and tfmdata.shared.otfdata -    if otfdata then -      local features = otfdata.luatex.features -      for i, _ in pairs(features) do -        for j, _ in pairs(features[i]) do -          if features[i][j][s] and features[i][j][s][l] then -            fontspec.log("language '%s' for script '%s' exists in font '%s'", -                         language, script, tfmdata.fullname) -            return true -          end -        end -      end -    end -  end -end - -local check_feature = function (id, script, language, feature) -  local s = stringlower(script) -  local l = stringlower(language) -  local f = stringlower(feature:gsub("^[+-]", ""):gsub("=.*$", "")) -  if id and id > 0 then -    local tfmdata = identifiers[id] -    local otfdata = tfmdata.shared and tfmdata.shared.otfdata -    if otfdata then -      local features = otfdata.luatex.features -      for i, _ in pairs(features) do -        if features[i][f] and features[i][f][s] then -          if features[i][f][s][l] == true then -            fontspec.log("feature '%s' for language '%s' and script '%s' exists in font '%s'", -                         feature, language, script, tfmdata.fullname) -            return true -          end -        end -      end -    end -  end -end - -local get_math_dimension = function(fnt, str) -  if type(fnt) == "string" then -    fnt = fontid(fnt) -  end -  local tfmdata = identifiers[fnt] -  if tfmdata then -    local mathdata = tfmdata.MathConstants -    if mathdata then -      return mathdata[str] -    end -  end -end - -aux.provides_script       = check_script -aux.provides_language     = check_language -aux.provides_feature      = check_feature -aux.get_math_dimension    = get_math_dimension - -local set_capheight = function (tfmdata) -    local capheight -    local shared = tfmdata.shared -    if shared then -      local metadata       = shared.otfdata.metadata -      local units_per_em   = metadata.units_per_em or tfmdata.units -      local os2_capheight  = shared.otfdata.pfminfo.os2_capheight -      local size           = tfmdata.size - -      if os2_capheight > 0 then -          capheight = os2_capheight / units_per_em * size -      else -          local X8 = string.byte"X" -          if tfmdata.characters[X8] then -              capheight = tfmdata.characters[X8].height -          else -              capheight = metadata.ascent / units_per_em * size -          end -      end -    else -        local X8 = string.byte"X" -        if tfmdata.characters[X8] then -            capheight = tfmdata.characters[X8].height -        end -    end -    if capheight then -        tfmdata.parameters[8] = capheight -    end -end -luatexbase.add_to_callback("luaotfload.patch_font", -                           set_capheight, -                           "luaotfload.set_capheight") - ---[[doc-- -End of auxiliary functionality that was moved from fontspec.lua. ---doc]]-- - --- vim:ts=2:sw=2:expandtab:ft=lua diff --git a/luaotfload.dtx b/luaotfload.dtx deleted file mode 100644 index 3615fa8..0000000 --- a/luaotfload.dtx +++ /dev/null @@ -1,2685 +0,0 @@ -% \iffalse meta-comment -% -% Copyright (C) 2009-2014 -%      by  Elie Roux      <elie.roux@telecom-bretagne.eu> -%      and Khaled Hosny   <khaledhosny@eglug.org> -%      and Philipp Gesang <philipp.gesang@alumni.uni-heidelberg.de> -% -%      Home:      https://github.com/lualatex/luaotfload -%      Support:   <lualatex-dev@tug.org>. -% -% This work is under the GPL v2.0 license. -% -% This work consists of the main source file luaotfload.dtx -% and the derived files -%    luaotfload.sty, luaotfload.lua -% -% Unpacking: -%    tex luaotfload.dtx -% -% Documentation: -%    lualatex luaotfload.dtx -% -%    The class ltxdoc loads the configuration file ltxdoc.cfg -%    if available. Here you can specify further options, e.g. -%    use A4 as paper format: -%       \PassOptionsToClass{a4paper}{article} -% -% -% -%<*ignore> -\begingroup -  \def\x{LaTeX2e}% -\expandafter\endgroup -\ifcase 0\ifx\install y1\fi\expandafter -         \ifx\csname processbatchFile\endcsname\relax\else1\fi -         \ifx\fmtname\x\else 1\fi\relax -\else\csname fi\endcsname -%</ignore> -%<*install> -\input docstrip.tex -\Msg{************************************************************************} -\Msg{* Installation} -\Msg{* Package: luaotfload v2.4-4 OpenType layout system} -\Msg{************************************************************************} - -\keepsilent -\askforoverwritefalse - -\let\MetaPrefix\relax - -\preamble -This is a generated file. - -Copyright (C) 2009-2014 -     by  Elie Roux      <elie.roux@telecom-bretagne.eu> -     and Khaled Hosny   <khaledhosny@eglug.org> -     and Philipp Gesang <philipp.gesang@alumni.uni-heidelberg.de> - -     Home:      https://github.com/lualatex/luaotfload -     Support:   <lualatex-dev@tug.org>. - -This work is under the GPL v2.0 license. - -This work consists of the main source file luaotfload.dtx -and the derived files -    luaotfload.sty, luaotfload.lua - -\endpreamble - -\let\MetaPrefix\DoubleperCent - -\generate{% -  \usedir{tex/luatex/luaotfload}% -  \file{luaotfload.sty}{\from{luaotfload.dtx}{package}}% -} - -% The following hacks are to generate a lua file with lua comments starting with -% -- instead of %% - -\def\MetaPrefix{-- } - -\def\luapostamble{% -  \MetaPrefix^^J% -  \MetaPrefix\space End of File `\outFileName'.% -} - -\def\currentpostamble{\luapostamble}% - -\generate{% -  \usedir{tex/luatex/luaotfload}% -  \file{luaotfload.lua}{\from{luaotfload.dtx}{lua}}%% -} - -\obeyspaces -\Msg{************************************************************************} -\Msg{*} -\Msg{* To finish the installation you have to move the following} -\Msg{* files into a directory searched by TeX:} -\Msg{*} -\Msg{*     luaotfload.sty, luaotfload.lua} -\Msg{*} -\Msg{* Happy TeXing!} -\Msg{*} -\Msg{************************************************************************} - -\endbatchfile -%</install> -%<*ignore> -\fi -%</ignore> -%<*driver> -\NeedsTeXFormat{LaTeX2e} -\ProvidesFile{luaotfload.drv}% -  [2014/05/18 v2.4-4 OpenType layout system]% -\documentclass{ltxdoc} -\usepackage{metalogo,multicol,mdwlist,fancyvrb,xspace} -\usepackage[x11names]{xcolor} -% -\def\primarycolor{DodgerBlue4}  %%-> rgb  16  78 139 | #104e8b -\def\secondarycolor{Goldenrod4} %%-> rgb 139 105 200 | #8b6914 -% -\usepackage[ -    bookmarks=true, -   colorlinks=true, -    linkcolor=\primarycolor, -     urlcolor=\secondarycolor, -    citecolor=\primarycolor, -     pdftitle={The luaotfload package}, -   pdfsubject={OpenType layout system for Plain TeX and LaTeX}, -    pdfauthor={Elie Roux & Khaled Hosny & Philipp Gesang}, -  pdfkeywords={luatex, lualatex, unicode, opentype} -]{hyperref} -\usepackage{fontspec} -\usepackage{unicode-math} -\setmainfont[ -% Numbers     = OldStyle, %% buggy with font cache -  Ligatures   = TeX, -  BoldFont    = {Linux Libertine O Bold}, -  ItalicFont  = {Linux Libertine O Italic}, -  SlantedFont = {Linux Libertine O Italic}, -]{Linux Libertine O} -\setmonofont[Ligatures=TeX,Scale=MatchLowercase]{Liberation Mono} -%setsansfont[Ligatures=TeX]{Linux Biolinum O} -\setsansfont[Ligatures=TeX,Scale=MatchLowercase]{Iwona Medium} -%setmathfont{XITS Math} - -\usepackage{hologo} - -\newcommand\TEX      {\TeX\xspace} -\newcommand\LUA      {Lua\xspace} -\newcommand\PDFTEX   {pdf\TeX\xspace} -\newcommand\LUATEX   {Lua\TeX\xspace} -\newcommand\XETEX    {\XeTeX\xspace} -\newcommand\LATEX    {\LaTeX\xspace} -\newcommand\LUALATEX {Lua\LaTeX\xspace} -\newcommand\CONTEXT  {Con\TeX t\xspace} -\newcommand\OpenType {\identifier{Open\kern-.25ex Type}\xspace} - -\def\definehighlight[#1][#2]% - {\ifcsname #1\endcsname\else -    \expandafter\def\csname #1\endcsname% -      {\bgroup#2\csname #1_indeed\endcsname} -    \expandafter\def\csname #1_indeed\endcsname##1% -      {##1\egroup}% -  \fi} - -\def\restoreunderscore{\catcode`\_=12\relax} - -\definehighlight    [fileent][\ttfamily\restoreunderscore]         %% files, dirs -\definehighlight   [texmacro][\sffamily\itshape\textbackslash]     %% cs -\definehighlight[luafunction][\sffamily\itshape\restoreunderscore] %% lua identifiers -\definehighlight [identifier][\sffamily]                           %% names -\definehighlight     [abbrev][\rmfamily\scshape]                   %% acronyms -\definehighlight   [emphasis][\rmfamily\slshape]                   %% level 1 emph - -\newcommand*\email[1]{\href{mailto:#1}{#1}} - -\renewcommand\partname{Part}%% gets rid of the stupid “file” heading - -\usepackage{syntax}%% bnf for font request syntax - -\usepackage{titlesec} - -\def\movecountertomargin#1{\llap{\rmfamily\upshape#1\hskip2em}} -\def\zeropoint{0pt} -\titleformat \part -             {\normalsize\rmfamily\bfseries} -             {\movecountertomargin\thepart} \zeropoint {} -\titleformat \section -             {\normalsize\rmfamily\scshape} -             {\movecountertomargin\thesection} \zeropoint {} -\titleformat \subsection -             {\small\rmfamily\itshape} -             {\movecountertomargin\thesubsection} \zeropoint {} -\titleformat \subsubsection -             {\normalsize\rmfamily\upshape} -             {\movecountertomargin\thesubsubsection} \zeropoint {} - -\usepackage{tocloft} -\renewcommand \cftpartfont   {\rmfamily\upshape} -\renewcommand \cftsecfont    {\rmfamily\upshape} -\renewcommand \cftsubsecfont {\rmfamily\upshape} -\setlength \cftbeforepartskip {1ex} -\setlength \cftbeforesecskip  {1ex} - -\VerbatimFootnotes -\begin{document} -  \DocInput{luaotfload.dtx}% -\end{document} -%</driver> -% \fi -% -% \CheckSum{0} -% -% \CharacterTable -%  {Upper-case    \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 -%   Lower-case    \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 -%   Digits        \0\1\2\3\4\5\6\7\8\9 -%   Exclamation   \!     Double quote  \"     Hash (number) \# -%   Dollar        \$     Percent       \%     Ampersand     \& -%   Acute accent  \'     Left paren    \(     Right paren   \) -%   Asterisk      \*     Plus          \+     Comma         \, -%   Minus         \-     Point         \.     Solidus       \/ -%   Colon         \:     Semicolon     \;     Less than     \< -%   Equals        \=     Greater than  \>     Question mark \? -%   Commercial at \@     Left bracket  \[     Backslash     \\ -%   Right bracket \]     Circumflex    \^     Underscore    \_ -%   Grave accent  \`     Left brace    \{     Vertical bar  \| -%   Right brace   \}     Tilde         \~} -% -% \GetFileInfo{luaotfload.drv} -% -% \title{The \identifier{luaotfload} package} -% \date{2014/05/18 v2.4-4} -% \author{Elie Roux · Khaled Hosny · Philipp Gesang\\ -%         Home:      \url{https://github.com/lualatex/luaotfload}\\ -%         Support:   \email{lualatex-dev@tug.org}} -% -% \maketitle -% -% \begin{abstract} -% This package is an adaptation of the \CONTEXT font loading system. -% It allows for loading \OpenType fonts with an extended syntax and adds -% support for a variety of font features. -% \end{abstract} -% -% \tableofcontents -% -% \part{Package Description} -% -% \section{Introduction} -% -% Font management and installation has always been painful with \TEX.  A lot of -% files are needed for one font (\abbrev{tfm}, \abbrev{pfb}, \abbrev{map}, -% \abbrev{fd}, \abbrev{vf}), and due to the 8-Bit encoding each font is limited -% to 256 characters. -% But the font world has evolved since the original -% \TEX, and new typographic systems have appeared, most notably the so -% called \emphasis{smart font} technologies like \OpenType -% fonts (\abbrev{otf}). -% These fonts can contain many more characters than \TEX fonts, as well as additional -% functionality like ligatures, old-style numbers, small capitals, -% etc., and support more complex writing systems like Arabic and -% Indic\footnote{% -%   Unfortunately, \identifier{luaotfload} doesn‘t support many Indic -%   scripts right now. -%   Assistance in implementing the prerequisites is greatly -%   appreciated. -% } -% scripts. -% \OpenType fonts are widely deployed and available for all -% modern operating systems. -% As of 2013 they have become the de facto standard for advanced text -% layout. -% However, until recently the only way to use them directly in the \TEX -% world was with the \XETEX engine. -% -% Unlike \XETEX, \LUATEX has no built-in support for -% \OpenType or technologies other than the original \TEX fonts. -% Instead, it provides hooks for executing \LUA code during the \TEX run -% that allow implementing extensions for loading fonts and manipulating -% how input text is processed without modifying the underlying engine. -% This is where \identifier{luaotfload} comes into play: -% Based on code from \CONTEXT, it extends \LUATEX with functionality necessary -% for handling \OpenType fonts. -% Additionally, it provides means for accessing fonts known to the operating -% system conveniently by indexing the metadata. -% -% -% \section{Thanks} -% -% \identifier{Luaotfload} is part of \LUALATEX, the community-driven -% project to provide a foundation for using the \LATEX format with the -% full capabilites of the \LUATEX engine. -% As such, the distinction between end users, contributors, and project -% maintainers is intentionally kept less strict, lest we unduly -% personalize the common effort. -% -% Nevertheless, the current maintainers would like to express their -% gratitude to Khaled Hosny, Akira Kakuto, Hironori Kitagawa and Dohyun -% Kim. -% Their contributions -- be it patches, advice, or systematic -% testing -- made the switch from version 1.x to 2.2 possible. -% Also, Hans Hagen, the author of the font loader, made porting the -% code to \LATEX a breeze due to the extra effort he invested into -% isolating it from the rest of \CONTEXT, not to mention his assistance -% in the task and willingness to respond to our suggestions. -% -% -% \section{Loading Fonts} -% -% \identifier{luaotfload} supports an extended font request syntax: -% -% \begin{quote} -%       |\font\foo={|% -%       \meta{prefix}|:|% -%       \meta{font name}|:|% -%       \meta{font features}|}|% -%       \meta{\TEX font features} -% \end{quote} -% -% \noindent -% The curly brackets are optional and escape the spaces in the enclosed -% font name. -% Alternatively, double quotes serve the same purpose. -% A selection of individual parts of the syntax are discussed below; -% for a more formal description see figure \ref{font-syntax}. -% -% \begin{figure}[b] -%   \setlength\grammarparsep{12pt plus 2pt minus 2pt} -%   \setlength\grammarindent{5cm} -%   \begingroup -%     \small -%     \begin{grammar} -%       <definition>      ::= `\\font', {\sc csname}, `=', <font request>, [ <size> ] ; -% -%       <size>            ::= `at', {\sc dimension} ; -% -%       <font request>    ::= `"', <unquoted font request> `"' -%       \alt                  `{', <unquoted font request> `}' -%       \alt                  <unquoted font request> ; -% -%       <unquoted font request> ::= <specification>, [`:', <feature list> ] -%       \alt                        `[', <path lookup> `]', [ [`:'], <feature list> ] ; -% -%       <specification>    ::= <prefixed spec>, [ <subfont no> ], \{ <modifier> \} -%       \alt                   <anon lookup>, \{ <modifier> \} ; -% -%       <prefixed spec>    ::= `file:', <file lookup> -%       \alt                   `name:', <name lookup> ; -% -%       <file lookup>      ::= \{ <name character> \} ; -% -%       <name lookup>      ::= \{ <name character> \} ; -% -%       <anon lookup>      ::= {\sc tfmname} | <name lookup> ; -% -%       <path lookup>      ::= \{ {\sc all_characters} - `]' \} ; -% -%       <modifier>         ::= `/', (`I' | `B' | `BI' | `IB' | `S=', \{ {\sc digit} \} ) ; -% -%       <subfont no>       ::= `(', \{ {\sc digit} \}, `)' ; -% -%       <feature list>     ::= <feature expr>, \{ `;', <feature expr> \} ; -% -%       <feature expr>     ::= {\sc feature_id}, `=', {\sc feature_value} -%       \alt                   <feature switch>, {\sc feature_id} ; -% -%       <feature switch>   ::= `+' | `-' ; -% -%       <name character>   ::= {\sc all_characters} - ( `(' | `/' | `:' ) ; -%     \end{grammar} -%   \endgroup -%   \caption{Font request syntax. -%            Braces or double quotes around the -%            \emphasis{specification} rule will -%            preserve whitespace in file names. -%            In addition to the font style modifiers -%            (\emphasis{slash-notation}) given above, there -%            are others that are recognized but will be silently -%            ignored: {\ttfamily aat}, -%                     {\ttfamily icu}, and -%                     {\ttfamily gr}. -%            The special terminals are: -%            {\sc feature\textunderscore id} for a valid font -%               feature name and -%            {\sc feature\textunderscore value} for the corresponding -%               value. -%            {\sc tfmname} is the name of a \abbrev{tfm} file. -%            {\sc digit}  again refers to bytes 48--57, and -%            {\sc all\textunderscore characters} to all byte values. -%            {\sc csname} and {\sc dimension} are the \TEX concepts.} -%   \label{font-syntax} -% \end{figure} -% -% \subsection{Prefix -- the \identifier{luaotfload}{ }Way} -% -% In \identifier{luaotfload}, the canonical syntax for font requests -% requires a \emphasis{prefix}: -% \begin{quote} -%   |\font\fontname=|\meta{prefix}|:|\meta{fontname}\dots -% \end{quote} -% where \meta{prefix} is either \verb|file:| or \verb|name:|.\footnote{% -%   The development version also knows two further prefixes, -%   \verb|kpse:| and \verb|my:|. -%   A \verb|kpse| lookup is restricted to files that can be found by -%   \identifier{kpathsea} and -%   will not attempt to locate system fonts. -%   This behavior can be of value when an extra degree of encapsulation is -%   needed, for instance when supplying a customized tex distribution. -% -%   The \verb|my| lookup takes this a step further: it lets you define -%   a custom resolver function and hook it into the \luafunction{resolve_font} -%   callback. -%   This ensures full control over how a file is located. -%   For a working example see the -%   \href{https://bitbucket.org/phg/lua-la-tex-tests/src/5f6a535d/pln-lookup-callback-1.tex} -%        {test repo}. -% } -% It determines whether the font loader should interpret the request as -% a \emphasis{file name} or -%   \emphasis{font name}, respectively, -% which again influences how it will attempt to locate the font. -% Examples for font names are -%             “Latin Modern Italic”, -%             “GFS Bodoni Rg”, and -%             “PT Serif Caption” -% -- they are the human readable identifiers -% usually listed in drop-down menus and the like.\footnote{% -%   Font names may appear like a great choice at first because they -%   offer seemingly more intuitive identifiers in comparison to arguably -%   cryptic file names: -%   “PT Sans Bold” is a lot more descriptive than \fileent{PTS75F.ttf}. -%   On the other hand, font names are quite arbitrary and there is no -%   universal method to determine their meaning. -%   While \identifier{luaotfload} provides fairly sophisticated heuristic -%   to figure out a matching font style, weight, and optical size, it -%   cannot be relied upon to work satisfactorily for all font files. -%   For an in-depth analysis of the situation and how broken font names -%   are, please refer to -%   \href{http://www.ntg.nl/pipermail/ntg-context/2013/073889.html} -%        {this post} -%   by Hans Hagen, the author of the font loader. -%   If in doubt, use filenames. -%   \fileent{luaotfload-tool} can perform the matching for you with the -%   option \verb|--find=<name>|, and you can use the file name it returns -%   in your font definition. -% } -% In order for fonts installed both in system locations and in your -% \fileent{texmf} to be accessible by font name, \identifier{luaotfload} must -% first collect the metadata included in the files. -% Please refer to section~\ref{sec:fontdb} below for instructions on how to -% create the database. -% -% File names are whatever your file system allows them to be, except -% that that they may not contain the characters -%                             \verb|(|, -%                             \verb|:|, and -%                             \verb|/|. -% As is obvious from the last exception, the \verb|file:| lookup will -% not process paths to the font location -- only those -% files found when generating the database are addressable this way. -% Continue below in the \XETEX section if you need to load your fonts -% by path. -% The file names corresponding to the example font names above are -%           \fileent{lmroman12-italic.otf}, -%           \fileent{GFSBodoni.otf}, and -%           \fileent{PTZ56F.ttf}. -% -% \subsection{Compatibility Layer} -% -% In addition to the regular prefixed requests, \identifier{luaotfload} -% accepts loading fonts the \XETEX way. -% There are again two modes: bracketed and unbracketed. -% A bracketed request looks as follows. -% -% \begin{quote} -%   |\font\fontname=[|\meta{path to file}|]| -% \end{quote} -% -% \noindent -% Inside the square brackets, every character except for a closing -% bracket is permitted, allowing for specifying paths to a font file. -% Naturally, path-less file names are equally valid and processed the -% same way as an ordinary \verb|file:| lookup. -% -% \begin{quote} -%   |\font\fontname=|\meta{font name} \dots -% \end{quote} -% -% Unbracketed (or, for lack of a better word: \emphasis{anonymous}) -% font requests resemble the conventional \TEX syntax. -% However, they have a broader spectrum of possible interpretations: -% before anything else, \identifier{luaotfload} attempts to load a -% traditional \TEX Font Metric (\abbrev{tfm} or \abbrev{ofm}). -% If this fails, it performs a \verb|name:| lookup, which itself will -% fall back to a \verb|file:| lookup if no database entry matches -% \meta{font name}. -% -% Furthermore, \identifier{luaotfload} supports the slashed (shorthand) -% font style notation from \XETEX. -% -% \begin{quote} -%   |\font\fontname=|\meta{font name}|/|\meta{modifier}\dots -% \end{quote} -% -% \noindent -% Currently, four style modifiers are supported: -%   \verb|I| for italic shape, -%   \verb|B| for bold   weight, -%   \verb|BI| or \verb|IB| for the combination of both. -% Other “slashed” modifiers are too specific to the \XETEX engine and -% have no meaning in \LUATEX. -% -% \subsection{Examples} -% -% \subsubsection{Loading by File Name} -% -% For example, conventional \abbrev{type1} font can be loaded with a \verb|file:| -% request like so: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\lmromanten={file:ec-lmr10} at 10pt -%   \end{verbatim} -% \end{quote} -% -% The \OpenType version of Janusz Nowacki’s font \emphasis{Antykwa -% Półtawskiego}\footnote{% -%   \url{http://jmn.pl/antykwa-poltawskiego/}, also available in -%   in \TEX Live. -% } -% in its condensed variant can be loaded as follows: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\apcregular=file:antpoltltcond-regular.otf at 42pt -%   \end{verbatim} -% \end{quote} -% -% The next example shows how to load the \emphasis{Porson} font digitized by -% the Greek Font Society using \XETEX-style syntax and an absolute path from a -% non-standard directory: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\gfsporson="[/tmp/GFSPorson.otf]" at 12pt -%   \end{verbatim} -% \end{quote} -% -% \subsubsection{Loading by Font Name} -% -% The \verb|name:| lookup does not depend on cryptic filenames: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\pagellaregular={name:TeX Gyre Pagella} at 9pt -%   \end{verbatim} -% \end{quote} -% -% A bit more specific but essentially the same lookup would be: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\pagellaregular={name:TeX Gyre Pagella Regular} at 9pt -%   \end{verbatim} -% \end{quote} -% -% \noindent -% Which fits nicely with the whole set: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\pagellaregular   ={name:TeX Gyre Pagella Regular}    at 9pt -%   \font\pagellaitalic    ={name:TeX Gyre Pagella Italic}     at 9pt -%   \font\pagellabold      ={name:TeX Gyre Pagella Bold}       at 9pt -%   \font\pagellabolditalic={name:TeX Gyre Pagella Bolditalic} at 9pt -% -%   {\pagellaregular     foo bar baz\endgraf} -%   {\pagellaitalic      foo bar baz\endgraf} -%   {\pagellabold        foo bar baz\endgraf} -%   {\pagellabolditalic  foo bar baz\endgraf} -% -%   ... -%   \end{verbatim} -% \end{quote} -% -% \subsubsection{Modifiers} -% -% If the entire \emphasis{Iwona} family\footnote{% -%   \url{http://jmn.pl/kurier-i-iwona/}, -%   also in \TEX Live. -% } -% is installed in some location accessible by \identifier{luaotfload}, -% the regular shape can be loaded as follows: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\iwona=Iwona at 20pt -%   \end{verbatim} -% \end{quote} -% -% \noindent -% To load the most common of the other styles, the slash notation can -% be employed as shorthand: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\iwonaitalic    =Iwona/I    at 20pt -%   \font\iwonabold      =Iwona/B    at 20pt -%   \font\iwonabolditalic=Iwona/BI   at 20pt -%   \end{verbatim} -% \end{quote} -% -% \noindent -% which is equivalent to these full names: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\iwonaitalic    ="Iwona Italic"       at 20pt -%   \font\iwonabold      ="Iwona Bold"         at 20pt -%   \font\iwonabolditalic="Iwona BoldItalic"   at 20pt -%   \end{verbatim} -% \end{quote} -% -% \section{Font features} -% -% \emphasis{Font features} are the second to last component in the -% general scheme for font requests: -% -% \begin{quote} -%       |\font\foo={|% -%       \meta{prefix}|:|% -%       \meta{font name}|:|% -%       \meta{font features}|}|% -%       \meta{\TEX font features} -% \end{quote} -% -% \noindent -% If style modifiers are present (\XETEX style), they must precede -% \meta{font features}. -% -% The element \meta{font features} is a semicolon-separated list of feature -% tags\footnote{% -%   Cf. \url{http://www.microsoft.com/typography/otspec/featurelist.htm}. -% } -% and font options. -% Prepending a font feature with a |+| (plus sign) enables it, whereas -% a |-| (minus) disables it. For instance, the request -% -% \begin{quote} -%   \begin{verbatim} -%   \font\test=LatinModernRoman:+clig;-kern -%   \end{verbatim} -% \end{quote} -% -% \noindent activates contextual ligatures (|clig|) and disables -% kerning (|kern|). -% Alternatively the options |true| or |false| can be passed to -% the feature in a key/value expression. -% The following request has the same meaning as the last one: -% -% \begin{quote} -%   \begin{verbatim} -%   \font\test=LatinModernRoman:clig=true;kern=false -%   \end{verbatim} -% \end{quote} -% -% \noindent -% Furthermore, this second syntax is required should a font feature -% accept other options besides a true/false switch. -% For example, \emphasis{stylistic alternates} (|salt|) are variants of given -% glyphs. -% They can be selected either explicitly by supplying the variant -% index (starting from one), or randomly by setting the value to, -% obviously, |random|. -% -% \iffalse TODO verify that this actually works with a font that supports -%               the salt/random feature!\fi -% \begin{quote} -%   \begin{verbatim} -%   \font\librmsaltfirst=LatinModernRoman:salt=1 -%   \end{verbatim} -% \end{quote} -% -% \noindent Other font options include: -% -% \begin{description} -% -% \item [mode] \hfill \\ -%        \identifier{luaotfload} has two \OpenType processing -%        \emphasis{modes}: -%        \identifier{base} and \identifier{node}. -% -%        \identifier{base} mode works by mapping \OpenType -%        features to traditional \TEX ligature and kerning mechanisms. -%        Supporting only non-contextual substitutions and kerning -%        pairs, it is the slightly faster, albeit somewhat limited, variant. -%        \identifier{node} mode works by processing \TeX’s internal -%        node list directly at the \LUA end and supports -%        a wider range of \OpenType features. -%        The downside is that the intricate operations required for -%        \identifier{node} mode may slow down typesetting especially -%        with complex fonts and it does not work in math mode. -% -%        By default \identifier{luaotfload} is in \identifier{node} -%        mode, and \identifier{base} mode has to be requested where needed, -%        e.~g. for math fonts. -% -% \item [script] \label{script-tag} \hfill \\ -%        An \OpenType script tag;\footnote{% -%          See \url{http://www.microsoft.com/typography/otspec/scripttags.htm} -%          for a list of valid values. -%          For scripts derived from the Latin alphabet the value -%          |latn| is good choice. -%        } -%        the default value is |dlft|. -%        Some fonts, including very popular ones by foundries like Adobe, -%        do not assign features to the |dflt| script, in -%        which case the script needs to be set explicitly. -% -% \item [language] \hfill \\ -%        An \OpenType language system identifier,\footnote{% -%          Cf. \url{http://www.microsoft.com/typography/otspec/languagetags.htm}. -%        } -%        defaulting to |dflt|. -% -% \item [featurefile] \hfill \\ -%        A comma-separated list of feature files to be applied to the -%        font. -%        Feature files contain a textual representation of -%        \OpenType tables and extend the features of a font -%        on fly. -%        After they are applied to a font, features defined in a -%        feature file can be enabled or disabled just like any -%        other font feature. -%        The syntax is documented in \identifier{Adobe}’s -%        \OpenType Feature File Specification.\footnote{% -%          Cf. \url{http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html}. -%        } -% -%        For a demonstration of how to set a |tkrn| feature consult -%        the file |tkrn.fea| that is part of \identifier{luaotfload}. -%        It can be read and applied as follows: -% -%        |\font\test=Latin Modern Roman:featurefile=tkrn.fea;+tkrn| -% -% \item [color] \hfill \\ -%        A font color, defined as a triplet of two-digit hexadecimal -%        \abbrev{rgb} values, with an optional fourth value for -%        transparency -%        (where |00| is completely transparent and |FF| is opaque). -% -%        For example, in order to set text in semitransparent red: -% -%        \begin{quote} -%           \begin{verbatim} -%  \font\test={Latin Modern Roman}:color=FF0000BB -%           \end{verbatim} -%        \end{quote} -% -% \item [kernfactor \& letterspace] \hfill \\ -%        Define a font with letterspacing (tracking) enabled. -%        In \identifier{luaotfload}, letterspacing is implemented by -%        inserting additional kerning between glyphs. -% -%        This approach is derived from and still quite similar to the -%        \emphasis{character kerning} (\texmacro{setcharacterkerning} / -%        \texmacro{definecharacterkerning} \& al.) functionality of -%        Context, see the file \fileent{typo-krn.lua} there. -%        The main difference is that \identifier{luaotfload} does not -%        use \LUATEX attributes to assign letterspacing to regions, -%        but defines virtual letterspaced versions of a font. -% -%        The option \identifier{kernfactor} accepts a numeric value that -%        determines the letterspacing factor to be applied to the font -%        size. -%        E.~g. a kern factor of $0.42$ applied to a $10$ pt font -%        results in $4.2$ pt of additional kerning applied to each -%        pair of glyphs. -%        Ligatures are split into their component glyphs unless -%        explicitly ignored (see below). -% -%        For compatibility with \XETEX an alternative -%        \identifier{letterspace} option is supplied that interprets the -%        supplied value as a \emphasis{percentage} of the font size but -%        is otherwise identical to \identifier{kernfactor}. -%        Consequently, both definitions in below snippet yield the same -%        letterspacing width: -% -%        \begin{quote} -%               \begin{verbatim} -%  \font\iwonakernedA="file:Iwona-Regular.otf:kernfactor=0.125" -%  \font\iwonakernedB="file:Iwona-Regular.otf:letterspace=12.5" -%               \end{verbatim} -%        \end{quote} -% -%        Specific pairs of letters and ligatures may be exempt from -%        letterspacing by defining the \LUA functions -%        \luafunction{keeptogether} and \luafunction{keepligature}, -%        respectively, inside the namespace \verb|luaotfload.letterspace|. -%        Both functions are called whenever the letterspacing callback -%        encounters an appropriate node or set of nodes. -%        If they return a true-ish value, no extra kern is inserted at -%        the current position. -%        \luafunction{keeptogether} receives a pair of consecutive -%        glyph nodes in order of their appearance in the node list. -%        \luafunction{keepligature} receives a single node which can be -%        analyzed into components. -%        (For details refer to the \emphasis{glyph nodes} section in the -%        \LUATEX reference manual.) -%        The implementation of both functions is left entirely to the -%        user. -% -% -% \item [protrusion \& expansion] \hfill \\ -%        These keys control microtypographic features of the font, -%        namely \emphasis{character protrusion} and \emphasis{font -%        expansion}. -%        Their arguments are names of \LUA tables that contain -%        values for the respective features.\footnote{% -%           For examples of the table layout please refer to the -%           section of the file \fileent{luaotfload-fonts-ext.lua} where the -%           default values are defined. -%           Alternatively and with loss of information, you can dump -%           those tables into your terminal by issuing -%           \begin{verbatim} -%  \directlua{inspect(fonts.protrusions.setups.default) -%             inspect(fonts.expansions.setups.default)} -%           \end{verbatim} -%           at some point after loading \fileent{luaotfload.sty}. -%        } -%        For both, only the set \identifier{default} is predefined. -% -%        For example, to define a font with the default -%        protrusion vector applied\footnote{% -%          You also need to set -%              \verb|pdfprotrudechars=2| and -%              \verb|pdfadjustspacing=2| -%          to activate protrusion and expansion, respectively. -%          See the -%          \href{http://mirrors.ctan.org/systems/pdftex/manual/pdftex-a.pdf}% -%               {\PDFTEX manual} -%          for details. -%        }: -% -%        \begin{quote} -%           \begin{verbatim} -%  \font\test=LatinModernRoman:protrusion=default -%           \end{verbatim} -%        \end{quote} -% \end{description} -% -% \paragraph{Non-standard font features} -% \identifier{luaotfload} adds a number of features that are not defined -% in the original \OpenType specification, most of them -% aiming at emulating the behavior familiar from other \TEX engines. -% Currently (2013) there are three of them: -% -% \begin{description} -% -%   \item [anum] -%           Substitutes the glyphs in the \abbrev{ascii} number range -%           with their counterparts from eastern Arabic or Persian, -%           depending on the value of \identifier{language}. -% -%   \item [tlig] -%           Applies legacy \TEX ligatures: -% -%           \begin{tabular}{rlrl} -%              ``  &  \verb|``|  &  ''  &  \verb|''|  \\ -%              `   &  \verb|`|   &  '   &  \verb|'|   \\ -%              "   &  \verb|"|   &  --  &  \verb|--|  \\ -%              --- &  \verb|---| &  !`  &  \verb|!`|  \\ -%              ?`  &  \verb|?`|  &      &             \\ -%           \end{tabular} -% -%           \footnote{% -%             These contain the feature set \verb|trep| of earlier -%             versions of \identifier{luaotfload}. -% -%             Note to \XETEX users: this is the equivalent of the -%             assignment \verb|mapping=text-tex| using \XETEX's input -%             remapping feature. -%           } -% -%   \item [itlc] -%           Computes italic correction values (active by default). -% -% \end{description} -% -% -% -% \section{Font names database} -% \label{sec:fontdb} -% -% As mentioned above, \identifier{luaotfload} keeps track of which -% fonts are available to \LUATEX by means of a \emphasis{database}. -% This allows referring to fonts not only by explicit filenames but -% also by the proper names contained in the metadata which is often -% more accessible to humans.\footnote{% -%   The tool \href{http://www.lcdf.org/type/}{\fileent{otfinfo}} (comes -%   with \TEX Live), when invoked on a font file with the \verb|-i| -%   option, lists the variety of name fields defined for it. -% } -% -% When \identifier{luaotfload} is asked to load a font by a font name, -% it will check if the database exists and load it, or else generate a -% fresh one. -% Should it then fail to locate the font, an update to the database is -% performed in case the font has been added to the system only -% recently.  As soon as the database is updated, the resolver will try -% and look up the font again, all without user intervention. -% The goal is for \identifier{luaotfload} to act in the background and -% behave as unobtrusively as possible, while providing a convenient -% interface to the fonts installed on the system. -% -% Generating the database for the first time may take a while since it -% inspects every font file on your computer. -% This is particularly noticeable if it occurs during a typesetting run. -% In any case, subsequent updates to the database will be quite fast. -% -% \subsection[luaotfload-tool / mkluatexfontdb.lua]% -%            {\fileent{luaotfload-tool} / -%             \fileent{mkluatexfontdb.lua}\footnote{% -%   The script may be named just \fileent{mkluatexfontdb} in your -%   distribution. -% }} -% -% It can still be desirable at times to do some of these steps -% manually, and without having to compile a document. -% To this end, \identifier{luaotfload} comes with the utility -% \fileent{luaotfload-tool} that offers an interface to the database -% functionality. -% Being a \LUA script, there are two ways to run it: -% either make it executable (\verb|chmod +x| on unixoid systems) or -% pass it as an argument to \fileent{texlua}.\footnote{% -%   Tests by the maintainer show only marginal performance gain by -%   running with Luigi Scarso’s -%   \href{https://foundry.supelec.fr/projects/luajittex/}% -%        {\identifier{Luajit\kern-.25ex\TEX}}, -%   which is probably due to the fact that most of the time is spent -%   on file system operations. -% -%   \emphasis{Note}: -%   On \abbrev{MS} \identifier{Windows} systems, the script can be run -%   either by calling the wrapper application -%   \fileent{luaotfload-tool.exe} or as -%   \verb|texlua.exe luaotfload-tool.lua|. -% } -% Invoked with the argument \verb|--update| it will perform a database -% update, scanning for fonts not indexed. -% -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool --update -%   \end{verbatim} -% \end{quote} -% -% Adding the \verb|--force| switch will initiate a complete -% rebuild of the database. -% -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool --update --force -%   \end{verbatim} -% \end{quote} -% -% For sake of backwards compatibility, \fileent{luaotfload-tool} may be -% renamed or symlinked to \fileent{mkluatexfontdb}. -% Whenever it is run under this name, it will update the database -% first, mimicking the behavior of earlier versions of -% \identifier{luaotfload}. -% -% \subsection{Search Paths} -% -% \identifier{luaotfload} scans those directories where fonts are -% expected to be located on a given system. -% On a Linux machine it follows the paths listed in the -% \identifier{Fontconfig} configuration files; -% consult \verb|man 5 fonts.conf| for further information. -% On \identifier{Windows} systems, the standard location is -% \verb|Windows\Fonts|, -% while \identifier{Mac OS~X} requires a multitude of paths to -% be examined. -% The complete list is is given in table \ref{table-searchpaths}. -% Other paths can be specified by setting the environment variable -% \verb+OSFONTDIR+. -% If it is non-empty, then search will be extended to the included -% directories. -% -% \begin{table}[t] -%   \hrule -%   \caption{List of paths searched for each supported operating -%            system.} -%   \renewcommand{\arraystretch}{1.2} -%   \begin{center} -%     \begin{tabular}{lp{.5\textwidth}} -%       Windows     & \verb|%WINDIR%\Fonts| -%       \\ -%       Linux       & \fileent{/usr/local/etc/fonts/fonts.conf} and\hfill\break -%                     \fileent{/etc/fonts/fonts.conf} -%       \\ -%       Mac         & \fileent{\textasciitilde/Library/Fonts},\break -%                     \fileent{/Library/Fonts},\break -%                     \fileent{/System/Library/Fonts}, and\hfill\break -%                     \fileent{/Network/Library/Fonts} -%       \\ -%     \end{tabular} -%   \end{center} -%   \label{table-searchpaths} -%   \hrule -% \end{table} -% -% \subsection{Querying from Outside} -% -% \fileent{luaotfload-tool} also provides rudimentary means of -% accessing the information collected in the font database. -% If the option \verb|--find=|\emphasis{name} is given, the script will -% try and search the fonts indexed by \identifier{luaotfload} for a -% matching name. -% For instance, the invocation -% -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool  --find="Iwona Regular" -%   \end{verbatim} -% \end{quote} -% -% \noindent -% will verify if “Iwona Regular” is found in the database and can be -% readily requested in a document. -% -% If you are unsure about the actual font name, then add the -% \verb|-F| (or \verb|--fuzzy|) switch to the command line to enable -% approximate matching. -% Suppose you cannot precisely remember if the variant of -% \identifier{Iwona} you are looking for was “Bright” or “Light”. -% The query -% -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool  -F --find="Iwona Bright" -%   \end{verbatim} -% \end{quote} -% -% \noindent -% will tell you that indeed the latter name is correct. -% -% Basic information about fonts in the database can be displayed -% using the \verb|-i| option (\verb|--info|). -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool  -i --find="Iwona Light Italic" -%   \end{verbatim} -% \end{quote} -% \noindent -% The meaning of the printed values is described in section 4.4 of the -% \LUATEX reference manual.\footnote{% -%   In \TEX Live: \fileent{texmf-dist/doc/luatex/base/luatexref-t.pdf}. -% } -% -% For a much more detailed report about a given font try the \verb|-I| option -% instead (\verb|--inspect|). -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool  -I --find="Iwona Light Italic" -%   \end{verbatim} -% \end{quote} -% -% \verb|luaotfload-tool --help| will list the available command line -% switches, including some not discussed in detail here. -% For a full documentation of \identifier{luaotfload-tool} and its -% capabilities refer to the manpage -% (\verb|man 1 luaotfload-tool|).\footnote{% -%   Or see \verb|luaotfload-tool.rst| in the source directory. -% } -% -% \subsection{Blacklisting Fonts} -% \label{font-blacklist} -% -% Some fonts are problematic in general, or just in \LUATEX. -% If you find that compiling your document takes far too long or eats -% away all your system’s memory, you can track down the culprit by -% running \verb|luaotfload-tool -v| to increase verbosity. -% Take a note of the \emphasis{filename} of the font that database -% creation fails with and append it to the file -% \fileent{luaotfload-blacklist.cnf}. -% -% A blacklist file is a list of font filenames, one per line. -% Specifying the full path to where the file is located is optional, the -% plain filename should suffice. -% File extensions (\fileent{.otf}, \fileent{.ttf}, etc.) may be omitted. -% Anything after a percent (|%|) character until the end of the line -% is ignored, so use this to add comments. -% Place this file to some location where the \identifier{kpse} -% library can find it, e.~g. -% \fileent{texmf-local/tex/luatex/luaotfload} if you are running -% \identifier{\TEX Live},\footnote{% -%   You may have to run \verb|mktexlsr| if you created a new file in -%   your \fileent{texmf} tree. -% } -% or just leave it in the working directory of your document. -% \identifier{luaotfload} reads all files named -% \fileent{luaotfload-blacklist.cnf} it finds, so the fonts in -% \fileent{./luaotfload-blacklist.cnf} extend the global blacklist. -% -% Furthermore, a filename prepended with a dash character (|-|) is -% removed from the blacklist, causing it to be temporarily whitelisted -% without modifying the global file. -% An example with explicit paths: -% -% \begin{verbatim} -% % example otf-blacklist.cnf -% /Library/Fonts/GillSans.ttc  % Luaotfload ignores this font. -% -/Library/Fonts/Optima.ttc   % This one is usable again, even if -%                              % blacklisted somewhere else. -% \end{verbatim} -% -% \section{Files from \CONTEXT and \LUATEX-Fonts} -% -% \identifier{luaotfload} relies on code originally written by Hans -% Hagen\footnote{% -%   The creator of the \href{http://wiki.contextgarden.net}{\CONTEXT} -%   format. -% } -% for and tested with \CONTEXT. -% It integrates the font loader as distributed in -% the \identifier{\LUATEX-Fonts} package. -% The original \LUA source files have been combined using the -% \fileent{mtx-package} script into a single, self-contained blob. -% In this form the font loader has no further dependencies\footnote{% -%   It covers, however, to some extent the functionality of the -%   \identifier{lualibs} package. -% } -% and requires only minor adaptions to integrate into -% \identifier{luaotfload}. -% The guiding principle is to let \CONTEXT/\LUATEX-Fonts take care of -% the implementation, and update the imported code from time to time. -% As maintainers, we aim at importing files from upstream essentially -% \emphasis{unmodified}, except for renaming them to prevent name -% clashes. -% This job has been greatly alleviated since the advent of -% \LUATEX-Fonts, prior to which the individual dependencies had to be -% manually spotted and extracted from the \CONTEXT source code in a -% complicated and error-prone fashion. -% -% Below is a commented list of the files distributed with -% \identifier{luaotfload} in one way or the other. -% See figure \ref{file-graph} on page \pageref{file-graph} for a -% graphical representation of the dependencies. -% From \LUATEX-Fonts, only the file \fileent{luatex-fonts-merged.lua} -% has been imported as \fileent{luaotfload-fontloader.lua}. -% It is generated by \fileent{mtx-package}, a \LUA source code merging -% too developed by Hans Hagen.\footnote{% -%   \fileent{mtx-package} is -%   \href -%     {http://repo.or.cz/w/context.git/blob_plain/refs/heads/origin:/scripts/context/lua/mtx-package.lua} -%     {part of \CONTEXT} -%   and requires \fileent{mtxrun}. -%   Run -%   \verb|mtxrun --script package --help| -%   to display further information. -%   For the actual merging code see the file -%   \fileent{util-mrg.lua} that is part of \CONTEXT. -% } -% It houses several \LUA files that can be classed in three -% categories. -% -%   \begin{itemize} -%     \let\normalitem=\item -%     \def\incitem#1{% -%       \normalitem{\fileent{#1}} -%     } -%     \normalitem \emphasis{\LUA utility libraries}, a subset -%                 of what is provided by the \identifier{lualibs} -%                 package. -% -%                 \begin{multicols}{2} -%                   \begin{itemize} -%                     \incitem{l-lua.lua}       \incitem{l-lpeg.lua} -%                     \incitem{l-function.lua}  \incitem{l-string.lua} -%                     \incitem{l-table.lua}     \incitem{l-io.lua} -%                     \incitem{l-file.lua}      \incitem{l-boolean.lua} -%                     \incitem{l-math.lua}      \incitem{util-str.lua} -%                   \end{itemize} -%                 \end{multicols} -% -%     \normalitem The \emphasis{font loader} itself. -%                 These files have been written for -%                 \LUATEX-Fonts and they are distributed along -%                 with \identifier{luaotfload}. -%                 \begin{multicols}{2} -%                   \begin{itemize} -%                     \incitem{luatex-basics-gen.lua} -%                     \incitem{luatex-basics-nod.lua} -%                     \incitem{luatex-fonts-enc.lua} -%                     \incitem{luatex-fonts-syn.lua} -%                     \incitem{luatex-fonts-tfm.lua} -%                     \incitem{luatex-fonts-chr.lua} -%                     \incitem{luatex-fonts-lua.lua} -%                     \incitem{luatex-fonts-def.lua} -%                     \incitem{luatex-fonts-ext.lua} -%                     \incitem{luatex-fonts-cbk.lua} -%                   \end{itemize} -%                 \end{multicols} -% -%     \normalitem Code related to \emphasis{font handling and -%                 node processing}, taken directly from -%                 \CONTEXT. -%                 \begin{multicols}{2} -%                   \begin{itemize} -%                     \incitem{data-con.lua} \incitem{font-ini.lua} -%                     \incitem{font-con.lua} \incitem{font-cid.lua} -%                     \incitem{font-map.lua} \incitem{font-oti.lua} -%                     \incitem{font-otf.lua} \incitem{font-otb.lua} -%                     \incitem{node-inj.lua} \incitem{font-ota.lua} -%                     \incitem{font-otn.lua} \incitem{font-def.lua} -%                     \incitem{font-otp.lua} -%                   \end{itemize} -%                 \end{multicols} -%   \end{itemize} -% -% Note that if \identifier{luaotfload} cannot locate the -% merged file, it will load the individual \LUA libraries -% instead. -% Their names remain the same as in \CONTEXT (without the -% \verb|otfl|-prefix) since we imported the relevant section of -% \fileent{luatex-fonts.lua} unmodified into \fileent{luaotfload.lua}. -% Thus if you prefer running bleeding edge code from the -% \CONTEXT beta, all you have to do is remove -% \fileent{luaotfload-merged.lua} from the search path. -% -% Also, the merged file at some point -% loads the Adobe Glyph List from a \LUA table that is contained in -% \fileent{luaotfload-glyphlist.lua}, which is automatically generated by the -% script \fileent{mkglyphlist}.\footnote{% -%   See \fileent{luaotfload-font-enc.lua}. -%   The hard-coded file name is why we have to replace the procedure -%   that loads the file in \fileent{luaotfload-override.lua}. -% } -% There is a make target \identifier{glyphs} that will create a fresh -% glyph list so we don’t need to import it from \CONTEXT -% any longer. -% -% In addition to these, \identifier{luaotfload} requires a number of -% files not contained in the merge. Some of these have no equivalent in -% \LUATEX-Fonts or \CONTEXT, some were taken unmodified from the -% latter. -% -% \begin{itemize} -%     \let\normalitem=\item -%     \def\ouritem#1{% -%       \normalitem{\fileent{#1}}% -%       \space--\hskip1em -%     } -%     \ouritem {luaotfload-features.lua}     font feature handling; -%                                            incorporates some of the code from -%                                            \fileent{font-otc} from \CONTEXT; -%     \ouritem {luaotfload-override.lua}     overrides the \CONTEXT logging -%                                            functionality. -%     \ouritem {luaotfload-loaders.lua}      registers the \OpenType -%                                            font reader as handler for -%                                            Postscript fonts -%                                            (\abbrev{pfa}, \abbrev{pfb}). -%     \ouritem {luaotfload-database.lua}     font names database. -%     \ouritem {luaotfload-colors.lua}       color handling. -%     \ouritem {luaotfload-auxiliary.lua}    access to internal functionality -%                                            for package authors -%                                            (proposals for additions welcome). -%     \ouritem {luaotfload-letterspace.lua}  font-based letterspacing. -% \end{itemize} -% -% \begin{figure}[b] -%   \caption{Schematic of the files in \identifier{Luaotfload}} -%   \includegraphics[width=\textwidth]{filegraph.pdf} -%   \label{file-graph} -% \end{figure} -% -% \section{Auxiliary Functions} -% -% With release version 2.2, \identifier{luaotfload} received -% additional functions for package authors to call from outside -% (see the file \fileent{luaotfload-auxiliary.lua} for details). -% The purpose of this addition twofold. -% Firstly, \identifier{luaotfload} failed to provide a stable interface -% to internals in the past which resulted in an unmanageable situation -% of different packages abusing the raw access to font objects by means -% of the \luafunction{patch_font} callback. -% When the structure of the font object changed due to an update, all -% of these imploded and several packages had to be fixed while -% simultaneously providing fallbacks for earlier versions. -% Now the patching is done on the \identifier{luaotfload} side and can -% be adapted with future modifications to font objects without touching -% the packages that depend on it. -% Second, some the capabilities of the font loader and the names -% database are not immediately relevant in \identifier{luaotfload} -% itself but might nevertheless be of great value to package authors or -% end users. -% -% Note that the current interface is not yet set in stone and the -% development team is open to suggestions for improvements or -% additions. -% -% \subsection{Callback Functions} -% -% The \luafunction{patch_font} callback is inserted in the wrapper -% \identifier{luaotfload} provides for the font definition callback -% (see below, page \pageref{define-font}). -% At this place it allows manipulating the font object immediately after -% the font loader is done creating it. -% For a short demonstration of its usefulness, here is a snippet that -% writes an entire font object to the file \fileent{fontdump.lua}: -% -% \begin{quote} -%   \begin{verbatim} -%   \input luaotfload.sty -%   \directlua{ -%     local dumpfile    = "fontdump.lua" -%     local dump_font   = function (tfmdata) -%       local data = table.serialize(tfmdata) -%       io.savedata(dumpfile, data) -%     end -% -%     luatexbase.add_to_callback( -%       "luaotfload.patch_font", -%       dump_font, -%       "my_private_callbacks.dump_font" -%     ) -%   } -%   \font\dumpme=name:Iwona -%   \bye -%   \end{verbatim} -% \end{quote} -% -% \emphasis{Beware}: this creates a Lua file of around 150,000 lines of -% code, taking up 3~\abbrev{mb} of disk space. -% By inspecting the output you can get a first impression of how a font -% is structured in \LUATEX’s memory, what elements it is composed of, -% and in what ways it can be rearranged. -% -% \subsubsection{Compatibility with Earlier Versions} -% -% As has been touched on in the preface to this section, the structure -% of the object as returned by the fontloader underwent rather drastic -% changes during different stages of its development, and not all -% packages that made use of font patching have kept up with every one -% of it. -% To ensure compatibility with these as well as older versions of -% some packages, \identifier{luaotfload} sets up copies of or references -% to data in the font table where it used to be located. -% For instance, important parameters like the requested point size, the -% units factor, and the font name have again been made accessible from -% the toplevel of the table even though they were migrated to different -% subtables in the meantime. -% -% \subsubsection{Patches} -% -% These are mostly concerned with establishing compatibility with -% \XETEX. -% -% \begin{itemize} -%   \let\normalitem=\item -%   \def\ouritem#1{% -%     \normalitem{\luafunction{#1}}% -%     \hfill\break -%   } -% -%   \ouritem  {set_sscale_dimens} -%             Calculate \texmacro{fontdimen}s 10 and 11 to emulate \XETEX. -% -%   \ouritem  {set_capheight} -%             Calculates \texmacro{fontdimen} 8 like \XETEX. -% -%   \ouritem  {patch_cambria_domh} -%             Correct some values of the font \emphasis{Cambria Math}. -% -% \end{itemize} -% -% \subsection{Package Author’s Interface} -% -% As \LUATEX release 1.0 is nearing, the demand for a reliable interface -% for package authors increases. -% -% \subsubsection{Font Properties} -% -% Below functions mostly concern querying the different components of a -% font like for instance the glyphs it contains, or what font features -% are defined for which scripts. -% -% \begin{itemize} -%   \let\normalitem=\item -%   \def\ouritem#1{% -%     \normalitem{\luafunction{#1}}% -%     \hfill\break -%   } -% -%   \ouritem  {aux.font_has_glyph (id : int, index : int)} -%             Predicate that returns true if the font \luafunction{id} -%             has glyph \luafunction{index}. -% -%   \ouritem  {aux.slot_of_name(name : string)} -%             Translates an Adobe Glyph name to the corresponding glyph -%             slot. -% -%   \ouritem  {aux.name_of_slot(slot : int)} -%             The inverse of \luafunction{slot_of_name}; note that this -%             might be incomplete as multiple glyph names may map to the -%             same codepoint, only one of which is returned by -%             \luafunction{name_of_slot}. -% -%   \ouritem  {aux.provides_script(id : int, script : string)} -%             Test if a font supports \luafunction{script}. -% -%   \ouritem  {aux.provides_language(id : int, script : string, language : string)} -%             Test if a font defines \luafunction{language} for a given -%             \luafunction{script}. -% -%   \ouritem  {aux.provides_feature(id : int, script : string, -%              language : string, feature : string)} -%             Test if a font defines \luafunction{feature} for -%             \luafunction{language} for a given \luafunction{script}. -% -%   \ouritem  {aux.get_math_dimension(id : int, dimension : string)} -%             Get the dimension \luafunction{dimension} of font \luafunction{id}. -% -%   \ouritem  {aux.sprint_math_dimension(id : int, dimension : string)} -%             Same as \luafunction{get_math_dimension()}, but output the value -%             in scaled points at the \TEX end. -% -% \end{itemize} -% -% \subsubsection{Database} -% -% \begin{itemize} -%   \let\normalitem=\item -%   \def\ouritem#1{% -%     \normalitem{\luafunction{#1}}% -%     \hfill\break -%   } -% -%   \ouritem  {aux.scan_external_dir(dir : string)} -%             Include fonts in directory \luafunction{dir} in font lookups without -%             adding them to the database. -% -% \end{itemize} -% -% \section{Troubleshooting} -% -% \subsection {Database Generation} -% If you encounter problems with some fonts, please first update to the latest -% version of this package before reporting a bug, as -% \identifier{luaotfload} is under active development and still a -% moving target. -% The development takes place on \identifier{github} at -% \url{https://github.com/lualatex/luaotfload} where there is an issue -% tracker for submitting bug reports, feature requests and the likes -% requests and the likes. -% -% Bug reports are more likely to be addressed if they contain the output of -% -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool --diagnose=environment,files,permissions -%   \end{verbatim} -% \end{quote} -% -% \noindent Consult the man page for a description of these options. -% -% Errors during database generation can be traced by increasing the -% verbosity level and redirecting log output to \fileent{stdout}: -% -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool -fuvvv --log=stdout -%   \end{verbatim} -% \end{quote} -% -% \noindent or to a file in \fileent{/tmp}: -% -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool -fuvvv --log=file -%   \end{verbatim} -% \end{quote} -% -% \noindent In the latter case, invoke the \verb|tail(1)| utility on the file -% for live monitoring of the progress. -% -% If database generation fails, the font last printed to the terminal or log -% file is likely to be the culprit. -% Please specify it when reporting a bug, and blacklist it for the time -% being (see above, page \pageref{font-blacklist}). -% -% \subsection {Font Features} -% A common problem is the lack of features for some -% \OpenType fonts even when specified. -% This can be related to the fact that some fonts do not provide -% features for the \verb|dflt| script (see above on page -% \pageref{script-tag}), -% which is the default one in this package. -% If this happens, assigning a noth script when the font is defined should -% fix it. -% For example with \verb|latn|: -% -% \begin{quote} -%   \begin{verbatim} -%     \font\test=file:MyFont.otf:script=latn;+liga; -%   \end{verbatim} -% \end{quote} -% -% You can get a list of features that a font defines for scripts and languages -% by querying it in \fileent{luaotfload-tool}: -% -% \begin{quote} -%   \begin{verbatim} -%     luaotfload-tool --find="Iwona" --inspect -%   \end{verbatim} -% \end{quote} -% -% \subsection {\LUATEX Programming} -% Another strategy that helps avoiding problems is to not access raw \LUATEX -% internals directly. -% Some of them, even though they are dangerous to access, have not been -% overridden or disabled. -% Thus, whenever possible prefer the functions in the -% \luafunction{aux} namespace over direct manipulation of font objects. -% For example, raw access to the \luafunction{font.fonts} table like: -% -% \begin{quote} -%   \begin{verbatim} -%     local somefont = font.fonts[2] -%   \end{verbatim} -% \end{quote} -% -% \noindent can render already defined fonts unusable. -% Instead, the function \luafunction{font.getfont()} should be used because -% it has been replaced by a safe variant. -% -% However, \luafunction{font.getfont()} only covers fonts handled by the font -% loader, e.~g. \identifier{OpenType} and \identifier{TrueType} fonts, but -% not \abbrev{tfm} or \abbrev{ofm}. -% Should you absolutely require access to all fonts known to \LUATEX, including -% the virtual and autogenerated ones, then you need to query both -% \luafunction{font.getfont()} and \luafunction{font.fonts}. -% In this case, best define you own accessor: -% -% \begin{quote} -%   \begin{verbatim} -%     local unsafe_getfont = function (id) -%         local tfmdata = font.getfont (id) -%         if not tfmdata then -%             tfmdata = font.fonts[id] -%         end -%         return tfmdata -%     end -% -%     --- use like getfont() -%     local somefont = unsafe_getfont (2) -%   \end{verbatim} -% \end{quote} -% -% \part{Implementation} -% -% \section{\fileent{luaotfload.lua}} -% -% This file initializes the system and loads the font loader. -% To minimize potential conflicts between other packages and the -% code imported from \CONTEXT, several precautions are in order. -% Some of the functionality that the font loader expects to be present, -% like raw access to callbacks, are assumed to have been disabled by -% \identifier{luatexbase} when this file is processed. -% In some cases it is possible to trick it by putting dummies into -% place and restoring the behavior from \identifier{luatexbase} after -% initilization. -% Other cases such as attribute allocation require that we hook the -% functionality from \identifier{luatexbase} into locations where they -% normally wouldn’t be. -% -% Anyways we can import the code base without modifications, which is -% due mostly to the extra effort by -% Hans Hagen to make \LUATEX-Fonts self-contained and encapsulate it, -% and especially due to his willingness to incorporate our suggestions. -% -% \iffalse -%<*lua> -% \fi -%    \begin{macrocode} -luaotfload                        = luaotfload or {} -local luaotfload                  = luaotfload - -config                            = config or { } -config.luaotfload                 = config.luaotfload or { } -------.luaotfload.resolver        = config.luaotfload.resolver         or "normal" -config.luaotfload.resolver        = config.luaotfload.resolver         or "cached" -config.luaotfload.definer         = config.luaotfload.definer          or "patch" -config.luaotfload.compatibility   = config.luaotfload.compatibility    or false -config.luaotfload.loglevel        = config.luaotfload.loglevel         or 2 -config.luaotfload.color_callback  = config.luaotfload.color_callback   or "pre_linebreak_filter" -config.luaotfload.prioritize      = config.luaotfload.prioritize       or "sys" -config.luaotfload.names_dir       = config.luaotfload.names_dir        or "names" -config.luaotfload.cache_dir       = config.luaotfload.cache_dir        or "fonts" -config.luaotfload.index_file      = config.luaotfload.index_file       or "luaotfload-names.lua" -config.luaotfload.formats         = config.luaotfload.formats          or "otf,ttf,ttc,dfont" -if not config.luaotfload.strip then -    config.luaotfload.strip = true -end - -luaotfload.module = { -    name          = "luaotfload", -    version       = 2.40005, --- 2.4-4 -    date          = "2014/05/18", -    description   = "OpenType layout system.", -    author        = "Elie Roux & Hans Hagen", -    copyright     = "Elie Roux", -    license       = "GPL v2.0" -} - -local luatexbase       = luatexbase - -local setmetatable     = setmetatable -local type, next       = type, next - -local kpsefind_file    = kpse.find_file -local lfsisfile        = lfs.isfile - -local add_to_callback, create_callback = -      luatexbase.add_to_callback, luatexbase.create_callback -local reset_callback, call_callback = -      luatexbase.reset_callback, luatexbase.call_callback - -local dummy_function = function () end - -local error, warning, info, log = -    luatexbase.provides_module(luaotfload.module) - -luaotfload.error        = error -luaotfload.warning      = warning -luaotfload.info         = info -luaotfload.log          = log - -%    \end{macrocode} -%    We set the minimum version requirement for \LUATEX to v0.76, -%    because the font loader requires recent features like direct -%    attribute indexing and \luafunction{node.end_of_math()} that aren’t -%    available in earlier versions.\footnote{% -%     See Taco’s announcement of v0.76: -%     \url{http://comments.gmane.org/gmane.comp.tex.luatex.user/4042} -%     and this commit by Hans that introduced those features. -%     \url{http://repo.or.cz/w/context.git/commitdiff/a51f6cf6ee087046a2ae5927ed4edff0a1acec1b}. -%   } -% -%    \begin{macrocode} - -local luatex_version = 76 - -if tex.luatexversion < luatex_version then -    warning("LuaTeX v%.2f is old, v%.2f is recommended.", -             tex.luatexversion/100, -             luatex_version   /100) -    --- we install a fallback for older versions as a safety -    if not node.end_of_math then -        local math_t          = node.id"math" -        local traverse_nodes  = node.traverse_id -        node.end_of_math = function (n) -            for n in traverse_nodes(math_t, n.next) do -                return n -            end -        end -    end -end - -%    \end{macrocode} -% \subsection{Module loading} -% We load the files imported from \CONTEXT with this function. -% It automatically prepends the prefix \fileent{luaotfload-} to its -% argument, so we can refer to the files with their actual \CONTEXT name. -% -%    \begin{macrocode} - -local fl_prefix = "luaotfload" -- “luatex” for luatex-plain -local loadmodule = function (name) -    require(fl_prefix .."-"..name) -end - -%    \end{macrocode} -% Before \TeX Live 2013 version, \LUATEX had a bug that made ofm fonts fail -% when called with their extension. There was a side-effect making ofm -% totally unloadable when luaotfload was present. The following lines are -% a patch for this bug. The utility of these lines is questionable as they -% are not necessary since \TeX Live 2013. They should be removed in the next -% version. -% -%    \begin{macrocode} -local Cs, P, lpegmatch = lpeg.Cs, lpeg.P, lpeg.match - -local p_dot, p_slash = P".",  P"/" -local p_suffix       = (p_dot * (1 - p_dot - p_slash)^1 * P(-1)) / "" -local p_removesuffix = Cs((p_suffix + 1)^1) - -local find_vf_file = function (name) -    local fullname = kpsefind_file(name, "ovf") -    if not fullname then -        --fullname = kpsefind_file(file.removesuffix(name), "ovf") -        fullname = kpsefind_file(lpegmatch(p_removesuffix, name), "ovf") -    end -    if fullname then -        log("loading virtual font file %s.", fullname) -    end -    return fullname -end - -%    \end{macrocode} -% \subsection{Preparing the Font Loader} -% We treat the fontloader as a black box so behavior is consistent -% between formats. -% We do no longer run the intermediate wrapper file -% \fileent{luaotfload-fonts.lua} which we used to import from -% \href{http://standalone.contextgarden.net/current/context/experimental/tex/generic/context/luatex/}{\LUATEX-Plain}. -% Rather, we load the fontloader code directly in the same fashion as -% \identifier{luatex-fonts}. -% How this is executed depends on the presence on the \emphasis{merged -% font loader code}. -% In \identifier{luaotfload} this is contained in the file -% \fileent{luaotfload-merged.lua}. -% If this file cannot be found, the original libraries from \CONTEXT of -% which the merged code was composed are loaded instead. -% The imported font loader will call \luafunction{callback.register} once -% while reading \fileent{font-def.lua}. -% This is unavoidable unless we modify the imported files, but harmless -% if we make it call a dummy instead. -% However, this problem might vanish if we decide to do the merging -% ourselves, like the \identifier{lualibs} package does. -% With this step we would obtain the freedom to load our own overrides in -% the process right where they are needed, at the cost of losing -% encapsulation. -% The decision on how to progress is currently on indefinite hold. -% -%    \begin{macrocode} - -local starttime = os.gettimeofday() - -local trapped_register  = callback.register -callback.register       = dummy_function - -%    \end{macrocode} -% By default, the fontloader requires a number of \emphasis{private -% attributes} for internal use. -% These must be kept consistent with the attribute handling methods as -% provided by \identifier{luatexbase}. -% Our strategy is to override the function that allocates new attributes -% before we initialize the font loader, making it a wrapper around -% \luafunction{luatexbase.new_attribute}.\footnote{% -%     Many thanks, again, to Hans Hagen for making this part -%     configurable! -% } -% The attribute identifiers are prefixed “\fileent{luaotfload@}” to -% avoid name clashes. -% -%    \begin{macrocode} - -do -    local new_attribute    = luatexbase.new_attribute -    local the_attributes   = luatexbase.attributes - -    attributes = attributes or { } - -    attributes.private = function (name) -        local attr   = "luaotfload@" .. name --- used to be: “otfl@” -        local number = the_attributes[attr] -        if not number then -            number = new_attribute(attr) -        end -        return number -    end -end - -%    \end{macrocode} -% These next lines replicate the behavior of \fileent{luatex-fonts.lua}. -% -%    \begin{macrocode} - -local context_environment = { } - -local push_namespaces = function () -    log("push namespace for font loader") -    local normalglobal = { } -    for k, v in next, _G do -        normalglobal[k] = v -    end -    return normalglobal -end - -local pop_namespaces = function (normalglobal, isolate) -    if normalglobal then -        local _G = _G -        local mode = "non-destructive" -        if isolate then mode = "destructive" end -        log("pop namespace from font loader -- " .. mode) -        for k, v in next, _G do -            if not normalglobal[k] then -                context_environment[k] = v -                if isolate then -                    _G[k] = nil -                end -            end -        end -        for k, v in next, normalglobal do -            _G[k] = v -        end -        -- just to be sure: -        setmetatable(context_environment,_G) -    else -        log("irrecoverable error during pop_namespace: no globals to restore") -        os.exit() -    end -end - -luaotfload.context_environment  = context_environment -luaotfload.push_namespaces      = push_namespaces -luaotfload.pop_namespaces       = pop_namespaces - -local our_environment = push_namespaces() - -%    \end{macrocode} -% The font loader requires that the attribute with index zero be zero. -% We happily oblige. -% (Cf. \fileent{luatex-fonts-nod.lua}.) -% -%    \begin{macrocode} - -tex.attribute[0] = 0 - -%    \end{macrocode} -% Now that things are sorted out we can finally load the fontloader. -% -%    \begin{macrocode} - -loadmodule"fontloader.lua" ----loadmodule"font-odv.lua" --- <= Devanagari support from Context - -if fonts then - -    if not fonts._merge_loaded_message_done_ then -        log [["I am using the merged version of 'luaotfload.lua' here.]] -        log [[ If you run into problems or experience unexpected]] -        log [[ behaviour, and if you have ConTeXt installed you can try]] -        log [[ to delete the file 'luaotfload-merged.lua' as I might]] -        log [[ then use the possibly updated libraries. The merged]] -        log [[ version is not supported as it is a frozen instance.]] -        log [[ Problems can be reported to the ConTeXt mailing list."]] -    end -    fonts._merge_loaded_message_done_ = true - -else--- the loading sequence is known to change, so this might have to -    --- be updated with future updates! -    --- do not modify it though unless there is a change to the merged -    --- package! -    loadmodule("l-lua.lua") -    loadmodule("l-lpeg.lua") -    loadmodule("l-function.lua") -    loadmodule("l-string.lua") -    loadmodule("l-table.lua") -    loadmodule("l-io.lua") -    loadmodule("l-file.lua") -    loadmodule("l-boolean.lua") -    loadmodule("l-math.lua") -    loadmodule("util-str.lua") -    loadmodule('luatex-basics-gen.lua') -    loadmodule('data-con.lua') -    loadmodule('luatex-basics-nod.lua') -    loadmodule('font-ini.lua') -    loadmodule('font-con.lua') -    loadmodule('luatex-fonts-enc.lua') -    loadmodule('font-cid.lua') -    loadmodule('font-map.lua') -    loadmodule('luatex-fonts-syn.lua') -    loadmodule('luatex-fonts-tfm.lua') -    loadmodule('font-oti.lua') -    loadmodule('font-otf.lua') -    loadmodule('font-otb.lua') -    loadmodule('node-inj.lua') -    loadmodule('font-ota.lua') -    loadmodule('font-otn.lua') -    loadmodule('font-otp.lua')--- since 2013-04-23 -    loadmodule('luatex-fonts-lua.lua') -    loadmodule('font-def.lua') -    loadmodule('luatex-fonts-def.lua') -    loadmodule('luatex-fonts-ext.lua') -    loadmodule('luatex-fonts-cbk.lua') -end --- non-merge fallback scope - -%    \end{macrocode} -% Here we adjust the globals created during font loader initialization. -% If the second argument to \luafunction{pop_namespaces()} is \verb|true| -% this will restore the state of \luafunction{_G}, eliminating every -% global generated since the last call to \luafunction{push_namespaces()}. -% At the moment we see no reason to do this, and since the font loader is -% considered an essential part of \identifier{luatex} as well as a very -% well organized piece of code, we happily concede it the right to add to -% \luafunction{_G} if needed. -% -%    \begin{macrocode} - -pop_namespaces(our_environment, false)-- true) - -log("fontloader loaded in %0.3f seconds", os.gettimeofday()-starttime) - -%    \end{macrocode} -% \subsection{Callbacks} -% After the fontloader is ready we can restore the callback trap from -% \identifier{luatexbase}. -% -%    \begin{macrocode} - -callback.register = trapped_register - -%    \end{macrocode} -% We do our own callback handling with the means provided by luatexbase. -% Note: \luafunction{pre_linebreak_filter} and \luafunction{hpack_filter} -% are coupled in \CONTEXT in the concept of \emphasis{node processor}. -% -%    \begin{macrocode} - -add_to_callback("pre_linebreak_filter", -                nodes.simple_font_handler, -                "luaotfload.node_processor", -                1) -add_to_callback("hpack_filter", -                nodes.simple_font_handler, -                "luaotfload.node_processor", -                1) -add_to_callback("find_vf_file", -                find_vf_file, "luaotfload.find_vf_file") - -loadmodule"override.lua"   --- “luat-ovr” - -logs.set_loglevel(config.luaotfload.loglevel) - -%    \end{macrocode} -% Now we load the modules written for \identifier{luaotfload}. -% -%    \begin{macrocode} -loadmodule"loaders.lua"    --- “font-pfb” new in 2.0, added 2011 -loadmodule"database.lua"   --- “font-nms” -loadmodule"colors.lua"     --- “font-clr” - -%    \end{macrocode} -% Relying on the \verb|name:| resolver for everything has been the source -% of permanent trouble with the database. -% With the introduction of the new syntax parser we now have enough -% granularity to distinguish between the \XETEX emulation layer and the -% genuine \verb|name:| and \verb|file:| lookups of \LUATEX-Fonts. -% Another benefit is that we can now easily plug in or replace new lookup -% behaviors if necessary. -% The name resolver remains untouched, but it calls -% \luafunction{fonts.names.resolve()} internally anyways (see -% \fileent{luaotfload-database.lua}). -% -%    \begin{macrocode} - -local filesuffix          = file.suffix -local fileremovesuffix    = file.removesuffix -local request_resolvers   = fonts.definers.resolvers -local formats             = fonts.formats -local names               = fonts.names -formats.ofm               = "type1" - -fonts.encodings.known     = fonts.encodings.known or { } - -%    \end{macrocode} -% \identifier{luaotfload} promises easy access to system fonts. -% Without additional precautions, this cannot be achieved by -% \identifier{kpathsea} alone, because it searches only the -% \fileent{texmf} directories by default. -% Although it is possible for \identifier{kpathsea} to include extra -% paths by adding them to the \verb|OSFONTDIR| environment variable, -% this is still short of the goal »\emphasis{it just works!}«. -% When building the font database \identifier{luaotfload} scans -% system font directories anyways, so we already have all the -% information for looking sytem fonts. -% With the release version 2.2 the file names are indexed in the database -% as well and we are ready to resolve \verb|file:| lookups this way. -% Thus we no longer need to call the \identifier{kpathsea} library in -% most cases when looking up font files, only when generating the database, -% and when verifying the existence of a file in the \fileent{texmf} tree. -% -%    \begin{macrocode} - -local resolve_file        = names.crude_file_lookup ---local resolve_file        = names.crude_file_lookup_verbose -local resolve_name        = names.resolve_name - -local file_resolver = function (specification) -    local name    = resolve_file (specification.name) -    local suffix  = filesuffix(name) -    if formats[suffix] then -        specification.forced      = string.lower (suffix) -        specification.forcedname  = file.removesuffix(name) -    else -        specification.name = name -    end -end - -request_resolvers.file = file_resolver - -%    \end{macrocode} -% We classify as \verb|anon:| those requests that have neither a -% prefix nor brackets. According to Khaled\footnote{% -%     \url{https://github.com/phi-gamma/luaotfload/issues/4#issuecomment-17090553}. -% } -% they are the \XETEX equivalent of a \verb|name:| request, so we will be -% treating them as such. -% -%    \begin{macrocode} - ---request_resolvers.anon = request_resolvers.name - -%    \end{macrocode} -% There is one drawback, though. -% This syntax is also used for requesting fonts in \identifier{Type1} -% (\abbrev{tfm}, \abbrev{ofm}) format. -% These are essentially \verb|file:| lookups and must be caught before -% the \verb|name:| resolver kicks in, lest they cause the database to -% update. -% Even if we were to require the \verb|file:| prefix for all -% \identifier{Type1} requests, tests have shown that certain fonts still -% include further fonts (e.~g. \fileent{omlgcb.ofm} will ask for -% \fileent{omsecob.tfm}) \emphasis{using the old syntax}. -% For this reason, we introduce an extra check with an early return. -% -%    \begin{macrocode} - -local type1_formats = { "tfm", "ofm", } - -request_resolvers.anon = function (specification) -    local name = specification.name -    for i=1, #type1_formats do -        local format = type1_formats[i] -        if resolvers.findfile(name, format) then -            specification.forcedname = file.addsuffix(name, format) -            specification.forced     = format -            return -        end -    end -    --- under some weird circumstances absolute paths get -    --- passed to the definer; we have to catch them -    --- before the name: resolver misinterprets them. -    name = specification.specification -    local exists, _ = lfsisfile(name) -    if exists then --- garbage; we do this because we are nice, -                   --- not because it is correct -        logs.names_report("log", 1, "load", "file %q exists", name) -        logs.names_report("log", 1, "load", -          "... overriding borked anon: lookup with path: lookup") -        specification.name = name -        request_resolvers.path(specification) -        return -    end -    request_resolvers.name(specification) -end - -%    \end{macrocode} -% Prior to version 2.2, \identifier{luaotfload} did not distinguish -% \verb|file:| and \verb|path:| lookups, causing complications with the -% resolver. -% Now we test if the requested name is an absolute path in the file -% system, otherwise we fall back to the \verb|file:| lookup. -% -%    \begin{macrocode} - -request_resolvers.path = function (specification) -    local name       = specification.name -    local exists, _  = lfsisfile(name) -    if not exists then -- resort to file: lookup -        logs.names_report("log", 1, "load", -          "path lookup of %q unsuccessful, falling back to file:", -          name) -        file_resolver (specification) -    else -        local suffix = filesuffix (name) -        if formats[suffix] then -            specification.forced      = string.lower (suffix) -            specification.name        = file.removesuffix(name) -            specification.forcedname  = name -        else -            specification.name = name -        end -    end -end - -%    \end{macrocode} -% {\bfseries EXPERIMENTAL}: -% \identifier{kpse}-only resolver, for those who can do without system -% fonts. -% -%    \begin{macrocode} - -request_resolvers.kpse = function (specification) -    local name       = specification.name -    local suffix     = filesuffix(name) -    if suffix and formats[suffix] then -        name = file.removesuffix(name) -        if resolvers.findfile(name, suffix) then -            specification.forced       = string.lower (suffix) -            specification.forcedname   = name -            return -        end -    end -    for t, format in next, formats do --- brute force -        if kpse.find_file (name, format) then -            specification.forced = t -            specification.name   = name -            return -        end -    end -end - -%    \end{macrocode} -% The \verb|name:| resolver wraps the database function -% \luafunction{resolve_name}. -% -%    \begin{macrocode} - ---- fonts.names.resolvers.name -- Customized version of the ---- generic name resolver. - -request_resolvers.name = function (specification) -    local resolved, subfont = resolve_name (specification) -    if resolved then -        specification.resolved   = resolved -        specification.sub        = subfont -        specification.forced     = string.lower (filesuffix (resolved) or "") -        specification.forcedname = resolved -        specification.name       = fileremovesuffix (resolved) -    else -        file_resolver (specification) -    end -end - -%    \end{macrocode} -% Also {\bfseries EXPERIMENTAL}: -% custom file resolvers via callback. -% -%    \begin{macrocode} -create_callback("luaotfload.resolve_font", "simple", dummy_function) - -request_resolvers.my = function (specification) -    call_callback("luaotfload.resolve_font", specification) -end - -%    \end{macrocode} -% We create a callback for patching fonts on the fly, to be used by other -% packages. -% It initially contains the empty function that we are going to override -% below. -% -%    \begin{macrocode} - -create_callback("luaotfload.patch_font", "simple", dummy_function) - -%    \end{macrocode} -% \subsection{\CONTEXT override} -% \label{define-font} -% We provide a simplified version of the original font definition -% callback. -% -%    \begin{macrocode} - -local read_font_file = fonts.definers.read - ---- spec -> size -> id -> tmfdata -local patch_defined_font = function (specification, size, id) -    local tfmdata = read_font_file(specification, size, id) -    if type(tfmdata) == "table" and tfmdata.shared then -        --- We need to test for the “shared” field here -        --- or else the fontspec capheight callback will -        --- operate on tfm fonts. -        call_callback("luaotfload.patch_font", tfmdata, specification) -    end -    return tfmdata -end - -reset_callback "define_font" - -%    \end{macrocode} -% Finally we register the callbacks. -% -%    \begin{macrocode} - -local font_definer = config.luaotfload.definer - -if font_definer == "generic"  then -  add_to_callback("define_font", -                  fonts.definers.read, -                  "luaotfload.define_font", -                  1) -elseif font_definer == "patch"  then -  add_to_callback("define_font", -                  patch_defined_font, -                  "luaotfload.define_font", -                  1) -end - -loadmodule"features.lua"        --- contains what was “font-ltx” and “font-otc” -loadmodule"letterspace.lua"     --- extra character kerning -loadmodule"auxiliary.lua"       --- additionaly high-level functionality (new) - -luaotfload.aux.start_rewrite_fontname () --- to be migrated to fontspec - --- vim:tw=71:sw=4:ts=4:expandtab - -%    \end{macrocode} -% -% \iffalse -%</lua> -% \fi -% -% \section{\fileent{luaotfload.sty}} -% -% \iffalse -%<*package> -% \fi -% -%    Classical Plain+\LATEX package initialization. -% -%    \begin{macrocode} -\csname ifluaotfloadloaded\endcsname -\let\ifluaotfloadloaded\endinput -\bgroup\expandafter\expandafter\expandafter\egroup -\expandafter\ifx\csname ProvidesPackage\endcsname\relax -  \input luatexbase.sty -\else -  \NeedsTeXFormat{LaTeX2e} -  \ProvidesPackage{luaotfload}% -    [2014/05/18 v2.4-4 OpenType layout system] -  \RequirePackage{luatexbase} -\fi -\ifnum\luatexversion<76 -  %% here some deprecation warning would be in order -  \RequireLuaModule{lualibs} -  \RequireLuaModule{luaotfload-legacy} -\else -  \RequireLuaModule{luaotfload} -\fi -\endinput -%    \end{macrocode} -% \iffalse -%</package> -% \fi -% -% \clearpage -% \section{The GNU GPL License v2} -% -% The GPL requires the complete license text to be distributed along -% with the code. I recommend the canonical source, instead: -% \url{http://www.gnu.org/licenses/old-licenses/gpl-2.0.html}. -% But if you insist on an included copy, here it is. -% You might want to zoom in. -% -% \newsavebox{\gpl} -% \begin{lrbox}{\gpl} -% \begin{minipage}{3\textwidth} -% \columnsep=3\columnsep -% \begin{multicols}{3} -% \begin{center} -% {\Large GNU GENERAL PUBLIC LICENSE\par} -% \bigskip -% {Version 2, June 1991} -% \end{center} -% -% \begin{center} -% {\parindent 0in -% -% Copyright \textcopyright\ 1989, 1991 Free Software Foundation, Inc. -% -% \bigskip -% -% 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA -% -% \bigskip -% -% Everyone is permitted to copy and distribute verbatim copies -% of this license document, but changing it is not allowed. -% } -% \end{center} -% -% \begin{center} -% {\bf\large Preamble} -% \end{center} -% -% -% The licenses for most software are designed to take away your freedom to -% share and change it.  By contrast, the GNU General Public License is -% intended to guarantee your freedom to share and change free software---to -% make sure the software is free for all its users.  This General Public -% License applies to most of the Free Software Foundation's software and to -% any other program whose authors commit to using it.  (Some other Free -% Software Foundation software is covered by the GNU Library General Public -% License instead.)  You can apply it to your programs, too. -% -% When we speak of free software, we are referring to freedom, not price. -% Our General Public Licenses are designed to make sure that you have the -% freedom to distribute copies of free software (and charge for this service -% if you wish), that you receive source code or can get it if you want it, -% that you can change the software or use pieces of it in new free programs; -% and that you know you can do these things. -% -% To protect your rights, we need to make restrictions that forbid anyone to -% deny you these rights or to ask you to surrender the rights.  These -% restrictions translate to certain responsibilities for you if you -% distribute copies of the software, or if you modify it. -% -% For example, if you distribute copies of such a program, whether gratis or -% for a fee, you must give the recipients all the rights that you have.  You -% must make sure that they, too, receive or can get the source code.  And -% you must show them these terms so they know their rights. -% -% We protect your rights with two steps: (1) copyright the software, and (2) -% offer you this license which gives you legal permission to copy, -% distribute and/or modify the software. -% -% Also, for each author's protection and ours, we want to make certain that -% everyone understands that there is no warranty for this free software.  If -% the software is modified by someone else and passed on, we want its -% recipients to know that what they have is not the original, so that any -% problems introduced by others will not reflect on the original authors' -% reputations. -% -% Finally, any free program is threatened constantly by software patents. -% We wish to avoid the danger that redistributors of a free program will -% individually obtain patent licenses, in effect making the program -% proprietary.  To prevent this, we have made it clear that any patent must -% be licensed for everyone's free use or not licensed at all. -% -% The precise terms and conditions for copying, distribution and -% modification follow. -% -% \begin{center} -% {\Large \sc Terms and Conditions For Copying, Distribution and -%   Modification} -% \end{center} -% -% \begin{enumerate} -% \item -% This License applies to any program or other work which contains a notice -% placed by the copyright holder saying it may be distributed under the -% terms of this General Public License.  The ``Program'', below, refers to -% any such program or work, and a ``work based on the Program'' means either -% the Program or any derivative work under copyright law: that is to say, a -% work containing the Program or a portion of it, either verbatim or with -% modifications and/or translated into another language.  (Hereinafter, -% translation is included without limitation in the term ``modification''.) -% Each licensee is addressed as ``you''. -% -% Activities other than copying, distribution and modification are not -% covered by this License; they are outside its scope.  The act of -% running the Program is not restricted, and the output from the Program -% is covered only if its contents constitute a work based on the -% Program (independent of having been made by running the Program). -% Whether that is true depends on what the Program does. -% -% \item You may copy and distribute verbatim copies of the Program's source -%   code as you receive it, in any medium, provided that you conspicuously -%   and appropriately publish on each copy an appropriate copyright notice -%   and disclaimer of warranty; keep intact all the notices that refer to -%   this License and to the absence of any warranty; and give any other -%   recipients of the Program a copy of this License along with the Program. -% -% You may charge a fee for the physical act of transferring a copy, and you -% may at your option offer warranty protection in exchange for a fee. -% -% \item -% You may modify your copy or copies of the Program or any portion -% of it, thus forming a work based on the Program, and copy and -% distribute such modifications or work under the terms of Section 1 -% above, provided that you also meet all of these conditions: -% -% \begin{enumerate} -% -% \item -% You must cause the modified files to carry prominent notices stating that -% you changed the files and the date of any change. -% -% \item -% You must cause any work that you distribute or publish, that in -% whole or in part contains or is derived from the Program or any -% part thereof, to be licensed as a whole at no charge to all third -% parties under the terms of this License. -% -% \item -% If the modified program normally reads commands interactively -% when run, you must cause it, when started running for such -% interactive use in the most ordinary way, to print or display an -% announcement including an appropriate copyright notice and a -% notice that there is no warranty (or else, saying that you provide -% a warranty) and that users may redistribute the program under -% these conditions, and telling the user how to view a copy of this -% License.  (Exception: if the Program itself is interactive but -% does not normally print such an announcement, your work based on -% the Program is not required to print an announcement.) -% -% \end{enumerate} -% -% -% These requirements apply to the modified work as a whole.  If -% identifiable sections of that work are not derived from the Program, -% and can be reasonably considered independent and separate works in -% themselves, then this License, and its terms, do not apply to those -% sections when you distribute them as separate works.  But when you -% distribute the same sections as part of a whole which is a work based -% on the Program, the distribution of the whole must be on the terms of -% this License, whose permissions for other licensees extend to the -% entire whole, and thus to each and every part regardless of who wrote it. -% -% Thus, it is not the intent of this section to claim rights or contest -% your rights to work written entirely by you; rather, the intent is to -% exercise the right to control the distribution of derivative or -% collective works based on the Program. -% -% In addition, mere aggregation of another work not based on the Program -% with the Program (or with a work based on the Program) on a volume of -% a storage or distribution medium does not bring the other work under -% the scope of this License. -% -% \item -% You may copy and distribute the Program (or a work based on it, -% under Section 2) in object code or executable form under the terms of -% Sections 1 and 2 above provided that you also do one of the following: -% -% \begin{enumerate} -% -% \item -% -% Accompany it with the complete corresponding machine-readable -% source code, which must be distributed under the terms of Sections -% 1 and 2 above on a medium customarily used for software interchange; or, -% -% \item -% -% Accompany it with a written offer, valid for at least three -% years, to give any third party, for a charge no more than your -% cost of physically performing source distribution, a complete -% machine-readable copy of the corresponding source code, to be -% distributed under the terms of Sections 1 and 2 above on a medium -% customarily used for software interchange; or, -% -% \item -% -% Accompany it with the information you received as to the offer -% to distribute corresponding source code.  (This alternative is -% allowed only for noncommercial distribution and only if you -% received the program in object code or executable form with such -% an offer, in accord with Subsection b above.) -% -% \end{enumerate} -% -% -% The source code for a work means the preferred form of the work for -% making modifications to it.  For an executable work, complete source -% code means all the source code for all modules it contains, plus any -% associated interface definition files, plus the scripts used to -% control compilation and installation of the executable.  However, as a -% special exception, the source code distributed need not include -% anything that is normally distributed (in either source or binary -% form) with the major components (compiler, kernel, and so on) of the -% operating system on which the executable runs, unless that component -% itself accompanies the executable. -% -% If distribution of executable or object code is made by offering -% access to copy from a designated place, then offering equivalent -% access to copy the source code from the same place counts as -% distribution of the source code, even though third parties are not -% compelled to copy the source along with the object code. -% -% \item -% You may not copy, modify, sublicense, or distribute the Program -% except as expressly provided under this License.  Any attempt -% otherwise to copy, modify, sublicense or distribute the Program is -% void, and will automatically terminate your rights under this License. -% However, parties who have received copies, or rights, from you under -% this License will not have their licenses terminated so long as such -% parties remain in full compliance. -% -% \item -% You are not required to accept this License, since you have not -% signed it.  However, nothing else grants you permission to modify or -% distribute the Program or its derivative works.  These actions are -% prohibited by law if you do not accept this License.  Therefore, by -% modifying or distributing the Program (or any work based on the -% Program), you indicate your acceptance of this License to do so, and -% all its terms and conditions for copying, distributing or modifying -% the Program or works based on it. -% -% \item -% Each time you redistribute the Program (or any work based on the -% Program), the recipient automatically receives a license from the -% original licensor to copy, distribute or modify the Program subject to -% these terms and conditions.  You may not impose any further -% restrictions on the recipients' exercise of the rights granted herein. -% You are not responsible for enforcing compliance by third parties to -% this License. -% -% \item -% If, as a consequence of a court judgment or allegation of patent -% infringement or for any other reason (not limited to patent issues), -% conditions are imposed on you (whether by court order, agreement or -% otherwise) that contradict the conditions of this License, they do not -% excuse you from the conditions of this License.  If you cannot -% distribute so as to satisfy simultaneously your obligations under this -% License and any other pertinent obligations, then as a consequence you -% may not distribute the Program at all.  For example, if a patent -% license would not permit royalty-free redistribution of the Program by -% all those who receive copies directly or indirectly through you, then -% the only way you could satisfy both it and this License would be to -% refrain entirely from distribution of the Program. -% -% If any portion of this section is held invalid or unenforceable under -% any particular circumstance, the balance of the section is intended to -% apply and the section as a whole is intended to apply in other -% circumstances. -% -% It is not the purpose of this section to induce you to infringe any -% patents or other property right claims or to contest validity of any -% such claims; this section has the sole purpose of protecting the -% integrity of the free software distribution system, which is -% implemented by public license practices.  Many people have made -% generous contributions to the wide range of software distributed -% through that system in reliance on consistent application of that -% system; it is up to the author/donor to decide if he or she is willing -% to distribute software through any other system and a licensee cannot -% impose that choice. -% -% This section is intended to make thoroughly clear what is believed to -% be a consequence of the rest of this License. -% -% \item -% If the distribution and/or use of the Program is restricted in -% certain countries either by patents or by copyrighted interfaces, the -% original copyright holder who places the Program under this License -% may add an explicit geographical distribution limitation excluding -% those countries, so that distribution is permitted only in or among -% countries not thus excluded.  In such case, this License incorporates -% the limitation as if written in the body of this License. -% -% \item -% The Free Software Foundation may publish revised and/or new versions -% of the General Public License from time to time.  Such new versions will -% be similar in spirit to the present version, but may differ in detail to -% address new problems or concerns. -% -% Each version is given a distinguishing version number.  If the Program -% specifies a version number of this License which applies to it and ``any -% later version'', you have the option of following the terms and conditions -% either of that version or of any later version published by the Free -% Software Foundation.  If the Program does not specify a version number of -% this License, you may choose any version ever published by the Free Software -% Foundation. -% -% \item -% If you wish to incorporate parts of the Program into other free -% programs whose distribution conditions are different, write to the author -% to ask for permission.  For software which is copyrighted by the Free -% Software Foundation, write to the Free Software Foundation; we sometimes -% make exceptions for this.  Our decision will be guided by the two goals -% of preserving the free status of all derivatives of our free software and -% of promoting the sharing and reuse of software generally. -% -% \begin{center} -% {\Large\sc -% No Warranty -% } -% \end{center} -% -% \item -% {\sc Because the program is licensed free of charge, there is no warranty -% for the program, to the extent permitted by applicable law.  Except when -% otherwise stated in writing the copyright holders and/or other parties -% provide the program ``as is'' without warranty of any kind, either expressed -% or implied, including, but not limited to, the implied warranties of -% merchantability and fitness for a particular purpose.  The entire risk as -% to the quality and performance of the program is with you.  Should the -% program prove defective, you assume the cost of all necessary servicing, -% repair or correction.} -% -% \item -% {\sc In no event unless required by applicable law or agreed to in writing -% will any copyright holder, or any other party who may modify and/or -% redistribute the program as permitted above, be liable to you for damages, -% including any general, special, incidental or consequential damages arising -% out of the use or inability to use the program (including but not limited -% to loss of data or data being rendered inaccurate or losses sustained by -% you or third parties or a failure of the program to operate with any other -% programs), even if such holder or other party has been advised of the -% possibility of such damages.} -% -% \end{enumerate} -% -% -% \begin{center} -% {\Large\sc End of Terms and Conditions} -% \end{center} -% -% -% \pagebreak[2] -% -% \section*{Appendix: How to Apply These Terms to Your New Programs} -% -% If you develop a new program, and you want it to be of the greatest -% possible use to the public, the best way to achieve this is to make it -% free software which everyone can redistribute and change under these -% terms. -% -%   To do so, attach the following notices to the program.  It is safest to -%   attach them to the start of each source file to most effectively convey -%   the exclusion of warranty; and each file should have at least the -%   ``copyright'' line and a pointer to where the full notice is found. -% -% \begin{quote} -% one line to give the program's name and a brief idea of what it does. \\ -% Copyright (C) yyyy  name of author \\ -% -% This program is free software; you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation; either version 2 of the License, or -% (at your option) any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program; if not, write to the Free Software -% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. -% \end{quote} -% -% Also add information on how to contact you by electronic and paper mail. -% -% If the program is interactive, make it output a short notice like this -% when it starts in an interactive mode: -% -% \begin{quote} -% Gnomovision version 69, Copyright (C) yyyy  name of author \\ -% Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. \\ -% This is free software, and you are welcome to redistribute it -% under certain conditions; type `show c' for details. -% \end{quote} -% -% -% The hypothetical commands {\tt show w} and {\tt show c} should show the -% appropriate parts of the General Public License.  Of course, the commands -% you use may be called something other than {\tt show w} and {\tt show c}; -% they could even be mouse-clicks or menu items---whatever suits your -% program. -% -% You should also get your employer (if you work as a programmer) or your -% school, if any, to sign a ``copyright disclaimer'' for the program, if -% necessary.  Here is a sample; alter the names: -% -% \begin{quote} -% Yoyodyne, Inc., hereby disclaims all copyright interest in the program \\ -% `Gnomovision' (which makes passes at compilers) written by James Hacker. \\ -% -% signature of Ty Coon, 1 April 1989 \\ -% Ty Coon, President of Vice -% \end{quote} -% -% -% This General Public License does not permit incorporating your program -% into proprietary programs.  If your program is a subroutine library, you -% may consider it more useful to permit linking proprietary applications -% with the library.  If this is what you want to do, use the GNU Library -% General Public License instead of this License. -% -% \end{multicols} -% \end{minipage} -% \end{lrbox} -% -% \begin{center} -% \scalebox{0.33}{\usebox{\gpl}} -% \end{center} -% -% \Finale -\endinput diff --git a/misc/luaotfload-blacklist.cnf b/misc/luaotfload-blacklist.cnf new file mode 100644 index 0000000..e82669b --- /dev/null +++ b/misc/luaotfload-blacklist.cnf @@ -0,0 +1,4 @@ +spltfgbd.ttf +spltfgbi.ttf +spltfgit.ttf +spltfgrg.ttf diff --git a/misc/valgrind-kpse-suppression.sup b/misc/valgrind-kpse-suppression.sup new file mode 100644 index 0000000..dc32586 --- /dev/null +++ b/misc/valgrind-kpse-suppression.sup @@ -0,0 +1,140 @@ +{ +   kpathsea-garbage-1 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:kpathsea_cnf_get +} + +{ +   kpathsea-garbage-2 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:kpse_program_basename +} + + +{ +   kpathsea-garbage-3 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:kpse_find_file +} + + +{ +   kpathsea-garbage-4 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:find_file +} + +{ +   kpathsea-garbage-5 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:lua_kpse_lookup +} + +{ +   kpathsea-garbage-6 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:find_file +} + + +{ +   kpathsea-garbage-7 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:expand_path +} + +{ +   kpathsea-garbage-8 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:do_lua_kpathsea_lookup +} + + +{ +   kpathsea-garbage-9 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:kpathsea_find_file +} + + +{ +   kpathsea-garbage-10 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:kpathsea_init_db +} + + +{ +   kpathsea-garbage-11 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:kpathsea_find_file_generic +} + + +{ +   kpathsea-garbage-12 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:expand_var +} + + +{ +   kpathsea-garbage-13 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:init_path +} + + +{ +   kpathsea-garbage-14 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:kpse_in_name_ok +} + + +{ +   kpathsea-garbage-15 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:kpathsea_var_value +} + + +{ +   kpathsea-garbage-16 +   Memcheck:Leak +   match-leak-kinds: all +   ... +   fun:kpse_set_program_name +} + + diff --git a/mkcharacters b/scripts/mkcharacters index 5d4a2f4..abed2c9 100755 --- a/mkcharacters +++ b/scripts/mkcharacters @@ -5,8 +5,8 @@  --  DESCRIPTION:  import parts of char-def.lua  -- REQUIREMENTS:  lua, ConTeXt, the lualibs package  --       AUTHOR:  Philipp Gesang (Phg), <phg42.2a@gmail.com> ---      VERSION:  2.4 ---      CREATED:  2013-05-17 12:41:39+0200 +--      VERSION:  2.5 +--     MODIFIED: 2014-02-11 07:24:25+0100  -----------------------------------------------------------------------  -- we create a stripped-down version of char-def.lua  ----------------------------------------------------------------------- @@ -14,7 +14,7 @@  -----------------------------------------------------------------------  --                              config  ----------------------------------------------------------------------- -local charfile      = "./luaotfload-characters.lua" +local charfile      = "./build/luaotfload-characters.lua"  local chardef       = "/home/phg/base/char-def.lua"  ---  for every code point char-def.lua provides a set of fields. they diff --git a/mkglyphlist b/scripts/mkglyphlist index f7a1cb9..8fde098 100755 --- a/mkglyphlist +++ b/scripts/mkglyphlist @@ -5,19 +5,21 @@  --  DESCRIPTION:  part of the luaotfload package  -- REQUIREMENTS:  lua, lpeg, luasocket, the lualibs package  --       AUTHOR:  Philipp Gesang (Phg), <phg42.2a@gmail.com> ---      VERSION:  2.4 ---      CREATED:  04/23/2013 12:42:17 PM CEST +--      VERSION:  2.5 +--     MODIFIED:  2014-02-11 06:44:50+0100  -----------------------------------------------------------------------  -- interesting thread on the Context list:  -- http://www.ntg.nl/pipermail/ntg-context/2008/029057.html +-- +-- N.B. this script assumes network connectivity!  -----------------------------------------------------------------------  -----------------------------------------------------------------------  --                              config  ----------------------------------------------------------------------- -local glyphfile     = "./glyphlist.txt" -local font_age      = "./luaotfload-glyphlist.lua" +local glyphfile     = "./build/glyphlist.txt" +local font_age      = "./build/luaotfload-glyphlist.lua"  local glyph_source  = "http://partners.adobe.com/public/developer/en/opentype/glyphlist.txt"  ----------------------------------------------------------------------- @@ -133,6 +135,7 @@ end  local get_raw get_raw = function (retry)    local fh = io.open(glyphfile, "rb")    if fh then +    print ("info: reading glyph list from", glyphfile)      local data = fh:read"*all"      fh:close()      if data then return data end @@ -143,7 +146,7 @@ local get_raw get_raw = function (retry)      if glyphdata then        local fh = io.open(glyphfile, "wb")        if not fh then -        print"error: glyph file not writable" +        print (string.format ("error: glyph file (%s) not writable", glyphfile))          os.exit(-1)        end        fh:write(glyphdata) diff --git a/mkstatus b/scripts/mkstatus index 9940970..15cb97c 100755 --- a/mkstatus +++ b/scripts/mkstatus @@ -24,39 +24,44 @@ local md5sumhexa   = md5.sumhexa  local ioloaddata   = io.loaddata  local iosavedata   = io.savedata  local iopopen      = io.popen +local iowrite      = io.write +local lfsisdir     = lfs.isdir  -----------------------------------------------------------------------  -- settings  ----------------------------------------------------------------------- -local filelist = "luaotfload-status.lua" --- result +local verbose  = false +local filelist = "./build/luaotfload-status.lua" --- result  local names = {    --- only the runtime files and scripts -  "luaotfload-auxiliary.lua", -  "luaotfload-basics-gen.lua", -  "luaotfload-basics-nod.lua", -  "luaotfload-characters.lua", -  "luaotfload-colors.lua", -  "luaotfload-database.lua", -  "luaotfload-diagnostics.lua", -  "luaotfload-features.lua", -  "luaotfload-fonts-cbk.lua", -  "luaotfload-fonts-def.lua", -  "luaotfload-fonts-enc.lua", -  "luaotfload-fonts-ext.lua", -  "luaotfload-fonts-lua.lua", -  "luaotfload-fonts-tfm.lua", -  "luaotfload-glyphlist.lua", -  "luaotfload-letterspace.lua", -  "luaotfload-loaders.lua", -  "luaotfload.lua", -  "luaotfload-fontloader.lua", -  "luaotfload-override.lua", -  "luaotfload-tool.lua", -  "mkcharacters", -  "mkglyphlist", -  "mkstatus", +  { "src",      "luaotfload-auxiliary.lua",   }, +  { "src",      "luaotfload-basics-gen.lua",  }, +  { "src",      "luaotfload-basics-nod.lua",  }, +  { "build",    "luaotfload-characters.lua",  }, +  { "src",      "luaotfload-colors.lua",      }, +  { "src",      "luaotfload-database.lua",    }, +  { "src",      "luaotfload-diagnostics.lua", }, +  { "src",      "luaotfload-features.lua",    }, +  { "src",      "luaotfload-fonts-cbk.lua",   }, +  { "src",      "luaotfload-fonts-def.lua",   }, +  { "src",      "luaotfload-fonts-enc.lua",   }, +  { "src",      "luaotfload-fonts-ext.lua",   }, +  { "src",      "luaotfload-fonts-lua.lua",   }, +  { "src",      "luaotfload-fonts-tfm.lua",   }, +  { "build",    "luaotfload-glyphlist.lua",   }, +  { "src",      "luaotfload-letterspace.lua", }, +  { "src",      "luaotfload-loaders.lua",     }, +  { "src",      "luaotfload-log.lua",         }, +  { "src",      "luaotfload-main.lua",        }, +  { "src",      "luaotfload-fontloader.lua",  }, +  { "src",      "luaotfload-override.lua",    }, +  { "src",      "luaotfload-parsers.lua",     }, +  { "src",      "luaotfload-tool.lua",        }, +  { "scripts",  "mkcharacters",               }, +  { "scripts",  "mkglyphlist",                }, +  { "scripts",  "mkstatus",                   },  }  ----------------------------------------------------------------------- @@ -120,17 +125,38 @@ hash_all = function (list, acc)      return hash_all (table.fastcopy (names), { })    end -  local fname = list[#list] +  local finfo = list[#list]    list[#list] = nil -  if fname then -    local sum = hash_file (fname) -    acc[#acc+1] = { fname, sum } +  if finfo then +    local fpath +    if type (finfo) == "table" then +      local d, f = finfo [1], finfo [2] +      if lfs.isdir (d) then +        fpath = file.join (d, f) +      else +        fpath = f +      end +    else +      fpath = finfo +    end +    if verbose then +      iowrite "· md5(" +      iowrite (fpath) +    end +    local sum = hash_file (fpath) +    if verbose then +      iowrite ") = \"" +      iowrite (sum) +      iowrite "\"\n" +    end +    acc[#acc+1] = { fpath, sum }      return hash_all (list, acc)    end    return acc  end  local main = function () +  if arg [1] == "-v" then verbose = true end    local hashes      = hash_all ()    local notes       = git_info ()    local serialized  = table.serialize ({ notes = notes, diff --git a/mktests b/scripts/mktests index baa710c..b36b6dd 100755 --- a/mktests +++ b/scripts/mktests @@ -6,7 +6,7 @@  -- REQUIREMENTS:  Luatex > 0.76, Luaotfload  --       AUTHOR:  Philipp Gesang (Phg), <phg42.2a@gmail.com>  --      VERSION:  2.4 ---     MODIFIED:  2013-08-26 09:31:22+0200 +--     MODIFIED:  2014-05-15 22:16:47+0200  -----------------------------------------------------------------------  --  --===================================================================-- @@ -16,21 +16,17 @@  --===================================================================-- -local tests = { } - -config = { luaotfload = { -    names_dir     = "names", -    cache_dir     = "fonts", -    index_file    = "luaotfload-names.lua", -    resolver      = "normal", -    update_live   = true, --- suppress db updates -}} +local tests       = { } +local lpeg        = require "lpeg" +local lpegmatch   = lpeg.match  kpse.set_program_name "luatex"  require "lualibs"  require "luaotfload-basics-gen.lua" -require "luaotfload-override.lua" +require "luaotfload-log.lua" +require "luaotfload-parsers" +require "luaotfload-configuration"  require "luaotfload-database"  local names = fonts.names @@ -65,10 +61,64 @@ local pprint_spec = function (spec)  end  ----------------------------------------------------------------------- ---- tool tests +--- parser tests  ----------------------------------------------------------------------- +local test_config_input = [==[ + + +[foo] +bar = baz +xyzzy = no +buzz + +[lavernica "brutalitops"] +# It’s a locomotive that runs on us. +  laan-ev = zip zop zooey   ; jib-jab +Crouton = "Fibrosis \"\\ # " +]==] + +local test_config_output = { +  { section = { title = "foo" }, +    variables = { bar = "baz", +                  xyzzy = false, +                  buzz = true } }, +  { section = { title = "lavernica", +                subtitle = "brutalitops" }, +    variables = { ["laan-ev"] = "zip zop zooey", +                  crouton = "Fibrosis \"\\ # " } } +} + +local parse_config = function () +  local parser = luaotfload.parsers.config +  local result = lpegmatch (parser, test_config_input) +  --- compare values recursively +  local aux aux = function (t1, t2) +    --- cheaply non-tail recursive +    local k1 = table.keys (t1) +    local k2 = table.keys (t2) +    if #k1 ~= #k2 then +      return false +    end +    for i = 1, #k1 do +      local k  = k1[i] +      local v1 = t1[k] +      local v2 = t2[k] +      if type (v1) == "table" then +        if type (v2) ~= "table" or not aux (v1, v2) then +          return false +        end +      elseif v1 ~= v2 then +        return false +      end +    end +    return true +  end +  return aux (result, test_config_output) and 0 or 1, 1 +end + +tests["parse_config"] = parse_config  -----------------------------------------------------------------------  --- font tests @@ -86,11 +136,11 @@ local infer_regular_style = {    { "Garamond Premier Pro", "GaramondPremrPro.otf"      },    { "CMU Serif",            "cmunrm.otf"                },    { "CMU Sans Serif",       "cmunss.otf"                }, -  { "Minion Pro",           "MinionPro_Regular.otf"     }, +  { "Minion Pro",           "MinionPro-Regular.otf"     },    --- Below test will succeed only if we match for the    --- splainname (= sanitized tfmdata.fullname) field    --- explicitly. -  { "Minion Pro Italic",    "MinionPro_It.otf"          }, +  { "Minion Pro Italic",    "MinionPro-It.otf"          },  }  local choose_optical_size = { @@ -140,10 +190,10 @@ local choose_style = {      Also, the full Minion Pro set comes with different optical sizes which      for monetary reasons cannot considered here.    --]]-- -  { { name = "Minion Pro", style = "regular"    }, "MinionPro_Regular.otf"  }, -  { { name = "Minion Pro", style = "italic"     }, "MinionPro_It.otf"       }, -  { { name = "Minion Pro", style = "bold"       }, "MinionPro_Bold.otf"     }, -  { { name = "Minion Pro", style = "bolditalic" }, "MinionPro_BoldIt.otf"   }, +  { { name = "Minion Pro", style = "regular"    }, "MinionPro-Regular.otf"  }, +  { { name = "Minion Pro", style = "italic"     }, "MinionPro-It.otf"       }, +  { { name = "Minion Pro", style = "bold"       }, "MinionPro-Bold.otf"     }, +  { { name = "Minion Pro", style = "bolditalic" }, "MinionPro-BoldIt.otf"   },  }  --- this needs a database built with --formats=+pfa,pfb,afm @@ -227,12 +277,14 @@ end  tests ["resolve_font_name"] = resolve_font_name +  -----------------------------------------------------------------------  --- runner  -----------------------------------------------------------------------  local main = function ()    local failed, total = 0, 0 +  config.actions.apply_defaults ()    for name, test in next, tests do      texio.write_nl ("[" .. name .. "]")      local newfailed, newtotal = test () diff --git a/luaotfload-auxiliary.lua b/src/luaotfload-auxiliary.lua index d3de731..89bf51b 100644 --- a/luaotfload-auxiliary.lua +++ b/src/luaotfload-auxiliary.lua @@ -2,10 +2,10 @@  -----------------------------------------------------------------------  --         FILE:  luaotfload-auxiliary.lua  --  DESCRIPTION:  part of luaotfload --- REQUIREMENTS:  luaotfload 2.4 +-- REQUIREMENTS:  luaotfload 2.5  --       AUTHOR:  Khaled Hosny, Élie Roux, Philipp Gesang ---      VERSION:  2.4 ---      CREATED:  2013-05-01 14:40:50+0200 +--      VERSION:  2.5 +--     MODIFIED:  2014-01-02 21:24:25+0100  -----------------------------------------------------------------------  -- @@ -15,28 +15,26 @@  luaotfload                  = luaotfload or {}  luaotfload.aux              = luaotfload.aux or { } -config                      = config or { } -config.luaotfload           = config.luaotfload or { } - -local aux           = luaotfload.aux -local log           = luaotfload.log -local warning       = luaotfload.log -local fonthashes    = fonts.hashes -local identifiers   = fonthashes.identifiers - -local fontid        = font.id -local texsprint     = tex.sprint - -local dofile        = dofile -local getmetatable  = getmetatable -local setmetatable  = setmetatable -local utf8          = unicode.utf8 -local stringlower   = string.lower -local stringformat  = string.format -local stringgsub    = string.gsub -local stringbyte    = string.byte -local stringfind    = string.find -local tablecopy     = table.copy +local aux                   = luaotfload.aux +local log                   = luaotfload.log +local report                = log.report +local fonthashes            = fonts.hashes +local identifiers           = fonthashes.identifiers +local fontnames             = fonts.names + +local fontid                = font.id +local texsprint             = tex.sprint + +local dofile                = dofile +local getmetatable          = getmetatable +local setmetatable          = setmetatable +local utf8                  = unicode.utf8 +local stringlower           = string.lower +local stringformat          = string.format +local stringgsub            = string.gsub +local stringbyte            = string.byte +local stringfind            = string.find +local tablecopy             = table.copy  -----------------------------------------------------------------------  ---                          font patches @@ -57,8 +55,8 @@ local start_rewrite_fontname = function ()        rewrite_fontname,        "luaotfload.rewrite_fontname")      rewriting = true -    logs.names_report ("log", 0, "aux", -                       "start rewriting tfmdata.name field") +    report ("log", 1, "aux", +            "start rewriting tfmdata.name field")    end  end @@ -69,97 +67,13 @@ local stop_rewrite_fontname = function ()      luatexbase.remove_fromt_callback        ("luaotfload.patch_font", "luaotfload.rewrite_fontname")      rewriting = false -    logs.names_report ("log", 0, "aux", -                       "stop rewriting tfmdata.name field") +    report ("log", 1, "aux", +            "stop rewriting tfmdata.name field")    end  end  aux.stop_rewrite_fontname = stop_rewrite_fontname ---- as of 2.3 the compatibility hacks for TL 2013 are made optional - -if config.luaotfload.compatibility == true then - ---[[doc-- - -The font object (tfmdata) structure has changed since version 1.x, so -in case other packages haven’t been updated we put fallbacks in place -where they’d expect them. Specifically we have in mind: - -  · fontspec -  · unicode-math -  · microtype (most likely fixed till TL2013) - ---doc]]-- - ---- fontobj -> fontobj -local add_fontdata_fallbacks = function (fontdata) -  if type(fontdata) == "table" then -    local fontparameters = fontdata.parameters -    local metadata -    if not fontdata.shared then --- that would be a tfm -      --- we can’t really catch everything that -      --- goes wrong; for some reason, fontspec.lua -      --- just assumes it always gets an otf object, -      --- so its capheight callback, which does not -      --- bother to do any checks, will access -      --- fontdata.shared no matter what ... -      fontdata.units = fontdata.units_per_em - -    else --- otf -      metadata         = fontdata.shared.rawdata.metadata -      fontdata.name    = metadata.origname or fontdata.name -      fontdata.units   = fontdata.units_per_em -      fontdata.size    = fontdata.size or fontparameters.size -      local resources  = fontdata.resources -      --- for legacy fontspec.lua and unicode-math.lua -      fontdata.shared.otfdata = { -        pfminfo   = { os2_capheight = metadata.pfminfo.os2_capheight }, -        metadata  = { ascent = metadata.ascent }, -      } -      --- for microtype and fontspec -      --local fake_features = { } -      local fake_features = table.copy(resources.features) -      setmetatable(fake_features, { __index = function (tab, idx) -          warning("some package (probably fontspec) is outdated") -          warning( -            "attempt to index " .. -            "tfmdata.shared.otfdata.luatex.features (%s)", -            idx) -          --os.exit(1) -          return tab[idx] -        end, -      }) -      fontdata.shared.otfdata.luatex = { -        unicodes = resources.unicodes, -        features = fake_features, -      } -    end -  end -  return fontdata -end - -luatexbase.add_to_callback( -  "luaotfload.patch_font", -  add_fontdata_fallbacks, -  "luaotfload.fontdata_fallbacks") - ---[[doc-- - -Additionally, the font registry is expected at fonts.identifiers -(fontspec) or fonts.ids (microtype), but in the meantime it has been -migrated to fonts.hashes.identifiers.  We’ll make luaotfload satisfy -those assumptions. (Maybe it’d be more appropriate to use -font.getfont() since Hans made it a harmless wrapper [1].) - -[1] http://www.ntg.nl/pipermail/ntg-context/2013/072166.html - ---doc]]-- - -fonts.identifiers = fonts.hashes.identifiers -fonts.ids         = fonts.hashes.identifiers - -end  --[[doc--  This sets two dimensions apparently relied upon by the unicode-math @@ -453,7 +367,7 @@ do    local load_chardef = function () -    log ("Loading character metadata from %s.", chardef) +    report ("both", 1, "aux", "Loading character metadata from %s.", chardef)      chardata = dofile (kpse.find_file (chardef, "lua"))      if chardata == nil then @@ -511,19 +425,19 @@ local provides_script = function (font_id, asked_script)          --- where method: "gpos" | "gsub"          for feature, data in next, featuredata do            if data[asked_script] then -            log(stringformat( -              "font no %d (%s) defines feature %s for script %s", -              font_id, fontname, feature, asked_script)) +            report ("log", 1, "aux", +                    "font no %d (%s) defines feature %s for script %s", +                    font_id, fontname, feature, asked_script)              return true            end          end        end -      log(stringformat( -        "font no %d (%s) defines no feature for script %s", -        font_id, fontname, asked_script)) +      report ("log", 0, "aux", +              "font no %d (%s) defines no feature for script %s", +              font_id, fontname, asked_script)      end    end -  log(stringformat("no font with id %d", font_id)) +  report ("log", 0, "aux", "no font with id %d", font_id)    return false  end @@ -550,20 +464,22 @@ local provides_language = function (font_id, asked_script, asked_language)          for feature, data in next, featuredata do            local scriptdata = data[asked_script]            if scriptdata and scriptdata[asked_language] then -            log(stringformat("font no %d (%s) defines feature %s " -                          .. "for script %s with language %s", -                             font_id, fontname, feature, -                             asked_script, asked_language)) +            report ("log", 1, "aux", +                    "font no %d (%s) defines feature %s " +                    .. "for script %s with language %s", +                    font_id, fontname, feature, +                    asked_script, asked_language)              return true            end          end        end -      log(stringformat( -        "font no %d (%s) defines no feature for script %s with language %s", -        font_id, fontname, asked_script, asked_language)) +      report ("log", 0, "aux", +              "font no %d (%s) defines no feature " +              .. "for script %s with language %s", +              font_id, fontname, asked_script, asked_language)      end    end -  log(stringformat("no font with id %d", font_id)) +  report ("log", 0, "aux", "no font with id %d", font_id)    return false  end @@ -621,20 +537,21 @@ local provides_feature = function (font_id,        asked_script,          if feature then            local scriptdata = feature[asked_script]            if scriptdata and scriptdata[asked_language] then -            log(stringformat("font no %d (%s) defines feature %s " -                          .. "for script %s with language %s", -                             font_id, fontname, asked_feature, -                             asked_script, asked_language)) +            report ("log", 1, "aux", +                    "font no %d (%s) defines feature %s " +                    .. "for script %s with language %s", +                    font_id, fontname, asked_feature, +                    asked_script, asked_language)              return true            end          end        end -      log(stringformat( -        "font no %d (%s) does not define feature %s for script %s with language %s", -        font_id, fontname, asked_feature, asked_script, asked_language)) +      report ("log", 0, "aux", +              "font no %d (%s) does not define feature %s for script %s with language %s", +              font_id, fontname, asked_feature, asked_script, asked_language)      end    end -  log(stringformat("no font with id %d", font_id)) +  report ("log", 0, "aux", "no font with id %d", font_id)    return false  end @@ -674,8 +591,8 @@ aux.sprint_math_dimension = sprint_math_dimension  ---                    extra database functions  ----------------------------------------------------------------------- -local namesresolve      = fonts.names.resolve -local namesscan_dir     = fonts.names.scan_dir +local namesresolve      = fontnames.resolve +local namesscan_dir     = fontnames.scan_dir  --[====[-- TODO -> port this to new db model @@ -747,6 +664,42 @@ end  aux.resolve_fontlist = resolve_fontlist +--- index access ------------------------------------------------------ + +--- Based on a discussion on the Luatex mailing list: +--- http://tug.org/pipermail/luatex/2014-June/004881.html + +--[[doc-- + +  aux.read_font_index -- Read the names index from the canonical +  location and return its contents. This does not affect the behavior +  of Luaotfload: The returned table is independent of what the font +  resolvers use internally. Access is raw: each call to the function +  will result in the entire table being re-read from disk. + +--doc]]-- + +local load_names        = fontnames.load +local access_font_index = fontnames.access_font_index + +local read_font_index = function () +  return load_names (true) or { } +end + +--[[doc-- + +  aux.font_index -- Access Luaotfload’s internal database. If the +  database hasn’t been loaded yet this will cause it to be loaded, with +  all the possible side-effects like for instance creating the index +  file if it doesn’t exist, reading all font files, &c. + +--doc]]-- + +local font_index = function () return access_font_index () end + +aux.read_font_index = read_font_index +aux.font_index      = font_index +  --- loaded fonts ------------------------------------------------------  --- just a proof of concept diff --git a/luaotfload-basics-gen.lua b/src/luaotfload-basics-gen.lua index 9cf5b93..c19a49a 100644 --- a/luaotfload-basics-gen.lua +++ b/src/luaotfload-basics-gen.lua @@ -254,6 +254,18 @@ function caches.loaddata(paths,name)      for i=1,#paths do          local data = false          local luaname, lucname = makefullname(paths[i],name) +        if lucname and not lfs.isfile(lucname) and type(caches.compile) == "function" then +            -- in case we used luatex and luajittex mixed ... lub or luc file +            texio.write(string.format("(compiling luc: %s)",lucname)) +            data = loadfile(luaname) +            if data then +                data = data() +            end +            if data then +                caches.compile(data,luaname,lucname) +                return data +            end +        end          if lucname and lfs.isfile(lucname) then -- maybe also check for size              texio.write(string.format("(load luc: %s)",lucname))              data = loadfile(lucname) @@ -341,3 +353,16 @@ end  function table.setmetatableindex(t,f)      setmetatable(t,{ __index = f })  end + +-- helper for plain: + +arguments = { } + +if arg then +    for i=1,#arg do +        local k, v = string.match(arg[i],"^%-%-([^=]+)=?(.-)$") +        if k and v then +            arguments[k] = v +        end +    end +end diff --git a/luaotfload-basics-nod.lua b/src/luaotfload-basics-nod.lua index 50a1e86..373dab5 100644 --- a/luaotfload-basics-nod.lua +++ b/src/luaotfload-basics-nod.lua @@ -54,22 +54,33 @@ nodes.handlers     = { }  local nodecodes    = { } for k,v in next, node.types   () do nodecodes[string.gsub(v,"_","")] = k end  local whatcodes    = { } for k,v in next, node.whatsits() do whatcodes[string.gsub(v,"_","")] = k end  local glyphcodes   = { [0] = "character", "glyph", "ligature", "ghost", "left", "right" } +local disccodes    = { [0] = "discretionary", "explicit", "automatic", "regular", "first", "second" }  nodes.nodecodes    = nodecodes  nodes.whatcodes    = whatcodes  nodes.whatsitcodes = whatcodes  nodes.glyphcodes   = glyphcodes +nodes.disccodes    = disccodes  local free_node    = node.free  local remove_node  = node.remove  local new_node     = node.new  local traverse_id  = node.traverse_id -local math_code    = nodecodes.math -  nodes.handlers.protectglyphs   = node.protect_glyphs  nodes.handlers.unprotectglyphs = node.unprotect_glyphs +local math_code   = nodecodes.math +local end_of_math = node.end_of_math + +function node.end_of_math(n) +    if n.id == math_code and n.subtype == 1 then +        return n +    else +        return end_of_math(n) +    end +end +  function nodes.remove(head, current, free_too)     local t = current     head, current = remove_node(head,current) diff --git a/luaotfload-colors.lua b/src/luaotfload-colors.lua index b8ecb87..9be2974 100644 --- a/luaotfload-colors.lua +++ b/src/luaotfload-colors.lua @@ -1,9 +1,9 @@  if not modules then modules = { } end modules ['luaotfload-colors'] = { -    version   = "2.4", -    comment   = "companion to luaotfload.lua (font color)", +    version   = "2.5", +    comment   = "companion to luaotfload-main.lua (font color)",      author    = "Khaled Hosny, Elie Roux, Philipp Gesang",      copyright = "Luaotfload Development Team", -    license   = "GNU GPL v2" +    license   = "GNU GPL v2.0"  }  --[[doc-- @@ -19,14 +19,8 @@ explanation: http://tug.org/pipermail/luatex/2013-May/004305.html  --doc]]-- - -local color_callback = config.luaotfload.color_callback -if not color_callback then -    --- maybe this would be better as a method: "early" | "late" -    color_callback = "pre_linebreak_filter" ---  color_callback = "pre_output_filter" --- old behavior, breaks expansion -end - +local log                   = luaotfload.log +local logreport             = log.report  local newnode               = node.new  local nodetype              = node.id @@ -34,6 +28,9 @@ local traverse_nodes        = node.traverse  local insert_node_before    = node.insert_before  local insert_node_after     = node.insert_after +local texset                = tex.set +local texget                = tex.get +  local stringformat          = string.format  local stringgsub            = string.gsub  local stringfind            = string.find @@ -86,8 +83,9 @@ local sanitize_color_expression = function (digits)      digits = tostring(digits)      local sanitized = lpegmatch(valid_digits, digits)      if not sanitized then -        luaotfload.warning( -            "%q is not a valid rgb[a] color expression", digits) +        logreport("both", 0, "color", +                  "%q is not a valid rgb[a] color expression", +                  digits)          return nil      end      return sanitized @@ -276,20 +274,24 @@ local color_handler = function (head)      local new_head = node_colorize(head, nil, nil)      -- now append our page resources      if res then -        res["1"] = true -        local tpr, t = tex.pdfpageresources, "" +        res["1"]  = true +        local tpr = texget("pdfpageresources") +        local t   = ""          for k in pairs(res) do              local str = stringformat("/TransGs%s<</ca %s/CA %s>>", k, k, k)              if not stringfind(tpr,str) then                  t = t .. str              end          end +        print""          if t ~= "" then +            print(">>", tpr, "<<")              if not stringfind(tpr,"/ExtGState<<.*>>") then                  tpr = tpr.."/ExtGState<<>>"              end              tpr = stringgsub(tpr,"/ExtGState<<","%1"..t) -            tex.pdfpageresources = tpr +            texset("global", "pdfpageresources", tpr) +            print(">>", tpr, "<<")          end          res = nil -- reset res      end @@ -300,6 +302,11 @@ local color_callback_activated = 0  --- unit -> unit  add_color_callback = function ( ) +    local color_callback = config.luaotfload.run.color_callback +    if not color_callback then +        color_callback = "pre_linebreak_filter" +    end +      if color_callback_activated == 0 then          luatexbase.add_to_callback(color_callback,                                     color_handler, diff --git a/src/luaotfload-configuration.lua b/src/luaotfload-configuration.lua new file mode 100644 index 0000000..dfa222c --- /dev/null +++ b/src/luaotfload-configuration.lua @@ -0,0 +1,704 @@ +#!/usr/bin/env texlua +------------------------------------------------------------------------------- +--         FILE:  luaotfload-configuration.lua +--  DESCRIPTION:  config file reader +-- REQUIREMENTS:  Luaotfload 2.5 or above +--       AUTHOR:  Philipp Gesang (Phg), <phg42.2a@gmail.com> +--      VERSION:  same as Luaotfload +--     MODIFIED:  2014-07-13 14:19:32+0200 +------------------------------------------------------------------------------- +-- + +if not modules then modules = { } end modules ["luaotfload-configuration"] = { +  version   = "2.5", +  comment   = "part of Luaotfload", +  author    = "Philipp Gesang", +  copyright = "Luaotfload Development Team", +  license   = "GNU GPL v2.0" +} + +luaotfload                    = luaotfload or { } +config                        = config or { } +config.luaotfload             = { } + +local status_file             = "luaotfload-status" +local luaotfloadstatus        = require (status_file) + +local string                  = string +local stringsub               = string.sub +local stringexplode           = string.explode +local stringstrip             = string.strip +local stringfind              = string.find + +local table                   = table +local tableappend             = table.append +local tablecopy               = table.copy +local tableconcat             = table.concat +local tabletohash             = table.tohash + +local math                    = math +local mathfloor               = math.floor + +local io                      = io +local ioloaddata              = io.loaddata +local iopopen                 = io.popen + +local os                      = os +local osgetenv                = os.getenv + +local lpeg                    = require "lpeg" +local lpegmatch               = lpeg.match +local commasplitter           = lpeg.splitat "," +local equalssplitter          = lpeg.splitat "=" + +local kpse                    = kpse +local kpseexpand_path         = kpse.expand_path +local kpselookup              = kpse.lookup + +local lfs                     = lfs +local lfsisfile               = lfs.isfile +local lfsisdir                = lfs.isdir + +local file                    = file +local filejoin                = file.join +local filereplacesuffix       = file.replacesuffix + + +local parsers                 = luaotfload.parsers + +local log                     = luaotfload.log +local logreport               = log.report + +local config_parser           = parsers.config +local stripslashes            = parsers.stripslashes + +local getwritablepath         = caches.getwritablepath + +------------------------------------------------------------------------------- +---                                SETTINGS +------------------------------------------------------------------------------- + +local path_t = 0 +local kpse_t = 1 + +local val_home            = kpseexpand_path "~" +local val_xdg_config_home = kpseexpand_path "$XDG_CONFIG_HOME" + +if val_xdg_config_home == "" then val_xdg_config_home = "~/.config" end + +local config_paths = { +  --- needs adapting for those other OS +  { path_t, "./luaotfload.conf" }, +  { path_t, "./luaotfloadrc" }, +  { path_t, filejoin (val_xdg_config_home, "luaotfload/luaotfload.conf") }, +  { path_t, filejoin (val_xdg_config_home, "luaotfload/luaotfloadrc") }, +  { path_t, filejoin (val_home, ".luaotfloadrc") }, +  { kpse_t, "luaotfloadrc" }, +  { kpse_t, "luaotfload.conf" }, +} + +local valid_formats = tabletohash { +  "otf",   "ttc", "ttf", "dfont", "afm", "pfb", "pfa", +} + +local feature_presets = { +  arab = tabletohash { +    "ccmp", "locl", "isol", "fina", "fin2", +    "fin3", "medi", "med2", "init", "rlig", +    "calt", "liga", "cswh", "mset", "curs", +    "kern", "mark", "mkmk", +  }, +  deva = tabletohash { +    "ccmp", "locl", "init", "nukt", "akhn", +    "rphf", "blwf", "half", "pstf", "vatu", +    "pres", "blws", "abvs", "psts", "haln", +    "calt", "blwm", "abvm", "dist", "kern", +    "mark", "mkmk", +  }, +  khmr = tabletohash { +    "ccmp", "locl", "pref", "blwf", "abvf", +    "pstf", "pres", "blws", "abvs", "psts", +    "clig", "calt", "blwm", "abvm", "dist", +    "kern", "mark", "mkmk", +  }, +  thai = tabletohash { +    "ccmp", "locl", "liga", "kern", "mark", +    "mkmk", +  }, +} + + + +------------------------------------------------------------------------------- +---                                DEFAULTS +------------------------------------------------------------------------------- + +local default_config = { +  db = { +    formats     = "otf,ttf,ttc,dfont", +    scan_local  = false, +    skip_read   = false, +    strip       = true, +    update_live = true, +    compress    = true, +    max_fonts   = 2^51, +  }, +  run = { +    resolver       = "cached", +    definer        = "patch", +    log_level      = 0, +    color_callback = "pre_linebreak_filter", +  }, +  misc = { +    bisect         = false, +    version        = luaotfload.version, +    statistics     = false, +    termwidth      = nil, +  }, +  paths = { +    names_dir           = "names", +    cache_dir           = "fonts", +    index_file          = "luaotfload-names.lua", +    lookups_file        = "luaotfload-lookup-cache.lua", +    lookup_path_lua     = nil, +    lookup_path_luc     = nil, +    index_path_lua      = nil, +    index_path_luc      = nil, +  }, +  default_features = { +    global = { mode = "node" }, +    dflt = tabletohash { +      "ccmp", "locl", "rlig", "liga", "clig", +      "kern", "mark", "mkmk", 'itlc', +    }, + +    arab = feature_presets.arab, +    syrc = feature_presets.arab, +    mong = feature_presets.arab, +    nko  = feature_presets.arab, + +    deva = feature_presets.deva, +    beng = feature_presets.deva, +    guru = feature_presets.deva, +    gujr = feature_presets.deva, +    orya = feature_presets.deva, +    taml = feature_presets.deva, +    telu = feature_presets.deva, +    knda = feature_presets.deva, +    mlym = feature_presets.deva, +    sinh = feature_presets.deva, + +    khmr = feature_presets.khmr, +    tibt = feature_presets.khmr, +    thai = feature_presets.thai, +    lao  = feature_presets.thai, + +    hang = tabletohash { "ccmp", "ljmo", "vjmo", "tjmo", }, +  }, +} + +------------------------------------------------------------------------------- +---                          RECONFIGURATION TASKS +------------------------------------------------------------------------------- + +--[[doc-- + +    Procedures to be executed in order to put the new configuration into effect. + +--doc]]-- + +local reconf_tasks = nil + +local min_terminal_width = 40 + +--- The “termwidth” value is only considered when printing +--- short status messages, e.g. when building the database +--- online. +local check_termwidth = function () +  if config.luaotfload.misc.termwidth == nil then +      local tw = 79 +      if not (   os.type == "windows" --- Assume broken terminal. +              or osgetenv "TERM" == "dumb") +      then +          local p = iopopen "tput cols" +          if p then +              result = tonumber (p:read "*all") +              p:close () +              if result then +                  tw = result +              else +                  logreport ("log", 2, "db", "tput returned non-number.") +              end +          else +              logreport ("log", 2, "db", "Shell escape disabled or tput executable missing.") +              logreport ("log", 2, "db", "Assuming 79 cols terminal width.") +          end +      end +      config.luaotfload.misc.termwidth = tw +  end +  return true +end + +local set_font_filter = function () +  local names = fonts.names +  if names and names.set_font_filter then +    local formats = config.luaotfload.db.formats +    if not formats or formats == "" then +      formats = default_config.db.formats +    end +    names.set_font_filter (formats) +  end +  return true +end + +local set_name_resolver = function () +  local names = fonts.names +  if names and names.resolve_cached then +    --- replace the resolver from luatex-fonts +    if config.luaotfload.db.resolver == "cached" then +        logreport ("both", 2, "cache", "Caching of name: lookups active.") +        names.resolvespec  = names.resolve_cached +    else +        names.resolvespec  = names.resolve_name +    end +  end +  return true +end + +local set_loglevel = function () +  log.set_loglevel (config.luaotfload.run.log_level) +  return true +end + +local build_cache_paths = function () +  local paths  = config.luaotfload.paths +  local prefix = getwritablepath (paths.names_dir, "") + +  if not prefix then +    luaotfload.error ("Impossible to find a suitable writeable cache...") +    return false +  end + +  prefix = lpegmatch (stripslashes, prefix) +  logreport ("log", 0, "conf", "Root cache directory is %s.", prefix) + +  local index_file      = filejoin (prefix, paths.index_file) +  local lookups_file    = filejoin (prefix, paths.lookups_file) + +  paths.prefix          = prefix +  paths.index_path_lua  = filereplacesuffix (index_file,   "lua") +  paths.index_path_luc  = filereplacesuffix (index_file,   "luc") +  paths.lookup_path_lua = filereplacesuffix (lookups_file, "lua") +  paths.lookup_path_luc = filereplacesuffix (lookups_file, "luc") +  return true +end + + +local set_default_features = function () +  local default_features = config.luaotfload.default_features +  luaotfload.features    = luaotfload.features or { +                             global   = { }, +                             defaults = { }, +                           } +  current_features       = luaotfload.features +  for var, val in next, default_features do +    if var == "global" then +      current_features.global = val +    else +      current_features.defaults[var] = val +    end +  end +  return true +end + + +reconf_tasks = { +  { "Set the log level"         , set_loglevel         }, +  { "Build cache paths"         , build_cache_paths    }, +  { "Check terminal dimensions" , check_termwidth      }, +  { "Set the font filter"       , set_font_filter      }, +  { "Install font name resolver", set_name_resolver    }, +  { "Set default features"      , set_default_features }, +} + +------------------------------------------------------------------------------- +---                          OPTION SPECIFICATION +------------------------------------------------------------------------------- + +local string_t    = "string" +local table_t     = "table" +local number_t    = "number" +local boolean_t   = "boolean" +local function_t  = "function" + +local tointeger = function (n) +  n = tonumber (n) +  if n then +    return mathfloor (n + 0.5) +  end +end + +local toarray = function (s) +  local fields = { lpegmatch (commasplitter, s) } +  local ret    = { } +  for i = 1, #fields do +    local field = stringstrip (fields[i]) +    if field and field ~= "" then +      ret[#ret + 1] = field +    end +  end +  return ret +end + +local tohash = function (s) +  local result = { } +  local fields = toarray (s) +  for _, field in next, fields do +    local var, val +    if stringfind (field, "=") then +      local tmp +      var, tmp = lpegmatch (equalssplitter, field) +      if tmp == "true" or tmp == "yes" then val = true else val = tmp end +    else +      var, val = field, true +    end +    result[var] = val +  end +  return result +end + +local option_spec = { +  db = { +    formats      = { +      in_t  = string_t, +      out_t = string_t, +      transform = function (f) +        local fields = toarray (f) + +        --- check validity +        if not fields then +          logreport ("both", 0, "conf", +                     "Expected list of identifiers, got %q.", f) +          return nil +        end + +        --- strip dupes +        local known  = { } +        local result = { } +        for i = 1, #fields do +          local field = fields[i] +          if known[field] ~= true then +            --- yet unknown, tag as seen +            known[field] = true +            --- include in output if valid +            if valid_formats[field] == true then +              result[#result + 1] = field +            else +              logreport ("both", 4, "conf", +                         "Invalid font format identifier %q, ignoring.", +                         field) +            end +          end +        end +        if #result == 0 then +          --- force defaults +          return nil +        end +        return tableconcat (result, ",") +      end +    }, +    scan_local   = { in_t = boolean_t, }, +    skip_read    = { in_t = boolean_t, }, +    strip        = { in_t = boolean_t, }, +    update_live  = { in_t = boolean_t, }, +    compress     = { in_t = boolean_t, }, +    max_fonts    = { +      in_t      = number_t, +      out_t     = number_t, --- TODO int_t from 5.3.x on +      transform = tointeger, +    }, +  }, +  run = { +    resolver = { +      in_t      = string_t, +      out_t     = string_t, +      transform = function (r) return r == "normal" and r or "cached" end, +    }, +    definer = { +      in_t      = string_t, +      out_t     = string_t, +      transform = function (d) return d == "generic" and d or "patch" end, +    }, +    log_level = { +      in_t      = number_t, +      out_t     = number_t, --- TODO int_t from 5.3.x on +      transform = tointeger, +    }, +    color_callback = { +      in_t      = string_t, +      out_t     = string_t, +      transform = function (cb) +        --- These are the two that make sense. +        return cb == "pre_output_filter" and cb or "pre_linebreak_filter" +      end, +    }, +  }, +  misc = { +    bisect          = { in_t = boolean_t, }, --- doesn’t make sense in a config file +    version         = { in_t = string_t,  }, +    statistics      = { in_t = boolean_t, }, +    termwidth       = { +      in_t      = number_t, +      out_t     = number_t, +      transform = function (w) +        w = tointeger (w) +        if w < min_terminal_width then +          return min_terminal_width +        end +        return w +      end, +    }, +  }, +  paths = { +    names_dir           = { in_t = string_t, }, +    cache_dir           = { in_t = string_t, }, +    index_file          = { in_t = string_t, }, +    lookups_file        = { in_t = string_t, }, +    lookup_path_lua     = { in_t = string_t, }, +    lookup_path_luc     = { in_t = string_t, }, +    index_path_lua      = { in_t = string_t, }, +    index_path_luc      = { in_t = string_t, }, +  }, +  default_features = { +    __default = { in_t  = string_t, out_t = table_t, transform = tohash, }, +  }, +} + +------------------------------------------------------------------------------- +---                           MAIN FUNCTIONALITY +------------------------------------------------------------------------------- + +--[[doc-- + +  tilde_expand -- Rudimentary tilde expansion; covers just the “substitute ‘~’ +  by the current users’s $HOME” part. + +--doc]]-- + +local tilde_expand = function (p) +  if #p > 2 then +    if stringsub (p, 1, 2) == "~/" then +      local homedir = osgetenv "HOME" +      if homedir and lfsisdir (homedir) then +        p = filejoin (homedir, stringsub (p, 3)) +      end +    end +  end +  return p +end + +local resolve_config_path = function () +  for i = 1, #config_paths do +    local t, p = unpack (config_paths[i]) +    local fullname +    if t == kpse_t then +      fullname = kpse.lookup (p) +      logreport ("both", 6, "conf", "kpse lookup: %s -> %s.", p, fullname) +    elseif t == path_t then +      local expanded = tilde_expand (p) +      if lfsisfile (expanded) then +        fullname = expanded +      end +      logreport ("both", 6, "conf", "path lookup: %s -> %s.", p, fullname) +    end +    if fullname then +      logreport ("both", 3, "conf", "Reading configuration file at %q.", fullname) +      return fullname +    end +  end +  logreport ("both", 2, "conf", "No configuration file found.") +  return false +end + +local add_config_paths = function (t) +  if not next (t) then +    return +  end +  local result = { } +  for i = 1, #t do +    local path = t[i] +    result[#result + 1] = { path_t, path } +  end +  config_paths = tableappend (result, config_paths) +end + +local process_options = function (opts) +  local new = { } +  for i = 1, #opts do +    local section = opts[i] +    local title = section.section.title +    local vars  = section.variables + +    if not title then --- trigger warning: arrow code ahead +      logreport ("both", 2, "conf", "Section %d lacks a title; skipping.", i) +    elseif not vars then +      logreport ("both", 2, "conf", "Section %d (%s) lacks a variable section; skipping.", i, title) +    else +      local spec = option_spec[title] +      if not spec then +        logreport ("both", 2, "conf", "Section %d (%s) unknown; skipping.", i, title) +      else +        local newsection = new[title] +        if not newsection then +          newsection = { } +          new[title] = newsection +        end + +        for var, val in next, vars do +          local vspec = spec[var] or spec.__default +          local t_val = type (val) +          if not vspec then +            logreport ("both", 2, "conf", +                       "Section %d (%s): invalid configuration variable %q (%q); ignoring.", +                       i, title, +                       var, tostring (val)) +          elseif t_val ~= vspec.in_t then +            logreport ("both", 2, "conf", +                       "Section %d (%s): type mismatch of input value %q (%q, %s != %s); ignoring.", +                       i, title, +                       var, tostring (val), t_val, vspec.in_t) +          else --- type matches +            local transform = vspec.transform +            if transform then +              local dval +              local t_transform = type (transform) +              if t_transform == function_t then +                dval = transform (val) +              elseif t_transform == table_t then +                dval = transform[val] +              end +              if dval then +                local out_t = vspec.out_t +                if out_t then +                  local t_dval = type (dval) +                  if t_dval == out_t then +                    newsection[var] = dval +                  else +                    logreport ("both", 2, "conf", +                               "Section %d (%s): type mismatch of derived value of %q (%q, %s != %s); ignoring.", +                               i, title, +                               var, tostring (dval), t_dval, out_t) +                  end +                else +                  newsection[var] = dval +                end +              else +                logreport ("both", 2, "conf", +                           "Section %d (%s): value of %q could not be derived via %s from input %q; ignoring.", +                           i, title, var, t_transform, tostring (val)) +              end +            else --- insert as is +              newsection[var] = val +            end +          end +        end +      end +    end +  end +  return new +end + +local apply +apply = function (old, new) +  if not new then +    if not old then +      return false +    end +    return tablecopy (old) +  elseif not old then +    return tablecopy (new) +  end +  local result = tablecopy (old) +  for name, section in next, new do +    local t_section = type (section) +    if t_section ~= table_t then +      logreport ("both", 1, "conf", +                 "Error applying configuration: entry %s is %s, expected table.", +                 section, t_section) +      --- ignore +    else +      local currentsection = result[name] +      for var, val in next, section do +        currentsection[var] = val +      end +    end +  end +  result.status = luaotfloadstatus +  return result +end + +local reconfigure = function () +  for i = 1, #reconf_tasks do +    local name, task = unpack (reconf_tasks[i]) +    logreport ("both", 3, "conf", "Launch post-configuration task %q.", name) +    if not task () then +      logreport ("both", 0, "conf", "Post-configuration task %q failed.", name) +      return false +    end +  end +  return true +end + +local read = function (extra) +  if extra then +    add_config_paths (extra) +  end + +  local readme = resolve_config_path () +  if readme == false then +    logreport ("both", 2, "conf", "No configuration file.") +    return false +  end + +  local raw = ioloaddata (readme) +  if not raw then +    logreport ("both", 2, "conf", "Error reading the configuration file %q.", readme) +    return false +  end + +  local parsed = lpegmatch (parsers.config, raw) +  if not parsed then +    logreport ("both", 2, "conf", "Error parsing configuration file %q.", readme) +    return false +  end + +  local ret, msg = process_options (parsed) +  if not ret then +    logreport ("both", 2, "conf", "File %q is not a valid configuration file.", readme) +    logreport ("both", 2, "conf", "Error: %s", msg) +    return false +  end +  return ret +end + +local apply_defaults = function () +  local defaults      = default_config +  local vars          = read () +  --- Side-effects galore ... +  config.luaotfload   = apply (defaults, vars) +  return reconfigure () +end + +------------------------------------------------------------------------------- +---                                 EXPORTS +------------------------------------------------------------------------------- + +luaotfload.default_config = default_config + +config.actions = { +  read             = read, +  apply            = apply, +  apply_defaults   = apply_defaults, +  reconfigure      = reconfigure, +} + diff --git a/luaotfload-database.lua b/src/luaotfload-database.lua index cb3ec33..7a01ca6 100644 --- a/luaotfload-database.lua +++ b/src/luaotfload-database.lua @@ -1,9 +1,9 @@  if not modules then modules = { } end modules ['luaotfload-database'] = { -    version   = "2.4", -    comment   = "companion to luaotfload.lua", +    version   = "2.5", +    comment   = "companion to luaotfload-main.lua",      author    = "Khaled Hosny, Elie Roux, Philipp Gesang",      copyright = "Luaotfload Development Team", -    license   = "GNU GPL v2" +    license   = "GNU GPL v2.0"  }  --[[doc-- @@ -36,13 +36,20 @@ if not modules then modules = { } end modules ['luaotfload-database'] = {  --doc]]-- -local lpeg = require "lpeg" +local lpeg                     = require "lpeg" +local P, Cc, lpegmatch         = lpeg.P, lpeg.Cc, lpeg.match -local P, R, S, lpegmatch -    = lpeg.P, lpeg.R, lpeg.S, lpeg.match +local parsers                  = luaotfload.parsers +local read_fonts_conf          = parsers.read_fonts_conf +local stripslashes             = parsers.stripslashes +local splitcomma               = parsers.splitcomma + +local log                      = luaotfload.log +local report                   = log.report +local report_status            = log.names_status +local report_status_start      = log.names_status_start +local report_status_stop       = log.names_status_stop -local C, Cc, Cf, Cg, Cs, Ct -    = lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Cs, lpeg.Ct  --- Luatex builtins  local load                     = load @@ -60,7 +67,6 @@ local iolines                  = io.lines  local ioopen                   = io.open  local iopopen                  = io.popen  local kpseexpand_path          = kpse.expand_path -local kpseexpand_var           = kpse.expand_var  local kpsefind_file            = kpse.find_file  local kpselookup               = kpse.lookup  local kpsereadable_file        = kpse.readable_file @@ -70,6 +76,7 @@ local lfscurrentdir            = lfs.currentdir  local lfsdir                   = lfs.dir  local mathabs                  = math.abs  local mathmin                  = math.min +local osgetenv                 = os.getenv  local osgettimeofday           = os.gettimeofday  local osremove                 = os.remove  local stringfind               = string.find @@ -106,12 +113,12 @@ local stringis_empty           = string.is_empty  local stringsplit              = string.split  local stringstrip              = string.strip  local tableappend              = table.append +local tablecontains            = table.contains  local tablecopy                = table.copy  local tablefastcopy            = table.fastcopy  local tabletofile              = table.tofile  local tabletohash              = table.tohash  local tableserialize           = table.serialize -local runasscript              = caches == nil  --- the font loader namespace is “fonts”, same as in Context  --- we need to put some fallbacks into place for when running  --- as a script @@ -119,80 +126,19 @@ fonts                          = fonts          or { }  fonts.names                    = fonts.names    or { }  fonts.definers                 = fonts.definers or { } -local luaotfloadconfig         = config.luaotfload --- always present -luaotfloadconfig.resolver      = luaotfloadconfig.resolver or "normal" -luaotfloadconfig.formats       = luaotfloadconfig.formats  or "otf,ttf,ttc,dfont" -luaotfloadconfig.strip         = luaotfloadconfig.strip == true - ---- this option allows for disabling updates ---- during a TeX run -luaotfloadconfig.update_live   = luaotfloadconfig.update_live ~= false -luaotfloadconfig.compress      = luaotfloadconfig.compress ~= false -  local names                    = fonts.names  local name_index               = nil --> upvalue for names.data  local lookup_cache             = nil --> for names.lookups -names.version                  = 2.41 +names.version                  = 2.51  names.data                     = nil      --- contains the loaded database  names.lookups                  = nil      --- contains the lookup cache -names.path                     = { index = { }, lookups = { } } -names.path.globals             = { -    prefix                     = "", --- writable_path/names_dir -    names_dir                  = luaotfloadconfig.names_dir or "names", -    index_file                 = luaotfloadconfig.index_file -                              or "luaotfload-names.lua", -    lookups_file               = "luaotfload-lookup-cache.lua", -} -  --- string -> (string * string)  local make_luanames = function (path)      return filereplacesuffix(path, "lua"),             filereplacesuffix(path, "luc")  end -local report                = logs.names_report -local report_status         = logs.names_status -local report_status_start   = logs.names_status_start -local report_status_stop    = logs.names_status_stop - ---- The “termwidth” value is only considered when printing ---- short status messages, e.g. when building the database ---- online. -if not luaotfloadconfig.termwidth then -    local tw = 79 -    if not (    os.type == "windows" --- Assume broken terminal. -            or os.getenv "TERM" == "dumb") -    then -        local p = iopopen "tput cols" -        if p then -            result = tonumber (p:read "*all") -            p:close () -            if result then -                tw = result -            else -                report ("log", 2, "db", "tput returned non-number.") -            end -        else -            report ("log", 2, "db", "Shell escape disabled or tput executable missing.") -            report ("log", 2, "db", "Assuming 79 cols terminal width.") -        end -    end -    luaotfloadconfig.termwidth = tw -end - -names.patterns          = { } -local patterns          = names.patterns - -local trailingslashes   = P"/"^1 * P(-1) -local stripslashes      = C((1 - trailingslashes)^0) -patterns.stripslashes   = stripslashes - -local comma             = P"," -local noncomma          = 1-comma -local splitcomma        = Ct((C(noncomma^1) + comma)^1) -patterns.splitcomma     = splitcomma -  local format_precedence = {      "otf",   "ttc", "ttf",      "dfont", "afm", "pfb", @@ -208,47 +154,9 @@ local set_location_precedence = function (precedence)  end  --[[doc-- -    We use the functions in the cache.* namespace that come with the -    fontloader (see luat-basics-gen). it’s safe to use for the most part -    since most checks and directory creations are already done. It -    uses TEXMFCACHE or TEXMFVAR as starting points. - -    There is one quirk, though: ``getwritablepath()`` will always -    assume that files in subdirectories of the cache tree are writable. -    It gives no feedback at all if it fails to open a file in write -    mode. This may cause trouble when the index or lookup cache were -    created by different user. ---doc]]-- - -if not runasscript then -    local globals   = names.path.globals -    local names_dir = globals.names_dir -    prefix = getwritablepath (names_dir, "") -    if not prefix then -        luaotfload.error -            ("Impossible to find a suitable writeable cache...") -    else -        prefix = lpegmatch (stripslashes, prefix) -        report ("log", 0, "db", -                "Root cache directory is %s.", prefix) -    end - -    globals.prefix     = prefix -    local lookup_path  = names.path.lookups -    local index        = names.path.index -    local lookups_file = filejoin (prefix, globals.lookups_file) -    local index_file   = filejoin (prefix, globals.index_file) -    lookup_path.lua, lookup_path.luc = make_luanames (lookups_file) -    index.lua, index.luc     = make_luanames (index_file) -else --- running as script, inject some dummies -    caches = { } -    logs   = { report = function () end } -end +    Auxiliary functions - ---[[doc-- -Auxiliary functions  --doc]]--  --- fontnames contain all kinds of garbage; as a precaution we @@ -343,7 +251,6 @@ This is a sketch of the luaotfload db:          status      : filestatus;          mappings    : fontentry list;          meta        : metadata; -        names       : namedata; // TODO: check for relevance after db is finalized      }      and familytable = {          local  : (format, familyentry) hash; // specified with include dir @@ -351,21 +258,24 @@ This is a sketch of the luaotfload db:          system : (format, familyentry) hash;      }      and familyentry = { -        regular     : sizes; -        italic      : sizes; -        bold        : sizes; -        bolditalic  : sizes; +        r  : sizes; // regular +        i  : sizes; // italic +        b  : sizes; // bold +        bi : sizes; // bold italic      }      and sizes = {          default : int;              // points into mappings or names          optical : (int, int) list;  // design size -> index entry      }      and metadata = { +        created     : string       // creation time          formats     : string list; // { "otf", "ttf", "ttc", "dfont" } -        statistics  : TODO; -        version     : float; +        local       : bool;        (* set if local fonts were added to the db *) +        modified    : string       // modification time +        statistics  : TODO;        // created when built with "--stats" +        version     : float;       // index version      } -    and filemap = { +    and filemap = { // created by generate_filedata()          base : {              local  : (string, int) hash; // basename -> idx              system : (string, int) hash; @@ -378,36 +288,36 @@ This is a sketch of the luaotfload db:          };          full : (int, string) hash; // idx -> full path      } -    and fontentry = { -        barename    : string; -        familyname  : string; -        filename    : string; -        fontname    : string; // <- metadata -        fullname    : string; // <- metadata -        sanitized   : { -            family         : string; -            fontstyle_name : string; // <- new in 2.4 -            fontname       : string; // <- metadata -            fullname       : string; // <- namedata.names -            metafamily     : string; -            pfullname      : string; -            prefmodifiers  : string; -            psname         : string; -            subfamily      : string; -        }; -        size         : int list; -        slant        : int; -        subfont      : int; -        location     : local | system | texmf; -        weight       : int; -        width        : int; -        units_per_em : int;         // mainly 1000, but also 2048 or 256 +    and fontentry = { // finalized by collect_families() +        basename        : string;   // file name without path "foo.otf" +        conflicts       : { barename : int; basename : int }; // filename conflict with font at index; happens with subfonts +        familyname      : string;   // sanitized name of the font family the font belongs to, usually from the names table +        fontname        : string;   // sanitized name of the font +        fontstyle_name  : string;   // the fontstyle_name field returned by fontloader.info() +        format          : string;   // "otf" | "ttf" | "dfont" | "pfa" | "pfb" | "afm" +        fullname        : string;   // sanitized full name of the font including style modifiers +        fullpath        : string;   // path to font in filesystem +        index           : int;      // index in the mappings table +        italicangle     : float;    // italic angle; non-zero with oblique faces +        location        : string;   // "texmf" | "system" | "local" +        metafamily      : string;   // alternative family identifier if appropriate, sanitized +        plainname       : string;   // unsanitized font name +        prefmodifiers   : string;   // sanitized preferred subfamily (names table 14) +        psname          : string;   // PostScript name +        size            : (false | float * float * float);  // if available, size info from the size table converted from decipoints +        splainname      : string;   // sanitized version of the “plainname” field +        splitstyle      : string;   // style information obtained by splitting the full name at the last dash +        subfamily       : string;   // sanitized subfamily (names table 2) +        subfont         : (int | bool);     // integer if font is part of a TrueType collection ("ttc") +        version         : string;   // font version string +        weight          : int;      // usWeightClass      }      and filestatus = (string,       // fullname                        { index       : int list; // pointer into mappings                          timestamp   : int;      }) dict -beware that this is a reconstruction and may be incomplete. +beware that this is a reconstruction and may be incomplete or out of +date. Last update: 2014-04-06, describing version 2.51.  mtx-fonts has in names.tma: @@ -444,7 +354,10 @@ mtx-fonts has in names.tma:  --doc]]-- -local initialize_namedata = function (formats) --- returns dbobj +--- string list -> string option -> dbobj + +local initialize_namedata = function (formats, created) +    local now = os.date "%F %T"      return {          --families        = { },          status          = { }, -- was: status; map abspath -> mapping @@ -452,7 +365,10 @@ local initialize_namedata = function (formats) --- returns dbobj          names           = { },  --      files           = { }, -- created later          meta            = { +            created    = created or now,              formats    = formats, +            ["local"]  = false, +            modified   = now,              statistics = { },              version    = names.version,          }, @@ -536,29 +452,32 @@ local load_lua_file = function (path)  end  --- define locals in scope -local crude_file_lookup -local crude_file_lookup_verbose +local access_font_index +local collect_families +local font_file_lookup  local find_closest  local flush_lookup_cache -local ot_fullinfo -local t1_fullinfo -local load_names +local generate_filedata +local get_font_filter +local group_modifiers  local load_lookups +local load_names +local getmetadata +local order_design_sizes +local ot_fullinfo  local read_blacklist -local read_fonts_conf  local reload_db -local resolve_name  local resolve_cached  local resolve_fullpath -local save_names +local resolve_name  local save_lookups -local update_names -local get_font_filter +local save_names  local set_font_filter +local t1_fullinfo +local update_names  --- state of the database  local fonts_reloaded = false -local fonts_read     = 0  --- limit output when approximate font matching (luaotfload-tool -F)  local fuzzy_limit = 1 --- display closest only @@ -566,15 +485,17 @@ local fuzzy_limit = 1 --- display closest only  --- bool? -> dbobj  load_names = function (dry_run)      local starttime = osgettimeofday () -    local foundname, data = load_lua_file (names.path.index.lua) +    local foundname, data = load_lua_file (config.luaotfload.paths.index_path_lua)      if data then -        report ("both", 2, "db", -                "Font names database loaded", "%s", foundname) +        report ("log", 0, "db", +                "Font names database loaded from %s", foundname) +        report ("term", 3, "db", +                "Font names database loaded from %s", foundname)          report ("info", 3, "db", "Loading took %0.f ms.",                  1000 * (osgettimeofday () - starttime)) -        local db_version, nms_version +        local db_version, names_version          if data.meta then              db_version = data.meta.version          else @@ -583,11 +504,11 @@ load_names = function (dry_run)              --- an earlier index version broke.              db_version = data.version or -42 --- invalid          end -        nms_version = names.version -        if db_version ~= nms_version then +        names_version = names.version +        if db_version ~= names_version then              report ("both", 0, "db",                      [[Version mismatch; expected %4.3f, got %4.3f.]], -                    nms_version, db_version) +                    names_version, db_version)              if not fonts_reloaded then                  report ("both", 0, "db", [[Force rebuild.]])                  data = update_names ({ }, true, false) @@ -611,11 +532,29 @@ load_names = function (dry_run)      return data  end +--[[doc-- + +    access_font_index -- Provide a reference of the index table. Will +    cause the index to be loaded if not present. + +--doc]]-- + +access_font_index = function () +    if not name_index then name_index = load_names () end +    return name_index +end + +getmetadata = function () +    if not name_index then name_index = load_names() end +    return tablefastcopy (name_index.meta) +end +  --- unit -> unit  load_lookups = function ( ) -    local foundname, data = load_lua_file(names.path.lookups.lua) +    local foundname, data = load_lua_file(config.luaotfload.paths.lookup_path_lua)      if data then -        report("both", 3, "cache", +        report("log", 0, "cache", "Lookup cache loaded from %s.", foundname) +        report("term", 3, "cache",                 "Lookup cache loaded from %s.", foundname)      else          report("both", 1, "cache", @@ -650,57 +589,10 @@ local style_category = {      i           = "italic",  } -local type1_formats = { "tfm", "ofm", } +local type1_metrics = { "tfm", "ofm", }  local dummy_findfile = resolvers.findfile -- from basics-gen ---- filemap -> string -> string -> (string | bool) -local verbose_lookup = function (data, kind, filename) -    local found = data[kind][filename] -    if found ~= nil then -        found = data.full[found] -        if found == nil then --> texmf -            report("info", 0, "db", -                "Crude file lookup: req=%s; hit=%s => kpse.", -                filename, kind) -            found = dummy_findfile(filename) -        else -            report("info", 0, "db", -                "Crude file lookup: req=%s; hit=%s; ret=%s.", -                filename, kind, found) -        end -        return found -    end -    return false -end - ---- string -> (string * string * bool) -crude_file_lookup_verbose = function (filename) -    if not name_index then name_index = load_names() end -    local mappings  = name_index.mappings -    local files     = name_index.files -    local found - -    --- look up in db first ... -    found = verbose_lookup(files, "bare", filename) -    if found then -        return found, nil, true -    end -    found = verbose_lookup(files, "base", filename) -    if found then -        return found, nil, true -    end - -    --- ofm and tfm, returns pair -    for i=1, #type1_formats do -        local format = type1_formats[i] -        if resolvers.findfile(filename, format) then -            return file.addsuffix(filename, format), format, true -        end -    end -    return filename, nil, false -end -  local lookup_filename = function (filename)      if not name_index then name_index = load_names () end      local files    = name_index.files @@ -736,8 +628,18 @@ local lookup_filename = function (filename)      end  end ---- string -> (string * string * bool) -crude_file_lookup = function (filename) +--[[doc-- + +    font_file_lookup -- The ``file:`` are ultimately delegated here. +    The lookups are kind of a blunt instrument since they try locating +    the file using every conceivable method, which is quite +    inefficient. Nevertheless, resolving files that way is rarely the +    bottleneck. + +--doc]]-- + +--- string -> string * string * bool +font_file_lookup = function (filename)      local found = lookup_filename (filename)      if not found then @@ -748,21 +650,37 @@ crude_file_lookup = function (filename)          return found, nil, true      end -    for i=1, #type1_formats do -        local format = type1_formats[i] +    for i=1, #type1_metrics do +        local format = type1_metrics[i]          if resolvers.findfile(filename, format) then              return file.addsuffix(filename, format), format, true          end      end +    if not fonts_reloaded and config.luaotfload.db.update_live == true then +        return reload_db (stringformat ("File not found: %s.", filename), +                          font_file_lookup, +                          filename) +    end      return filename, nil, false  end  --[[doc-- -Existence of the resolved file name is verified differently depending -on whether the index entry has a texmf flag set. + +    get_font_file -- Look up the file of an entry in the mappings +    table. If the index is valid, pass on the name and subfont index +    after verifing the existence of the resolved file. This +    verification differs depending the index entry’s ``location`` +    field: + +        * ``texmf`` fonts are verified using the (slow) function +          ``kpse.lookup()``; +        * other locations are tested by resolving the full path and +          checking for the presence of a file there. +  --doc]]-- +--- int -> bool * (string * int) option  local get_font_file = function (index)      local entry = name_index.mappings [index]      if not entry then @@ -809,29 +727,20 @@ We’ll just store successful name: lookups in a separate cache file.  type lookup_cache = (string, (string * num)) dict -Complete, needs testing: - ×  1) add cache to dbobj - ×  2) wrap lookups in cached versions - ×  3) make caching optional (via the config table) for debugging - ×  4) make names_update() cache aware (nil if “force”) - ×  5) add logging - ×  6) add cache control to luaotfload-tool - ×  7) incr db version (now 2.203) - ×  8) save cache only at the end of a run - -The spec is modified in place (ugh), so we’ll have to catalogue what -fields actually influence its behavior. +The spec is expected to be modified in place (ugh), so we’ll have to +catalogue what fields actually influence its behavior.  Idk what the “spec” resolver is for.          lookup      inspects            modifies +        ----------  -----------------   ---------------------------          file:       name                forced, name -        name:*      name, style, sub,   resolved, sub, name, forced +        name:[*]    name, style, sub,   resolved, sub, name, forced                      optsize, size          spec:       name, sub           resolved, sub, name, forced -* name: contains both the name resolver from luatex-fonts and -  resolve_name() below +[*] name: contains both the name resolver from luatex-fonts and +    resolve_name() below  From my reading of font-def.lua, what a resolver does is  basically rewrite the “name” field of the specification record @@ -1178,13 +1087,11 @@ resolve_name = function (specification)                                                name,                                                style)      end -    if not resolved then -        resolved = specification.name, false -    end      if not resolved then -        if not fonts_reloaded then -            return reload_db ("Font not found.", +        if not fonts_reloaded and config.luaotfload.db.update_live == true then +            return reload_db (stringformat ("Font %s not found.", +                                            specification.name or "<?>"),                                resolve_name,                                specification)          end @@ -1225,7 +1132,7 @@ reload_db = function (why, caller, ...)      local namedata  = name_index      local formats   = tableconcat (namedata.meta.formats, ",") -    report ("both", 1, "db", +    report ("both", 0, "db",              "Reload initiated (formats: %s); reason: %q.",              formats, why) @@ -1277,7 +1184,7 @@ find_closest = function (name, limit)      if not name_index then name_index = load_names () end      if not name_index or type (name_index) ~= "table" then          if not fonts_reloaded then -            return reload_db("no database", find_closest, name) +            return reload_db("Font index missing.", find_closest, name)          end          return false      end @@ -1345,10 +1252,18 @@ end --- find_closest()      <http://www.ntg.nl/pipermail/ntg-context/2013/075885.html>      regarding the omission of ``fontloader.close()``. +    TODO --   check if fontloader.info() is ready for prime in 0.78+ +         --   fields /tables needed: +                    -- names +                    -- postscriptname +                    -- validation_state +                    -- .. +  --doc]]--  local load_font_file = function (filename, subfont)      local rawfont, _msg = fontloaderopen (filename, subfont) +    --local rawfont, _msg = fontloaderinfo (filename, subfont)      if not rawfont then          report ("log", 1, "db", "ERROR: failed to open %s.", filename)          return @@ -1379,42 +1294,87 @@ local get_size_info = function (metadata)      return false  end -local get_english_names = function (names, basename) - +local get_english_names = function (metadata) +    local names = metadata.names      local english_names      if names then          --inspect(names)          for _, raw_namedata in next, names do              if raw_namedata.lang == "English (US)" then -                english_names = raw_namedata.names +                return raw_namedata.names              end          end -    else -        -- no names table, probably a broken font -        report("log", 1, "db", -               "Broken font %s rejected due to missing names table.", +    end + +    -- no (English) names table, probably a broken font +    report("both", 3, "db", +            "%s: missing or broken English names table.", basename) +    return { fontname = metadata.fontname, +             fullname = metadata.fullname, } +end + +--[[-- +    In case of broken PS names we set some dummies. However, we cannot +    directly modify the font data as returned by fontloader.open() because +    it is a userdata object. + +    For this reason we copy what is necessary whilst keeping the table +    structure the same as in the tfmdata. +--]]-- +local get_raw_info = function (metadata, basename) +    local fullname +    local fontname = metadata.fontname +    local fullname = metadata.fullname +    local psname + +    local validation_state = metadata.validation_state +    if (validation_state and tablecontains (validation_state, "bad_ps_fontname")) +        or not fontname +    then +        --- Broken names table, e.g. avkv.ttf with UTF-16 strings; +        --- we put some dummies in place like the fontloader +        --- (font-otf.lua) does. +        report("both", 3, "db", +               "%s has invalid postscript font names, using dummies.",                 basename) +        fontname = "bad-fontname-" .. basename +        fullname = "bad-fullname-" .. basename      end -    return english_names or { } +    return { +        familyname          = metadata.familyname, +        fontname            = fontname, +        fontstyle_name      = metadata.fontstyle_name, +        fullname            = fullname, +        italicangle         = metadata.italicangle, +        names               = metadata.names, +        pfminfo             = metadata.pfminfo, +        units_per_em        = metadata.units_per_em, +        version             = metadata.version, +        design_size         = metadata.design_size, +        design_range_top    = metadata.design_range_top, +        design_range_bottom = metadata.design_range_bottom, +    }  end -local organize_namedata = function (metadata, +local organize_namedata = function (rawinfo,                                      english_names,                                      basename,                                      info)      local default_name = english_names.compatfull                        or english_names.fullname                        or english_names.postscriptname -                      or metadata.fullname -                      or metadata.fontname -                      or info.fullname --- TODO check if fontloader.info() is ready for prime +                      or rawinfo.fullname +                      or rawinfo.fontname +                      or info.fullname                        or info.fontname      local default_family = english_names.preffamily                          or english_names.family -                        or metadata.familyname +                        or rawinfo.familyname                          or info.familyname +--    local default_modifier = english_names.prefmodifiers +--                          or english_names.subfamily      local fontnames = {          --- see          --- https://developer.apple.com/fonts/TTRefMan/RM06/Chap6name.html @@ -1445,9 +1405,9 @@ local organize_namedata = function (metadata,          },          metadata = { -            fullname      = metadata.fullname, -            fontname      = metadata.fontname, -            familyname    = metadata.familyname, +            fullname      = rawinfo.fullname, +            fontname      = rawinfo.fontname, +            familyname    = rawinfo.familyname,          },          info = { @@ -1458,14 +1418,14 @@ local organize_namedata = function (metadata,      }      -- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size -    if metadata.fontstyle_name then +    if rawinfo.fontstyle_name then          --- not present in all fonts, often differs from the preferred          --- subfamily as well as subfamily fields, e.g. with          --- LMSans10-BoldOblique:          ---     subfamily:      “Bold Italic”          ---     prefmodifiers:  “10 Bold Oblique”          ---     fontstyle_name: “Bold Oblique” -        for _, name in next, metadata.fontstyle_name do +        for _, name in next, rawinfo.fontstyle_name do              if name.lang == 1033 then --- I hate magic numbers                  fontnames.fontstyle_name = name.name              end @@ -1474,11 +1434,10 @@ local organize_namedata = function (metadata,      return {          sanitized     = sanitize_fontnames (fontnames), -        fontname      = metadata.fontname, -        fullname      = metadata.fullname, -        familyname    = metadata.familyname, +        fontname      = rawinfo.fontname, +        fullname      = rawinfo.fullname, +        familyname    = rawinfo.familyname,      } -  end @@ -1498,23 +1457,16 @@ local organize_styledata = function (fontname,                                       metadata,                                       english_names,                                       info) -    local pfminfo   = metadata.pfminfo +    local pfminfo   = metadata.pfminfo or { }      local names     = metadata.names      return { -    -- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size +    --- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size          size            = get_size_info (metadata), -        weight          = { -            pfminfo.weight,                     -- integer (multiple of 100?) -            sanitize_fontname (info.weight),    -- style name -        }, +        weight          = pfminfo.weight or 400,          split           = split_fontname (fontname),          width           = pfminfo.width,          italicangle     = metadata.italicangle, ---        italicangle     = { ---            metadata.italicangle,   -- float ---            info.italicangle,       -- truncated to integer point size? ---        },      --- this is for querying, see www.ntg.nl/maps/40/07.pdf for details          units_per_em    = metadata.units_per_em,          version         = metadata.version, @@ -1541,13 +1493,18 @@ ot_fullinfo = function (filename,          return nil      end -    local english_names = get_english_names (metadata.names, basename) -    local namedata      = organize_namedata (metadata, +    local rawinfo = get_raw_info (metadata, basename) +    --- Closing the file manually is a tad faster and more memory +    --- efficient than having it closed by the gc +    fontloaderclose (metadata) + +    local english_names = get_english_names (rawinfo) +    local namedata      = organize_namedata (rawinfo,                                               english_names,                                               basename,                                               info)      local style         = organize_styledata (namedata.fontname, -                                              metadata, +                                              rawinfo,                                                english_names,                                                info) @@ -1559,11 +1516,8 @@ ot_fullinfo = function (filename,          format          = format,          names           = namedata,          style           = style, -        version         = metadata.version, +        version         = rawinfo.version,      } -    --- Closing the file manually is a tad faster and more memory -    --- efficient than having it closed by the gc -    fontloaderclose (metadata)      return res  end @@ -1626,8 +1580,7 @@ t1_fullinfo = function (filename, _subfont, location, basename, format)          size             = false,          splitstyle       = splitstyle,          fontstyle_name   = style ~= "" and style or weight, -        weight           = { metadata.pfminfo.weight, -                             weight }, +        weight           = metadata.pfminfo.weight or 400,          italicangle      = italicangle,      }  end @@ -1946,13 +1899,16 @@ read_blacklist = function ()      local whitelist = { }      if files and type(files) == "table" then -        for _,v in next, files do -            for line in iolines(v) do +        for _, path in next, files do +            for line in iolines (path) do                  line = stringstrip(line) -- to get rid of lines like " % foo" -                local first_chr = stringsub(line, 1, 1) --- faster than find +                local first_chr = stringsub(line, 1, 1)                  if first_chr == "%" or stringis_empty(line) then                      -- comment or empty line                  elseif first_chr == "-" then +                    report ("both", 3, "db", +                            "Whitelisted file %q via %q.", +                            line, path)                      whitelist[#whitelist+1] = stringsub(line, 2, -1)                  else                      local cmt = stringfind(line, "%%") @@ -1960,7 +1916,9 @@ read_blacklist = function ()                          line = stringsub(line, 1, cmt - 1)                      end                      line = stringstrip(line) -                    report("log", 2, "db", "Blacklisted file %q.", line) +                    report ("both", 3, "db", +                            "Blacklisted file %q via %q.", +                            line, path)                      blacklist[#blacklist+1] = line                  end              end @@ -2033,9 +1991,6 @@ do      get_font_filter = function (formats)          return tablefastcopy (current_formats)      end - -    --- initialize -    set_font_filter (luaotfloadconfig.formats)  end  local process_dir_tree @@ -2138,7 +2093,7 @@ end  --- indicates the number of characters already consumed on the  --- line.  local truncate_string = function (str, restrict) -    local tw  = luaotfloadconfig.termwidth +    local tw  = config.luaotfload.misc.termwidth      local wd  = tw - restrict      local len = utf8len (str)      if wd - len < 0 then @@ -2148,73 +2103,44 @@ local truncate_string = function (str, restrict)      return str  end ---[[doc-- -    scan_dir() scans a directory and populates the list of fonts -    with all the fonts it finds. +--[[doc-- -        · dirname       : name of the directory to scan -        · currentnames  : current font db object -        · targetnames   : font db object to fill -        · dry_run       : don’t touch anything +    collect_font_filenames_dir -- Traverse the directory root at +    ``dirname`` looking for font files. Returns a list of {*filename*; +    *location*} pairs.  --doc]]-- ---- string -> dbobj -> dbobj -> bool -> bool -> (int * int) - -local scan_dir = function (dirname, currentnames, targetnames, -                           dry_run, location) +--- string -> string -> string * string list +local collect_font_filenames_dir = function (dirname, location)      if lpegmatch (p_blacklist, dirname) then          report ("both", 4, "db",                  "Skipping blacklisted directory %s.", dirname)          --- ignore -        return 0, 0 +        return { }      end -    local found = find_font_files (dirname, location ~= "texmf") +    local found = find_font_files (dirname, location ~= "texmf" and location ~= "local")      if not found then          report ("both", 4, "db",                  "No such directory: %q; skipping.", dirname) -        return 0, 0 +        return { }      end -    report ("both", 4, "db", "Scanning directory %s.", dirname) -    local n_new         = 0   --- total of fonts collected -    local n_found       = #found -    local max_fonts     = luaotfloadconfig.max_fonts - -    report ("both", 4, "db", "%d font files detected.", n_found) -    for j=1, n_found do -        if max_fonts and fonts_read >= max_fonts then -            break -        end +    local nfound = #found +    local files  = { } +    report ("both", 4, "db", +            "%d font files detected in %s.", +            nfound, dirname) +    for j = 1, nfound do          local fullname = found[j] -        fullname = path_normalize(fullname) -        local new - -        if dry_run == true then -            local truncated = truncate_string (fullname, 43) -            report ("log", 2, "db", -                    "Would have been loading %s.", fullname) -            report_status ("term", "db", -                           "Would have been loading %s", truncated) -        else -            local truncated = truncate_string (fullname, 32) -            report ("log", 2, "db", "Loading font %s.", fullname) -            report_status ("term", "db", "Loading font %s", truncated) -            local new = read_font_names (fullname, currentnames, -                                         targetnames, texmf) -            if new == true then -                fonts_read = fonts_read + 1 -                n_new = n_new + 1 -            end -        end +        files[#files + 1] = { path_normalize (fullname), location }      end -    report ("both", 4, "db", "Done. %d fonts indexed in %q.", -            n_found, dirname) -    return n_found, n_new +    return files  end +  --- string list -> string list  local filter_out_pwd = function (dirs)      local result = { } @@ -2233,27 +2159,29 @@ end  local path_separator = ostype == "windows" and ";" or ":"  --[[doc-- -    scan_texmf_fonts() scans all fonts in the texmf tree through the -    kpathsea variables OPENTYPEFONTS and TTFONTS of texmf.cnf. + +    collect_font_filenames_texmf -- Scan texmf tree for font files +    relying on the kpathsea variables $OPENTYPEFONTS and $TTFONTS of +    texmf.cnf.      The current working directory comes as “.” (texlive) or absolute      path (miktex) and will always be filtered out. ---doc]]-- ---- dbobj -> dbobj -> bool? -> (int * int) +    Returns a list of { *filename*; *location* } pairs. + +--doc]]-- -local scan_texmf_fonts = function (currentnames, targetnames, dry_run) +--- unit -> string * string list +local collect_font_filenames_texmf = function () -    local n_scanned, n_new, fontdirs = 0, 0      local osfontdir = kpseexpand_path "$OSFONTDIR"      if stringis_empty (osfontdir) then -        report ("info", 1, "db", "Scanning TEXMF fonts...") +        report ("info", 1, "db", "Scanning TEXMF for fonts...")      else -        report ("info", 1, "db", "Scanning TEXMF and OS fonts...") -        if logs.get_loglevel () > 3 then +        report ("info", 1, "db", "Scanning TEXMF and $OSFONTDIR for fonts...") +        if log.get_loglevel () > 3 then              local osdirs = filesplitpath (osfontdir) -            report ("info", 0, "db", -                    "$OSFONTDIR has %d entries:", #osdirs) +            report ("info", 0, "db", "$OSFONTDIR has %d entries:", #osdirs)              for i = 1, #osdirs do                  report ("info", 0, "db", "[%d] %s", i, osdirs[i])              end @@ -2264,255 +2192,22 @@ local scan_texmf_fonts = function (currentnames, targetnames, dry_run)      fontdirs = fontdirs .. path_separator .. kpseexpand_path "$TTFONTS"      fontdirs = fontdirs .. path_separator .. kpseexpand_path "$T1FONTS" -    if not stringis_empty (fontdirs) then -        local tasks = filter_out_pwd (filesplitpath (fontdirs)) -        report ("info", 3, "db", -                "Initiating scan of %d directories.", #tasks) -        report_status_start (2, 4) -        for _, d in next, tasks do -            local found, new = scan_dir (d, currentnames, targetnames, -                                         dry_run, "texmf") -            n_scanned = n_scanned + found -            n_new     = n_new     + new -        end -        report_status_stop ("term", "db", "Scanned %d files, %d new.", n_scanned, n_new) -    end - -    return n_scanned, n_new -end - ---[[ -  For the OS fonts, there are several options: -   - if OSFONTDIR is set (which is the case under windows by default but -     not on the other OSs), it scans it at the same time as the texmf tree, -     in the scan_texmf_fonts. -   - if not: -     - under Windows and Mac OSX, we take a look at some hardcoded directories -     - under Unix, we read /etc/fonts/fonts.conf and read the directories in it - -  This means that if you have fonts in fancy directories, you need to set them -  in OSFONTDIR. -]] - -local read_fonts_conf -do --- closure for read_fonts_conf() - -    local alpha             = R("az", "AZ") -    local digit             = R"09" -    local tag_name          = C(alpha^1) -    local whitespace        = S" \n\r\t\v" -    local ws                = whitespace^1 -    local comment           = P"<!--" * (1 - P"--")^0 * P"-->" - -    ---> header specifica -    local xml_declaration   = P"<?xml" * (1 - P"?>")^0 * P"?>" -    local xml_doctype       = P"<!DOCTYPE" * ws -                            * "fontconfig" * (1 - P">")^0 * P">" -    local header            = xml_declaration^-1 -                            * (xml_doctype + comment + ws)^0 - -    ---> enforce root node -    local root_start        = P"<"  * ws^-1 * P"fontconfig" * ws^-1 * P">" -    local root_stop         = P"</" * ws^-1 * P"fontconfig" * ws^-1 * P">" - -    local dquote, squote    = P[["]], P"'" -    local xml_namestartchar = S":_" + alpha --- ascii only, funk the rest -    local xml_namechar      = S":._" + alpha + digit -    local xml_name          = ws^-1 -                            * C(xml_namestartchar * xml_namechar^0) -    local xml_attvalue      = dquote * C((1 - S[[%&"]])^1) * dquote * ws^-1 -                            + squote * C((1 - S[[%&']])^1) * squote * ws^-1 -    local xml_attr          = Cg(xml_name * P"=" * xml_attvalue) -    local xml_attr_list     = Cf(Ct"" * xml_attr^1, rawset) - -    --[[doc-- -         scan_node creates a parser for a given xml tag. -    --doc]]-- -    --- string -> bool -> lpeg_t -    local scan_node = function (tag) -        --- Node attributes go into a table with the index “attributes” -        --- (relevant for “prefix="xdg"” and the likes). -        local p_tag = P(tag) -        local with_attributes   = P"<" * p_tag -                                * Cg(xml_attr_list, "attributes")^-1 -                                * ws^-1 -                                * P">" -        local plain             = P"<" * p_tag * ws^-1 * P">" -        local node_start        = plain + with_attributes -        local node_stop         = P"</" * p_tag * ws^-1 * P">" -        --- there is no nesting, the earth is flat ... -        local node              = node_start -                                * Cc(tag) * C(comment + (1 - node_stop)^1) -                                * node_stop -        return Ct(node) -- returns {string, string [, attributes = { key = val }] } -    end - -    --[[doc-- -         At the moment, the interesting tags are “dir” for -         directory declarations, and “include” for including -         further configuration files. - -         spec: http://freedesktop.org/software/fontconfig/fontconfig-user.html -    --doc]]-- -    local include_node        = scan_node"include" -    local dir_node            = scan_node"dir" - -    local element             = dir_node -                              + include_node -                              + comment         --> ignore -                              + P(1-root_stop)  --> skip byte - -    local root                = root_start * Ct(element^0) * root_stop -    local p_cheapxml          = header * root - -    --lpeg.print(p_cheapxml) ---> 757 rules with v0.10 - -    --[[doc-- -         fonts_conf_scanner() handles configuration files. -         It is called on an abolute path to a config file (e.g. -         /home/luser/.config/fontconfig/fonts.conf) and returns a list -         of the nodes it managed to extract from the file. -    --doc]]-- -    --- string -> path list -    local fonts_conf_scanner = function (path) -        local fh = ioopen(path, "r") -        if not fh then -            report("both", 3, "db", "Cannot open fontconfig file %s.", path) -            return -        end -        local raw = fh:read"*all" -        fh:close() - -        local confdata = lpegmatch(p_cheapxml, raw) -        if not confdata then -            report("both", 3, "db", "Cannot scan fontconfig file %s.", path) -            return -        end -        return confdata -    end - -    local p_conf   = P".conf" * P(-1) -    local p_filter = (1 - p_conf)^1 * p_conf - -    local conf_filter = function (path) -        if lpegmatch (p_filter, path) then -            return true -        end -        return false +    if stringis_empty (fontdirs) then +        return { }      end -    --[[doc-- -         read_fonts_conf_indeed() is called with six arguments; the -         latter three are tables that represent the state and are -         always returned. -         The first three are -             · the path to the file -             · the expanded $HOME -             · the expanded $XDG_CONFIG_DIR -    --doc]]-- -    --- string -> string -> string -> tab -> tab -> (tab * tab * tab) -    local read_fonts_conf_indeed -    read_fonts_conf_indeed = function (start, home, xdg_home, -                                       acc, done, dirs_done) - -        local paths = fonts_conf_scanner(start) -        if not paths then --- nothing to do -            return acc, done, dirs_done -        end - -        for i=1, #paths do -            local pathobj = paths[i] -            local kind, path = pathobj[1], pathobj[2] -            local attributes = pathobj.attributes -            if attributes and attributes.prefix == "xdg" then -                --- this prepends the xdg root (usually ~/.config) -                path = filejoin(xdg_home, path) -            end - -            if kind == "dir" then -                if stringsub(path, 1, 1) == "~" then -                    path = filejoin(home, stringsub(path, 2)) -                end -                --- We exclude paths with texmf in them, as they should be -                --- found anyway; also duplicates are ignored by checking -                --- if they are elements of dirs_done. -                --- -                --- FIXME does this mean we cannot access paths from -                --- distributions (e.g. Context minimals) installed -                --- separately? -                if not (stringfind(path, "texmf") or dirs_done[path]) then -                    acc[#acc+1] = path -                    dirs_done[path] = true -                end +    local tasks = filter_out_pwd (filesplitpath (fontdirs)) +    report ("info", 3, "db", +            "Initiating scan of %d directories.", #tasks) -            elseif kind == "include" then -                --- here the path can be four things: a directory or a file, -                --- in absolute or relative path. -                if stringsub(path, 1, 1) == "~" then -                    path = filejoin(home, stringsub(path, 2)) -                elseif --- if the path is relative, we make it absolute -                    not ( lfsisfile(path) or lfsisdir(path) ) -                then -                    path = filejoin(filedirname(start), path) -                end -                if  lfsisfile(path) -                    and kpsereadable_file(path) -                    and not done[path] -                then -                    --- we exclude path with texmf in them, as they should -                    --- be found otherwise -                    acc = read_fonts_conf_indeed( -                                path, home, xdg_home, -                                acc,  done, dirs_done) -                elseif lfsisdir(path) then --- arrow code ahead -                    local config_files = find_files (path, conf_filter) -                    for _, filename in next, config_files do -                        if not done[filename] then -                            acc = read_fonts_conf_indeed( -                            filename, home, xdg_home, -                            acc,      done, dirs_done) -                        end -                    end -                end --- match “kind” -            end --- iterate paths -        end - -        --inspect(acc) -        --inspect(done) -        return acc, done, dirs_done -    end --- read_fonts_conf_indeed() - -    --[[doc-- -         read_fonts_conf() sets up an accumulator and two sets -         for tracking what’s been done. - -         Also, the environment variables HOME and XDG_CONFIG_HOME -- -         which are constants anyways -- are expanded so don’t have to -         repeat that over and over again as with the old parser. -         Now they’re just passed on to every call of -         read_fonts_conf_indeed(). - -         read_fonts_conf() is also the only reference visible outside -         the closure. -    --doc]]-- -    --- list -> list -    read_fonts_conf = function (path_list) -        local home      = kpseexpand_path"~" --- could be os.getenv"HOME" -        local xdg_home  = kpseexpand_path"$XDG_CONFIG_HOME" -        if xdg_home == "" then xdg_home = filejoin(home, ".config") end -        local acc       = { } ---> list: paths collected -        local done      = { } ---> set:  files inspected -        local dirs_done = { } ---> set:  dirs in list -        for i=1, #path_list do --- we keep the state between files -            acc, done, dirs_done = read_fonts_conf_indeed( -                                     path_list[i], home, xdg_home, -                                     acc, done, dirs_done) -        end -        return acc +    local files = { } +    for _, dir in next, tasks do +        files = tableappend (files, collect_font_filenames_dir (dir, "texmf"))      end -end --- read_fonts_conf closure +    report ("term", 3, "db", "Collected %d files.", #files) +    return files +end ---- TODO stuff those paths into some writable table  --- unit -> string list  local function get_os_dirs ()      if os.name == 'macosx' then @@ -2523,14 +2218,14 @@ local function get_os_dirs ()              "/Network/Library/Fonts",          }      elseif os.type == "windows" or os.type == "msdos" then -        local windir = os.getenv("WINDIR") +        local windir = osgetenv("WINDIR")          return { filejoin(windir, 'Fonts') }      else          local fonts_conves = { --- plural, much?              "/usr/local/etc/fonts/fonts.conf",              "/etc/fonts/fonts.conf",          } -        local os_dirs = read_fonts_conf(fonts_conves) +        local os_dirs = read_fonts_conf(fonts_conves, find_files)          return os_dirs      end      return {} @@ -2538,51 +2233,118 @@ end  --[[doc-- -    scan_os_fonts() scans the OS fonts through -      - fontconfig for Unix (reads the fonts.conf file[s] and scans the -        directories) -      - a static set of directories for Windows and MacOSX +    retrieve_namedata -- Scan the list of collected fonts and populate +    the list of namedata. + +        · dirname       : name of the directory to scan +        · currentnames  : current font db object +        · targetnames   : font db object to fill +        · dry_run       : don’t touch anything -    **NB**: If $OSFONTDIR is nonempty, as it appears to be by default -            on Windows setups, the system fonts will have already been -            processed while scanning the TEXMF. Thus, this function is -            never called. +    Returns the number of fonts that were actually added to the index.  --doc]]-- ---- dbobj -> dbobj -> bool? -> (int * int) -local scan_os_fonts = function (currentnames, -                                targetnames, -                                dry_run) +--- string * string list -> dbobj -> dbobj -> bool? -> int +local retrieve_namedata = function (files, currentnames, targetnames, dry_run) + +    local nfiles    = #files +    local nnew      = 0 + +    report ("info", 1, "db", "Scanning %d collected font files ...", nfiles) + +    local bylocation = { texmf     = { 0, 0 } +                       , ["local"] = { 0, 0 } +                       , system    = { 0, 0 } +                       } +    report_status_start (2, 4) +    for i = 1, nfiles do +        local fullname, location   = unpack (files[i]) +        local count                = bylocation[location] +        count[1]                   = count[1] + 1 +        if dry_run == true then +            local truncated = truncate_string (fullname, 43) +            report ("log", 2, "db", "Would have been loading %s.", fullname) +            report_status ("term", "db", "Would have been loading %s", truncated) +            --- skip the read_font_names part +        else +            local truncated = truncate_string (fullname, 32) +            report ("log", 2, "db", "Loading font %s.", fullname) +            report_status ("term", "db", "Loading font %s", truncated) +            local new = read_font_names (fullname, currentnames, +                                         targetnames, location) +            if new == true then +                nnew     = nnew + 1 +                count[2] = count[2] + 1 +            end +        end +    end +    report_status_stop ("term", "db", "Scanned %d files, %d new.", nfiles, nnew) +    for location, count in next, bylocation do +        report ("term", 4, "db", "   * %s: %d files, %d new", +                location, count[1], count[2]) +    end +    return nnew +end + +--- unit -> string * string list +local collect_font_filenames_system = function ()      local n_scanned, n_new = 0, 0 -    report ("info", 1, "db", "Scanning OS fonts...") +    report ("info", 1, "db", "Scanning system fonts...")      report ("info", 2, "db",              "Searching in static system directories...") -    report_status_start (2, 4) -    for _, d in next, get_os_dirs () do -        local found, new = scan_dir (d, currentnames, -                                     targetnames, dry_run) -        n_scanned = n_scanned + found -        n_new     = n_new     + new +    local files = { } +    for _, dir in next, get_os_dirs () do +        tableappend (files, collect_font_filenames_dir (dir, "system"))      end -    report_status_stop ("term", "db", "Scanned %d files, %d new.", n_scanned, n_new) - -    return n_scanned, n_new +    report ("term", 3, "db", "Collected %d files.", #files) +    return files  end ---- unit -> (bool, lookup_cache) +--- unit -> bool  flush_lookup_cache = function ()      lookup_cache = { }      collectgarbage "collect" -    return true, lookup_cache +    return true +end + +--[[doc-- + +    collect_font_filenames_local -- Scan $PWD (during a TeX run) +    for font files. + +    Side effect: This sets the “local” flag in the subtable “meta” to +    prevent the merged table from being saved to disk. + +    TODO the local tree could be cached in $PWD. + +--doc]]-- + +--- unit -> string * string list +local collect_font_filenames_local = function () +    local pwd = lfscurrentdir () +    report ("both", 1, "db", "Scanning for fonts in $PWD (%q) ...", pwd) + +    local files  = collect_font_filenames_dir (pwd, "local") +    local nfiles = #files +    if nfiles > 0 then +        targetnames.meta["local"] = true --- prevent saving to disk +        report ("term", 1, "db", "Found %d files.", pwd) +    else +        report ("term", 1, "db", +                "Couldn’t find a thing here. What a waste.", pwd) +    end +    report ("term", 3, "db", "Collected %d files.", #files) +    return files  end +--- dbobj -> dbobj -> int * int  --- fontentry list -> filemap -local generate_filedata = function (mappings) +generate_filedata = function (mappings)      report ("both", 2, "db", "Creating filename map.") @@ -2715,11 +2477,16 @@ do      local splitfontname = lpeg.splitat "-"      local choose_exact = function (field) +        --- only clean matches, without guessing          if italic_synonym [field] then              return "i" -        elseif field == "bold" then +        end + +        if field == "bold" then              return "b" -        elseif field == "bolditalic" or field == "boldoblique" then +        end + +        if field == "bolditalic" or field == "boldoblique" then              return "bi"          end @@ -2741,19 +2508,30 @@ do                  style = choose_exact (subfamily)              end          end ---        if not style and splitstyle then ---            style = choose_exact (splitstyle) ---        end          return style      end +    pick_fallback_style = function (italicangle, weight) +        --- more aggressive, but only to determine bold faces +        if weight > 500 then --- bold spectrum matches +            if italicangle == 0 then +                return tostring (weight) +            else +                return tostring (weight) .. "i" +            end +        end +        return false +    end +      --- we use only exact matches here since there are constructs      --- like “regularitalic” (Cabin, Bodoni Old Fashion)      check_regular = function (fontstyle_name,                                prefmodifiers,                                subfamily, -                              splitstyle) +                              splitstyle, +                              italicangle, +                              weight)          if fontstyle_name then              return regular_synonym [fontstyle_name] @@ -2763,6 +2541,8 @@ do              return regular_synonym [subfamily]          elseif splitstyle then              return regular_synonym [splitstyle] +        elseif italicangle == 0 and weight == 400 then +            return true          end          return nil @@ -2786,7 +2566,7 @@ local pull_values = function (entry)      --- pull name info ...      entry.psname            = english.psname -    entry.fontname          = info.fontname +    entry.fontname          = info.fontname or metadata.fontname      entry.fullname          = english.fullname or info.fullname      entry.splainname        = metadata.fullname      entry.prefmodifiers     = english.prefmodifiers @@ -2806,7 +2586,7 @@ local pull_values = function (entry)      entry.splitstyle        = style.split      entry.weight            = style.weight -    if luaotfloadconfig.strip == true then +    if config.luaotfload.db.strip == true then          entry.file  = nil          entry.names = nil          entry.style = nil @@ -2814,7 +2594,7 @@ local pull_values = function (entry)  end  local add_family = function (name, subtable, modifier, entry) -    if not name then +    if not name then --- probably borked font          return      end      local familytable = subtable [name] @@ -2823,33 +2603,28 @@ local add_family = function (name, subtable, modifier, entry)          subtable [name] = familytable      end -    --- the style table is treated as an unordered list -    local styletable = familytable [modifier] -    if not styletable then -        styletable = { } -        familytable [modifier] = styletable -    end +    local size = entry.size -    if not entry.prefmodifiers then --- default size for this style/family combo -        styletable.default = entry.index -    end +    familytable [#familytable + 1] = { +        index    = entry.index, +        modifier = modifier, +    } +end -    local size = entry.size --- dsnsize * high * low -    if size then -        styletable [#styletable + 1] = { -            size [1], -            size [2], -            size [3], -            entry.index, -        } -    else -        styletable.default = entry.index +local get_subtable = function (families, entry) +    local location  = entry.location +    local format    = entry.format +    local subtable  = families [location] [format] +    if not subtable then +        subtable  = { } +        families [location] [format] = subtable      end +    return subtable  end -local collect_families = function (mappings) +collect_families = function (mappings) -    report ("info", 2, "db", "Analyzing families, sizes, and styles.") +    report ("info", 2, "db", "Analyzing families.")      local families = {          ["local"]  = { }, @@ -2865,14 +2640,7 @@ local collect_families = function (mappings)              pull_values (entry)          end -        local location  = entry.location -        local format    = entry.format - -        local subtable  = families [location] [format] -        if not subtable then -            subtable  = { } -            families [location] [format] = subtable -        end +        local subtable          = get_subtable (families, entry)          local familyname        = entry.familyname          local metafamily        = entry.metafamily @@ -2893,7 +2661,9 @@ local collect_families = function (mappings)              modifier = check_regular (fontstyle_name,                                        prefmodifiers,                                        subfamily, -                                      splitstyle) +                                      splitstyle, +                                      italicangle, +                                      weight)          end          if modifier then @@ -2921,6 +2691,11 @@ local collect_families = function (mappings)  --            if metafamily and metafamily ~= familyname then  --                add_family (metafamily, subtable, modifier, entry)  --            end +        elseif weight > 500 then -- in bold spectrum +            modifier = pick_fallback_style (italicangle, weight) +            if modifier then +                add_family (familyname, subtable, modifier, entry) +            end          end      end @@ -2928,11 +2703,121 @@ local collect_families = function (mappings)      return families  end +--[[doc-- + +    group_modifiers -- For not-quite-bold faces, determine whether +    they can fill in for a missing bold face slot in a matching family. + +    Some families like Lucida do not contain real bold / bold italic +    members. Instead, they have semibold variants at weight 600 which +    we must add in a separate pass. + +--doc]]-- + +local bold_spectrum_low  = 501 --- 500 is medium, 900 heavy/black +local bold_weight        = 700 +local style_categories   = { "r", "b", "i", "bi" } +local bold_categories    = {      "b",      "bi" } + +group_modifiers = function (mappings, families) +    report ("info", 2, "db", "Analyzing shapes, weights, and styles.") +    for location, location_data in next, families do +        for format, format_data in next, location_data do +            for familyname, collected in next, format_data do +                local styledata = { } --- will replace the “collected” table +                --- First, fill in the ordinary style data that +                --- fits neatly into the four relevant modifier +                --- categories. +                for _, modifier in next, style_categories do +                    local entries +                    for key, info in next, collected do +                        if info.modifier == modifier then +                            if not entries then +                                entries = { } +                            end +                            local index = info.index +                            local entry = mappings [index] +                            local size  = entry.size +                            if size then +                                entries [#entries + 1] = { +                                    size [1], +                                    size [2], +                                    size [3], +                                    index, +                                } +                            else +                                entries.default = index +                            end +                            collected [key] = nil +                        end +                        styledata [modifier] = entries +                    end +                end + +                --- At this point the family set may still lack +                --- entries for bold or bold italic. We will fill +                --- those in using the modifier with the numeric +                --- weight that is closest to bold (700). +                if next (collected) then --- there are uncategorized entries +                    for _, modifier in next, bold_categories do +                        if not styledata [modifier] then +                            local closest +                            local minimum = 2^51 +                            for key, info in next, collected do +                                local info_modifier = tonumber (info.modifier) and "b" or "bi" +                                if modifier == info_modifier then +                                    local index  = info.index +                                    local entry  = mappings [index] +                                    local weight = entry.weight +                                    local diff   = weight < 700 and 700 - weight or weight - 700 +                                    if diff < minimum then +                                        minimum = diff +                                        closest = weight +                                    end +                                end +                            end +                            if closest then +                                --- We know there is a substitute face for the modifier. +                                --- Now we scan the list again to extract the size data +                                --- in case the shape is available at multiple sizes. +                                local entries = { } +                                for key, info in next, collected do +                                    local info_modifier = tonumber (info.modifier) and "b" or "bi" +                                    if modifier == info_modifier then +                                        local index  = info.index +                                        local entry  = mappings [index] +                                        local size   = entry.size +                                        if entry.weight == closest then +                                            if size then +                                                entries [#entries + 1] =  { +                                                    size [1], +                                                    size [2], +                                                    size [3], +                                                    index, +                                                } +                                            else +                                                entries.default = index +                                            end +                                        end +                                    end +                                end +                                styledata [modifier] = entries +                            end +                        end +                    end +                end +                format_data [familyname] = styledata +            end +        end +    end +    return families +end +  local cmp_sizes = function (a, b)      return a [1] < b [1]  end -local order_design_sizes = function (families) +order_design_sizes = function (families)      report ("info", 2, "db", "Ordering design sizes.") @@ -2949,27 +2834,89 @@ local order_design_sizes = function (families)      return families  end -local retrieve_namedata = function (currentnames, -                                    targetnames, -                                    dry_run, -                                    n_rawnames, -                                    n_newnames) +--[[doc-- -    local rawnames, new = scan_texmf_fonts (currentnames, -                                            targetnames, -                                            dry_run) +    collect_font_filenames -- Scan the three search path categories for +    font files. This constitutes the first pass of the update mode. + +--doc]]-- -    n_rawnames    = n_rawnames + rawnames -    n_newnames    = n_newnames + new +--- unit -> string * bool list +local collect_font_filenames = function () -    rawnames, new = scan_os_fonts (currentnames, targetnames, dry_run) +    report ("info", 4, "db", "Scanning the filesystem for font files.") -    n_rawnames    = n_rawnames + rawnames -    n_newnames    = n_newnames + new +    local filenames = { } +    local bisect    = config.luaotfload.misc.bisect +    local max_fonts = config.luaotfload.db.max_fonts --- XXX revisit for lua 5.3 wrt integers -    return n_rawnames, n_newnames +    tableappend (filenames, collect_font_filenames_texmf  ()) +    tableappend (filenames, collect_font_filenames_system ()) +    if config.luaotfload.db.scan_local == true then +        tableappend (filenames, collect_font_filenames_local  ()) +    end +    --- Now drop everything above max_fonts. +    if max_fonts < #filenames then +        filenames = { unpack (filenames, 1, max_fonts) } +    end +    --- And choose the requested slice if in bisect mode. +    if bisect then +        return { unpack (filenames, bisect[1], bisect[2]) } +    end +    return filenames  end +--[[doc-- + +    nth_font_file -- Return the filename of the nth font. + +--doc]]-- + +--- int -> string +local nth_font_filename = function (n) +    report ("info", 4, "db", "Picking font file no. %d.", n) +    if not p_blacklist then +        read_blacklist () +    end +    local filenames = collect_font_filenames () +    return filenames[n] and filenames[n][1] or "<error>" +end + +--[[doc-- + +    font_slice -- Return the fonts in the range from lo to hi. + +--doc]]-- + +local font_slice = function (lo, hi) +    report ("info", 4, "db", "Retrieving font files nos. %d--%d.", lo, hi) +    if not p_blacklist then +        read_blacklist () +    end +    local filenames = collect_font_filenames () +    local result    = { } +    for i = lo, hi do +        result[#result + 1] = filenames[i][1] +    end +    return result +end + +--[[doc + +    count_font_files -- Return the number of files found by +    collect_font_filenames. This function is exported primarily +    for use with luaotfload-tool.lua in bisect mode. + +--doc]]-- + +--- unit -> int +local count_font_files = function () +    report ("info", 4, "db", "Counting font files.") +    if not p_blacklist then +        read_blacklist () +    end +    return #collect_font_filenames () +end  --- dbobj -> stats @@ -3053,7 +3000,7 @@ local collect_statistics = function (mappings)      local n_fullname = setsize (fullname)      local n_family   = setsize (family) -    if logs.get_loglevel () > 1 then +    if log.get_loglevel () > 1 then          local pprint_top = function (hash, n, set)              local freqs = { } @@ -3142,18 +3089,16 @@ end  --- dbobj? -> bool? -> bool? -> dbobj  update_names = function (currentnames, force, dry_run) -      local targetnames -    if luaotfloadconfig.update_live == false then +    if config.luaotfload.db.update_live == false then          report ("info", 2, "db",                  "Skipping database update.")          --- skip all db updates          return currentnames or name_index      end -    local starttime                 = osgettimeofday () -    local n_rawnames, n_newnames    = 0, 0 +    local starttime = osgettimeofday ()      --[[      The main function, scans everything @@ -3163,10 +3108,7 @@ update_names = function (currentnames, force, dry_run)      report("both", 1, "db", "Updating the font names database"                           .. (force and " forcefully." or ".")) -    --- pass 1 get raw data: read font files (normal case) or reuse -    --- information present in index - -    if luaotfloadconfig.skip_read == true then +    if config.luaotfload.db.skip_read == true then          --- the difference to a “dry run” is that we don’t search          --- for font files entirely. we also ignore the “force”          --- parameter since it concerns only the font files. @@ -3188,22 +3130,28 @@ update_names = function (currentnames, force, dry_run)              end          end -        targetnames = initialize_namedata (get_font_filter ()) +        targetnames = initialize_namedata (get_font_filter (), +                                           currentnames.meta and currentnames.meta.created)          read_blacklist () -        local n_raw, n_new= retrieve_namedata (currentnames, -                                               targetnames, -                                               dry_run, -                                               n_rawnames, -                                               n_newnames) +        --- pass 1: Collect the names of all fonts we are going to process. +        local font_filenames = collect_font_filenames () + +        --- pass 2: read font files (normal case) or reuse information +        --- present in index + +        n_new = retrieve_namedata (font_filenames, +                                   currentnames, +                                   targetnames, +                                   dry_run)          report ("info", 3, "db", -                "Scanned %d font files; %d new entries.", -                n_rawnames, n_newnames) +                "Found %d font files; %d new entries.", +                #font_filenames, n_new)      end -    --- pass 2 (optional): collect some stats about the raw font info -    if luaotfloadconfig.statistics == true then +    --- pass 3 (optional): collect some stats about the raw font info +    if config.luaotfload.misc.statistics == true then          targetnames.meta.statistics = collect_statistics                                              (targetnames.mappings)      end @@ -3212,13 +3160,17 @@ update_names = function (currentnames, force, dry_run)      --- non-texmf entries are redirected there and the mapping      --- needs to be 100% consistent -    --- pass 3: build filename table +    --- pass 4: build filename table      targetnames.files       = generate_filedata (targetnames.mappings) -    --- pass 4: build family lookup table -    targetnames.families    = collect_families (targetnames.mappings) +    --- pass 5: build family lookup table +    targetnames.families    = collect_families  (targetnames.mappings) + +    --- pass 6: arrange style and size info +    targetnames.families    = group_modifiers (targetnames.mappings, +                                               targetnames.families) -    --- pass 5: order design size tables +    --- pass 7: order design size tables      targetnames.families    = order_design_sizes (targetnames.families) @@ -3229,25 +3181,29 @@ update_names = function (currentnames, force, dry_run)      if dry_run ~= true then -        save_names () - -        local success, _lookups = flush_lookup_cache () -        if success then -            local success = save_lookups () -            if success then -                logs.names_report ("info", 2, "cache", -                                   "Lookup cache emptied.") -                return targetnames +        if n_new == 0 then +            report ("info", 2, "db", "No new fonts found, skip saving to disk.") +        else +            local success, reason = save_names () +            if not success then +                report ("both", 0, "db", +                        "Failed to save database to disk: %s", +                        reason)              end          end + +        if flush_lookup_cache () and save_lookups () then +            report ("both", 2, "cache", "Lookup cache emptied.") +            return targetnames +        end      end      return targetnames  end  --- unit -> bool  save_lookups = function ( ) -    local path    = names.path.lookups -    local luaname, lucname = path.lua, path.luc +    local paths = config.luaotfload.paths +    local luaname, lucname = paths.lookup_path_lua, paths.lookup_path_luc      if fileiswritable (luaname) and fileiswritable (lucname) then          tabletofile (luaname, lookup_cache, true)          osremove (lucname) @@ -3271,17 +3227,22 @@ save_lookups = function ( )  end  --- save_names() is usually called without the argument ---- dbobj? -> bool +--- dbobj? -> bool * string option  save_names = function (currentnames)      if not currentnames then          currentnames = name_index      end -    local path = names.path.index -    local luaname, lucname = path.lua, path.luc +    if not currentnames or type (currentnames) ~= "table" then +        return false, "invalid names table" +    elseif currentnames.meta and currentnames.meta["local"] then +        return false, "table contains local entries" +    end +    local paths = config.luaotfload.paths +    local luaname, lucname = paths.index_path_lua, paths.index_path_luc      if fileiswritable (luaname) and fileiswritable (lucname) then          osremove (lucname)          local gzname = luaname .. ".gz" -        if luaotfloadconfig.compress then +        if config.luaotfload.db.compress then              local serialized = tableserialize (currentnames, true)              save_gzipped (gzname, serialized)              caches.compile (currentnames, "", lucname) @@ -3399,7 +3360,7 @@ end  local getwritablecachepath = function ( )      --- fonts.handlers.otf doesn’t exist outside a Luatex run,      --- so we have to improvise -    local writable = getwritablepath (luaotfloadconfig.cache_dir) +    local writable = getwritablepath (config.luaotfload.paths.cache_dir)      if writable then          return writable      end @@ -3407,7 +3368,7 @@ end  local getreadablecachepaths = function ( )      local readables = caches.getreadablepaths -                        (luaotfloadconfig.cache_dir) +                        (config.luaotfload.paths.cache_dir)      local result    = { }      if readables then          for i=1, #readables do @@ -3424,7 +3385,7 @@ end  local purge_cache = function ( )      local writable_path = getwritablecachepath ()      local luanames, lucnames, rest = collect_cache(writable_path) -    if logs.get_loglevel() > 1 then +    if log.get_loglevel() > 1 then          print_cache("writable path", writable_path, luanames, lucnames, rest)      end      local success = purge_from_cache("writable path", writable_path, luanames, false) @@ -3435,7 +3396,7 @@ end  local erase_cache = function ( )      local writable_path = getwritablecachepath ()      local luanames, lucnames, rest, all = collect_cache(writable_path) -    if logs.get_loglevel() > 1 then +    if log.get_loglevel() > 1 then          print_cache("writable path", writable_path, luanames, lucnames, rest)      end      local success = purge_from_cache("writable path", writable_path, all, true) @@ -3472,36 +3433,31 @@ end  --- export functionality to the namespace “fonts.names”  ----------------------------------------------------------------------- -names.scan_dir                    = scan_dir  names.set_font_filter             = set_font_filter  names.flush_lookup_cache          = flush_lookup_cache  names.save_lookups                = save_lookups  names.load                        = load_names +names.access_font_index           = access_font_index  names.data                        = function () return name_index end  names.save                        = save_names  names.update                      = update_names -names.crude_file_lookup           = crude_file_lookup -names.crude_file_lookup_verbose   = crude_file_lookup_verbose +names.font_file_lookup            = font_file_lookup  names.read_blacklist              = read_blacklist  names.sanitize_fontname           = sanitize_fontname  names.getfilename                 = resolve_fullpath +names.getmetadata                 = getmetadata  names.set_location_precedence     = set_location_precedence +names.count_font_files            = count_font_files +names.nth_font_filename           = nth_font_filename +names.font_slice                  = font_slice +names.resolve_cached              = resolve_cached +names.resolve_name                = resolve_name  --- font cache  names.purge_cache    = purge_cache  names.erase_cache    = erase_cache  names.show_cache     = show_cache ---- replace the resolver from luatex-fonts -if luaotfloadconfig.resolver == "cached" then -    report("both", 2, "cache", "Caching of name: lookups active.") -    names.resolvespec  = resolve_cached -    names.resolve_name = resolve_cached -else -    names.resolvespec  = resolve_name -    names.resolve_name = resolve_name -end -  names.find_closest = find_closest  -- for testing purpose diff --git a/luaotfload-diagnostics.lua b/src/luaotfload-diagnostics.lua index 68ed18c..d9b13f5 100644 --- a/luaotfload-diagnostics.lua +++ b/src/luaotfload-diagnostics.lua @@ -4,13 +4,11 @@  --  DESCRIPTION:  functionality accessible by the --diagnose option  -- REQUIREMENTS:  luaotfload-tool.lua  --       AUTHOR:  Philipp Gesang (Phg), <phg42.2a@gmail.com> ---      VERSION:  2.4 ---      CREATED:  2013-07-28 10:01:18+0200 +--      VERSION:  2.5 +--     MODIFIED:  2014-01-02 21:23:06+0100  -----------------------------------------------------------------------  --  local names                    = fonts.names -local luatexstatus             = status -local status                   = config.luaotfload.status  local kpse                     = require "kpse"  local kpseexpand_path          = kpse.expand_path @@ -49,10 +47,15 @@ local lpeg                     = require "lpeg"  local C, Cg, Ct                = lpeg.C, lpeg.Cg, lpeg.Ct  local lpegmatch                = lpeg.match +local report                   = luaotfload.log.report  local out = function (...) -    logs.names_report (false, 0, "diagnose", ...) +    report (false, 0, "diagnose", ...)  end +local parsers                  = luaotfload.parsers +local stripslashes             = parsers.stripslashes +local splitcomma               = parsers.splitcomma +  local check_index = function (errcnt)      out "================= font names ==================" @@ -94,8 +97,9 @@ local check_index = function (errcnt)      return errcnt  end -local verify_files = function (errcnt, status) +local verify_files = function (errcnt)      out "================ verify files =================" +    local status = config.luaotfload.status      local hashes = status.hashes      local notes  = status.notes      if not hashes or #hashes == 0 then @@ -163,8 +167,6 @@ local analyze_permissions = function (raw)      return lpegmatch (p_permissions, raw)  end -local stripslashes = names.patterns.stripslashes -  local get_permissions = function (t, location)      if stringsub (location, #location) == "/" then          --- strip trailing slashes (lfs idiosyncrasy on Win) @@ -238,19 +240,23 @@ local check_conformance = function (spec, permissions, errcnt)      return errcnt  end -local path = names.path - -local desired_permissions = { -    { "d", {"r","w"}, function () return caches.getwritablepath () end }, -    { "d", {"r","w"}, path.globals.prefix }, -    { "f", {"r","w"}, path.index.lua .. ".gz" }, -    { "f", {"r","w"}, path.index.luc }, -    { "f", {"r","w"}, path.lookups.lua }, -    { "f", {"r","w"}, path.lookups.luc }, -} +local desired_permissions +local init_desired_permissions = function () +    inspect(config.luaotfload.paths) +    local paths = config.luaotfload.paths +    desired_permissions = { +        { "d", {"r","w"}, function () return caches.getwritablepath () end }, +        { "d", {"r","w"}, paths.prefix }, +        { "f", {"r","w"}, paths.index_path_lua .. ".gz" }, +        { "f", {"r","w"}, paths.index_path_luc }, +        { "f", {"r","w"}, paths.lookup_path_lua }, +        { "f", {"r","w"}, paths.lookup_path_luc }, +    } +end  local check_permissions = function (errcnt)      out [[=============== file permissions ==============]] +    if not desired_permissions then init_desired_permissions () end      for i = 1, #desired_permissions do          local t, spec, path = unpack (desired_permissions[i])          if type (path) == "function" then @@ -334,8 +340,7 @@ else          end          out ("Requesting <%s>.", request) -        local response, code, headers, status -            = https.request (request) +        local response, code, headers, status = https.request (request)          if status ~= alright then              out "Request failed!"              return false @@ -605,8 +610,6 @@ local anamneses   = {      "permissions"  } -local splitcomma = names.patterns.splitcomma -  local diagnose = function (job)      local errcnt = 0      local asked  = job.asked_diagnostics @@ -628,7 +631,7 @@ local diagnose = function (job)      end      if asked.files == true then -        errcnt = verify_files (errcnt, status) +        errcnt = verify_files (errcnt)          asked.files = nil      end diff --git a/luaotfload-features.lua b/src/luaotfload-features.lua index 4b2f206..1fb6d7c 100644 --- a/luaotfload-features.lua +++ b/src/luaotfload-features.lua @@ -1,15 +1,26 @@  if not modules then modules = { } end modules ["features"] = { -    version   = "2.4", -    comment   = "companion to luaotfload.lua", +    version   = "2.5", +    comment   = "companion to luaotfload-main.lua",      author    = "Hans Hagen, Khaled Hosny, Elie Roux, Philipp Gesang",      copyright = "PRAGMA ADE / ConTeXt Development Team",      license   = "see context related readme files"  } -local type, next        = type, next +local type              = type +local next              = next  local tonumber          = tonumber  local tostring          = tostring + +local lpeg              = require "lpeg"  local lpegmatch         = lpeg.match +local P                 = lpeg.P +local R                 = lpeg.R +local C                 = lpeg.C + +local table             = table +local tabletohash       = table.tohash +local setmetatableindex = table.setmetatableindex +local insert            = table.insert  ---[[ begin included font-ltx.lua ]]  --- this appears to be based in part on luatex-fonts-def.lua @@ -43,7 +54,8 @@ function fonts.definers.getspecification(str)      return "", str, "", ":", str  end -local report = logs.names_report +local log              = luaotfload.log +local report           = log.report  local stringfind       = string.find  local stringlower      = string.lower @@ -53,58 +65,6 @@ local stringformat     = string.format  local stringis_empty   = string.is_empty  local mathceil         = math.ceil -local defaults = { -    dflt = { -        "ccmp", "locl", "rlig", "liga", "clig", -        "kern", "mark", "mkmk", 'itlc', -    }, -    arab = { -        "ccmp", "locl", "isol", "fina", "fin2", -        "fin3", "medi", "med2", "init", "rlig", -        "calt", "liga", "cswh", "mset", "curs", -        "kern", "mark", "mkmk", -    }, -    deva = { -        "ccmp", "locl", "init", "nukt", "akhn", -        "rphf", "blwf", "half", "pstf", "vatu", -        "pres", "blws", "abvs", "psts", "haln", -        "calt", "blwm", "abvm", "dist", "kern", -        "mark", "mkmk", -    }, -    khmr = { -        "ccmp", "locl", "pref", "blwf", "abvf", -        "pstf", "pres", "blws", "abvs", "psts", -        "clig", "calt", "blwm", "abvm", "dist", -        "kern", "mark", "mkmk", -    }, -    thai = { -        "ccmp", "locl", "liga", "kern", "mark", -        "mkmk", -    }, -    hang = { -        "ccmp", "ljmo", "vjmo", "tjmo", -    }, -} - -local global_defaults = { mode = "node" } - -defaults.beng = defaults.deva -defaults.guru = defaults.deva -defaults.gujr = defaults.deva -defaults.orya = defaults.deva -defaults.taml = defaults.deva -defaults.telu = defaults.deva -defaults.knda = defaults.deva -defaults.mlym = defaults.deva -defaults.sinh = defaults.deva - -defaults.syrc = defaults.arab -defaults.mong = defaults.arab -defaults.nko  = defaults.arab - -defaults.tibt = defaults.khmr - -defaults.lao  = defaults.thai  ---[[ begin excerpt from font-ott.lua ]] @@ -749,7 +709,7 @@ local verbosebaselines = swapped(baselines)  --doc]]-- -local support_incomplete = table.tohash({ +local support_incomplete = tabletohash({      "deva", "beng", "guru", "gujr",      "orya", "taml", "telu", "knda",      "mlym", "sinh", @@ -764,6 +724,8 @@ local support_incomplete = table.tohash({  --- (string, string) dict -> (string, string) dict  local set_default_features = function (speclist) +    local default_features = luaotfload.features +      speclist = speclist or { }      speclist[""] = nil --- invalid options stub @@ -788,32 +750,31 @@ local set_default_features = function (speclist)                or "dflt"          if support_incomplete[script] then              report("log", 0, "load", -                "support for the requested script: " -                .. "%q may be incomplete", script) +                "Support for the requested script: " +                .. "%q may be incomplete.", script)          end      else          script = "dflt"      end      speclist.script = script -    report("log", 0, "load", -        "auto-selecting default features for script: %s", +    report("log", 1, "load", +        "Auto-selecting default features for script: %s.",          script) -    local requested = defaults[script] +    local requested = default_features.defaults[script]      if not requested then -        report("log", 0, "load", -            "no defaults for script %q, falling back to \"dflt\"", +        report("log", 1, "load", +            "No default features for script %q, falling back to \"dflt\".",              script) -        requested = defaults.dflt +        requested = default_features.defaults.dflt      end -    for i=1, #requested do -        local feat = requested[i] -        if speclist[feat] ~= false then speclist[feat] = true end +    for feat, state in next, requested do +        if not speclist[feat] then speclist[feat] = state end      end -    for feat, state in next, global_defaults do +    for feat, state in next, default_features.global do          --- This is primarily intended for setting node          --- mode unless “base” is requested, as stated          --- in the manual. @@ -822,259 +783,6 @@ local set_default_features = function (speclist)      return speclist  end ------------------------------------------------------------------------ ----                    request syntax parser 2.2 ------------------------------------------------------------------------ ---- the luaotfload font request syntax (see manual) ---- has a canonical form: ---- ----     \font<csname>=<prefix>:<identifier>:<features> ---- ---- where ----   <csname> is the control sequence that activates the font ----   <prefix> is either “file” or “name”, determining the lookup ----   <identifer> is either a file name (no path) or a font ----               name, depending on the lookup ----   <features> is a list of switches or options, separated by ----              semicolons or commas; a switch is of the form “+” foo ----              or “-” foo, options are of the form lhs “=” rhs ---- ---- however, to ensure backward compatibility we also have ---- support for Xetex-style requests. ---- ---- for the Xetex emulation see: ---- · The XeTeX Reference Guide by Will Robertson, 2011 ---- · The XeTeX Companion by Michel Goosens, 2010 ---- · About XeTeX by Jonathan Kew, 2005 ---- ---- ---- caueat emptor. ----     the request is parsed into one of **four** different ----     lookup categories: the regular ones, file and name, ----     as well as the Xetex compatibility ones, path and anon. ----     (maybe a better choice of identifier would be “ambig”.) ---- ----     according to my reconstruction, the correct chaining ----     of the lookups for each category is as follows: ---- ----     | File -> ( db/filename lookup ) ---- ----     | Name -> ( db/name lookup, ----                 db/filename lookup ) ---- ----     | Path -> ( db/filename lookup, ----                 fullpath lookup ) ---- ----     | Anon -> ( kpse.find_file(),     // <- for tfm, ofm ----                 db/name lookup, ----                 db/filename lookup, ----                 fullpath lookup ) ---- ----     caching of successful lookups is essential. we now ----     as of v2.2 have an experimental lookup cache that is ----     stored in a separate file. it pertains only to name: ----     lookups, and is described in more detail in ----     luaotfload-database.lua. ---- ------------------------------------------------------------------------ - ---[[doc-- - -    One further incompatibility between Xetex and Luatex-Fonts consists -    in their option list syntax: apparently, Xetex requires key-value -    options to be prefixed by a "+" (ascii “plus”) character. We -    silently accept this as well, dropping the first byte if it is a -    plus or minus character. - -    Reference: https://github.com/lualatex/luaotfload/issues/79#issuecomment-18104483 - ---doc]]-- - -local handle_normal_option = function (key, val) -    val = stringlower(val) -    --- the former “toboolean()” handler -    if val == "true"  then -        val = true -    elseif val == "false" then -        val = false -    end -    return key, val -end - ---[[doc-- - -    Xetex style indexing begins at zero which we just increment before -    passing it along to the font loader.  Ymmv. - ---doc]]-- - -local handle_xetex_option = function (key, val) -    val = stringlower(val) -    local numeric = tonumber(val) --- decimal only; keeps colors intact -    if numeric then --- ugh -        if  mathceil(numeric) == numeric then -- integer, possible index -            val = tostring(numeric + 1) -        end -    elseif val == "true"  then -        val = true -    elseif val == "false" then -        val = false -    end -    return key, val -end - ---[[doc-- - -    Instead of silently ignoring invalid options we emit a warning to -    the log. - -    Note that we have to return a pair to please rawset(). This creates -    an entry on the resulting features hash which will later be removed -    during set_default_features(). - ---doc]]-- - -local handle_invalid_option = function (opt) -    report("log", 0, "load", "font option %q unknown.", opt) -    return "", false -end - ---[[doc-- - -    Dirty test if a file: request is actually a path: lookup; don’t -    ask! Note this fails on Windows-style absolute paths. These will -    *really* have to use the correct request. - ---doc]]-- - -local check_garbage = function (_,i, garbage) -    if stringfind(garbage, "/") then -        report("log", 0, "load",  --- ffs use path! -            "warning: path in file: lookups is deprecated; ") -        report("log", 0, "load", "use bracket syntax instead!") -        report("log", 0, "load", -            "position: %d; full match: %q", -            i, garbage) -        return true -    end -    return false -end - -local lpegmatch = lpeg.match -local P, S, R   = lpeg.P, lpeg.S, lpeg.R -local C, Cc, Cf, Cg, Cmt, Cs, Ct -    = lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Cmt, lpeg.Cs, lpeg.Ct - ---- terminals and low-level classes ----------------------------------- ---- note we could use the predefined ones from lpeg.patterns -local dot         = P"." -local colon       = P":" -local featuresep  = S",;" -local slash       = P"/" -local equals      = P"=" -local lbrk, rbrk  = P"[", P"]" - -local spacing     = S" \t\v" -local ws          = spacing^0 - -local digit       = R"09" -local alpha       = R("az", "AZ") -local anum        = alpha + digit -local decimal     = digit^1 * (dot * digit^0)^-1 - ---- modifiers --------------------------------------------------------- ---[[doc-- -    The slash notation: called “modifiers” (Kew) or “font options” -    (Robertson, Goosens) -    we only support the shorthands for italic / bold / bold italic -    shapes, as well as setting optical size, the rest is ignored. ---doc]]-- -local style_modifier    = (P"BI" + P"IB" + P"bi" + P"ib" + S"biBI") -                        / stringlower -local size_modifier     = S"Ss" * P"="    --- optical size -                        * Cc"optsize" * C(decimal) -local other_modifier    = P"AAT" + P"aat" --- apple stuff;  unsupported -                        + P"ICU" + P"icu" --- not applicable -                        + P"GR"  + P"gr"  --- sil stuff;    unsupported -local garbage_modifier  = ((1 - colon - slash)^0 * Cc(false)) -local modifier          = slash * (other_modifier      --> ignore -                                 + Cs(style_modifier)  --> collect -                                 + Ct(size_modifier)   --> collect -                                 + garbage_modifier)   --> warn -local modifier_list     = Cg(Ct(modifier^0), "modifiers") - ---- lookups ----------------------------------------------------------- -local fontname          = C((1-S":(/")^1)  --- like luatex-fonts -local unsupported       = Cmt((1-S":(")^1, check_garbage) -local prefixed          = P"name:" * ws * Cg(fontname, "name") ---- initially we intended file: to emulate the behavior of ---- luatex-fonts, i.e. no paths allowed. after all, we do have XeTeX ---- emulation with the path lookup and it interferes with db lookups. ---- turns out fontspec and other widely used packages rely on file: ---- with paths already, so we’ll add a less strict rule here.  anyways, ---- we’ll emit a warning. -                        + P"file:" * ws * Cg(unsupported, "path") -                        + P"file:" * ws * Cg(fontname, "file") ---- EXPERIMENTAL: kpse lookup -                        + P"kpse:" * ws * Cg(fontname, "kpse") ---- EXPERIMENTAL: custom lookup -                        + P"my:" * ws * Cg(fontname, "my") -local unprefixed        = Cg(fontname, "anon") -local path_lookup       = lbrk * Cg(C((1-rbrk)^1), "path") * rbrk - ---- features ---------------------------------------------------------- -local field_char        = anum + S"+-." --- sic! -local field             = field_char^1 ---- assignments are “lhs=rhs” ----              or “+lhs=rhs” (Xetex-style) ---- switches    are “+key” | “-key” -local normal_option     = C(field) * ws * equals * ws * C(field) * ws -local xetex_option      = P"+" * ws * normal_option -local ignore_option     = (1 - equals - featuresep)^1 -                        * equals -                        * (1 - featuresep)^1 -local assignment        = xetex_option  / handle_xetex_option -                        + normal_option / handle_normal_option -                        + ignore_option / handle_invalid_option -local switch            = P"+" * ws * C(field) * Cc(true) -                        + P"-" * ws * C(field) * Cc(false) -                        +             C(field) * Cc(true)   --- default -local feature_expr      = ws * Cg(assignment + switch) * ws -local option            = feature_expr -local feature_list      = Cf(Ct"" -                           * option -                           * (featuresep * option^-1)^0 -                           , rawset) -                        * featuresep^-1 - ---- other ------------------------------------------------------------- ---- This rule is present in the original parser. It sets the “sub” ---- field of the specification which allows addressing a specific ---- font inside a TTC container. Neither in Luatex-Fonts nor in ---- Luaotfload is this documented, so we might as well silently drop ---- it. However, as backward compatibility is one of our prime goals we ---- just insert it here and leave it undocumented until someone cares ---- to ask. (Note: afair subfonts are numbered, but this rule matches a ---- string; I won’t mess with it though until someone reports a ---- problem.) ---- local subvalue   = P("(") * (C(P(1-S("()"))^1)/issub) * P(")") -- for Kim ---- Note to self: subfonts apparently start at index 0. Tested with ---- Cambria.ttc that includes “Cambria Math” at 0 and “Cambria” at 1. ---- Other values cause luatex to segfault. -local subfont           = P"(" * Cg((1 - S"()")^1, "sub") * P")" ---- top-level rules --------------------------------------------------- ---- \font\foo=<specification>:<features> -local features          = Cg(feature_list, "features") -local specification     = (prefixed + unprefixed) -                        * subfont^-1 -                        * modifier_list^-1 -local font_request      = Ct(path_lookup   * (colon^-1 * features)^-1 -                           + specification * (colon    * features)^-1) - ---  lpeg.print(font_request) ---- new parser: 657 rules ---- old parser: 230 rules -  local import_values = {      --- That’s what the 1.x parser did, not quite as graciously,      --- with an array of branch expressions. @@ -1138,7 +846,7 @@ end  --- spec -> spec  local handle_request = function (specification) -    local request = lpegmatch(font_request, +    local request = lpegmatch(luaotfload.parsers.font_request,                                specification.specification)      if not request then          --- happens when called with an absolute path @@ -1230,7 +938,6 @@ local report_otf          = logs.reporter("fonts","otf loading")  local otf                 = fonts.handlers.otf  local registerotffeature  = otf.features.register -local setmetatableindex   = table.setmetatableindex  --[[HH-- @@ -1286,6 +993,7 @@ local function addfeature(data,feature,specifications)                  local subtables     = specification.subtables or { specification.data } or { }                  local featuretype   = types[specification.type or "substitution"]                  local featureflags  = specification.flags or noflags +                local featureorder  = specification.order or { feature }                  local added         = false                  local featurename   = stringformat("ctx_%s_%s",feature,s)                  local st = { } @@ -1346,17 +1054,23 @@ local function addfeature(data,feature,specifications)                      -- script = { lang1, lang2, lang3 } or script = { lang1 = true, ... }                      for k, v in next, askedfeatures do                          if v[1] then -                            askedfeatures[k] = table.tohash(v) +                            askedfeatures[k] = tabletohash(v)                          end                      end -                    sequences[#sequences+1] = { +                    local sequence = {                          chain     = 0,                          features  = { [feature] = askedfeatures },                          flags     = featureflags,                          name      = featurename, +                        order     = featureorder,                          subtables = st,                          type      = featuretype,                      } +                    if specification.prepend then +                        insert(sequences,1,sequence) +                    else +                        insert(sequences,sequence) +                    end                      -- register in metadata (merge as there can be a few)                      if not gsubfeatures then                          gsubfeatures  = { } @@ -1386,6 +1100,7 @@ local function addfeature(data,feature,specifications)      end  end +  otf.enhancers.addfeature = addfeature  local extrafeatures = { } @@ -1414,6 +1129,8 @@ local tlig = {              [0x0060] = 0x2018,                   -- quoteright          },          flags     = { }, +        order     = { "tlig" }, +        prepend   = true,      },      {          type     = "ligature", @@ -1432,6 +1149,8 @@ local tlig = {              [0x00BB] = {0x003E, 0x003E},         -- RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK          },          flags    = { }, +        order    = { "tlig" }, +        prepend  = true,      },      {          type     = "ligature", @@ -1443,6 +1162,8 @@ local tlig = {              [0x00BF] = {0x003F, 0x0060},         -- questiondown          },          flags    = { }, +        order    = { "tlig" }, +        prepend  = true,      },  } @@ -1494,6 +1215,7 @@ local anum_specification = {          features = { arab = { far = true, urd = true, snd = true } },          data     = anum_persian,          flags    = { }, +        order    = { "anum" },          valid    = valid,      },      { @@ -1501,23 +1223,16 @@ local anum_specification = {          features = { arab = { ["*"] = true } },          data     = anum_arabic,          flags    = { }, +        order    = { "anum" },          valid    = valid,      },  } ---[[doc-- - -    Below the specifications as given in the removed font-otc.lua. -    The rest was identical to what this file had from the beginning. -    Both make the “anum.tex” test pass anyways. - ---doc]]-- - -otf.addfeature("anum",anum_specification) +otf.addfeature ("anum", anum_specification)  registerotffeature { -    name        = 'anum', -    description = 'arabic digits', +    name        = "anum", +    description = "arabic digits",  }  -- vim:tw=71:sw=4:ts=4:expandtab diff --git a/luaotfload-fontloader.lua b/src/luaotfload-fontloader.lua index 1773241..2f26be7 100644 --- a/luaotfload-fontloader.lua +++ b/src/luaotfload-fontloader.lua @@ -1,6 +1,6 @@  -- merged file : luatex-fonts-merged.lua  -- parent file : luatex-fonts.lua --- merge date  : 12/24/13 17:52:44 +-- merge date  : 07/06/14 22:50:12  do -- begin closure to overcome local limits and interference @@ -82,6 +82,9 @@ function optionalrequire(...)      return result    end  end +if lua then +  lua.mask=load([[τεχ = 1]]) and "utf" or "ascii" +end  end -- closure @@ -101,7 +104,9 @@ local byte,char,gmatch,format=string.byte,string.char,string.gmatch,string.forma  local floor=math.floor  local P,R,S,V,Ct,C,Cs,Cc,Cp,Cmt=lpeg.P,lpeg.R,lpeg.S,lpeg.V,lpeg.Ct,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.Cp,lpeg.Cmt  local lpegtype,lpegmatch,lpegprint=lpeg.type,lpeg.match,lpeg.print -setinspector(function(v) if lpegtype(v) then lpegprint(v) return true end end) +if setinspector then +  setinspector(function(v) if lpegtype(v) then lpegprint(v) return true end end) +end  lpeg.patterns=lpeg.patterns or {}   local patterns=lpeg.patterns  local anything=P(1) @@ -120,7 +125,7 @@ local uppercase=R("AZ")  local underscore=P("_")  local hexdigit=digit+lowercase+uppercase  local cr,lf,crlf=P("\r"),P("\n"),P("\r\n") -local newline=crlf+S("\r\n")  +local newline=P("\r")*(P("\n")+P(true))+P("\n")  local escaped=P("\\")*anything  local squote=P("'")  local dquote=P('"') @@ -142,8 +147,8 @@ patterns.utfbom_32_le=utfbom_32_le  patterns.utfbom_16_be=utfbom_16_be  patterns.utfbom_16_le=utfbom_16_le  patterns.utfbom_8=utfbom_8 -patterns.utf_16_be_nl=P("\000\r\000\n")+P("\000\r")+P("\000\n") -patterns.utf_16_le_nl=P("\r\000\n\000")+P("\r\000")+P("\n\000") +patterns.utf_16_be_nl=P("\000\r\000\n")+P("\000\r")+P("\000\n")  +patterns.utf_16_le_nl=P("\r\000\n\000")+P("\r\000")+P("\n\000")   patterns.utf8one=R("\000\127")  patterns.utf8two=R("\194\223")*utf8next  patterns.utf8three=R("\224\239")*utf8next*utf8next @@ -170,9 +175,11 @@ patterns.spacer=spacer  patterns.whitespace=whitespace  patterns.nonspacer=nonspacer  patterns.nonwhitespace=nonwhitespace -local stripper=spacer^0*C((spacer^0*nonspacer^1)^0) +local stripper=spacer^0*C((spacer^0*nonspacer^1)^0)    +local fullstripper=whitespace^0*C((whitespace^0*nonwhitespace^1)^0)  local collapser=Cs(spacer^0/""*nonspacer^0*((spacer^0/" "*nonspacer^1)^0))  patterns.stripper=stripper +patterns.fullstripper=fullstripper  patterns.collapser=collapser  patterns.lowercase=lowercase  patterns.uppercase=uppercase @@ -210,9 +217,12 @@ patterns.integer=sign^-1*digit^1  patterns.unsigned=digit^0*period*digit^1  patterns.float=sign^-1*patterns.unsigned  patterns.cunsigned=digit^0*comma*digit^1 +patterns.cpunsigned=digit^0*(period+comma)*digit^1  patterns.cfloat=sign^-1*patterns.cunsigned +patterns.cpfloat=sign^-1*patterns.cpunsigned  patterns.number=patterns.float+patterns.integer  patterns.cnumber=patterns.cfloat+patterns.integer +patterns.cpnumber=patterns.cpfloat+patterns.integer  patterns.oct=zero*octdigit^1  patterns.octal=patterns.oct  patterns.HEX=zero*P("X")*(digit+uppercase)^1 @@ -395,7 +405,7 @@ function lpeg.replacer(one,two,makefunction,isutf)      return pattern    end  end -function lpeg.finder(lst,makefunction)  +function lpeg.finder(lst,makefunction,isutf)     local pattern    if type(lst)=="table" then      pattern=P(false) @@ -411,7 +421,11 @@ function lpeg.finder(lst,makefunction)    else      pattern=P(lst)    end -  pattern=(1-pattern)^0*pattern +  if isutf then +    pattern=((utf8char or 1)-pattern)^0*pattern +  else +    pattern=(1-pattern)^0*pattern +  end    if makefunction then      return function(str)        return lpegmatch(pattern,str) @@ -625,21 +639,22 @@ function lpeg.append(list,pp,delayed,checked)    end    return p  end -local function make(t) -  local p +local function make(t,hash) +  local p=P(false)    local keys=sortedkeys(t)    for i=1,#keys do      local k=keys[i]      local v=t[k] -    if not p then +    local h=hash[v] +    if h then        if next(v) then -        p=P(k)*make(v) +        p=p+P(k)*(make(v,hash)+P(true))        else -        p=P(k) +        p=p+P(k)*P(true)        end      else        if next(v) then -        p=p+P(k)*make(v) +        p=p+P(k)*make(v,hash)        else          p=p+P(k)        end @@ -649,16 +664,37 @@ local function make(t)  end  function lpeg.utfchartabletopattern(list)     local tree={} -  for i=1,#list do -    local t=tree -    for c in gmatch(list[i],".") do -      if not t[c] then -        t[c]={} +  local hash={} +  local n=#list +  if n==0 then +    for s in next,list do +      local t=tree +      for c in gmatch(s,".") do +        local tc=t[c] +        if not tc then +          tc={} +          t[c]=tc +        end +        t=tc        end -      t=t[c] +      hash[t]=s +    end +  else +    for i=1,n do +      local t=tree +      local s=list[i] +      for c in gmatch(s,".") do +        local tc=t[c] +        if not tc then +          tc={} +          t[c]=tc +        end +        t=tc +      end +      hash[t]=s      end    end -  return make(tree) +  return make(tree,hash)  end  patterns.containseol=lpeg.finder(eol)  local function nextstep(n,step,result) @@ -748,11 +784,15 @@ function string.limit(str,n,sentinel)    end  end  local stripper=patterns.stripper +local fullstripper=patterns.fullstripper  local collapser=patterns.collapser  local longtostring=patterns.longtostring  function string.strip(str)    return lpegmatch(stripper,str) or ""  end +function string.fullstrip(str) +  return lpegmatch(fullstripper,str) or "" +end  function string.collapsespaces(str)    return lpegmatch(collapser,str) or ""  end @@ -886,6 +926,36 @@ local function sortedkeys(tab)      return {}    end  end +local function sortedhashonly(tab) +  if tab then +    local srt,s={},0 +    for key,_ in next,tab do +      if type(key)=="string" then +        s=s+1 +        srt[s]=key +      end +    end +    sort(srt) +    return srt +  else +    return {} +  end +end +local function sortedindexonly(tab) +  if tab then +    local srt,s={},0 +    for key,_ in next,tab do +      if type(key)=="number" then +        s=s+1 +        srt[s]=key +      end +    end +    sort(srt) +    return srt +  else +    return {} +  end +end  local function sortedhashkeys(tab,cmp)     if tab then      local srt,s={},0 @@ -911,6 +981,8 @@ function table.allkeys(t)    return sortedkeys(keys)  end  table.sortedkeys=sortedkeys +table.sortedhashonly=sortedhashonly +table.sortedindexonly=sortedindexonly  table.sortedhashkeys=sortedhashkeys  local function nothing() end  local function sortedhash(t,cmp) @@ -923,14 +995,14 @@ local function sortedhash(t,cmp)      end      local n=0      local m=#s -    local function kv(s) +    local function kv()         if n<m then          n=n+1          local k=s[n]          return k,t[k]        end      end -    return kv,s +    return kv     else      return nothing    end @@ -1095,7 +1167,7 @@ local function simple_table(t)          if tv=="number" then            nt=nt+1            if hexify then -            tt[nt]=format("0x%04X",v) +            tt[nt]=format("0x%X",v)            else              tt[nt]=tostring(v)             end @@ -1126,7 +1198,7 @@ local function do_serialize(root,name,depth,level,indexed)        local tn=type(name)        if tn=="number" then          if hexify then -          handle(format("%s[0x%04X]={",depth,name)) +          handle(format("%s[0x%X]={",depth,name))          else            handle(format("%s[%s]={",depth,name))          end @@ -1165,7 +1237,7 @@ local function do_serialize(root,name,depth,level,indexed)        if compact and first and tk=="number" and k>=first and k<=last then          if tv=="number" then            if hexify then -            handle(format("%s 0x%04X,",depth,v)) +            handle(format("%s 0x%X,",depth,v))            else              handle(format("%s %s,",depth,v))             end @@ -1206,25 +1278,25 @@ local function do_serialize(root,name,depth,level,indexed)        elseif tv=="number" then          if tk=="number" then            if hexify then -            handle(format("%s [0x%04X]=0x%04X,",depth,k,v)) +            handle(format("%s [0x%X]=0x%X,",depth,k,v))            else              handle(format("%s [%s]=%s,",depth,k,v))             end          elseif tk=="boolean" then            if hexify then -            handle(format("%s [%s]=0x%04X,",depth,k and "true" or "false",v)) +            handle(format("%s [%s]=0x%X,",depth,k and "true" or "false",v))            else              handle(format("%s [%s]=%s,",depth,k and "true" or "false",v))             end          elseif noquotes and not reserved[k] and lpegmatch(propername,k) then            if hexify then -            handle(format("%s %s=0x%04X,",depth,k,v)) +            handle(format("%s %s=0x%X,",depth,k,v))            else              handle(format("%s %s=%s,",depth,k,v))             end          else            if hexify then -            handle(format("%s [%q]=0x%04X,",depth,k,v)) +            handle(format("%s [%q]=0x%X,",depth,k,v))            else              handle(format("%s [%q]=%s,",depth,k,v))             end @@ -1233,7 +1305,7 @@ local function do_serialize(root,name,depth,level,indexed)          if reduce and tonumber(v) then            if tk=="number" then              if hexify then -              handle(format("%s [0x%04X]=%s,",depth,k,v)) +              handle(format("%s [0x%X]=%s,",depth,k,v))              else                handle(format("%s [%s]=%s,",depth,k,v))              end @@ -1247,7 +1319,7 @@ local function do_serialize(root,name,depth,level,indexed)          else            if tk=="number" then              if hexify then -              handle(format("%s [0x%04X]=%q,",depth,k,v)) +              handle(format("%s [0x%X]=%q,",depth,k,v))              else                handle(format("%s [%s]=%q,",depth,k,v))              end @@ -1263,7 +1335,7 @@ local function do_serialize(root,name,depth,level,indexed)          if not next(v) then            if tk=="number" then              if hexify then -              handle(format("%s [0x%04X]={},",depth,k)) +              handle(format("%s [0x%X]={},",depth,k))              else                handle(format("%s [%s]={},",depth,k))              end @@ -1279,7 +1351,7 @@ local function do_serialize(root,name,depth,level,indexed)            if st then              if tk=="number" then                if hexify then -                handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", "))) +                handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", ")))                else                  handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))                end @@ -1299,7 +1371,7 @@ local function do_serialize(root,name,depth,level,indexed)        elseif tv=="boolean" then          if tk=="number" then            if hexify then -            handle(format("%s [0x%04X]=%s,",depth,k,v and "true" or "false")) +            handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false"))            else              handle(format("%s [%s]=%s,",depth,k,v and "true" or "false"))            end @@ -1315,7 +1387,7 @@ local function do_serialize(root,name,depth,level,indexed)            local f=getinfo(v).what=="C" and dump(dummy) or dump(v)            if tk=="number" then              if hexify then -              handle(format("%s [0x%04X]=load(%q),",depth,k,f)) +              handle(format("%s [0x%X]=load(%q),",depth,k,f))              else                handle(format("%s [%s]=load(%q),",depth,k,f))              end @@ -1330,7 +1402,7 @@ local function do_serialize(root,name,depth,level,indexed)        else          if tk=="number" then            if hexify then -            handle(format("%s [0x%04X]=%q,",depth,k,tostring(v))) +            handle(format("%s [0x%X]=%q,",depth,k,tostring(v)))            else              handle(format("%s [%s]=%q,",depth,k,tostring(v)))            end @@ -1384,7 +1456,7 @@ local function serialize(_handle,root,name,specification)      end    elseif tname=="number" then      if hexify then -      handle(format("[0x%04X]={",name)) +      handle(format("[0x%X]={",name))      else        handle("["..name.."]={")      end @@ -1636,7 +1708,9 @@ function table.print(t,...)      serialize(print,t,...)    end  end -setinspector(function(v) if type(v)=="table" then serialize(print,v,"table") return true end end) +if setinspector then +  setinspector(function(v) if type(v)=="table" then serialize(print,v,"table") return true end end) +end  function table.sub(t,i,j)    return { unpack(t,i,j) }  end @@ -1689,6 +1763,44 @@ function table.values(t,s)      return {}    end  end +function table.filtered(t,pattern,sort,cmp) +  if t and type(pattern)=="string" then +    if sort then +      local s +      if cmp then +        s=sortedhashkeys(t,function(a,b) return cmp(t,a,b) end) +      else +        s=sortedkeys(t)  +      end +      local n=0 +      local m=#s +      local function kv(s) +        while n<m do +          n=n+1 +          local k=s[n] +          if find(k,pattern) then +            return k,t[k] +          end +        end +      end +      return kv,s +    else +      local n=next(t) +      local function iterator() +        while n do +          local k=n +          n=next(t,k) +          if find(k,pattern) then +            return k,t[k] +          end +        end +      end +      return iterator,t +    end +  else +    return nothing +  end +end  end -- closure @@ -1706,7 +1818,7 @@ local byte,find,gsub,format=string.byte,string.find,string.gsub,string.format  local concat=table.concat  local floor=math.floor  local type=type -if string.find(os.getenv("PATH"),";") then +if string.find(os.getenv("PATH"),";",1,true) then    io.fileseparator,io.pathseparator="\\",";"  else    io.fileseparator,io.pathseparator="/",":" @@ -2508,15 +2620,28 @@ local unpack,concat=table.unpack,table.concat  local P,V,C,S,R,Ct,Cs,Cp,Carg,Cc=lpeg.P,lpeg.V,lpeg.C,lpeg.S,lpeg.R,lpeg.Ct,lpeg.Cs,lpeg.Cp,lpeg.Carg,lpeg.Cc  local patterns,lpegmatch=lpeg.patterns,lpeg.match  local utfchar,utfbyte=utf.char,utf.byte -local loadstripped=_LUAVERSION<5.2 and load or function(str) -  return load(dump(load(str),true))  +local loadstripped=nil +if _LUAVERSION<5.2 then +  loadstripped=function(str,shortcuts) +    return load(str) +  end +else +  loadstripped=function(str,shortcuts) +    if shortcuts then +      return load(dump(load(str),true),nil,nil,shortcuts) +    else +      return load(dump(load(str),true)) +    end +  end  end  if not number then number={} end   local stripper=patterns.stripzeros  local function points(n) +  n=tonumber(n)    return (not n or n==0) and "0pt" or lpegmatch(stripper,format("%.5fpt",n/65536))  end  local function basepoints(n) +  n=tonumber(n)    return (not n or n==0) and "0bp" or lpegmatch(stripper,format("%.5fbp",n*(7200/7227)/65536))  end  number.points=points @@ -2579,11 +2704,40 @@ local pattern=Carg(1)/function(t)  function strings.tabtospace(str,tab)    return lpegmatch(pattern,str,1,tab or 7)  end -function strings.striplong(str)  -  str=gsub(str,"^%s*","") -  str=gsub(str,"[\n\r]+ *","\n") -  return str +local newline=patterns.newline +local endofstring=patterns.endofstring +local whitespace=patterns.whitespace +local spacer=patterns.spacer +local space=spacer^0 +local nospace=space/"" +local endofline=nospace*newline +local stripend=(whitespace^1*endofstring)/"" +local normalline=(nospace*((1-space*(newline+endofstring))^1)*nospace) +local stripempty=endofline^1/"" +local normalempty=endofline^1 +local singleempty=endofline*(endofline^0/"") +local doubleempty=endofline*endofline^-1*(endofline^0/"") +local stripstart=stripempty^0 +local p_prune_normal=Cs (stripstart*(stripend+normalline+normalempty )^0 ) +local p_prune_collapse=Cs (stripstart*(stripend+normalline+doubleempty )^0 ) +local p_prune_noempty=Cs (stripstart*(stripend+normalline+singleempty )^0 ) +local p_retain_normal=Cs ((normalline+normalempty )^0 ) +local p_retain_collapse=Cs ((normalline+doubleempty )^0 ) +local p_retain_noempty=Cs ((normalline+singleempty )^0 ) +local striplinepatterns={ +  ["prune"]=p_prune_normal, +  ["prune and collapse"]=p_prune_collapse, +  ["prune and no empty"]=p_prune_noempty, +  ["retain"]=p_retain_normal, +  ["retain and collapse"]=p_retain_collapse, +  ["retain and no empty"]=p_retain_noempty, +  ["collapse"]=patterns.collapser, +} +strings.striplinepatterns=striplinepatterns +function strings.striplines(str,how) +  return str and lpegmatch(how and striplinepatterns[how] or p_prune_collapse,str) or str  end +strings.striplong=strings.striplines  function strings.nice(str)    str=gsub(str,"[:%-+_]+"," ")     return str @@ -2659,31 +2813,58 @@ function number.sparseexponent(f,n)    end    return tostring(n)  end -local preamble=[[ -local type = type -local tostring = tostring -local tonumber = tonumber -local format = string.format -local concat = table.concat -local signed = number.signed -local points = number.points -local basepoints = number.basepoints -local utfchar = utf.char -local utfbyte = utf.byte -local lpegmatch = lpeg.match -local nspaces = string.nspaces -local tracedchar = string.tracedchar -local autosingle = string.autosingle -local autodouble = string.autodouble -local sequenced = table.sequenced -local formattednumber = number.formatted -local sparseexponent = number.sparseexponent -]]  local template=[[  %s  %s  return function(%s) return %s end  ]] +local preamble,environment="",{} +if _LUAVERSION<5.2 then +  preamble=[[ +local lpeg=lpeg +local type=type +local tostring=tostring +local tonumber=tonumber +local format=string.format +local concat=table.concat +local signed=number.signed +local points=number.points +local basepoints= number.basepoints +local utfchar=utf.char +local utfbyte=utf.byte +local lpegmatch=lpeg.match +local nspaces=string.nspaces +local tracedchar=string.tracedchar +local autosingle=string.autosingle +local autodouble=string.autodouble +local sequenced=table.sequenced +local formattednumber=number.formatted +local sparseexponent=number.sparseexponent +    ]] +else +  environment={ +    global=global or _G, +    lpeg=lpeg, +    type=type, +    tostring=tostring, +    tonumber=tonumber, +    format=string.format, +    concat=table.concat, +    signed=number.signed, +    points=number.points, +    basepoints=number.basepoints, +    utfchar=utf.char, +    utfbyte=utf.byte, +    lpegmatch=lpeg.match, +    nspaces=string.nspaces, +    tracedchar=string.tracedchar, +    autosingle=string.autosingle, +    autodouble=string.autodouble, +    sequenced=table.sequenced, +    formattednumber=number.formatted, +    sparseexponent=number.sparseexponent, +  } +end  local arguments={ "a1" }   setmetatable(arguments,{ __index=function(t,k)      local v=t[k-1]..",a"..k @@ -2722,7 +2903,7 @@ local format_i=function(f)    if f and f~="" then      return format("format('%%%si',a%s)",f,n)    else -    return format("format('%%i',a%s)",n) +    return format("format('%%i',a%s)",n)     end  end  local format_d=format_i @@ -2734,6 +2915,14 @@ local format_f=function(f)    n=n+1    return format("format('%%%sf',a%s)",f,n)  end +local format_F=function()  +  n=n+1 +  if not f or f=="" then +    return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or format((a%s %% 1 == 0) and '%%i' or '%%.9f',a%s))",n,n,n,n) +  else +    return format("format((a%s %% 1 == 0) and '%%i' or '%%%sf',a%s)",n,f,n) +  end +end  local format_g=function(f)    n=n+1    return format("format('%%%sg',a%s)",f,n) @@ -2948,7 +3137,7 @@ local builder=Cs { "start",      (        P("%")/""*(          V("!")  -+V("s")+V("q")+V("i")+V("d")+V("f")+V("g")+V("G")+V("e")+V("E")+V("x")+V("X")+V("o") ++V("s")+V("q")+V("i")+V("d")+V("f")+V("F")+V("g")+V("G")+V("e")+V("E")+V("x")+V("X")+V("o")  +V("c")+V("C")+V("S")   +V("Q")   +V("N") @@ -2968,6 +3157,7 @@ local builder=Cs { "start",    ["i"]=(prefix_any*P("i"))/format_i,    ["d"]=(prefix_any*P("d"))/format_d,    ["f"]=(prefix_any*P("f"))/format_f, +  ["F"]=(prefix_any*P("F"))/format_F,    ["g"]=(prefix_any*P("g"))/format_g,    ["G"]=(prefix_any*P("G"))/format_G,    ["e"]=(prefix_any*P("e"))/format_e, @@ -3005,8 +3195,8 @@ local builder=Cs { "start",    ["!"]=Carg(2)*prefix_any*P("!")*C((1-P("!"))^1)*P("!")/format_extension,  }  local direct=Cs ( -    P("%")/""*Cc([[local format = string.format return function(str) return format("%]])*(S("+- .")+R("09"))^0*S("sqidfgGeExXo")*Cc([[",str) end]])*P(-1) -  ) +  P("%")*(S("+- .")+R("09"))^0*S("sqidfgGeExXo")*P(-1)/[[local format = string.format return function(str) return format("%0",str) end]] +)  local function make(t,str)    local f    local p @@ -3015,10 +3205,10 @@ local function make(t,str)      f=loadstripped(p)()    else      n=0 -    p=lpegmatch(builder,str,1,"..",t._extensions_)  +    p=lpegmatch(builder,str,1,t._connector_,t._extensions_)       if n>0 then        p=format(template,preamble,t._preamble_,arguments[n],p) -      f=loadstripped(p)() +      f=loadstripped(p,t._environment_)()       else        f=function() return str end      end @@ -3030,10 +3220,22 @@ local function use(t,fmt,...)    return t[fmt](...)  end  strings.formatters={} -function strings.formatters.new() -  local t={ _extensions_={},_preamble_="",_type_="formatter" } -  setmetatable(t,{ __index=make,__call=use }) -  return t +if _LUAVERSION<5.2 then +  function strings.formatters.new(noconcat) +    local t={ _type_="formatter",_connector_=noconcat and "," or "..",_extensions_={},_preamble_=preamble,_environment_={} } +    setmetatable(t,{ __index=make,__call=use }) +    return t +  end +else +  function strings.formatters.new(noconcat) +    local e={}  +    for k,v in next,environment do +      e[k]=v +    end +    local t={ _type_="formatter",_connector_=noconcat and "," or "..",_extensions_={},_preamble_="",_environment_=e } +    setmetatable(t,{ __index=make,__call=use }) +    return t +  end  end  local formatters=strings.formatters.new()   string.formatters=formatters  @@ -3041,8 +3243,12 @@ string.formatter=function(str,...) return formatters[str](...) end  local function add(t,name,template,preamble)    if type(t)=="table" and t._type_=="formatter" then      t._extensions_[name]=template or "%s" -    if preamble then +    if type(preamble)=="string" then        t._preamble_=preamble.."\n"..t._preamble_  +    elseif type(preamble)=="table" then +      for k,v in next,preamble do +        t._environment_[k]=v +      end      end    end  end @@ -3051,9 +3257,24 @@ patterns.xmlescape=Cs((P("<")/"<"+P(">")/">"+P("&")/"&"+P('"')/""  patterns.texescape=Cs((C(S("#$%\\{}"))/"\\%1"+P(1))^0)  patterns.luaescape=Cs(((1-S('"\n'))^1+P('"')/'\\"'+P('\n')/'\\n"')^0)   patterns.luaquoted=Cs(Cc('"')*((1-S('"\n'))^1+P('"')/'\\"'+P('\n')/'\\n"')^0*Cc('"')) -add(formatters,"xml",[[lpegmatch(xmlescape,%s)]],[[local xmlescape = lpeg.patterns.xmlescape]]) -add(formatters,"tex",[[lpegmatch(texescape,%s)]],[[local texescape = lpeg.patterns.texescape]]) -add(formatters,"lua",[[lpegmatch(luaescape,%s)]],[[local luaescape = lpeg.patterns.luaescape]]) +if _LUAVERSION<5.2 then +  add(formatters,"xml",[[lpegmatch(xmlescape,%s)]],"local xmlescape = lpeg.patterns.xmlescape") +  add(formatters,"tex",[[lpegmatch(texescape,%s)]],"local texescape = lpeg.patterns.texescape") +  add(formatters,"lua",[[lpegmatch(luaescape,%s)]],"local luaescape = lpeg.patterns.luaescape") +else +  add(formatters,"xml",[[lpegmatch(xmlescape,%s)]],{ xmlescape=lpeg.patterns.xmlescape }) +  add(formatters,"tex",[[lpegmatch(texescape,%s)]],{ texescape=lpeg.patterns.texescape }) +  add(formatters,"lua",[[lpegmatch(luaescape,%s)]],{ luaescape=lpeg.patterns.luaescape }) +end +local dquote=patterns.dquote  +local equote=patterns.escaped+dquote/'\\"'+1 +local space=patterns.space +local cquote=Cc('"') +local pattern=Cs(dquote*(equote-P(-2))^0*dquote)           ++Cs(cquote*(equote-space)^0*space*equote^0*cquote)  +function string.optionalquoted(str) +  return lpegmatch(pattern,str) or str +end  end -- closure @@ -3250,6 +3471,17 @@ function caches.loaddata(paths,name)    for i=1,#paths do      local data=false      local luaname,lucname=makefullname(paths[i],name) +    if lucname and not lfs.isfile(lucname) and type(caches.compile)=="function" then +      texio.write(string.format("(compiling luc: %s)",lucname)) +      data=loadfile(luaname) +      if data then +        data=data() +      end +      if data then +        caches.compile(data,luaname,lucname) +        return data +      end +    end      if lucname and lfs.isfile(lucname) then         texio.write(string.format("(load luc: %s)",lucname))        data=loadfile(lucname) @@ -3305,6 +3537,15 @@ end  function table.setmetatableindex(t,f)    setmetatable(t,{ __index=f })  end +arguments={} +if arg then +  for i=1,#arg do +    local k,v=string.match(arg[i],"^%-%-([^=]+)=?(.-)$") +    if k and v then +      arguments[k]=v +    end +  end +end  end -- closure @@ -3473,9 +3714,17 @@ local free_node=node.free  local remove_node=node.remove  local new_node=node.new  local traverse_id=node.traverse_id -local math_code=nodecodes.math  nodes.handlers.protectglyphs=node.protect_glyphs  nodes.handlers.unprotectglyphs=node.unprotect_glyphs +local math_code=nodecodes.math +local end_of_math=node.end_of_math +function node.end_of_math(n) +  if n.id==math_code and n.subtype==1 then +    return n +  else +    return end_of_math(n) +  end +end  function nodes.remove(head,current,free_too)    local t=current    head,current=remove_node(head,current) @@ -3769,14 +4018,15 @@ constructors.sharefonts=false  constructors.nofsharedfonts=0  local sharednames={}  function constructors.trytosharefont(target,tfmdata) -  if constructors.sharefonts then +  if constructors.sharefonts then       local characters=target.characters      local n=1      local t={ target.psname }      local u=sortedkeys(characters)      for i=1,#u do +      local k=u[i]        n=n+1;t[n]=k -      n=n+1;t[n]=characters[u[i]].index or k +      n=n+1;t[n]=characters[k].index or k      end      local h=md5.HEX(concat(t," "))      local s=sharednames[h] @@ -4006,6 +4256,7 @@ function constructors.scale(tfmdata,specification)      if changed then        local c=changed[unicode]        if c then +        local ligatures=character.ligatures           description=descriptions[c] or descriptions[unicode] or character          character=characters[c] or character          index=description.index or c @@ -4017,6 +4268,9 @@ function constructors.scale(tfmdata,specification)              touni=tounicode[i]             end          end +        if ligatures and not character.ligatures then +          character.ligatures=ligatures  +        end        else          description=descriptions[unicode] or character          index=description.index or unicode @@ -4694,6 +4948,7 @@ end  local fonts=fonts  fonts.encodings={}  fonts.encodings.agl={} +fonts.encodings.known={}  setmetatable(fonts.encodings.agl,{ __index=function(t,k)    if k=="unicodes" then      texio.write(" <loading (extended) adobe glyph list>") @@ -5620,7 +5875,6 @@ unify=function(data,filename)          if unicode then            krn[unicode]=kern          else -          print(unicode,name)          end        end        description.kerns=krn @@ -6405,7 +6659,7 @@ local type,next,tonumber,tostring=type,next,tonumber,tostring  local abs=math.abs  local insert=table.insert  local lpegmatch=lpeg.match -local reversed,concat,remove=table.reversed,table.concat,table.remove +local reversed,concat,remove,sortedkeys=table.reversed,table.concat,table.remove,table.sortedkeys  local ioflush=io.flush  local fastcopy,tohash,derivetable=table.fastcopy,table.tohash,table.derive  local formatters=string.formatters @@ -6427,7 +6681,7 @@ local report_otf=logs.reporter("fonts","otf loading")  local fonts=fonts  local otf=fonts.handlers.otf  otf.glists={ "gsub","gpos" } -otf.version=2.749  +otf.version=2.756   otf.cache=containers.define("fonts","otf",otf.version,true)  local fontdata=fonts.hashes.identifiers  local chardata=characters and characters.data  @@ -6579,6 +6833,7 @@ local valid_fields=table.tohash {    "upos",    "use_typo_metrics",    "uwidth", +  "validation_state",    "version",    "vert_base",    "weight", @@ -6969,15 +7224,22 @@ actions["prepare glyphs"]=function(data,filename,raw)              local glyph=cidglyphs[index]              if glyph then                local unicode=glyph.unicode +if   unicode>=0x00E000 and unicode<=0x00F8FF then +                unicode=-1 +elseif unicode>=0x0F0000 and unicode<=0x0FFFFD then +                unicode=-1 +elseif unicode>=0x100000 and unicode<=0x10FFFD then +                unicode=-1 +end                local name=glyph.name or cidnames[index] -              if not unicode or unicode==-1 or unicode>=criterium then +              if not unicode or unicode==-1 then                   unicode=cidunicodes[index]                end                if unicode and descriptions[unicode] then                  report_otf("preventing glyph %a at index %H to overload unicode %U",name or "noname",index,unicode)                  unicode=-1                end -              if not unicode or unicode==-1 or unicode>=criterium then +              if not unicode or unicode==-1 then                   if not name then                    name=format("u%06X",private)                  end @@ -7023,7 +7285,7 @@ actions["prepare glyphs"]=function(data,filename,raw)        if glyph then          local unicode=glyph.unicode          local name=glyph.name -        if not unicode or unicode==-1 or unicode>=criterium then +        if not unicode or unicode==-1 then             unicode=private            unicodes[name]=private            if trace_private then @@ -7045,7 +7307,6 @@ actions["prepare glyphs"]=function(data,filename,raw)          }          local altuni=glyph.altuni          if altuni then -          local d            for i=1,#altuni do              local a=altuni[i]              local u=a.unicode @@ -7058,15 +7319,8 @@ actions["prepare glyphs"]=function(data,filename,raw)                  vv={ [u]=unicode }                  variants[v]=vv                end -            elseif d then -              d[#d+1]=u -            else -              d={ u }              end            end -          if d then -            duplicates[unicode]=d -          end          end        else          report_otf("potential problem: glyph %U is used but empty",index) @@ -7084,47 +7338,45 @@ actions["check encoding"]=function(data,filename,raw)    local duplicates=resources.duplicates    local mapdata=raw.map or {}    local unicodetoindex=mapdata and mapdata.map or {} +  local indextounicode=mapdata and mapdata.backmap or {}    local encname=lower(data.enc_name or mapdata.enc_name or "") -  local criterium=0xFFFF +  local criterium=0xFFFF  +  local privateoffset=constructors.privateoffset    if find(encname,"unicode") then       if trace_loading then        report_otf("checking embedded unicode map %a",encname)      end -    for unicode,index in next,unicodetoindex do  -      if unicode<=criterium and not descriptions[unicode] then -        local parent=indices[index]  -        if not parent then -          report_otf("weird, unicode %U points to nowhere with index %H",unicode,index) +    local reported={} +    for maybeunicode,index in next,unicodetoindex do +      if descriptions[maybeunicode] then +      else +        local unicode=indices[index] +        if not unicode then +        elseif maybeunicode==unicode then +        elseif unicode>privateoffset then          else -          local parentdescription=descriptions[parent] -          if parentdescription then -            local altuni=parentdescription.altuni -            if not altuni then -              altuni={ { unicode=unicode } } -              parentdescription.altuni=altuni -              duplicates[parent]={ unicode } +          local d=descriptions[unicode] +          if d then +            local c=d.copies +            if c then +              c[maybeunicode]=true              else -              local done=false -              for i=1,#altuni do -                if altuni[i].unicode==unicode then -                  done=true -                  break -                end -              end -              if not done then -                insert(altuni,{ unicode=unicode }) -                insert(duplicates[parent],unicode) -              end -            end -            if trace_loading then -              report_otf("weird, unicode %U points to nowhere with index %H",unicode,index) +              d.copies={ [maybeunicode]=true }              end -          else -            report_otf("weird, unicode %U points to %U with index %H",unicode,index) +          elseif index and not reported[index] then +            report_otf("missing index %i",index) +            reported[index]=true            end          end        end      end +    for unicode,data in next,descriptions do +      local d=data.copies +      if d then +        duplicates[unicode]=sortedkeys(d) +        data.copies=nil +      end +    end    elseif properties.cidinfo then      report_otf("warning: no unicode map, used cidmap %a",properties.cidinfo.usedname)    else @@ -7132,6 +7384,7 @@ actions["check encoding"]=function(data,filename,raw)    end    if mapdata then      mapdata.map={}  +    mapdata.backmap={}     end  end  actions["add duplicates"]=function(data,filename,raw) @@ -7142,28 +7395,37 @@ actions["add duplicates"]=function(data,filename,raw)    local indices=resources.indices     local duplicates=resources.duplicates    for unicode,d in next,duplicates do -    for i=1,#d do -      local u=d[i] -      if not descriptions[u] then -        local description=descriptions[unicode] -        local duplicate=table.copy(description)  -        duplicate.comment=format("copy of U+%05X",unicode) -        descriptions[u]=duplicate -        local n=0 -        for _,description in next,descriptions do -          if kerns then -            local kerns=description.kerns -            for _,k in next,kerns do -              local ku=k[unicode] -              if ku then -                k[u]=ku -                n=n+1 +    local nofduplicates=#d +    if nofduplicates>4 then +      if trace_loading then +        report_otf("ignoring excessive duplicates of %U (n=%s)",unicode,nofduplicates) +      end +    else +      for i=1,nofduplicates do +        local u=d[i] +        if not descriptions[u] then +          local description=descriptions[unicode] +          local n=0 +          for _,description in next,descriptions do +            if kerns then +              local kerns=description.kerns +              for _,k in next,kerns do +                local ku=k[unicode] +                if ku then +                  k[u]=ku +                  n=n+1 +                end                end              end            end -        end -        if trace_loading then -          report_otf("duplicating %U to %U with index %H (%s kerns)",unicode,u,description.index,n) +          if u>0 then  +            local duplicate=table.copy(description)  +            duplicate.comment=format("copy of U+%05X",unicode) +            descriptions[u]=duplicate +            if trace_loading then +              report_otf("duplicating %U to %U with index %H (%s kerns)",unicode,u,description.index,n) +            end +          end          end        end      end @@ -7358,10 +7620,16 @@ actions["reorganize subtables"]=function(data,filename,raw)              report_otf("skipping weird lookup number %s",k)            elseif features then              local f={} +            local o={}              for i=1,#features do                local df=features[i]                local tag=strip(lower(df.tag)) -              local ft=f[tag] if not ft then ft={} f[tag]=ft end +              local ft=f[tag] +              if not ft then +                ft={} +                f[tag]=ft +                o[#o+1]=tag +              end                local dscripts=df.scripts                for i=1,#dscripts do                  local d=dscripts[i] @@ -7381,6 +7649,7 @@ actions["reorganize subtables"]=function(data,filename,raw)                subtables=subtables,                markclass=markclass,                features=f, +              order=o,              }            else              lookups[name]={ @@ -7616,6 +7885,14 @@ actions["reorganize lookups"]=function(data,filename,raw)                  rule.current=s_hashed(names,s_h_cache)                end                rule.glyphs=nil +              local lookups=rule.lookups +              if lookups then +                for i=1,#names do +                  if not lookups[i] then +                    lookups[i]=""  +                  end +                end +              end              end            end          end @@ -7887,6 +8164,11 @@ actions["check metadata"]=function(data,filename,raw)        ttftables[i].data="deleted"      end    end +  if metadata.validation_state and table.contains(metadata.validation_state,"bad_ps_fontname") then +    local name=file.nameonly(filename) +    metadata.fontname="bad-fontname-"..name +    metadata.fullname="bad-fullname-"..name +  end  end  actions["cleanup tables"]=function(data,filename,raw)    data.resources.indices=nil  @@ -8184,6 +8466,24 @@ local function otftotfm(specification)      local features=specification.features.normal      local rawdata=otf.load(filename,sub,features and features.featurefile)      if rawdata and next(rawdata) then +      local descriptions=rawdata.descriptions +      local duplicates=rawdata.resources.duplicates +      if duplicates then +        local nofduplicates,nofduplicated=0,0 +        for parent,list in next,duplicates do +          for i=1,#list do +            local unicode=list[i] +            if not descriptions[unicode] then +              descriptions[unicode]=descriptions[parent]  +              nofduplicated=nofduplicated+1 +            end +          end +          nofduplicates=nofduplicates+#list +        end +        if trace_otf and nofduplicated~=nofduplicates then +          report_otf("%i extra duplicates copied out of %i",nofduplicated,nofduplicates) +        end +      end        rawdata.lookuphash={}        tfmdata=copytotfm(rawdata,cache_id)        if tfmdata and next(tfmdata) then @@ -8341,13 +8641,14 @@ local function gref(descriptions,n)        return f_unicode(n)      end    elseif n then -    local num,nam={},{} -    for i=2,#n do +    local num,nam,j={},{},0 +    for i=1,#n do        local ni=n[i]        if tonumber(ni) then  +        j=j+1          local di=descriptions[ni] -        num[i]=f_unicode(ni) -        nam[i]=di and di.name or "-" +        num[j]=f_unicode(ni) +        nam[j]=di and di.name or "-"        end      end      return f_unilist(num,nam) @@ -8430,8 +8731,8 @@ local function finalize_ligatures(tfmdata,ligatures)          local ligature=ligatures[i]          if ligature then            local unicode,lookupdata=ligature[1],ligature[2] -          if trace then -            trace_ligatures_detail("building % a into %a",lookupdata,unicode) +          if trace_ligatures_detail then +            report_prepare("building % a into %a",lookupdata,unicode)            end            local size=#lookupdata            local firstcode=lookupdata[1]  @@ -8443,8 +8744,8 @@ local function finalize_ligatures(tfmdata,ligatures)                local firstdata=characters[firstcode]                if not firstdata then                  firstcode=private -                if trace then -                  trace_ligatures_detail("defining %a as %a",firstname,firstcode) +                if trace_ligatures_detail then +                  report_prepare("defining %a as %a",firstname,firstcode)                  end                  unicodes[firstname]=firstcode                  firstdata={ intermediate=true,ligatures={} } @@ -8467,8 +8768,8 @@ local function finalize_ligatures(tfmdata,ligatures)                    break                  end                end -              if trace then -                trace_ligatures_detail("codes (%a,%a) + (%a,%a) -> %a",firstname,firstcode,secondname,secondcode,target) +              if trace_ligatures_detail then +                report_prepare("codes (%a,%a) + (%a,%a) -> %a",firstname,firstcode,secondname,secondcode,target)                end                local firstligs=firstdata.ligatures                if firstligs then @@ -8479,6 +8780,8 @@ local function finalize_ligatures(tfmdata,ligatures)                firstcode=target                firstname=secondname              end +          elseif trace_ligatures_detail then +            report_prepare("no glyph (%a,%a) for building %a",firstname,firstcode,target)            end            if okay then              ligatures[i]=false @@ -8488,12 +8791,14 @@ local function finalize_ligatures(tfmdata,ligatures)        end        alldone=done==0      end -    if trace then -      for k,v in next,characters do -        if v.ligatures then table.print(v,k) end +    if trace_ligatures_detail then +      for k,v in table.sortedhash(characters) do +        if v.ligatures then +          table.print(v,k) +        end        end      end -    tfmdata.resources.private=private +    resources.private=private    end  end  local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplist) @@ -8712,7 +9017,7 @@ local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplis          end          changed[unicode]=data        elseif lookuptype=="alternate" then -        local replacement=data[alternate] +         local replacement=data[alternate]          if replacement then            changed[unicode]=replacement            if trace_alternatives then @@ -8803,8 +9108,9 @@ basemethods.shared={  basemethod="independent"  local function featuresinitializer(tfmdata,value)    if true then  -    local t=trace_preparing and os.clock() +    local starttime=trace_preparing and os.clock()      local features=tfmdata.shared.features +    local fullname=trace_preparing and tfmdata.properties.fullname      if features then        applybasemethod("initializehashes",tfmdata)        local collectlookups=otf.collectlookups @@ -8814,26 +9120,34 @@ local function featuresinitializer(tfmdata,value)        local language=properties.language        local basesubstitutions=rawdata.resources.features.gsub        local basepositionings=rawdata.resources.features.gpos -      if basesubstitutions then -        for feature,data in next,basesubstitutions do -          local value=features[feature] -          if value then -            local validlookups,lookuplist=collectlookups(rawdata,feature,script,language) -            if validlookups then -              applybasemethod("preparesubstitutions",tfmdata,feature,value,validlookups,lookuplist) -              registerbasefeature(feature,value) -            end -          end -        end -      end -      if basepositionings then -        for feature,data in next,basepositionings do -          local value=features[feature] -          if value then -            local validlookups,lookuplist=collectlookups(rawdata,feature,script,language) -            if validlookups then -              applybasemethod("preparepositionings",tfmdata,feature,features[feature],validlookups,lookuplist) -              registerbasefeature(feature,value) +      if basesubstitutions or basepositionings then +        local sequences=tfmdata.resources.sequences +        for s=1,#sequences do +          local sequence=sequences[s] +          local sfeatures=sequence.features +          if sfeatures then +            local order=sequence.order +            if order then +              for i=1,#order do  +                local feature=order[i] +                if features[feature] then +                  local validlookups,lookuplist=collectlookups(rawdata,feature,script,language) +                  if not validlookups then +                  elseif basesubstitutions and basesubstitutions[feature] then +                    if trace_preparing then +                      report_prepare("filtering base feature %a for %a",feature,fullname) +                    end +                    applybasemethod("preparesubstitutions",tfmdata,feature,value,validlookups,lookuplist) +                    registerbasefeature(feature,value) +                  elseif basepositionings and basepositionings[feature] then +                    if trace_preparing then +                      report_prepare("filtering base feature %a for %a",feature,fullname) +                    end +                    applybasemethod("preparepositionings",tfmdata,feature,features[feature],validlookups,lookuplist) +                    registerbasefeature(feature,value) +                  end +                end +              end              end            end          end @@ -8841,7 +9155,7 @@ local function featuresinitializer(tfmdata,value)        registerbasehash(tfmdata)      end      if trace_preparing then -      report_prepare("preparation time is %0.3f seconds for %a",os.clock()-t,tfmdata.properties.fullname) +      report_prepare("preparation time is %0.3f seconds for %a",os.clock()-starttime,fullname)      end    end  end @@ -8881,26 +9195,12 @@ nodes.injections=nodes.injections or {}  local injections=nodes.injections  local nodecodes=nodes.nodecodes  local glyph_code=nodecodes.glyph -local disc_code=nodecodes.disc  local kern_code=nodecodes.kern -local nuts=nodes.nuts -local nodepool=nuts.pool +local nodepool=nodes.pool  local newkern=nodepool.kern -local tonode=nuts.tonode -local tonut=nuts.tonut -local getfield=nuts.getfield -local getnext=nuts.getnext -local getprev=nuts.getprev -local getid=nuts.getid -local getattr=nuts.getattr -local getfont=nuts.getfont -local getsubtype=nuts.getsubtype -local getchar=nuts.getchar -local setfield=nuts.setfield -local setattr=nuts.setattr -local traverse_id=nuts.traverse_id -local insert_node_before=nuts.insert_before -local insert_node_after=nuts.insert_after +local traverse_id=node.traverse_id +local insert_node_before=node.insert_before +local insert_node_after=node.insert_after  local a_kernpair=attributes.private('kernpair')  local a_ligacomp=attributes.private('ligacomp')  local a_markbase=attributes.private('markbase') @@ -8919,21 +9219,21 @@ function injections.setcursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmne    local dx,dy=factor*(exit[1]-entry[1]),factor*(exit[2]-entry[2])    local ws,wn=tfmstart.width,tfmnext.width    local bound=#cursives+1 -  setattr(start,a_cursbase,bound) -  setattr(nxt,a_curscurs,bound) +  start[a_cursbase]=bound +  nxt[a_curscurs]=bound    cursives[bound]={ rlmode,dx,dy,ws,wn }    return dx,dy,bound  end  function injections.setpair(current,factor,rlmode,r2lflag,spec,tfmchr)    local x,y,w,h=factor*spec[1],factor*spec[2],factor*spec[3],factor*spec[4]    if x~=0 or w~=0 or y~=0 or h~=0 then -    local bound=getattr(current,a_kernpair) +    local bound=current[a_kernpair]      if bound then        local kb=kerns[bound]        kb[2],kb[3],kb[4],kb[5]=(kb[2] or 0)+x,(kb[3] or 0)+y,(kb[4] or 0)+w,(kb[5] or 0)+h      else        bound=#kerns+1 -      setattr(current,a_kernpair,bound) +      current[a_kernpair]=bound        kerns[bound]={ rlmode,x,y,w,h,r2lflag,tfmchr.width }      end      return x,y,w,h,bound @@ -8944,35 +9244,35 @@ function injections.setkern(current,factor,rlmode,x,tfmchr)    local dx=factor*x    if dx~=0 then      local bound=#kerns+1 -    setattr(current,a_kernpair,bound) +    current[a_kernpair]=bound      kerns[bound]={ rlmode,dx }      return dx,bound    else      return 0,0    end  end -function injections.setmark(start,base,factor,rlmode,ba,ma,index,baseismark)  -  local dx,dy=factor*(ba[1]-ma[1]),factor*(ba[2]-ma[2])    -  local bound=getattr(base,a_markbase)           +function injections.setmark(start,base,factor,rlmode,ba,ma)  +  local dx,dy=factor*(ba[1]-ma[1]),factor*(ba[2]-ma[2]) +  local bound=base[a_markbase]    local index=1    if bound then      local mb=marks[bound]      if mb then        index=#mb+1        mb[index]={ dx,dy,rlmode } -      setattr(start,a_markmark,bound) -      setattr(start,a_markdone,index) +      start[a_markmark]=bound +      start[a_markdone]=index        return dx,dy,bound      else -      report_injections("possible problem, %U is base mark without data (id %a)",getchar(base),bound) +      report_injections("possible problem, %U is base mark without data (id %a)",base.char,bound)      end    end    index=index or 1    bound=#marks+1 -  setattr(base,a_markbase,bound) -  setattr(start,a_markmark,bound) -  setattr(start,a_markdone,index) -  marks[bound]={ [index]={ dx,dy,rlmode,baseismark } } +  base[a_markbase]=bound +  start[a_markmark]=bound +  start[a_markdone]=index +  marks[bound]={ [index]={ dx,dy,rlmode } }    return dx,dy,bound  end  local function dir(n) @@ -8981,15 +9281,15 @@ end  local function trace(head)    report_injections("begin run")    for n in traverse_id(glyph_code,head) do -    if getsubtype(n)<256 then -      local kp=getattr(n,a_kernpair) -      local mb=getattr(n,a_markbase) -      local mm=getattr(n,a_markmark) -      local md=getattr(n,a_markdone) -      local cb=getattr(n,a_cursbase) -      local cc=getattr(n,a_curscurs) -      local char=getchar(n) -      report_injections("font %s, char %U, glyph %c",getfont(n),char,char) +    if n.subtype<256 then +      local kp=n[a_kernpair] +      local mb=n[a_markbase] +      local mm=n[a_markmark] +      local md=n[a_markdone] +      local cb=n[a_cursbase] +      local cc=n[a_curscurs] +      local char=n.char +      report_injections("font %s, char %U, glyph %c",n.font,char,char)        if kp then          local k=kerns[kp]          if k[3] then @@ -9030,23 +9330,21 @@ local function show_result(head)    local current=head    local skipping=false    while current do -    local id=getid(current) +    local id=current.id      if id==glyph_code then -      report_injections("char: %C, width %p, xoffset %p, yoffset %p", -        getchar(current),getfield(current,"width"),getfield(current,"xoffset"),getfield(current,"yoffset")) +      report_injections("char: %C, width %p, xoffset %p, yoffset %p",current.char,current.width,current.xoffset,current.yoffset)        skipping=false      elseif id==kern_code then -      report_injections("kern: %p",getfield(current,"kern")) +      report_injections("kern: %p",current.kern)        skipping=false      elseif not skipping then        report_injections()        skipping=true      end -    current=getnext(current) +    current=current.next    end  end  function injections.handler(head,where,keep) -  head=tonut(head)    local has_marks,has_cursives,has_kerns=next(marks),next(cursives),next(kerns)    if has_marks or has_cursives then      if trace_injections then @@ -9056,18 +9354,17 @@ function injections.handler(head,where,keep)      if has_kerns then         local nf,tm=nil,nil        for n in traverse_id(glyph_code,head) do  -        if getsubtype(n)<256 then +        if n.subtype<256 then            nofvalid=nofvalid+1            valid[nofvalid]=n -          local f=getfont(n) -          if f~=nf then -            nf=f -            tm=fontdata[nf].resources.marks  +          if n.font~=nf then +            nf=n.font +            tm=fontdata[nf].resources.marks            end            if tm then -            mk[n]=tm[getchar(n)] +            mk[n]=tm[n.char]            end -          local k=getattr(n,a_kernpair) +          local k=n[a_kernpair]            if k then              local kk=kerns[k]              if kk then @@ -9087,16 +9384,15 @@ function injections.handler(head,where,keep)      else        local nf,tm=nil,nil        for n in traverse_id(glyph_code,head) do -        if getsubtype(n)<256 then +        if n.subtype<256 then            nofvalid=nofvalid+1            valid[nofvalid]=n -          local f=getfont(n) -          if f~=nf then -            nf=f -            tm=fontdata[nf].resources.marks  +          if n.font~=nf then +            nf=n.font +            tm=fontdata[nf].resources.marks            end            if tm then -            mk[n]=tm[getchar(n)] +            mk[n]=tm[n.char]            end          end        end @@ -9105,7 +9401,7 @@ function injections.handler(head,where,keep)        local cx={}        if has_kerns and next(ky) then          for n,k in next,ky do -          setfield(n,"yoffset",k) +          n.yoffset=k          end        end        if has_cursives then @@ -9114,9 +9410,9 @@ function injections.handler(head,where,keep)          for i=1,nofvalid do             local n=valid[i]            if not mk[n] then -            local n_cursbase=getattr(n,a_cursbase) +            local n_cursbase=n[a_cursbase]              if p_cursbase then -              local n_curscurs=getattr(n,a_curscurs) +              local n_curscurs=n[a_curscurs]                if p_cursbase==n_curscurs then                  local c=cursives[n_curscurs]                  if c then @@ -9139,20 +9435,20 @@ function injections.handler(head,where,keep)                  end                end              elseif maxt>0 then -              local ny=getfield(n,"yoffset") +              local ny=n.yoffset                for i=maxt,1,-1 do                  ny=ny+d[i]                  local ti=t[i] -                setfield(ti,"yoffset",getfield(ti,"yoffset")+ny) +                ti.yoffset=ti.yoffset+ny                end                maxt=0              end              if not n_cursbase and maxt>0 then -              local ny=getfield(n,"yoffset") +              local ny=n.yoffset                for i=maxt,1,-1 do                  ny=ny+d[i]                  local ti=t[i] -                setfield(ti,"yoffset",ny) +                ti.yoffset=ny                end                maxt=0              end @@ -9160,11 +9456,11 @@ function injections.handler(head,where,keep)            end          end          if maxt>0 then -          local ny=getfield(n,"yoffset") +          local ny=n.yoffset            for i=maxt,1,-1 do              ny=ny+d[i]              local ti=t[i] -            setfield(ti,"yoffset",ny) +            ti.yoffset=ny            end            maxt=0          end @@ -9175,66 +9471,57 @@ function injections.handler(head,where,keep)        if has_marks then          for i=1,nofvalid do            local p=valid[i] -          local p_markbase=getattr(p,a_markbase) +          local p_markbase=p[a_markbase]            if p_markbase then              local mrks=marks[p_markbase]              local nofmarks=#mrks -            for n in traverse_id(glyph_code,getnext(p)) do -              local n_markmark=getattr(n,a_markmark) +            for n in traverse_id(glyph_code,p.next) do +              local n_markmark=n[a_markmark]                if p_markbase==n_markmark then -                local index=getattr(n,a_markdone) or 1 +                local index=n[a_markdone] or 1                  local d=mrks[index]                  if d then                    local rlmode=d[3]                    local k=wx[p] -                  local px=getfield(p,"xoffset") -                  local ox=0                    if k then                      local x=k[2]                      local w=k[4]                      if w then                        if rlmode and rlmode>=0 then -                        ox=px-getfield(p,"width")+d[1]-(w-x) +                        n.xoffset=p.xoffset-p.width+d[1]-(w-x)                        else -                        ox=px-d[1]-x +                        n.xoffset=p.xoffset-d[1]-x                        end                      else                        if rlmode and rlmode>=0 then -                        ox=px-getfield(p,"width")+d[1] +                        n.xoffset=p.xoffset-p.width+d[1]                        else -                        ox=px-d[1]-x +                        n.xoffset=p.xoffset-d[1]-x                        end                      end                    else -                    local wp=getfield(p,"width") -                    local wn=getfield(n,"width")                       if rlmode and rlmode>=0 then -                      ox=px-wp+d[1] +                      n.xoffset=p.xoffset-p.width+d[1]                      else -                      ox=px-d[1] +                      n.xoffset=p.xoffset-d[1]                      end -                    if wn~=0 then -                      insert_node_before(head,n,newkern(-wn/2)) -                      insert_node_after(head,n,newkern(-wn/2)) +                    local w=n.width +                    if w~=0 then +                      insert_node_before(head,n,newkern(-w/2)) +                      insert_node_after(head,n,newkern(-w/2))                      end                    end -                  setfield(n,"xoffset",ox) -                  local py=getfield(p,"yoffset") -                  local oy=0                    if mk[p] then -                    oy=py+d[2] +                    n.yoffset=p.yoffset+d[2]                    else -                    oy=getfield(n,"yoffset")+py+d[2] +                    n.yoffset=n.yoffset+p.yoffset+d[2]                    end -                  setfield(n,"yoffset",oy)                    if nofmarks==1 then                      break                    else                      nofmarks=nofmarks-1                    end                  end -              elseif not n_markmark then -                break                 else                end              end @@ -9286,7 +9573,6 @@ function injections.handler(head,where,keep)        if not keep then          kerns={}        end -head=tonode(head)        return head,true      elseif not keep then        kerns,cursives,marks={},{},{} @@ -9296,14 +9582,14 @@ head=tonode(head)        trace(head)      end      for n in traverse_id(glyph_code,head) do -      if getsubtype(n)<256 then -        local k=getattr(n,a_kernpair) +      if n.subtype<256 then +        local k=n[a_kernpair]          if k then            local kk=kerns[k]            if kk then              local rl,x,y,w=kk[1],kk[2] or 0,kk[3],kk[4]              if y and y~=0 then -              setfield(n,"yoffset",y)  +              n.yoffset=y               end              if w then                local wx=w-x @@ -9334,10 +9620,10 @@ head=tonode(head)      if not keep then        kerns={}      end -    return tonode(head),true +    return head,true    else    end -  return tonode(head),false +  return head,false  end  end -- closure @@ -9511,6 +9797,7 @@ local isolated={    [0x0856]=true,[0x0858]=true,[0x0857]=true,    [0x07FA]=true,    [zwnj]=true, +  [0x08AD]=true,  }  local final={     [0x0622]=true,[0x0623]=true,[0x0624]=true,[0x0625]=true, @@ -9528,15 +9815,16 @@ local final={    [0x06D3]=true,[0x06D5]=true,[0x06EE]=true,[0x06EF]=true,    [0x0759]=true,[0x075A]=true,[0x075B]=true,[0x076B]=true,    [0x076C]=true,[0x0771]=true,[0x0773]=true,[0x0774]=true, -	[0x0778]=true,[0x0779]=true, +  [0x0778]=true,[0x0779]=true,    [0x08AA]=true,[0x08AB]=true,[0x08AC]=true,    [0xFEF5]=true,[0xFEF7]=true,[0xFEF9]=true,[0xFEFB]=true, -	[0x0710]=true,[0x0715]=true,[0x0716]=true,[0x0717]=true, -	[0x0718]=true,[0x0719]=true,[0x0728]=true,[0x072A]=true, -	[0x072C]=true,[0x071E]=true, +  [0x0710]=true,[0x0715]=true,[0x0716]=true,[0x0717]=true, +  [0x0718]=true,[0x0719]=true,[0x0728]=true,[0x072A]=true, +  [0x072C]=true,[0x071E]=true,    [0x072F]=true,[0x074D]=true,    [0x0840]=true,[0x0849]=true,[0x0854]=true,[0x0846]=true, -  [0x084F]=true +  [0x084F]=true, +  [0x08AE]=true,[0x08B1]=true,[0x08B2]=true,  }  local medial={     [0x0626]=true,[0x0628]=true,[0x062A]=true,[0x062B]=true, @@ -9596,8 +9884,8 @@ local medial={    [0x07D2]=true,[0x07D0]=true,[0x07CF]=true,[0x07CD]=true,    [0x07CB]=true,[0x07D3]=true,[0x07E4]=true,[0x07D5]=true,    [0x07E6]=true, -  [tatweel]=true, -  [zwj]=true, +  [tatweel]=true,[zwj]=true, +  [0x08A1]=true,[0x08AF]=true,[0x08B0]=true,  }  local arab_warned={}  local function warning(current,what) @@ -9752,25 +10040,12 @@ registertracker("otf.positions","otf.marks,otf.kerns,otf.cursive")  registertracker("otf.actions","otf.replacements,otf.positions")  registertracker("otf.injections","nodes.injections")  registertracker("*otf.sample","otf.steps,otf.actions,otf.analyzing") -local nuts=nodes.nuts -local tonode=nuts.tonode -local tonut=nuts.tonut -local getfield=nuts.getfield -local getnext=nuts.getnext -local getprev=nuts.getprev -local getid=nuts.getid -local getattr=nuts.getattr -local getfont=nuts.getfont -local getsubtype=nuts.getsubtype -local getchar=nuts.getchar -local setfield=nuts.setfield -local setattr=nuts.setattr -local insert_node_after=nuts.insert_after -local delete_node=nuts.delete -local copy_node=nuts.copy -local find_node_tail=nuts.tail -local flush_node_list=nuts.flush_list -local end_of_math=nuts.end_of_math +local insert_node_after=node.insert_after +local delete_node=nodes.delete +local copy_node=node.copy +local find_node_tail=node.tail or node.slide +local flush_node_list=node.flush_list +local end_of_math=node.end_of_math  local setmetatableindex=table.setmetatableindex  local zwnj=0x200C  local zwj=0x200D @@ -9881,83 +10156,83 @@ local function pref(kind,lookupname)    return formatters["feature %a, lookup %a"](kind,lookupname)  end  local function copy_glyph(g)  -  local components=getfield(g,"components") +  local components=g.components    if components then -    setfield(g,"components",nil) +    g.components=nil      local n=copy_node(g) -    setfield(g,"components",components) +    g.components=components      return n    else      return copy_node(g)    end  end  local function markstoligature(kind,lookupname,head,start,stop,char) -  if start==stop and getchar(start)==char then +  if start==stop and start.char==char then      return head,start    else -    local prev=getprev(start) -    local next=getnext(stop) -    setfield(start,"prev",nil) -    setfield(stop,"next",nil) +    local prev=start.prev +    local next=stop.next +    start.prev=nil +    stop.next=nil      local base=copy_glyph(start)      if head==start then        head=base      end -    setfield(base,"char",char) -    setfield(base,"subtype",ligature_code) -    setfield(base,"components",start) +    base.char=char +    base.subtype=ligature_code +    base.components=start      if prev then -      setfield(prev,"next",base) +      prev.next=base      end      if next then -      setfield(next,"prev",base) +      next.prev=base      end -    setfield(base,"next",next) -    setfield(base,"prev",prev) +    base.next=next +    base.prev=prev      return head,base    end  end  local function getcomponentindex(start) -  if getid(start)~=glyph_code then +  if start.id~=glyph_code then      return 0 -  elseif getsubtype(start)==ligature_code then +  elseif start.subtype==ligature_code then      local i=0 -    local components=getfield(start,"components") +    local components=start.components      while components do        i=i+getcomponentindex(components) -      components=getnext(components) +      components=components.next      end      return i -  elseif not marks[getchar(start)] then +  elseif not marks[start.char] then      return 1    else      return 0    end  end  local function toligature(kind,lookupname,head,start,stop,char,markflag,discfound)  -  if start==stop and getchar(start)==char then -    setfield(start,"char",char) +  if start==stop and start.char==char then +    start.char=char      return head,start    end -  local prev=getprev(start) -  local next=getnext(stop) -  setfield(start,"prev",nil) -  setfield(stop,"next",nil) +  local prev=start.prev +  local next=stop.next +  start.prev=nil +  stop.next=nil    local base=copy_glyph(start)    if start==head then      head=base    end -  setfield(base,"char",char) -  setfield(base,"subtype",ligature_code) -  setfield(base,"components",start)  +  base.char=char +  base.subtype=ligature_code +  base.components=start     if prev then -    setfield(prev,"next",base) +    prev.next=base    end    if next then -    setfield(next,"prev",base) +    next.prev=base    end -  setfield(base,"next",next) -  setfield(base,"prev",prev) +  base.next=next +  base.prev=prev    if not discfound then      local deletemarks=markflag~="mark"      local components=start @@ -9966,42 +10241,42 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun      local head=base      local current=base      while start do -      local char=getchar(start) +      local char=start.char        if not marks[char] then          baseindex=baseindex+componentindex          componentindex=getcomponentindex(start)        elseif not deletemarks then  -        setattr(start,a_ligacomp,baseindex+(getattr(start,a_ligacomp) or componentindex)) +        start[a_ligacomp]=baseindex+(start[a_ligacomp] or componentindex)          if trace_marks then -          logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(char),getattr(start,a_ligacomp)) +          logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(char),start[a_ligacomp])          end          head,current=insert_node_after(head,current,copy_node(start))         elseif trace_marks then          logwarning("%s: delete mark %s",pref(kind,lookupname),gref(char))        end -      start=getnext(start) +      start=start.next      end -    local start=getnext(current) -    while start and getid(start)==glyph_code do -      local char=getchar(start) +    local start=current.next +    while start and start.id==glyph_code do +      local char=start.char        if marks[char] then -        setattr(start,a_ligacomp,baseindex+(getattr(start,a_ligacomp) or componentindex)) +        start[a_ligacomp]=baseindex+(start[a_ligacomp] or componentindex)          if trace_marks then -          logwarning("%s: set mark %s, gets index %s",pref(kind,lookupname),gref(char),getattr(start,a_ligacomp)) +          logwarning("%s: set mark %s, gets index %s",pref(kind,lookupname),gref(char),start[a_ligacomp])          end        else          break        end -      start=getnext(start) +      start=start.next      end    end    return head,base  end  function handlers.gsub_single(head,start,kind,lookupname,replacement)    if trace_singles then -    logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(getchar(start)),gref(replacement)) +    logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(start.char),gref(replacement))    end -  setfield(start,"char",replacement) +  start.char=replacement    return head,start,true  end  local function get_alternative_glyph(start,alternatives,value,trace_alternatives) @@ -10027,7 +10302,7 @@ local function get_alternative_glyph(start,alternatives,value,trace_alternatives          return false,trace_alternatives and formatters["invalid value %a, %s"](value,"out of range")        end      elseif value==0 then -      return getchar(start),trace_alternatives and formatters["invalid value %a, %s"](value,"no change") +      return start.char,trace_alternatives and formatters["invalid value %a, %s"](value,"no change")      elseif value<1 then        return alternatives[1],trace_alternatives and formatters["invalid value %a, taking %a"](value,1)      else @@ -10038,25 +10313,25 @@ end  local function multiple_glyphs(head,start,multiple,ignoremarks)    local nofmultiples=#multiple    if nofmultiples>0 then -    setfield(start,"char",multiple[1]) +    start.char=multiple[1]      if nofmultiples>1 then -      local sn=getnext(start) +      local sn=start.next        for k=2,nofmultiples do          local n=copy_node(start)  -        setfield(n,"char",multiple[k]) -        setfield(n,"next",sn) -        setfield(n,"prev",start) +        n.char=multiple[k] +        n.next=sn +        n.prev=start          if sn then -          setfield(sn,"prev",n) +          sn.prev=n          end -        setfield(start,"next",n) +        start.next=n          start=n        end      end      return head,start,true    else      if trace_multiples then -      logprocess("no multiple for %s",gref(getchar(start))) +      logprocess("no multiple for %s",gref(start.char))      end      return head,start,false    end @@ -10066,34 +10341,34 @@ function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence    local choice,comment=get_alternative_glyph(start,alternative,value,trace_alternatives)    if choice then      if trace_alternatives then -      logprocess("%s: replacing %s by alternative %a to %s, %s",pref(kind,lookupname),gref(getchar(start)),choice,gref(choice),comment) +      logprocess("%s: replacing %s by alternative %a to %s, %s",pref(kind,lookupname),gref(start.char),choice,gref(choice),comment)      end -    setfield(start,"char",choice) +    start.char=choice    else      if trace_alternatives then -      logwarning("%s: no variant %a for %s, %s",pref(kind,lookupname),value,gref(getchar(start)),comment) +      logwarning("%s: no variant %a for %s, %s",pref(kind,lookupname),value,gref(start.char),comment)      end    end    return head,start,true  end  function handlers.gsub_multiple(head,start,kind,lookupname,multiple,sequence)    if trace_multiples then -    logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(getchar(start)),gref(multiple)) +    logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(start.char),gref(multiple))    end    return multiple_glyphs(head,start,multiple,sequence.flags[1])  end  function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence) -  local s,stop,discfound=getnext(start),nil,false -  local startchar=getchar(start) +  local s,stop,discfound=start.next,nil,false +  local startchar=start.char    if marks[startchar] then      while s do -      local id=getid(s) -      if id==glyph_code and getfont(s)==currentfont and getsubtype(s)<256 then -        local lg=ligature[getchar(s)] +      local id=s.id +      if id==glyph_code and s.font==currentfont and s.subtype<256 then +        local lg=ligature[s.char]          if lg then            stop=s            ligature=lg -          s=getnext(s) +          s=s.next          else            break          end @@ -10105,9 +10380,9 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)        local lig=ligature.ligature        if lig then          if trace_ligatures then -          local stopchar=getchar(stop) +          local stopchar=stop.char            head,start=markstoligature(kind,lookupname,head,start,stop,lig) -          logprocess("%s: replacing %s upto %s by ligature %s case 1",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start))) +          logprocess("%s: replacing %s upto %s by ligature %s case 1",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char))          else            head,start=markstoligature(kind,lookupname,head,start,stop,lig)          end @@ -10118,18 +10393,18 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)    else      local skipmark=sequence.flags[1]      while s do -      local id=getid(s) -      if id==glyph_code and getsubtype(s)<256 then -        if getfont(s)==currentfont then -          local char=getchar(s) +      local id=s.id +      if id==glyph_code and s.subtype<256 then +        if s.font==currentfont then +          local char=s.char            if skipmark and marks[char] then -            s=getnext(s) +            s=s.next            else              local lg=ligature[char]              if lg then                stop=s                ligature=lg -              s=getnext(s) +              s=s.next              else                break              end @@ -10139,7 +10414,7 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)          end        elseif id==disc_code then          discfound=true -        s=getnext(s) +        s=s.next        else          break        end @@ -10148,35 +10423,36 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)      if lig then        if stop then          if trace_ligatures then -          local stopchar=getchar(stop) +          local stopchar=stop.char            head,start=toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound) -          logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start))) +          logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char))          else            head,start=toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound)          end +        return head,start,true        else -        setfield(start,"char",lig) +        start.char=lig          if trace_ligatures then            logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(kind,lookupname),gref(startchar),gref(lig))          end +        return head,start,true        end -      return head,start,true      else      end    end    return head,start,false  end  function handlers.gpos_mark2base(head,start,kind,lookupname,markanchors,sequence) -  local markchar=getchar(start) +  local markchar=start.char    if marks[markchar] then -    local base=getprev(start)  -    if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -      local basechar=getchar(base) +    local base=start.prev  +    if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then +      local basechar=base.char        if marks[basechar] then          while true do -          base=getprev(base) -          if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -            basechar=getchar(base) +          base=base.prev +          if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then +            basechar=base.char              if not marks[basechar] then                break              end @@ -10225,16 +10501,16 @@ function handlers.gpos_mark2base(head,start,kind,lookupname,markanchors,sequence    return head,start,false  end  function handlers.gpos_mark2ligature(head,start,kind,lookupname,markanchors,sequence) -  local markchar=getchar(start) +  local markchar=start.char    if marks[markchar] then -    local base=getprev(start)  -    if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -      local basechar=getchar(base) +    local base=start.prev  +    if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then +      local basechar=base.char        if marks[basechar] then          while true do -          base=getprev(base) -          if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -            basechar=getchar(base) +          base=base.prev +          if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then +            basechar=base.char              if not marks[basechar] then                break              end @@ -10246,7 +10522,7 @@ function handlers.gpos_mark2ligature(head,start,kind,lookupname,markanchors,sequ            end          end        end -      local index=getattr(start,a_ligacomp) +      local index=start[a_ligacomp]        local baseanchors=descriptions[basechar]        if baseanchors then          baseanchors=baseanchors.anchors @@ -10291,22 +10567,22 @@ function handlers.gpos_mark2ligature(head,start,kind,lookupname,markanchors,sequ    return head,start,false  end  function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence) -  local markchar=getchar(start) +  local markchar=start.char    if marks[markchar] then -    local base=getprev(start)  -    local slc=getattr(start,a_ligacomp) +    local base=start.prev  +    local slc=start[a_ligacomp]      if slc then         while base do -        local blc=getattr(base,a_ligacomp) +        local blc=base[a_ligacomp]          if blc and blc~=slc then -          base=getprev(base) +          base=base.prev          else            break          end        end      end -    if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then  -      local basechar=getchar(base) +    if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then  +      local basechar=base.char        local baseanchors=descriptions[basechar]        if baseanchors then          baseanchors=baseanchors.anchors @@ -10344,20 +10620,20 @@ function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence    return head,start,false  end  function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence)  -  local alreadydone=cursonce and getattr(start,a_cursbase) +  local alreadydone=cursonce and start[a_cursbase]    if not alreadydone then      local done=false -    local startchar=getchar(start) +    local startchar=start.char      if marks[startchar] then        if trace_cursive then          logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar))        end      else -      local nxt=getnext(start) -      while not done and nxt and getid(nxt)==glyph_code and getfont(nxt)==currentfont and getsubtype(nxt)<256 do -        local nextchar=getchar(nxt) +      local nxt=start.next +      while not done and nxt and nxt.id==glyph_code and nxt.font==currentfont and nxt.subtype<256 do +        local nextchar=nxt.char          if marks[nextchar] then -          nxt=getnext(nxt) +          nxt=nxt.next          else            local entryanchors=descriptions[nextchar]            if entryanchors then @@ -10391,13 +10667,13 @@ function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence)      return head,start,done    else      if trace_cursive and trace_details then -      logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone) +      logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(start.char),alreadydone)      end      return head,start,false    end  end  function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence) -  local startchar=getchar(start) +  local startchar=start.char    local dx,dy,w,h=setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns,characters[startchar])    if trace_kerns then      logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),dx,dy,w,h) @@ -10405,33 +10681,33 @@ function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence)    return head,start,false  end  function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence) -  local snext=getnext(start) +  local snext=start.next    if not snext then      return head,start,false    else      local prev,done=start,false      local factor=tfmdata.parameters.factor      local lookuptype=lookuptypes[lookupname] -    while snext and getid(snext)==glyph_code and getfont(snext)==currentfont and getsubtype(snext)<256 do -      local nextchar=getchar(snext) +    while snext and snext.id==glyph_code and snext.font==currentfont and snext.subtype<256 do +      local nextchar=snext.char        local krn=kerns[nextchar]        if not krn and marks[nextchar] then          prev=snext -        snext=getnext(snext) +        snext=snext.next        else          if not krn then          elseif type(krn)=="table" then            if lookuptype=="pair" then               local a,b=krn[2],krn[3]              if a and #a>0 then -              local startchar=getchar(start) +              local startchar=start.char                local x,y,w,h=setpair(start,factor,rlmode,sequence.flags[4],a,characters[startchar])                if trace_kerns then                  logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h)                end              end              if b and #b>0 then -              local startchar=getchar(start) +              local startchar=start.char                local x,y,w,h=setpair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar])                if trace_kerns then                  logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) @@ -10444,7 +10720,7 @@ function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence)          elseif krn~=0 then            local k=setkern(snext,factor,rlmode,krn)            if trace_kerns then -            logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar)) +            logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar))            end            done=true          end @@ -10479,13 +10755,13 @@ function chainmores.chainsub(head,start,stop,kind,chainname,currentcontext,looku    return head,start,false  end  function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,lookuphash,replacements) -  local char=getchar(start) +  local char=start.char    local replacement=replacements[char]    if replacement then      if trace_singles then        logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement))      end -    setfield(start,"char",replacement) +    start.char=replacement      return head,start,true    else      return head,start,false @@ -10498,8 +10774,8 @@ function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lo      logwarning("todo: check if we need to loop over the replacements: %s",concat(subtables," "))    end    while current do -    if getid(current)==glyph_code then -      local currentchar=getchar(current) +    if current.id==glyph_code then +      local currentchar=current.char        local lookupname=subtables[1]         local replacement=lookuphash[lookupname]        if not replacement then @@ -10516,21 +10792,21 @@ function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lo            if trace_singles then              logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement))            end -          setfield(current,"char",replacement) +          current.char=replacement          end        end        return head,start,true      elseif current==stop then        break      else -      current=getnext(current) +      current=current.next      end    end    return head,start,false  end  chainmores.gsub_single=chainprocs.gsub_single  function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) -  local startchar=getchar(start) +  local startchar=start.char    local subtables=currentlookup.subtables    local lookupname=subtables[1]    local replacements=lookuphash[lookupname] @@ -10559,8 +10835,8 @@ function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext    local subtables=currentlookup.subtables    local value=featurevalue==true and tfmdata.shared.features[kind] or featurevalue    while current do -    if getid(current)==glyph_code then  -      local currentchar=getchar(current) +    if current.id==glyph_code then  +      local currentchar=current.char        local lookupname=subtables[1]        local alternatives=lookuphash[lookupname]        if not alternatives then @@ -10575,7 +10851,7 @@ function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext              if trace_alternatives then                logprocess("%s: replacing %s by alternative %a to %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(char),choice,gref(choice),comment)              end -            setfield(start,"char",choice) +            start.char=choice            else              if trace_alternatives then                logwarning("%s: no variant %a for %s, %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(char),comment) @@ -10589,14 +10865,14 @@ function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext      elseif current==stop then        break      else -      current=getnext(current) +      current=current.next      end    end    return head,start,false  end  chainmores.gsub_alternate=chainprocs.gsub_alternate  function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) -  local startchar=getchar(start) +  local startchar=start.char    local subtables=currentlookup.subtables    local lookupname=subtables[1]    local ligatures=lookuphash[lookupname] @@ -10611,20 +10887,20 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,          logwarning("%s: no ligatures starting with %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar))        end      else -      local s=getnext(start) +      local s=start.next        local discfound=false        local last=stop        local nofreplacements=0        local skipmark=currentlookup.flags[1]        while s do -        local id=getid(s) +        local id=s.id          if id==disc_code then -          s=getnext(s) +          s=s.next            discfound=true          else -          local schar=getchar(s) +          local schar=s.char            if skipmark and marks[schar] then  -            s=getnext(s) +            s=s.next            else              local lg=ligatures[schar]              if lg then @@ -10632,7 +10908,7 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,                if s==stop then                  break                else -                s=getnext(s) +                s=s.next                end              else                break @@ -10649,7 +10925,7 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,            if start==stop then              logprocess("%s: replacing character %s by ligature %s case 3",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(l2))            else -            logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop)),gref(l2)) +            logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(stop.char),gref(l2))            end          end          head,start=toligature(kind,lookupname,head,start,stop,l2,currentlookup.flags[1],discfound) @@ -10658,7 +10934,7 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,          if start==stop then            logwarning("%s: replacing character %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar))          else -          logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop))) +          logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(stop.char))          end        end      end @@ -10667,7 +10943,7 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,  end  chainmores.gsub_ligature=chainprocs.gsub_ligature  function chainprocs.gpos_mark2base(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) -  local markchar=getchar(start) +  local markchar=start.char    if marks[markchar] then      local subtables=currentlookup.subtables      local lookupname=subtables[1] @@ -10676,14 +10952,14 @@ function chainprocs.gpos_mark2base(head,start,stop,kind,chainname,currentcontext        markanchors=markanchors[markchar]      end      if markanchors then -      local base=getprev(start)  -      if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -        local basechar=getchar(base) +      local base=start.prev  +      if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then +        local basechar=base.char          if marks[basechar] then            while true do -            base=getprev(base) -            if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -              basechar=getchar(base) +            base=base.prev +            if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then +              basechar=base.char                if not marks[basechar] then                  break                end @@ -10730,7 +11006,7 @@ function chainprocs.gpos_mark2base(head,start,stop,kind,chainname,currentcontext    return head,start,false  end  function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) -  local markchar=getchar(start) +  local markchar=start.char    if marks[markchar] then      local subtables=currentlookup.subtables      local lookupname=subtables[1] @@ -10739,14 +11015,14 @@ function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcon        markanchors=markanchors[markchar]      end      if markanchors then -      local base=getprev(start)  -      if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -        local basechar=getchar(base) +      local base=start.prev  +      if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then +        local basechar=base.char          if marks[basechar] then            while true do -            base=getprev(base) -            if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -              basechar=getchar(base) +            base=base.prev +            if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then +              basechar=base.char                if not marks[basechar] then                  break                end @@ -10758,7 +11034,7 @@ function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcon              end            end          end -        local index=getattr(start,a_ligacomp) +        local index=start[a_ligacomp]          local baseanchors=descriptions[basechar].anchors          if baseanchors then            local baseanchors=baseanchors['baselig'] @@ -10797,7 +11073,7 @@ function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcon    return head,start,false  end  function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) -  local markchar=getchar(start) +  local markchar=start.char    if marks[markchar] then        local subtables=currentlookup.subtables        local lookupname=subtables[1] @@ -10806,20 +11082,20 @@ function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext          markanchors=markanchors[markchar]        end        if markanchors then -        local base=getprev(start)  -        local slc=getattr(start,a_ligacomp) +        local base=start.prev  +        local slc=start[a_ligacomp]          if slc then             while base do -            local blc=getattr(base,a_ligacomp) +            local blc=base[a_ligacomp]              if blc and blc~=slc then -              base=getprev(base) +              base=base.prev              else                break              end            end          end -        if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then  -          local basechar=getchar(base) +        if base and base.id==glyph_code and base.font==currentfont and base.subtype<256 then  +          local basechar=base.char            local baseanchors=descriptions[basechar].anchors            if baseanchors then              baseanchors=baseanchors['basemark'] @@ -10855,9 +11131,9 @@ function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext    return head,start,false  end  function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) -  local alreadydone=cursonce and getattr(start,a_cursbase) +  local alreadydone=cursonce and start[a_cursbase]    if not alreadydone then -    local startchar=getchar(start) +    local startchar=start.char      local subtables=currentlookup.subtables      local lookupname=subtables[1]      local exitanchors=lookuphash[lookupname] @@ -10871,11 +11147,11 @@ function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,l            logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar))          end        else -        local nxt=getnext(start) -        while not done and nxt and getid(nxt)==glyph_code and getfont(nxt)==currentfont and getsubtype(nxt)<256 do -          local nextchar=getchar(nxt) +        local nxt=start.next +        while not done and nxt and nxt.id==glyph_code and nxt.font==currentfont and nxt.subtype<256 do +          local nextchar=nxt.char            if marks[nextchar] then -            nxt=getnext(nxt) +            nxt=nxt.next            else              local entryanchors=descriptions[nextchar]              if entryanchors then @@ -10909,7 +11185,7 @@ function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,l        return head,start,done      else        if trace_cursive and trace_details then -        logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone) +        logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(start.char),alreadydone)        end        return head,start,false      end @@ -10917,7 +11193,7 @@ function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,l    return head,start,false  end  function chainprocs.gpos_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) -  local startchar=getchar(start) +  local startchar=start.char    local subtables=currentlookup.subtables    local lookupname=subtables[1]    local kerns=lookuphash[lookupname] @@ -10934,9 +11210,9 @@ function chainprocs.gpos_single(head,start,stop,kind,chainname,currentcontext,lo  end  chainmores.gpos_single=chainprocs.gpos_single  function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) -  local snext=getnext(start) +  local snext=start.next    if snext then -    local startchar=getchar(start) +    local startchar=start.char      local subtables=currentlookup.subtables      local lookupname=subtables[1]      local kerns=lookuphash[lookupname] @@ -10946,26 +11222,26 @@ function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,look          local lookuptype=lookuptypes[lookupname]          local prev,done=start,false          local factor=tfmdata.parameters.factor -        while snext and getid(snext)==glyph_code and getfont(snext)==currentfont and getsubtype(snext)<256 do -          local nextchar=getchar(snext) +        while snext and snext.id==glyph_code and snext.font==currentfont and snext.subtype<256 do +          local nextchar=snext.char            local krn=kerns[nextchar]            if not krn and marks[nextchar] then              prev=snext -            snext=getnext(snext) +            snext=snext.next            else              if not krn then              elseif type(krn)=="table" then                if lookuptype=="pair" then                  local a,b=krn[2],krn[3]                  if a and #a>0 then -                  local startchar=getchar(start) +                  local startchar=start.char                    local x,y,w,h=setpair(start,factor,rlmode,sequence.flags[4],a,characters[startchar])                    if trace_kerns then                      logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h)                    end                  end                  if b and #b>0 then -                  local startchar=getchar(start) +                  local startchar=start.char                    local x,y,w,h=setpair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar])                    if trace_kerns then                      logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) @@ -10977,7 +11253,7 @@ function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,look                  if a and a~=0 then                    local k=setkern(snext,factor,rlmode,a)                    if trace_kerns then -                    logprocess("%s: inserting first kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar)) +                    logprocess("%s: inserting first kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar))                    end                  end                  if b and b~=0 then @@ -10988,7 +11264,7 @@ function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,look              elseif krn~=0 then                local k=setkern(snext,factor,rlmode,krn)                if trace_kerns then -                logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar)) +                logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar))                end                done=true              end @@ -11009,10 +11285,6 @@ local function show_skip(kind,chainname,char,ck,class)      logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(kind,chainname),gref(char),class,ck[1],ck[2])    end  end -local quit_on_no_replacement=true -directives.register("otf.chain.quitonnoreplacement",function(value)  -  quit_on_no_replacement=value -end)  local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash)    local flags=sequence.flags    local done=false @@ -11030,7 +11302,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq      local seq=ck[3]      local s=#seq      if s==1 then -      match=getid(current)==glyph_code and getfont(current)==currentfont and getsubtype(current)<256 and seq[1][getchar(current)] +      match=current.id==glyph_code and current.font==currentfont and current.subtype<256 and seq[1][current.char]      else        local f,l=ck[4],ck[5]        if f==1 and f==l then @@ -11038,13 +11310,13 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq          if f==l then          else            local n=f+1 -          last=getnext(last) +          last=last.next            while n<=l do              if last then -              local id=getid(last) +              local id=last.id                if id==glyph_code then -                if getfont(last)==currentfont and getsubtype(last)<256 then -                  local char=getchar(last) +                if last.font==currentfont and last.subtype<256 then +                  local char=last.char                    local ccd=descriptions[char]                    if ccd then                      local class=ccd.class @@ -11053,10 +11325,10 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                        if trace_skips then                          show_skip(kind,chainname,char,ck,class)                        end -                      last=getnext(last) +                      last=last.next                      elseif seq[n][char] then                        if n<l then -                        last=getnext(last) +                        last=last.next                        end                        n=n+1                      else @@ -11072,7 +11344,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                    break                  end                elseif id==disc_code then -                last=getnext(last) +                last=last.next                else                  match=false                  break @@ -11085,15 +11357,15 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq          end        end        if match and f>1 then -        local prev=getprev(start) +        local prev=start.prev          if prev then            local n=f-1            while n>=1 do              if prev then -              local id=getid(prev) +              local id=prev.id                if id==glyph_code then -                if getfont(prev)==currentfont and getsubtype(prev)<256 then  -                  local char=getchar(prev) +                if prev.font==currentfont and prev.subtype<256 then  +                  local char=prev.char                    local ccd=descriptions[char]                    if ccd then                      local class=ccd.class @@ -11123,7 +11395,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                  match=false                  break                end -              prev=getprev(prev) +              prev=prev.prev              elseif seq[n][32] then                 n=n -1              else @@ -11143,15 +11415,15 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq          end        end        if match and s>l then -        local current=last and getnext(last) +        local current=last and last.next          if current then            local n=l+1            while n<=s do              if current then -              local id=getid(current) +              local id=current.id                if id==glyph_code then -                if getfont(current)==currentfont and getsubtype(current)<256 then  -                  local char=getchar(current) +                if current.font==currentfont and current.subtype<256 then  +                  local char=current.char                    local ccd=descriptions[char]                    if ccd then                      local class=ccd.class @@ -11181,7 +11453,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                  match=false                  break                end -              current=getnext(current) +              current=current.next              elseif seq[n][32] then                n=n+1              else @@ -11204,7 +11476,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq      if match then        if trace_contexts then          local rule,lookuptype,f,l=ck[1],ck[2],ck[4],ck[5] -        local char=getchar(start) +        local char=start.char          if ck[9] then            logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a, %a => %a",              cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10]) @@ -11238,12 +11510,12 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq            repeat              if skipped then                while true do -                local char=getchar(start) +                local char=start.char                  local ccd=descriptions[char]                  if ccd then                    local class=ccd.class                    if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then -                    start=getnext(start) +                    start=start.next                    else                      break                    end @@ -11273,7 +11545,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                end              end              if start then -              start=getnext(start) +              start=start.next              else              end            until i>nofchainlookups @@ -11283,7 +11555,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq          if replacements then            head,start,done=chainprocs.reversesub(head,start,last,kind,chainname,ck,lookuphash,replacements)           else -          done=quit_on_no_replacement  +          done=true             if trace_contexts then              logprocess("%s: skipping match",cref(kind,chainname))            end @@ -11352,14 +11624,20 @@ local autofeatures=fonts.analyzers.features  local function initialize(sequence,script,language,enabled)    local features=sequence.features    if features then -    for kind,scripts in next,features do -      local valid=enabled[kind] -      if valid then -        local languages=scripts[script] or scripts[wildcard] -        if languages and (languages[language] or languages[wildcard]) then -          return { valid,autofeatures[kind] or false,sequence.chain or 0,kind,sequence } +    local order=sequence.order +    if order then +      for i=1,#order do +        local kind=order[i]  +        local valid=enabled[kind] +        if valid then +          local scripts=features[kind]  +          local languages=scripts[script] or scripts[wildcard] +          if languages and (languages[language] or languages[wildcard]) then +            return { valid,autofeatures[kind] or false,sequence.chain or 0,kind,sequence } +          end          end        end +    else      end    end    return false @@ -11386,12 +11664,12 @@ function otf.dataset(tfmdata,font)      }      rs[language]=rl      local sequences=tfmdata.resources.sequences -for s=1,#sequences do -  local v=enabled and initialize(sequences[s],script,language,enabled) -  if v then -    rl[#rl+1]=v -  end -end +    for s=1,#sequences do +      local v=enabled and initialize(sequences[s],script,language,enabled) +      if v then +        rl[#rl+1]=v +      end +    end    end    return rl  end @@ -11400,7 +11678,6 @@ local function featuresprocessor(head,font,attr)    if not lookuphash then      return head,false    end -  head=tonut(head)    if trace_steps then      checkstep(head)    end @@ -11433,10 +11710,10 @@ local function featuresprocessor(head,font,attr)        local handler=handlers[typ]        local start=find_node_tail(head)         while start do -        local id=getid(start) +        local id=start.id          if id==glyph_code then -          if getfont(start)==font and getsubtype(start)<256 then -            local a=getattr(start,0) +          if start.font==font and start.subtype<256 then +            local a=start[0]              if a then                a=a==attr              else @@ -11447,7 +11724,7 @@ local function featuresprocessor(head,font,attr)                  local lookupname=subtables[i]                  local lookupcache=lookuphash[lookupname]                  if lookupcache then -                  local lookupmatch=lookupcache[getchar(start)] +                  local lookupmatch=lookupcache[start.char]                    if lookupmatch then                      head,start,success=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i)                      if success then @@ -11458,15 +11735,15 @@ local function featuresprocessor(head,font,attr)                    report_missing_cache(typ,lookupname)                  end                end -              if start then start=getprev(start) end +              if start then start=start.prev end              else -              start=getprev(start) +              start=start.prev              end            else -            start=getprev(start) +            start=start.prev            end          else -          start=getprev(start) +          start=start.prev          end        end      else @@ -11484,16 +11761,16 @@ local function featuresprocessor(head,font,attr)              local head=start              local done=false              while start do -              local id=getid(start) -              if id==glyph_code and getfont(start)==font and getsubtype(start)<256 then -                local a=getattr(start,0) +              local id=start.id +              if id==glyph_code and start.font==font and start.subtype<256 then +                local a=start[0]                  if a then -                  a=(a==attr) and (not attribute or getattr(start,a_state)==attribute) +                  a=(a==attr) and (not attribute or start[a_state]==attribute)                  else -                  a=not attribute or getattr(start,a_state)==attribute +                  a=not attribute or start[a_state]==attribute                  end                  if a then -                  local lookupmatch=lookupcache[getchar(start)] +                  local lookupmatch=lookupcache[start.char]                    if lookupmatch then                      local ok                      head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) @@ -11501,12 +11778,12 @@ local function featuresprocessor(head,font,attr)                        done=true                      end                    end -                  if start then start=getnext(start) end +                  if start then start=start.next end                  else -                  start=getnext(start) +                  start=start.next                  end                else -                start=getnext(start) +                start=start.next                end              end              if done then @@ -11515,18 +11792,18 @@ local function featuresprocessor(head,font,attr)              end            end            local function kerndisc(disc)  -            local prev=getprev(disc) -            local next=getnext(disc) +            local prev=disc.prev +            local next=disc.next              if prev and next then -              setfield(prev,"next",next) -              local a=getattr(prev,0) +              prev.next=next +              local a=prev[0]                if a then -                a=(a==attr) and (not attribute or getattr(prev,a_state)==attribute) +                a=(a==attr) and (not attribute or prev[a_state]==attribute)                else -                a=not attribute or getattr(prev,a_state)==attribute +                a=not attribute or prev[a_state]==attribute                end                if a then -                local lookupmatch=lookupcache[getchar(prev)] +                local lookupmatch=lookupcache[prev.char]                  if lookupmatch then                    local h,d,ok=handler(head,prev,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1)                    if ok then @@ -11535,22 +11812,22 @@ local function featuresprocessor(head,font,attr)                    end                  end                end -              setfield(prev,"next",disc) +              prev.next=disc              end              return next            end            while start do -            local id=getid(start) +            local id=start.id              if id==glyph_code then -              if getfont(start)==font and getsubtype(start)<256 then -                local a=getattr(start,0) +              if start.font==font and start.subtype<256 then +                local a=start[0]                  if a then -                  a=(a==attr) and (not attribute or getattr(start,a_state)==attribute) +                  a=(a==attr) and (not attribute or start[a_state]==attribute)                  else -                  a=not attribute or getattr(start,a_state)==attribute +                  a=not attribute or start[a_state]==attribute                  end                  if a then -                  local lookupmatch=lookupcache[getchar(start)] +                  local lookupmatch=lookupcache[start.char]                    if lookupmatch then                      local ok                      head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) @@ -11558,38 +11835,38 @@ local function featuresprocessor(head,font,attr)                        success=true                      end                    end -                  if start then start=getnext(start) end +                  if start then start=start.next end                  else -                  start=getnext(start) +                  start=start.next                  end                else -                start=getnext(start) +                start=start.next                end              elseif id==disc_code then -              if getsubtype(start)==discretionary_code then -                local pre=getfield(start,"pre") +              if start.subtype==discretionary_code then +                local pre=start.pre                  if pre then                    local new=subrun(pre) -                  if new then setfield(start,"pre",new) end +                  if new then start.pre=new end                  end -                local post=getfield(start,"post") +                local post=start.post                  if post then                    local new=subrun(post) -                  if new then setfield(start,"post",new) end +                  if new then start.post=new end                  end -                local replace=getfield(start,"replace") +                local replace=start.replace                  if replace then                    local new=subrun(replace) -                  if new then setfield(start,"replace",new) end +                  if new then start.replace=new end                  end  elseif typ=="gpos_single" or typ=="gpos_pair" then    kerndisc(start)                end -              start=getnext(start) +              start=start.next              elseif id==whatsit_code then  -              local subtype=getsubtype(start) +              local subtype=start.subtype                if subtype==dir_code then -                local dir=getfield(start,"dir") +                local dir=start.dir                  if   dir=="+TRT" or dir=="+TLT" then                    topstack=topstack+1                    dirstack[topstack]=dir @@ -11608,7 +11885,7 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then                    report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir)                  end                elseif subtype==localpar_code then -                local dir=getfield(start,"dir") +                local dir=start.dir                  if dir=="TRT" then                    rlparmode=-1                  elseif dir=="TLT" then @@ -11621,11 +11898,11 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then                    report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode)                  end                end -              start=getnext(start) +              start=start.next              elseif id==math_code then -              start=getnext(end_of_math(start)) +              start=end_of_math(start).next              else -              start=getnext(start) +              start=start.next              end            end          end @@ -11634,20 +11911,20 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then            local head=start            local done=false            while start do -            local id=getid(start) -            if id==glyph_code and getfont(start)==font and getsubtype(start)<256 then -              local a=getattr(start,0) +            local id=start.id +            if id==glyph_code and start.id==font and start.subtype<256 then +              local a=start[0]                if a then -                a=(a==attr) and (not attribute or getattr(start,a_state)==attribute) +                a=(a==attr) and (not attribute or start[a_state]==attribute)                else -                a=not attribute or getattr(start,a_state)==attribute +                a=not attribute or start[a_state]==attribute                end                if a then                  for i=1,ns do                    local lookupname=subtables[i]                    local lookupcache=lookuphash[lookupname]                    if lookupcache then -                    local lookupmatch=lookupcache[getchar(start)] +                    local lookupmatch=lookupcache[start.char]                      if lookupmatch then                        local ok                        head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) @@ -11662,12 +11939,12 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then                      report_missing_cache(typ,lookupname)                    end                  end -                if start then start=getnext(start) end +                if start then start=start.next end                else -                start=getnext(start) +                start=start.next                end              else -              start=getnext(start) +              start=start.next              end            end            if done then @@ -11676,22 +11953,22 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then            end          end          local function kerndisc(disc)  -          local prev=getprev(disc) -          local next=getnext(disc) +          local prev=disc.prev +          local next=disc.next            if prev and next then -            setfield(prev,"next",next) -            local a=getattr(prev,0) +            prev.next=next +            local a=prev[0]              if a then -              a=(a==attr) and (not attribute or getattr(prev,a_state)==attribute) +              a=(a==attr) and (not attribute or prev[a_state]==attribute)              else -              a=not attribute or getattr(prev,a_state)==attribute +              a=not attribute or prev[a_state]==attribute              end              if a then                for i=1,ns do                  local lookupname=subtables[i]                  local lookupcache=lookuphash[lookupname]                  if lookupcache then -                  local lookupmatch=lookupcache[getchar(prev)] +                  local lookupmatch=lookupcache[prev.char]                    if lookupmatch then                      local h,d,ok=handler(head,prev,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i)                      if ok then @@ -11704,26 +11981,26 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then                  end                end              end -            setfield(prev,"next",disc) +            prev.next=disc            end            return next          end          while start do -          local id=getid(start) +          local id=start.id            if id==glyph_code then -            if getfont(start)==font and getsubtype(start)<256 then -              local a=getattr(start,0) +            if start.font==font and start.subtype<256 then +              local a=start[0]                if a then -                a=(a==attr) and (not attribute or getattr(start,a_state)==attribute) +                a=(a==attr) and (not attribute or start[a_state]==attribute)                else -                a=not attribute or getattr(start,a_state)==attribute +                a=not attribute or start[a_state]==attribute                end                if a then                  for i=1,ns do                    local lookupname=subtables[i]                    local lookupcache=lookuphash[lookupname]                    if lookupcache then -                    local lookupmatch=lookupcache[getchar(start)] +                    local lookupmatch=lookupcache[start.char]                      if lookupmatch then                        local ok                        head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) @@ -11738,38 +12015,38 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then                      report_missing_cache(typ,lookupname)                    end                  end -                if start then start=getnext(start) end +                if start then start=start.next end                else -                start=getnext(start) +                start=start.next                end              else -              start=getnext(start) +              start=start.next              end            elseif id==disc_code then -            if getsubtype(start)==discretionary_code then -              local pre=getfield(start,"pre") +            if start.subtype==discretionary_code then +              local pre=start.pre                if pre then                  local new=subrun(pre) -                if new then setfield(start,"pre",new) end +                if new then start.pre=new end                end -              local post=getfield(start,"post") +              local post=start.post                if post then                  local new=subrun(post) -                if new then setfield(start,"post",new) end +                if new then start.post=new end                end -              local replace=getfield(start,"replace") +              local replace=start.replace                if replace then                  local new=subrun(replace) -                if new then setfield(start,"replace",new) end +                if new then start.replace=new end                end  elseif typ=="gpos_single" or typ=="gpos_pair" then    kerndisc(start)              end -            start=getnext(start) +            start=start.next            elseif id==whatsit_code then -            local subtype=getsubtype(start) +            local subtype=start.subtype              if subtype==dir_code then -              local dir=getfield(start,"dir") +              local dir=start.dir                if   dir=="+TRT" or dir=="+TLT" then                  topstack=topstack+1                  dirstack[topstack]=dir @@ -11788,7 +12065,7 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then                  report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir)                end              elseif subtype==localpar_code then -              local dir=getfield(start,"dir") +              local dir=start.dir                if dir=="TRT" then                  rlparmode=-1                elseif dir=="TLT" then @@ -11801,11 +12078,11 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then                  report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode)                end              end -            start=getnext(start) +            start=start.next            elseif id==math_code then -            start=getnext(end_of_math(start)) +            start=end_of_math(start).next            else -            start=getnext(start) +            start=start.next            end          end        end @@ -11817,7 +12094,6 @@ elseif typ=="gpos_single" or typ=="gpos_pair" then        registerstep(head)      end    end -  head=tonode(head)    return head,done  end  local function generic(lookupdata,lookupname,unicode,lookuphash) @@ -12420,6 +12696,14 @@ local function packdata(data)                features[script]=pack_normal(feature)              end            end +          local order=sequence.order +          if order then +            sequence.order=pack_indexed(order) +          end +          local markclass=sequence.markclass +          if markclass then +            sequence.markclass=pack_boolean(markclass) +          end          end        end        local lookups=resources.lookups @@ -12832,6 +13116,20 @@ local function unpackdata(data)                end              end            end +          local order=feature.order +          if order then +            local tv=tables[order] +            if tv then +              feature.order=tv +            end +          end +          local markclass=feature.markclass +          if markclass then +            local tv=tables[markclass] +            if tv then +              feature.markclass=tv +            end +          end          end        end        local lookups=resources.lookups diff --git a/luaotfload-fonts-cbk.lua b/src/luaotfload-fonts-cbk.lua index 9db94f6..9db94f6 100644 --- a/luaotfload-fonts-cbk.lua +++ b/src/luaotfload-fonts-cbk.lua diff --git a/luaotfload-fonts-def.lua b/src/luaotfload-fonts-def.lua index 0c2f0db..0c2f0db 100644 --- a/luaotfload-fonts-def.lua +++ b/src/luaotfload-fonts-def.lua diff --git a/luaotfload-fonts-enc.lua b/src/luaotfload-fonts-enc.lua index e20c3a0..e20c3a0 100644 --- a/luaotfload-fonts-enc.lua +++ b/src/luaotfload-fonts-enc.lua diff --git a/luaotfload-fonts-ext.lua b/src/luaotfload-fonts-ext.lua index b60d045..b60d045 100644 --- a/luaotfload-fonts-ext.lua +++ b/src/luaotfload-fonts-ext.lua diff --git a/src/luaotfload-fonts-inj.lua b/src/luaotfload-fonts-inj.lua new file mode 100644 index 0000000..ae48150 --- /dev/null +++ b/src/luaotfload-fonts-inj.lua @@ -0,0 +1,526 @@ +if not modules then modules = { } end modules ['node-inj'] = { +    version   = 1.001, +    comment   = "companion to node-ini.mkiv", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files", +} + +-- This is very experimental (this will change when we have luatex > .50 and +-- a few pending thingies are available. Also, Idris needs to make a few more +-- test fonts. Btw, future versions of luatex will have extended glyph properties +-- that can be of help. Some optimizations can go away when we have faster machines. + +-- todo: make a special one for context + +local next = next +local utfchar = utf.char + +local trace_injections = false  trackers.register("nodes.injections", function(v) trace_injections = v end) + +local report_injections = logs.reporter("nodes","injections") + +local attributes, nodes, node = attributes, nodes, node + +fonts                    = fonts +local fontdata           = fonts.hashes.identifiers + +nodes.injections         = nodes.injections or { } +local injections         = nodes.injections + +local nodecodes          = nodes.nodecodes +local glyph_code         = nodecodes.glyph +local kern_code          = nodecodes.kern +local nodepool           = nodes.pool +local newkern            = nodepool.kern + +local traverse_id        = node.traverse_id +local insert_node_before = node.insert_before +local insert_node_after  = node.insert_after + +local a_kernpair = attributes.private('kernpair') +local a_ligacomp = attributes.private('ligacomp') +local a_markbase = attributes.private('markbase') +local a_markmark = attributes.private('markmark') +local a_markdone = attributes.private('markdone') +local a_cursbase = attributes.private('cursbase') +local a_curscurs = attributes.private('curscurs') +local a_cursdone = attributes.private('cursdone') + +-- This injector has been tested by Idris Samawi Hamid (several arabic fonts as well as +-- the rather demanding Husayni font), Khaled Hosny (latin and arabic) and Kaj Eigner +-- (arabic, hebrew and thai) and myself (whatever font I come across). I'm pretty sure +-- that this code is not 100% okay but examples are needed to figure things out. + +function injections.installnewkern(nk) +    newkern = nk or newkern +end + +local cursives = { } +local marks    = { } +local kerns    = { } + +-- Currently we do gpos/kern in a bit inofficial way but when we have the extra fields in +-- glyphnodes to manipulate ht/dp/wd explicitly I will provide an alternative; also, we +-- can share tables. + +-- For the moment we pass the r2l key ... volt/arabtype tests .. idris: this needs +-- checking with husayni (volt and fontforge). + +function injections.setcursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmnext) +    local dx, dy = factor*(exit[1]-entry[1]), factor*(exit[2]-entry[2]) +    local ws, wn = tfmstart.width, tfmnext.width +    local bound = #cursives + 1 +    start[a_cursbase] = bound +    nxt[a_curscurs] = bound +    cursives[bound] = { rlmode, dx, dy, ws, wn } +    return dx, dy, bound +end + +function injections.setpair(current,factor,rlmode,r2lflag,spec,tfmchr) +    local x, y, w, h = factor*spec[1], factor*spec[2], factor*spec[3], factor*spec[4] +    -- dy = y - h +    if x ~= 0 or w ~= 0 or y ~= 0 or h ~= 0 then +        local bound = current[a_kernpair] +        if bound then +            local kb = kerns[bound] +            -- inefficient but singles have less, but weird anyway, needs checking +            kb[2], kb[3], kb[4], kb[5] = (kb[2] or 0) + x, (kb[3] or 0) + y, (kb[4] or 0)+ w, (kb[5] or 0) + h +        else +            bound = #kerns + 1 +            current[a_kernpair] = bound +            kerns[bound] = { rlmode, x, y, w, h, r2lflag, tfmchr.width } +        end +        return x, y, w, h, bound +    end +    return x, y, w, h -- no bound +end + +function injections.setkern(current,factor,rlmode,x,tfmchr) +    local dx = factor*x +    if dx ~= 0 then +        local bound = #kerns + 1 +        current[a_kernpair] = bound +        kerns[bound] = { rlmode, dx } +        return dx, bound +    else +        return 0, 0 +    end +end + +function injections.setmark(start,base,factor,rlmode,ba,ma,index,baseismark) -- ba=baseanchor, ma=markanchor +    local dx, dy = factor*(ba[1]-ma[1]), factor*(ba[2]-ma[2])     -- the index argument is no longer used but when this +    local bound = base[a_markbase]                    -- fails again we should pass it +    local index = 1 +    if bound then +        local mb = marks[bound] +        if mb then +         -- if not index then index = #mb + 1 end +            index = #mb + 1 +            mb[index] = { dx, dy, rlmode } +            start[a_markmark] = bound +            start[a_markdone] = index +            return dx, dy, bound +        else +            report_injections("possible problem, %U is base mark without data (id %a)",base.char,bound) +        end +    end +--     index = index or 1 +    index = index or 1 +    bound = #marks + 1 +    base[a_markbase] = bound +    start[a_markmark] = bound +    start[a_markdone] = index +    marks[bound] = { [index] = { dx, dy, rlmode, baseismark } } +    return dx, dy, bound +end + +local function dir(n) +    return (n and n<0 and "r-to-l") or (n and n>0 and "l-to-r") or "unset" +end + +local function trace(head) +    report_injections("begin run") +    for n in traverse_id(glyph_code,head) do +        if n.subtype < 256 then +            local kp = n[a_kernpair] +            local mb = n[a_markbase] +            local mm = n[a_markmark] +            local md = n[a_markdone] +            local cb = n[a_cursbase] +            local cc = n[a_curscurs] +            local char = n.char +            report_injections("font %s, char %U, glyph %c",n.font,char,char) +            if kp then +                local k = kerns[kp] +                if k[3] then +                    report_injections("  pairkern: dir %a, x %p, y %p, w %p, h %p",dir(k[1]),k[2],k[3],k[4],k[5]) +                else +                    report_injections("  kern: dir %a, dx %p",dir(k[1]),k[2]) +                end +            end +            if mb then +                report_injections("  markbase: bound %a",mb) +            end +            if mm then +                local m = marks[mm] +                if mb then +                    local m = m[mb] +                    if m then +                        report_injections("  markmark: bound %a, index %a, dx %p, dy %p",mm,md,m[1],m[2]) +                    else +                        report_injections("  markmark: bound %a, missing index",mm) +                    end +                else +                    m = m[1] +                    report_injections("  markmark: bound %a, dx %p, dy %p",mm,m and m[1],m and m[2]) +                end +            end +            if cb then +                report_injections("  cursbase: bound %a",cb) +            end +            if cc then +                local c = cursives[cc] +                report_injections("  curscurs: bound %a, dir %a, dx %p, dy %p",cc,dir(c[1]),c[2],c[3]) +            end +        end +    end +    report_injections("end run") +end + +-- todo: reuse tables (i.e. no collection), but will be extra fields anyway +-- todo: check for attribute + +-- We can have a fast test on a font being processed, so we can check faster for marks etc +-- but I'll make a context variant anyway. + +local function show_result(head) +    local current = head +    local skipping = false +    while current do +        local id = current.id +        if id == glyph_code then +            report_injections("char: %C, width %p, xoffset %p, yoffset %p",current.char,current.width,current.xoffset,current.yoffset) +            skipping = false +        elseif id == kern_code then +            report_injections("kern: %p",current.kern) +            skipping = false +        elseif not skipping then +            report_injections() +            skipping = true +        end +        current = current.next +    end +end + +function injections.handler(head,where,keep) +    local has_marks, has_cursives, has_kerns = next(marks), next(cursives), next(kerns) +    if has_marks or has_cursives then +        if trace_injections then +            trace(head) +        end +        -- in the future variant we will not copy items but refs to tables +        local done, ky, rl, valid, cx, wx, mk, nofvalid = false, { }, { }, { }, { }, { }, { }, 0 +        if has_kerns then -- move outside loop +            local nf, tm = nil, nil +            for n in traverse_id(glyph_code,head) do -- only needed for relevant fonts +                if n.subtype < 256 then +                    nofvalid = nofvalid + 1 +                    valid[nofvalid] = n +                    if n.font ~= nf then +                        nf = n.font +                        tm = fontdata[nf].resources.marks +                    end +                    if tm then +                        mk[n] = tm[n.char] +                    end +                    local k = n[a_kernpair] +                    if k then +                        local kk = kerns[k] +                        if kk then +                            local x, y, w, h = kk[2] or 0, kk[3] or 0, kk[4] or 0, kk[5] or 0 +                            local dy = y - h +                            if dy ~= 0 then +                                ky[n] = dy +                            end +                            if w ~= 0 or x ~= 0 then +                                wx[n] = kk +                            end +                            rl[n] = kk[1] -- could move in test +                        end +                    end +                end +            end +        else +            local nf, tm = nil, nil +            for n in traverse_id(glyph_code,head) do +                if n.subtype < 256 then +                    nofvalid = nofvalid + 1 +                    valid[nofvalid] = n +                    if n.font ~= nf then +                        nf = n.font +                        tm = fontdata[nf].resources.marks +                    end +                    if tm then +                        mk[n] = tm[n.char] +                    end +                end +            end +        end +        if nofvalid > 0 then +            -- we can assume done == true because we have cursives and marks +            local cx = { } +            if has_kerns and next(ky) then +                for n, k in next, ky do +                    n.yoffset = k +                end +            end +            -- todo: reuse t and use maxt +            if has_cursives then +                local p_cursbase, p = nil, nil +                -- since we need valid[n+1] we can also use a "while true do" +                local t, d, maxt = { }, { }, 0 +                for i=1,nofvalid do -- valid == glyphs +                    local n = valid[i] +                    if not mk[n] then +                        local n_cursbase = n[a_cursbase] +                        if p_cursbase then +                            local n_curscurs = n[a_curscurs] +                            if p_cursbase == n_curscurs then +                                local c = cursives[n_curscurs] +                                if c then +                                    local rlmode, dx, dy, ws, wn = c[1], c[2], c[3], c[4], c[5] +                                    if rlmode >= 0 then +                                        dx = dx - ws +                                    else +                                        dx = dx + wn +                                    end +                                    if dx ~= 0 then +                                        cx[n] = dx +                                        rl[n] = rlmode +                                    end +                                --  if rlmode and rlmode < 0 then +                                        dy = -dy +                                --  end +                                    maxt = maxt + 1 +                                    t[maxt] = p +                                    d[maxt] = dy +                                else +                                    maxt = 0 +                                end +                            end +                        elseif maxt > 0 then +                            local ny = n.yoffset +                            for i=maxt,1,-1 do +                                ny = ny + d[i] +                                local ti = t[i] +                                ti.yoffset = ti.yoffset + ny +                            end +                            maxt = 0 +                        end +                        if not n_cursbase and maxt > 0 then +                            local ny = n.yoffset +                            for i=maxt,1,-1 do +                                ny = ny + d[i] +                                local ti = t[i] +                                ti.yoffset = ny +                            end +                            maxt = 0 +                        end +                        p_cursbase, p = n_cursbase, n +                    end +                end +                if maxt > 0 then +                    local ny = n.yoffset +                    for i=maxt,1,-1 do +                        ny = ny + d[i] +                        local ti = t[i] +                        ti.yoffset = ny +                    end +                    maxt = 0 +                end +                if not keep then +                    cursives = { } +                end +            end +            if has_marks then +                for i=1,nofvalid do +                    local p = valid[i] +                    local p_markbase = p[a_markbase] +                    if p_markbase then +                        local mrks = marks[p_markbase] +                        local nofmarks = #mrks +                        for n in traverse_id(glyph_code,p.next) do +                            local n_markmark = n[a_markmark] +                            if p_markbase == n_markmark then +                                local index = n[a_markdone] or 1 +                                local d = mrks[index] +                                if d then +                                    local rlmode = d[3] +                                    -- +                                    local k = wx[p] +                                    if k then +                                        local x = k[2] +                                        local w = k[4] +                                        if w then +                                            if rlmode and rlmode >= 0 then +                                                -- kern(x) glyph(p) kern(w-x) mark(n) +                                                n.xoffset = p.xoffset - p.width + d[1] - (w-x) +                                            else +                                                -- kern(w-x) glyph(p) kern(x) mark(n) +                                                n.xoffset = p.xoffset - d[1] - x +                                            end +                                        else +                                            if rlmode and rlmode >= 0 then +                                                -- okay for husayni +                                                n.xoffset = p.xoffset - p.width + d[1] +                                            else +                                                -- needs checking: is x ok here? +                                                n.xoffset = p.xoffset - d[1] - x +                                            end +                                        end +                                    else +                                        if rlmode and rlmode >= 0 then +                                            n.xoffset = p.xoffset - p.width + d[1] +                                        else +                                            n.xoffset = p.xoffset - d[1] +                                        end +                                        local w = n.width +                                        if w ~= 0 then +                                            insert_node_before(head,n,newkern(-w/2)) +                                            insert_node_after(head,n,newkern(-w/2)) +                                        end +                                    end +                                    --                                    -- +                                    if mk[p] then +                                        n.yoffset = p.yoffset + d[2] +                                    else +                                        n.yoffset = n.yoffset + p.yoffset + d[2] +                                    end +                                    -- +                                    if nofmarks == 1 then +                                        break +                                    else +                                        nofmarks = nofmarks - 1 +                                    end +                                end +                            else +                                -- KE: there can be <mark> <mkmk> <mark> sequences in ligatures +                            end +                        end +                    end +                end +                if not keep then +                    marks = { } +                end +            end +            -- todo : combine +            if next(wx) then +                for n, k in next, wx do +                 -- only w can be nil (kernclasses), can be sped up when w == nil +                    local x = k[2] +                    local w = k[4] +                    if w then +                        local rl = k[1] -- r2l = k[6] +                        local wx = w - x +                        if rl < 0 then	-- KE: don't use r2l here +                            if wx ~= 0 then +                                insert_node_before(head,n,newkern(wx)) -- type 0/2 +                            end +                            if x ~= 0 then +                                insert_node_after (head,n,newkern(x))  -- type 0/2 +                            end +                        else +                            if x ~= 0 then +                                insert_node_before(head,n,newkern(x))  -- type 0/2 +                            end +                            if wx ~= 0 then +                                insert_node_after (head,n,newkern(wx)) -- type 0/2 +                            end +                        end +                    elseif x ~= 0 then +                        -- this needs checking for rl < 0 but it is unlikely that a r2l script +                        -- uses kernclasses between glyphs so we're probably safe (KE has a +                        -- problematic font where marks interfere with rl < 0 in the previous +                        -- case) +                        insert_node_before(head,n,newkern(x)) -- a real font kern, type 0 +                    end +                end +            end +            if next(cx) then +                for n, k in next, cx do +                    if k ~= 0 then +                        local rln = rl[n] +                        if rln and rln < 0 then +                            insert_node_before(head,n,newkern(-k)) -- type 0/2 +                        else +                            insert_node_before(head,n,newkern(k))  -- type 0/2 +                        end +                    end +                end +            end +            if not keep then +                kerns = { } +            end +         -- if trace_injections then +         --     show_result(head) +         -- end +            return head, true +        elseif not keep then +            kerns, cursives, marks = { }, { }, { } +        end +    elseif has_kerns then +        if trace_injections then +            trace(head) +        end +        for n in traverse_id(glyph_code,head) do +            if n.subtype < 256 then +                local k = n[a_kernpair] +                if k then +                    local kk = kerns[k] +                    if kk then +                        local rl, x, y, w = kk[1], kk[2] or 0, kk[3], kk[4] +                        if y and y ~= 0 then +                            n.yoffset = y -- todo: h ? +                        end +                        if w then +                            -- copied from above +                         -- local r2l = kk[6] +                            local wx = w - x +                            if rl < 0 then  -- KE: don't use r2l here +                                if wx ~= 0 then +                                    insert_node_before(head,n,newkern(wx)) +                                end +                                if x ~= 0 then +                                    insert_node_after (head,n,newkern(x)) +                                end +                            else +                                if x ~= 0 then +                                    insert_node_before(head,n,newkern(x)) +                                end +                                if wx ~= 0 then +                                    insert_node_after(head,n,newkern(wx)) +                                end +                            end +                        else +                            -- simple (e.g. kernclass kerns) +                            if x ~= 0 then +                                insert_node_before(head,n,newkern(x)) +                            end +                        end +                    end +                end +            end +        end +        if not keep then +            kerns = { } +        end +     -- if trace_injections then +     --     show_result(head) +     -- end +        return head, true +    else +        -- no tracing needed +    end +    return head, false +end diff --git a/luaotfload-fonts-lua.lua b/src/luaotfload-fonts-lua.lua index ec3fe38..ec3fe38 100644 --- a/luaotfload-fonts-lua.lua +++ b/src/luaotfload-fonts-lua.lua diff --git a/src/luaotfload-fonts-otn.lua b/src/luaotfload-fonts-otn.lua new file mode 100644 index 0000000..c57be5f --- /dev/null +++ b/src/luaotfload-fonts-otn.lua @@ -0,0 +1,2848 @@ +if not modules then modules = { } end modules ['font-otn'] = { +    version   = 1.001, +    comment   = "companion to font-ini.mkiv", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files", +} + +-- preprocessors = { "nodes" } + +-- this is still somewhat preliminary and it will get better in due time; +-- much functionality could only be implemented thanks to the husayni font +-- of Idris Samawi Hamid to who we dedicate this module. + +-- in retrospect it always looks easy but believe it or not, it took a lot +-- of work to get proper open type support done: buggy fonts, fuzzy specs, +-- special made testfonts, many skype sessions between taco, idris and me, +-- torture tests etc etc ... unfortunately the code does not show how much +-- time it took ... + +-- todo: +-- +-- kerning is probably not yet ok for latin around dics nodes (interesting challenge) +-- extension infrastructure (for usage out of context) +-- sorting features according to vendors/renderers +-- alternative loop quitters +-- check cursive and r2l +-- find out where ignore-mark-classes went +-- default features (per language, script) +-- handle positions (we need example fonts) +-- handle gpos_single (we might want an extra width field in glyph nodes because adding kerns might interfere) +-- mark (to mark) code is still not what it should be (too messy but we need some more extreem husayni tests) +-- remove some optimizations (when I have a faster machine) +-- +-- maybe redo the lot some way (more context specific) + +--[[ldx-- +<p>This module is a bit more split up that I'd like but since we also want to test +with plain <l n='tex'/> it has to be so. This module is part of <l n='context'/> +and discussion about improvements and functionality mostly happens on the +<l n='context'/> mailing list.</p> + +<p>The specification of OpenType is kind of vague. Apart from a lack of a proper +free specifications there's also the problem that Microsoft and Adobe +may have their own interpretation of how and in what order to apply features. +In general the Microsoft website has more detailed specifications and is a +better reference. There is also some information in the FontForge help files.</p> + +<p>Because there is so much possible, fonts might contain bugs and/or be made to +work with certain rederers. These may evolve over time which may have the side +effect that suddenly fonts behave differently.</p> + +<p>After a lot of experiments (mostly by Taco, me and Idris) we're now at yet another +implementation. Of course all errors are mine and of course the code can be +improved. There are quite some optimizations going on here and processing speed +is currently acceptable. Not all functions are implemented yet, often because I +lack the fonts for testing. Many scripts are not yet supported either, but I will +look into them as soon as <l n='context'/> users ask for it.</p> + +<p>Because there are different interpretations possible, I will extend the code +with more (configureable) variants. I can also add hooks for users so that they can +write their own extensions.</p> + +<p>Glyphs are indexed not by unicode but in their own way. This is because there is no +relationship with unicode at all, apart from the fact that a font might cover certain +ranges of characters. One character can have multiple shapes. However, at the +<l n='tex'/> end we use unicode so and all extra glyphs are mapped into a private +space. This is needed because we need to access them and <l n='tex'/> has to include +then in the output eventually.</p> + +<p>The raw table as it coms from <l n='fontforge'/> gets reorganized in to fit out needs. +In <l n='context'/> that table is packed (similar tables are shared) and cached on disk +so that successive runs can use the optimized table (after loading the table is +unpacked). The flattening code used later is a prelude to an even more compact table +format (and as such it keeps evolving).</p> + +<p>This module is sparsely documented because it is a moving target. The table format +of the reader changes and we experiment a lot with different methods for supporting +features.</p> + +<p>As with the <l n='afm'/> code, we may decide to store more information in the +<l n='otf'/> table.</p> + +<p>Incrementing the version number will force a re-cache. We jump the number by one +when there's a fix in the <l n='fontforge'/> library or <l n='lua'/> code that +results in different tables.</p> +--ldx]]-- + +-- action                    handler     chainproc             chainmore              comment +-- +-- gsub_single               ok          ok                    ok +-- gsub_multiple             ok          ok                    not implemented yet +-- gsub_alternate            ok          ok                    not implemented yet +-- gsub_ligature             ok          ok                    ok +-- gsub_context              ok          -- +-- gsub_contextchain         ok          -- +-- gsub_reversecontextchain  ok          -- +-- chainsub                  --          ok +-- reversesub                --          ok +-- gpos_mark2base            ok          ok +-- gpos_mark2ligature        ok          ok +-- gpos_mark2mark            ok          ok +-- gpos_cursive              ok          untested +-- gpos_single               ok          ok +-- gpos_pair                 ok          ok +-- gpos_context              ok          -- +-- gpos_contextchain         ok          -- +-- +-- todo: contextpos and contextsub and class stuff +-- +-- actions: +-- +-- handler   : actions triggered by lookup +-- chainproc : actions triggered by contextual lookup +-- chainmore : multiple substitutions triggered by contextual lookup (e.g. fij -> f + ij) +-- +-- remark: the 'not implemented yet' variants will be done when we have fonts that use them +-- remark: we need to check what to do with discretionaries + +-- We used to have independent hashes for lookups but as the tags are unique +-- we now use only one hash. If needed we can have multiple again but in that +-- case I will probably prefix (i.e. rename) the lookups in the cached font file. + +-- Todo: make plugin feature that operates on char/glyphnode arrays + +local concat, insert, remove = table.concat, table.insert, table.remove +local gmatch, gsub, find, match, lower, strip = string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip +local type, next, tonumber, tostring = type, next, tonumber, tostring +local lpegmatch = lpeg.match +local random = math.random +local formatters = string.formatters + +local logs, trackers, nodes, attributes = logs, trackers, nodes, attributes + +local registertracker = trackers.register + +local fonts = fonts +local otf   = fonts.handlers.otf + +local trace_lookups      = false  registertracker("otf.lookups",      function(v) trace_lookups      = v end) +local trace_singles      = false  registertracker("otf.singles",      function(v) trace_singles      = v end) +local trace_multiples    = false  registertracker("otf.multiples",    function(v) trace_multiples    = v end) +local trace_alternatives = false  registertracker("otf.alternatives", function(v) trace_alternatives = v end) +local trace_ligatures    = false  registertracker("otf.ligatures",    function(v) trace_ligatures    = v end) +local trace_contexts     = false  registertracker("otf.contexts",     function(v) trace_contexts     = v end) +local trace_marks        = false  registertracker("otf.marks",        function(v) trace_marks        = v end) +local trace_kerns        = false  registertracker("otf.kerns",        function(v) trace_kerns        = v end) +local trace_cursive      = false  registertracker("otf.cursive",      function(v) trace_cursive      = v end) +local trace_preparing    = false  registertracker("otf.preparing",    function(v) trace_preparing    = v end) +local trace_bugs         = false  registertracker("otf.bugs",         function(v) trace_bugs         = v end) +local trace_details      = false  registertracker("otf.details",      function(v) trace_details      = v end) +local trace_applied      = false  registertracker("otf.applied",      function(v) trace_applied      = v end) +local trace_steps        = false  registertracker("otf.steps",        function(v) trace_steps        = v end) +local trace_skips        = false  registertracker("otf.skips",        function(v) trace_skips        = v end) +local trace_directions   = false  registertracker("otf.directions",   function(v) trace_directions   = v end) + +local report_direct   = logs.reporter("fonts","otf direct") +local report_subchain = logs.reporter("fonts","otf subchain") +local report_chain    = logs.reporter("fonts","otf chain") +local report_process  = logs.reporter("fonts","otf process") +local report_prepare  = logs.reporter("fonts","otf prepare") +local report_warning  = logs.reporter("fonts","otf warning") + +registertracker("otf.verbose_chain", function(v) otf.setcontextchain(v and "verbose") end) +registertracker("otf.normal_chain",  function(v) otf.setcontextchain(v and "normal")  end) + +registertracker("otf.replacements", "otf.singles,otf.multiples,otf.alternatives,otf.ligatures") +registertracker("otf.positions","otf.marks,otf.kerns,otf.cursive") +registertracker("otf.actions","otf.replacements,otf.positions") +registertracker("otf.injections","nodes.injections") + +registertracker("*otf.sample","otf.steps,otf.actions,otf.analyzing") + +local insert_node_after  = node.insert_after +local delete_node        = nodes.delete +local copy_node          = node.copy +local find_node_tail     = node.tail or node.slide +local flush_node_list    = node.flush_list +local end_of_math        = node.end_of_math + +local setmetatableindex  = table.setmetatableindex + +local zwnj               = 0x200C +local zwj                = 0x200D +local wildcard           = "*" +local default            = "dflt" + +local nodecodes          = nodes.nodecodes +local whatcodes          = nodes.whatcodes +local glyphcodes         = nodes.glyphcodes +local disccodes          = nodes.disccodes + +local glyph_code         = nodecodes.glyph +local glue_code          = nodecodes.glue +local disc_code          = nodecodes.disc +local whatsit_code       = nodecodes.whatsit +local math_code          = nodecodes.math + +local dir_code           = whatcodes.dir +local localpar_code      = whatcodes.localpar + +local discretionary_code = disccodes.discretionary + +local ligature_code      = glyphcodes.ligature + +local privateattribute   = attributes.private + +-- Something is messed up: we have two mark / ligature indices, one at the injection +-- end and one here ... this is bases in KE's patches but there is something fishy +-- there as I'm pretty sure that for husayni we need some connection (as it's much +-- more complex than an average font) but I need proper examples of all cases, not +-- of only some. + +local a_state            = privateattribute('state') +local a_markbase         = privateattribute('markbase') +local a_markmark         = privateattribute('markmark') +local a_markdone         = privateattribute('markdone') -- assigned at the injection end +local a_cursbase         = privateattribute('cursbase') +local a_curscurs         = privateattribute('curscurs') +local a_cursdone         = privateattribute('cursdone') +local a_kernpair         = privateattribute('kernpair') +local a_ligacomp         = privateattribute('ligacomp') -- assigned here (ideally it should be combined) + +local injections         = nodes.injections +local setmark            = injections.setmark +local setcursive         = injections.setcursive +local setkern            = injections.setkern +local setpair            = injections.setpair + +local markonce           = true +local cursonce           = true +local kernonce           = true + +local fonthashes         = fonts.hashes +local fontdata           = fonthashes.identifiers + +local otffeatures        = fonts.constructors.newfeatures("otf") +local registerotffeature = otffeatures.register + +local onetimemessage     = fonts.loggers.onetimemessage or function() end + +otf.defaultnodealternate = "none" -- first last + +-- we share some vars here, after all, we have no nested lookups and less code + +local tfmdata             = false +local characters          = false +local descriptions        = false +local resources           = false +local marks               = false +local currentfont         = false +local lookuptable         = false +local anchorlookups       = false +local lookuptypes         = false +local handlers            = { } +local rlmode              = 0 +local featurevalue        = false + +-- head is always a whatsit so we can safely assume that head is not changed + +-- we use this for special testing and documentation + +local checkstep       = (nodes and nodes.tracers and nodes.tracers.steppers.check)    or function() end +local registerstep    = (nodes and nodes.tracers and nodes.tracers.steppers.register) or function() end +local registermessage = (nodes and nodes.tracers and nodes.tracers.steppers.message)  or function() end + +local function logprocess(...) +    if trace_steps then +        registermessage(...) +    end +    report_direct(...) +end + +local function logwarning(...) +    report_direct(...) +end + +local f_unicode = formatters["%U"] +local f_uniname = formatters["%U (%s)"] +local f_unilist = formatters["% t (% t)"] + +local function gref(n) -- currently the same as in font-otb +    if type(n) == "number" then +        local description = descriptions[n] +        local name = description and description.name +        if name then +            return f_uniname(n,name) +        else +            return f_unicode(n) +        end +    elseif n then +        local num, nam = { }, { } +        for i=1,#n do +            local ni = n[i] +            if tonumber(ni) then -- later we will start at 2 +                local di = descriptions[ni] +                num[i] = f_unicode(ni) +                nam[i] = di and di.name or "-" +            end +        end +        return f_unilist(num,nam) +    else +        return "<error in node mode tracing>" +    end +end + +local function cref(kind,chainname,chainlookupname,lookupname,index) -- not in the mood to alias f_ +    if index then +        return formatters["feature %a, chain %a, sub %a, lookup %a, index %a"](kind,chainname,chainlookupname,lookupname,index) +    elseif lookupname then +        return formatters["feature %a, chain %a, sub %a, lookup %a"](kind,chainname,chainlookupname,lookupname) +    elseif chainlookupname then +        return formatters["feature %a, chain %a, sub %a"](kind,chainname,chainlookupname) +    elseif chainname then +        return formatters["feature %a, chain %a"](kind,chainname) +    else +        return formatters["feature %a"](kind) +    end +end + +local function pref(kind,lookupname) +    return formatters["feature %a, lookup %a"](kind,lookupname) +end + +-- We can assume that languages that use marks are not hyphenated. We can also assume +-- that at most one discretionary is present. + +-- We do need components in funny kerning mode but maybe I can better reconstruct then +-- as we do have the font components info available; removing components makes the +-- previous code much simpler. Also, later on copying and freeing becomes easier. +-- However, for arabic we need to keep them around for the sake of mark placement +-- and indices. + +local function copy_glyph(g) -- next and prev are untouched ! +    local components = g.components +    if components then +        g.components = nil +        local n = copy_node(g) +        g.components = components +        return n +    else +        return copy_node(g) +    end +end + +-- start is a mark and we need to keep that one + +local function markstoligature(kind,lookupname,head,start,stop,char) +    if start == stop and start.char == char then +        return head, start +    else +        local prev = start.prev +        local next = stop.next +        start.prev = nil +        stop.next = nil +        local base = copy_glyph(start) +        if head == start then +            head = base +        end +        base.char = char +        base.subtype = ligature_code +        base.components = start +        if prev then +            prev.next = base +        end +        if next then +            next.prev = base +        end +        base.next = next +        base.prev = prev +        return head, base +    end +end + +-- The next code is somewhat complicated by the fact that some fonts can have ligatures made +-- from ligatures that themselves have marks. This was identified by Kai in for instance +-- arabtype:  KAF LAM SHADDA ALEF FATHA (0x0643 0x0644 0x0651 0x0627 0x064E). This becomes +-- KAF LAM-ALEF with a SHADDA on the first and a FATHA op de second component. In a next +-- iteration this becomes a KAF-LAM-ALEF with a SHADDA on the second and a FATHA on the +-- third component. + +local function getcomponentindex(start) +    if start.id ~= glyph_code then +        return 0 +    elseif start.subtype == ligature_code then +        local i = 0 +        local components = start.components +        while components do +            i = i + getcomponentindex(components) +            components = components.next +        end +        return i +    elseif not marks[start.char] then +        return 1 +    else +        return 0 +    end +end + +-- eventually we will do positioning in an other way (needs addional w/h/d fields) + +local function toligature(kind,lookupname,head,start,stop,char,markflag,discfound) -- brr head +    if start == stop and start.char == char then +        start.char = char +        return head, start +    end +    local prev = start.prev +    local next = stop.next +    start.prev = nil +    stop.next = nil +    local base = copy_glyph(start) +    if start == head then +        head = base +    end +    base.char = char +    base.subtype = ligature_code +    base.components = start -- start can have components +    if prev then +        prev.next = base +    end +    if next then +        next.prev = base +    end +    base.next = next +    base.prev = prev +    if not discfound then +        local deletemarks = markflag ~= "mark" +        local components = start +        local baseindex = 0 +        local componentindex = 0 +        local head = base +        local current = base +        -- first we loop over the glyphs in start .. stop +        while start do +            local char = start.char +            if not marks[char] then +                baseindex = baseindex + componentindex +                componentindex = getcomponentindex(start) +            elseif not deletemarks then -- quite fishy +                start[a_ligacomp] = baseindex + (start[a_ligacomp] or componentindex) +                if trace_marks then +                    logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(char),start[a_ligacomp]) +                end +                head, current = insert_node_after(head,current,copy_node(start)) -- unlikely that mark has components +            elseif trace_marks then +                logwarning("%s: delete mark %s",pref(kind,lookupname),gref(char)) +            end +            start = start.next +        end +        -- we can have one accent as part of a lookup and another following +     -- local start = components -- was wrong (component scanning was introduced when more complex ligs in devanagari was added) +        local start = current.next +        while start and start.id == glyph_code do +            local char = start.char +            if marks[char] then +                start[a_ligacomp] = baseindex + (start[a_ligacomp] or componentindex) +                if trace_marks then +                    logwarning("%s: set mark %s, gets index %s",pref(kind,lookupname),gref(char),start[a_ligacomp]) +                end +            else +                break +            end +            start = start.next +        end +    end +    return head, base +end + +function handlers.gsub_single(head,start,kind,lookupname,replacement) +    if trace_singles then +        logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(start.char),gref(replacement)) +    end +    start.char = replacement +    return head, start, true +end + +local function get_alternative_glyph(start,alternatives,value,trace_alternatives) +    local n = #alternatives +    if value == "random" then +        local r = random(1,n) +        return alternatives[r], trace_alternatives and formatters["value %a, taking %a"](value,r) +    elseif value == "first" then +        return alternatives[1], trace_alternatives and formatters["value %a, taking %a"](value,1) +    elseif value == "last" then +        return alternatives[n], trace_alternatives and formatters["value %a, taking %a"](value,n) +    else +        value = tonumber(value) +        if type(value) ~= "number" then +            return alternatives[1], trace_alternatives and formatters["invalid value %s, taking %a"](value,1) +        elseif value > n then +            local defaultalt = otf.defaultnodealternate +            if defaultalt == "first" then +                return alternatives[n], trace_alternatives and formatters["invalid value %s, taking %a"](value,1) +            elseif defaultalt == "last" then +                return alternatives[1], trace_alternatives and formatters["invalid value %s, taking %a"](value,n) +            else +                return false, trace_alternatives and formatters["invalid value %a, %s"](value,"out of range") +            end +        elseif value == 0 then +            return start.char, trace_alternatives and formatters["invalid value %a, %s"](value,"no change") +        elseif value < 1 then +            return alternatives[1], trace_alternatives and formatters["invalid value %a, taking %a"](value,1) +        else +            return alternatives[value], trace_alternatives and formatters["value %a, taking %a"](value,value) +        end +    end +end + +local function multiple_glyphs(head,start,multiple,ignoremarks) +    local nofmultiples = #multiple +    if nofmultiples > 0 then +        start.char = multiple[1] +        if nofmultiples > 1 then +            local sn = start.next +            for k=2,nofmultiples do -- todo: use insert_node +-- untested: +-- +-- while ignoremarks and marks[sn.char] then +--     local sn = sn.next +-- end +                local n = copy_node(start) -- ignore components +                n.char = multiple[k] +                n.next = sn +                n.prev = start +                if sn then +                    sn.prev = n +                end +                start.next = n +                start = n +            end +        end +        return head, start, true +    else +        if trace_multiples then +            logprocess("no multiple for %s",gref(start.char)) +        end +        return head, start, false +    end +end + +function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence) +    local value = featurevalue == true and tfmdata.shared.features[kind] or featurevalue +    local choice, comment = get_alternative_glyph(start,alternative,value,trace_alternatives) +    if choice then +        if trace_alternatives then +            logprocess("%s: replacing %s by alternative %a to %s, %s",pref(kind,lookupname),gref(start.char),choice,gref(choice),comment) +        end +        start.char = choice +    else +        if trace_alternatives then +            logwarning("%s: no variant %a for %s, %s",pref(kind,lookupname),value,gref(start.char),comment) +        end +    end +    return head, start, true +end + +function handlers.gsub_multiple(head,start,kind,lookupname,multiple,sequence) +    if trace_multiples then +        logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(start.char),gref(multiple)) +    end +    return multiple_glyphs(head,start,multiple,sequence.flags[1]) +end + +function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence) +    local s, stop, discfound = start.next, nil, false +    local startchar = start.char +    if marks[startchar] then +        while s do +            local id = s.id +            if id == glyph_code and s.font == currentfont and s.subtype<256 then +                local lg = ligature[s.char] +                if lg then +                    stop = s +                    ligature = lg +                    s = s.next +                else +                    break +                end +            else +                break +            end +        end +        if stop then +            local lig = ligature.ligature +            if lig then +                if trace_ligatures then +                    local stopchar = stop.char +                    head, start = markstoligature(kind,lookupname,head,start,stop,lig) +                    logprocess("%s: replacing %s upto %s by ligature %s case 1",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) +                else +                    head, start = markstoligature(kind,lookupname,head,start,stop,lig) +                end +                return head, start, true +            else +                -- ok, goto next lookup +            end +        end +    else +        local skipmark = sequence.flags[1] +        while s do +            local id = s.id +            if id == glyph_code and s.subtype<256 then +                if s.font == currentfont then +                    local char = s.char +                    if skipmark and marks[char] then +                        s = s.next +                    else +                        local lg = ligature[char] +                        if lg then +                            stop = s +                            ligature = lg +                            s = s.next +                        else +                            break +                        end +                    end +                else +                    break +                end +            elseif id == disc_code then +                discfound = true +                s = s.next +            else +                break +            end +        end +        local lig = ligature.ligature +        if lig then +            if stop then +                if trace_ligatures then +                    local stopchar = stop.char +                    head, start = toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound) +                    logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) +                else +                    head, start = toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound) +                end +                return head, start, true +            else +                -- weird but happens (in some arabic font) +                start.char = lig +                if trace_ligatures then +                    logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(kind,lookupname),gref(startchar),gref(lig)) +                end +                return head, start, true +            end +        else +            -- weird but happens +        end +    end +    return head, start, false +end + +--[[ldx-- +<p>We get hits on a mark, but we're not sure if the it has to be applied so +we need to explicitly test for basechar, baselig and basemark entries.</p> +--ldx]]-- + +function handlers.gpos_mark2base(head,start,kind,lookupname,markanchors,sequence) +    local markchar = start.char +    if marks[markchar] then +        local base = start.prev -- [glyph] [start=mark] +        if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then +            local basechar = base.char +            if marks[basechar] then +                while true do +                    base = base.prev +                    if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then +                        basechar = base.char +                        if not marks[basechar] then +                            break +                        end +                    else +                        if trace_bugs then +                            logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) +                        end +                        return head, start, false +                    end +                end +            end +            local baseanchors = descriptions[basechar] +            if baseanchors then +                baseanchors = baseanchors.anchors +            end +            if baseanchors then +                local baseanchors = baseanchors['basechar'] +                if baseanchors then +                    local al = anchorlookups[lookupname] +                    for anchor,ba in next, baseanchors do +                        if al[anchor] then +                            local ma = markanchors[anchor] +                            if ma then +                                local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma) +                                if trace_marks then +                                    logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", +                                        pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) +                                end +                                return head, start, true +                            end +                        end +                    end +                    if trace_bugs then +                        logwarning("%s, no matching anchors for mark %s and base %s",pref(kind,lookupname),gref(markchar),gref(basechar)) +                    end +                end +            elseif trace_bugs then +            --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar)) +                onetimemessage(currentfont,basechar,"no base anchors",report_fonts) +            end +        elseif trace_bugs then +            logwarning("%s: prev node is no char",pref(kind,lookupname)) +        end +    elseif trace_bugs then +        logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) +    end +    return head, start, false +end + +function handlers.gpos_mark2ligature(head,start,kind,lookupname,markanchors,sequence) +    -- check chainpos variant +    local markchar = start.char +    if marks[markchar] then +        local base = start.prev -- [glyph] [optional marks] [start=mark] +        if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then +            local basechar = base.char +            if marks[basechar] then +                while true do +                    base = base.prev +                    if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then +                        basechar = base.char +                        if not marks[basechar] then +                            break +                        end +                    else +                        if trace_bugs then +                            logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) +                        end +                        return head, start, false +                    end +                end +            end +            local index = start[a_ligacomp] +            local baseanchors = descriptions[basechar] +            if baseanchors then +                baseanchors = baseanchors.anchors +                if baseanchors then +                   local baseanchors = baseanchors['baselig'] +                   if baseanchors then +                        local al = anchorlookups[lookupname] +                        for anchor, ba in next, baseanchors do +                            if al[anchor] then +                                local ma = markanchors[anchor] +                                if ma then +                                    ba = ba[index] +                                    if ba then +                                        local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma) -- index +                                        if trace_marks then +                                            logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", +                                                pref(kind,lookupname),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy) +                                        end +                                        return head, start, true +                                    else +                                        if trace_bugs then +                                            logwarning("%s: no matching anchors for mark %s and baselig %s with index %a",pref(kind,lookupname),gref(markchar),gref(basechar),index) +                                        end +                                    end +                                end +                            end +                        end +                        if trace_bugs then +                            logwarning("%s: no matching anchors for mark %s and baselig %s",pref(kind,lookupname),gref(markchar),gref(basechar)) +                        end +                    end +                end +            elseif trace_bugs then +            --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar)) +                onetimemessage(currentfont,basechar,"no base anchors",report_fonts) +            end +        elseif trace_bugs then +            logwarning("%s: prev node is no char",pref(kind,lookupname)) +        end +    elseif trace_bugs then +        logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) +    end +    return head, start, false +end + +function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence) +    local markchar = start.char +    if marks[markchar] then +        local base = start.prev -- [glyph] [basemark] [start=mark] +        local slc = start[a_ligacomp] +        if slc then -- a rather messy loop ... needs checking with husayni +            while base do +                local blc = base[a_ligacomp] +                if blc and blc ~= slc then +                    base = base.prev +                else +                    break +                end +            end +        end +        if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then -- subtype test can go +            local basechar = base.char +            local baseanchors = descriptions[basechar] +            if baseanchors then +                baseanchors = baseanchors.anchors +                if baseanchors then +                    baseanchors = baseanchors['basemark'] +                    if baseanchors then +                        local al = anchorlookups[lookupname] +                        for anchor,ba in next, baseanchors do +                            if al[anchor] then +                                local ma = markanchors[anchor] +                                if ma then +                                    local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,true) +                                    if trace_marks then +                                        logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", +                                            pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) +                                    end +                                    return head, start, true +                                end +                            end +                        end +                        if trace_bugs then +                            logwarning("%s: no matching anchors for mark %s and basemark %s",pref(kind,lookupname),gref(markchar),gref(basechar)) +                        end +                    end +                end +            elseif trace_bugs then +            --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar)) +                onetimemessage(currentfont,basechar,"no base anchors",report_fonts) +            end +        elseif trace_bugs then +            logwarning("%s: prev node is no mark",pref(kind,lookupname)) +        end +    elseif trace_bugs then +        logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) +    end +    return head, start, false +end + +function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence) -- to be checked +    local alreadydone = cursonce and start[a_cursbase] +    if not alreadydone then +        local done = false +        local startchar = start.char +        if marks[startchar] then +            if trace_cursive then +                logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) +            end +        else +            local nxt = start.next +            while not done and nxt and nxt.id == glyph_code and nxt.font == currentfont and nxt.subtype<256 do +                local nextchar = nxt.char +                if marks[nextchar] then +                    -- should not happen (maybe warning) +                    nxt = nxt.next +                else +                    local entryanchors = descriptions[nextchar] +                    if entryanchors then +                        entryanchors = entryanchors.anchors +                        if entryanchors then +                            entryanchors = entryanchors['centry'] +                            if entryanchors then +                                local al = anchorlookups[lookupname] +                                for anchor, entry in next, entryanchors do +                                    if al[anchor] then +                                        local exit = exitanchors[anchor] +                                        if exit then +                                            local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) +                                            if trace_cursive then +                                                logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) +                                            end +                                            done = true +                                            break +                                        end +                                    end +                                end +                            end +                        end +                    elseif trace_bugs then +                    --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar)) +                        onetimemessage(currentfont,startchar,"no entry anchors",report_fonts) +                    end +                    break +                end +            end +        end +        return head, start, done +    else +        if trace_cursive and trace_details then +            logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(start.char),alreadydone) +        end +        return head, start, false +    end +end + +function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence) +    local startchar = start.char +    local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) +    if trace_kerns then +        logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),dx,dy,w,h) +    end +    return head, start, false +end + +function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence) +    -- todo: kerns in disc nodes: pre, post, replace -> loop over disc too +    -- todo: kerns in components of ligatures +    local snext = start.next +    if not snext then +        return head, start, false +    else +        local prev, done = start, false +        local factor = tfmdata.parameters.factor +        local lookuptype = lookuptypes[lookupname] +        while snext and snext.id == glyph_code and snext.font == currentfont and snext.subtype<256 do +            local nextchar = snext.char +            local krn = kerns[nextchar] +            if not krn and marks[nextchar] then +                prev = snext +                snext = snext.next +            else +                if not krn then +                    -- skip +                elseif type(krn) == "table" then +                    if lookuptype == "pair" then -- probably not needed +                        local a, b = krn[2], krn[3] +                        if a and #a > 0 then +                            local startchar = start.char +                            local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) +                            if trace_kerns then +                                logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) +                            end +                        end +                        if b and #b > 0 then +                            local startchar = start.char +                            local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) +                            if trace_kerns then +                                logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) +                            end +                        end +                    else -- wrong ... position has different entries +                        report_process("%s: check this out (old kern stuff)",pref(kind,lookupname)) +                     -- local a, b = krn[2], krn[6] +                     -- if a and a ~= 0 then +                     --     local k = setkern(snext,factor,rlmode,a) +                     --     if trace_kerns then +                     --         logprocess("%s: inserting first kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar)) +                     --     end +                     -- end +                     -- if b and b ~= 0 then +                     --     logwarning("%s: ignoring second kern xoff %s",pref(kind,lookupname),b*factor) +                     -- end +                    end +                    done = true +                elseif krn ~= 0 then +                    local k = setkern(snext,factor,rlmode,krn) +                    if trace_kerns then +                        logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar)) +                    end +                    done = true +                end +                break +            end +        end +        return head, start, done +    end +end + +--[[ldx-- +<p>I will implement multiple chain replacements once I run into a font that uses +it. It's not that complex to handle.</p> +--ldx]]-- + +local chainmores = { } +local chainprocs = { } + +local function logprocess(...) +    if trace_steps then +        registermessage(...) +    end +    report_subchain(...) +end + +local logwarning = report_subchain + +local function logprocess(...) +    if trace_steps then +        registermessage(...) +    end +    report_chain(...) +end + +local logwarning = report_chain + +-- We could share functions but that would lead to extra function calls with many +-- arguments, redundant tests and confusing messages. + +function chainprocs.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname) +    logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) +    return head, start, false +end + +function chainmores.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname,n) +    logprocess("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) +    return head, start, false +end + +-- The reversesub is a special case, which is why we need to store the replacements +-- in a bit weird way. There is no lookup and the replacement comes from the lookup +-- itself. It is meant mostly for dealing with Urdu. + +function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,lookuphash,replacements) +    local char = start.char +    local replacement = replacements[char] +    if replacement then +        if trace_singles then +            logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement)) +        end +        start.char = replacement +        return head, start, true +    else +        return head, start, false +    end +end + +--[[ldx-- +<p>This chain stuff is somewhat tricky since we can have a sequence of actions to be +applied: single, alternate, multiple or ligature where ligature can be an invalid +one in the sense that it will replace multiple by one but not neccessary one that +looks like the combination (i.e. it is the counterpart of multiple then). For +example, the following is valid:</p> + +<typing> +<line>xxxabcdexxx [single a->A][multiple b->BCD][ligature cde->E] xxxABCDExxx</line> +</typing> + +<p>Therefore we we don't really do the replacement here already unless we have the +single lookup case. The efficiency of the replacements can be improved by deleting +as less as needed but that would also make the code even more messy.</p> +--ldx]]-- + +-- local function delete_till_stop(head,start,stop,ignoremarks) -- keeps start +--     local n = 1 +--     if start == stop then +--         -- done +--     elseif ignoremarks then +--         repeat -- start x x m x x stop => start m +--             local next = start.next +--             if not marks[next.char] then +--                 local components = next.components +--                 if components then -- probably not needed +--                     flush_node_list(components) +--                 end +--                 head = delete_node(head,next) +--             end +--             n = n + 1 +--         until next == stop +--     else -- start x x x stop => start +--         repeat +--             local next = start.next +--             local components = next.components +--             if components then -- probably not needed +--                 flush_node_list(components) +--             end +--             head = delete_node(head,next) +--             n = n + 1 +--         until next == stop +--     end +--     return head, n +-- end + +--[[ldx-- +<p>Here we replace start by a single variant, First we delete the rest of the +match.</p> +--ldx]]-- + +function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) +    -- todo: marks ? +    local current = start +    local subtables = currentlookup.subtables +    if #subtables > 1 then +        logwarning("todo: check if we need to loop over the replacements: %s",concat(subtables," ")) +    end +    while current do +        if current.id == glyph_code then +            local currentchar = current.char +            local lookupname = subtables[1] -- only 1 +            local replacement = lookuphash[lookupname] +            if not replacement then +                if trace_bugs then +                    logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) +                end +            else +                replacement = replacement[currentchar] +                if not replacement or replacement == "" then +                    if trace_bugs then +                        logwarning("%s: no single for %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar)) +                    end +                else +                    if trace_singles then +                        logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement)) +                    end +                    current.char = replacement +                end +            end +            return head, start, true +        elseif current == stop then +            break +        else +            current = current.next +        end +    end +    return head, start, false +end + +chainmores.gsub_single = chainprocs.gsub_single + +--[[ldx-- +<p>Here we replace start by a sequence of new glyphs. First we delete the rest of +the match.</p> +--ldx]]-- + +function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) + -- local head, n = delete_till_stop(head,start,stop) +    local startchar = start.char +    local subtables = currentlookup.subtables +    local lookupname = subtables[1] +    local replacements = lookuphash[lookupname] +    if not replacements then +        if trace_bugs then +            logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname)) +        end +    else +        replacements = replacements[startchar] +        if not replacements or replacement == "" then +            if trace_bugs then +                logwarning("%s: no multiple for %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar)) +            end +        else +            if trace_multiples then +                logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements)) +            end +            return multiple_glyphs(head,start,replacements,currentlookup.flags[1]) +        end +    end +    return head, start, false +end + +chainmores.gsub_multiple = chainprocs.gsub_multiple + +--[[ldx-- +<p>Here we replace start by new glyph. First we delete the rest of the match.</p> +--ldx]]-- + +-- char_1 mark_1 -> char_x mark_1 (ignore marks) +-- char_1 mark_1 -> char_x + +-- to be checked: do we always have just one glyph? +-- we can also have alternates for marks +-- marks come last anyway +-- are there cases where we need to delete the mark + +function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +    local current = start +    local subtables = currentlookup.subtables +    local value  = featurevalue == true and tfmdata.shared.features[kind] or featurevalue +    while current do +        if current.id == glyph_code then -- is this check needed? +            local currentchar = current.char +            local lookupname = subtables[1] +            local alternatives = lookuphash[lookupname] +            if not alternatives then +                if trace_bugs then +                    logwarning("%s: no alternative hit",cref(kind,chainname,chainlookupname,lookupname)) +                end +            else +                alternatives = alternatives[currentchar] +                if alternatives then +                    local choice, comment = get_alternative_glyph(current,alternatives,value,trace_alternatives) +                    if choice then +                        if trace_alternatives then +                            logprocess("%s: replacing %s by alternative %a to %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(char),choice,gref(choice),comment) +                        end +                        start.char = choice +                    else +                        if trace_alternatives then +                            logwarning("%s: no variant %a for %s, %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(char),comment) +                        end +                    end +                elseif trace_bugs then +                    logwarning("%s: no alternative for %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(currentchar),comment) +                end +            end +            return head, start, true +        elseif current == stop then +            break +        else +            current = current.next +        end +    end +    return head, start, false +end + +chainmores.gsub_alternate = chainprocs.gsub_alternate + +--[[ldx-- +<p>When we replace ligatures we use a helper that handles the marks. I might change +this function (move code inline and handle the marks by a separate function). We +assume rather stupid ligatures (no complex disc nodes).</p> +--ldx]]-- + +function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) +    local startchar = start.char +    local subtables = currentlookup.subtables +    local lookupname = subtables[1] +    local ligatures = lookuphash[lookupname] +    if not ligatures then +        if trace_bugs then +            logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) +        end +    else +        ligatures = ligatures[startchar] +        if not ligatures then +            if trace_bugs then +                logwarning("%s: no ligatures starting with %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) +            end +        else +            local s = start.next +            local discfound = false +            local last = stop +            local nofreplacements = 0 +            local skipmark = currentlookup.flags[1] +            while s do +                local id = s.id +                if id == disc_code then +                    s = s.next +                    discfound = true +                else +                    local schar = s.char +                    if skipmark and marks[schar] then -- marks +                        s = s.next +                    else +                        local lg = ligatures[schar] +                        if lg then +                            ligatures, last, nofreplacements = lg, s, nofreplacements + 1 +                            if s == stop then +                                break +                            else +                                s = s.next +                            end +                        else +                            break +                        end +                    end +                end +            end +            local l2 = ligatures.ligature +            if l2 then +                if chainindex then +                    stop = last +                end +                if trace_ligatures then +                    if start == stop then +                        logprocess("%s: replacing character %s by ligature %s case 3",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(l2)) +                    else +                        logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(stop.char),gref(l2)) +                    end +                end +                head, start = toligature(kind,lookupname,head,start,stop,l2,currentlookup.flags[1],discfound) +                return head, start, true, nofreplacements +            elseif trace_bugs then +                if start == stop then +                    logwarning("%s: replacing character %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) +                else +                    logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(stop.char)) +                end +            end +        end +    end +    return head, start, false, 0 +end + +chainmores.gsub_ligature = chainprocs.gsub_ligature + +function chainprocs.gpos_mark2base(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +    local markchar = start.char +    if marks[markchar] then +        local subtables = currentlookup.subtables +        local lookupname = subtables[1] +        local markanchors = lookuphash[lookupname] +        if markanchors then +            markanchors = markanchors[markchar] +        end +        if markanchors then +            local base = start.prev -- [glyph] [start=mark] +            if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then +                local basechar = base.char +                if marks[basechar] then +                    while true do +                        base = base.prev +                        if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then +                            basechar = base.char +                            if not marks[basechar] then +                                break +                            end +                        else +                            if trace_bugs then +                                logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) +                            end +                            return head, start, false +                        end +                    end +                end +                local baseanchors = descriptions[basechar].anchors +                if baseanchors then +                    local baseanchors = baseanchors['basechar'] +                    if baseanchors then +                        local al = anchorlookups[lookupname] +                        for anchor,ba in next, baseanchors do +                            if al[anchor] then +                                local ma = markanchors[anchor] +                                if ma then +                                    local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma) +                                    if trace_marks then +                                        logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", +                                            cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) +                                    end +                                    return head, start, true +                                end +                            end +                        end +                        if trace_bugs then +                            logwarning("%s, no matching anchors for mark %s and base %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) +                        end +                    end +                end +            elseif trace_bugs then +                logwarning("%s: prev node is no char",cref(kind,chainname,chainlookupname,lookupname)) +            end +        elseif trace_bugs then +            logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) +        end +    elseif trace_bugs then +        logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) +    end +    return head, start, false +end + +function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +    local markchar = start.char +    if marks[markchar] then +        local subtables = currentlookup.subtables +        local lookupname = subtables[1] +        local markanchors = lookuphash[lookupname] +        if markanchors then +            markanchors = markanchors[markchar] +        end +        if markanchors then +            local base = start.prev -- [glyph] [optional marks] [start=mark] +            if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then +                local basechar = base.char +                if marks[basechar] then +                    while true do +                        base = base.prev +                        if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then +                            basechar = base.char +                            if not marks[basechar] then +                                break +                            end +                        else +                            if trace_bugs then +                                logwarning("%s: no base for mark %s",cref(kind,chainname,chainlookupname,lookupname),markchar) +                            end +                            return head, start, false +                        end +                    end +                end +                -- todo: like marks a ligatures hash +                local index = start[a_ligacomp] +                local baseanchors = descriptions[basechar].anchors +                if baseanchors then +                   local baseanchors = baseanchors['baselig'] +                   if baseanchors then +                        local al = anchorlookups[lookupname] +                        for anchor,ba in next, baseanchors do +                            if al[anchor] then +                                local ma = markanchors[anchor] +                                if ma then +                                    ba = ba[index] +                                    if ba then +                                        local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma) -- index +                                        if trace_marks then +                                            logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", +                                                cref(kind,chainname,chainlookupname,lookupname),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy) +                                        end +                                        return head, start, true +                                    end +                                end +                            end +                        end +                        if trace_bugs then +                            logwarning("%s: no matching anchors for mark %s and baselig %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) +                        end +                    end +                end +            elseif trace_bugs then +                logwarning("feature %s, lookup %s: prev node is no char",kind,lookupname) +            end +        elseif trace_bugs then +            logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) +        end +    elseif trace_bugs then +        logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) +    end +    return head, start, false +end + +function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +    local markchar = start.char +    if marks[markchar] then +     -- local alreadydone = markonce and start[a_markmark] +     -- if not alreadydone then +        --  local markanchors = descriptions[markchar].anchors markanchors = markanchors and markanchors.mark +            local subtables = currentlookup.subtables +            local lookupname = subtables[1] +            local markanchors = lookuphash[lookupname] +            if markanchors then +                markanchors = markanchors[markchar] +            end +            if markanchors then +                local base = start.prev -- [glyph] [basemark] [start=mark] +                local slc = start[a_ligacomp] +                if slc then -- a rather messy loop ... needs checking with husayni +                    while base do +                        local blc = base[a_ligacomp] +                        if blc and blc ~= slc then +                            base = base.prev +                        else +                            break +                        end +                    end +                end +                if base and base.id == glyph_code and base.font == currentfont and base.subtype<256 then -- subtype test can go +                    local basechar = base.char +                    local baseanchors = descriptions[basechar].anchors +                    if baseanchors then +                        baseanchors = baseanchors['basemark'] +                        if baseanchors then +                            local al = anchorlookups[lookupname] +                            for anchor,ba in next, baseanchors do +                                if al[anchor] then +                                    local ma = markanchors[anchor] +                                    if ma then +                                        local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,true) +                                        if trace_marks then +                                            logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", +                                                cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) +                                        end +                                        return head, start, true +                                    end +                                end +                            end +                            if trace_bugs then +                                logwarning("%s: no matching anchors for mark %s and basemark %s",gref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) +                            end +                        end +                    end +                elseif trace_bugs then +                    logwarning("%s: prev node is no mark",cref(kind,chainname,chainlookupname,lookupname)) +                end +            elseif trace_bugs then +                logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) +            end +     -- elseif trace_marks and trace_details then +     --     logprocess("%s, mark %s is already bound (n=%s), ignoring mark2mark",pref(kind,lookupname),gref(markchar),alreadydone) +     -- end +    elseif trace_bugs then +        logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) +    end +    return head, start, false +end + +function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +    local alreadydone = cursonce and start[a_cursbase] +    if not alreadydone then +        local startchar = start.char +        local subtables = currentlookup.subtables +        local lookupname = subtables[1] +        local exitanchors = lookuphash[lookupname] +        if exitanchors then +            exitanchors = exitanchors[startchar] +        end +        if exitanchors then +            local done = false +            if marks[startchar] then +                if trace_cursive then +                    logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) +                end +            else +                local nxt = start.next +                while not done and nxt and nxt.id == glyph_code and nxt.font == currentfont and nxt.subtype<256 do +                    local nextchar = nxt.char +                    if marks[nextchar] then +                        -- should not happen (maybe warning) +                        nxt = nxt.next +                    else +                        local entryanchors = descriptions[nextchar] +                        if entryanchors then +                            entryanchors = entryanchors.anchors +                            if entryanchors then +                                entryanchors = entryanchors['centry'] +                                if entryanchors then +                                    local al = anchorlookups[lookupname] +                                    for anchor, entry in next, entryanchors do +                                        if al[anchor] then +                                            local exit = exitanchors[anchor] +                                            if exit then +                                                local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) +                                                if trace_cursive then +                                                    logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) +                                                end +                                                done = true +                                                break +                                            end +                                        end +                                    end +                                end +                            end +                        elseif trace_bugs then +                        --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar)) +                            onetimemessage(currentfont,startchar,"no entry anchors",report_fonts) +                        end +                        break +                    end +                end +            end +            return head, start, done +        else +            if trace_cursive and trace_details then +                logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(start.char),alreadydone) +            end +            return head, start, false +        end +    end +    return head, start, false +end + +function chainprocs.gpos_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) +    -- untested .. needs checking for the new model +    local startchar = start.char +    local subtables = currentlookup.subtables +    local lookupname = subtables[1] +    local kerns = lookuphash[lookupname] +    if kerns then +        kerns = kerns[startchar] -- needed ? +        if kerns then +            local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) +            if trace_kerns then +                logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),dx,dy,w,h) +            end +        end +    end +    return head, start, false +end + +chainmores.gpos_single = chainprocs.gpos_single -- okay? + +-- when machines become faster i will make a shared function + +function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) +    local snext = start.next +    if snext then +        local startchar = start.char +        local subtables = currentlookup.subtables +        local lookupname = subtables[1] +        local kerns = lookuphash[lookupname] +        if kerns then +            kerns = kerns[startchar] +            if kerns then +                local lookuptype = lookuptypes[lookupname] +                local prev, done = start, false +                local factor = tfmdata.parameters.factor +                while snext and snext.id == glyph_code and snext.font == currentfont and snext.subtype<256 do +                    local nextchar = snext.char +                    local krn = kerns[nextchar] +                    if not krn and marks[nextchar] then +                        prev = snext +                        snext = snext.next +                    else +                        if not krn then +                            -- skip +                        elseif type(krn) == "table" then +                            if lookuptype == "pair" then +                                local a, b = krn[2], krn[3] +                                if a and #a > 0 then +                                    local startchar = start.char +                                    local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) +                                    if trace_kerns then +                                        logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) +                                    end +                                end +                                if b and #b > 0 then +                                    local startchar = start.char +                                    local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) +                                    if trace_kerns then +                                        logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) +                                    end +                                end +                            else +                                report_process("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname)) +                                local a, b = krn[2], krn[6] +                                if a and a ~= 0 then +                                    local k = setkern(snext,factor,rlmode,a) +                                    if trace_kerns then +                                        logprocess("%s: inserting first kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar)) +                                    end +                                end +                                if b and b ~= 0 then +                                    logwarning("%s: ignoring second kern xoff %s",cref(kind,chainname,chainlookupname),b*factor) +                                end +                            end +                            done = true +                        elseif krn ~= 0 then +                            local k = setkern(snext,factor,rlmode,krn) +                            if trace_kerns then +                                logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar)) +                            end +                            done = true +                        end +                        break +                    end +                end +                return head, start, done +            end +        end +    end +    return head, start, false +end + +chainmores.gpos_pair = chainprocs.gpos_pair -- okay? + +-- what pointer to return, spec says stop +-- to be discussed ... is bidi changer a space? +-- elseif char == zwnj and sequence[n][32] then -- brrr + +-- somehow l or f is global +-- we don't need to pass the currentcontext, saves a bit +-- make a slow variant then can be activated but with more tracing + +local function show_skip(kind,chainname,char,ck,class) +    if ck[9] then +        logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a, %a => %a",cref(kind,chainname),gref(char),class,ck[1],ck[2],ck[9],ck[10]) +    else +        logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(kind,chainname),gref(char),class,ck[1],ck[2]) +    end +end + +local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash) +    --  local rule, lookuptype, sequence, f, l, lookups = ck[1], ck[2] ,ck[3], ck[4], ck[5], ck[6] +    local flags        = sequence.flags +    local done         = false +    local skipmark     = flags[1] +    local skipligature = flags[2] +    local skipbase     = flags[3] +    local someskip     = skipmark or skipligature or skipbase -- could be stored in flags for a fast test (hm, flags could be false !) +    local markclass    = sequence.markclass                   -- todo, first we need a proper test +    local skipped      = false +    for k=1,#contexts do +        local match   = true +        local current = start +        local last    = start +        local ck      = contexts[k] +        local seq     = ck[3] +        local s       = #seq +        -- f..l = mid string +        if s == 1 then +            -- never happens +            match = current.id == glyph_code and current.font == currentfont and current.subtype<256 and seq[1][current.char] +        else +            -- maybe we need a better space check (maybe check for glue or category or combination) +            -- we cannot optimize for n=2 because there can be disc nodes +            local f, l = ck[4], ck[5] +            -- current match +            if f == 1 and f == l then -- current only +                -- already a hit +             -- match = true +            else -- before/current/after | before/current | current/after +                -- no need to test first hit (to be optimized) +                if f == l then -- new, else last out of sync (f is > 1) +                 -- match = true +                else +                    local n = f + 1 +                    last = last.next +                    while n <= l do +                        if last then +                            local id = last.id +                            if id == glyph_code then +                                if last.font == currentfont and last.subtype<256 then +                                    local char = last.char +                                    local ccd = descriptions[char] +                                    if ccd then +                                        local class = ccd.class +                                        if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then +                                            skipped = true +                                            if trace_skips then +                                                show_skip(kind,chainname,char,ck,class) +                                            end +                                            last = last.next +                                        elseif seq[n][char] then +                                            if n < l then +                                                last = last.next +                                            end +                                            n = n + 1 +                                        else +                                            match = false +                                            break +                                        end +                                    else +                                        match = false +                                        break +                                    end +                                else +                                    match = false +                                    break +                                end +                            elseif id == disc_code then +                                last = last.next +                            else +                                match = false +                                break +                            end +                        else +                            match = false +                            break +                        end +                    end +                end +            end +            -- before +            if match and f > 1 then +                local prev = start.prev +                if prev then +                    local n = f-1 +                    while n >= 1 do +                        if prev then +                            local id = prev.id +                            if id == glyph_code then +                                if prev.font == currentfont and prev.subtype<256 then -- normal char +                                    local char = prev.char +                                    local ccd = descriptions[char] +                                    if ccd then +                                        local class = ccd.class +                                        if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then +                                            skipped = true +                                            if trace_skips then +                                                show_skip(kind,chainname,char,ck,class) +                                            end +                                        elseif seq[n][char] then +                                            n = n -1 +                                        else +                                            match = false +                                            break +                                        end +                                    else +                                        match = false +                                        break +                                    end +                                else +                                    match = false +                                    break +                                end +                            elseif id == disc_code then +                                -- skip 'm +                            elseif seq[n][32] then +                                n = n -1 +                            else +                                match = false +                                break +                            end +                            prev = prev.prev +                        elseif seq[n][32] then -- somewhat special, as zapfino can have many preceding spaces +                            n = n -1 +                        else +                            match = false +                            break +                        end +                    end +                elseif f == 2 then +                    match = seq[1][32] +                else +                    for n=f-1,1 do +                        if not seq[n][32] then +                            match = false +                            break +                        end +                    end +                end +            end +            -- after +            if match and s > l then +                local current = last and last.next +                if current then +                    -- removed optimization for s-l == 1, we have to deal with marks anyway +                    local n = l + 1 +                    while n <= s do +                        if current then +                            local id = current.id +                            if id == glyph_code then +                                if current.font == currentfont and current.subtype<256 then -- normal char +                                    local char = current.char +                                    local ccd = descriptions[char] +                                    if ccd then +                                        local class = ccd.class +                                        if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then +                                            skipped = true +                                            if trace_skips then +                                                show_skip(kind,chainname,char,ck,class) +                                            end +                                        elseif seq[n][char] then +                                            n = n + 1 +                                        else +                                            match = false +                                            break +                                        end +                                    else +                                        match = false +                                        break +                                    end +                                else +                                    match = false +                                    break +                                end +                            elseif id == disc_code then +                                -- skip 'm +                            elseif seq[n][32] then -- brrr +                                n = n + 1 +                            else +                                match = false +                                break +                            end +                            current = current.next +                        elseif seq[n][32] then +                            n = n + 1 +                        else +                            match = false +                            break +                        end +                    end +                elseif s-l == 1 then +                    match = seq[s][32] +                else +                    for n=l+1,s do +                        if not seq[n][32] then +                            match = false +                            break +                        end +                    end +                end +            end +        end +        if match then +            -- ck == currentcontext +            if trace_contexts then +                local rule, lookuptype, f, l = ck[1], ck[2], ck[4], ck[5] +                local char = start.char +                if ck[9] then +                    logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a, %a => %a", +                        cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10]) +                else +                    logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a", +                        cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype) +                end +            end +            local chainlookups = ck[6] +            if chainlookups then +                local nofchainlookups = #chainlookups +                -- we can speed this up if needed +                if nofchainlookups == 1 then +                    local chainlookupname = chainlookups[1] +                    local chainlookup = lookuptable[chainlookupname] +                    if chainlookup then +                        local cp = chainprocs[chainlookup.type] +                        if cp then +                            local ok +                            head, start, ok = cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) +                            if ok then +                                done = true +                            end +                        else +                            logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) +                        end +                    else -- shouldn't happen +                        logprocess("%s is not yet supported",cref(kind,chainname,chainlookupname)) +                    end +                 else +                    local i = 1 +                    repeat +                        if skipped then +                            while true do +                                local char = start.char +                                local ccd = descriptions[char] +                                if ccd then +                                    local class = ccd.class +                                    if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then +                                        start = start.next +                                    else +                                        break +                                    end +                                else +                                    break +                                end +                            end +                        end +                        local chainlookupname = chainlookups[i] +                        local chainlookup = lookuptable[chainlookupname] +                        if not chainlookup then +                            -- okay, n matches, < n replacements +                            i = i + 1 +                        else +                            local cp = chainmores[chainlookup.type] +                            if not cp then +                                -- actually an error +                                logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) +                                i = i + 1 +                            else +                                local ok, n +                                head, start, ok, n = cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence) +                                -- messy since last can be changed ! +                                if ok then +                                    done = true +                                    -- skip next one(s) if ligature +                                    i = i + (n or 1) +                                else +                                    i = i + 1 +                                end +                            end +                        end +                        if start then +                            start = start.next +                        else +                            -- weird +                        end +                    until i > nofchainlookups +                end +            else +                local replacements = ck[7] +                if replacements then +                    head, start, done = chainprocs.reversesub(head,start,last,kind,chainname,ck,lookuphash,replacements) -- sequence +                else +                    done = true -- can be meant to be skipped +                    if trace_contexts then +                        logprocess("%s: skipping match",cref(kind,chainname)) +                    end +                end +            end +        end +    end +    return head, start, done +end + +-- Because we want to keep this elsewhere (an because speed is less an issue) we +-- pass the font id so that the verbose variant can access the relevant helper tables. + +local verbose_handle_contextchain = function(font,...) +    logwarning("no verbose handler installed, reverting to 'normal'") +    otf.setcontextchain() +    return normal_handle_contextchain(...) +end + +otf.chainhandlers = { +    normal  = normal_handle_contextchain, +    verbose = verbose_handle_contextchain, +} + +function otf.setcontextchain(method) +    if not method or method == "normal" or not otf.chainhandlers[method] then +        if handlers.contextchain then -- no need for a message while making the format +            logwarning("installing normal contextchain handler") +        end +        handlers.contextchain = normal_handle_contextchain +    else +        logwarning("installing contextchain handler %a",method) +        local handler = otf.chainhandlers[method] +        handlers.contextchain = function(...) +            return handler(currentfont,...) -- hm, get rid of ... +        end +    end +    handlers.gsub_context             = handlers.contextchain +    handlers.gsub_contextchain        = handlers.contextchain +    handlers.gsub_reversecontextchain = handlers.contextchain +    handlers.gpos_contextchain        = handlers.contextchain +    handlers.gpos_context             = handlers.contextchain +end + +otf.setcontextchain() + +local missing = { } -- we only report once + +local function logprocess(...) +    if trace_steps then +        registermessage(...) +    end +    report_process(...) +end + +local logwarning = report_process + +local function report_missing_cache(typ,lookup) +    local f = missing[currentfont] if not f then f = { } missing[currentfont] = f end +    local t = f[typ]               if not t then t = { } f[typ]               = t end +    if not t[lookup] then +        t[lookup] = true +        logwarning("missing cache for lookup %a, type %a, font %a, name %a",lookup,typ,currentfont,tfmdata.properties.fullname) +    end +end + +local resolved = { } -- we only resolve a font,script,language pair once + +-- todo: pass all these 'locals' in a table + +local lookuphashes = { } + +setmetatableindex(lookuphashes, function(t,font) +    local lookuphash = fontdata[font].resources.lookuphash +    if not lookuphash or not next(lookuphash) then +        lookuphash = false +    end +    t[font] = lookuphash +    return lookuphash +end) + +-- fonts.hashes.lookups = lookuphashes + +local autofeatures = fonts.analyzers.features -- was: constants + +local function initialize(sequence,script,language,enabled) +    local features = sequence.features +    if features then +        for kind, scripts in next, features do +            local valid = enabled[kind] +            if valid then +                local languages = scripts[script] or scripts[wildcard] +                if languages and (languages[language] or languages[wildcard]) then +                    return { valid, autofeatures[kind] or false, sequence.chain or 0, kind, sequence } +                end +            end +        end +    end +    return false +end + +function otf.dataset(tfmdata,font) -- generic variant, overloaded in context +    local shared     = tfmdata.shared +    local properties = tfmdata.properties +    local language   = properties.language or "dflt" +    local script     = properties.script   or "dflt" +    local enabled    = shared.features +    local res = resolved[font] +    if not res then +        res = { } +        resolved[font] = res +    end +    local rs = res[script] +    if not rs then +        rs = { } +        res[script] = rs +    end +    local rl = rs[language] +    if not rl then +        rl = { +            -- indexed but we can also add specific data by key +        } +        rs[language] = rl +        local sequences = tfmdata.resources.sequences +--         setmetatableindex(rl, function(t,k) +--             if type(k) == "number" then +--                 local v = enabled and initialize(sequences[k],script,language,enabled) +--                 t[k] = v +--                 return v +--             end +--         end) +for s=1,#sequences do +    local v = enabled and initialize(sequences[s],script,language,enabled) +    if v then +        rl[#rl+1] = v +    end +end +    end +    return rl +end + +-- elseif id == glue_code then +--     if p[5] then -- chain +--         local pc = pp[32] +--         if pc then +--             start, ok = start, false -- p[1](start,kind,p[2],pc,p[3],p[4]) +--             if ok then +--                 done = true +--             end +--             if start then start = start.next end +--         else +--             start = start.next +--         end +--     else +--         start = start.next +--     end + +-- there will be a new direction parser (pre-parsed etc) + +-- less bytecode: 290 -> 254 +-- +-- attr = attr or false +-- +-- local a = getattr(start,0) +-- if (a == attr and (not attribute or getattr(start,a_state) == attribute)) or (not attribute or getattr(start,a_state) == attribute) then +--     -- the action +-- end + +local function featuresprocessor(head,font,attr) + +    local lookuphash = lookuphashes[font] -- we can also check sequences here + +    if not lookuphash then +        return head, false +    end + +    if trace_steps then +        checkstep(head) +    end + +    tfmdata         = fontdata[font] +    descriptions    = tfmdata.descriptions +    characters      = tfmdata.characters +    resources       = tfmdata.resources + +    marks           = resources.marks +    anchorlookups   = resources.lookup_to_anchor +    lookuptable     = resources.lookups +    lookuptypes     = resources.lookuptypes + +    currentfont     = font +    rlmode          = 0 + +    local sequences = resources.sequences +    local done      = false +    local datasets  = otf.dataset(tfmdata,font,attr) + +    local dirstack  = { } -- could move outside function + +    -- We could work on sub start-stop ranges instead but I wonder if there is that +    -- much speed gain (experiments showed that it made not much sense) and we need +    -- to keep track of directions anyway. Also at some point I want to play with +    -- font interactions and then we do need the full sweeps. + +    -- Keeping track of the headnode is needed for devanagari (I generalized it a bit +    -- so that multiple cases are also covered.) + +    for s=1,#datasets do +        local dataset = datasets[s] +        featurevalue = dataset[1] -- todo: pass to function instead of using a global + +        local sequence  = dataset[5] -- sequences[s] -- also dataset[5] +        local rlparmode = 0 +        local topstack  = 0 +        local success   = false +        local attribute = dataset[2] +        local chain     = dataset[3] -- sequence.chain or 0 +        local typ       = sequence.type +        local subtables = sequence.subtables +        if chain < 0 then +            -- this is a limited case, no special treatments like 'init' etc +            local handler = handlers[typ] +            -- we need to get rid of this slide! probably no longer needed in latest luatex +            local start = find_node_tail(head) -- slow (we can store tail because there's always a skip at the end): todo +            while start do +                local id = start.id +                if id == glyph_code then +                    if start.font == font and start.subtype<256 then +                        local a = start[0] +                        if a then +                            a = a == attr +                        else +                            a = true +                        end +                        if a then +                            for i=1,#subtables do +                                local lookupname = subtables[i] +                                local lookupcache = lookuphash[lookupname] +                                if lookupcache then +                                    local lookupmatch = lookupcache[start.char] +                                    if lookupmatch then +                                        head, start, success = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) +                                        if success then +                                            break +                                        end +                                    end +                                else +                                    report_missing_cache(typ,lookupname) +                                end +                            end +                            if start then start = start.prev end +                        else +                            start = start.prev +                        end +                    else +                        start = start.prev +                    end +                else +                    start = start.prev +                end +            end +        else +            local handler = handlers[typ] +            local ns = #subtables +            local start = head -- local ? +            rlmode = 0 -- to be checked ? +            if ns == 1 then -- happens often +                local lookupname = subtables[1] +                local lookupcache = lookuphash[lookupname] +                if not lookupcache then -- also check for empty cache +                    report_missing_cache(typ,lookupname) +                else + +                    local function subrun(start) +                        -- mostly for gsub, gpos would demand a more clever approach +                        local head = start +                        local done = false +                        while start do +                            local id = start.id +                            if id == glyph_code and start.font == font and start.subtype <256 then +                                local a = start[0] +                                if a then +                                    a = (a == attr) and (not attribute or start[a_state] == attribute) +                                else +                                    a = not attribute or start[a_state] == attribute +                                end +                                if a then +                                    local lookupmatch = lookupcache[start.char] +                                    if lookupmatch then +                                        -- sequence kan weg +                                        local ok +                                        head, start, ok = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) +                                        if ok then +                                            done = true +                                        end +                                    end +                                    if start then start = start.next end +                                else +                                    start = start.next +                                end +                            else +                                start = start.next +                            end +                        end +                        if done then +                            success = true +                            return head +                        end +                    end + +                    local function kerndisc(disc) -- we can assume that prev and next are glyphs +                        local prev = disc.prev +                        local next = disc.next +                        if prev and next then +                            prev.next = next +                         -- next.prev = prev +                            local a = prev[0] +                            if a then +                                a = (a == attr) and (not attribute or prev[a_state] == attribute) +                            else +                                a = not attribute or prev[a_state] == attribute +                            end +                            if a then +                                local lookupmatch = lookupcache[prev.char] +                                if lookupmatch then +                                    -- sequence kan weg +                                    local h, d, ok = handler(head,prev,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) +                                    if ok then +                                        done = true +                                        success = true +                                    end +                                end +                            end +                            prev.next = disc +                         -- next.prev = disc +                        end +                        return next +                    end + +                    while start do +                        local id = start.id +                        if id == glyph_code then +                            if start.font == font and start.subtype<256 then +                                local a = start[0] +                                if a then +                                    a = (a == attr) and (not attribute or start[a_state] == attribute) +                                else +                                    a = not attribute or start[a_state] == attribute +                                end +                                if a then +                                    local lookupmatch = lookupcache[start.char] +                                    if lookupmatch then +                                        -- sequence kan weg +                                        local ok +                                        head, start, ok = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) +                                        if ok then +                                            success = true +                                        end +                                    end +                                    if start then start = start.next end +                                else +                                    start = start.next +                                end +                            else +                                start = start.next +                            end +                        elseif id == disc_code then +                            -- mostly for gsub +                            if start.subtype == discretionary_code then +                                local pre = start.pre +                                if pre then +                                    local new = subrun(pre) +                                    if new then start.pre = new end +                                end +                                local post = start.post +                                if post then +                                    local new = subrun(post) +                                    if new then start.post = new end +                                end +                                local replace = start.replace +                                if replace then +                                    local new = subrun(replace) +                                    if new then start.replace = new end +                                end +elseif typ == "gpos_single" or typ == "gpos_pair" then +    kerndisc(start) +                            end +                            start = start.next +                        elseif id == whatsit_code then -- will be function +                            local subtype = start.subtype +                            if subtype == dir_code then +                                local dir = start.dir +                                if     dir == "+TRT" or dir == "+TLT" then +                                    topstack = topstack + 1 +                                    dirstack[topstack] = dir +                                elseif dir == "-TRT" or dir == "-TLT" then +                                    topstack = topstack - 1 +                                end +                                local newdir = dirstack[topstack] +                                if newdir == "+TRT" then +                                    rlmode = -1 +                                elseif newdir == "+TLT" then +                                    rlmode = 1 +                                else +                                    rlmode = rlparmode +                                end +                                if trace_directions then +                                    report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir) +                                end +                            elseif subtype == localpar_code then +                                local dir = start.dir +                                if dir == "TRT" then +                                    rlparmode = -1 +                                elseif dir == "TLT" then +                                    rlparmode = 1 +                                else +                                    rlparmode = 0 +                                end +                                -- one might wonder if the par dir should be looked at, so we might as well drop the next line +                                rlmode = rlparmode +                                if trace_directions then +                                    report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode) +                                end +                            end +                            start = start.next +                        elseif id == math_code then +                            start = end_of_math(start).next +                        else +                            start = start.next +                        end +                    end +                end +            else + +                local function subrun(start) +                    -- mostly for gsub, gpos would demand a more clever approach +                    local head = start +                    local done = false +                    while start do +                        local id = start.id +                        if id == glyph_code and start.id == font and start.subtype <256 then +                            local a = start[0] +                            if a then +                                a = (a == attr) and (not attribute or start[a_state] == attribute) +                            else +                                a = not attribute or start[a_state] == attribute +                            end +                            if a then +                                for i=1,ns do +                                    local lookupname = subtables[i] +                                    local lookupcache = lookuphash[lookupname] +                                    if lookupcache then +                                        local lookupmatch = lookupcache[start.char] +                                        if lookupmatch then +                                            -- we could move all code inline but that makes things even more unreadable +                                            local ok +                                            head, start, ok = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) +                                            if ok then +                                                done = true +                                                break +                                            elseif not start then +                                                -- don't ask why ... shouldn't happen +                                                break +                                            end +                                        end +                                    else +                                        report_missing_cache(typ,lookupname) +                                    end +                                end +                                if start then start = start.next end +                            else +                                start = start.next +                            end +                        else +                            start = start.next +                        end +                    end +                    if done then +                        success = true +                        return head +                    end +                end + +                local function kerndisc(disc) -- we can assume that prev and next are glyphs +                    local prev = disc.prev +                    local next = disc.next +                    if prev and next then +                        prev.next = next +                     -- next.prev = prev +                        local a = prev[0] +                        if a then +                            a = (a == attr) and (not attribute or prev[a_state] == attribute) +                        else +                            a = not attribute or prev[a_state] == attribute +                        end +                        if a then +                            for i=1,ns do +                                local lookupname = subtables[i] +                                local lookupcache = lookuphash[lookupname] +                                if lookupcache then +                                    local lookupmatch = lookupcache[prev.char] +                                    if lookupmatch then +                                        -- we could move all code inline but that makes things even more unreadable +                                        local h, d, ok = handler(head,prev,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) +                                        if ok then +                                            done = true +                                            break +                                        end +                                    end +                                else +                                    report_missing_cache(typ,lookupname) +                                end +                            end +                        end +                        prev.next = disc +                     -- next.prev = disc +                    end +                    return next +                end + +                while start do +                    local id = start.id +                    if id == glyph_code then +                        if start.font == font and start.subtype<256 then +                            local a = start[0] +                            if a then +                                a = (a == attr) and (not attribute or start[a_state] == attribute) +                            else +                                a = not attribute or start[a_state] == attribute +                            end +                            if a then +                                for i=1,ns do +                                    local lookupname = subtables[i] +                                    local lookupcache = lookuphash[lookupname] +                                    if lookupcache then +                                        local lookupmatch = lookupcache[start.char] +                                        if lookupmatch then +                                            -- we could move all code inline but that makes things even more unreadable +                                            local ok +                                            head, start, ok = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) +                                            if ok then +                                                success = true +                                                break +                                            elseif not start then +                                                -- don't ask why ... shouldn't happen +                                                break +                                            end +                                        end +                                    else +                                        report_missing_cache(typ,lookupname) +                                    end +                                end +                                if start then start = start.next end +                            else +                                start = start.next +                            end +                        else +                            start = start.next +                        end +                    elseif id == disc_code then +                        -- mostly for gsub +                        if start.subtype == discretionary_code then +                            local pre = start.pre +                            if pre then +                                local new = subrun(pre) +                                if new then start.pre = new end +                            end +                            local post = start.post +                            if post then +                                local new = subrun(post) +                                if new then start.post = new end +                            end +                            local replace = start.replace +                            if replace then +                                local new = subrun(replace) +                                if new then start.replace = new end +                            end +elseif typ == "gpos_single" or typ == "gpos_pair" then +    kerndisc(start) +                        end +                        start = start.next +                    elseif id == whatsit_code then +                        local subtype = start.subtype +                        if subtype == dir_code then +                            local dir = start.dir +                            if     dir == "+TRT" or dir == "+TLT" then +                                topstack = topstack + 1 +                                dirstack[topstack] = dir +                            elseif dir == "-TRT" or dir == "-TLT" then +                                topstack = topstack - 1 +                            end +                            local newdir = dirstack[topstack] +                            if newdir == "+TRT" then +                                rlmode = -1 +                            elseif newdir == "+TLT" then +                                rlmode = 1 +                            else +                                rlmode = rlparmode +                            end +                            if trace_directions then +                                report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir) +                            end +                        elseif subtype == localpar_code then +                            local dir = start.dir +                            if dir == "TRT" then +                                rlparmode = -1 +                            elseif dir == "TLT" then +                                rlparmode = 1 +                            else +                                rlparmode = 0 +                            end +                            rlmode = rlparmode +                            if trace_directions then +                                report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode) +                            end +                        end +                        start = start.next +                    elseif id == math_code then +                        start = end_of_math(start).next +                    else +                        start = start.next +                    end +                end +            end +        end +        if success then +            done = true +        end +        if trace_steps then -- ? +            registerstep(head) +        end +    end +    return head, done +end + +local function generic(lookupdata,lookupname,unicode,lookuphash) +    local target = lookuphash[lookupname] +    if target then +        target[unicode] = lookupdata +    else +        lookuphash[lookupname] = { [unicode] = lookupdata } +    end +end + +local action = { + +    substitution = generic, +    multiple     = generic, +    alternate    = generic, +    position     = generic, + +    ligature = function(lookupdata,lookupname,unicode,lookuphash) +        local target = lookuphash[lookupname] +        if not target then +            target = { } +            lookuphash[lookupname] = target +        end +        for i=1,#lookupdata do +            local li = lookupdata[i] +            local tu = target[li] +            if not tu then +                tu = { } +                target[li] = tu +            end +            target = tu +        end +        target.ligature = unicode +    end, + +    pair = function(lookupdata,lookupname,unicode,lookuphash) +        local target = lookuphash[lookupname] +        if not target then +            target = { } +            lookuphash[lookupname] = target +        end +        local others = target[unicode] +        local paired = lookupdata[1] +        if others then +            others[paired] = lookupdata +        else +            others = { [paired] = lookupdata } +            target[unicode] = others +        end +    end, + +} + +local function prepare_lookups(tfmdata) + +    local rawdata          = tfmdata.shared.rawdata +    local resources        = rawdata.resources +    local lookuphash       = resources.lookuphash +    local anchor_to_lookup = resources.anchor_to_lookup +    local lookup_to_anchor = resources.lookup_to_anchor +    local lookuptypes      = resources.lookuptypes +    local characters       = tfmdata.characters +    local descriptions     = tfmdata.descriptions + +    -- we cannot free the entries in the descriptions as sometimes we access +    -- then directly (for instance anchors) ... selectively freeing does save +    -- much memory as it's only a reference to a table and the slot in the +    -- description hash is not freed anyway + +    for unicode, character in next, characters do -- we cannot loop over descriptions ! + +        local description = descriptions[unicode] + +        if description then + +            local lookups = description.slookups +            if lookups then +                for lookupname, lookupdata in next, lookups do +                    action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash) +                end +            end + +            local lookups = description.mlookups +            if lookups then +                for lookupname, lookuplist in next, lookups do +                    local lookuptype = lookuptypes[lookupname] +                    for l=1,#lookuplist do +                        local lookupdata = lookuplist[l] +                        action[lookuptype](lookupdata,lookupname,unicode,lookuphash) +                    end +                end +            end + +            local list = description.kerns +            if list then +                for lookup, krn in next, list do  -- ref to glyph, saves lookup +                    local target = lookuphash[lookup] +                    if target then +                        target[unicode] = krn +                    else +                        lookuphash[lookup] = { [unicode] = krn } +                    end +                end +            end + +            local list = description.anchors +            if list then +                for typ, anchors in next, list do -- types +                    if typ == "mark" or typ == "cexit" then -- or entry? +                        for name, anchor in next, anchors do +                            local lookups = anchor_to_lookup[name] +                            if lookups then +                                for lookup, _ in next, lookups do +                                    local target = lookuphash[lookup] +                                    if target then +                                        target[unicode] = anchors +                                    else +                                        lookuphash[lookup] = { [unicode] = anchors } +                                    end +                                end +                            end +                        end +                    end +                end +            end + +        end + +    end + +end + +local function split(replacement,original) +    local result = { } +    for i=1,#replacement do +        result[original[i]] = replacement[i] +    end +    return result +end + +local valid = { +    coverage        = { chainsub = true, chainpos = true, contextsub = true }, +    reversecoverage = { reversesub = true }, +    glyphs          = { chainsub = true, chainpos = true }, +} + +local function prepare_contextchains(tfmdata) +    local rawdata    = tfmdata.shared.rawdata +    local resources  = rawdata.resources +    local lookuphash = resources.lookuphash +    local lookups    = rawdata.lookups +    if lookups then +        for lookupname, lookupdata in next, rawdata.lookups do +            local lookuptype = lookupdata.type +            if lookuptype then +                local rules = lookupdata.rules +                if rules then +                    local format = lookupdata.format +                    local validformat = valid[format] +                    if not validformat then +                        report_prepare("unsupported format %a",format) +                    elseif not validformat[lookuptype] then +                        -- todo: dejavu-serif has one (but i need to see what use it has) +                        report_prepare("unsupported format %a, lookuptype %a, lookupname %a",format,lookuptype,lookupname) +                    else +                        local contexts = lookuphash[lookupname] +                        if not contexts then +                            contexts = { } +                            lookuphash[lookupname] = contexts +                        end +                        local t, nt = { }, 0 +                        for nofrules=1,#rules do +                            local rule         = rules[nofrules] +                            local current      = rule.current +                            local before       = rule.before +                            local after        = rule.after +                            local replacements = rule.replacements +                            local sequence     = { } +                            local nofsequences = 0 +                            -- Eventually we can store start, stop and sequence in the cached file +                            -- but then less sharing takes place so best not do that without a lot +                            -- of profiling so let's forget about it. +                            if before then +                                for n=1,#before do +                                    nofsequences = nofsequences + 1 +                                    sequence[nofsequences] = before[n] +                                end +                            end +                            local start = nofsequences + 1 +                            for n=1,#current do +                                nofsequences = nofsequences + 1 +                                sequence[nofsequences] = current[n] +                            end +                            local stop = nofsequences +                            if after then +                                for n=1,#after do +                                    nofsequences = nofsequences + 1 +                                    sequence[nofsequences] = after[n] +                                end +                            end +                            if sequence[1] then +                                -- Replacements only happen with reverse lookups as they are single only. We +                                -- could pack them into current (replacement value instead of true) and then +                                -- use sequence[start] instead but it's somewhat ugly. +                                nt = nt + 1 +                                t[nt] = { nofrules, lookuptype, sequence, start, stop, rule.lookups, replacements } +                                for unic, _  in next, sequence[start] do +                                    local cu = contexts[unic] +                                    if not cu then +                                        contexts[unic] = t +                                    end +                                end +                            end +                        end +                    end +                else +                    -- no rules +                end +            else +                report_prepare("missing lookuptype for lookupname %a",lookupname) +            end +        end +    end +end + +-- we can consider lookuphash == false (initialized but empty) vs lookuphash == table + +local function featuresinitializer(tfmdata,value) +    if true then -- value then +        -- beware we need to use the topmost properties table +        local rawdata    = tfmdata.shared.rawdata +        local properties = rawdata.properties +        if not properties.initialized then +            local starttime = trace_preparing and os.clock() +            local resources = rawdata.resources +            resources.lookuphash = resources.lookuphash or { } +            prepare_contextchains(tfmdata) +            prepare_lookups(tfmdata) +            properties.initialized = true +            if trace_preparing then +                report_prepare("preparation time is %0.3f seconds for %a",os.clock()-starttime,tfmdata.properties.fullname) +            end +        end +    end +end + +registerotffeature { +    name         = "features", +    description  = "features", +    default      = true, +    initializers = { +        position = 1, +        node     = featuresinitializer, +    }, +    processors   = { +        node     = featuresprocessor, +    } +} + +-- This can be used for extra handlers, but should be used with care! + +otf.handlers = handlers diff --git a/luaotfload-fonts-tfm.lua b/src/luaotfload-fonts-tfm.lua index b9bb1bd..b9bb1bd 100644 --- a/luaotfload-fonts-tfm.lua +++ b/src/luaotfload-fonts-tfm.lua diff --git a/luaotfload-letterspace.lua b/src/luaotfload-letterspace.lua index 7c5a967..20f29f5 100644 --- a/luaotfload-letterspace.lua +++ b/src/luaotfload-letterspace.lua @@ -1,11 +1,14 @@  if not modules then modules = { } end modules ['letterspace'] = { -    version   = "2.4", -    comment   = "companion to luaotfload.lua", +    version   = "2.5", +    comment   = "companion to luaotfload-main.lua",      author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL; adapted by Philipp Gesang",      copyright = "PRAGMA ADE / ConTeXt Development Team",      license   = "see context related readme files"  } +local log                = luaotfload.log +local report             = log.report +  local getmetatable       = getmetatable  local require            = require  local setmetatable       = setmetatable @@ -512,10 +515,10 @@ otffeatures.register {  local initializecompatfontkerning = function (tfmdata, percentage)    local factor = tonumber (percentage)    if not factor then -    logs.names_report ("both", 0, "letterspace", -                       "Invalid argument to letterspace: %s (type %q), " .. -                       "was expecting percentage as Lua number instead.", -                       percentage, type (percentage)) +    report ("both", 0, "letterspace", +            "Invalid argument to letterspace: %s (type %q), " .. +            "was expecting percentage as Lua number instead.", +            percentage, type (percentage))      return    end    return initializefontkerning (tfmdata, factor * 0.01) diff --git a/luaotfload-loaders.lua b/src/luaotfload-loaders.lua index 523dc24..2aa8c7c 100644 --- a/luaotfload-loaders.lua +++ b/src/luaotfload-loaders.lua @@ -1,6 +1,6 @@  if not modules then modules = { } end modules ["loaders"] = { -    version   = "2.4", -    comment   = "companion to luaotfload.lua", +    version   = "2.5", +    comment   = "companion to luaotfload-main.lua",      author    = "Hans Hagen, Khaled Hosny, Elie Roux, Philipp Gesang",      copyright = "PRAGMA ADE / ConTeXt Development Team",      license   = "see context related readme files" diff --git a/luaotfload-override.lua b/src/luaotfload-log.lua index fe70dcb..5698c84 100644 --- a/luaotfload-override.lua +++ b/src/luaotfload-log.lua @@ -1,9 +1,9 @@ -if not modules then modules = { } end modules ['luat-ovr'] = { -    version   = "2.4", -    comment   = "companion to luatex-*.tex", +if not modules then modules = { } end modules ["luaotfload-log"] = { +    version   = "2.5", +    comment   = "companion to Luaotfload",      author    = "Khaled Hosny, Elie Roux, Philipp Gesang",      copyright = "Luaotfload Development Team", -    license   = "GNU GPL v2" +    license   = "GNU GPL v2.0"  }  --[[doc-- @@ -13,22 +13,24 @@ hand, the more efficient approach followed by Context isn’t an option  because we lack a user interface to toggle per-subsystem tracing.  --doc]]-- -local module_name       = "luaotfload" +local module_name       = "luaotfload" --- prefix for messages + +luaotfload              = luaotfload or { } +luaotfload.log          = luaotfload.log or { } +local log               = luaotfload.log  local ioopen            = io.open  local iowrite           = io.write  local lfsisdir          = lfs.isdir  local lfsisfile         = lfs.isfile -local md5sumhexa        = md5.sumhexa  local osdate            = os.date  local ostime            = os.time +local osuuid            = os.uuid  local select            = select  local stringformat      = string.format  local stringsub         = string.sub  local tableconcat       = table.concat -local texio_write_nl    = texio.write_nl  local texiowrite_nl     = texio.write_nl -local texio_write       = texio.write  local texiowrite        = texio.write  local type              = type @@ -50,17 +52,13 @@ local set_loglevel = function (n)      end      return true  end -logs.setloglevel    = set_loglevel -logs.set_loglevel   = set_loglevel -logs.set_log_level  = set_loglevel --- accomodating lazy typists +log.set_loglevel   = set_loglevel  --- unit -> int  local get_loglevel = function ( )      return loglevel  end -logs.getloglevel    = get_loglevel -logs.get_loglevel   = get_loglevel -logs.get_log_level  = get_loglevel +log.get_loglevel   = get_loglevel  local writeln  --- pointer to terminal/log writer  local statusln --- terminal writer that reuses the current line @@ -77,8 +75,7 @@ local choose_logfile = function ( )      if lfsisdir (tmppath) then          local fname          repeat --- ensure that file of that name doesn’t exist -            fname = tmppath .. "/luaotfload-log-" -                            .. stringsub (md5sumhexa (ostime ()), 1, 8) +            fname = tmppath .. "/luaotfload-log-" .. osuuid()          until not lfsisfile (fname)          iowrite (stringformat (log_msg, fname, fname))          return ioopen (fname, "w") @@ -131,9 +128,9 @@ local set_logout = function (s, finalizers)      return finalizers  end -logs.set_logout = set_logout +log.set_logout = set_logout -local log = function (category, fmt, ...) +local basic_logger = function (category, fmt, ...)      local res = { module_name, "|", category, ":" }      if fmt then          res [#res + 1] = stringformat (fmt, ...) @@ -203,7 +200,7 @@ local level_ids = { common  = 1, loading = 2, search  = 3 }  --[[doc-- -    The names_report logger is used more or less all over luaotfload. +    The report() logger is used more or less all over luaotfload.      Its requirements are twofold:      1) Provide two logging channels, the terminal and the log file; @@ -222,12 +219,12 @@ local level_ids = { common  = 1, loading = 2, search  = 3 }      over. (This is a little sub-optimal performance-wise since the      function calls to the logger are executed regardless.) The log      level during a Luatex run can be adjusted by setting the “loglevel” -    field in config.luaotfload, or by calling logs.set_loglevel() as +    field in config.luaotfload, or by calling log.set_loglevel() as      defined above.  --doc]]-- -local names_report = function (mode, lvl, ...) +local report = function (mode, lvl, ...)      if type(lvl) == "string" then          lvl = level_ids[lvl]      end @@ -235,9 +232,9 @@ local names_report = function (mode, lvl, ...)      if loglevel >= lvl then          if mode == "log" then -            log (...) +            basic_logger (...)          elseif mode == "both" and logout ~= "redirect" then -            log (...) +            basic_logger (...)              stdout (writeln, ...)          else              stdout (writeln, ...) @@ -245,7 +242,7 @@ local names_report = function (mode, lvl, ...)      end  end -logs.names_report = names_report +log.report = report  --[[doc-- @@ -269,10 +266,10 @@ logs.names_report = names_report  local status_logger = function (mode, ...)      if mode == "log" then -        log (...) +        basic_logger (...)      else          if mode == "both" and logout ~= "redirect" then -            log (...) +            basic_logger (...)              stdout (statusln, ...)          else              stdout (statusln, ...) @@ -304,7 +301,7 @@ local status_start = function (low, high)      or os.getenv "TERM" == "dumb"      then          status_writer = function (mode, ...) -            names_report (mode, high, ...) +            report (mode, high, ...)          end          return      end @@ -313,7 +310,7 @@ local status_start = function (low, high)          status_writer = status_logger      else          status_writer = function (mode, ...) -            names_report (mode, high, ...) +            report (mode, high, ...)          end      end  end @@ -334,9 +331,9 @@ local status_stop = function (...)      end  end -logs.names_status = function (...) status_writer (...) end -logs.names_status_start = status_start -logs.names_status_stop  = status_stop +log.names_status = function (...) status_writer (...) end +log.names_status_start = status_start +log.names_status_stop  = status_stop  --[[doc-- @@ -351,7 +348,7 @@ logs.names_status_stop  = status_stop  --doc]]--  local texioreporter = function (message) -    names_report("log", 2, message) +    report ("log", 2, message)  end  texio.reporter = texioreporter @@ -389,10 +386,10 @@ if fonts then --- need to be running TeX          if k == "unicodes" then              local glyphlist = resolvers.findfile"luaotfload-glyphlist.lua"              if glyphlist then -                names_report("log", 1, "load", "loading the Adobe glyph list") +                report ("log", 1, "load", "loading the Adobe glyph list")              else                  glyphlist = resolvers.findfile"font-age.lua" -                names_report("both", 0, "load", +                report ("both", 0, "load",                      "loading the extended glyph list from ConTeXt")              end              local unicodes      = dofile(glyphlist) diff --git a/src/luaotfload-main.lua b/src/luaotfload-main.lua new file mode 100644 index 0000000..6616468 --- /dev/null +++ b/src/luaotfload-main.lua @@ -0,0 +1,708 @@ +----------------------------------------------------------------------- +--         FILE:  luaotfload-main.lua +--  DESCRIPTION:  Luatex fontloader initialization +-- REQUIREMENTS:  luatex v.0.78 or later; the lualibs package +--       AUTHOR:  Élie Roux, Khaled Hosny, Philipp Gesang +--      VERSION:  same as Luaotfload +--     MODIFIED:  2014-02-14 22:51:09+0100 +----------------------------------------------------------------------- +-- +--- Note: +--- This file was part of the original luaotfload.dtx and has been +--- converted to a pure Lua file during the transition from Luaotfload +--- version 2.4 to 2.5. Thus, the comments are still in TeX (Latex) +--- markup. + +if not modules then modules = { } end modules ["luaotfload-main"] = { +    version   = "2.5", +    comment   = "fontloader initialization", +    author    = "Hans Hagen, Khaled Hosny, Elie Roux, Philipp Gesang", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "GNU General Public License v. 2.0" +} + + +--[[doc-- + +    This file initializes the system and loads the font loader. To +    minimize potential conflicts between other packages and the code +    imported from \CONTEXT, several precautions are in order. Some of +    the functionality that the font loader expects to be present, like +    raw access to callbacks, are assumed to have been disabled by +    \identifier{luatexbase} when this file is processed. In some cases +    it is possible to trick it by putting dummies into place and +    restoring the behavior from \identifier{luatexbase} after +    initilization. Other cases such as attribute allocation require +    that we hook the functionality from \identifier{luatexbase} into +    locations where they normally wouldn’t be. + +    Anyways we can import the code base without modifications, which is +    due mostly to the extra effort by Hans Hagen to make \LUATEX-Fonts +    self-contained and encapsulate it, and especially due to his +    willingness to incorporate our suggestions. + +--doc]]-- + +local initial_log_level = 0 + +luaotfload                        = luaotfload or { } +local luaotfload                  = luaotfload +luaotfload.log                    = luaotfload.log or { } + +luaotfload.module = { +    name          = "luaotfload-main", +    version       = 2.50000, +    date          = "2014/**/**", +    description   = "OpenType layout system.", +    author        = "Elie Roux & Hans Hagen", +    copyright     = "Elie Roux", +    license       = "GPL v2.0" +} + +local luatexbase       = luatexbase + +local setmetatable     = setmetatable +local type, next       = type, next +local stringlower      = string.lower +local stringformat     = string.format + +local kpsefind_file    = kpse.find_file +local lfsisfile        = lfs.isfile + +local add_to_callback  = luatexbase.add_to_callback +local create_callback  = luatexbase.create_callback +local reset_callback   = luatexbase.reset_callback +local call_callback    = luatexbase.call_callback + +local dummy_function    = function () end --- XXX this will be moved to the luaotfload namespace when we have the init module + +local error, warning, info, log = +    luatexbase.provides_module(luaotfload.module) + +luaotfload.log.tex        = { +    error        = error, +    warning      = warning, +    info         = info, +    log          = log, +} + +--[[doc-- + +     We set the minimum version requirement for \LUATEX to v0.76, +     because the font loader requires recent features like direct +     attribute indexing and \luafunction{node.end_of_math()} that aren’t +     available in earlier versions.\footnote{% +      See Taco’s announcement of v0.76: +      \url{http://comments.gmane.org/gmane.comp.tex.luatex.user/4042} +      and this commit by Hans that introduced those features. +      \url{http://repo.or.cz/w/context.git/commitdiff/a51f6cf6ee087046a2ae5927ed4edff0a1acec1b}. +    } + +--doc]]-- + +local min_luatex_version = 76 + +if tex.luatexversion < min_luatex_version then +    warning ("LuaTeX v%.2f is old, v%.2f or later is recommended.", +             tex.luatexversion  / 100, +             min_luatex_version / 100) +    --- we install a fallback for older versions as a safety +    if not node.end_of_math then +        local math_t          = node.id "math" +        local traverse_nodes  = node.traverse_id +        node.end_of_math = function (n) +            for n in traverse_nodes (math_t, n.next) do +                return n +            end +        end +    end +end + +--[[doc-- + +    \subsection{Module loading} +    We load the files imported from \CONTEXT with this function. It +    automatically prepends the prefix \fileent{luaotfload-} to its +    argument, so we can refer to the files with their actual \CONTEXT +    name. + +--doc]]-- + +local fl_prefix = "luaotfload" -- “luatex” for luatex-plain +local loadmodule = function (name) +    require (fl_prefix .."-"..name) +end + +loadmodule "log.lua"             --- log messages +--loadmodule "parsers.lua"         --- new in 2.5; fonts.conf and syntax +--loadmodule "configuration.lua"   --- configuration options + +local log             = luaotfload.log +local logreport       = log.report + +log.set_loglevel (default_log_level) + +--[[doc-- + +  Before \TeX Live 2013 version, \LUATEX had a bug that made ofm fonts +  fail when called with their extension. There was a side-effect making +  ofm totally unloadable when luaotfload was present. The following +  lines are a patch for this bug. The utility of these lines is +  questionable as they are not necessary since \TeX Live 2013. They +  should be removed in the next version. + +--doc]]-- + +local Cs, P, lpegmatch = lpeg.Cs, lpeg.P, lpeg.match + +local p_dot, p_slash = P".",  P"/" +local p_suffix       = (p_dot * (1 - p_dot - p_slash)^1 * P(-1)) / "" +local p_removesuffix = Cs((p_suffix + 1)^1) + +local find_vf_file = function (name) +    local fullname = kpsefind_file(name, "ovf") +    if not fullname then +        --fullname = kpsefind_file(file.removesuffix(name), "ovf") +        fullname = kpsefind_file(lpegmatch(p_removesuffix, name), "ovf") +    end +    if fullname then +        logreport ("log", 0, "main", +                   "loading virtual font file %s.", fullname) +    end +    return fullname +end + +--[[doc-- + +    \subsection{Preparing the Font Loader} +    We treat the fontloader as a black box so behavior is consistent +    between formats. +    We load the fontloader code directly in the same fashion as the +    Plain format \identifier{luatex-fonts} that is part of Context. +    How this is executed depends on the presence on the +    \emphasis{merged font loader code}. +    In \identifier{luaotfload} this is contained in the file +    \fileent{luaotfload-merged.lua}. +    If this file cannot be found, the original libraries from \CONTEXT +    of which the merged code was composed are loaded instead. +    Since these files are not shipped with Luaotfload, an installation +    of Context is required. +    (Since we pull the fontloader directly from the Context minimals, +    the necessary Context version is likely to be more recent than that +    of other TeX distributions like Texlive.) +    The imported font loader will call \luafunction{callback.register} +    once while reading \fileent{font-def.lua}. +    This is unavoidable unless we modify the imported files, but +    harmless if we make it call a dummy instead. +    However, this problem might vanish if we decide to do the merging +    ourselves, like the \identifier{lualibs} package does. +    With this step we would obtain the freedom to load our own +    overrides in the process right where they are needed, at the cost +    of losing encapsulation. +    The decision on how to progress is currently on indefinite hold. + +--doc]]-- + +local starttime         = os.gettimeofday () +local trapped_register  = callback.register +callback.register       = dummy_function + +--[[doc-- + +    By default, the fontloader requires a number of \emphasis{private +    attributes} for internal use. +    These must be kept consistent with the attribute handling methods +    as provided by \identifier{luatexbase}. +    Our strategy is to override the function that allocates new +    attributes before we initialize the font loader, making it a +    wrapper around \luafunction{luatexbase.new_attribute}.\footnote{% +        Many thanks, again, to Hans Hagen for making this part +        configurable! +    } +    The attribute identifiers are prefixed “\fileent{luaotfload@}” to +    avoid name clashes. + +--doc]]-- + +do +    local new_attribute    = luatexbase.new_attribute +    local the_attributes   = luatexbase.attributes + +    attributes = attributes or { } + +    attributes.private = function (name) +        local attr   = "luaotfload@" .. name --- used to be: “otfl@” +        local number = the_attributes[attr] +        if not number then +            number = new_attribute(attr) +        end +        return number +    end +end + +--[[doc-- + +    These next lines replicate the behavior of +    \fileent{luatex-fonts.lua}. + +--doc]]-- + +local context_environment = { } + +local push_namespaces = function () +    logreport ("log", 1, "main", "push namespace for font loader") +    local normalglobal = { } +    for k, v in next, _G do +        normalglobal[k] = v +    end +    return normalglobal +end + +local pop_namespaces = function (normalglobal, isolate) +    if normalglobal then +        local _G = _G +        local mode = "non-destructive" +        if isolate then mode = "destructive" end +        logreport ("log", 1, "main", "pop namespace from font loader -- " .. mode) +        for k, v in next, _G do +            if not normalglobal[k] then +                context_environment[k] = v +                if isolate then +                    _G[k] = nil +                end +            end +        end +        for k, v in next, normalglobal do +            _G[k] = v +        end +        -- just to be sure: +        setmetatable(context_environment,_G) +    else +        logreport ("both", 0, "main", +                   "irrecoverable error during pop_namespace: no globals to restore") +        os.exit() +    end +end + +luaotfload.context_environment  = context_environment +luaotfload.push_namespaces      = push_namespaces +luaotfload.pop_namespaces       = pop_namespaces + +local our_environment = push_namespaces() + +--[[doc-- + +    The font loader requires that the attribute with index zero be +    zero. We happily oblige. +    (Cf. \fileent{luatex-fonts-nod.lua}.) + +--doc]]-- + +tex.attribute[0] = 0 + +--[[doc-- + +    Now that things are sorted out we can finally load the fontloader. + +--doc]]-- + +loadmodule "fontloader.lua" +---loadmodule"font-odv.lua" --- <= Devanagari support from Context + +if fonts then + +    if not fonts._merge_loaded_message_done_ then +        logreport ("log", 5, "main", [["I am using the merged fontloader here.]]) +        logreport ("log", 5, "main", [[ If you run into problems or experience unexpected]]) +        logreport ("log", 5, "main", [[ behaviour, and if you have ConTeXt installed you can try]]) +        logreport ("log", 5, "main", [[ to delete the file 'luaotfload-fontloader.lua' as I might]]) +        logreport ("log", 5, "main", [[ then use the possibly updated libraries. The merged]]) +        logreport ("log", 5, "main", [[ version is not supported as it is a frozen instance.]]) +        logreport ("log", 5, "main", [[ Problems can be reported to the ConTeXt mailing list."]]) +    end +    fonts._merge_loaded_message_done_ = true + +else--- the loading sequence is known to change, so this might have to +    --- be updated with future updates! +    --- do not modify it though unless there is a change to the merged +    --- package! +    loadmodule("l-lua.lua") +    loadmodule("l-lpeg.lua") +    loadmodule("l-function.lua") +    loadmodule("l-string.lua") +    loadmodule("l-table.lua") +    loadmodule("l-io.lua") +    loadmodule("l-file.lua") +    loadmodule("l-boolean.lua") +    loadmodule("l-math.lua") +    loadmodule("util-str.lua") +    loadmodule('luatex-basics-gen.lua') +    loadmodule('data-con.lua') +    loadmodule('luatex-basics-nod.lua') +    loadmodule('font-ini.lua') +    loadmodule('font-con.lua') +    loadmodule('luatex-fonts-enc.lua') +    loadmodule('font-cid.lua') +    loadmodule('font-map.lua') +    loadmodule('luatex-fonts-syn.lua') +    loadmodule('luatex-fonts-tfm.lua') +    loadmodule('font-oti.lua') +    loadmodule('font-otf.lua') +    loadmodule('font-otb.lua') +    loadmodule('luatex-fonts-inj.lua')  --> since 2014-01-07, replaces node-inj.lua +    loadmodule('font-ota.lua') +    loadmodule('luatex-fonts-otn.lua')  --> since 2014-01-07, replaces font-otn.lua +    loadmodule('font-otp.lua')          --> since 2013-04-23 +    loadmodule('luatex-fonts-lua.lua') +    loadmodule('font-def.lua') +    loadmodule('luatex-fonts-def.lua') +    loadmodule('luatex-fonts-ext.lua') +    loadmodule('luatex-fonts-cbk.lua') +end --- non-merge fallback scope + +--[[doc-- + +    Here we adjust the globals created during font loader +    initialization. If the second argument to +    \luafunction{pop_namespaces()} is \verb|true| this will restore the +    state of \luafunction{_G}, eliminating every global generated since +    the last call to \luafunction{push_namespaces()}. At the moment we +    see no reason to do this, and since the font loader is considered +    an essential part of \identifier{luatex} as well as a very well +    organized piece of code, we happily concede it the right to add to +    \luafunction{_G} if needed. + +--doc]]-- + +pop_namespaces(our_environment, false)-- true) + +logreport ("both", 0, "main", +           "fontloader loaded in %0.3f seconds", os.gettimeofday()-starttime) + +--[[doc-- + +    \subsection{Callbacks} +    After the fontloader is ready we can restore the callback trap from +    \identifier{luatexbase}. + +--doc]]-- + +callback.register = trapped_register + +--[[doc-- + +    We do our own callback handling with the means provided by +    luatexbase. +    Note: \luafunction{pre_linebreak_filter} and +    \luafunction{hpack_filter} are coupled in \CONTEXT in the concept +    of \emphasis{node processor}. + +--doc]]-- + +add_to_callback("pre_linebreak_filter", +                nodes.simple_font_handler, +                "luaotfload.node_processor", +                1) +add_to_callback("hpack_filter", +                nodes.simple_font_handler, +                "luaotfload.node_processor", +                1) +add_to_callback("find_vf_file", +                find_vf_file, "luaotfload.find_vf_file") + +loadmodule "override.lua"   --- load glyphlist on demand + +--[[doc-- + +    Now we load the modules written for \identifier{luaotfload}. + +--doc]]-- + +loadmodule "parsers.lua"         --- fonts.conf and syntax +loadmodule "configuration.lua"   --- configuration options + +if not config.actions.apply_defaults () then +    logreport ("log", 0, "load", "Configuration unsuccessful.") +end + +loadmodule "loaders.lua"         --- Type1 font wrappers +loadmodule "database.lua"        --- Font management. +loadmodule "colors.lua"          --- Per-font colors. + +if not config.actions.reconfigure () then +    logreport ("log", 0, "load", "Post-configuration hooks failed.") +end + +--[[doc-- + +    Relying on the \verb|name:| resolver for everything has been the +    source of permanent trouble with the database. +    With the introduction of the new syntax parser we now have enough +    granularity to distinguish between the \XETEX emulation layer and +    the genuine \verb|name:| and \verb|file:| lookups of \LUATEX-Fonts. +    Another benefit is that we can now easily plug in or replace new +    lookup behaviors if necessary. +    The name resolver remains untouched, but it calls +    \luafunction{fonts.names.resolve()} internally anyways (see +    \fileent{luaotfload-database.lua}). + +--doc]]-- + +local filesuffix          = file.suffix +local fileremovesuffix    = file.removesuffix +local request_resolvers   = fonts.definers.resolvers +local formats             = fonts.formats +local names               = fonts.names +formats.ofm               = "type1" + +fonts.encodings.known     = fonts.encodings.known or { } + +--[[doc-- + +    \identifier{luaotfload} promises easy access to system fonts. +    Without additional precautions, this cannot be achieved by +    \identifier{kpathsea} alone, because it searches only the +    \fileent{texmf} directories by default. +    Although it is possible for \identifier{kpathsea} to include extra +    paths by adding them to the \verb|OSFONTDIR| environment variable, +    this is still short of the goal »\emphasis{it just works!}«. +    When building the font database \identifier{luaotfload} scans +    system font directories anyways, so we already have all the +    information for looking sytem fonts. +    With the release version 2.2 the file names are indexed in the +    database as well and we are ready to resolve \verb|file:| lookups +    this way. +    Thus we no longer need to call the \identifier{kpathsea} library in +    most cases when looking up font files, only when generating the +    database, and when verifying the existence of a file in the +    \fileent{texmf} tree. + +--doc]]-- + +local resolve_file        = names.font_file_lookup + +local file_resolver = function (specification) +    local name    = resolve_file (specification.name) +    local suffix  = filesuffix(name) +    if formats[suffix] then +        specification.forced      = stringlower (suffix) +        specification.forcedname  = file.removesuffix(name) +    else +        specification.name = name +    end +end + +request_resolvers.file = file_resolver + +--[[doc-- + +    We classify as \verb|anon:| those requests that have neither a +    prefix nor brackets. According to Khaled\footnote{% +        \url{https://github.com/phi-gamma/luaotfload/issues/4#issuecomment-17090553}. +    } +    they are the \XETEX equivalent of a \verb|name:| request, so we +    will be treating them as such. + +--doc]]-- + +--request_resolvers.anon = request_resolvers.name + +--[[doc-- + +    There is one drawback, though. +    This syntax is also used for requesting fonts in \identifier{Type1} +    (\abbrev{tfm}, \abbrev{ofm}) format. +    These are essentially \verb|file:| lookups and must be caught +    before the \verb|name:| resolver kicks in, lest they cause the +    database to update. +    Even if we were to require the \verb|file:| prefix for all +    \identifier{Type1} requests, tests have shown that certain fonts +    still include further fonts (e.~g. \fileent{omlgcb.ofm} will ask +    for \fileent{omsecob.tfm}) \emphasis{using the old syntax}. +    For this reason, we introduce an extra check with an early return. + +--doc]]-- + +local type1_formats = { "tfm", "ofm", "TFM", "OFM", } + +request_resolvers.anon = function (specification) +    local name = specification.name +    for i=1, #type1_formats do +        local format = type1_formats[i] +        if resolvers.findfile(name, format) then +            specification.forcedname = file.addsuffix(name, format) +            specification.forced     = format +            return +        end +    end +    --- under some weird circumstances absolute paths get +    --- passed to the definer; we have to catch them +    --- before the name: resolver misinterprets them. +    name = specification.specification +    local exists, _ = lfsisfile(name) +    if exists then --- garbage; we do this because we are nice, +                   --- not because it is correct +        logreport ("log", 1, "load", "file %q exists", name) +        logreport ("log", 1, "load", +                   "... overriding borked anon: lookup with path: lookup") +        specification.name = name +        request_resolvers.path(specification) +        return +    end +    request_resolvers.name(specification) +end + +--[[doc-- + +    Prior to version 2.2, \identifier{luaotfload} did not distinguish +    \verb|file:| and \verb|path:| lookups, causing complications with +    the resolver. +    Now we test if the requested name is an absolute path in the file +    system, otherwise we fall back to the \verb|file:| lookup. + +--doc]]-- + +request_resolvers.path = function (specification) +    local name       = specification.name +    local exists, _  = lfsisfile(name) +    if not exists then -- resort to file: lookup +        logreport ("log", 0, "load", +                   "path lookup of %q unsuccessful, falling back to file:", +                   name) +        file_resolver (specification) +    else +        local suffix = filesuffix (name) +        if formats[suffix] then +            specification.forced      = stringlower (suffix) +            specification.name        = file.removesuffix(name) +            specification.forcedname  = name +        else +            specification.name = name +        end +    end +end + +--[[doc-- + +    {\bfseries EXPERIMENTAL}: +    \identifier{kpse}-only resolver, for those who can do without +    system fonts. + +--doc]]-- + +request_resolvers.kpse = function (specification) +    local name       = specification.name +    local suffix     = filesuffix(name) +    if suffix and formats[suffix] then +        name = file.removesuffix(name) +        if resolvers.findfile(name, suffix) then +            specification.forced       = stringlower (suffix) +            specification.forcedname   = name +            return +        end +    end +    for t, format in next, formats do --- brute force +        if kpse.find_file (name, format) then +            specification.forced = t +            specification.name   = name +            return +        end +    end +end + +--[[doc-- + +    The \verb|name:| resolver. + +--doc]]-- + +--- fonts.names.resolvers.name -- Customized version of the +--- generic name resolver. + +request_resolvers.name = function (specification) +    local resolver = names.resolve_cached +    if config.luaotfload.run.resolver == "normal" then +        resolver = names.resolve_name +    end +    local resolved, subfont = resolver (specification) +    if resolved then +        logreport ("log", 0, "load", "Lookup/name: %q -> \"%s%s\"", +                   specification.name, +                   resolved, +                   subfont and stringformat ("(%d)", subfont) or "") +        specification.resolved   = resolved +        specification.sub        = subfont +        specification.forced     = stringlower (filesuffix (resolved) or "") +        specification.forcedname = resolved +        specification.name       = fileremovesuffix (resolved) +    else +        file_resolver (specification) +    end +end + +--[[doc-- + +    Also {\bfseries EXPERIMENTAL}: custom file resolvers via callback. + +--doc]]-- +create_callback("luaotfload.resolve_font", "simple", dummy_function) + +request_resolvers.my = function (specification) +    call_callback("luaotfload.resolve_font", specification) +end + +--[[doc-- + +    We create a callback for patching fonts on the fly, to be used by +    other packages. +    It initially contains the empty function that we are going to +    override below. + +--doc]]-- + +create_callback("luaotfload.patch_font", "simple", dummy_function) + +--[[doc-- + +    \subsection{\CONTEXT override} +    \label{define-font} +    We provide a simplified version of the original font definition +    callback. + +--doc]]-- + +local read_font_file = fonts.definers.read + +local definers = { +    generic = read_font_file, +    --- spec -> size -> id -> tmfdata +    patch = function (specification, size, id) +        local tfmdata = read_font_file (specification, size, id) +        if type (tfmdata) == "table" and tfmdata.shared then +            --- We need to test for the “shared” field here +            --- or else the fontspec capheight callback will +            --- operate on tfm fonts. +            call_callback ("luaotfload.patch_font", tfmdata, specification) +        end +        return tfmdata +    end, +} + +reset_callback "define_font" + +--[[doc-- + +    Finally we register the callbacks. + +--doc]]-- + +local definer = config.luaotfload.run.definer +add_to_callback ("define_font", definers[definer], "luaotfload.define_font", 1) + +loadmodule "features.lua"     --- font request and feature handling +loadmodule "letterspace.lua"  --- extra character kerning +loadmodule "auxiliary.lua"    --- additional high-level functionality + +luaotfload.aux.start_rewrite_fontname () --- to be migrated to fontspec + +-- vim:tw=79:sw=4:ts=4:et diff --git a/src/luaotfload-override.lua b/src/luaotfload-override.lua new file mode 100644 index 0000000..b75530b --- /dev/null +++ b/src/luaotfload-override.lua @@ -0,0 +1,52 @@ +if not modules then modules = { } end modules ["luaotfload-override"] = { +    version   = "2.5", +    comment   = "companion to Luaotfload", +    author    = "Khaled Hosny, Elie Roux, Philipp Gesang", +    copyright = "Luaotfload Development Team", +    license   = "GNU GPL v2.0" +} + +local findfile      = resolvers.findfile +local encodings     = fonts.encodings + +local log           = luaotfload.log +local report        = log.report + +--[[doc-- + +    Adobe Glyph List. +    ------------------------------------------------------------------- + +    Context provides a somewhat different font-age.lua from an unclear +    origin. Unfortunately, the file name it reads from is hard-coded +    in font-enc.lua, so we have to replace the entire table. + +    This shouldn’t cause any complications. Due to its implementation +    the glyph list will be loaded upon loading a OTF or TTF for the +    first time during a TeX run. (If one sticks to TFM/OFM then it is +    never read at all.) For this reason we can install a metatable that +    looks up the file of our choosing and only falls back to the +    Context one in case it cannot be found. + +--doc]]-- + +encodings.agl = { } + +setmetatable(fonts.encodings.agl, { __index = function (t, k) +    if k ~= "unicodes" then +        return nil +    end +    local glyphlist = findfile "luaotfload-glyphlist.lua" +    if glyphlist then +        report ("log", 1, "load", "loading the Adobe glyph list") +    else +        glyphlist = findfile "font-age.lua" +        report ("both", 0, "load", +                "loading the extended glyph list from ConTeXt") +    end +    local unicodes = dofile(glyphlist) +    encodings.agl  = { unicodes = unicodes } +    return unicodes +end }) + +-- vim:tw=71:sw=4:ts=4:expandtab diff --git a/src/luaotfload-parsers.lua b/src/luaotfload-parsers.lua new file mode 100644 index 0000000..3eeb614 --- /dev/null +++ b/src/luaotfload-parsers.lua @@ -0,0 +1,701 @@ +#!/usr/bin/env texlua +------------------------------------------------------------------------------- +--         FILE:  luaotfload-parsers.lua +--  DESCRIPTION:  various lpeg-based parsers used in Luaotfload +-- REQUIREMENTS:  Luaotfload > 2.4 +--       AUTHOR:  Philipp Gesang (Phg), <phg42.2a@gmail.com> +--      VERSION:  same as Luaotfload +--      CREATED:  2014-01-14 10:15:20+0100 +------------------------------------------------------------------------------- +-- + +if not modules then modules = { } end modules ['luaotfload-parsers'] = { +  version   = "2.5", +  comment   = "companion to luaotfload-main.lua", +  author    = "Philipp Gesang", +  copyright = "Luaotfload Development Team", +  license   = "GNU GPL v2.0" +} + +luaotfload              = luaotfload or { } +luaotfload.parsers      = luaotfload.parsers or { } +local parsers           = luaotfload.parsers + +local rawset            = rawset + +local lpeg              = require "lpeg" +local P, R, S           = lpeg.P, lpeg.R, lpeg.S +local lpegmatch         = lpeg.match +local C, Cc, Cf         = lpeg.C, lpeg.Cc, lpeg.Cf +local Cg, Cmt, Cs, Ct   = lpeg.Cg, lpeg.Cmt, lpeg.Cs, lpeg.Ct + +local kpse              = kpse +local kpseexpand_path   = kpse.expand_path +local kpsereadable_file = kpse.readable_file + +local file              = file +local filejoin          = file.join +local filedirname       = file.dirname + +local io                = io +local ioopen            = io.open + +local log               = luaotfload.log +local logreport         = log.report + +local string            = string +local stringsub         = string.sub +local stringfind        = string.find +local stringlower       = string.lower + +local mathceil          = math.ceil + +local lfs               = lfs +local lfsisfile         = lfs.isfile +local lfsisdir          = lfs.isdir + +------------------------------------------------------------------------------- +---                         COMMON PATTERNS +------------------------------------------------------------------------------- + +local dot               = P"." +local colon             = P":" +local semicolon         = P";" +local comma             = P"," +local noncomma          = 1 - comma +local slash             = P"/" +local backslash         = P"\\" +local equals            = P"=" +local dash              = P"-" +local gartenzaun        = P"#" +local lbrk, rbrk        = P"[", P"]" +local squote            = P"'" +local dquote            = P"\"" + +local newline           = P"\n" +local returnchar        = P"\r" +local spacing           = S" \t\v" +local linebreak         = S"\n\r" +local whitespace        = spacing + linebreak +local ws                = spacing^0 +local xmlws             = whitespace^1 +local eol               = P"\n\r" + P"\r\n" + linebreak + +local digit             = R"09" +local alpha             = R("az", "AZ") +local anum              = alpha + digit +local decimal           = digit^1 * (dot * digit^0)^-1 + +------------------------------------------------------------------------------- +---                                FONTCONFIG +------------------------------------------------------------------------------- + +--[[doc-- + +  For fonts installed on the operating system, there are several +  options to make Luaotfload index them: + +   - If OSFONTDIR is set (which is the case under windows by default +     but not on the other OSs), it scans it at the same time as the +     texmf tree, in the function scan_texmf_fonts(). + +   - Otherwise +     - under Windows and Mac OSX, we take a look at some hardcoded +       directories, +     - under Unix, it reads /etc/fonts/fonts.conf and processes the +       directories specified there. + +  This means that if you have fonts in fancy directories, you need to +  set them in OSFONTDIR. + +  Beware: OSFONTDIR is a kpathsea variable, so fonts found in these +  paths, though technically system fonts, are registered in the +  category “texmf”, not “system”. This may have consequences for the +  lookup order when a font file (or a font with the same name +  information) is located in both the system and the texmf tree. + +--doc]]-- + +local tag_name          = C(alpha^1) +local comment           = P"<!--" * (1 - P"--")^0 * P"-->" + +---> header specifica +local xml_declaration   = P"<?xml" * (1 - P"?>")^0 * P"?>" +local xml_doctype       = P"<!DOCTYPE" * xmlws +                        * "fontconfig" * (1 - P">")^0 * P">" +local header            = xml_declaration^-1 +                        * (xml_doctype + comment + xmlws)^0 + +---> enforce root node +local root_start        = P"<"  * xmlws^-1 * P"fontconfig" * xmlws^-1 * P">" +local root_stop         = P"</" * xmlws^-1 * P"fontconfig" * xmlws^-1 * P">" + +local dquote, squote    = P[["]], P"'" +local xml_namestartchar = S":_" + alpha --- ascii only, funk the rest +local xml_namechar      = S":._" + alpha + digit +local xml_name          = xmlws^-1 +                        * C(xml_namestartchar * xml_namechar^0) +local xml_attvalue      = dquote * C((1 - S[[%&"]])^1) * dquote * xmlws^-1 +                        + squote * C((1 - S[[%&']])^1) * squote * xmlws^-1 +local xml_attr          = Cg(xml_name * P"=" * xml_attvalue) +local xml_attr_list     = Cf(Ct"" * xml_attr^1, rawset) + +--[[doc-- +      scan_node creates a parser for a given xml tag. +--doc]]-- +--- string -> bool -> lpeg_t +local scan_node = function (tag) +    --- Node attributes go into a table with the index “attributes” +    --- (relevant for “prefix="xdg"” and the likes). +    local p_tag = P(tag) +    local with_attributes   = P"<" * p_tag +                            * Cg(xml_attr_list, "attributes")^-1 +                            * xmlws^-1 +                            * P">" +    local plain             = P"<" * p_tag * xmlws^-1 * P">" +    local node_start        = plain + with_attributes +    local node_stop         = P"</" * p_tag * xmlws^-1 * P">" +    --- there is no nesting, the earth is flat ... +    local node              = node_start +                            * Cc(tag) * C(comment + (1 - node_stop)^1) +                            * node_stop +    return Ct(node) -- returns {string, string [, attributes = { key = val }] } +end + +--[[doc-- +      At the moment, the interesting tags are “dir” for +      directory declarations, and “include” for including +      further configuration files. + +      spec: http://freedesktop.org/software/fontconfig/fontconfig-user.html +--doc]]-- +local include_node        = scan_node"include" +local dir_node            = scan_node"dir" + +local element             = dir_node +                          + include_node +                          + comment         --> ignore +                          + P(1-root_stop)  --> skip byte + +local root                = root_start * Ct(element^0) * root_stop +local p_cheapxml          = header * root + +--lpeg.print(p_cheapxml) ---> 757 rules with v0.10 + +--[[doc-- +      fonts_conf_scanner() handles configuration files. +      It is called on an abolute path to a config file (e.g. +      /home/luser/.config/fontconfig/fonts.conf) and returns a list +      of the nodes it managed to extract from the file. +--doc]]-- +--- string -> path list +local fonts_conf_scanner = function (path) +  local fh = ioopen(path, "r") +  if not fh then +    logreport("both", 3, "db", "Cannot open fontconfig file %s.", path) +    return +  end +  local raw = fh:read"*all" +  fh:close() + +  local confdata = lpegmatch(p_cheapxml, raw) +  if not confdata then +    logreport("both", 3, "db", "Cannot scan fontconfig file %s.", path) +    return +  end +  return confdata +end + +local p_conf   = P".conf" * P(-1) +local p_filter = (1 - p_conf)^1 * p_conf + +local conf_filter = function (path) +  if lpegmatch (p_filter, path) then +    return true +  end +  return false +end + +--[[doc-- +      read_fonts_conf_indeed() is called with seven arguments; the +      latter three are tables that represent the state and are +      always returned. +      The first four are +          · the path to the file +          · the expanded $HOME +          · the expanded $XDG_CONFIG_HOME +          · the expanded $XDG_DATA_HOME +--doc]]-- +--- string -> string -> string -> tab -> tab -> (tab * tab * tab) +local read_fonts_conf_indeed +read_fonts_conf_indeed = function (start, home, xdg_config_home, +                                   xdg_data_home, +                                   acc, done, dirs_done, +                                   find_files) + +  local paths = fonts_conf_scanner(start) +  if not paths then --- nothing to do +    return acc, done, dirs_done +  end + +  for i=1, #paths do +    local pathobj = paths[i] +    local kind, path = pathobj[1], pathobj[2] +    local attributes = pathobj.attributes + +    if kind == "dir" then +      if attributes and attributes.prefix == "xdg" then +        path = filejoin(xdg_data_home, path) +      end +      if stringsub(path, 1, 1) == "~" then +        path = filejoin(home, stringsub(path, 2)) +      end +      --- We exclude paths with texmf in them, as they should be +      --- found anyway; also duplicates are ignored by checking +      --- if they are elements of dirs_done. +      --- +      --- FIXME does this mean we cannot access paths from +      --- distributions (e.g. Context minimals) installed +      --- separately? +      if not (stringfind(path, "texmf") or dirs_done[path]) then +        acc[#acc+1] = path +        dirs_done[path] = true +      end + +    elseif kind == "include" then +      if attributes and attributes.prefix == "xdg" then +        path = filejoin(xdg_config_home, path) +      end +      --- here the path can be four things: a directory or a file, +      --- in absolute or relative path. +      if stringsub(path, 1, 1) == "~" then +        path = filejoin(home, stringsub(path, 2)) +      elseif --- if the path is relative, we make it absolute +        not ( lfsisfile(path) or lfsisdir(path) ) +        then +          path = filejoin(filedirname(start), path) +        end +        if  lfsisfile(path) +          and kpsereadable_file(path) +          and not done[path] +          then +            --- we exclude path with texmf in them, as they should +            --- be found otherwise +            acc = read_fonts_conf_indeed( +            path, home, xdg_config_home, xdg_data_home, +            acc,  done, dirs_done) +          elseif lfsisdir(path) then --- arrow code ahead +            local config_files = find_files (path, conf_filter) +            for _, filename in next, config_files do +              if not done[filename] then +                acc = read_fonts_conf_indeed( +                filename, home, xdg_config_home, xdg_data_home, +                acc,      done, dirs_done) +              end +            end +          end --- match “kind” +        end --- iterate paths +      end + +      --inspect(acc) +      --inspect(done) +      return acc, done, dirs_done +    end --- read_fonts_conf_indeed() + +--[[doc-- +      read_fonts_conf() sets up an accumulator and two sets +      for tracking what’s been done. + +      Also, the environment variables HOME, XDG_DATA_HOME and +      XDG_CONFIG_HOME -- which are constants anyways -- are expanded +      so we don’t have to repeat that over and over again as with the +      old parser. Now they’re just passed on to every call of +      read_fonts_conf_indeed(). +--doc]]-- + +--- list -> (string -> function option -> string list) -> list + +local read_fonts_conf = function (path_list, find_files) +  local home      = kpseexpand_path"~" --- could be os.getenv"HOME" +  local xdg_config_home  = kpseexpand_path"$XDG_CONFIG_HOME" +  if xdg_config_home == "" then xdg_config_home = filejoin(home, ".config") end +  local xdg_data_home  = kpseexpand_path"$XDG_DATA_HOME" +  if xdg_data_home == "" then xdg_data_home = filejoin(home, ".local/share") end +  local acc       = { } ---> list: paths collected +  local done      = { } ---> set:  files inspected +  local dirs_done = { } ---> set:  dirs in list +  for i=1, #path_list do --- we keep the state between files +    acc, done, dirs_done = read_fonts_conf_indeed( +                                path_list[i], home, xdg_config_home, +                                xdg_data_home, +                                acc, done, dirs_done, +                                find_files) +  end +  return acc +end + +luaotfload.parsers.read_fonts_conf = read_fonts_conf + + + +------------------------------------------------------------------------------- +---                               MISC PARSERS +------------------------------------------------------------------------------- + + +local trailingslashes   = slash^1 * P(-1) +local stripslashes      = C((1 - trailingslashes)^0) +parsers.stripslashes    = stripslashes + +local splitcomma        = Ct((C(noncomma^1) + comma)^1) +parsers.splitcomma      = splitcomma + + + +------------------------------------------------------------------------------- +---                              FONT REQUEST +------------------------------------------------------------------------------- + + +--[[doc------------------------------------------------------------------------ + +    The luaotfload font request syntax (see manual) +    has a canonical form: + +        \font<csname>=<prefix>:<identifier>:<features> + +    where +      <csname> is the control sequence that activates the font +      <prefix> is either “file” or “name”, determining the lookup +      <identifer> is either a file name (no path) or a font +                  name, depending on the lookup +      <features> is a list of switches or options, separated by +                 semicolons or commas; a switch is of the form “+” foo +                 or “-” foo, options are of the form lhs “=” rhs + +    however, to ensure backward compatibility we also have +    support for Xetex-style requests. + +    for the Xetex emulation see: +    · The XeTeX Reference Guide by Will Robertson, 2011 +    · The XeTeX Companion by Michel Goosens, 2010 +    · About XeTeX by Jonathan Kew, 2005 + + +    caueat emptor. + +        the request is parsed into one of **four** different lookup +        categories: the regular ones, file and name, as well as the +        Xetex compatibility ones, path and anon. (maybe a better choice +        of identifier would be “ambig”.) + +        according to my reconstruction, the correct chaining of the +        lookups for each category is as follows: + +        | File -> ( db/filename lookup ) + +        | Name -> ( db/name lookup, +                    db/filename lookup ) + +        | Path -> ( db/filename lookup, +                    fullpath lookup ) + +        | Anon -> ( kpse.find_file(),     // <- for tfm, ofm +                    db/name lookup, +                    db/filename lookup, +                    fullpath lookup ) + +        caching of successful lookups is essential. we now as of v2.2 +        have a lookup cache that is stored in a separate file. it +        pertains only to name: lookups, and is described in more detail +        in luaotfload-database.lua. + +------------------------------------------------------------------------------- + +    One further incompatibility between Xetex and Luatex-Fonts consists +    in their option list syntax: apparently, Xetex requires key-value +    options to be prefixed by a "+" (ascii “plus”) character. We +    silently accept this as well, dropping the first byte if it is a +    plus or minus character. + +    Reference: https://github.com/lualatex/luaotfload/issues/79#issuecomment-18104483 + +--doc]]------------------------------------------------------------------------ + + +local handle_normal_option = function (key, val) +    val = stringlower(val) +    --- the former “toboolean()” handler +    if val == "true"  then +        val = true +    elseif val == "false" then +        val = false +    end +    return key, val +end + +--[[doc-- + +    Xetex style indexing begins at zero which we just increment before +    passing it along to the font loader.  Ymmv. + +--doc]]-- + +local handle_xetex_option = function (key, val) +    val = stringlower(val) +    local numeric = tonumber(val) --- decimal only; keeps colors intact +    if numeric then --- ugh +        if  mathceil(numeric) == numeric then -- integer, possible index +            val = tostring(numeric + 1) +        end +    elseif val == "true"  then +        val = true +    elseif val == "false" then +        val = false +    end +    return key, val +end + +--[[doc-- + +    Instead of silently ignoring invalid options we emit a warning to +    the log. + +    Note that we have to return a pair to please rawset(). This creates +    an entry on the resulting features hash which will later be removed +    during set_default_features(). + +--doc]]-- + +local handle_invalid_option = function (opt) +    logreport("log", 0, "load", "font option %q unknown.", opt) +    return "", false +end + +--[[doc-- + +    Dirty test if a file: request is actually a path: lookup; don’t +    ask! Note this fails on Windows-style absolute paths. These will +    *really* have to use the correct request. + +--doc]]-- + +local check_garbage = function (_,i, garbage) +    if stringfind(garbage, "/") then +        logreport("log", 0, "load",  --- ffs use path! +                  "warning: path in file: lookups is deprecated; ") +        logreport("log", 0, "load", "use bracket syntax instead!") +        logreport("log", 0, "load", +                  "position: %d; full match: %q", +                  i, garbage) +        return true +    end +    return false +end + +local featuresep = comma + semicolon + +--- modifiers --------------------------------------------------------- +--[[doc-- +    The slash notation: called “modifiers” (Kew) or “font options” +    (Robertson, Goosens) +    we only support the shorthands for italic / bold / bold italic +    shapes, as well as setting optical size, the rest is ignored. +--doc]]-- +local style_modifier    = (P"BI" + P"IB" + P"bi" + P"ib" + S"biBI") +                        / stringlower +local size_modifier     = S"Ss" * P"="    --- optical size +                        * Cc"optsize" * C(decimal) +local other_modifier    = P"AAT" + P"aat" --- apple stuff;  unsupported +                        + P"ICU" + P"icu" --- not applicable +                        + P"GR"  + P"gr"  --- sil stuff;    unsupported +local garbage_modifier  = ((1 - colon - slash)^0 * Cc(false)) +local modifier          = slash * (other_modifier      --> ignore +                                 + Cs(style_modifier)  --> collect +                                 + Ct(size_modifier)   --> collect +                                 + garbage_modifier)   --> warn +local modifier_list     = Cg(Ct(modifier^0), "modifiers") + +--- lookups ----------------------------------------------------------- +local fontname          = C((1-S":(/")^1)  --- like luatex-fonts +local unsupported       = Cmt((1-S":(")^1, check_garbage) +local prefixed          = P"name:" * ws * Cg(fontname, "name") +--- initially we intended file: to emulate the behavior of +--- luatex-fonts, i.e. no paths allowed. after all, we do have XeTeX +--- emulation with the path lookup and it interferes with db lookups. +--- turns out fontspec and other widely used packages rely on file: +--- with paths already, so we’ll add a less strict rule here.  anyways, +--- we’ll emit a warning. +                        + P"file:" * ws * Cg(unsupported, "path") +                        + P"file:" * ws * Cg(fontname, "file") +--- EXPERIMENTAL: kpse lookup +                        + P"kpse:" * ws * Cg(fontname, "kpse") +--- EXPERIMENTAL: custom lookup +                        + P"my:" * ws * Cg(fontname, "my") +local unprefixed        = Cg(fontname, "anon") +local path_lookup       = lbrk * Cg(C((1-rbrk)^1), "path") * rbrk + +--- features ---------------------------------------------------------- +local field_char        = anum + S"+-." --- sic! +local field             = field_char^1 +--- assignments are “lhs=rhs” +---              or “+lhs=rhs” (Xetex-style) +--- switches    are “+key” | “-key” +local normal_option     = C(field) * ws * equals * ws * C(field) * ws +local xetex_option      = P"+" * ws * normal_option +local ignore_option     = (1 - equals - featuresep)^1 +                        * equals +                        * (1 - featuresep)^1 +local assignment        = xetex_option  / handle_xetex_option +                        + normal_option / handle_normal_option +                        + ignore_option / handle_invalid_option +local switch            = P"+" * ws * C(field) * Cc(true) +                        + P"-" * ws * C(field) * Cc(false) +                        +             C(field) * Cc(true)   --- default +local feature_expr      = ws * Cg(assignment + switch) * ws +local option            = feature_expr +local feature_list      = Cf(Ct"" +                           * option +                           * (featuresep * option^-1)^0 +                           , rawset) +                        * featuresep^-1 + +--- other ------------------------------------------------------------- +--- This rule is present in the original parser. It sets the “sub” +--- field of the specification which allows addressing a specific +--- font inside a TTC container. Neither in Luatex-Fonts nor in +--- Luaotfload is this documented, so we might as well silently drop +--- it. However, as backward compatibility is one of our prime goals we +--- just insert it here and leave it undocumented until someone cares +--- to ask. (Note: afair subfonts are numbered, but this rule matches a +--- string; I won’t mess with it though until someone reports a +--- problem.) +--- local subvalue   = P("(") * (C(P(1-S("()"))^1)/issub) * P(")") -- for Kim +--- Note to self: subfonts apparently start at index 0. Tested with +--- Cambria.ttc that includes “Cambria Math” at 0 and “Cambria” at 1. +--- Other values cause luatex to segfault. +local subfont           = P"(" * Cg((1 - S"()")^1, "sub") * P")" +--- top-level rules --------------------------------------------------- +--- \font\foo=<specification>:<features> +local features          = Cg(feature_list, "features") +local specification     = (prefixed + unprefixed) +                        * subfont^-1 +                        * modifier_list^-1 +local font_request      = Ct(path_lookup   * (colon^-1 * features)^-1 +                           + specification * (colon    * features)^-1) + +--  lpeg.print(font_request) +--- v2.5 parser: 1065 rules +--- v1.2 parser:  230 rules + +luaotfload.parsers.font_request = font_request + +------------------------------------------------------------------------------- +---                                INI FILES +------------------------------------------------------------------------------- + +--[[doc-- + +    Luaotfload uses the pervasive flavor of the INI files that allows '#' in +    addition to ';' to indicate comment lines (see git-config(1) for a +    description of the syntax we’re targeting). + +--doc]]-- + +local truth_ids = { +  ["true"]  = true, +  ["1"]     = true, +  yes       = true, +  on        = true, +  ["false"] = false, +  ["2"]     = false, +  no        = false, +  off       = false, +} + +local maybe_cast = function (var) +  local bool = truth_ids[var] +  if bool ~= nil then +    return bool +  end +  return tonumber (var) or var +end +local escape = function (chr, repl) +  return (backslash * P(chr) / (repl or chr)) +end +local valid_escapes     = escape "\"" +                        + escape "\\" +                        + escape ("n", "\n") +                        + escape ("t", "\t") +                        + escape ("b", "\b") +local comment_char      = semicolon + gartenzaun +local comment_line      = ws * comment_char * (1 - eol)^0 * eol +local blank_line        = ws * eol +local skip_line         = comment_line + blank_line +local ini_id_char       = alpha + (dash / "_") +local ini_id            = Cs(alpha * ini_id_char^0) / stringlower +local ini_value_char    = (valid_escapes + (1 - newline - backslash - comment_char)) +local ini_value         = (Cs (ini_value_char^0) / string.strip) +                        * (comment_char * (1 - eol)^0)^-1 +local ini_string_char   = (valid_escapes + (1 - newline - dquote - backslash)) +local ini_string        = dquote +                        * Cs (ini_string_char^0) +                        * dquote + +local ini_heading_title = Ct (Cg (ini_id, "title") +                            * (ws * Cg (ini_string / stringlower, "subtitle"))^-1) +local ini_heading       = lbrk * ws +                        * Cg (ini_heading_title, "section") +                        * ws * rbrk * ws * eol + +local ini_variable_full = Cg (ws +                            * ini_id +                            * ws +                            * equals +                            * ws +                            * (ini_string + (ini_value / maybe_cast)) +                            * ws +                            * eol) +local ini_variable_true = Cg (ws * ini_id * ws * eol * Cc (true)) +local ini_variable      = ini_variable_full +                        + ini_variable_true +                        + skip_line +local ini_variables     = Cg (Cf (Ct "" * ini_variable^0, rawset), "variables") + +local ini_section       = Ct (ini_heading * ini_variables) +local ini_sections      = skip_line^0 * ini_section^0 +local config            = Ct (ini_sections) + +--[=[doc-- + +    The INI parser converts an input of the form + +            [==[ +              [foo] +              bar = baz +              xyzzy = no +              buzz + +              [lavernica "brutalitops"] +              # It’s a locomotive that runs on us. +                laan-ev = zip zop zooey   ; jib-jab +              Crouton = "Fibrosis \"\\ # " + +            ]==] + +    to a Lua table of the form + +            { { section = { title = "foo" }, +                variables = { bar = "baz", +                              xyzzy = false, +                              buzz = true } }, +              { section = { title = "boing", +                            subtitle = "brutalitops" }, +                variables = { ["laan-ev"] = "zip zop zooey", +                              crouton = "Fibrosis \"\\ # " } } } + +--doc]=]-- + +luaotfload.parsers.config = config + +-- vim:ft=lua:tw=71:et:sts=4:ts=8 diff --git a/luaotfload-tool.lua b/src/luaotfload-tool.lua index 5607c04..8cfcac0 100755 --- a/luaotfload-tool.lua +++ b/src/luaotfload-tool.lua @@ -2,17 +2,22 @@  -----------------------------------------------------------------------  --         FILE:  luaotfload-tool.lua  --  DESCRIPTION:  database functionality --- REQUIREMENTS:  luaotfload 2.4 +-- REQUIREMENTS:  luaotfload 2.5  --       AUTHOR:  Khaled Hosny, Élie Roux, Philipp Gesang ---      VERSION:  2.4-4 ---      LICENSE:  GPL v2 ---     MODIFIED:  2013-07-28 13:12:04+0200 +--      VERSION:  2.5 +--      LICENSE:  GPL v2.0 +--     MODIFIED:  2014-05-15 21:47:39+0200  ----------------------------------------------------------------------- -local version = "2.4-4" --- <int: major>.<int: minor><alpha: fixes> +luaotfload          = luaotfload or { } +local version       = "2.5" --- <int: major>.<int: minor>-<int: fixes> +luaotfload.version  = version +luaotfload.self     = "luaotfload-tool"  --[[doc-- +luaotfload-tool(1) +  This file was originally written (as \fileent{mkluatexfontdb.lua}) by  Elie Roux and Khaled Hosny and, as a derived work of ConTeXt, is  provided under the terms of the GPL v2.0 license as printed in full @@ -33,17 +38,16 @@ kpse.set_program_name "luatex"  --[[doc--      We test for Lua 5.1 by means of capability detection to see if -    we’re running an outdated Luatex.  If so, we hand over control to -    the legacy db runner. +    we’re running an outdated Luatex.  If so, we bail.      \url{http://lua-users.org/wiki/LuaVersionCompatibility}  --doc]]-- -local ioopen          = io.open  local iowrite         = io.write  local kpsefind_file   = kpse.find_file +local mathfloor       = math.floor  local next            = next  local osdate          = os.date  local ostype          = os.type @@ -63,8 +67,10 @@ if _G.getfenv ~= nil then -- 5.1 or LJ          runtime = { "jit", jit.version }      else          runtime = { "stock", _VERSION } -        local oldscript = kpsefind_file "luaotfload-legacy-tool.lua" -        return require (oldscript) +        print "FATAL ERROR" +        print "Luaotfload requires a Luatex version >=0.76." +        print "Please update your TeX distribution!" +        os.exit (-1)      end  else -- 5.2      runtime = { "stock", _VERSION } @@ -83,56 +89,19 @@ string.quoted = string.quoted or function (str)    return string.format("%q",str)   end -require(loader_path) +require (loader_path)  --[[doc-- -Depending on how the script is called we change its behavior. -For backwards compatibility, moving or symlinking the script to a -file name starting with \fileent{mkluatexfontdb} will cause it to -trigger a database update on every run. -Running as \fileent{luaotfload-tool} -- the new name -- will do this upon -request only. - -There are two naming conventions followed here: firstly that of -utilities such as \fileent{mktexpk}, \fileent{mktexlsr} and the likes, -and secondly that of \fileent{fmtutil}. -After support for querying the database was added, the latter appeared -to be the more appropriate. ---doc]]-- -config                        = config or { } -local config                  = config -local luaotfloadconfig        = config.luaotfload or { } -config.luaotfload             = luaotfloadconfig -luaotfloadconfig.version      = luaotfloadconfig.version   or version -luaotfloadconfig.names_dir    = luaotfloadconfig.names_dir or "names" -luaotfloadconfig.cache_dir    = luaotfloadconfig.cache_dir or "fonts" -luaotfloadconfig.index_file   = luaotfloadconfig.index_file -                             or "luaotfload-names.lua" -luaotfloadconfig.formats      = luaotfloadconfig.formats -                             or "otf,ttf,ttc,dfont" -luaotfloadconfig.reload       = false -if not luaotfloadconfig.strip then -    luaotfloadconfig.strip = true -end +    XXX: +        Creating the config table will be moved to the common +        initialization when the times comes. -do -- we don’t have file.basename and the likes yet, so inline parser ftw -    local slash        = P"/" -    local dot          = P"." -    local noslash      = 1 - slash -    local slashes      = slash^1 -    local path         =  slashes^-1 * (noslash^1 * slashes)^1 -    local thename      = (1 - slash - dot)^1 -    local extension    = dot * (1 - slash - dot)^1 -    local p_basename   = path^-1 * C(thename) * extension^-1 * P(-1) - -    local self = lpegmatch(p_basename, stringlower(arg[0])) -    if self == "luaotfload-tool" then -        luaotfloadconfig.self = "luaotfload-tool" -    else -        luaotfloadconfig.self = "mkluatexfontdb" -    end -end +--doc]]-- + +config                          = config or { } +local config                    = config +config.luaotfload               = config.luaotfload or { }  config.lualibs                  = config.lualibs or { }  config.lualibs.verbose          = false @@ -140,8 +109,13 @@ config.lualibs.prefer_merged    = true  config.lualibs.load_extended    = true  require "lualibs" -local tabletohash               = table.tohash +local iosavedata                = io.savedata +local lfsisdir                  = lfs.isdir +local lfsisfile                 = lfs.isfile  local stringsplit               = string.split +local tableserialize            = table.serialize +local tablesortedkeys           = table.sortedkeys +local tabletohash               = table.tohash  --[[doc--  \fileent{luatex-basics-gen.lua} calls functions from the @@ -169,22 +143,17 @@ require"luaotfload-basics-gen.lua"  texio.write, texio.write_nl          = backup.write, backup.write_nl  utilities                            = backup.utilities -require"luaotfload-override.lua"  --- this populates the logs.* namespace -require"luaotfload-database" -require"alt_getopt" - -local names = fonts.names - -local status_file                    = "luaotfload-status" -local luaotfloadstatus               = require (status_file) -luaotfloadconfig.status              = luaotfloadstatus +require "luaotfload-log.lua"       --- this populates the luaotfload.log.* namespace +require "luaotfload-parsers"       --- fonts.conf, configuration, and request syntax +require "luaotfload-configuration" --- configuration file handling +require "luaotfload-database" +require "alt_getopt" +local names                          = fonts.names  local sanitize_fontname              = names.sanitize_fontname -local pathdata      = names.path -local names_plain   = pathdata.index.lua -local names_gzip    = names_plain .. ".gz" -local names_bin     = pathdata.index.luc +local log                            = luaotfload.log +local report                         = log.report  local help_messages = {      ["luaotfload-tool"] = [[ @@ -211,9 +180,6 @@ Usage: %s [OPTIONS...]                                 "environment", "index", "permissions", or                                 "repository" -  --alias=<name>               force behavior of "luaotfload-tool" or legacy -                               "mkluatexfontdb" -  -------------------------------------------------------------------------------                                     DATABASE @@ -221,6 +187,7 @@ Usage: %s [OPTIONS...]    -n --no-reload               suppress db update    --no-strip                   keep redundant information in db    -f --force                   force re-indexing all fonts +  -L --local                   scan font files in $PWD    -c --no-compress             do not gzip index file (text version only)    -l --flush-lookups           empty lookup cache of font requests    -D --dry-run                 skip loading of fonts, just scan @@ -243,6 +210,10 @@ Usage: %s [OPTIONS...]    --fields=<f1>,<f2>,…,<fn>    which fields <f> to print with --list    -b --show-blacklist          show blacklisted files +  --bisect=<directive>         control database bisection: valid +                               directives are "start", "stop", "run", "status", +                               "good", "bad" +  The font database will be saved to     %s     %s @@ -258,49 +229,37 @@ The font cache will be written to  ]],      mkluatexfontdb = [[ - -Usage: %s [OPTION]... - -Rebuild or update the Luaotfload font names database. - -Valid options: -  -f --force                   force re-indexing all fonts -  -q --quiet                   don't output anything -  -v --verbose=LEVEL           be more verbose (print the searched directories) -  -vv                          print the loaded fonts -  -vvv                         print all steps of directory searching -  -V --version                 print version and exit -  -h --help                    print this message -  --alias=<name>               force behavior of "luaotfload-tool" or legacy -                               "mkluatexfontdb" - -The font database will be saved to -   %s -   %s - +FATAL ERROR +As of Luaotfload v2.5, legacy behavior is not supported anymore. Please +update your scripts and/or habits! Kthxbye.  ]],      short = [[  Usage: luaotfload-tool [--help] [--version] [--verbose=<lvl>] -                       [--update] [--force] [--prefer-texmf] +                       [--update] [--force] [--prefer-texmf] [--local]                         [--dry-run] [--formats=<extension list>]                         [--find=<font name>] [--fuzzy] [--info] [--inspect]                         [--list=<criterion>] [--fields=<field list>]                         [--cache=<directive>] [--flush-lookups]                         [--show-blacklist] [--diagnose=<procedure>] +                       [--no-compress] [--no-strip] [--local] +                       [--max-fonts=<n>] [--bisect=<directive>]  Enter 'luaotfload-tool --help' for a larger list of options.  ]]  }  local help_msg = function (version) -    local template = help_messages[version] +    local template      = help_messages[version] +    local paths         = config.luaotfload.paths +    local names_plain   = paths.index_path_lua +    local names_gzip    = names_plain .. ".gz" +    local names_bin     = paths.index_path_luc +      iowrite(stringformat(template, -                         luaotfloadconfig.self, ---                         names_plain, +                         luaotfload.self,                           names_gzip,                           names_bin, -                         caches.getwritablepath ( -                         luaotfloadconfig.cache_dir))) +                         caches.getwritablepath (config.luaotfload.cache_dir)))  end  local about = [[ @@ -311,16 +270,28 @@ local about = [[  ]]  local version_msg = function ( ) -    local out = function (...) texiowrite_nl (stringformat (...)) end -    out (about, luaotfloadconfig.self) -    out ("%s version %q", luaotfloadconfig.self, version) -    out ("revision %q", luaotfloadstatus.notes.revision) -    out ("database version %q", names.version) +    local out   = function (...) texiowrite_nl (stringformat (...)) end +    local uname = os.uname () +    local meta  = names.getmetadata () +    out (about, luaotfload.self) +    out ("%s version: %q", luaotfload.self, version) +    out ("Revision: %q", config.luaotfload.status.notes.revision)      out ("Lua interpreter: %s; version %q", runtime[1], runtime[2]) -    out ("Luatex SVN revision %d", status.luatex_svn) -    out ("Luatex version %.2f.%d", +    out ("Luatex SVN revision: %d", status.luatex_svn) +    out ("Luatex version: %.2f.%d",           status.luatex_version / 100,           status.luatex_revision) +    out ("Platform: type=%s name=%s", os.type, os.name) + +    local uname_vars = tablesortedkeys (uname) +    for i = 1, #uname_vars do +        local var = uname_vars[i] +        out ("    + %8s: %s", var, uname[var]) +    end +    out ("Index: version=%q created=%q modified=%q", +         config.luaotfload.status.notes.revision, +         meta.created or "ages ago", +         meta.modified or "ages ago")      out ""  end @@ -332,7 +303,7 @@ local head_adornchars = {  }  local textwidth         = 80 -local wd_leftcolumn     = math.floor(textwidth * .25) +local wd_leftcolumn     = mathfloor(textwidth * .25)  local key_fmt           = stringformat([[%%%ds]], wd_leftcolumn)  local val_fmt           = [[%s]]  local fieldseparator    = ":" @@ -380,15 +351,36 @@ local print_heading = function (title, level)      texiowrite_nl (s .. stringrep(adornchar, textwidth-utf.len(s)))  end +local baseindent = "    " + +--[[doc-- + +    show_info_items -- Together with show_info_table prints the table returned by +    fontloader.info(), recursing into nested tables if appropriate (as necessitated +    by Luatex versions 0.78+ which include the pfminfo table in the result. + +--doc]]-- + +local show_info_table show_info_table = function (t, depth) +    depth           = depth or 0 +    local indent    = stringrep (baseindent, depth) +    local keys      = tablesortedkeys (t) +    for n = 1, #keys do +        local key = keys [n] +        local val = t [key] +        if type (val) == "table" then +            texiowrite_nl (indent .. stringformat (info_fmt, key, "<table>")) +            show_info_table (val, depth + 1) +        else +            texiowrite_nl (indent .. stringformat (info_fmt, key, val)) +        end +    end +end +  local show_info_items = function (fontinfo) -    local items = table.sortedkeys(fontinfo) -    print_heading(fontinfo.fullname, 1) +    print_heading (fontinfo.fullname, 1)      texiowrite_nl "" -    for n = 1, #items do -        local item = items[n] -        texiowrite_nl(stringformat( -            info_fmt, item, fontinfo[item])) -    end +    show_info_table (fontinfo)      texiowrite_nl ""  end @@ -707,9 +699,9 @@ local show_font_info = function (basename, askedname, detail, warnings)          if nfonts > 0 then -- true type collection              local subfont              if askedname then -                logs.names_report(true, 1, "resolve", -                    [[%s is part of the font collection %s]], -                    askedname, basename) +                report (true, 1, "resolve", +                        [[%s is part of the font collection %s]], +                        askedname, basename)                  subfont = subfont_by_name(shortinfo, askedname)              end              if subfont then @@ -718,11 +710,11 @@ local show_font_info = function (basename, askedname, detail, warnings)                      show_full_info(fullname, subfont, warnings)                  end              else -- list all subfonts -                logs.names_report(true, 1, "resolve", -                    [[%s is a font collection]], basename) +                report (true, 1, "resolve", +                        [[%s is a font collection]], basename)                  for subfont = 1, nfonts do -                    logs.names_report(true, 1, "resolve", -                        [[Showing info for font no. %d]], n) +                    report (true, 1, "resolve", +                            [[Showing info for font no. %d]], n)                      show_info_items(shortinfo[subfont])                      if detail == true then                          show_full_info(fullname, subfont, warnings) @@ -736,8 +728,7 @@ local show_font_info = function (basename, askedname, detail, warnings)              end          end      else -        logs.names_report(true, 1, "resolve", -            "Font %s not found", filename) +        report (true, 1, "resolve", "Font %s not found", filename)      end  end @@ -748,23 +739,40 @@ set.  --]]--  local action_sequence = { -    "loglevel",  "help",  "version", "diagnose", -    "blacklist", "cache", "flush",   "generate", -    "list",      "query", +    "config",   "loglevel",  "help",  "version", +    "diagnose", "blacklist", "cache", "flush", +    "bisect",   "generate",  "list",  "query",  }  local action_pending  = tabletohash(action_sequence, false) +action_pending.config   = true  --- always read the configuration  action_pending.loglevel = true  --- always set the loglevel  action_pending.generate = false --- this is the default action  local actions = { } --- (jobspec -> (bool * bool)) list  actions.loglevel = function (job) -    logs.set_loglevel(job.log_level) -    logs.names_report("info", 3, "util", -                      "Setting log level", "%d", job.log_level) -    logs.names_report("log", 2, "util", "Lua=%s", _VERSION) +    local lvl = job.log_level +    if lvl then +        log.set_loglevel(lvl) +        report ("info", 3, "util", "Setting the log level to %d.", lvl) +        report ("log", 2, "util", "Lua=%q", _VERSION) +    end +    return true, true +end + +actions.config = function (job) +    local defaults            = luaotfload.default_config +    local vars                = config.actions.read (job.extra_config) +    config.luaotfload         = config.actions.apply (defaults, vars) +    config.luaotfload         = config.actions.apply (config.luaotfload, job.config) + +    --inspect(config.luaotfload) +    --os.exit() +    if not config.actions.reconfigure () then +        return false, false +    end      return true, true  end @@ -781,29 +789,321 @@ end  actions.blacklist = function (job)      names.read_blacklist()      local n = 0 -    for n, entry in next, table.sortedkeys(names.blacklist) do +    for n, entry in next, tablesortedkeys(names.blacklist) do          iowrite (stringformat("(%d %s)\n", n, entry))      end      return true, false  end  actions.generate = function (job) -    local fontnames, savedname -    fontnames = names.update(fontnames, job.force_reload, job.dry_run) -    logs.names_report("info", 2, "db", -        "Fonts in the database: %i", #fontnames.mappings) +    local fontnames = names.update(fontnames, job.force_reload, job.dry_run) +    report ("info", 2, "db", "Fonts in the database: %i", #fontnames.mappings)      if names.data() then          return true, true      end      return false, false  end +------------------------------------------------------------------------------- +--- bisect mode +------------------------------------------------------------------------------- + +local bisect_status_path = caches.getwritablepath "bisect" +local bisect_status_file = bisect_status_path .."/" .. "luaotfload-bisect-status.lua" +local bisect_status_fmt  = [[ +--[==[------------------------------------------------------------------------- +    This file is generated by Luaotfload. It can be safely deleted. +    Creation date: %s. +-------------------------------------------------------------------------]==]-- + +%s + +--- vim:ft=lua:ts=8:et:sw=2 +]] + +--[[doc-- + +    write_bisect_status -- Write the history of the current bisection to disk. + +--doc]]-- + +--- state list -> bool +local write_bisect_status = function (data) +    local payload = tableserialize (data, true) +    local status  = stringformat (bisect_status_fmt, +                                  osdate ("%Y-%m-d %H:%M:%S", os.time ()), +                                  payload) +    if status and iosavedata (bisect_status_file, status) then +        report ("info", 4, "bisect", +                "Bisection state written to %s.", bisect_status_file) +        return true +    end +    report ("info", 0, "bisect", +            "Failed to write bisection state to %s.", bisect_status_file) +    return false +end + +--[[doc-- + +    read_bisect_status -- Read the bisect log from disk. + +--doc]]-- + +--- unit -> state list +local read_bisect_status = function () +    report ("info", 4, "bisect", "Testing for status file: %q.", bisect_status_file) +    if not lfsisfile (bisect_status_file) then +        report ("info", 2, "bisect", "No such file: %q.", bisect_status_file) +        report ("info", 0, "bisect", "Not in bisect mode.") +        return false +    end +    report ("info", 4, "bisect", "Reading status file: %q.", bisect_status_file) +    local success, status = pcall (dofile, bisect_status_file) +    if not success then +        report ("info", 0, "bisect", "Could not read status file.") +        return false +    end +    return status +end + +--[[doc-- + +    bisect_start -- Begin a bisect session. Determines the number of +    fonts and sets the initial high, low, and pivot values. + +--doc]]-- + +local bisect_start = function () +    if lfsisfile (bisect_status_file) then +        report ("info", 0, "bisect", +                "Bisect session in progress.", +                bisect_status_file) +        report ("info", 0, "bisect", +                "Use --bisect=stop to erase it before starting over.") +        return false, false +    end +    report ("info", 2, "bisect", +            "Starting bisection of font database %q.", bisect_status_file) +    local n     = names.count_font_files () +    local pivot = mathfloor (n / 2) +    local data  = { { 1, n, pivot } } +    report ("info", 0, "bisect", "Initializing pivot to %d.", pivot) +    if write_bisect_status (data) then +        return true, false +    end +    return false, false +end + +--[[doc-- + +    bisect_stop -- Terminate bisection session by removing all state info. + +--doc]]-- + +local bisect_stop = function () +    report ("info", 3, "bisect", "Erasing bisection state at %s.", bisect_status_file) +    if lfsisfile (bisect_status_file) then +        local success, msg = os.remove (bisect_status_file) +        if not success then +            report ("info", 2, "bisect", +                    "Failed to erase file %s (%s).", +                     bisect_status_file, msg) +        end +    end +    if lfsisdir (bisect_status_path) then +        local success, msg = os.remove (bisect_status_path) +        if not success then +            report ("info", 2, "bisect", +                    "Failed to erase directory %s (%s).", +                     bisect_status_path, msg) +        end +    end +    if lfsisfile (bisect_status_file) then +        return false, false +    end +    return true, false +end + +--[[doc-- + +    bisect_terminate -- Wrap up a bisect session by printing the +    offending font and removing the state file. + +--doc]]-- + +local bisect_terminate = function (nsteps, culprit) +    report ("info", 1, "bisect", +            "Bisection completed after %d steps.", nsteps) +    report ("info", 0, "bisect", +            "Bad file: %s.", names.nth_font_filename (culprit)) +    report ("info", 0, "bisect", +            "Run with --bisect=stop to finish bisection.") +    return true, false +end + +--[[doc-- + +    list_remainder -- Show remaining fonts in bisect slice. + +--doc]]-- + +local list_remainder = function (lo, hi) +    local fonts = names.font_slice (lo, hi) +    report ("info", 0, "bisect", "%d fonts left.", hi - lo + 1) +    for i = 1, #fonts do +        report ("info", 1, "bisect", "   · %2d: %s", lo, fonts[i]) +        lo = lo + 1 +    end +end + +--[[doc-- + +    bisect_set -- Prepare the next bisection step by setting high, low, +    and pivot to new values. + +    The “run” directive always picks the segment below the pivot so we +    can rely on the “outcome parameter” to be referring to that. + +--doc]]-- + +local bisect_set = function (outcome) +    local status = read_bisect_status () +    if not status then +        return false, false +    end + +    local nsteps        = #status +    local previous      = status[nsteps] +    if previous == true then +        --- Bisection already completed; we exit early through +        --- bisect_terminate() to avoid further writes to the +        --- state files that mess up step counting. +        nsteps = nsteps - 1 +        return bisect_terminate (nsteps, status[nsteps][1]) +    end + +    local lo, hi, pivot = unpack (previous) + +    report ("info", 3, "bisect", "Previous step %d: lo=%d, hi=%d, pivot=%d.", +            nsteps, lo, hi, pivot) + +    if outcome == "bad" then +        hi = pivot +        if lo >= hi then --- complete +            status[nsteps + 1] = { lo, lo, lo } +            status[nsteps + 2] = true +            write_bisect_status (status) +            return bisect_terminate (nsteps, lo) +        end +        pivot = mathfloor ((lo + hi) / 2) +        report ("info", 0, "bisect", +                "Continuing with the lower segment: lo=%d, hi=%d, pivot=%d.", +                lo, hi, pivot) +    elseif outcome == "good" then +        lo = pivot + 1 +        if lo >= hi then --- complete +            status[nsteps + 1] = { lo, lo, lo } +            write_bisect_status (status) +            status[nsteps + 2] = true +            return bisect_terminate (nsteps, lo) +        end +        pivot = mathfloor ((lo + hi) / 2) +        report ("info", 0, "bisect", +                "Continuing with the upper segment: lo=%d, hi=%d, pivot=%d.", +                lo, hi, pivot) +    else -- can’t happen +        report ("info", 0, "bisect", "What the hell?", lo, hi, pivot) +        return false, false +    end + +    status[nsteps + 1] = { lo, hi, pivot } +    write_bisect_status (status) +    if hi - lo <= 10 then +        list_remainder (lo, hi) +    end +    return true, false +end + +--[[doc-- + +    bisect_status -- Output information about the current bisect session. + +--doc]]-- + +local bisect_status = function () +    local status = read_bisect_status () +    if not status then +        return false, false +    end +    local nsteps = #status +    if nsteps > 1 then +        for i = nsteps - 1, 1, -1 do +            local step = status[i] +            report ("info", 2, "bisect", "Step %d: lo=%d, hi=%d, pivot=%d.", +                    i, unpack (step)) +        end +    end +    local current = status[nsteps] +    report ("info", 0, "bisect", "Step %d: lo=%d, hi=%d, pivot=%d.", +            nsteps, unpack (current)) +    return true, false +end + +--[[doc-- + +    bisect_run -- Run Luaotfload utilizing the current bisection state. +    This should be combined with the --update mode, possibly with the +    --force option. + +    Luaotfload always tests the segment below the pivot first. + +--doc]]-- + +local bisect_run = function () +    local status = read_bisect_status () +    if not status then +        return false, false +    end +    local nsteps        = #status +    local currentstep   = nsteps + 1 +    local current       = status[nsteps] +    if current == true then -- final step +        current = status[nsteps - 1] +    end +    local lo, hi, pivot = unpack (current) +    report ("info", 3, "bisect", "Previous step %d: lo=%d, hi=%d, pivot=%d.", +            nsteps, lo, hi, pivot) +    report ("info", 1, "bisect", "Step %d: Testing fonts from %d to %d.", +            currentstep, lo, pivot) +    config.luaotfload.misc.bisect = { lo, pivot } +    return true, true +end + +local bisect_modes = { +    start   = bisect_start, +    good    = function () return bisect_set "good" end, +    bad     = function () return bisect_set "bad"  end, +    stop    = bisect_stop, +    status  = bisect_status, +    run     = bisect_run, +} + +actions.bisect = function (job) +    local mode   = job.bisect +    local runner = bisect_modes[mode] +    if not runner then +        report ("info", 0, "bisect", "Unknown directive %q.", mode) +        return false, false +    end +    return runner (job) +end +  actions.flush = function (job) -    local success, lookups = names.flush_lookup_cache() +    local success = names.flush_lookup_cache()      if success then          local success = names.save_lookups()          if success then -            logs.names_report("info", 2, "cache", "Lookup cache emptied") +            report ("info", 2, "cache", "Lookup cache emptied")              return true, true          end      end @@ -819,8 +1119,8 @@ local cache_directives = {  actions.cache = function (job)      local directive = cache_directives[job.cache]      if not directive or type(directive) ~= "function" then -        logs.names_report("info", 2, "cache", -                          "Invalid font cache directive %s.", job.cache) +        report ("info", 2, "cache", +                "Invalid font cache directive %s.", job.cache)          return false, false      end      if directive() then @@ -856,36 +1156,35 @@ actions.query = function (job)      then          foundname, subfont = names.resolve_name (tmpspec)          if foundname then -            foundname, _, success = names.crude_file_lookup (foundname) +            foundname, _, success = names.font_file_lookup (foundname)          end      elseif tmpspec.lookup == "file" then          foundname, _, success = -            names.crude_file_lookup (tmpspec.name) +            names.font_file_lookup (tmpspec.name)      end      if success then -        logs.names_report(false, 0, -            "resolve", "Font %q found!", query) +        report (false, 0, "resolve", "Font %q found!", query)          if subfont then -            logs.names_report(false, 0, "resolve", -                "Resolved file name %q, subfont nr. %q", +            report (false, 0, "resolve", +                    "Resolved file name %q, subfont nr. %q",                  foundname, subfont)          else -            logs.names_report(false, 0, "resolve", -                              "Resolved file name %q", foundname) +            report (false, 0, "resolve", +                    "Resolved file name %q", foundname)          end          if job.show_info then              show_font_info (foundname, query, job.full_info, job.warnings)              iowrite "\n"          end      else -        logs.names_report(false, 0, -            "resolve", "Cannot find %q in index.", query) -        logs.names_report(false, 0, -            "resolve", "Hint: use the --fuzzy option to display suggestions.", query) +        report (false, 0, "resolve", "Cannot find %q in index.", query) +        report (false, 0, "resolve", +                "Hint: use the --fuzzy option to display suggestions.", +                query)          if job.fuzzy == true then -            logs.names_report(false, 0, -                "resolve", "Looking for close matches, this may take a while ...") +            report (false, 0, "resolve", +                    "Looking for close matches, this may take a while ...")              local _success = names.find_closest(query, job.fuzzy_limit)          end      end @@ -959,7 +1258,7 @@ set_primary_field = function (fields, addme, acc, n)      return acc  end -local splitcomma = names.patterns.splitcomma +local splitcomma = luaotfload.parsers.splitcomma  actions.list = function (job)      local criterion     = job.criterion @@ -983,7 +1282,7 @@ actions.list = function (job)      local nmappings = #mappings      if criterion == "*" then -        logs.names_report(false, 1, "list", "All %d entries", nmappings) +        report (false, 1, "list", "All %d entries", nmappings)          for i=1, nmappings do              local entry     = mappings[i]              local fields    = get_fields(entry, asked_fields) @@ -998,12 +1297,12 @@ actions.list = function (job)          criterion          = criterion[1]          asked_fields       = set_primary_field(asked_fields, criterion) -        logs.names_report(false, 1, "list", "By %s", criterion) +        report (false, 1, "list", "By %s", criterion)          --- firstly, build a list of fonts to operate on          local targets = { }          if asked_value then --- only those whose value matches -            logs.names_report(false, 2, "list", "Restricting to value %s", asked_value) +            report (false, 2, "list", "Restricting to value %s", asked_value)              for i=1, nmappings do                  local entry = mappings[i]                  if  entry[criterion] @@ -1048,7 +1347,7 @@ actions.list = function (job)              end          end          local ntargets = #targets -        logs.names_report(false, 2, "list", "%d entries", ntargets) +        report (false, 2, "list", "%d entries", ntargets)          --- now, output the collection          for i=1, ntargets do @@ -1088,7 +1387,7 @@ end  --[[--  Command-line processing. -mkluatexfontdb.lua relies on the script alt_getopt to process argv and +luaotfload-tool relies on the script alt_getopt to process argv and  analyzes its output.  TODO with extended lualibs we have the functionality from the @@ -1103,13 +1402,15 @@ local process_cmdline = function ( ) -- unit -> jobspec          warnings     = false,          criterion    = "",          query        = "", -        log_level    = 0, --- 2 is approx. the old behavior +        log_level    = nil, +        bisect       = nil, +        config       = { db = { }, misc = { }, run = { }, paths = { } },      }      local long_options = { -        alias              = 1, +        ["bisect"]         = 1,          cache              = 1, -        ["no-compress"]    = "c", +        conf               = 1,          diagnose           = 1,          ["dry-run"]        = "D",          ["flush-lookups"]  = "l", @@ -1123,12 +1424,15 @@ local process_cmdline = function ( ) -- unit -> jobspec          inspect            = "I",          limit              = 1,          list               = 1, +        ["local"]          = "L",          log                = 1,          ["max-fonts"]      = 1, +        ["no-compress"]    = "c",          ["no-reload"]      = "n",          ["no-strip"]       = 0,          ["skip-read"]      = "R",          ["prefer-texmf"]   = "p", +        ["print-conf"]     = 0,          quiet              = "q",          ["show-blacklist"] = "b",          stats              = "S", @@ -1138,7 +1442,7 @@ local process_cmdline = function ( ) -- unit -> jobspec          warnings           = "w",      } -    local short_options = "bcDfFiIlnpqRSuvVhw" +    local short_options = "bcDfFiIlLnpqRSuvVhw"      local options, _, optarg =          alt_getopt.get_ordered_opts (arg, short_options, long_options) @@ -1151,11 +1455,13 @@ local process_cmdline = function ( ) -- unit -> jobspec          elseif v == "u" then              action_pending["generate"] = true          elseif v == "v" then -            if result.log_level > 0 then -                result.log_level = result.log_level + 1 +            local lvl = result.log_level +            if not lvl or lvl < 1 then +                lvl = 1              else -                result.log_level = 1 +                lvl = lvl + 1              end +            result.log_level = lvl          elseif v == "V" then              action_pending["version"] = true          elseif v == "h" then @@ -1177,7 +1483,7 @@ local process_cmdline = function ( ) -- unit -> jobspec          elseif v == "log" then              local str = optarg[n]              if str then -                finalizers = logs.set_logout(str, finalizers) +                finalizers = log.set_logout(str, finalizers)              end          elseif v == "find" then              action_pending["query"] = true @@ -1194,10 +1500,11 @@ local process_cmdline = function ( ) -- unit -> jobspec          elseif v == "I" then              result.show_info = true              result.full_info = true -        elseif v == "alias" then -            luaotfloadconfig.self = optarg[n]          elseif v == "l" then              action_pending["flush"] = true +        elseif v == "L" then +            action_pending["generate"] = true +            config.luaotfload.db.scan_local = true          elseif v == "list" then              action_pending["list"] = true              result.criterion = optarg[n] @@ -1218,35 +1525,46 @@ local process_cmdline = function ( ) -- unit -> jobspec              action_pending["diagnose"] = true              result.asked_diagnostics = optarg[n]          elseif v == "formats" then -            names.set_font_filter (optarg[n]) +            result.config.db.formats = optarg[n] +            --names.set_font_filter (optarg[n])          elseif v == "n" then -            luaotfloadconfig.update_live = false +            config.luaotfload.db.update_live = false          elseif v == "S" then -            luaotfloadconfig.statistics = true +            config.luaotfload.misc.statistics = true          elseif v == "R" then              ---  dev only, undocumented -            luaotfloadconfig.skip_read = true +            config.luaotfload.db.skip_read = true          elseif v == "c" then -            luaotfloadconfig.compress = false +            config.luaotfload.db.compress = false          elseif v == "no-strip" then -            luaotfloadconfig.strip = false +            config.luaotfload.db.strip = false          elseif v == "max-fonts" then              local n = optarg[n]              if n then                  n = tonumber(n)                  if n and n > 0 then -                    luaotfloadconfig.max_fonts = n +                    config.luaotfload.db.max_fonts = n                  end              end +        elseif v == "bisect" then +            result.bisect         = optarg[n] +            action_pending.bisect = true +        elseif v == "conf" then +            local extra = stringexplode (optarg[n], ",+") +            if extra then +                local extra_config = result.extra_config +                if extra_config then +                    table.append (extra_config, extra) +                else +                    result.extra_config = extra +                end +            end +        elseif v == "print-conf" then +            result.print_config = true          end      end -    if luaotfloadconfig.self == "mkluatexfontdb" then --- TODO drop legacy ballast after 2.4 -        result.help_version = "mkluatexfontdb" -        action_pending["generate"] = true -        result.log_level = math.max(1, result.log_level) -        logs.set_logout("stdout", finalizers) -    elseif nopts == 0 then +    if nopts == 0 then          action_pending["help"] = true          result.help_version = "short"      end @@ -1264,24 +1582,23 @@ local main = function ( ) -- unit -> int          local actionname = action_sequence[i]          local exit       = false          if action_pending[actionname] then -            logs.names_report("log", 3, "util", "Preparing for task", -                              "%s", actionname) +            report ("log", 3, "util", "Preparing for task", "%s", actionname)              local action             = actions[actionname]              local success, continue  = action(job)              if not success then -                logs.names_report(false, 0, "util", -                    "Could not finish task", "%s", actionname) +                report (false, 0, "util", +                        "Failed to execute task.", "%s", actionname)                  retval = -1                  exit   = true              elseif not continue then -                logs.names_report(false, 3, "util", -                    "Task completed, exiting", "%s", actionname) -                exit   = true +                report (false, 3, "util", +                        "Task completed, exiting.", "%s", actionname) +                exit = true              else -                logs.names_report(false, 3, "util", -                    "Task completed successfully", "%s", actionname) +                report (false, 3, "util", +                        "Task completed successfully.", "%s", actionname)              end          end          if exit then break end diff --git a/src/luaotfload.sty b/src/luaotfload.sty new file mode 100644 index 0000000..a235d6b --- /dev/null +++ b/src/luaotfload.sty @@ -0,0 +1,45 @@ +%% Copyright (C) 2009-2014 +%% +%%      by  Elie Roux      <elie.roux@telecom-bretagne.eu> +%%      and Khaled Hosny   <khaledhosny@eglug.org> +%%      and Philipp Gesang <philipp.gesang@alumni.uni-heidelberg.de> +%% +%% This file is part of Luaotfload. +%% +%%      Home:      https://github.com/lualatex/luaotfload +%%      Support:   <lualatex-dev@tug.org>. +%% +%% Luaotfload is under the GPL v2.0 (exactly) license. +%% +%% ---------------------------------------------------------------------------- +%% +%% Luaotfload is free software; you can redistribute it and/or +%% modify it under the terms of the GNU General Public License +%% as published by the Free Software Foundation; version 2 +%% of the License. +%% +%% Luaotfload is distributed in the hope that it will be useful, +%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%% GNU General Public License for more details. +%% +%% You should have received a copy of the GNU General Public License +%% along with Luaotfload; if not, see <http://www.gnu.org/licenses/>. +%% +%% ---------------------------------------------------------------------------- +%% +%% Classical Plain+\LATEX package initialization. +%% +\csname ifluaotfloadloaded\endcsname +\let\ifluaotfloadloaded\endinput +\bgroup\expandafter\expandafter\expandafter\egroup +\expandafter\ifx\csname ProvidesPackage\endcsname\relax +  \input luatexbase.sty +\else +  \NeedsTeXFormat{LaTeX2e} +  \ProvidesPackage{luaotfload}% +    [2014/42/42 v2.5 OpenType layout system] +  \RequirePackage{luatexbase} +\fi +\RequireLuaModule{luaotfload-main} + diff --git a/tests/alternate_sub.tex b/tests/alternate_sub.tex deleted file mode 100644 index 862c665..0000000 --- a/tests/alternate_sub.tex +++ /dev/null @@ -1,15 +0,0 @@ -\input luaotfload.sty -\font\0=name:Scheherazade:mode=node;script=arab;salt=0 at 10pt -\font\1=name:Scheherazade:mode=node;script=arab;salt=1 at 10pt -\font\2=name:Scheherazade:mode=node;script=arab;salt=2 at 10pt -\0 \char"06DD -\1 \char"06DD -\2 \char"06DD - -\font\0=name:Scheherazade:mode=base;script=arab;salt=0 at 10pt -\font\1=name:Scheherazade:mode=base;script=arab;salt=1 at 10pt -\font\2=name:Scheherazade:mode=base;script=arab;salt=2 at 10pt -\0 \char"06DD -\1 \char"06DD -\2 \char"06DD -\bye diff --git a/tests/anum.tex b/tests/anum.tex deleted file mode 100644 index 773955f..0000000 --- a/tests/anum.tex +++ /dev/null @@ -1,17 +0,0 @@ -\input luaotfload.sty - -\font\testd={name:amiri:script=arab;language=dflt;+anum} -\font\testa={name:amiri:script=arab;language=ara;+anum} -\font\testp={name:amiri:script=arab;language=far;+anum} -\font\tests={name:amiri:script=arab;language=snd;+anum} -\font\testu={name:amiri:script=arab;language=urd;+anum} -\font\testx={name:amiri:script=arab;language=xxx;+anum} - -\def\test{\luatextextdir TRT ضرب 0123456789} -\testd \test\par -\testa \test\par -\testp \test\par -\tests \test\par -\testu \test\par -\testx \test\par -\bye diff --git a/tests/caseinsensitive.tex b/tests/caseinsensitive.tex deleted file mode 100644 index 0e9fb5f..0000000 --- a/tests/caseinsensitive.tex +++ /dev/null @@ -1,6 +0,0 @@ -\input luaotfload.sty - -\font\termesr  ={tex gyre termes:+liga}    at 10pt - -\termesr   fi fl ffi ffl ff\par -\bye diff --git a/tests/color.tex b/tests/color.tex deleted file mode 100644 index 188889c..0000000 --- a/tests/color.tex +++ /dev/null @@ -1,10 +0,0 @@ -\input luaotfload.sty - -\font\testa=file:lmroman10-regular.otf:color=FF0000BB;+trep at 10pt -\font\testb=file:lmroman10-regular.otf:color=FFFF0099;+trep at 10pt -\font\testc=file:lmroman10-regular.otf:color=559922;+trep   at 12pt - -\testa FF0000BB \par -\testb FFFF0099 \par -\testc 559922 \par -\bye diff --git a/tests/fallback.tex b/tests/fallback.tex deleted file mode 100644 index 71baea9..0000000 --- a/tests/fallback.tex +++ /dev/null @@ -1,6 +0,0 @@ -\input luaotfload.sty -\font\testa={XITS Math} -\font\testb={XITS Math/B} -\testa text\par -\testb text\par -\bye diff --git a/tests/featurefiles.tex b/tests/featurefiles.tex deleted file mode 100644 index c1a3044..0000000 --- a/tests/featurefiles.tex +++ /dev/null @@ -1,5 +0,0 @@ -\input luaotfload.sty -\font\pagella=texgyrepagella-regular:mode=node;featurefile=tkrn.fea;+tkrn - -\pagella TEX -\bye diff --git a/tests/font_patch.tex b/tests/font_patch.tex deleted file mode 100644 index d3bba07..0000000 --- a/tests/font_patch.tex +++ /dev/null @@ -1,28 +0,0 @@ -\input{luaotfload.sty} -\directlua { -  local function patch(fontdata) -    local mc = fontdata.MathConstants -    local em = fontdata.parameters.units -    local sz = fontdata.parameters.size -    if fontdata.psname == "CambriaMath" and mc then -      mc.DisplayOperatorMinHeight = 2800 / em * sz -    end -  end -  %% part of luaotfload-auxiliary -  %luatexbase.add_to_callback( -    %"luaotfload.patch_font", -    %patch, -    %"luaotfload.aux.patch_cambria_domh") -} - -\font\4={name:Cambria Math:mode=base;script=math} at 10pt -\font\5={name:Cambria Math:mode=base;script=math;ssty=1} at 7pt -\font\6={name:Cambria Math:mode=node;script=math;ssty=2} at 5pt -\textfont4=\4 \scriptfont4=\5 \scriptscriptfont4=\6 - -$$ -\Umathchar"1"4`∫ -\Umathchar"1"4`∑ -$$ -\bye - diff --git a/tests/fontconfig_conf_reading.tex b/tests/fontconfig_conf_reading.tex deleted file mode 100644 index 66ab377..0000000 --- a/tests/fontconfig_conf_reading.tex +++ /dev/null @@ -1,8 +0,0 @@ -\directlua{ -  config = { lualibs = { load_extended = false } } -  require"lualibs" -  require"luaotfload-database" -  local results = fonts.names.read_fonts_conf{"fonts.conf.test"} -  inspect(results) -} -\bye diff --git a/tests/fontencoding.tex b/tests/fontencoding.tex deleted file mode 100644 index bbbbac6..0000000 --- a/tests/fontencoding.tex +++ /dev/null @@ -1,4 +0,0 @@ -\input luaotfload.sty -\font\tenrm=ec-lmr10 -\tenrm -\bye diff --git a/tests/fonts.conf.test b/tests/fonts.conf.test deleted file mode 100644 index 3c3e132..0000000 --- a/tests/fonts.conf.test +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0"?> -<!DOCTYPE fontconfig SYSTEM "fonts.dtd"> -<!-- /etc/fonts/fonts.conf file to configure system font access --> -<fontconfig> -    <!-- this is a comment --> -    <!-- this is a comment with the <dir>test 0 failed!</dir> --> -    <!----> -    <!-- -    this is a comment --> -    <!-- -    this is a comment -    --> -    <!--  -    this is a comment -    <dir>test -1 failed!</dir> -    --> -    <include prefix="xdg">fontconfig/fonts.conf</include><!-- this is a dir --> -    <dir>test 1 ok</dir> -    <dir>test 2 ok</dir><dir>test 3 ok</dir> -    <dir>test 4 ok</dir><!-- comment--><dir>test 5 ok</dir> -    <!-- this starts to be more debian-specific... change it to fit your system --> -    <include>/etc/fonts/conf.d</include><!-- this is a dir --> -    <include>/etc/fonts/fonts.conf</include> -    <include ignore_missing="no">/etc/fonts/conf.d/69-unifont.conf</include> -</fontconfig> diff --git a/tests/fontspec_lookup.ltx b/tests/fontspec_lookup.ltx deleted file mode 100644 index 6645427..0000000 --- a/tests/fontspec_lookup.ltx +++ /dev/null @@ -1,41 +0,0 @@ -\documentclass[a5paper,12pt]{scrartcl} -\usepackage{fontspec} -%% -------------------------------------------------------------------- -%% weirdness ahead -%% -------------------------------------------------------------------- -\setmainfont -  [Numbers=Lining, -   BoldFont={TeX Gyre Pagella Bold}, -   BoldItalicFont={TeX Gyre Termes BoldItalic}] -  {EB Garamond} -%% -------------------------------------------------------------------- - -%% -------------------------------------------------------------------- -%% excerpt from samples/knuth.tex -%% -------------------------------------------------------------------- -\def\knuth{% -  Thus, I came to the conclusion that the designer of a new -  system must not only be the implementer and first -  large--scale user; the designer should also write the first -  user manual. - -  The separation of any of these four components would have -  hurt \TeX\ significantly. If I had not participated fully in -  all these activities, literally hundreds of improvements -  would never have been made, because I would never have -  thought of them or perceived why they were important. - -} - -%% -------------------------------------------------------------------- -%% main -%% -------------------------------------------------------------------- -\begin{document} - -  \section{regular}     {\rmfamily\upshape\knuth} -  \section{bold face}   {\rmfamily\bfseries\knuth} -  \section{italic}      {\rmfamily\itshape\knuth} -  \section{slanted}     {\rmfamily\slshape\knuth} -  \section{bold italic} {\rmfamily\bfseries\itshape\knuth} - -\end{document} diff --git a/tests/frac.tex b/tests/frac.tex deleted file mode 100644 index c6a4868..0000000 --- a/tests/frac.tex +++ /dev/null @@ -1,4 +0,0 @@ -\input luaotfload.sty -\font\testa={name:Linux Libertine O:script=latn;+frac} at 10pt -\testa 1/8 -\bye diff --git a/tests/fullname.tex b/tests/fullname.tex deleted file mode 100644 index 78cf4d0..0000000 --- a/tests/fullname.tex +++ /dev/null @@ -1,15 +0,0 @@ -\input luaotfload.sty - -\font\testa={LM Roman Slanted 10 Regular} at 10pt -\font\testb={LM Roman 9 Italic}           at 10pt -\font\testc={TeX Gyre Termes Bold}        at 25pt -% Also testing with absolute filename, please change the path according to your  -% system -\font\testd=file:/usr/share/texmf/fonts/opentype/public/lm/lmmono10-italic.otf - -\testa abcd ABCD\par -\testb abcd ABCD\par -\testc abcd ABCD\par -\testd abcd ABCD\par - -\bye diff --git a/tests/itlc.tex b/tests/itlc.tex deleted file mode 100644 index bff0c39..0000000 --- a/tests/itlc.tex +++ /dev/null @@ -1,5 +0,0 @@ -\input luaotfload.sty -\font\testa={Latin Modern Roman:script=latn}   at 10pt -\font\testb={Latin Modern Roman/I:script=latn} at 10pt -\testa ({\testb f\/}) -\bye diff --git a/tests/lookups.tex b/tests/lookups.tex deleted file mode 100644 index 8b03d8e..0000000 --- a/tests/lookups.tex +++ /dev/null @@ -1,20 +0,0 @@ -\input luaotfload.sty -%% lookup font by name (involving database) -\font\first=name:iwonaregular at 42pt -%% lookup font by file name (kpse) -\font\second=file:antpoltltsemiexpd-bolditalic.otf at 42pt -%% lookup font by name, with style in slash notation -\font\third={name:Antykwa torunska/I} at 42pt -%% unspecified lookup; in definers.read: -%%  - first it falls back to “file” -%%  - empty “method” field triggers fallback to “name” -%%  - names.resolve -> kpse.lookup -> hit! -\font\fourth=iwona at 42pt - -{\first   foo \endgraf} -{\second  bar \endgraf} -{\third   baz \endgraf} -{\fourth  xyzzy \endgraf} - -\bye - diff --git a/tests/marks.tex b/tests/marks.tex deleted file mode 100644 index 9dcf460..0000000 --- a/tests/marks.tex +++ /dev/null @@ -1,10 +0,0 @@ -\input luaotfload.sty -\font\test={file:GenBasR.ttf:script=latn} -%font\test={name:AntykwaTorunska:script=latn} -\test ä\quad Ä - -\test a\char"0308 %% -> combining dihaeresis -\quad A\char"0308 %% -> combining dihaeresis -\quad j\char"0323 %% -> combining dot below - -\bye diff --git a/tests/math.tex b/tests/math.tex deleted file mode 100644 index a2615f1..0000000 --- a/tests/math.tex +++ /dev/null @@ -1,56 +0,0 @@ -% start out with plain.tex, -% having TFM-based CM fonts preloaded in \fam 0...3 - -% load OT math font in \fam 4 -\input luaotfload.sty -\font\4={name:XITS Math:mode=base;script=math} at 10pt -\font\5={name:XITS Math:mode=base;script=math;ssty=1} at 7pt -\font\6={name:XITS Math:mode=node;script=math;ssty=2} at 5pt -\textfont4=\4 \scriptfont4=\5 \scriptscriptfont4=\6 - -\Umathcode`a="7"4"1D44E -\Umathcode`b="7"4"1D44F -\Umathcode`x="7"4"1D465 -\Umathcode`y="7"4"1D466 -\Umathcode`A="7"4"1D434 -\Umathcode`B="7"4"1D435 -\Umathcode`C="7"4"1D436 -\Umathcode`P="7"4"1D443 -\Umathcode`3="7"4`3 -\Umathcode`+="2"4`+ -\Umathcode`=="3"4`= -\Umathcode`(="4"4`( -\Umathcode`)="5"4`) - -\Udelcode`(="4`( -\Udelcode`)="4`) - -$$\Uradical "4 "221A {x}$$ -$$\Uroot    "4 "221A {3}{x^3+y^3}$$ - -$$ -  \Udelimiterover  "4 "23DE {a+b} -+ \Udelimiterunder "4 "23DF {a+b} = C -$$ - -$$ -  \Umathaccent    "0 "4 "23DE {a+b} -$$ - -$$ -A \mathrel{\Uoverdelimiter  "4 "2192 {a+b}} -B \mathrel{\Uunderdelimiter "4 "2192 {a+b}} -$$ - -$$ -{a \overwithdelims() b} -$$ - - -\centerline{$ a \overwithdelims() b $} - -$$ -P(a) -$$ - -\bye diff --git a/tests/microtypography.tex b/tests/microtypography.tex deleted file mode 100644 index 99deb5f..0000000 --- a/tests/microtypography.tex +++ /dev/null @@ -1,36 +0,0 @@ -\input luaotfload.sty - -\pdfprotrudechars2 \pdfadjustspacing2 - -\font\testa=file:texgyretermes-regular:script=latn at 12pt -\font\testb=file:texgyretermes-regular:script=latn;protrusion=default at 12pt - -\testa We thrive in information thick worlds because of our -marvelous and everyday capacity to select, edit, -single out, structure, highlight, group, pair, merge, -harmonize, synthesize, focus, organize, condense, -reduce, boil down, choose, categorize, catalog, classify, -list, abstract, scan, look into, idealize, isolate, -discriminate, distinguish, screen, pigeonhole, pick over, -sort, integrate, blend, inspect, filter, lump, skip, -smooth, chunk, average, approximate, cluster, aggregate, -outline, summarize, itemize, review, dip into, -flip through, browse, glance into, leaf through, skim, -refine, enumerate, glean, synopsize, winnow the wheat -from the chaff and separate the sheep from the goats.\par - -\testb We thrive in information thick worlds because of our -marvelous and everyday capacity to select, edit, -single out, structure, highlight, group, pair, merge, -harmonize, synthesize, focus, organize, condense, -reduce, boil down, choose, categorize, catalog, classify, -list, abstract, scan, look into, idealize, isolate, -discriminate, distinguish, screen, pigeonhole, pick over, -sort, integrate, blend, inspect, filter, lump, skip, -smooth, chunk, average, approximate, cluster, aggregate, -outline, summarize, itemize, review, dip into, -flip through, browse, glance into, leaf through, skim, -refine, enumerate, glean, synopsize, winnow the wheat -from the chaff and separate the sheep from the goats.\par - -\bye diff --git a/tests/opbd.fea b/tests/opbd.fea deleted file mode 100644 index 54f687a..0000000 --- a/tests/opbd.fea +++ /dev/null @@ -1,187 +0,0 @@ -languagesystem DFLT dlft; -languagesystem grek dflt; -languagesystem latn dflt; -languagesystem latn AZE; -languagesystem latn CRT; -languagesystem latn MOL; -languagesystem latn NLD; -languagesystem latn PLK; -languagesystem latn ROM; -languagesystem latn TRK; - -feature rtbd { -  lookupflag 0; -    pos \exclam <100 0 0 0>; -    pos \percent <100 0 0 0>; -    pos \ampersand <100 0 0 0>; -    pos \parenright <300 0 0 0>; -    pos \asterisk <200 0 0 0>; -    pos \plus <250 0 0 0>; -    pos \comma <500 0 0 0>; -    pos \hyphen <500 0 0 0>; -    pos \period <700 0 0 0>; -    pos \slash <300 0 0 0>; -    pos \one <100 0 0 0>; -    pos \seven <50 0 0 0>; -    pos \colon <500 0 0 0>; -    pos \semicolon <500 0 0 0>; -    pos \question <200 0 0 0>; -    pos \at <50 0 0 0>; -    pos \A <50 0 0 0>; -    pos \K <50 0 0 0>; -    pos \L <50 0 0 0>; -    pos \T <50 0 0 0>; -    pos \V <50 0 0 0>; -    pos \W <50 0 0 0>; -    pos \X <50 0 0 0>; -    pos \Y <50 0 0 0>; -    pos \k <50 0 0 0>; -    pos \p <50 0 0 0>; -    pos \r <50 0 0 0>; -    pos \v <50 0 0 0>; -    pos \w <50 0 0 0>; -    pos \x <50 0 0 0>; -    pos \y <70 0 0 0>; -    pos \asciitilde <250 0 0 0>; -    pos \Agrave <50 0 0 0>; -    pos \Aacute <50 0 0 0>; -    pos \Acircumflex <50 0 0 0>; -    pos \Atilde <50 0 0 0>; -    pos \Adieresis <50 0 0 0>; -    pos \Aring <50 0 0 0>; -    pos \Amacron <50 0 0 0>; -    pos \Abreve <50 0 0 0>; -    pos \Aogonek <50 0 0 0>; -    pos \Kcommaaccent <50 0 0 0>; -    pos \kcommaaccent <50 0 0 0>; -    pos \Lacute <50 0 0 0>; -    pos \Lcommaaccent <50 0 0 0>; -    pos \Lcaron <50 0 0 0>; -    pos \Ldot <50 0 0 0>; -    pos \Lslash <50 0 0 0>; -    pos \racute <50 0 0 0>; -    pos \rcommaaccent <50 0 0 0>; -    pos \rcaron <50 0 0 0>; -    pos \wcircumflex <50 0 0 0>; -    pos \ycircumflex <70 0 0 0>; -    pos \Acaron <50 0 0 0>; -    pos \Aringacute <50 0 0 0>; -    pos \Adblgrave <50 0 0 0>; -    pos \rdblgrave <50 0 0 0>; -    pos \uni021A <50 0 0 0>; -    pos \Alpha <50 0 0 0>; -    pos \Kappa <50 0 0 0>; -    pos \Lambda <50 0 0 0>; -    pos \Tau <50 0 0 0>; -    pos \Ldotbelow <50 0 0 0>; -    pos \Ldotbelowmacron <50 0 0 0>; -    pos \rdotaccent <50 0 0 0>; -    pos \rdotbelow <50 0 0 0>; -    pos \rdotbelowmacron <50 0 0 0>; -    pos \Tdotbelow <50 0 0 0>; -    pos \Tlinebelow <50 0 0 0>; -    pos \wgrave <50 0 0 0>; -    pos \wacute <50 0 0 0>; -    pos \wdieresis <50 0 0 0>; -    pos \Ahookabove <50 0 0 0>; -    pos \Acircumflexacute <50 0 0 0>; -    pos \Acircumflexgrave <50 0 0 0>; -    pos \Acircumflexhookabove <50 0 0 0>; -    pos \Acircumflextilde <50 0 0 0>; -    pos \Acircumflexdotbelow <50 0 0 0>; -    pos \Abreveacute <50 0 0 0>; -    pos \Abrevegrave <50 0 0 0>; -    pos \Abrevehookabove <50 0 0 0>; -    pos \Abrevetilde <50 0 0 0>; -    pos \Abrevedotbelow <50 0 0 0>; -    pos \ygrave <70 0 0 0>; -    pos \ydotbelow <70 0 0 0>; -    pos \yhookabove <70 0 0 0>; -    pos \ytilde <70 0 0 0>; -    pos \endash <300 0 0 0>; -    pos \emdash <200 0 0 0>; -    pos \quoteleft <700 0 0 0>; -    pos \quoteright <700 0 0 0>; -    pos \quotedblleft <400 0 0 0>; -    pos \quotedblright <400 0 0 0>; -    pos \Aogonekacute <50 0 0 0>; -    pos \L_uni0303 <50 0 0 0>; -    pos \T_uni0303 <50 0 0 0>; -    pos \T_uni0308 <50 0 0 0>; -} rtbd; - -feature lfbd { -  lookupflag 0; -    pos \percent <-100 0 -100 0>; -    pos \ampersand <-50 0 -50 0>; -    pos \parenleft <-100 0 -100 0>; -    pos \asterisk <-200 0 -200 0>; -    pos \plus <-250 0 -250 0>; -    pos \hyphen <-400 0 -400 0>; -    pos \slash <-200 0 -200 0>; -    pos \one <-100 0 -100 0>; -    pos \at <-50 0 -50 0>; -    pos \A <-50 0 -50 0>; -    pos \J <-50 0 -50 0>; -    pos \T <-50 0 -50 0>; -    pos \V <-50 0 -50 0>; -    pos \W <-50 0 -50 0>; -    pos \X <-50 0 -50 0>; -    pos \Y <-50 0 -50 0>; -    pos \p <-50 0 -50 0>; -    pos \q <-50 0 -50 0>; -    pos \v <-50 0 -50 0>; -    pos \w <-50 0 -50 0>; -    pos \x <-50 0 -50 0>; -    pos \y <-50 0 -50 0>; -    pos \asciitilde <-200 0 -200 0>; -    pos \Agrave <-50 0 -50 0>; -    pos \Aacute <-50 0 -50 0>; -    pos \Acircumflex <-50 0 -50 0>; -    pos \Atilde <-50 0 -50 0>; -    pos \Adieresis <-50 0 -50 0>; -    pos \Aring <-50 0 -50 0>; -    pos \Amacron <-50 0 -50 0>; -    pos \Abreve <-50 0 -50 0>; -    pos \Aogonek <-50 0 -50 0>; -    pos \Jcircumflex <-50 0 -50 0>; -    pos \wcircumflex <-50 0 -50 0>; -    pos \ycircumflex <-50 0 -50 0>; -    pos \Acaron <-50 0 -50 0>; -    pos \Aringacute <-50 0 -50 0>; -    pos \Adblgrave <-50 0 -50 0>; -    pos \uni021A <-50 0 -50 0>; -    pos \Alpha <-50 0 -50 0>; -    pos \Lambda <-50 0 -50 0>; -    pos \Tau <-50 0 -50 0>; -    pos \Tdotbelow <-50 0 -50 0>; -    pos \Tlinebelow <-50 0 -50 0>; -    pos \wgrave <-50 0 -50 0>; -    pos \wacute <-50 0 -50 0>; -    pos \wdieresis <-50 0 -50 0>; -    pos \Ahookabove <-50 0 -50 0>; -    pos \Acircumflexacute <-50 0 -50 0>; -    pos \Acircumflexgrave <-50 0 -50 0>; -    pos \Acircumflexhookabove <-50 0 -50 0>; -    pos \Acircumflextilde <-50 0 -50 0>; -    pos \Acircumflexdotbelow <-50 0 -50 0>; -    pos \Abreveacute <-50 0 -50 0>; -    pos \Abrevegrave <-50 0 -50 0>; -    pos \Abrevehookabove <-50 0 -50 0>; -    pos \Abrevetilde <-50 0 -50 0>; -    pos \Abrevedotbelow <-50 0 -50 0>; -    pos \ygrave <-50 0 -50 0>; -    pos \ydotbelow <-50 0 -50 0>; -    pos \yhookabove <-50 0 -50 0>; -    pos \ytilde <-50 0 -50 0>; -    pos \endash <-300 0 -300 0>; -    pos \emdash <-200 0 -200 0>; -    pos \quoteleft <-500 0 -500 0>; -    pos \quoteright <-500 0 -500 0>; -    pos \quotedblleft <-300 0 -300 0>; -    pos \quotedblright <-300 0 -300 0>; -    pos \Aogonekacute <-50 0 -50 0>; -    pos \J_uni030C.cap <-50 0 -50 0>; -    pos \T_uni0303 <-50 0 -50 0>; -    pos \T_uni0308 <-50 0 -50 0>; -} lfbd; diff --git a/tests/opbd.tex b/tests/opbd.tex deleted file mode 100644 index 1a838cd..0000000 --- a/tests/opbd.tex +++ /dev/null @@ -1,35 +0,0 @@ -\input luaotfload.sty - -\pdfprotrudechars2 \pdfadjustspacing2 - -\font\testa=file:texgyrepagella-regular:script=latn at 12pt -\font\testb=file:texgyrepagella-regular:mode=node;script=latn;protrusion=yes;featurefile=opbd.fea;+opbd at 12pt - -\testa We thrive in information thick worlds because of our -marvelous and everyday capacity to select, edit, -single out, structure, highlight, group, pair, merge, -harmonize, synthesize, focus, organize, condense, -reduce, boil down, choose, categorize, catalog, classify, -list, abstract, scan, look into, idealize, isolate, -discriminate, distinguish, screen, pigeonhole, pick over, -sort, integrate, blend, inspect, filter, lump, skip, -smooth, chunk, average, approximate, cluster, aggregate, -outline, summarize, itemize, review, dip into, -flip through, browse, glance into, leaf through, skim, -refine, enumerate, glean, synopsize, winnow the wheat -from the chaff and separate the sheep from the goats.\par - -\testb We thrive in information thick worlds because of our -marvelous and everyday capacity to select, edit, -single out, structure, highlight, group, pair, merge, -harmonize, synthesize, focus, organize, condense, -reduce, boil down, choose, categorize, catalog, classify, -list, abstract, scan, look into, idealize, isolate, -discriminate, distinguish, screen, pigeonhole, pick over, -sort, integrate, blend, inspect, filter, lump, skip, -smooth, chunk, average, approximate, cluster, aggregate, -outline, summarize, itemize, review, dip into, -flip through, browse, glance into, leaf through, skim, -refine, enumerate, glean, synopsize, winnow the wheat -from the chaff and separate the sheep from the goats.\par -\bye diff --git a/tests/opticalsize.tex b/tests/opticalsize.tex deleted file mode 100644 index 53b1bb8..0000000 --- a/tests/opticalsize.tex +++ /dev/null @@ -1,13 +0,0 @@ -\input luaotfload.sty - -\font\testa={Latin Modern Roman/S=12} at 10pt -\font\testb={Latin Modern Roman}      at 5pt -\font\testc={Latin Modern Roman}      at 10pt -\font\testd={Latin Modern Roman/S=15} at 10pt -\font\teste={Latin Modern Roman/S=3}  at 10pt -\testa abcd ABCD \par -\testb abcd ABCD \par -\testc abcd ABCD \par -\testd abcd ABCD \par -\teste abcd ABCD \par -\bye diff --git a/tests/pln-aux-1.tex b/tests/pln-aux-1.tex deleted file mode 100644 index a228be0..0000000 --- a/tests/pln-aux-1.tex +++ /dev/null @@ -1,55 +0,0 @@ -\input luaotfload.sty - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% usage for glyph tests -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\baselineskip=17.28pt - -\font\iwonaregular=name:iwona at 14.4pt -\font\lmromanten=file:lmroman10-regular.otf at 14.4pt -\font\cmuregular=file:cmunrm.otf at 14.4pt - -%% wrap tests in macros (could move to style file) -\def\doifglyphelse#1#2#3{% -  \directlua{ -    local codepoint = tonumber('\string#1') -    if not codepoint then codepoint = unicode.utf8.byte('\string#1') end -    if luaotfload.aux.font_has_glyph(font.current(), codepoint) then -      tex.sprint('\string#2') -    else -      tex.sprint('\string#3') -    end -  }% -} - -\def\doifglyph#1#2{\doifglyphelse{#1}{#2}{}} - -%% no otf font loaded yet, so both fail: -first: -\doifglyphelse{a}{true}{false} -\doifglyph    {a}{yep} - -%% load lm and try repeat: -\lmromanten -second: -\doifglyphelse{a}{true}{false} -\doifglyph    {a}{yep} - -%% let’s test some more free fonts -\def\checkglyphset{% -  \doifglyphelse  ö{ö}{nope} -  \doifglyphelse  п{п}{nope} -  \doifglyphelse  α{α}{nope} -  \doifglyphelse  Æ{Æ}{nope} -  \doifglyphelse  ą{ą}{nope} -  \doifglyphelse  ř{ř}{nope} -  \doifglyphelse  ˝{˝}{nope} -  \doifglyphelse  ѩ{ѩ}{nope} -  \endgraf -} - -\iwonaregular \checkglyphset -\lmromanten   \checkglyphset -\cmuregular   \checkglyphset - -\bye diff --git a/tests/pln-aux-2.tex b/tests/pln-aux-2.tex deleted file mode 100644 index 62192a5..0000000 --- a/tests/pln-aux-2.tex +++ /dev/null @@ -1,102 +0,0 @@ -\input luaotfload.sty -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% script, features, and language -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -\font\minionregular=file:MinionPro_Regular.otf  at 9pt -\font\biolinum=file:LinBiolinum_R.otf  at 9pt -\font\cmuregular=file:cmunrm.otf  at 9pt - -%% (1) luaotfload.aux.provides_script(font_id, script) -%%      #1 defined font; #2 OT script tag -\def\providesscript[#1][#2]{% -  \bgroup#1% -  let’s see whether \detokenize{#1} has script #2: -  \directlua{ -    local aux = luaotfload.aux -    local succ = aux.provides_script(font.current(), [[#2]]) -    if succ then tex.sprint"true" else tex.sprint"false" end -  }% -  \egroup -  \endgraf% -} - -\providesscript [\minionregular][latn]%% Latin -\providesscript      [\biolinum][latn] -\providesscript    [\cmuregular][latn] -\providesscript [\minionregular][cyrl]%% Cyrillic -\providesscript      [\biolinum][cyrl] -\providesscript    [\cmuregular][cyrl] -\providesscript [\minionregular][tibt]%% Tibetan -\providesscript      [\biolinum][tibt] -\providesscript    [\cmuregular][tibt] - -\hrule % -------------------------------------------------------------- - -%% (2) luaotfload.aux.provides_language(font_id, script, language) -%%      #1 defined font; #2 OT script tag; #3 OT language tag -\def\provideslanguage[#1][#2][#3]{% -  \bgroup#1% -  let’s see whether \detokenize{#1} supports language #3 for script #2: -  \directlua{ -    local aux = luaotfload.aux -    local succ = aux.provides_language(font.current(), [[#2]], [[#3]]) -    if succ then tex.sprint"true" else tex.sprint"false" end -  }% -  \egroup -  \endgraf% -} - -\provideslanguage [\minionregular][latn][nld]%% Latin/Dutch -\provideslanguage      [\biolinum][latn][nld] -\provideslanguage    [\cmuregular][latn][nld] -\provideslanguage [\minionregular][latn][deu]%% Latin/German -\provideslanguage      [\biolinum][latn][deu] -\provideslanguage    [\cmuregular][latn][deu] -\provideslanguage [\minionregular][cyrl][rus]%% Cyrillic/Russian -\provideslanguage      [\biolinum][cyrl][rus] -\provideslanguage    [\cmuregular][cyrl][rus] -\provideslanguage [\minionregular][cyrl][klm]%% Cyrillic/Kalmyk -\provideslanguage      [\biolinum][cyrl][klm] -\provideslanguage    [\cmuregular][cyrl][klm] -\provideslanguage [\minionregular][cyrl][srb]%% Cyrillic/Serbian -\provideslanguage      [\biolinum][cyrl][srb] -\provideslanguage    [\cmuregular][cyrl][srb] -\provideslanguage [\minionregular][tibt][tib]%% Tibetan -\provideslanguage      [\biolinum][tibt][tib] -\provideslanguage    [\cmuregular][tibt][tib] - -\hrule % -------------------------------------------------------------- - -%% (3) luaotfload.aux.provides_feature( -%%                      font_id, script, language, feature) -%%      #1 defined font;    #2 OT script tag; -%%      #3 OT language tag; #4 OT feature -\def\providesfeature[#1][#2][#3][#4]{%this is getting ridiculous -  \bgroup#1% -  let’s see whether \detokenize{#1} supports feature #4 for the -  combination of script #2 with language #3: -  \directlua{ -    local aux = luaotfload.aux -    local succ = aux.provides_feature( -      font.current(), [[#2]], [[#3]], [[#4]]) -    if succ then tex.sprint"true" else tex.sprint"false" end -  }% -  \egroup -  \endgraf% -} - -\providesfeature [\minionregular][latn][nld][liga]%% Latin/Dutch -\providesfeature      [\biolinum][latn][nld][liga] -\providesfeature    [\cmuregular][latn][nld][liga] -\providesfeature [\minionregular][latn][deu][liga]%% Latin/German -\providesfeature      [\biolinum][latn][deu][liga] -\providesfeature    [\cmuregular][latn][deu][liga] -\providesfeature [\minionregular][cyrl][srb][liga]%% Cyrillic/Serbian -\providesfeature      [\biolinum][cyrl][srb][liga] -\providesfeature    [\cmuregular][cyrl][srb][liga] -\providesfeature [\minionregular][tibt][tib][liga]%% Tibetan -\providesfeature      [\biolinum][tibt][tib][liga] -\providesfeature    [\cmuregular][tibt][tib][liga] - -\bye diff --git a/tests/pln-aux-3.tex b/tests/pln-aux-3.tex deleted file mode 100644 index 12a80cf..0000000 --- a/tests/pln-aux-3.tex +++ /dev/null @@ -1,39 +0,0 @@ -\input luaotfload.sty - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% math dimension getter -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -\font\xitsmath=file:xits-math.otf -\font\cambriamath=file:cambria.ttc(1) - -\font\main=file:Iwona-Regular.otf at 12pt\main - -\directlua{ -  local aux = luaotfload.aux -  local test_a = function (fontname, dimension) -    tex.sprint( -      "(", fontname, " (", dimension, " ", -      aux.get_math_dimension(fontname, dimension), -      [[))\endgraf ]]) -  end -     -  local test_b = function (fontname, dimension) -    aux.sprint_math_dimension(fontname, dimension) -    tex.print[[\endgraf ]] -  end - -  test_a("xitsmath",     "AxisHeight") -  test_a("xitsmath",     "RadicalVerticalGap") -  test_a("cambriamath",  "StackTopShiftUp") -  test_a("cambriamath",  "FractionNumeratorGapMin") - -  test_b("xitsmath",     "AxisHeight") -  test_b("xitsmath",     "RadicalVerticalGap") -  test_b("cambriamath",  "StackTopShiftUp") -  test_b("cambriamath",  "FractionNumeratorGapMin") -} - -foo bar baz - -\bye diff --git a/tests/pln-aux-4.tex b/tests/pln-aux-4.tex deleted file mode 100644 index 80ffc0b..0000000 --- a/tests/pln-aux-4.tex +++ /dev/null @@ -1,41 +0,0 @@ -\input luaotfload.sty - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% unicode character mappings -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -\font\ptserifregular = file:PTF55F.ttf \ptserifregular - -%% here we map the function luaotfload.aux.name_of_slot -%% on a short text, printing a list of letters, their -%% code points and names (as specified in the Adobe -%% Glyph List). - -\directlua{ -  local aux = luaotfload.aux -  local cbk = function (str) -    if string.match(str, "^EOF") then -      luatexbase.remove_from_callback("process_input_buffer", "weird") -      return [[the end!]] -    end -    local res = { } -    for chr in string.utfcharacters(str) do -      local val = unicode.utf8.byte(chr) -      local line = chr .. " <> " .. tostring(val) -      line = line .. " <> " .. (aux.name_of_slot(val) or "") -      res[\string#res+1] = line -    end -    return table.concat(res, [[\endgraf]]) -  end - -  luatexbase.add_to_callback("process_input_buffer", cbk, "weird") -} - -Я узнал что у меня -Есть огромная семья -И тропинка и лесок -В поле каждый колосок - -EOF - -\bye diff --git a/tests/pln-request-4-slashed.tex b/tests/pln-request-4-slashed.tex deleted file mode 100644 index 5e7d99e..0000000 --- a/tests/pln-request-4-slashed.tex +++ /dev/null @@ -1,12 +0,0 @@ -\ifdefined\directlua\input luaotfload.sty\fi -\font\iwona           =iwona      at 20pt -\font\iwonabold       =iwona/b    at 20pt -\font\iwonaitalic     =iwona/i    at 20pt -\font\iwonabolditalic =iwona/bi   at 20pt - -\def\test{foo bar baz \endgraf} -{\iwona           \test} -{\iwonabold       \test} -{\iwonaitalic     \test} -{\iwonabolditalic \test} -\bye diff --git a/tests/pln-request-5-cached.tex b/tests/pln-request-5-cached.tex deleted file mode 100644 index 8ba4a5e..0000000 --- a/tests/pln-request-5-cached.tex +++ /dev/null @@ -1,18 +0,0 @@ -\ifdefined\directlua -  \directlua{config = config or { luaotfload = { } } -             config.luaotfload.resolver = "cached" -             config.luaotfload.loglevel = 5 } -  \input luaotfload.sty -\fi - -\font\iwona           =name:iwona      at 20pt -\font\iwonabold       =name:iwona/b    at 20pt -\font\iwonaitalic     =name:iwona/i    at 20pt -\font\iwonabolditalic =name:iwona/bi   at 20pt - -\def\test{foo bar baz \endgraf} -{\iwona           \test} -{\iwonabold       \test} -{\iwonaitalic     \test} -{\iwonabolditalic \test} -\bye diff --git a/tests/pln-subfont-1.tex b/tests/pln-subfont-1.tex deleted file mode 100644 index fb8e1e7..0000000 --- a/tests/pln-subfont-1.tex +++ /dev/null @@ -1,12 +0,0 @@ -\ifdefined\directlua\input luaotfload.sty\fi -%% This requires the Cambria fonts from MS. -\directlua{ -  inspect(fontloader.info"cambria.ttc") -} -%% Here we load both subfonts in the collection -%% with the not-quite documented subfont syntax. -\font\subfontone="file:cambria.ttc(0)" at 42pt -\font\subfonttwo="file:cambria.ttc(1)" at 42pt -\subfontone foo bar baz \endgraf -\subfonttwo foo bar baz \endgraf -\bye diff --git a/tests/pln-tfm.tex b/tests/pln-tfm.tex deleted file mode 100644 index 16ae41a..0000000 --- a/tests/pln-tfm.tex +++ /dev/null @@ -1,10 +0,0 @@ -\ifdefined\directlua\input luaotfload.sty \fi -%% TFM’s can be loaded with a file: request ... -\font\antykwatorunska="file:rm-anttr" -%% or with an anonymous request, like in þe olde TeX: -\font\antykwatorunskabcap=ec-anttbcap -\font\lmromanten={file:ec-lmr10} at 10pt -\antykwatorunska foo bar -\antykwatorunskabcap baz xyzzy  -\lmromanten Donde, está, la biblioteca. Me llamo T-Bone La araña discoteca. -\bye diff --git a/tests/sanitize_color.tex b/tests/sanitize_color.tex deleted file mode 100644 index fe95d37..0000000 --- a/tests/sanitize_color.tex +++ /dev/null @@ -1,8 +0,0 @@ -\input luaotfload.sty -\font\testa={name:Latin Modern Roman:color=0000ff99f} at 10pt -\font\testb={name:Latin Modern Roman:color=0000ff9}   at 10pt -\font\testc={name:Latin Modern Roman:color=0000f}     at 10pt -\testa test\par -\testb test\par -\testc test\par -\bye diff --git a/tests/systemfonts.tex b/tests/systemfonts.tex deleted file mode 100644 index af08509..0000000 --- a/tests/systemfonts.tex +++ /dev/null @@ -1,50 +0,0 @@ -\input luaotfload.sty - -\font\termesr  ={TeX Gyre Termes:+liga}    at 10pt -\font\termesb  ={TeX Gyre Termes/B:+liga}  at 10pt -\font\termesi  ={TeX Gyre Termes/I:+liga}  at 10pt -\font\termesbi ={TeX Gyre Termes/BI:+liga} at 10pt -\font\termesib ={TeX Gyre Termes/IB:+liga} at 10pt -\font\termesicu={TeX Gyre Termes/ICU/B:+liga} at 10pt -\font\termesaat={TeX Gyre Termes/AAT/IB:+liga} at 10pt -\font\termess  ={TeX Gyre Termes/ICU/S=10:+liga} at 10pt -\font\dsans    ={DejaVu Sans:+liga} at 10pt -\font\dsansi   ={DejaVu Sans/I:+liga} at 10pt -\font\dsansb   ={DejaVu Sans/B:+liga} at 10pt -\font\dsansib  ={DejaVu Sans/IB:+liga} at 10pt -\font\dsansextr={DejaVu Sans/Extra Light:+liga} at 10pt -\font\dsansecnd={DejaVu Sans/Condensed:+liga} at 10pt -\font\lmr      ={Latin Modern Roman:+liga} at 10pt -\font\lmf      ={Latin Modern Roman/S=5:+liga} at 10pt -\font\lmb      ={Latin Modern Roman/B:+liga} at 10pt -\font\lmi      ={Latin Modern Roman/I:+liga} at 10pt -\font\lmbi     ={Latin Modern Roman/BI:+liga} at 10pt -\font\lms      ={Latin Modern Roman Slanted:+liga} at 10pt -\font\lmltn    ={Latin Modern Roman:script=latn} at 10pt -%% get this font here: -%% http://sourceforge.net/projects/arabeyes/files/kacst_fonts/kacst_one_5.0.tar.bz2/download -\font\arab     ={KacstOne:mode=node;script=arab} at 10pt - -\termesr   fi fl ffi ffl ff\par -\termesb   fi fl ffi ffl ff\par -\termesi   fi fl ffi ffl ff\par -\termesbi  fi fl ffi ffl ff\par -\termesib  fi fl ffi ffl ff\par -\termesicu fi fl ffi ffl ff\par -\termesaat fi fl ffi ffl ff\par -\termess   fi fl ffi ffl ff\par -\dsans     fi fl ffi ffl ff\par -\dsansi    fi fl ffi ffl ff\par -\dsansb    fi fl ffi ffl ff\par -\dsansib   fi fl ffi ffl ff\par -\dsansextr fi fl ffi ffl ff\par -\dsansecnd fi fl ffi ffl ff\par -\lmr       fi fl ffi ffl ff\par -\lmf       fi fl ffi ffl ff\par -\lmb       fi fl ffi ffl ff\par -\lmi       fi fl ffi ffl ff\par -\lmbi      fi fl ffi ffl ff\par -\lms       fi fl ffi ffl ff\par -\lmltn     fi fl ffi ffl ff\par -\leavevmode\arab\luatextextdir TRT بِسْمِ الله الرَّحْمنِ الرحيم\par -\bye diff --git a/tests/texligatures.tex b/tests/texligatures.tex deleted file mode 100644 index 8317ee1..0000000 --- a/tests/texligatures.tex +++ /dev/null @@ -1,7 +0,0 @@ -\input luaotfload.sty - -\font\testa={file:lmroman10-regular:mode=base;script=latn;+tlig} at 10pt -\font\testb={file:lmroman10-regular:mode=node;script=latn;+tlig} at 10pt -\testa ``fi `fl' ffi--ffl---ff'' !` ?` "\par -\testb ``fi `fl' ffi--ffl---ff'' !` ?` "\par -\bye diff --git a/tests/tfmofm.ltx b/tests/tfmofm.ltx deleted file mode 100644 index 0f9f904..0000000 --- a/tests/tfmofm.ltx +++ /dev/null @@ -1,6 +0,0 @@ -\documentclass{article} -\usepackage{luaotfload} - -\begin{document} -Hello \(1+1=\sqrt{4}\) -\end{document} diff --git a/tests/tkrn.fea b/tests/tkrn.fea deleted file mode 100644 index c83927d..0000000 --- a/tests/tkrn.fea +++ /dev/null @@ -1,8 +0,0 @@ -languagesystem DFLT dflt; -languagesystem latn dflt; - -feature tkrn { -  lookupflag 0; -    pos E X -125; -    pos T <0 0 -166 0> E  <0 -235 0 0>; -} tkrn; diff --git a/tests/weirdfonts.tex b/tests/weirdfonts.tex deleted file mode 100644 index 9cbf8ac..0000000 --- a/tests/weirdfonts.tex +++ /dev/null @@ -1,15 +0,0 @@ -%% non-standard fonts deserve an extra test file -\documentclass{scrartcl} -\usepackage{fontspec} -%% ···································································· -%% libertine monospace -%% ------------------- -%% real-world example from: http://tex.stackexchange.com/q/110566 -%% causing database lookups to fail; addressed in luaotfload since -%% https://github.com/phi-gamma/luaotfload/commit/4d0d2c19ab36d4918a72041a087fbcb451ac8c52 -\setmonofont{Linux Libertine Mono O} -%% ···································································· - -\begin{document} -  foo {\ttfamily bar} baz -\end{document} diff --git a/tests/zero_width_marks_lig.tex b/tests/zero_width_marks_lig.tex deleted file mode 100644 index 2c6dba9..0000000 --- a/tests/zero_width_marks_lig.tex +++ /dev/null @@ -1,16 +0,0 @@ -\input luaotfload.sty - -% https://bugs.freedesktop.org/attachment.cgi?id=72363 -\font\testa=file:TestLig.ttf:script=tibt;+ccmp+abvs+blws+kern at 10pt - -\testa གཚོའི་ཁིའུ་ཨཱཿཀ - -% good result for the first part:  -% https://bugs.freedesktop.org/attachment.cgi?id=72365 -% for the second part, the two circles shoud appear clearlybefore the last  -% letter, not mixed with it - -% see http://lists.freedesktop.org/archives/harfbuzz/2013-April/003101.html -% for some technical details. - -\bye  | 
