summaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2022-02-28 03:02:40 +0000
committerNorbert Preining <norbert@preining.info>2022-02-28 03:02:40 +0000
commit7a8d1925127ad121a8f591db931524f9b8eacb93 (patch)
treedf5084184ea4d6732d6a9fae1d263b6915dbb36d /macros
parenta911d39178540078c8ff5bc880418478d7433c40 (diff)
CTAN sync 202202280302
Diffstat (limited to 'macros')
-rw-r--r--macros/latex/contrib/create-theorem/LICENSE416
-rw-r--r--macros/latex/contrib/create-theorem/README.md11
-rw-r--r--macros/latex/contrib/create-theorem/create-theorem-doc.pdfbin0 -> 111285 bytes
-rw-r--r--macros/latex/contrib/create-theorem/create-theorem-doc.tex385
-rw-r--r--macros/latex/contrib/create-theorem/create-theorem.sty1458
-rw-r--r--macros/latex/contrib/crefthe/crefthe-doc.pdfbin112174 -> 115616 bytes
-rw-r--r--macros/latex/contrib/crefthe/crefthe-doc.tex21
-rw-r--r--macros/latex/contrib/crefthe/crefthe.sty80
-rw-r--r--macros/latex/contrib/fnpct/README7
-rw-r--r--macros/latex/contrib/fnpct/fnpct-2019-10-05.sty1191
-rw-r--r--macros/latex/contrib/fnpct/fnpct-manual.cls6
-rw-r--r--macros/latex/contrib/fnpct/fnpct-manual.pdfbin509779 -> 494932 bytes
-rw-r--r--macros/latex/contrib/fnpct/fnpct-manual.tex28
-rw-r--r--macros/latex/contrib/fnpct/fnpct.sty51
-rw-r--r--macros/latex/contrib/projlib/README.md24
-rw-r--r--macros/latex/contrib/projlib/new-modules/projlib-author.sty312
-rw-r--r--macros/latex/contrib/projlib/new-modules/projlib-datetime.sty1645
-rw-r--r--macros/latex/contrib/projlib/new-modules/projlib-draft.sty140
-rw-r--r--macros/latex/contrib/projlib/new-modules/projlib-font.sty137
-rw-r--r--macros/latex/contrib/projlib/new-modules/projlib-language.sty508
-rw-r--r--macros/latex/contrib/projlib/new-modules/projlib-logo.sty100
-rw-r--r--macros/latex/contrib/projlib/new-modules/projlib-math.sty234
-rw-r--r--macros/latex/contrib/projlib/new-modules/projlib-paper.sty108
-rw-r--r--macros/luatex/generic/luaotfload/NEWS6
-rw-r--r--macros/luatex/generic/luaotfload/README.md4
-rw-r--r--macros/luatex/generic/luaotfload/filegraph.pdfbin40019 -> 40007 bytes
-rw-r--r--macros/luatex/generic/luaotfload/fontloader-basics-gen.lua2
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-auxiliary.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-colors.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-conf.pdfbin77690 -> 77520 bytes
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-configuration.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-database.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-diagnostics.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-dvi.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-embolden.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-fallback.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-features.lua9
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-filelist.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-glyphlist.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-harf-define.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-harf-plug.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-harf-var-cff2.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-harf-var-t2-writer.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-harf-var-ttf.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-init.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-latex.pdfbin494376 -> 493487 bytes
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-letterspace.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-loaders.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-log.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-main.tex10
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-multiscript.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-notdef.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-parsers.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-resolvers.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-scripts.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-status.lua62
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-suppress.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-szss.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-t3.lua142
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-tool.12
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-tool.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-tool.pdfbin76959 -> 76788 bytes
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-tool.rst4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-tounicode.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload-unicode.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload.conf.52
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload.conf.rst4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload.lua4
-rw-r--r--macros/luatex/generic/luaotfload/luaotfload.sty2
-rw-r--r--macros/luatex/generic/luaotfload/scripts-demo.pdfbin22075 -> 21673 bytes
-rw-r--r--macros/luatex/generic/luaotfload/shaper-demo-graphite.pdfbin8560 -> 8548 bytes
-rw-r--r--macros/luatex/generic/luaotfload/shaper-demo.pdfbin8481 -> 8469 bytes
-rw-r--r--macros/luatex/generic/lutabulartools/README.md5
-rw-r--r--macros/luatex/generic/lutabulartools/lutabulartools.lua177
-rw-r--r--macros/luatex/generic/lutabulartools/lutabulartools.pdfbin59544 -> 83452 bytes
-rw-r--r--macros/luatex/generic/lutabulartools/lutabulartools.sty102
-rw-r--r--macros/luatex/generic/lutabulartools/lutabulartools.tex198
-rw-r--r--macros/luatex/generic/penlight/penlight.lua2256
-rw-r--r--macros/luatex/generic/penlight/penlight.pdfbin29642 -> 38237 bytes
-rw-r--r--macros/luatex/generic/penlight/penlight.sty12
-rw-r--r--macros/luatex/generic/penlight/penlight.tex109
-rw-r--r--macros/luatex/generic/penlight/penlightextras.lua738
-rw-r--r--macros/unicodetex/latex/book-of-common-prayer/README.md (renamed from macros/xetex/latex/book-of-common-prayer/README.md)6
-rw-r--r--macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.pdfbin0 -> 128251 bytes
-rw-r--r--macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.sty (renamed from macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.sty)33
-rw-r--r--macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.tex (renamed from macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.tex)73
-rw-r--r--macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.pdfbin95265 -> 0 bytes
87 files changed, 9366 insertions, 1574 deletions
diff --git a/macros/latex/contrib/create-theorem/LICENSE b/macros/latex/contrib/create-theorem/LICENSE
new file mode 100644
index 0000000000..2244313901
--- /dev/null
+++ b/macros/latex/contrib/create-theorem/LICENSE
@@ -0,0 +1,416 @@
+The LaTeX Project Public License
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+LPPL Version 1.3c 2008-05-04
+
+Copyright 1999 2002-2008 LaTeX3 Project
+ Everyone is allowed to distribute verbatim copies of this
+ license document, but modification of it is not allowed.
+
+
+PREAMBLE
+========
+
+The LaTeX Project Public License (LPPL) is the primary license under
+which the LaTeX kernel and the base LaTeX packages are distributed.
+
+You may use this license for any work of which you hold the copyright
+and which you wish to distribute. This license may be particularly
+suitable if your work is TeX-related (such as a LaTeX package), but
+it is written in such a way that you can use it even if your work is
+unrelated to TeX.
+
+The section `WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE',
+below, gives instructions, examples, and recommendations for authors
+who are considering distributing their works under this license.
+
+This license gives conditions under which a work may be distributed
+and modified, as well as conditions under which modified versions of
+that work may be distributed.
+
+We, the LaTeX3 Project, believe that the conditions below give you
+the freedom to make and distribute modified versions of your work
+that conform with whatever technical specifications you wish while
+maintaining the availability, integrity, and reliability of
+that work. If you do not see how to achieve your goal while
+meeting these conditions, then read the document `cfgguide.tex'
+and `modguide.tex' in the base LaTeX distribution for suggestions.
+
+
+DEFINITIONS
+===========
+
+In this license document the following terms are used:
+
+ `Work'
+ Any work being distributed under this License.
+
+ `Derived Work'
+ Any work that under any applicable law is derived from the Work.
+
+ `Modification'
+ Any procedure that produces a Derived Work under any applicable
+ law -- for example, the production of a file containing an
+ original file associated with the Work or a significant portion of
+ such a file, either verbatim or with modifications and/or
+ translated into another language.
+
+ `Modify'
+ To apply any procedure that produces a Derived Work under any
+ applicable law.
+
+ `Distribution'
+ Making copies of the Work available from one person to another, in
+ whole or in part. Distribution includes (but is not limited to)
+ making any electronic components of the Work accessible by
+ file transfer protocols such as FTP or HTTP or by shared file
+ systems such as Sun's Network File System (NFS).
+
+ `Compiled Work'
+ A version of the Work that has been processed into a form where it
+ is directly usable on a computer system. This processing may
+ include using installation facilities provided by the Work,
+ transformations of the Work, copying of components of the Work, or
+ other activities. Note that modification of any installation
+ facilities provided by the Work constitutes modification of the Work.
+
+ `Current Maintainer'
+ A person or persons nominated as such within the Work. If there is
+ no such explicit nomination then it is the `Copyright Holder' under
+ any applicable law.
+
+ `Base Interpreter'
+ A program or process that is normally needed for running or
+ interpreting a part or the whole of the Work.
+
+ A Base Interpreter may depend on external components but these
+ are not considered part of the Base Interpreter provided that each
+ external component clearly identifies itself whenever it is used
+ interactively. Unless explicitly specified when applying the
+ license to the Work, the only applicable Base Interpreter is a
+ `LaTeX-Format' or in the case of files belonging to the
+ `LaTeX-format' a program implementing the `TeX language'.
+
+
+
+CONDITIONS ON DISTRIBUTION AND MODIFICATION
+===========================================
+
+1. Activities other than distribution and/or modification of the Work
+are not covered by this license; they are outside its scope. In
+particular, the act of running the Work is not restricted and no
+requirements are made concerning any offers of support for the Work.
+
+2. You may distribute a complete, unmodified copy of the Work as you
+received it. Distribution of only part of the Work is considered
+modification of the Work, and no right to distribute such a Derived
+Work may be assumed under the terms of this clause.
+
+3. You may distribute a Compiled Work that has been generated from a
+complete, unmodified copy of the Work as distributed under Clause 2
+above, as long as that Compiled Work is distributed in such a way that
+the recipients may install the Compiled Work on their system exactly
+as it would have been installed if they generated a Compiled Work
+directly from the Work.
+
+4. If you are the Current Maintainer of the Work, you may, without
+restriction, modify the Work, thus creating a Derived Work. You may
+also distribute the Derived Work without restriction, including
+Compiled Works generated from the Derived Work. Derived Works
+distributed in this manner by the Current Maintainer are considered to
+be updated versions of the Work.
+
+5. If you are not the Current Maintainer of the Work, you may modify
+your copy of the Work, thus creating a Derived Work based on the Work,
+and compile this Derived Work, thus creating a Compiled Work based on
+the Derived Work.
+
+6. If you are not the Current Maintainer of the Work, you may
+distribute a Derived Work provided the following conditions are met
+for every component of the Work unless that component clearly states
+in the copyright notice that it is exempt from that condition. Only
+the Current Maintainer is allowed to add such statements of exemption
+to a component of the Work.
+
+ a. If a component of this Derived Work can be a direct replacement
+ for a component of the Work when that component is used with the
+ Base Interpreter, then, wherever this component of the Work
+ identifies itself to the user when used interactively with that
+ Base Interpreter, the replacement component of this Derived Work
+ clearly and unambiguously identifies itself as a modified version
+ of this component to the user when used interactively with that
+ Base Interpreter.
+
+ b. Every component of the Derived Work contains prominent notices
+ detailing the nature of the changes to that component, or a
+ prominent reference to another file that is distributed as part
+ of the Derived Work and that contains a complete and accurate log
+ of the changes.
+
+ c. No information in the Derived Work implies that any persons,
+ including (but not limited to) the authors of the original version
+ of the Work, provide any support, including (but not limited to)
+ the reporting and handling of errors, to recipients of the
+ Derived Work unless those persons have stated explicitly that
+ they do provide such support for the Derived Work.
+
+ d. You distribute at least one of the following with the Derived Work:
+
+ 1. A complete, unmodified copy of the Work;
+ if your distribution of a modified component is made by
+ offering access to copy the modified component from a
+ designated place, then offering equivalent access to copy
+ the Work from the same or some similar place meets this
+ condition, even though third parties are not compelled to
+ copy the Work along with the modified component;
+
+ 2. Information that is sufficient to obtain a complete,
+ unmodified copy of the Work.
+
+7. If you are not the Current Maintainer of the Work, you may
+distribute a Compiled Work generated from a Derived Work, as long as
+the Derived Work is distributed to all recipients of the Compiled
+Work, and as long as the conditions of Clause 6, above, are met with
+regard to the Derived Work.
+
+8. The conditions above are not intended to prohibit, and hence do not
+apply to, the modification, by any method, of any component so that it
+becomes identical to an updated version of that component of the Work as
+it is distributed by the Current Maintainer under Clause 4, above.
+
+9. Distribution of the Work or any Derived Work in an alternative
+format, where the Work or that Derived Work (in whole or in part) is
+then produced by applying some process to that format, does not relax or
+nullify any sections of this license as they pertain to the results of
+applying that process.
+
+10. a. A Derived Work may be distributed under a different license
+ provided that license itself honors the conditions listed in
+ Clause 6 above, in regard to the Work, though it does not have
+ to honor the rest of the conditions in this license.
+
+ b. If a Derived Work is distributed under a different license, that
+ Derived Work must provide sufficient documentation as part of
+ itself to allow each recipient of that Derived Work to honor the
+ restrictions in Clause 6 above, concerning changes from the Work.
+
+11. This license places no restrictions on works that are unrelated to
+the Work, nor does this license place any restrictions on aggregating
+such works with the Work by any means.
+
+12. Nothing in this license is intended to, or may be used to, prevent
+complete compliance by all parties with all applicable laws.
+
+
+NO WARRANTY
+===========
+
+There is no warranty for the Work. Except when otherwise stated in
+writing, the Copyright Holder provides the Work `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 Work is with you. Should the Work prove defective, you assume
+the cost of all necessary servicing, repair, or correction.
+
+In no event unless required by applicable law or agreed to in writing
+will The Copyright Holder, or any author named in the components of the
+Work, or any other party who may distribute and/or modify the Work as
+permitted above, be liable to you for damages, including any general,
+special, incidental or consequential damages arising out of any use of
+the Work or out of inability to use the Work (including, but not limited
+to, loss of data, data being rendered inaccurate, or losses sustained by
+anyone as a result of any failure of the Work to operate with any other
+programs), even if the Copyright Holder or said author or said other
+party has been advised of the possibility of such damages.
+
+
+MAINTENANCE OF THE WORK
+=======================
+
+The Work has the status `author-maintained' if the Copyright Holder
+explicitly and prominently states near the primary copyright notice in
+the Work that the Work can only be maintained by the Copyright Holder
+or simply that it is `author-maintained'.
+
+The Work has the status `maintained' if there is a Current Maintainer
+who has indicated in the Work that they are willing to receive error
+reports for the Work (for example, by supplying a valid e-mail
+address). It is not required for the Current Maintainer to acknowledge
+or act upon these error reports.
+
+The Work changes from status `maintained' to `unmaintained' if there
+is no Current Maintainer, or the person stated to be Current
+Maintainer of the work cannot be reached through the indicated means
+of communication for a period of six months, and there are no other
+significant signs of active maintenance.
+
+You can become the Current Maintainer of the Work by agreement with
+any existing Current Maintainer to take over this role.
+
+If the Work is unmaintained, you can become the Current Maintainer of
+the Work through the following steps:
+
+ 1. Make a reasonable attempt to trace the Current Maintainer (and
+ the Copyright Holder, if the two differ) through the means of
+ an Internet or similar search.
+
+ 2. If this search is successful, then enquire whether the Work
+ is still maintained.
+
+ a. If it is being maintained, then ask the Current Maintainer
+ to update their communication data within one month.
+
+ b. If the search is unsuccessful or no action to resume active
+ maintenance is taken by the Current Maintainer, then announce
+ within the pertinent community your intention to take over
+ maintenance. (If the Work is a LaTeX work, this could be
+ done, for example, by posting to comp.text.tex.)
+
+ 3a. If the Current Maintainer is reachable and agrees to pass
+ maintenance of the Work to you, then this takes effect
+ immediately upon announcement.
+
+ b. If the Current Maintainer is not reachable and the Copyright
+ Holder agrees that maintenance of the Work be passed to you,
+ then this takes effect immediately upon announcement.
+
+ 4. If you make an `intention announcement' as described in 2b. above
+ and after three months your intention is challenged neither by
+ the Current Maintainer nor by the Copyright Holder nor by other
+ people, then you may arrange for the Work to be changed so as
+ to name you as the (new) Current Maintainer.
+
+ 5. If the previously unreachable Current Maintainer becomes
+ reachable once more within three months of a change completed
+ under the terms of 3b) or 4), then that Current Maintainer must
+ become or remain the Current Maintainer upon request provided
+ they then update their communication data within one month.
+
+A change in the Current Maintainer does not, of itself, alter the fact
+that the Work is distributed under the LPPL license.
+
+If you become the Current Maintainer of the Work, you should
+immediately provide, within the Work, a prominent and unambiguous
+statement of your status as Current Maintainer. You should also
+announce your new status to the same pertinent community as
+in 2b) above.
+
+
+WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE
+======================================================
+
+This section contains important instructions, examples, and
+recommendations for authors who are considering distributing their
+works under this license. These authors are addressed as `you' in
+this section.
+
+Choosing This License or Another License
+----------------------------------------
+
+If for any part of your work you want or need to use *distribution*
+conditions that differ significantly from those in this license, then
+do not refer to this license anywhere in your work but, instead,
+distribute your work under a different license. You may use the text
+of this license as a model for your own license, but your license
+should not refer to the LPPL or otherwise give the impression that
+your work is distributed under the LPPL.
+
+The document `modguide.tex' in the base LaTeX distribution explains
+the motivation behind the conditions of this license. It explains,
+for example, why distributing LaTeX under the GNU General Public
+License (GPL) was considered inappropriate. Even if your work is
+unrelated to LaTeX, the discussion in `modguide.tex' may still be
+relevant, and authors intending to distribute their works under any
+license are encouraged to read it.
+
+A Recommendation on Modification Without Distribution
+-----------------------------------------------------
+
+It is wise never to modify a component of the Work, even for your own
+personal use, without also meeting the above conditions for
+distributing the modified component. While you might intend that such
+modifications will never be distributed, often this will happen by
+accident -- you may forget that you have modified that component; or
+it may not occur to you when allowing others to access the modified
+version that you are thus distributing it and violating the conditions
+of this license in ways that could have legal implications and, worse,
+cause problems for the community. It is therefore usually in your
+best interest to keep your copy of the Work identical with the public
+one. Many works provide ways to control the behavior of that work
+without altering any of its licensed components.
+
+How to Use This License
+-----------------------
+
+To use this license, place in each of the components of your work both
+an explicit copyright notice including your name and the year the work
+was authored and/or last substantially modified. Include also a
+statement that the distribution and/or modification of that
+component is constrained by the conditions in this license.
+
+Here is an example of such a notice and statement:
+
+ %% pig.dtx
+ %% Copyright 2005 M. Y. Name
+ %
+ % This work may be distributed and/or modified under the
+ % conditions of the LaTeX Project Public License, either version 1.3
+ % of this license or (at your option) any later version.
+ % The latest version of this license is in
+ % http://www.latex-project.org/lppl.txt
+ % and version 1.3 or later is part of all distributions of LaTeX
+ % version 2005/12/01 or later.
+ %
+ % This work has the LPPL maintenance status `maintained'.
+ %
+ % The Current Maintainer of this work is M. Y. Name.
+ %
+ % This work consists of the files pig.dtx and pig.ins
+ % and the derived file pig.sty.
+
+Given such a notice and statement in a file, the conditions
+given in this license document would apply, with the `Work' referring
+to the three files `pig.dtx', `pig.ins', and `pig.sty' (the last being
+generated from `pig.dtx' using `pig.ins'), the `Base Interpreter'
+referring to any `LaTeX-Format', and both `Copyright Holder' and
+`Current Maintainer' referring to the person `M. Y. Name'.
+
+If you do not want the Maintenance section of LPPL to apply to your
+Work, change `maintained' above into `author-maintained'.
+However, we recommend that you use `maintained', as the Maintenance
+section was added in order to ensure that your Work remains useful to
+the community even when you can no longer maintain and support it
+yourself.
+
+Derived Works That Are Not Replacements
+---------------------------------------
+
+Several clauses of the LPPL specify means to provide reliability and
+stability for the user community. They therefore concern themselves
+with the case that a Derived Work is intended to be used as a
+(compatible or incompatible) replacement of the original Work. If
+this is not the case (e.g., if a few lines of code are reused for a
+completely different task), then clauses 6b and 6d shall not apply.
+
+
+Important Recommendations
+-------------------------
+
+ Defining What Constitutes the Work
+
+ The LPPL requires that distributions of the Work contain all the
+ files of the Work. It is therefore important that you provide a
+ way for the licensee to determine which files constitute the Work.
+ This could, for example, be achieved by explicitly listing all the
+ files of the Work near the copyright notice of each file or by
+ using a line such as:
+
+ % This work consists of all files listed in manifest.txt.
+
+ in that place. In the absence of an unequivocal list it might be
+ impossible for the licensee to determine what is considered by you
+ to comprise the Work and, in such a case, the licensee would be
+ entitled to make reasonable conjectures as to which files comprise
+ the Work.
+
diff --git a/macros/latex/contrib/create-theorem/README.md b/macros/latex/contrib/create-theorem/README.md
new file mode 100644
index 0000000000..80f2a02181
--- /dev/null
+++ b/macros/latex/contrib/create-theorem/README.md
@@ -0,0 +1,11 @@
+<!-- Copyright (C) 2021-2022 by Jinwen XU -->
+
+# `create-theorem` - Initializing theorem-like environments with multilingual support
+
+The package `create-theorem` provides the commands `\NameTheorem` and `\CreateTheorem` for naming and initializing theorem-like environments. They both have key-value based interface and are especially useful in multi-language documents, allowing you to easily declare theorem-like environments that automatically adapt to the language settings.
+
+*For more information, please refer to its documentation.*
+
+# License
+
+This work is released under the LaTeX Project Public License, v1.3c or later.
diff --git a/macros/latex/contrib/create-theorem/create-theorem-doc.pdf b/macros/latex/contrib/create-theorem/create-theorem-doc.pdf
new file mode 100644
index 0000000000..52d4bd1e93
--- /dev/null
+++ b/macros/latex/contrib/create-theorem/create-theorem-doc.pdf
Binary files differ
diff --git a/macros/latex/contrib/create-theorem/create-theorem-doc.tex b/macros/latex/contrib/create-theorem/create-theorem-doc.tex
new file mode 100644
index 0000000000..c077649309
--- /dev/null
+++ b/macros/latex/contrib/create-theorem/create-theorem-doc.tex
@@ -0,0 +1,385 @@
+%! TEX program = xelatex
+\documentclass[classical]{einfart}
+
+% \linenumbers
+
+\usepackage{ProjLib}
+
+\makeatletter
+\pretocmd{\@maketitle}{\vspace*{-2.5\baselineskip}}{}{}
+\makeatother
+
+%%================================
+%% For typesetting code
+%%================================
+\usepackage{listings}
+\definecolor{maintheme}{RGB}{70,130,180}
+\definecolor{forestgreen}{RGB}{21,122,81}
+\definecolor{lightergray}{gray}{0.99}
+\lstset{language=[LaTeX]TeX,
+ keywordstyle=\color{maintheme},
+ basicstyle=\ttfamily,
+ commentstyle=\color{forestgreen}\ttfamily,
+ stringstyle=\rmfamily,
+ showstringspaces=false,
+ breaklines=true,
+ frame=lines,
+ backgroundcolor=\color{lightergray},
+ flexiblecolumns=true,
+ escapeinside={(*}{*)},
+ % numbers=left,
+ numberstyle=\scriptsize, stepnumber=1, numbersep=5pt,
+ % firstnumber=last,
+}
+\providecommand{\meta}[1]{$\langle${\normalfont\itshape#1}$\rangle$}
+\lstset{moretexcs=%
+ {
+ cref,crefname,
+ Cref,Crefname,
+ crefformat,
+ crefthe,crefthename,crefthemark,
+ Crefthe,Crefthename,
+ selectlanguage,
+ theoremstyle,
+ NameTheorem, CreateTheorem,
+ regexpatchcmd,
+ }
+}
+\lstnewenvironment{code}%
+{\setstretch{1.07}\LocallyStopLineNumbers%
+\setkeys{lst}{columns=fullflexible,keepspaces=true}%
+}
+{\ResumeLineNumbers\vspace{-.25\baselineskip}}
+\lstnewenvironment{code*}%
+{\setstretch{1.07}\LocallyStopLineNumbers%
+\setkeys{lst}{numbers=left,columns=fullflexible,keepspaces=true}%
+}
+{\ResumeLineNumbers}
+
+%%================================
+%% tip
+%%================================
+\usepackage[many]{tcolorbox}
+\newenvironment{tip}[1][Tip]{%
+ \LocallyStopLineNumbers%
+ \begin{tcolorbox}[breakable,
+ enhanced,
+ width = \textwidth,
+ colback = paper, colbacktitle = paper,
+ colframe = gray!50, boxrule=0.2mm,
+ coltitle = black,
+ fonttitle = \sffamily,
+ attach boxed title to top left = {yshift=-\tcboxedtitleheight/2, xshift=.5cm},
+ boxed title style = {boxrule=0pt, colframe=paper},
+ before skip = 3mm,
+ after skip = 3mm,
+ top = 2.5mm,
+ bottom = 1.5mm,
+ title={\scshape\sffamily #1}]%
+}{\end{tcolorbox}\ResumeLineNumbers}
+
+
+\newcommand{\packageoption}[1]{\texttt{\textcolor{black!67!green}{#1}}}
+\newcommand{\commandoption}[1]{\texttt{\textcolor{black!67!cyan}{#1}}}
+
+
+\newcommand{\createtheorempackage}{\textsf{create-theorem}}
+
+\begin{document}
+
+\title{\createtheorempackage{}\\\smallskip\itshape Initializing theorem-like environments with multilingual support}
+\author{Jinwen XU}
+\thanks{Corresponding to: \texttt{\createtheorempackage{} 2022/02/27}}
+\date{February 2022, in Paris}
+
+\maketitle
+
+\begin{abstract}
+ \raggedleft
+ The package \createtheorempackage{} provides the commands \lstinline|\NameTheorem| and \lstinline|\CreateTheorem| for naming and initializing theorem-like environments. They both have key-value based interface and are especially useful in multi-language documents, allowing you to easily declare theorem-like environments that automatically adapt to the language settings.
+\end{abstract}
+
+\vspace{-.5\baselineskip}
+
+% \section*{Before everything}
+
+% Make sure that the package \textsf{crefthe} is correctly installed. If you are using TeX Live 2022 or newer, or the newest version of MikTeX, then nothing needs to be worried.
+
+\section{How to load it}
+
+First, you need a backend to provide the command \lstinline|\newtheorem| with the usual behaviour, for example, \textsf{amsthm}. After that, you can simply load the current package with:
+
+\begin{code}
+\usepackage{create-theorem}
+\end{code}
+
+\begin{tip}
+ Since \createtheorempackage{} uses \textsf{cleveref} internally, it should usually be placed near the last of your preamble --- notably, it needs to be loaded after \textsf{varioref} and \textsf{hyperref}.
+\end{tip}
+
+It has the following options:
+\begin{itemize}
+ \item \packageoption{draft} or \packageoption{fast}
+ \begin{itemize}
+ \item Fast mode. The functionality will be appropriately reduced to get faster compilation speed, recommended to use during the writing stage.
+ \end{itemize}
+ \item \packageoption{name as context}
+ \begin{itemize}
+ \item When referencing, the names correspond to the current language context. For example, the English names will be displayed when referencing a theorem-like environment in English context, no matter which language context the original environment is in.
+ \item Synonymous names: \packageoption{name-as-context} \,$|$\, \packageoption{nameascontext} \,$|$\, \packageoption{regionalref}
+ \end{itemize}
+ \item \packageoption{name as is}
+ \begin{itemize}
+ \item When referencing, the names correspond to the language contexts in which the environments are defined. For example, if the environment is defined in English context, then the English names will be displayed when referencing it, regardless of the current language context.
+ \item Synonymous names: \packageoption{name-as-is} \,$|$\, \packageoption{nameasis} \,$|$\, \packageoption{originalref}
+ \end{itemize}
+ \item \packageoption{name in link}
+ \begin{itemize}
+ \item Include the names in the hyperlinks when referencing.
+ \item Synonymous names: \packageoption{name-in-link} \,$|$\, \packageoption{nameinlink}
+ \end{itemize}
+ \item \packageoption{no preset names}
+ \begin{itemize}
+ \item Disable preset names. Use this option if you want to define you own name set.
+ \item Synonymous names: \packageoption{no-preset-names} \,$|$\, \packageoption{nopresetnames}
+ \end{itemize}
+\end{itemize}
+
+\section{How to use it}
+
+\subsection[Naming theorem-like environments]{Naming theorem-like environments with \texttt{\textbackslash\textcolor{maintheme}{NameTheorem}}}
+
+The syntax of \lstinline|\NameTheorem| is as follows:
+\begin{code}
+\NameTheorem{(*\meta{name of environment}*)}{(*\meta{key-value configuration}*)}
+\end{code}
+
+Supported keys are:
+\vspace{-.2\baselineskip}
+\begin{itemize}
+ \item \commandoption{heading}\lstinline| = |\meta{configuration}
+ \begin{itemize}
+ \item The heading of the environment, where \meta{configuration} can be:
+ \begin{itemize}
+ \item a single string in monolingual documents: \commandoption{heading}\lstinline| = |\meta{string};
+ \item a key-value name list in multilingual documents:
+\begin{code}
+(*\commandoption{heading}*) = {
+ (*\meta{language name}*) = (*\meta{string}*)
+}
+\end{code}
+ \end{itemize}
+ \end{itemize}
+ \item \commandoption{crefname}\lstinline| = |\meta{configuration}
+ \begin{itemize}
+ \item The name for \lstinline|\cref| the environment, where \meta{configuration} can be:
+ \begin{itemize}
+ \item a single string in monolingual documents: \commandoption{crefname}\lstinline| = {name}{names}|;
+ \item a key-value name list in multilingual documents:
+\begin{code}
+(*\commandoption{crefname}*) = {
+ (*\meta{language name}*) = {(*\meta{singular name}*)}{(*\meta{plural name}*)}
+}
+\end{code}
+ \end{itemize}
+ \item Also supports the syntax of \lstinline|\crefthename|, thus you can assign names of the form:
+\begin{code}
+[(*\meta{singular definite article}*)]{(*\meta{singular name}*)}[(*\meta{plural definite article}*)]{(*\meta{plural name}*)}
+\end{code}
+ \end{itemize}
+ \item \commandoption{Crefname}
+ \begin{itemize}
+ \item The name for \lstinline|\Cref| the environment, its syntax is the same as that of \commandoption{crefname}.
+ \item Also supports the syntax of \lstinline|\Crefthename|.
+ \end{itemize}
+\end{itemize}
+
+\begin{center}
+ \itshape
+ If you're feeling confused, don't worry, there is an Examples section later illustrating its usage.
+\end{center}
+
+\begin{tip}
+ You can also define the names within \lstinline|\CreateTheorem| while initializing the theorem-like environments. \lstinline|\NameTheorem| is especially useful for package or class authors who wish to preset suitable names (with styles) in their packages or classes.
+\end{tip}
+
+
+\subsection[Initializing theorem-like environments]{Initializing theorem-like environments with \texttt{\textbackslash\textcolor{maintheme}{CreateTheorem}}}
+
+The syntax of \lstinline|\CreateTheorem| is as follows:
+\begin{code}
+\CreateTheorem{(*\meta{name of environment}*)}{(*\meta{key-value configuration}*)}
+\end{code}
+
+Supported keys are:
+\vspace{-.2\baselineskip}
+\begin{itemize}
+ \item \commandoption{name}\lstinline| = |\meta{configuration}
+ \begin{itemize}
+ \item Same as \lstinline|\NameTheorem{|\meta{name of environment}\lstinline|}{|\meta{configuration}\lstinline|}|.
+ \end{itemize}
+ \item \commandoption{style}\lstinline| = |\meta{theorem style}
+ \begin{itemize}
+ \item Specifying the \lstinline|\theoremstyle| for the current environment.
+ \item Synonymous names: \commandoption{apply style} \,$|$\, \commandoption{apply-style} \,$|$\, \commandoption{applystyle}
+ \end{itemize}
+ \item \commandoption{parent counter}\lstinline| = |\meta{parent counter}
+ \begin{itemize}
+ \item Specifying the \meta{parent counter} for the current environment, \emph{i.e.}, numbering will restart whenever that sectional level is encountered.
+ \item Synonymous names: \commandoption{parent-counter} \,$|$\, \commandoption{parentcounter} \,$|$\, \\\phantom{Synonymous names: }\commandoption{number within} \,$|$\, \commandoption{number-within} \,$|$\, \commandoption{numberwithin}
+ \end{itemize}
+ \item \commandoption{shared counter}\lstinline| = |\meta{shared counter}
+ \begin{itemize}
+ \item Specifying the \meta{shared counter} for the current environment, \emph{i.e.}, numbering will progress sequentially for all theorem-like environments using this counter.
+ \item Synonymous names: \commandoption{shared-counter} \,$|$\, \commandoption{sharedcounter} \,$|$\, \\\phantom{Synonymous names: }\commandoption{number like} \,$|$\, \commandoption{number-like} \,$|$\, \commandoption{numberlike}
+ \end{itemize}
+ \item \commandoption{numberless}
+ \begin{itemize}
+ \item Defining the current environment to be unnumbered.
+ \end{itemize}
+ \item \commandoption{create starred version}
+ \begin{itemize}
+ \item Defining a corresponding starred (unnumbered) version of the current environment.
+ \item Synonymous names: \commandoption{create-starred-version} \,$|$\, \commandoption{createstarredversion} \,$|$\, \\\phantom{Synonymous names: }\commandoption{create numberless version} \,$|$\, \commandoption{create-numberless-version} \\\phantom{Synonymous names: }\,$|$ \commandoption{createnumberlessversion}
+ \end{itemize}
+ \item \commandoption{copy existed}\lstinline| = |\meta{existed environment}
+ \begin{itemize}
+ \item Defining the current environment to be the same as \meta{existed environment}.
+ \item This key is usually useful in the following two situations:
+ \begin{enumerate}
+ \item To use a more concise name. For example, with \lstinline|\CreateTheorem{thm}{|\commandoption{copy \\existed} \lstinline|= theorem}|, one can then use the name \texttt{thm} to write theorems.
+ \item To remove the numbering of some environments. For example, one can remove the numbering of the \texttt{remark} environment with \lstinline|\CreateTheorem{remark}{|\commandoption{copy existed} \lstinline|= remark*}|.
+ \end{enumerate}
+ \item Synonymous names: \commandoption{copy-existed} \,$|$\, \commandoption{copyexisted}
+ \end{itemize}
+\end{itemize}
+
+\begin{center}
+ \itshape
+ If you're feeling confused, don't worry, check out the Examples section below.
+\end{center}
+
+\begin{tip}
+ The names for the following environments have been preset: \texttt{assertion}, \texttt{assumption}, \texttt{axiom}, \texttt{conclusion}, \texttt{conjecture}, \texttt{convention}, \texttt{corollary}, \texttt{definition}, \texttt{example}, \texttt{exercise}, \texttt{fact}, \texttt{hypothesis}, \texttt{lemma}, \texttt{notation}, \texttt{observation}, \texttt{postulate}, \texttt{problem}, \texttt{property}, \texttt{proposition}, \texttt{question}, \texttt{remark} and \texttt{theorem}. If you are fine with the preset names, then you don't need to specify the key ``\commandoption{name}'' while creating them, otherwise you shall have to use the package option ``\packageoption{no preset names}'' to disable the presets and define your own ones.
+\end{tip}
+
+\medskip
+
+Please note that, by default, an normal environment \meta{env} and its starred relative \meta{env}\texttt{*} do not share the same set of names, for the sake of generality. However, with proper usage of \commandoption{create starred version} and \commandoption{copy existed}, you are already able to produce all of the following combinations that shares the same set of names: 1$)$ numbered \meta{env}, numbered \meta{env}\texttt{*} ; 2$)$ numbered \meta{env}, unnumbered \meta{env}\texttt{*} ; 3$)$ unnumbered \meta{env}, numbered \meta{env}\texttt{*} ; and 4$)$ unnumbered \meta{env}, unnumbered \meta{env}\texttt{*}. I left it as an easy exercise for you ;-)
+
+\section{Examples}
+
+\subsection{The environment \texttt{idea}}
+
+First, let's getting familiar with these two commands by creating the environment \texttt{idea}.
+
+\begin{code}
+\NameTheorem{idea}{
+ (*\commandoption{heading}*) = Idea,
+ (*\commandoption{crefname}*) = {idea}{ideas},
+ (*\commandoption{Crefname}*) = {Idea}{Ideas},
+}
+\CreateTheorem{idea}{ parent counter = section }
+\end{code}
+
+or to do it in one turn:
+
+\begin{code}
+ \CreateTheorem{idea}{
+ (*\commandoption{name}*) = {
+ (*\commandoption{heading}*) = Idea,
+ (*\commandoption{crefname}*) = {idea}{ideas},
+ (*\commandoption{Crefname}*) = {Idea}{Ideas},
+ },
+ (*\commandoption{parent counter}*) = section,
+ }
+\end{code}
+
+\smallskip
+This is not exciting at all. Now, let's say we are writing a bilingual note in English and French. (I shall omit the \lstinline|\NameTheorem| version and do it all at once in \lstinline|\CreateTheorem|.)
+
+\begin{code}
+\CreateTheorem{idea}{
+ (*\commandoption{name}*) = {
+ (*\commandoption{heading}*) = { english = Idea,
+ french = Idée, },
+ (*\commandoption{crefname}*) = { english = {idea}{ideas},
+ french = [l']{idée}[les]{idées}, },
+ (*\commandoption{Crefname}*) = { english = {Idea}{Ideas},
+ french = [L']{idée}[Les]{idées}, },
+ },
+ (*\commandoption{parent counter}*) = section,
+}
+\end{code}
+
+With this, if you use \lstinline|\selectlanguage{french}|, the \texttt{idea} environment shall be automatically displayed as ``Idée''. And if you \lstinline|\crefthe| it, the definite article and the name showed up properly just as expected.
+
+\medskip
+Next we shall deal with the numbering problem. Let's continue to use this environment \texttt{idea} for demonstration -- suppose that we have set the names up with \lstinline|\NameTheorem|.
+
+\subsection{Let's play with numbering}
+
+Remember the exercise I left to you at the end of the previous section? Let's do this together.
+
+\subsubsection{Numbered \texttt{idea} and numbered \texttt{idea*}}
+
+This is easy, \commandoption{copy existed} suffices:
+
+\begin{code}
+\CreateTheorem{idea}{(*\commandoption{parent counter}*) = section}
+\CreateTheorem{idea*}{(*\commandoption{copy existed}*) = idea}
+\end{code}
+
+\subsubsection{Numbered \texttt{idea} and unnumbered \texttt{idea*}}
+
+This is the easiest, \commandoption{create starred version} will do.
+
+\begin{code}
+\CreateTheorem{idea}{
+ (*\commandoption{parent counter}*) = section,
+ (*\commandoption{create starred version}*),
+}
+\end{code}
+
+Notice that you cannot use \lstinline|\CreateTheorem{idea*}{|\commandoption{numberless}\lstinline|}| here, since we don't have names defined for \texttt{idea*}.
+
+\subsubsection{Unnumbered \texttt{idea} and numbered \texttt{idea*}}
+
+This is a bit tricky: by default we can only create numbered \texttt{idea} or unnumbered \texttt{idea*}, and the question is how to switch them. We shall need an intermediary.
+
+\begin{code}
+\CreateTheorem{idea}{(*\commandoption{create starred version}*)}
+\CreateTheorem{idea-temp}{(*\commandoption{copy existed}*) = idea*}
+\CreateTheorem{idea*}{(*\commandoption{copy existed}*) = idea}
+\CreateTheorem{idea}{(*\commandoption{copy existed}*) = idea-temp}
+\end{code}
+
+\subsubsection{Unnumbered \texttt{idea} and unnumbered \texttt{idea*}}
+
+This is the combination of the first two cases --- we need to create \texttt{idea*} first and then copy it to \texttt{idea}:
+
+\begin{code}
+\CreateTheorem{idea}{(*\commandoption{create starred version}*)}
+\CreateTheorem{idea}{(*\commandoption{copy existed}*) = idea*}
+\end{code}
+
+\smallskip
+In each case, the two environments \texttt{idea} and \texttt{idea*} share the same set of names.
+
+
+\section{Known issues}
+
+\begin{itemize}
+ \item The current mechanism does not work well for German, a problem originated in the package \textsf{crefthe}. The author plans to adopt a more refined approach in later versions so as to support the various situations in German.
+ \item If the option ``\packageoption{name in link}'' is enabled, the ``\packageoption{name as is}'' mode shall take longer time to produce the final result, for the current implementation uses \lstinline|\regexpatchcmd| to patch some of the referencing formats, which is not very efficient.
+ \item There may be inaccuracies in the translation of those preset names.
+\end{itemize}
+
+\medskip
+If you run into any issues or have ideas for improvement, feel free to discuss on:
+\begin{center}
+ \url{https://github.com/Jinwen-XU/create-theorem/issues}
+\end{center}
+or email me via \href{mailto:ProjLib@outlook.com}{\texttt{ProjLib@outlook.com}}.
+
+\end{document}
diff --git a/macros/latex/contrib/create-theorem/create-theorem.sty b/macros/latex/contrib/create-theorem/create-theorem.sty
new file mode 100644
index 0000000000..22e89a8d8d
--- /dev/null
+++ b/macros/latex/contrib/create-theorem/create-theorem.sty
@@ -0,0 +1,1458 @@
+%%
+%% Copyright (C) 2021-2022 by Jinwen XU
+%% ------------------------------------
+%%
+%% This file may be distributed and/or modified under the conditions of
+%% the LaTeX Project Public License, either version 1.3c of this license
+%% or (at your option) any later version. The latest version of this
+%% license is in:
+%%
+%% http://www.latex-project.org/lppl.txt
+%%
+\NeedsTeXFormat{LaTeX2e}[2020-10-01]
+\RequirePackage{l3keys2e}
+\ProvidesExplPackage
+ {create-theorem}
+ {2022/02/27} {}
+ {Initializing theorem-like environments with multilingual support}
+
+\keys_define:nn { create-theorem }
+ {
+ , draft .bool_set:N = \l__crthm_fast_bool
+ , draft .initial:n = { false }
+ , fast .bool_set:N = \l__crthm_fast_bool
+ , name-as-context .bool_set:N = \l__crthm_regionalref_bool
+ , name-as-context .initial:n = { true }
+ , name~as~context .bool_set:N = \l__crthm_regionalref_bool
+ , name as context .bool_set:N = \l__crthm_regionalref_bool
+ , regionalref .bool_set:N = \l__crthm_regionalref_bool
+ , name-as-is .bool_set_inverse:N = \l__crthm_regionalref_bool
+ , name~as~is .bool_set_inverse:N = \l__crthm_regionalref_bool
+ , name as is .bool_set_inverse:N = \l__crthm_regionalref_bool
+ , originalref .bool_set_inverse:N = \l__crthm_regionalref_bool
+ , name-in-link .bool_set:N = \l__crthm_nameinlink_bool
+ , name-in-link .initial:n = { false }
+ , name~in~link .bool_set:N = \l__crthm_nameinlink_bool
+ , name in link .bool_set:N = \l__crthm_nameinlink_bool
+ , no-preset-names .bool_set_inverse:N = \l__crthm_presetname_bool
+ , no-preset-names .initial:n = { false }
+ , no~preset~names .bool_set_inverse:N = \l__crthm_presetname_bool
+ , no preset names .bool_set_inverse:N = \l__crthm_presetname_bool
+ , unknown .code:n = {}
+ }
+\ProcessKeysOptions { create-theorem }
+
+\RequirePackage { aliascnt }
+
+\bool_if:NT \l__crthm_nameinlink_bool
+ {
+ \PassOptionsToPackage { nameinlink } { cleveref }
+ }
+
+\RequirePackage { crefthe }
+
+
+% Users should consider appending their languages to this list as needed,
+% otherwise the naming functionality may not work in monolingual documents.
+\clist_new:N \g_crthm_supported_language_clist
+\clist_gset:Nn \g_crthm_supported_language_clist
+ {
+ english ,
+ french ,
+ ngerman ,
+ italian ,
+ portuguese ,
+ brazilian ,
+ spanish ,
+ schinese ,
+ tchinese ,
+ japanese ,
+ russian ,
+ }
+
+\cs_new:Nn \crthm_add_to_language_list:n
+ {
+ \clist_gput_right:Nn \g_crthm_supported_language_clist { #1 }
+ }
+
+\NewDocumentCommand \CreateTheoremAddLanguage { m }
+ {
+ \crthm_add_to_language_list:n { #1 }
+ }
+
+
+\tl_new:N \l__crthm_current_env_tl
+
+\keys_define:nn { create-theorem-naming }
+ {
+ , heading .code:n = { \crthm_set_name_heading:ee { \l__crthm_current_env_tl } { #1 } }
+ , heading .value_required:n = true
+ , crefname .code:n = { \crthm_set_name_crefname:ee { \l__crthm_current_env_tl } { #1 } }
+ , crefname .value_required:n = true
+ , Crefname .code:n = { \crthm_set_name_Crefname:ee { \l__crthm_current_env_tl } { #1 } }
+ , Crefname .value_required:n = true
+ , unknown .code:n = {}
+ }
+
+\cs_new:Nn \crthm_set_name_heading:nn
+ % #1 = env name
+ % #2 = the settings
+ {
+ \str_if_in:nnTF { #2 } { = }
+ {% key-value definition
+ \keyval_parse:nnn
+ {}
+ { \__crthm_set_name_heading:nnn { #1 } }
+ { #2 }
+ }
+ {% simple definition
+ \clist_map_inline:Nn \g_crthm_supported_language_clist
+ {
+ \__crthm_set_name_heading:nnn { #1 } { ##1 } { #2 }
+ }
+ }
+ }
+\cs_generate_variant:Nn \crthm_set_name_heading:nn { ee }
+
+\cs_new:Nn \__crthm_set_name_heading:nnn
+ % #1 = env name
+ % #2 = language name
+ % #3 = corresponding heading name
+ {
+ \tl_gset:cn { g_crthm_name_heading_ #1 _ #2 _tl } { #3 }
+ }
+
+\cs_new:Nn \crthm_set_name_crefname:nn
+ % #1 = env name
+ % #2 = the settings
+ {
+ \crthm_set_name_crefname_generic:nnn { #1 } { #2 } { c }
+ }
+\cs_generate_variant:Nn \crthm_set_name_crefname:nn { ee }
+
+\cs_new:Nn \crthm_set_name_Crefname:nn
+ % #1 = env name
+ % #2 = the settings
+ {
+ \crthm_set_name_crefname_generic:nnn { #1 } { #2 } { C }
+ }
+\cs_generate_variant:Nn \crthm_set_name_Crefname:nn { ee }
+
+\cs_new:Nn \crthm_set_name_crefname_generic:nnn
+ % #1 = env name
+ % #2 = the settings
+ % #3 = c or C
+ {
+ \str_if_in:nnTF { #2 } { = }
+ {% key-value definition
+ \keyval_parse:nnn
+ {}
+ { \use:c { __crthm_set_name_ #3 refname:nnn } { #1 } }
+ { #2 }
+ }
+ {% simple definition
+ \clist_map_inline:Nn \g_crthm_supported_language_clist
+ {
+ \use:c { __crthm_set_name_ #3 refname:nnn } { #1 } { ##1 } { #2 }
+ }
+ }
+ }
+
+\cs_new:Nn \__crthm_set_name_crefname:nnn
+ % #1 = env name
+ % #2 = language name
+ % #3 = corresponding crefname {..}{..} or [..]{..}[..]{..}
+ {
+ \__crthm_set_name_crefname_generic:w { #1 } { #2 } #3 { c }
+ }
+
+\cs_new:Nn \__crthm_set_name_Crefname:nnn
+ % #1 = env name
+ % #2 = language name
+ % #3 = corresponding Crefname {..}{..} or [..]{..}[..]{..}
+ {
+ \__crthm_set_name_crefname_generic:w { #1 } { #2 } #3 { C }
+ }
+
+\NewExpandableDocumentCommand \__crthm_set_name_crefname_generic:w { m m O{} m O{} m m }
+ % #1 = env name
+ % #2 = language name
+ % #7 = c or C
+ {
+ \tl_if_blank:eTF { #3 }
+ {
+ \tl_gset:cn { g_crthm_name_ #7 ref_article_singular_ #1 _ #2 _tl } { \crefthemark{ \!\! } }
+ \tl_gset:cn { g_crthm_name_ #7 ref_article_plural_ #1 _ #2 _tl } { \crefthemark{ \!\! } }
+ }
+ {
+ \tl_gset:cn { g_crthm_name_ #7 ref_article_singular_ #1 _ #2 _tl } { \crefthemark { #3 } }
+ \tl_gset:cn { g_crthm_name_ #7 ref_article_plural_ #1 _ #2 _tl } { \crefthemark { #5 } }
+ }
+ \tl_gset:cn { g_crthm_name_ #7 ref_singular_ #1 _ #2 _tl } { #4 }
+ \tl_gset:cn { g_crthm_name_ #7 ref_plural_ #1 _ #2 _tl } { #6 }
+
+ \bool_if:NTF \l__crthm_regionalref_bool
+ {% "regionalref" mode
+ \use:c { __crthm_set_name_ #7 refname_do:nnn } { #1 _crthm_regional } { #1 } { \languagename }
+ }
+ {% "originalref" mode
+ \tl_if_exist:cF { c@ #1 _crthm_original_ #2 } { \newaliascnt { #1 _crthm_original_ #2 } { #1 } }
+ \use:c { __crthm_set_name_ #7 refname_do:nnn } { #1 _crthm_original_ #2 } { #1 } { #2 }
+ }
+ }
+
+\cs_new:Nn \__crthm_set_name_crefname_do:nnn
+ % #1 = counter name
+ % #2 = env name
+ % #3 = language name
+ {
+ \__crthm_set_name_crefname_do_generic:nnnn { #1 } { #2 } { #3 } { c }
+ }
+
+\cs_new:Nn \__crthm_set_name_Crefname_do:nnn
+ % #1 = counter name
+ % #2 = env name
+ % #3 = language name
+ {
+ \__crthm_set_name_crefname_do_generic:nnnn { #1 } { #2 } { #3 } { C }
+ }
+
+\seq_new:N \g__crthm_format_already_set_seq
+
+\cs_new:Nn \__crthm_set_name_crefname_do_generic:nnnn
+ % #1 = counter name
+ % #2 = env name
+ % #3 = language name
+ % #4 = c or C
+ {
+ \tl_gset:cn { #4 ref@ #1 @name }
+ {
+ \tl_use:c { g_crthm_name_ #4 ref_article_singular_ #2 _ #3 _tl }
+ \bool_if:NTF \g__crefthe_has_prep_bool
+ {
+ \tl_use:c { g_crthm_name_ cref_singular_ #2 _ #3 _tl }
+ }
+ {
+ \tl_use:c { g_crthm_name_ #4 ref_singular_ #2 _ #3 _tl }
+ }
+ }
+ \tl_gset:cn { #4 ref@ #1 @name@plural }
+ { % Use the definite article of the original language, with name's casing depends on the preposition and the article
+ \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ #3 _tl }
+ \bool_if:NTF \g__crefthe_has_prep_bool
+ {
+ \tl_use:c { g_crthm_name_ cref_plural_ #2 _ #3 _tl }
+ }
+ {
+ \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl }
+ }
+ }
+ %% Use the definite article of the original language, with name's casing correspond to the article
+ % \tl_gset:cn { #4 ref@ #1 @name }
+ % {
+ % \tl_use:c { g_crthm_name_ #4 ref_article_singular_ #2 _ #3 _tl }
+ % \tl_use:c { g_crthm_name_ #4 ref_singular_ #2 _ #3 _tl }
+ % }
+ % \tl_gset:cn { #4 ref@ #1 @name@plural }
+ % {
+ % \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ #3 _tl }
+ % \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl }
+ % }
+ %% Use the definite article of the current language, with name's casing correspond to the article
+ % \tl_gset:cn { #4 ref@ #1 @name }
+ % {
+ % \tl_use:c { g_crthm_name_ #4 ref_article_singular_ #2 _ \languagename _tl }
+ % \tl_use:c { g_crthm_name_ #4 ref_singular_ #2 _ #3 _tl }
+ % }
+ % \tl_gset:cn { #4 ref@ #1 @name@plural }
+ % {
+ % \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ \languagename _tl }
+ % \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl }
+ % }
+
+ \seq_if_in:NnF \g__crthm_format_already_set_seq { #2 _ #3 }
+ {
+ \@crefcopyformats { #2 _ #3 } { #1 }
+ \seq_gput_right:Nn \g__crthm_format_already_set_seq { #2 _ #3 }
+ }
+
+ \bool_if:NF \l__crthm_fast_bool
+ { % Patch the cref formats so that definite articles are placed outside the hyperlinks
+ \bool_if:NT \l__crthm_nameinlink_bool
+ { % Use the definite article of the original language, with name's casing depends on the preposition and the article
+ \crefthe_patch_format:nnnnn { 2 } { #4 ref@ #1 @format } { #4 ref@ #1 @name }
+ { \tl_use:c { g_crthm_name_ #4 ref_article_singular_ #2 _ #3 _tl } }
+ {
+ \bool_if:NTF \g__crefthe_has_prep_bool
+ {
+ \tl_use:c { g_crthm_name_ cref_singular_ #2 _ #3 _tl }
+ }
+ {
+ \tl_use:c { g_crthm_name_ #4 ref_singular_ #2 _ #3 _tl }
+ }
+ }
+ \crefthe_patch_format:nnnnn { 2 } { #4 ref@ #1 @format@first } { #4 ref@ #1 @name@plural }
+ { \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ #3 _tl } }
+ {
+ \bool_if:NTF \g__crefthe_has_prep_bool
+ {
+ \tl_use:c { g_crthm_name_ cref_plural_ #2 _ #3 _tl }
+ }
+ {
+ \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl }
+ }
+ }
+ \crefthe_patch_format:nnnnn { 3 } { #4 refrange@ #1 @format } { #4 ref@ #1 @name@plural }
+ { \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ #3 _tl } }
+ {
+ \bool_if:NTF \g__crefthe_has_prep_bool
+ {
+ \tl_use:c { g_crthm_name_ cref_plural_ #2 _ #3 _tl }
+ }
+ {
+ \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl }
+ }
+ }
+ \crefthe_patch_format:nnnnn { 3 } { #4 refrange@ #1 @format@first } { #4 ref@ #1 @name@plural }
+ { \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ #3 _tl } }
+ {
+ \bool_if:NTF \g__crefthe_has_prep_bool
+ {
+ \tl_use:c { g_crthm_name_ cref_plural_ #2 _ #3 _tl }
+ }
+ {
+ \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl }
+ }
+ }
+ %% Use the definite article of the original language, with name's casing correspond to the article
+ % \crefthe_patch_format:nnnnn { 2 } { #4 ref@ #1 @format } { #4 ref@ #1 @name }
+ % { \tl_use:c { g_crthm_name_ #4 ref_article_singular_ #2 _ #3 _tl } }
+ % { \tl_use:c { g_crthm_name_ #4 ref_singular_ #2 _ #3 _tl } }
+ % \crefthe_patch_format:nnnnn { 2 } { #4 ref@ #1 @format@first } { #4 ref@ #1 @name@plural }
+ % { \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ #3 _tl } }
+ % { \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl } }
+ % \crefthe_patch_format:nnnnn { 3 } { #4 refrange@ #1 @format } { #4 ref@ #1 @name@plural }
+ % { \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ #3 _tl } }
+ % { \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl } }
+ % \crefthe_patch_format:nnnnn { 3 } { #4 refrange@ #1 @format@first } { #4 ref@ #1 @name@plural }
+ % { \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ #3 _tl } }
+ % { \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl } }
+ %% Use the definite article of the current language, with name's casing correspond to the article
+ % \crefthe_patch_format:nnnnn { 2 } { #4 ref@ #1 @format } { #4 ref@ #1 @name }
+ % { \tl_use:c { g_crthm_name_ #4 ref_article_singular_ #2 _ \languagename _tl } }
+ % { \tl_use:c { g_crthm_name_ #4 ref_singular_ #2 _ #3 _tl } }
+ % \crefthe_patch_format:nnnnn { 2 } { #4 ref@ #1 @format@first } { #4 ref@ #1 @name@plural }
+ % { \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ \languagename _tl } }
+ % { \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl } }
+ % \crefthe_patch_format:nnnnn { 3 } { #4 refrange@ #1 @format } { #4 ref@ #1 @name@plural }
+ % { \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ \languagename _tl } }
+ % { \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl } }
+ % \crefthe_patch_format:nnnnn { 3 } { #4 refrange@ #1 @format@first } { #4 ref@ #1 @name@plural }
+ % { \tl_use:c { g_crthm_name_ #4 ref_article_plural_ #2 _ \languagename _tl } }
+ % { \tl_use:c { g_crthm_name_ #4 ref_plural_ #2 _ #3 _tl } }
+ }
+ }
+ }
+
+\NewDocumentCommand \NameTheorem { m m }
+ {
+ \tl_set:Nn \l__crthm_current_env_tl { #1 }
+ \newcounter { #1 }
+ \keys_set:nn { create-theorem-naming } { #2 }
+ }
+
+\bool_new:N \l__crthm_creating_apply_style_bool
+\tl_new:N \l__crthm_creating_apply_style_tl
+\bool_new:N \l__crthm_creating_numberless_bool
+\bool_new:N \l__crthm_creating_create_starred_bool
+\bool_new:N \l__crthm_creating_copy_existed_bool
+\tl_new:N \l__crthm_creating_copy_existed_tl
+\keys_define:nn { create-theorem-creating }
+ {
+ , name .code:n = { \exp_args:Ne \NameTheorem { \l__crthm_current_env_tl } { #1 } }
+ , name .value_required:n = true
+ , style .code:n = { \bool_set_true:N \l__crthm_creating_apply_style_bool
+ \tl_set:Nn \l__crthm_creating_apply_style_tl { #1 } }
+ , style .value_required:n = true
+ , apply~style .meta:n = { style = #1 }
+ , apply~style .value_required:n = true
+ , apply-style .meta:n = { style = #1 }
+ , apply-style .value_required:n = true
+ , apply style .meta:n = { style = #1 }
+ , apply style .value_required:n = true
+ , numberless .bool_set:N = \l__crthm_creating_numberless_bool
+ , create~starred~version .bool_set:N = \l__crthm_creating_create_starred_bool
+ , create-starred-version .meta:n = { create~starred~version }
+ , create starred version .meta:n = { create~starred~version }
+ , create~numberless~version .meta:n = { create~starred~version }
+ , create-numberless-version .meta:n = { create~starred~version }
+ , create numberless version .meta:n = { create~starred~version }
+ , copy~existed .code:n = { \bool_set_true:N \l__crthm_creating_copy_existed_bool
+ \tl_set:Nn \l__crthm_creating_copy_existed_tl { #1 } }
+ , copy~existed .value_required:n = true
+ , copy-existed .meta:n = { copy~existed = #1 }
+ , copy-existed .value_required:n = true
+ , copy existed .meta:n = { copy~existed = #1 }
+ , copy existed .value_required:n = true
+ , unknown .code:n = {}
+ }
+
+\keys_define:nn { create-theorem-creating / counter-management }
+ {
+ , parent~counter .code:n = { \crthm_counter_within:ee { \l__crthm_current_env_tl } { #1 } }
+ , parent~counter .value_required:n = true
+ , parent-counter .meta:n = { parent~counter = #1 }
+ , parent-counter .value_required:n = true
+ , parent counter .meta:n = { parent~counter = #1 }
+ , parent counter .value_required:n = true
+ , number~within .meta:n = { parent~counter = #1 }
+ , number~within .value_required:n = true
+ , number-within .meta:n = { parent~counter = #1 }
+ , number-within .value_required:n = true
+ , number within .meta:n = { parent~counter = #1 }
+ , number within .value_required:n = true
+ , shared~counter .code:n = { \crthm_counter_alias:ee { \l__crthm_current_env_tl } { #1 } }
+ , shared~counter .value_required:n = true
+ , shared-counter .meta:n = { shared~counter = #1 }
+ , shared-counter .value_required:n = true
+ , shared counter .meta:n = { shared~counter = #1 }
+ , shared counter .value_required:n = true
+ , number~like .meta:n = { shared~counter = #1 }
+ , number~like .value_required:n = true
+ , number-like .meta:n = { shared~counter = #1 }
+ , number-like .value_required:n = true
+ , number like .meta:n = { shared~counter = #1 }
+ , number like .value_required:n = true
+ , unknown .code:n = {}
+ }
+
+\NewDocumentCommand \CreateTheorem { m m }
+ {
+ \tl_set:Nn \l__crthm_current_env_tl { #1 }
+ \bool_set_false:N \l__crthm_creating_apply_style_bool
+ \bool_set_false:N \l__crthm_creating_numberless_bool
+ \bool_set_false:N \l__crthm_creating_create_starred_bool
+ \bool_set_false:N \l__crthm_creating_copy_existed_bool
+ \keys_set:nn { create-theorem-creating } { #2 }
+ \bool_if:NTF \l__crthm_creating_copy_existed_bool
+ {
+ \__crthm_define_env:ee { #1 } { \l__crthm_creating_copy_existed_tl }
+ \bool_if:NT \l__crthm_creating_create_starred_bool
+ {
+ \__crthm_define_env:ee { #1 * } { \l__crthm_creating_copy_existed_tl * }
+ }
+ }
+ {
+ \group_begin:
+ \bool_if:NT \l__crthm_creating_apply_style_bool
+ {
+ \exp_after:wN \theoremstyle \exp_after:wN { \l__crthm_creating_apply_style_tl }
+ }
+ \bool_if:NTF \l__crthm_creating_numberless_bool
+ {
+ \newtheorem*{ #1 _crthm_regional } { \tl_use:c { g_crthm_name_heading_ #1 _ \languagename _tl } }
+ }
+ {
+ \newtheorem { #1 _crthm_regional } [#1] { \tl_use:c { g_crthm_name_heading_ #1 _ \languagename _tl } }
+ }
+ \group_end:
+ \NewDocumentEnvironment { #1 } { O{} }
+ % In "regionalref" mode, the counter received by the referencing system is "#1 _crthm_regional"
+ % while in "originalref" mode, the counter received is "#1 _crthm_original_\languagename"
+ {
+ \begin{ #1 _crthm_regional }[##1]
+ \bool_if:NF \l__crthm_regionalref_bool
+ {
+ \addtocounter { #1 _crthm_original_\languagename } { -1 }
+ \refstepcounter { #1 _crthm_original_\languagename }
+ }
+ }
+ {
+ \end{ #1 _crthm_regional }
+ }
+ \bool_if:NT \l__crthm_creating_create_starred_bool
+ {
+ \newtheorem*{ #1 * } { \tl_use:c { g_crthm_name_heading_ #1 _ \languagename _tl } }
+ }
+ \keys_set:nn { create-theorem-creating / counter-management } { #2 }
+ }
+ }
+
+\cs_new:Nn \__crthm_define_env:nn
+ {
+ \cs_set_eq:cc { #1 } { #2 }
+ \cs_set_eq:cc { end #1 } { end #2 }
+ }
+\cs_generate_variant:Nn \__crthm_define_env:nn { ee }
+
+\cs_new:Nn \crthm_counter_within:nn
+ {
+ \counterwithin { #1 } { #2 }
+ }
+\cs_generate_variant:Nn \crthm_counter_within:nn { ee }
+
+\cs_new:Nn \crthm_counter_alias:nn
+ {
+ \cs_set_eq:cc { c@ #1 } { c@ #2 }
+ \cs_set_eq:cc { the #1 } { the #2 }
+ }
+\cs_generate_variant:Nn \crthm_counter_alias:nn { ee }
+
+
+\bool_if:NF \l__crthm_presetname_bool { \endinput }
+
+
+%=============================
+% --- Preset Names ---
+%=============================
+
+\NameTheorem { assertion }
+ {
+ , heading = {
+ , english = Assertion
+ , french = Assertion
+ , ngerman = Behauptung
+ , italian = Asserzione
+ , portuguese = Asserção
+ , brazilian = Asserção
+ , spanish = Aserción
+ , schinese = 断言
+ , tchinese = 斷言
+ , japanese = アサーション
+ , russian = Утверждение
+ }
+ , crefname = {
+ , english = {assertion}{assertions}
+ , french = [l']{assertion}[les]{assertions}
+ , ngerman = {Behauptung}{Behauptungen}
+ , italian = [l']{asserzione}[le]{asserzioni}
+ , portuguese = [a]{asserção}[as]{asserções}
+ , brazilian = [a]{asserção}[as]{asserções}
+ , spanish = [la]{aserción}[las]{aserciones}
+ , schinese = {断言}{断言}
+ , tchinese = {斷言}{斷言}
+ , japanese = {アサーション}{アサーション}
+ , russian = {утверждение}{утверждения}
+ }
+ , Crefname = {
+ , english = {Assertion}{Assertions}
+ , french = [L']{assertion}[Les]{assertions}
+ , ngerman = {Behauptung}{Behauptungen}
+ , italian = [L']{asserzione}[Le]{asserzioni}
+ , portuguese = [A]{asserção}[As]{asserções}
+ , brazilian = [A]{asserção}[As]{asserções}
+ , spanish = [La]{aserción}[Las]{aserciones}
+ , schinese = {断言}{断言}
+ , tchinese = {斷言}{斷言}
+ , japanese = {アサーション}{アサーション}
+ , russian = {Утверждение}{Утверждения}
+ }
+ }
+
+\NameTheorem { assumption }
+ {
+ , heading = {
+ , english = Assumption
+ , french = Supposition
+ , ngerman = Annahme
+ , italian = Supposizione
+ , portuguese = Suposição
+ , brazilian = Suposição
+ , spanish = Suposición
+ , schinese = 假定
+ , tchinese = 假定
+ , japanese = 仮定
+ , russian = Предположение
+ }
+ , crefname = {
+ , english = {assumption}{assumptions}
+ , french = [la]{supposition}[les]{suppositions}
+ , ngerman = {Annahme}{Annahmen}
+ , italian = [la]{supposizione}[le]{supposizioni}
+ , portuguese = [a]{suposição}[as]{suposições}
+ , brazilian = [a]{suposição}[as]{suposições}
+ , spanish = [la]{suposición}[las]{suposiciones}
+ , schinese = {假定}{假定}
+ , tchinese = {假定}{假定}
+ , japanese = {仮定}{仮定}
+ , russian = {предположение}{предположения}
+ }
+ , Crefname = {
+ , english = {Assumption}{Assumptions}
+ , french = [La]{supposition}[Les]{suppositions}
+ , ngerman = {Annahme}{Annahmen}
+ , italian = [la]{supposizione}[le]{supposizioni}
+ , portuguese = [A]{suposição}[As]{suposições}
+ , brazilian = [A]{suposição}[As]{suposições}
+ , spanish = [La]{suposición}[Las]{suposiciones}
+ , schinese = {假定}{假定}
+ , tchinese = {假定}{假定}
+ , japanese = {仮定}{仮定}
+ , russian = {Предположение}{Предположения}
+ }
+ }
+
+\NameTheorem { axiom }
+ {
+ , heading = {
+ , english = Axiom
+ , french = Axiome
+ , ngerman = Axiom
+ , italian = Assioma
+ , portuguese = Axioma
+ , brazilian = Axioma
+ , spanish = Axioma
+ , schinese = 公理
+ , tchinese = 公理
+ , japanese = 公理
+ , russian = Аксиома
+ }
+ , crefname = {
+ , english = {axiom}{axioms}
+ , french = [l']{axiome}[les]{axiomes}
+ , ngerman = {Axiom}{Axiome}
+ , italian = [l']{assioma}[gli]{assiomi}
+ , portuguese = [o]{axioma}[os]{axiomas}
+ , brazilian = [o]{axioma}[os]{axiomas}
+ , spanish = [el]{axioma}[los]{axiomas}
+ , schinese = {公理}{公理}
+ , tchinese = {公理}{公理}
+ , japanese = {公理}{公理}
+ , russian = {аксиома}{аксиомы}
+ }
+ , Crefname = {
+ , english = {Axiom}{Axioms}
+ , french = [L']{axiome}[Les]{axiomes}
+ , ngerman = {Axiom}{Axiome}
+ , italian = [L']{assioma}[Gli]{assiomi}
+ , portuguese = [O]{axioma}[Os]{axiomas}
+ , brazilian = [O]{axioma}[Os]{axiomas}
+ , spanish = [El]{axioma}[Los]{axiomas}
+ , schinese = {公理}{公理}
+ , tchinese = {公理}{公理}
+ , japanese = {公理}{公理}
+ , russian = {Аксиома}{Аксиомы}
+ }
+ }
+
+\NameTheorem { conclusion }
+ {
+ , heading = {
+ , english = Conclusion
+ , french = Conclusion
+ , ngerman = Schlussfolgerung
+ , italian = Conclusione
+ , portuguese = Conclusão
+ , brazilian = Conclusão
+ , spanish = Conclusión
+ , schinese = 结论
+ , tchinese = 結論
+ , japanese = 結論
+ , russian = Вывод
+ }
+ , crefname = {
+ , english = {conclusion}{conclusions}
+ , french = [la]{conclusion}[Les]{conclusions}
+ , ngerman = {Schlussfolgerung}{Schlussfolgerungen}
+ , italian = [la]{conclusione}[le]{conclusioni}
+ , portuguese = [a]{conclusão}[as]{conclusões}
+ , brazilian = [a]{conclusão}[as]{conclusões}
+ , spanish = [la]{conclusión}[las]{conclusiones}
+ , schinese = {结论}{结论}
+ , tchinese = {結論}{結論}
+ , japanese = {結論}{結論}
+ , russian = {вывод}{выводы}
+ }
+ , Crefname = {
+ , english = {Conclusion}{Conclusions}
+ , french = [La]{conclusion}[Les]{conclusions}
+ , ngerman = {Schlussfolgerung}{Schlussfolgerungen}
+ , italian = [La]{conclusione}[Le]{conclusioni}
+ , portuguese = [A]{conclusão}[As]{conclusões}
+ , brazilian = [A]{conclusão}[As]{conclusões}
+ , spanish = [La]{conclusión}[Las]{conclusiones}
+ , schinese = {结论}{结论}
+ , tchinese = {結論}{結論}
+ , japanese = {結論}{結論}
+ , russian = {Вывод}{Выводы}
+ }
+ }
+
+\NameTheorem { conjecture }
+ {
+ , heading = {
+ , english = Conjecture
+ , french = Conjecture
+ , ngerman = Vermutung
+ , italian = Congettura
+ , portuguese = Conjetura
+ , brazilian = Conjectura
+ , spanish = Conjetura
+ , schinese = 猜想
+ , tchinese = 猜想
+ , japanese = 予想
+ , russian = Гипотеза
+ }
+ , crefname = {
+ , english = {conjecture}{conjectures}
+ , french = [la]{conjecture}[les]{conjectures}
+ , ngerman = {Vermutung}{Vermutungen}
+ , italian = [la]{congettura}[le]{congetture}
+ , portuguese = [a]{conjetura}[as]{conjeturas}
+ , brazilian = [a]{conjectura}[as]{conjecturas}
+ , spanish = [la]{conjetura}[las]{conjeturas}
+ , schinese = {猜想}{猜想}
+ , tchinese = {猜想}{猜想}
+ , japanese = {予想}{予想}
+ , russian = {гипотеза}{гипотезы}
+ }
+ , Crefname = {
+ , english = {Conjecture}{Conjectures}
+ , french = [La]{conjecture}[Les]{conjectures}
+ , ngerman = {Vermutung}{Vermutungen}
+ , italian = [La]{congettura}[Le]{congetture}
+ , portuguese = [A]{conjetura}[As]{conjeturas}
+ , brazilian = [A]{conjectura}[As]{conjecturas}
+ , spanish = [La]{conjetura}[Las]{conjeturas}
+ , schinese = {猜想}{猜想}
+ , tchinese = {猜想}{猜想}
+ , japanese = {予想}{予想}
+ , russian = {Гипотеза}{Гипотезы}
+ }
+ }
+
+\NameTheorem { convention }
+ {
+ , heading = {
+ , english = Convention
+ , french = Convention
+ , ngerman = Konvention
+ , italian = Convenzione
+ , portuguese = Convenção
+ , brazilian = Convenção
+ , spanish = Convención
+ , schinese = 约定
+ , tchinese = 約定
+ , japanese = 慣例
+ , russian = Конвенция
+ }
+ , crefname = {
+ , english = {convention}{conventions}
+ , french = [la]{convention}[les]{conventions}
+ , ngerman = {Konvention}{Konventionen}
+ , italian = [la]{convenzione}[le]{convenzioni}
+ , portuguese = [a]{convenção}[as]{convenções}
+ , brazilian = [a]{convenção}[as]{convenções}
+ , spanish = [la]{convención}[las]{convenciones}
+ , schinese = {约定}{约定}
+ , tchinese = {約定}{約定}
+ , japanese = {慣例}{慣例}
+ , russian = {конвенция}{конвенции}
+ }
+ , Crefname = {
+ , english = {Convention}{Conventions}
+ , french = [La]{convention}[Les]{conventions}
+ , ngerman = {Konvention}{Konventionen}
+ , italian = [La]{convenzione}[Le]{convenzioni}
+ , portuguese = [A]{convenção}[As]{convenções}
+ , brazilian = [A]{convenção}[As]{convenções}
+ , spanish = [La]{convención}[Las]{convenciones}
+ , schinese = {约定}{约定}
+ , tchinese = {約定}{約定}
+ , japanese = {慣例}{慣例}
+ , russian = {Конвенция}{Конвенции}
+ }
+ }
+
+\NameTheorem { corollary }
+ {
+ , heading = {
+ , english = Corollary
+ , french = Corollaire
+ , ngerman = Korollar
+ , italian = Corollario
+ , portuguese = Corolário
+ , brazilian = Corolário
+ , spanish = Corolario
+ , schinese = 推论
+ , tchinese = 推論
+ , japanese = 系
+ , russian = Следствие
+ }
+ , crefname = {
+ , english = {corollary}{corollaries}
+ , french = [le]{corollaire}[les]{corollaires}
+ , ngerman = {Korollar}{Korollare}
+ , italian = [il]{corollario}[i]{corollari}
+ , portuguese = [o]{corolário}[os]{corolários}
+ , brazilian = [o]{corolário}[os]{corolários}
+ , spanish = [el]{corolario}[los]{corolarios}
+ , schinese = {推论}{推论}
+ , tchinese = {推論}{推論}
+ , japanese = {系}{系}
+ , russian = {следствие}{следствия}
+ }
+ , Crefname = {
+ , english = {Corollary}{Corollaries}
+ , french = [Le]{corollaire}[Les]{corollaires}
+ , ngerman = {Korollar}{Korollare}
+ , italian = [Il]{corollario}[I]{corollari}
+ , portuguese = [O]{corolário}[Os]{corolários}
+ , brazilian = [O]{corolário}[Os]{corolários}
+ , spanish = [El]{corolario}[Los]{corolarios}
+ , schinese = {推论}{推论}
+ , tchinese = {推論}{推論}
+ , japanese = {系}{系}
+ , russian = {Следствие}{Следствия}
+ }
+ }
+
+\NameTheorem { definition }
+ {
+ , heading = {
+ , english = Definition
+ , french = Définition
+ , ngerman = Definition
+ , italian = Definizione
+ , portuguese = Definição
+ , brazilian = Definição
+ , spanish = Definición
+ , schinese = 定义
+ , tchinese = 定義
+ , japanese = 定義
+ , russian = Определение
+ }
+ , crefname = {
+ , english = {definition}{definitions}
+ , french = [la]{définition}[les]{définitions}
+ , ngerman = {Definition}{Definitionen}
+ , italian = [la]{definizione}[le]{definizioni}
+ , portuguese = [a]{definição}[as]{definições}
+ , brazilian = [a]{definição}[as]{definições}
+ , spanish = [la]{definición}[las]{definiciones}
+ , schinese = {定义}{定义}
+ , tchinese = {定義}{定義}
+ , japanese = {定義}{定義}
+ , russian = {определение}{определения}
+ }
+ , Crefname = {
+ , english = {Definition}{Definitions}
+ , french = [La]{définition}[Les]{définitions}
+ , ngerman = {Definition}{Definitionen}
+ , italian = [La]{definizione}[Le]{definizioni}
+ , portuguese = [A]{definição}[As]{definições}
+ , brazilian = [A]{definição}[As]{definições}
+ , spanish = [La]{definición}[Las]{definiciones}
+ , schinese = {定义}{定义}
+ , tchinese = {定義}{定義}
+ , japanese = {定義}{定義}
+ , russian = {Определение}{Определения}
+ }
+ }
+
+\NameTheorem { example }
+ {
+ , heading = {
+ , english = Example
+ , french = Exemple
+ , ngerman = Beispiel
+ , italian = Esempio
+ , portuguese = Exemplo
+ , brazilian = Exemplo
+ , spanish = Ejemplo
+ , schinese = 例
+ , tchinese = 例
+ , japanese = 例
+ , russian = Пример
+ }
+ , crefname = {
+ , english = {example}{examples}
+ , french = [l']{example}[les]{examples}
+ , ngerman = {Beispiel}{Beispiele}
+ , italian = [l']{esempio}[gli]{esempi}
+ , portuguese = [o]{exemplo}[os]{exemplos}
+ , brazilian = [o]{exemplo}[os]{exemplos}
+ , spanish = [el]{ejemplo}[los]{ejemplos}
+ , schinese = {例}{例}
+ , tchinese = {例}{例}
+ , japanese = {例}{例}
+ , russian = {пример}{примеры}
+ }
+ , Crefname = {
+ , english = {Example}{Examples}
+ , french = [L']{example}[Les]{examples}
+ , ngerman = {Beispiel}{Beispiele}
+ , italian = [L']{esempio}[Gli]{esempi}
+ , portuguese = [O]{exemplo}[Os]{exemplos}
+ , brazilian = [O]{exemplo}[Os]{exemplos}
+ , spanish = [El]{ejemplo}[Los]{ejemplos}
+ , schinese = {例}{例}
+ , tchinese = {例}{例}
+ , japanese = {例}{例}
+ , russian = {Пример}{Примеры}
+ }
+ }
+
+\NameTheorem { exercise }
+ {
+ , heading = {
+ , english = Exercise
+ , french = Exercice
+ , ngerman = Übung
+ , italian = Esercizio
+ , portuguese = Exercício
+ , brazilian = Exercício
+ , spanish = Ejercicio
+ , schinese = 练习
+ , tchinese = 練習
+ , japanese = 練習
+ , russian = Упражнение
+ }
+ , crefname = {
+ , english = {exercise}{exercises}
+ , french = [l']{exercice}[les]{exercices}
+ , ngerman = {Übung}{Übungen}
+ , italian = [l']{esercizio}[gli]{esercizi}
+ , portuguese = [o]{exercício}[os]{exercícios}
+ , brazilian = [o]{exercício}[os]{exercícios}
+ , spanish = [el]{ejercicio}[los]{ejercicios}
+ , schinese = {练习}{练习}
+ , tchinese = {練習}{練習}
+ , japanese = {練習}{練習}
+ , russian = {упражнение}{упражнения}
+ }
+ , Crefname = {
+ , english = {Exercise}{Exercises}
+ , french = [L']{exercice}[Les]{exercices}
+ , ngerman = {Übung}{Übungen}
+ , italian = [L']{esercizio}[Gli]{esercizi}
+ , portuguese = [O]{exercício}[Os]{exercícios}
+ , brazilian = [O]{exercício}[Os]{exercícios}
+ , spanish = [El]{ejercicio}[Los]{ejercicios}
+ , schinese = {练习}{练习}
+ , tchinese = {練習}{練習}
+ , japanese = {練習}{練習}
+ , russian = {Упражнение}{Упражнения}
+ }
+ }
+
+\NameTheorem { fact }
+ {
+ , heading = {
+ , english = Fact
+ , french = Fait
+ , ngerman = Fakt
+ , italian = Fatto
+ , portuguese = Facto
+ , brazilian = Fato
+ , spanish = Hecho
+ , schinese = 事实
+ , tchinese = 事實
+ , japanese = 事実
+ , russian = Факт
+ }
+ , crefname = {
+ , english = {fact}{facts}
+ , french = [le]{fait}[les]{faits}
+ , ngerman = {Fakt}{Fakten}
+ , italian = [il]{fatto}[i]{fatti}
+ , portuguese = [o]{facto}[os]{factos}
+ , brazilian = [o]{fato}[os]{fatos}
+ , spanish = [el]{hecho}[los]{hechos}
+ , schinese = {事实}{事实}
+ , tchinese = {事實}{事實}
+ , japanese = {事実}{事実}
+ , russian = {факт}{факты}
+ }
+ , Crefname = {
+ , english = {Fact}{Facts}
+ , french = [Le]{fait}[Les]{faits}
+ , ngerman = {Fakt}{Fakten}
+ , italian = [Il]{fatto}[I]{fatti}
+ , portuguese = [O]{facto}[Os]{factos}
+ , brazilian = [O]{fato}[Os]{fatos}
+ , spanish = [El]{hecho}[Los]{hechos}
+ , schinese = {事实}{事实}
+ , tchinese = {事實}{事實}
+ , japanese = {事実}{事実}
+ , russian = {Факт}{Факты}
+ }
+ }
+
+\NameTheorem { hypothesis }
+ {
+ , heading = {
+ , english = Hypothesis
+ , french = Hypothèse
+ , ngerman = Hypothese
+ , italian = Ipotesi
+ , portuguese = Hipótese
+ , brazilian = Hipótese
+ , spanish = Hipótesis
+ , schinese = 假设
+ , tchinese = 假設
+ , japanese = 仮設
+ , russian = Гипотеза
+ }
+ , crefname = {
+ , english = {hypothesis}{hypotheses}
+ , french = [l']{hypothèse}[les]{hypothèses}
+ , ngerman = {Hypothese}{Hypothesen}
+ , italian = [l']{ipotesi}[le]{ipotesi}
+ , portuguese = [a]{hipótese}[as]{hipóteses}
+ , brazilian = [a]{hipótese}[as]{hipóteses}
+ , spanish = [la]{hipótesis}[las]{hipótesis}
+ , schinese = {假设}{假设}
+ , tchinese = {假設}{假設}
+ , japanese = {仮設}{仮設}
+ , russian = {гипотеза}{гипотезы}
+ }
+ , Crefname = {
+ , english = {Hypothesis}{Hypotheses}
+ , french = [L']{hypothèse}[Les]{hypothèses}
+ , ngerman = {Hypothese}{Hypothesen}
+ , italian = [L']{ipotesi}[Le]{ipotesi}
+ , portuguese = [A]{hipótese}[As]{hipóteses}
+ , brazilian = [A]{hipótese}[As]{hipóteses}
+ , spanish = [La]{hipótesis}[Las]{hipótesis}
+ , schinese = {假设}{假设}
+ , tchinese = {假設}{假設}
+ , japanese = {仮設}{仮設}
+ , russian = {Гипотеза}{Гипотезы}
+ }
+ }
+
+\NameTheorem { lemma }
+ {
+ , heading = {
+ , english = Lemma
+ , french = Lemme
+ , ngerman = Lemma
+ , italian = Lemma
+ , portuguese = Lema
+ , brazilian = Lema
+ , spanish = Lema
+ , schinese = 引理
+ , tchinese = 引理
+ , japanese = 補題
+ , russian = Лемма
+ }
+ , crefname = {
+ , english = {lemma}{Lemmas}
+ , french = [le]{lemme}[les]{lemmes}
+ , ngerman = {Lemma}{Lemmata}
+ , italian = [il]{lemma}[i]{lemmi}
+ , portuguese = [o]{lema}[os]{lemas}
+ , brazilian = [o]{lema}[os]{lemas}
+ , spanish = [el]{lema}[las]{lemas}
+ , schinese = {引理}{引理}
+ , tchinese = {引理}{引理}
+ , japanese = {補題}{補題}
+ , russian = {лемма}{леммы}
+ }
+ , Crefname = {
+ , english = {Lemma}{Lemmas}
+ , french = [Le]{lemme}[Les]{lemmes}
+ , ngerman = {Lemma}{Lemmata}
+ , italian = [Il]{lemma}[I]{lemmi}
+ , portuguese = [O]{lema}[Os]{lemas}
+ , brazilian = [O]{lema}[Os]{lemas}
+ , spanish = [el]{lema}[las]{lemas}
+ , schinese = {引理}{引理}
+ , tchinese = {引理}{引理}
+ , japanese = {補題}{補題}
+ , russian = {Лемма}{Леммы}
+ }
+ }
+
+\NameTheorem { notation }
+ {
+ , heading = {
+ , english = Notation
+ , french = Notation
+ , ngerman = Notation
+ , italian = Notazione
+ , portuguese = Notação
+ , brazilian = Notação
+ , spanish = Notación
+ , schinese = 记号
+ , tchinese = 記號
+ , japanese = 記法
+ , russian = Нотация
+ }
+ , crefname = {
+ , english = {notation}{notations}
+ , french = [la]{notation}[les]{notations}
+ , ngerman = {Notation}{Notationen}
+ , italian = [la]{notazione}[le]{notazioni}
+ , portuguese = [a]{notação}[as]{notações}
+ , brazilian = [a]{notação}[as]{notações}
+ , spanish = [la]{notación}[las]{notaciones}
+ , schinese = {记号}{记号}
+ , tchinese = {記號}{記號}
+ , japanese = {記法}{記法}
+ , russian = {нотация}{нотации}
+ }
+ , Crefname = {
+ , english = {Notation}{Notations}
+ , french = [La]{notation}[Les]{notations}
+ , ngerman = {Notation}{Notationen}
+ , italian = [La]{notazione}[Le]{notazioni}
+ , portuguese = [A]{notação}[As]{notações}
+ , brazilian = [A]{notação}[As]{notações}
+ , spanish = [La]{notación}[Las]{notaciones}
+ , schinese = {记号}{记号}
+ , tchinese = {記號}{記號}
+ , japanese = {記法}{記法}
+ , russian = {Нотация}{Нотации}
+ }
+ }
+
+\NameTheorem { observation }
+ {
+ , heading = {
+ , english = Observation
+ , french = Observation
+ , ngerman = Beobachtung
+ , italian = Osservazione
+ , portuguese = Observação
+ , brazilian = Observação
+ , spanish = Observación
+ , schinese = 观察
+ , tchinese = 觀察
+ , japanese = 観察
+ , russian = Наблюдение
+ }
+ , crefname = {
+ , english = {observation}{observations}
+ , french = [la]{observation}[les]{observations}
+ , ngerman = {Beobachtung}{Beobachtungen}
+ , italian = [l']{osservazione}[le]{osservazioni}
+ , portuguese = [a]{observação}[as]{observações}
+ , brazilian = [a]{observação}[as]{observações}
+ , spanish = [la]{observación}[las]{observaciones}
+ , schinese = {观察}{观察}
+ , tchinese = {觀察}{觀察}
+ , japanese = {観察}{観察}
+ , russian = {наблюдение}{наблюдения}
+ }
+ , Crefname = {
+ , english = {Observation}{Observations}
+ , french = [La]{observation}[Les]{observations}
+ , ngerman = {Beobachtung}{Beobachtungen}
+ , italian = [L']{osservazione}[Le]{osservazioni}
+ , portuguese = [A]{observação}[As]{observações}
+ , brazilian = [A]{observação}[As]{observações}
+ , spanish = [La]{observación}[Las]{observaciones}
+ , schinese = {观察}{观察}
+ , tchinese = {觀察}{觀察}
+ , japanese = {観察}{観察}
+ , russian = {Наблюдение}{Наблюдения}
+ }
+ }
+
+\NameTheorem { postulate }
+ {
+ , heading = {
+ , english = Postulate
+ , french = Postulat
+ , ngerman = Postulat
+ , italian = Postulato
+ , portuguese = Postulado
+ , brazilian = Postulado
+ , spanish = Postulado
+ , schinese = 公设
+ , tchinese = 公設
+ , japanese = 公準
+ , russian = Постулат
+ }
+ , crefname = {
+ , english = {postulate}{postulates}
+ , french = [le]{postulat}[les]{postulats}
+ , ngerman = {Postulat}{Postulate}
+ , italian = [il]{postulato}[i]{postulati}
+ , portuguese = [o]{postulado}[os]{postulados}
+ , brazilian = [o]{postulado}[os]{postulados}
+ , spanish = [el]{postulado}[los]{postulados}
+ , schinese = {公设}{公设}
+ , tchinese = {公設}{公設}
+ , japanese = {公準}{公準}
+ , russian = {постулат}{постулаты}
+ }
+ , Crefname = {
+ , english = {Postulate}{Postulates}
+ , french = [Le]{postulat}[Les]{postulats}
+ , ngerman = {Postulat}{Postulate}
+ , italian = [Il]{postulato}[I]{postulati}
+ , portuguese = [O]{postulado}[Os]{postulados}
+ , brazilian = [O]{postulado}[Os]{postulados}
+ , spanish = [El]{postulado}[Los]{postulados}
+ , schinese = {公设}{公设}
+ , tchinese = {公設}{公設}
+ , japanese = {公準}{公準}
+ , russian = {Постулат}{Постулаты}
+ }
+ }
+
+\NameTheorem { problem }
+ {
+ , heading = {
+ , english = Problem
+ , french = Problème
+ , ngerman = Problem
+ , italian = Problema
+ , portuguese = Problema
+ , brazilian = Problema
+ , spanish = Problema
+ , schinese = 问题
+ , tchinese = 問題
+ , japanese = 問題
+ , russian = Проблема
+ }
+ , crefname = {
+ , english = {problem}{problems}
+ , french = [le]{problème}[les]{problèmes}
+ , ngerman = {Problem}{Probleme}
+ , italian = [il]{problema}[i]{problemi}
+ , portuguese = [o]{problema}[os]{problemas}
+ , brazilian = [o]{problema}[os]{problemas}
+ , spanish = [el]{problema}[los]{problemas}
+ , schinese = {问题}{问题}
+ , tchinese = {問題}{問題}
+ , japanese = {問題}{問題}
+ , russian = {проблема}{проблемы}
+ }
+ , Crefname = {
+ , english = {Problem}{Problems}
+ , french = [Le]{problème}[Les]{problèmes}
+ , ngerman = {Problem}{Probleme}
+ , italian = [Il]{problema}[I]{problemi}
+ , portuguese = [O]{problema}[Os]{problemas}
+ , brazilian = [O]{problema}[Os]{problemas}
+ , spanish = [El]{problema}[Los]{problemas}
+ , schinese = {问题}{问题}
+ , tchinese = {問題}{問題}
+ , japanese = {問題}{問題}
+ , russian = {Проблема}{Проблемы}
+ }
+ }
+
+\NameTheorem { property }
+ {
+ , heading = {
+ , english = Property
+ , french = Propriété
+ , ngerman = Eigenschaft
+ , italian = Proprietà
+ , portuguese = Propriedade
+ , brazilian = Propriedade
+ , spanish = Propiedad
+ , schinese = 性质
+ , tchinese = 性質
+ , japanese = 性質
+ , russian = Имущество
+ }
+ , crefname = {
+ , english = {property}{properties}
+ , french = [la]{propriété}[les]{propriétés}
+ , ngerman = {Eigenschaft}{Eigenschaften}
+ , italian = [la]{proprietà}[le]{proprietà}
+ , portuguese = [a]{propriedade}[as]{propriedades}
+ , brazilian = [a]{propriedade}[as]{propriedades}
+ , spanish = [la]{propiedad}[las]{propiedades}
+ , schinese = {性质}{性质}
+ , tchinese = {性質}{性質}
+ , japanese = {性質}{性質}
+ , russian = {имущество}{имущества}
+ }
+ , Crefname = {
+ , english = {Property}{Properties}
+ , french = [La]{propriété}[Les]{propriétés}
+ , ngerman = {Eigenschaft}{Eigenschaften}
+ , italian = [La]{proprietà}[Le]{proprietà}
+ , portuguese = [A]{propriedade}[As]{propriedades}
+ , brazilian = [A]{propriedade}[As]{propriedades}
+ , spanish = [La]{propiedad}[Las]{propiedades}
+ , schinese = {性质}{性质}
+ , tchinese = {性質}{性質}
+ , japanese = {性質}{性質}
+ , russian = {Имущество}{Имущества}
+ }
+ }
+
+\NameTheorem { proposition }
+ {
+ , heading = {
+ , english = Proposition
+ , french = Proposition
+ , ngerman = Proposition
+ , italian = Proposizione
+ , portuguese = Proposição
+ , brazilian = Proposição
+ , spanish = Proposición
+ , schinese = 命题
+ , tchinese = 命題
+ , japanese = 命題
+ , russian = Предложение
+ }
+ , crefname = {
+ , english = {proposition}{propositions}
+ , french = [la]{proposition}[les]{propositions}
+ , ngerman = {Proposition}{Propositionen}
+ , italian = [la]{proposizione}[le]{proposte}
+ , portuguese = [a]{proposição}[as]{proposições}
+ , brazilian = [a]{proposição}[as]{proposições}
+ , spanish = [la]{proposición}[las]{proposiciones}
+ , schinese = {命题}{命题}
+ , tchinese = {命題}{命題}
+ , japanese = {命題}{命題}
+ , russian = {предложение}{предложения}
+ }
+ , Crefname = {
+ , english = {Proposition}{Propositions}
+ , french = [La]{proposition}[Les]{propositions}
+ , ngerman = {Proposition}{Propositionen}
+ , italian = [La]{proposizione}[Le]{proposte}
+ , portuguese = [A]{proposição}[As]{proposições}
+ , brazilian = [A]{proposição}[As]{proposições}
+ , spanish = [La]{proposición}[Las]{proposiciones}
+ , schinese = {命题}{命题}
+ , tchinese = {命題}{命題}
+ , japanese = {命題}{命題}
+ , russian = {Предложение}{Предложения}
+ }
+ }
+
+\NameTheorem { question }
+ {
+ , heading = {
+ , english = Question
+ , french = Question
+ , ngerman = Frage
+ , italian = Domanda
+ , portuguese = Pergunta
+ , brazilian = Pergunta
+ , spanish = Pregunta
+ , schinese = 问题
+ , tchinese = 問題
+ , japanese = 問題
+ , russian = Вопрос
+ }
+ , crefname = {
+ , english = {question}{questions}
+ , french = [la]{question}[les]{questions}
+ , ngerman = {Frage}{Fragen}
+ , italian = [la]{domanda}[le]{domande}
+ , portuguese = [a]{pergunta}[as]{perguntas}
+ , brazilian = [a]{pergunta}[as]{perguntas}
+ , spanish = [la]{pregunta}[las]{preguntas}
+ , schinese = {问题}{问题}
+ , tchinese = {問題}{問題}
+ , japanese = {問題}{問題}
+ , russian = {вопрос}{вопросы}
+ }
+ , Crefname = {
+ , english = {Question}{Questions}
+ , french = [La]{question}[Les]{questions}
+ , ngerman = {Frage}{Fragen}
+ , italian = [La]{domanda}[Le]{domande}
+ , portuguese = [A]{pergunta}[As]{perguntas}
+ , brazilian = [A]{pergunta}[As]{perguntas}
+ , spanish = [La]{pregunta}[Las]{preguntas}
+ , schinese = {问题}{问题}
+ , tchinese = {問題}{問題}
+ , japanese = {問題}{問題}
+ , russian = {Вопрос}{Вопросы}
+ }
+ }
+
+\NameTheorem { remark }
+ {
+ , heading = {
+ , english = Remark
+ , french = Remarque
+ , ngerman = Bemerkung
+ , italian = Commento
+ , portuguese = Comentário
+ , brazilian = Comentário
+ , spanish = Comentario
+ , schinese = 备注
+ , tchinese = 備註
+ , japanese = 注釈
+ , russian = Замечание
+ }
+ , crefname = {
+ , english = {remark}{remarks}
+ , french = [la]{remarque}[les]{remarques}
+ , ngerman = {Bemerkung}{Bemerkungen}
+ , italian = [il]{commento}[i]{commenti}
+ , portuguese = [o]{comentário}[os]{comentários}
+ , brazilian = [o]{comentário}[os]{comentários}
+ , spanish = [el]{comentario}[los]{comentarios}
+ , schinese = {备注}{备注}
+ , tchinese = {備註}{備註}
+ , japanese = {注釈}{注釈}
+ , russian = {замечание}{замечания}
+ }
+ , Crefname = {
+ , english = {Remark}{Remarks}
+ , french = [La]{remarque}[Les]{remarques}
+ , ngerman = {Bemerkung}{Bemerkungen}
+ , italian = [Il]{commento}[I]{commenti}
+ , portuguese = [O]{comentário}[Os]{comentários}
+ , brazilian = [O]{comentário}[Os]{comentários}
+ , spanish = [El]{comentario}[Los]{comentarios}
+ , schinese = {备注}{备注}
+ , tchinese = {備註}{備註}
+ , japanese = {注釈}{注釈}
+ , russian = {Замечание}{Замечания}
+ }
+ }
+
+\NameTheorem { theorem }
+ {
+ , heading = {
+ , english = Theorem
+ , french = Théorème
+ , ngerman = Satz
+ , italian = Teorema
+ , portuguese = Teorema
+ , brazilian = Teorema
+ , spanish = Teorema
+ , schinese = 定理
+ , tchinese = 定理
+ , japanese = 定理
+ , russian = Теорема
+ }
+ , crefname = {
+ , english = {theorem}{theorems}
+ , french = [le]{théorème}[les]{théorèmes}
+ , ngerman = {Satz}{Sätze}
+ , italian = [il]{teorema}[i]{teoremi}
+ , portuguese = [o]{teorema}[os]{teoremas}
+ , brazilian = [o]{teorema}[os]{teoremas}
+ , spanish = [el]{teorema}[los]{teoremas}
+ , schinese = {定理}{定理}
+ , tchinese = {定理}{定理}
+ , japanese = {定理}{定理}
+ , russian = {теорема}{теоремы}
+ }
+ , Crefname = {
+ , english = {Theorem}{Theorems}
+ , french = [Le]{théorème}[Les]{théorèmes}
+ , ngerman = {Satz}{Sätze}
+ , italian = [Il]{teorema}[I]{teoremi}
+ , portuguese = [O]{teorema}[Os]{teoremas}
+ , brazilian = [O]{teorema}[Os]{teoremas}
+ , spanish = [El]{teorema}[Los]{teoremas}
+ , schinese = {定理}{定理}
+ , tchinese = {定理}{定理}
+ , japanese = {定理}{定理}
+ , russian = {Теорема}{Теоремы}
+ }
+ }
+
+
+\endinput
+%%
+%% End of file `create-theorem.sty'.
diff --git a/macros/latex/contrib/crefthe/crefthe-doc.pdf b/macros/latex/contrib/crefthe/crefthe-doc.pdf
index 4e1a55b9d5..7f4f6ea5e0 100644
--- a/macros/latex/contrib/crefthe/crefthe-doc.pdf
+++ b/macros/latex/contrib/crefthe/crefthe-doc.pdf
Binary files differ
diff --git a/macros/latex/contrib/crefthe/crefthe-doc.tex b/macros/latex/contrib/crefthe/crefthe-doc.tex
index 04051d0f60..70cdc89d92 100644
--- a/macros/latex/contrib/crefthe/crefthe-doc.tex
+++ b/macros/latex/contrib/crefthe/crefthe-doc.tex
@@ -77,19 +77,23 @@
\newenvironment{demo}{%
\LocallyStopLineNumbers%
\begin{tcolorbox}[enhanced jigsaw,pad at break*=1mm,breakable,
- left=4mm,right=4mm,top=0.5mm,bottom=0mm,
+ left=2.5mm,right=3mm,top=0.5mm,bottom=0mm,
colback=gray!5!paper,boxrule=0pt,frame hidden,
- borderline west={1.5mm}{0mm}{gray!55!paper},arc=.7mm]%
+ borderline west={1.2mm}{0mm}{gray!55!paper},arc=.7mm]%
}{\end{tcolorbox}\ResumeLineNumbers}
+\newcommand{\packageoption}[1]{\texttt{\textcolor{black!67!green}{#1}}}
+\newcommand{\commandoption}[1]{\texttt{\textcolor{black!67!cyan}{#1}}}
+
+
\newcommand{\crefthepackage}{\textsf{crefthe}}
\begin{document}
-\title{\crefthepackage{} - cross referencing with proper definite articles}
+\title{\crefthepackage{}\\\smallskip\itshape Cross referencing with proper definite articles}
\author{Jinwen XU}
-\thanks{Corresponding to: \texttt{\crefthepackage{} 2022/02/20}}
+\thanks{Corresponding to: \texttt{\crefthepackage{} 2022/02/27}}
\date{February 2022, in Paris}
\maketitle
@@ -231,7 +235,7 @@ To place hyperlinks at the right place, \lstinline|\crefthename| touches the cor
\crefthepackage{} loads \textsf{cleveref} automatically and pass all the options to it. All its commands, used without optional arguments, degenerate to those in \textsf{cleveref}. For example, \lstinline|\crefthe{...}| is the same as \lstinline|\cref{...}|, and \lstinline|\crefthename| is the same as \lstinline|\crefname| if the definite articles are not specified. That said, you can safely use the command \lstinline|\crefthe| everywhere in your document without causing extra trouble.
-With the option \texttt{overwrite}, user commands in \textsf{cleveref} will be replaced by those offered here, thus you can simply write \lstinline|\cref| for \lstinline|\crefthe| -- and similarly with \lstinline|\Cref|, \lstinline|\crefname| and \lstinline|\Crefname|.
+With the package option \packageoption{overwrite}, user commands in \textsf{cleveref} will be replaced by those offered here, thus you can simply write \lstinline|\cref| for \lstinline|\crefthe| -- and similarly with \lstinline|\Cref|, \lstinline|\crefname| and \lstinline|\Crefname|.
\section{Known issues}
@@ -241,5 +245,12 @@ With the option \texttt{overwrite}, user commands in \textsf{cleveref} will be r
\item The names of theorem-like environments are not provided here, you need to define them by yourself. However, users are encouraged to use the \ProjLib{} toolkit (more specifically, the internal package \textsf{create-theorem}), which already handles everything for you.
\end{itemize}
+\medskip
+If you run into any issues or have ideas for improvement, feel free to discuss on:
+\begin{center}
+ \url{https://github.com/Jinwen-XU/crefthe/issues}
+\end{center}
+or email me via \href{mailto:ProjLib@outlook.com}{\texttt{ProjLib@outlook.com}}.
+
\end{document}
diff --git a/macros/latex/contrib/crefthe/crefthe.sty b/macros/latex/contrib/crefthe/crefthe.sty
index 7edf7e1788..213f3d2e60 100644
--- a/macros/latex/contrib/crefthe/crefthe.sty
+++ b/macros/latex/contrib/crefthe/crefthe.sty
@@ -13,7 +13,7 @@
\RequirePackage{l3keys2e}
\ProvidesExplPackage
{crefthe}
- {2022/02/20} {}
+ {2022/02/27} {}
{Cross referencing with proper definite articles}
\keys_define:nn { crefthe }
@@ -26,6 +26,7 @@
\ProcessKeysOptions { crefthe }
\RequirePackage { cleveref }
+\RequirePackage { regexpatch }
\NewCommandCopy \crefthe_cref_original:w \cref
\NewCommandCopy \crefthe_Cref_original:w \Cref
@@ -41,20 +42,28 @@
\tl_new:N \l__crefthe_prep_once_tl
\tl_new:N \l__crefthe_prep_each_tl
-\bool_new:N \l__crefthe_uppercase_bool
+\bool_new:N \g__crefthe_uppercase_bool
+\bool_new:N \g__crefthe_has_prep_bool
\NewDocumentCommand \crefthe { s t- t+ O{} m }
{
- \bool_set_false:N \l__crefthe_uppercase_bool
+ \bool_gset_false:N \g__crefthe_uppercase_bool
\__crefthe_cref_general:NNNnnN #1 #2 #3 { #4 } { #5 } \crefthe_cref_original:w
}
\NewDocumentCommand \Crefthe { s t- t+ O{} m }
{
- \bool_set_true:N \l__crefthe_uppercase_bool
+ \bool_gset_true:N \g__crefthe_uppercase_bool
\__crefthe_cref_general:NNNnnN #1 #2 #3 { #4 } { #5 } \crefthe_Cref_original:w
}
-\cs_new_protected:Npn \__crefthe_cref_general:NNNnnN #1 #2 #3 #4 #5 #6
+\cs_new_protected:Nn \__crefthe_cref_general:NNNnnN
{
+ \tl_if_blank:eTF #4
+ {
+ \bool_gset_false:N \g__crefthe_has_prep_bool
+ }
+ {
+ \bool_gset_true:N \g__crefthe_has_prep_bool
+ }
\bool_if:NTF #2
{ \tl_set:Nn \l__crefthe_prep_once_tl { #4 } }
{
@@ -78,7 +87,7 @@
}
% \__crefthe_prep_mode: defines the default mode for supported languages
-\cs_new:Npn \__crefthe_prep_mode:
+\cs_new:Nn \__crefthe_prep_mode:
{
\str_case:Vn \languagename
{
@@ -94,51 +103,80 @@
{
\__crefthe_name_general:nnnnnn { #1 } { #2 } { #3 } { #4 } { #5 } { c }
}
+
\NewDocumentCommand \Crefthename { m O{} m O{} m }
{
\__crefthe_name_general:nnnnnn { #1 } { #2 } { #3 } { #4 } { #5 } { C }
}
-\cs_new_protected:Npn \__crefthe_name_general:nnnnnn #1 #2 #3 #4 #5 #6
+
+\cs_new_protected:Nn \__crefthe_name_general:nnnnnn
{
% #6 is c or C
\tl_if_blank:nTF { #2 }
{
- \cs:w crefthe_#6refname_original:w \cs_end: { #1 } { #3 } { #5 }
- \cs_set:cn { #6ref_#1_format:nnn } { ##2 #3 ~ ##1 ##3 }
- \cs_set:cn { #6ref_#1_format_first:nnn } { ##2 #5 ~ ##1 ##3 }
+ \__crefthe_name_general_do:nnnnnn { #1 } {} { #3 } {} { #5 } { #6 }
}
{
- \cs:w crefthe_#6refname_original:w \cs_end: { #1 } { \crefthemark { #2 } #3 } { \crefthemark { #4 } #5 }
- \cs_set:cn { #6ref_#1_format:nnn } { \crefthemark { #2 } ##2 #3 ~ ##1 ##3 }
- \cs_set:cn { #6ref_#1_format_first:nnn } { \crefthemark { #4 } ##2 #5 ~ ##1 ##3 }
+ \__crefthe_name_general_do:nnnnnn { #1 } { \crefthemark { #2 } } { #3 } { \crefthemark { #4 } } { #5 } { #6 }
}
+ }
+
+\cs_new_protected:Nn \__crefthe_name_general_do:nnnnnn
+ {
+ \use:c { crefthe_#6refname_original:w } { #1 } { #2 #3 } { #4 #5 }
\hook_gput_code:nnn { begindocument } { crefthe }
{
- \cs_set_eq:cc { #6ref@ #1 @format } { #6ref_#1_format:nnn }
- \cs_set_eq:cc { #6ref@ #1 @format@first } { #6ref_#1_format_first:nnn }
+ \crefthe_patch_format:nnnnn { 2 } { #6ref@#1@format } { #6ref@#1@name } { #2 } { #3 }
+ \crefthe_patch_format:nnnnn { 2 } { #6ref@#1@format@first } { #6ref@#1@name@plural } { #4 } { #5 }
+ \crefthe_patch_format:nnnnn { 3 } { #6refrange@#1@format } { #6ref@#1@name@plural } { #4 } { #5 }
+ \crefthe_patch_format:nnnnn { 3 } { #6refrange@#1@format@first } { #6ref@#1@name@plural } { #4 } { #5 }
+ }
+ }
+
+\seq_new:N \g__crefthe_already_patched_seq
+
+\cs_new_protected:Nn \crefthe_patch_format:nnnnn
+ % #1 = the # number for the beginning mark of hyperlink
+ % #2 = name of the format command
+ % #3 = name of the command after hyperlink mark
+ % #4 = new content before the mark
+ % #5 = new content after the mark
+ {
+ \seq_if_in:NnF \g__crefthe_already_patched_seq { #2 - #3 }
+ {
+ \makeatletter
+ \tl_set:Nn \l_tmpa_tl { #4 }
+ \tl_set:Nn \l_tmpb_tl { #5 }
+ \exp_args:Nc \regexpatchcmd { #2 }
+ { \cP. #1 \c{ #3 } }
+ { \u{l_tmpa_tl} \cP\# #1 \u{l_tmpb_tl} }
+ { } { \PatchFailed }
+ \makeatother
+ \seq_gput_right:Nn \g__crefthe_already_patched_seq { #2 - #3 }
}
}
+
\cs_generate_variant:Nn \text_lowercase:n { V }
\NewDocumentCommand \crefthemark { m }
{
\crefthe_contraction:Ve \l__crefthe_prep_each_tl
{ \crefthe_contraction:Vn \l__crefthe_prep_once_tl { #1 } }
\tl_gclear:N \l__crefthe_prep_once_tl
- \tl_set:Nx \l__crefthe_prep_each_tl
+ \tl_gset:Nx \l__crefthe_prep_each_tl
{ \text_lowercase:V \l__crefthe_prep_each_tl }
\str_if_eq:eeF { \str_tail:n { #1 } } { ' } { ~ }
- \bool_set_false:N \l__crefthe_uppercase_bool
+ \bool_gset_false:N \g__crefthe_uppercase_bool
}
\prg_generate_conditional_variant:Nnn \str_case_e:nn { nv } { T, F, TF }
-\cs_new:Npn \crefthe_contraction:nn #1#2
+\cs_new:Nn \crefthe_contraction:nn
{
\exp_args:Ne \__crefthe_contraction:nnn
{ \text_lowercase:n { #2 } } { #1 } { #2 }
}
\cs_generate_variant:Nn \crefthe_contraction:nn { V, Ve }
-\cs_new:Npn \__crefthe_contraction:nnn #1 #2 #3
+\cs_new:Nn \__crefthe_contraction:nnn
{
% #1 is \text_lowercase:n { #3 }
% #2 is the preposition
@@ -157,9 +195,9 @@
{ #2~#3 }
}
}
-\cs_new:Npn \__crefthe_conditional_uppercase:n #1
+\cs_new:Nn \__crefthe_conditional_uppercase:n
{
- \bool_if:NTF \l__crefthe_uppercase_bool
+ \bool_if:NTF \g__crefthe_uppercase_bool
{ \text_titlecase_first:n }
{ \use:n }
{ #1 }
diff --git a/macros/latex/contrib/fnpct/README b/macros/latex/contrib/fnpct/README
index 88d9dc33ef..7a6961da7f 100644
--- a/macros/latex/contrib/fnpct/README
+++ b/macros/latex/contrib/fnpct/README
@@ -1,5 +1,5 @@
--------------------------------------------------------------------------
-the FNPCT package v1.1 2022/02/03
+the FNPCT package v1.1a 2022/02/27
footnotes' interaction with punctuation
@@ -23,8 +23,9 @@ This work has the LPPL maintenance status `maintained'.
The Current Maintainer of this work is Clemens Niederberger.
--------------------------------------------------------------------------
The fnpct package consists of the files
- - fnpct.sty,
- - fnpct_en.tex and fnpct_en.pdf
+ - fnpct.sty, fnpct-2019-10-05.sty
+ - fnpct-check-packages.tex
+ - fnpct-manual.tex, fnpct-manual.cls fnpct-manual.pdf
- README
--------------------------------------------------------------------------
the package is inspired by the following question on TeX.SE:
diff --git a/macros/latex/contrib/fnpct/fnpct-2019-10-05.sty b/macros/latex/contrib/fnpct/fnpct-2019-10-05.sty
new file mode 100644
index 0000000000..e599c1afd3
--- /dev/null
+++ b/macros/latex/contrib/fnpct/fnpct-2019-10-05.sty
@@ -0,0 +1,1191 @@
+% --------------------------------------------------------------------------
+% the FNPCT package
+%
+% footnote kerning
+%
+% --------------------------------------------------------------------------
+% Clemens Niederberger
+% Web: https://github.com/cgnieder/fnpct/
+% E-Mail: contact@mychemistry.eu
+% --------------------------------------------------------------------------
+% Copyright 2012--2020 Clemens Niederberger
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+% http://www.latex-project.org/lppl.txt
+% and version 1.3 or later is part of all distributions of LaTeX
+% version 2005/12/01 or later.
+%
+% This work has the LPPL maintenance status `maintained'.
+%
+% The Current Maintainer of this work is Clemens Niederberger.
+% --------------------------------------------------------------------------
+% If you have any ideas, questions, suggestions or bugs to report, please
+% feel free to contact me.
+% --------------------------------------------------------------------------
+% the package is inspired by the following question on TeX.SE:
+% http://tex.stackexchange.com/q/56094/5049
+% --------------------------------------------------------------------------
+\RequirePackage {xparse,l3keys2e,scrlfile}
+\ExplSyntaxOn
+
+\tl_const:Nn \c_fnpct_date_tl {2019/10/05}
+\tl_const:Nn \c_fnpct_version_major_number_tl {0}
+\tl_const:Nn \c_fnpct_version_minor_number_tl {5}
+\tl_const:Nn \c_fnpct_version_subrelease_tl {}
+\tl_const:Nx \c_fnpct_version_number_tl
+ {
+ \c_fnpct_version_major_number_tl .
+ \c_fnpct_version_minor_number_tl
+ }
+\tl_const:Nx \c_fnpct_version_tl
+ {
+ \c_fnpct_version_number_tl
+ \c_fnpct_version_subrelease_tl
+ }
+\tl_const:Nn \c_fnpct_info_tl {footnote~ kerning}
+
+\ProvidesExplPackage
+ {fnpct}
+ {\c_fnpct_date_tl}
+ {\c_fnpct_version_tl}
+ {\c_fnpct_info_tl}
+
+% --------------------------------------------------------------------------
+% this is plain's \nobreak:
+\cs_new:Npn \fnpct_no_break: { \tex_penalty:D 10000 \scan_stop: }
+
+% --------------------------------------------------------------------------
+% variables:
+\tl_new:N \l__fnpct_tmpa_tl
+\tl_new:N \l__fnpct_tmpb_tl
+
+\dim_new:N \l__fnpct_tmpa_dim
+\dim_new:N \l__fnpct_tmpb_dim
+
+\int_new:N \l__fnpct_tmpa_int
+
+\seq_new:N \l__fnpct_tmpa_seq
+\seq_new:N \l__fnpct_tmpb_seq
+
+\dim_new:N \l__fnpct_after_comma_dim
+\dim_set:Nn \l__fnpct_after_comma_dim {-.06em}
+\dim_new:N \l__fnpct_after_dot_dim
+\dim_set:Nn \l__fnpct_after_dot_dim {-.06em}
+\dim_new:N \l__fnpct_before_comma_dim
+\dim_set:Nn \l__fnpct_before_comma_dim {-.16em}
+\dim_new:N \l__fnpct_before_dot_dim
+\dim_set:Nn \l__fnpct_before_dot_dim {-.16em}
+\dim_new:N \l__fnpct_before_footnote_dim
+\dim_set:Nn \l__fnpct_before_footnote_dim {.06em}
+\dim_new:N \l__fnpct_normal_mark_width_dim
+\dim_set:Nn \l__fnpct_normal_mark_width_dim {1em}
+\dim_new:N \l__fnpct_normal_indent_dim
+\dim_set:Nn \l__fnpct_normal_indent_dim {1.5em}
+\dim_new:N \l__fnpct_normal_parindent_dim
+\dim_set:Nn \l__fnpct_normal_parindent_dim {1em}
+\dim_new:N \l__fnpct_french_before_footnote_space_dim
+\dim_set:Nn \l__fnpct_french_before_footnote_space_dim {.16667em}
+
+\int_new:N \l__fnpct_multiple_notes_int
+
+\tl_new:N \l__fnpct_multiple_footnotes_delimiter_tl
+\tl_set:Nn \l__fnpct_multiple_footnotes_delimiter_tl {;}
+\tl_new:N \l__fnpct_multiple_footnote_separator_tl
+\tl_new:N \l__fnpct_last_punct_mark_tl
+\tl_new:N \l__fnpct_punctuation_marks_tl
+\tl_set:Nn \l__fnpct_punctuation_marks_tl {.,}
+\tl_new:N \l__fnpct_multiple_true_tl
+\tl_new:N \l__fnpct_multiple_false_tl
+
+\bool_new:N \l__fnpct_strict_bool
+\bool_new:N \l__fnpct_punct_after_bool
+\bool_new:N \l__fnpct_dont_mess_around_bool
+\bool_new:N \l__fnpct_multiple_default_bool
+\bool_new:N \l__fnpct_multiple_footnotes_bool
+\bool_new:N \l__fnpct_reverse_switch_bool
+\bool_new:N \l__fnpct_normal_marks_bool
+\bool_new:N \l__fnpct_hyperref_bool
+\bool_new:N \g__fnpct_after_punctuation_bool
+\bool_new:N \g__fnpct_only_text_bool
+\bool_new:N \l__fnpct_makepagenote_bool
+
+\seq_new:N \l__fnpct_multiple_footnotes_seq
+\seq_new:N \l__fnpct_footnote_class_seq
+
+\prop_new:N \l__fnpct_punctuation_marks_after_prop
+\prop_new:N \l__fnpct_punctuation_marks_before_prop
+\prop_new:N \l__fnpct_sepfootnote_foot_classes_prop
+\prop_new:N \l__fnpct_sepfootnote_end_classes_prop
+\prop_new:N \l__fnpct_sepfootnote_symbol_classes_prop
+\prop_new:N \g__fnpct_adapted_notes_prop
+\prop_new:N \g__fnpct_inner_footnote_prop
+\prop_new:N \g__fnpct_inner_footnote_hyperref_prop
+\prop_new:N \l__fnpct_footnote_fixfoot_prop
+
+% --------------------------------------------------------------------------
+% variants:
+\cs_generate_variant:Nn \str_remove_once:Nn {NV}
+\cs_generate_variant:Nn \tl_remove_all:Nn {NV}
+\cs_generate_variant:Nn \tl_put_right:Nn {NV}
+\cs_generate_variant:Nn \seq_set_split:Nnn {NnV,NV}
+\cs_generate_variant:Nn \prop_gput:Nnn {Nx,Nxo}
+\cs_generate_variant:Nn \prop_put:Nnn {Nnx}
+\cs_generate_variant:Nn \prop_if_in:NnTF {Nx}
+
+% --------------------------------------------------------------------------
+% messages:
+\cs_new_protected:Npn \fnpct_dont_mess_around:
+ {
+ \iow_log:n { ................................................. }
+ \iow_log:n { . ~ fnpct~info: }
+ \iow_log:n { . }
+ \iow_log:n { . ~ All~right,~not~messing~around.~:( }
+ \iow_log:n { . ~ But~I'd~really~love~to. }
+ \iow_log:n { . ~ (https://www.youtube.com/results?search_query=mess+around+ray+charles) }
+ \iow_log:n { ................................................. }
+ }
+
+\msg_new:nnn {fnpct} {already-adapted}
+ {
+ The~ command~ \token_to_str:N #1 \c_space_tl has~ already~ been~
+ adapted. \\
+ I~ will~ do~ nothing~ instead.
+ }
+
+\msg_new:nnn {fnpct} {pagenote}
+ {
+ If~ you~ use~ the~ `pagenote'~ package~ with~ `fnpct'~ please~ make~ sure~
+ to~ use~ \token_to_str:N \makepagenote \c_space_tl after~ loading~
+ `fnpct'!
+ }
+
+\msg_new:nnn {fnpct} {deprecated}
+ {
+ You've~ tried~ setting~ #1~ `#2'~ \msg_line_context: .~ However,~
+ #1~ `#2'~ is~ deprecated.~
+ \tl_if_blank:nF {#3} {Please~ use~ #1~ `#3'~ instead.~}
+ Refer~ to~ the~ manual~ for~ details.
+ }
+
+\cs_new_protected:Npn \fnpct_message:nx #1#2
+ {
+ \bool_if:NTF \l__fnpct_strict_bool
+ { \msg_error:nnx {fnpct} {#1} }
+ { \msg_warning:nnx {fnpct} {#1} }
+ {#2}
+ }
+
+% --------------------------------------------------------------------------
+\prg_new_conditional:Npnn \fnpct_if_package_loaded:n #1 {p,T,F,TF}
+ {
+ \@ifpackageloaded {#1}
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+
+% --------------------------------------------------------------------------
+% multiple footnote input and output variables:
+% check for KOMA-Script's \multfootsep:
+\cs_if_exist:NTF \KOMAClassName
+ { \tl_set:Nn \l__fnpct_multiple_footnote_separator_tl { \multfootsep } }
+ { \tl_set:Nn \l__fnpct_multiple_footnote_separator_tl {,} }
+
+% --------------------------------------------------------------------------
+% #1: before/after
+% #2: punctuation mark
+% #3: dimension
+\cs_new_protected:Npn \fnpct_set_punctuation_dim:nnn #1#2#3
+ {
+ \prop_put:cnx { l__fnpct_punctuation_marks_#1_prop }
+ {#2}
+ { \dim_eval:n {#3} }
+ }
+\cs_generate_variant:Nn \fnpct_set_punctuation_dim:nnn {nV}
+
+\fnpct_set_punctuation_dim:nnn {after} {.} { \l__fnpct_after_dot_dim }
+\fnpct_set_punctuation_dim:nnn {after} {,} { \l__fnpct_after_comma_dim }
+\fnpct_set_punctuation_dim:nnn {before} {.} { \l__fnpct_before_dot_dim }
+\fnpct_set_punctuation_dim:nnn {before} {,} { \l__fnpct_before_comma_dim }
+
+\cs_new_protected:Npn \fnpct_rm_punctuation:n #1
+ {
+ \tl_remove_all:Nn \l__fnpct_punctuation_marks_tl {#1}
+ \prop_del:Nn \l__fnpct_punctuation_marks_after_prop {#1}
+ \prop_del:Nn \l__fnpct_punctuation_marks_before_prop {#1}
+ }
+
+\cs_new_protected:Npn \fnpct_add_punctuation:n #1
+ { \__fnpct_add_punctuation:w #1 [ \q_no_value ] \q_no_value \q_stop }
+\cs_generate_variant:Nn \fnpct_add_punctuation:n {V}
+
+\cs_new_protected:Npn \__fnpct_add_punctuation:w #1[#2]#3 \q_stop
+ {
+ \quark_if_no_value:nTF {#2}
+ {
+ % there is no option at all
+ \tl_map_inline:nn {#1}
+ {
+ \tl_put_right:Nn \l__fnpct_punctuation_marks_tl {##1}
+ \fnpct_set_punctuation_dim:nnn {after} {##1} {0pt}
+ \fnpct_set_punctuation_dim:nnn {before} {##1} {0pt}
+ \tl_set:No \l__fnpct_last_punct_mark_tl {##1}
+ }
+ }
+ {
+ \tl_if_blank:nTF {#1}
+ {
+ % #2 is a second option (= before space)
+ \fnpct_set_punctuation_dim:nVn {before}
+ \l__fnpct_last_punct_mark_tl
+ {#2}
+ }
+ {
+ % #2 is a first option (= after space), #1 might be more than one token:
+ \tl_if_single_token:nTF {#1}
+ {
+ \tl_put_right:Nn \l__fnpct_punctuation_marks_tl {#1}
+ \fnpct_set_punctuation_dim:nnn {after} {#1} {#2}
+ \fnpct_set_punctuation_dim:nnn {before} {#1} {0pt}
+ \tl_set:No \l__fnpct_last_punct_mark_tl {#1}
+ }
+ {
+ \int_zero:N \l__fnpct_tmpa_int
+ \tl_map_inline:nn {#1}
+ {
+ \tl_put_right:Nn \l__fnpct_punctuation_marks_tl {##1}
+ \int_compare:nTF { \l__fnpct_tmpa_int < ( \tl_count:n {#1} - 1 ) }
+ {
+ \fnpct_set_punctuation_dim:nnn {after} {##1} {0pt}
+ \fnpct_set_punctuation_dim:nnn {before} {##1} {0pt}
+ }
+ {
+ \fnpct_set_punctuation_dim:nnn {after} {##1} {#2}
+ \fnpct_set_punctuation_dim:nnn {before} {##1} {0pt}
+ }
+ \tl_set:No \l__fnpct_last_punct_mark_tl {##1}
+ \int_incr:N \l__fnpct_tmpa_int
+ }
+ }
+ }
+ }
+ % is there more?
+ \tl_if_eq:nnF {#3} { [\q_no_value]\q_no_value }
+ {
+ \quark_if_no_value:nF {#3}
+ {
+ \tl_set:Nn \l__fnpct_tmpa_tl {#3}
+ \tl_remove_all:Nn \l__fnpct_tmpa_tl { [\q_no_value]\q_no_value }
+ \fnpct_add_punctuation:V \l__fnpct_tmpa_tl
+ }
+ }
+ }
+
+% --------------------------------------------------------------------------
+% options:
+\keys_define:nn {fnpct}
+ {
+ bigfoot-default-top .code:n =
+ \msg_warning:nnnn {fnpct} {deprecated} {option} {bigfoot-default-top} ,
+ strict .bool_set:N = \l__fnpct_strict_bool ,
+ after-comma-space .code:n =
+ \fnpct_set_punctuation_dim:nnn {after} {,} {#1} ,
+ after-dot-space .code:n =
+ \fnpct_set_punctuation_dim:nnn {after} {.} {#1} ,
+ before-comma-space .code:n =
+ \fnpct_set_punctuation_dim:nnn {before} {,} {#1} ,
+ before-dot-space .code:n =
+ \fnpct_set_punctuation_dim:nnn {before} {.} {#1} ,
+ after-punct-space .code:n =
+ \prop_map_inline:Nn \l__fnpct_punctuation_marks_after_prop
+ { \fnpct_set_punctuation_dim:nnn {after} {##1} {#1} } ,
+ before-punct-space .code:n =
+ \prop_map_inline:Nn \l__fnpct_punctuation_marks_before_prop
+ { \fnpct_set_punctuation_dim:nnn {before} {##1} {#1} } ,
+ before-footnote-space .dim_set:N = \l__fnpct_before_footnote_dim ,
+ french-before-footnote-space .dim_set:N =
+ \l__fnpct_french_before_footnote_space_dim ,
+ punct-after .bool_set:N = \l__fnpct_punct_after_bool ,
+ dont-mess-around .code:n =
+ \prop_map_inline:Nn \l__fnpct_punctuation_marks_after_prop
+ { \fnpct_set_punctuation_dim:nnn {after} {##1} {0pt} }
+ \prop_map_inline:Nn \l__fnpct_punctuation_marks_before_prop
+ { \fnpct_set_punctuation_dim:nnn {before} {##1} {0pt} }
+ \dim_zero:N \l__fnpct_before_footnote_dim
+ \bool_set_true:N \l__fnpct_punct_after_bool
+ \bool_set_true:N \l__fnpct_dont_mess_around_bool
+ \fnpct_dont_mess_around: ,
+ mult-fn-delim .tl_set:N = \l__fnpct_multiple_footnotes_delimiter_tl ,
+ mult-fn-sep .tl_set:N = \l__fnpct_multiple_footnote_separator_tl ,
+ multiple .bool_set:N = \l__fnpct_multiple_default_bool ,
+ normal-marks .bool_set:N = \l__fnpct_normal_marks_bool ,
+ normal-mark-width .dim_set:N = \l__fnpct_normal_mark_width_dim ,
+ normal-indent .dim_set:N = \l__fnpct_normal_indent_dim ,
+ normal-parindent .dim_set:N = \l__fnpct_normal_parindent_dim ,
+ verb-format .tl_set:N = \l__fnpct_verbatim_format_tl ,
+ add-punct-marks .code:n = \fnpct_add_punctuation:n {#1} ,
+ remove-punct-marks .code:n =
+ \tl_map_function:nN {#1} \fnpct_rm_punctuation:n
+ }
+
+\ProcessKeysOptions {fnpct}
+
+% --------------------------------------------------------------------------
+% MAIN INTERNAL FOOTNOTE FUNCTION:
+% write the notes:
+\cs_new:Npn \__fnpct_no_value_or_quark_no_value:nTF #1#2#3
+ {
+ \IfNoValueTF {#1} {#2}
+ { \quark_if_no_value:nTF {#1} {#2} {#3} }
+ }
+
+\cs_new:Npn \fnpct_write_note:Nnnn #1#2#3#4
+ {
+ \__fnpct_no_value_or_quark_no_value:nTF {#2}
+ { #1 {#4} }
+ {
+ \__fnpct_no_value_or_quark_no_value:nTF {#3}
+ { #1 [#2] {#4} }
+ { #1 [#2] [#3] {#4} }
+ }
+ }
+
+\cs_new:Npn \fnpct_write_snote:Nnnn #1#2#3#4
+ {
+ \__fnpct_no_value_or_quark_no_value:nTF {#2}
+ {
+ \__fnpct_no_value_or_quark_no_value:nTF {#3}
+ { #1 {#4} }
+ { #1 [#3] {#4} }
+ }
+ {
+ \__fnpct_no_value_or_quark_no_value:nTF {#3}
+ { #1 (#2) {#4} }
+ { #1 (#2) [#3] {#4} }
+ }
+ }
+
+% check for punctuation:
+\cs_new_protected:Npn \fnpct_check_punctuation:TF #1#2
+ { \__fnpct_check_punctuation:nTF {0} {#1} {#2} }
+
+\cs_new_protected:Npn \__fnpct_check_punctuation:nTF #1#2#3
+ {
+ \__fnpct_get_head:NN
+ \l__fnpct_current_punct_mark_tl
+ \l__fnpct_punctuation_marks_tl
+ \exp_args:NV \peek_meaning_remove:NTF \l__fnpct_current_punct_mark_tl
+ {#2}
+ {
+ \__fnpct_check_punctuation_aux:nnn {#1}
+ { \__fnpct_check_punctuation:VTF \l__fnpct_tmpa_int {#2} {#3} }
+ {#3}
+ }
+ }
+
+\cs_new_protected:Npn \__fnpct_check_punctuation_aux:nnn #1#2#3
+ {
+ \int_set:Nn \l__fnpct_tmpa_int {#1}
+ \int_incr:N \l__fnpct_tmpa_int
+ \tl_set:Nx \l__fnpct_tmpb_tl { \int_to_arabic:n { \l__fnpct_tmpa_int } }
+ \tl_remove_all:NV \l__fnpct_punctuation_marks_tl
+ \l__fnpct_current_punct_mark_tl
+ \tl_put_right:NV \l__fnpct_punctuation_marks_tl
+ \l__fnpct_current_punct_mark_tl
+ \int_compare:nTF { \l__fnpct_tmpa_int < \tl_count:V \l__fnpct_punctuation_marks_tl }
+ {#2}
+ {#3}
+ }
+\cs_generate_variant:Nn \__fnpct_check_punctuation:nTF {V}
+
+\cs_new_protected:Npn \__fnpct_get_head:NN #1#2
+ { \tl_set:Nx #1 { \tl_head:V #2 } }
+
+% check for multiple notes:
+\cs_new_protected:Npn \fnpct_check_multiple:TF #1#2
+ {
+ \tl_set:Nn \l__fnpct_multiple_true_tl {#1}
+ \tl_set:Nn \l__fnpct_multiple_false_tl {#2}
+ \bool_gset_false:N \g__fnpct_after_punctuation_bool
+ \peek_after:Nw \__fnpct_check_multiple:
+ }
+
+\cs_new_protected:Npn \__fnpct_check_multiple:
+ {
+ \prop_map_inline:Nn \g__fnpct_adapted_notes_prop
+ {
+ \tl_set_rescan:Nnn \l__fnpct_tmpb_tl {} {##1}
+ \exp_args:NV
+ \token_if_eq_meaning:NNTF \l__fnpct_tmpb_tl \l_peek_token
+ {
+ \bool_set_true:N \l__fnpct_multiple_footnotes_bool
+ \prop_map_break:
+ }
+ { \bool_set_false:N \l__fnpct_multiple_footnotes_bool }
+ }
+ \bool_if:NTF \l__fnpct_multiple_footnotes_bool
+ { \tl_use:N \l__fnpct_multiple_true_tl }
+ { \tl_use:N \l__fnpct_multiple_false_tl }
+ }
+
+% check which mode is active:
+\prg_new_conditional:Npnn \fnpct_punct_if_after: {T,TF}
+ {
+ \bool_if:nTF
+ {
+ \bool_xor_p:nn
+ { \l__fnpct_punct_after_bool }
+ { \l__fnpct_reverse_switch_bool }
+ }
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+
+% this is where the magic happens:
+\cs_new_protected:Npn \fnpct_handle_punctuation:n #1
+ {
+ \fnpct_check_punctuation:TF
+ {
+ % this is need as some commands like biblatex's \autocite may in turn
+ % call \footnote. It then must not insert the pre-footnote-space:
+ \bool_gset_true:N \g__fnpct_after_punctuation_bool
+ % A: after=1 && reverse=0
+ % after=0 && reverse=1
+ % B: after=1 && reverse=1
+ % after=0 && reverse=0
+ \fnpct_punct_if_after:TF
+ {
+ \fnpct_no_break:
+ \skip_horizontal:N \l__fnpct_before_footnote_dim
+ }
+ {
+ \tl_use:N \l__fnpct_current_punct_mark_tl
+ \prop_get:NVNT \l__fnpct_punctuation_marks_after_prop
+ \l__fnpct_current_punct_mark_tl
+ \l__fnpct_tmpa_tl
+ {
+ \dim_set:Nn \l__fnpct_tmpa_dim { \l__fnpct_tmpa_tl }
+ \fnpct_no_break:
+ \skip_horizontal:N \l__fnpct_tmpa_dim
+ \fnpct_no_break:
+ \skip_horizontal:N \c_zero_skip
+ \fnpct_no_break:
+ }
+ }
+ #1
+ \bool_gset_false:N \g__fnpct_after_punctuation_bool
+ \fnpct_punct_if_after:T
+ {
+ \fnpct_no_break:
+ \prop_get:NVNT \l__fnpct_punctuation_marks_before_prop
+ \l__fnpct_current_punct_mark_tl
+ \l__fnpct_tmpa_tl
+ {
+ \dim_set:Nn \l__fnpct_tmpa_dim { \l__fnpct_tmpa_tl }
+ \fnpct_no_break:
+ \skip_horizontal:N \l__fnpct_tmpa_dim
+ \fnpct_no_break:
+ \skip_horizontal:N \c_zero_skip
+ \fnpct_no_break:
+ }
+ \tl_use:N \l__fnpct_current_punct_mark_tl
+ }
+ \bool_set_false:N \l__fnpct_reverse_switch_bool
+ }
+ {
+ % what about multiple footnotes? (their usage is discouraged with
+ % this package, but anyway...)
+ % this check should probably/maybe be removed (?!)
+ \fnpct_check_multiple:TF
+ {
+ \bool_set_true:N \l__fnpct_multiple_footnotes_bool
+ \fnpct_no_break:
+ \bool_if:NF \g__fnpct_after_punctuation_bool
+ { \skip_horizontal:N \l__fnpct_before_footnote_dim }
+ #1
+ \fnpct_no_break:
+ \textsuperscript { \l__fnpct_multiple_footnote_separator_tl }
+ }
+ {
+ % else insert space and then note
+ \bool_if:NTF \l__fnpct_multiple_footnotes_bool
+ { \bool_set_false:N \l__fnpct_multiple_footnotes_bool }
+ {
+ \fnpct_no_break:
+ \bool_if:NF \g__fnpct_after_punctuation_bool
+ { \skip_horizontal:N \l__fnpct_before_footnote_dim }
+ }
+ #1
+ \bool_set_false:N \l__fnpct_reverse_switch_bool
+ }
+ }
+ }
+
+
+% #1: original command
+% #2: optional argument of original command
+% #3: second optional argument of original command
+% #4: mandatory argument of original command
+% #5: boolean flag for starred version
+\cs_new_protected:Npn \fnpct_handle_note:Nnnnn #1#2#3#4#5
+ {
+ % if a punctuation mark follows remove it, insert dot, skip back
+ % and then insert footnote
+ \bool_if:NF \l__fnpct_dont_mess_around_bool
+ {
+ \IfBooleanT {#5}
+ { \bool_set_true:N \l__fnpct_reverse_switch_bool }
+ }
+ \fnpct_handle_punctuation:n
+ {
+ \fnpct_write_note:Nnnn #1 {#2} {#3} {#4}
+ \fnpct_write_inner:N #1
+ }
+ }
+\cs_generate_variant:Nn \fnpct_handle_note:Nnnnn {c}
+
+\cs_new_protected:Npn \fnpct_handle_snote:Nnnnn #1#2#3#4#5
+ {
+ \bool_if:NF \l__fnpct_dont_mess_around_bool
+ {
+ \IfBooleanT {#5}
+ { \bool_set_true:N \l__fnpct_reverse_switch_bool }
+ }
+ \fnpct_handle_punctuation:n
+ {
+ \fnpct_write_snote:Nnnn #1 {#2} {#3} {#4}
+ \fnpct_write_inner:N #1
+ }
+ }
+\cs_generate_variant:Nn \fnpct_handle_snote:Nnnnn {c}
+
+% --------------------------------------------------------------------------
+% MULTIPLE FOOTNOTES
+% #1: original note command
+% #2: star
+% #3: multiple notes separated by \l__fnpct_multiple_footnotes_delimiter_tl
+\cs_new_protected:Npn \fnpct_mult_note:Nnn #1#2#3
+ {
+ \bool_if:NF \l__fnpct_dont_mess_around_bool
+ {
+ \IfBooleanT {#2}
+ { \bool_set_true:N \l__fnpct_reverse_switch_bool }
+ }
+ % split input:
+ \seq_set_split:NVn \l__fnpct_multiple_footnotes_seq
+ \l__fnpct_multiple_footnotes_delimiter_tl
+ {#3}
+ \fnpct_handle_punctuation:n
+ { \fnpct_write_notes:NN #1 \l__fnpct_multiple_footnotes_seq }
+
+ }
+\cs_generate_variant:Nn \fnpct_mult_note:Nnn {c}
+
+% #1: original note command
+% #2: sequence variable holding the note arguments
+\cs_new_protected:Npn \fnpct_write_notes:NN #1#2
+ {
+ \int_zero:N \l__fnpct_multiple_notes_int
+ \seq_map_inline:Nn #2
+ {
+ \__fnpct_read_note_with_option:w ##1 \q_stop {#1}
+ \int_incr:N \l__fnpct_multiple_notes_int
+ }
+ }
+
+% this shouldn't be a document command but is a really easy way to make
+% biblatex's \footcite et.al. compatible with the `multiple' option
+% #1: optional star => only invoke \footnotetext
+% #2: optional argument to original note command
+% #3: second optional argument to original note command
+% #4: mandatory argument to original note command
+% #5: original note command
+\NewDocumentCommand \__fnpct_read_note_with_option:w { soo+u{\q_stop}m }
+ {
+ \bool_if:nT
+ {
+ \int_compare_p:n { \l__fnpct_multiple_notes_int > 0 }
+ &&
+ \int_compare_p:n
+ {
+ \l__fnpct_multiple_notes_int
+ <
+ \seq_count:N \l__fnpct_multiple_footnotes_seq
+ }
+ }
+ {
+ \IfBooleanTF {#1}
+ { \tex_unskip:D }
+ { \textsuperscript { \l__fnpct_multiple_footnote_separator_tl } }
+ }
+ \IfBooleanTF {#1}
+ {
+ % TODO: maybe provide option to change \footnotetext? Or do it
+ % automatically?
+ \bool_gset_true:N \g__fnpct_only_text_bool
+ \IfNoValueTF {#2}
+ { \footnotetext {#4} }
+ { \footnotetext [#2] {#4} }
+ }
+ { \fnpct_write_note:Nnnn #5 {#2} {#3} {#4} }
+ \fnpct_write_inner:N #5
+ }
+
+% --------------------------------------------------------------------------
+% NESTED FOOTNOTES:
+% 1 layer of nesting...
+% this shouldn't be a document command! But for the time being I'll stick to
+% this easy solution...
+\NewDocumentCommand \fnpct_inner_footnote:w {o+m}
+ {
+ \IfNoValueTF {#1}
+ {
+ % TODO: maybe detect what type of note we're in and use the appropriate mark?
+ % Or provide a user interface to choose the appropriate mark?
+ \fnpct_orig_footnotemark:w
+ \prop_gput:Nxn \g__fnpct_inner_footnote_prop { \thefootnote } {#2}
+ \bool_if:NT \l__fnpct_hyperref_bool
+ {
+ \prop_gput:Nxo \g__fnpct_inner_footnote_hyperref_prop
+ { \thefootnote } { \Hy@footnote@currentHref }
+ }
+ }
+ {
+ \fnpct_orig_footnotemark:w [#1]
+ \prop_gput:Nnn \g__fnpct_inner_footnote_prop {#1} {#2}
+ \bool_if:NT \l__fnpct_hyperref_bool
+ {
+ \prop_gput:Nno \g__fnpct_inner_footnote_hyperref_prop
+ {#1} { \Hy@footnote@currentHref }
+ }
+ }
+ }
+
+\cs_new_protected:Npn \fnpct_write_inner:N #1
+ {
+ \token_if_eq_meaning:NNF #1 \fnpct_inner_footnote:w
+ {
+ \prop_map_inline:Nn \g__fnpct_inner_footnote_prop
+ {
+ \footnotetext [##1]
+ {
+ \bool_if:NT \l__fnpct_hyperref_bool
+ {
+ \prop_get:NnN \g__fnpct_inner_footnote_hyperref_prop
+ {##1} \l__fnpct_tmpa_tl
+ \Hy@raisedlink
+ { \exp_args:NV \hyper@@anchor \l__fnpct_tmpa_tl }
+ }
+ ##2
+ }
+ }
+ \prop_gclear:N \g__fnpct_inner_footnote_prop
+ }
+ }
+
+\NewDocumentCommand \writeinnernotes {}
+ { \fnpct_write_inner:N X }
+
+% --------------------------------------------------------------------------
+% MANUAL KERNING:
+\cs_new_protected:Npn \fnpct_kfp:
+ {
+ \fnpct_no_break:
+ \prop_get:NnNT \l__fnpct_punctuation_marks_before_prop {.}
+ \l__fnpct_tmpa_tl
+ { \dim_set:Nn \l__fnpct_tmpa_dim { \l__fnpct_tmpa_tl } }
+ \prop_get:NnNT \l__fnpct_punctuation_marks_after_prop {.}
+ \l__fnpct_tmpb_tl
+ { \dim_set:Nn \l__fnpct_tmpb_dim { \l__fnpct_tmpb_tl } }
+ \bool_if:NTF \l__fnpct_punct_after_bool
+ { \skip_horizontal:N \l__fnpct_tmpa_dim }
+ { \skip_horizontal:N \l__fnpct_tmpb_dim }
+ }
+
+\cs_new_protected:Npn \fnpct_kfc:
+ {
+ \fnpct_no_break:
+ \prop_get:NnNT \l__fnpct_punctuation_marks_before_prop {,}
+ \l__fnpct_tmpa_tl
+ { \dim_set:Nn \l__fnpct_tmpa_dim { \l__fnpct_tmpa_tl } }
+ \prop_get:NnNT \l__fnpct_punctuation_marks_after_prop {,}
+ \l__fnpct_tmpb_tl
+ { \dim_set:Nn \l__fnpct_tmpb_dim { \l__fnpct_tmpb_tl } }
+ \bool_if:NTF \l__fnpct_punct_after_bool
+ { \skip_horizontal:N \l__fnpct_tmpa_dim }
+ { \skip_horizontal:N \l__fnpct_tmpb_dim }
+ }
+
+\NewDocumentCommand \kfp {} { \fnpct_kfp: }
+\NewDocumentCommand \kfc {} { \fnpct_kfc: }
+
+% --------------------------------------------------------------------------
+% NORMAL MARKS:
+\AtBeginDocument
+ {
+ \bool_if:NT \l__fnpct_normal_marks_bool
+ {
+ \cs_if_exist:NF \KOMAoption
+ { \RequirePackage { scrextend } }
+ \deffootnote
+ [ \l__fnpct_normal_mark_width_dim ]
+ { \l__fnpct_normal_indent_dim }
+ { \l__fnpct_normal_parindent_dim }
+ { \thefootnotemark . \enskip }
+ }
+ }
+
+% --------------------------------------------------------------------------
+% LET'S MAKE IT EASIER TO ADAPT EXISTING FUNCTIONS:
+% COPY, RENEW AND CREATE MULT
+
+\prg_new_conditional:Npnn \fnpct_if_adapted:N #1 {p,T,F,TF}
+ {
+ \prop_if_in:NnTF \g__fnpct_adapted_notes_prop {#1}
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+
+\cs_new_protected:Npn \fnpct_add_to_adapted:NN #1#2
+ {
+ % \tl_show:n {1:~#1} \tl_show:n {2:~#2}
+ \prop_gput:Nnn \g__fnpct_adapted_notes_prop {#1} {#2}
+ }
+
+% standard \footnote[<mark>]{<text>} like commands
+% #1: old new name
+% #2: internal name of old definition
+% #3: mult-variant
+%
+% \footnote[<mark>]{<text>} like:
+\cs_new_protected:Npn \fnpct_renew_and_mult:NNN #1#2#3
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \cs_new_eq:NN #2#1
+ \fnpct_create_mult_variant:NN #3#2
+ \bool_if:NTF \l__fnpct_multiple_default_bool
+ { \cs_set_eq:NN #1#3 }
+ {
+ \RenewDocumentCommand #1 {so+m}
+ { \fnpct_handle_note:Nnnnn #2 {##2} { \q_no_value } {##3} {##1} }
+ }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_renew_and_mult:NNN {ccc,Nc}
+
+% \footnote{<text>} like:
+\cs_new_protected:Npn \fnpct_renew_and_mult_no_opt:NNN #1#2#3
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \cs_new_eq:NN #2#1
+ \fnpct_create_mult_variant:NN #3#2
+ \bool_if:NTF \l__fnpct_multiple_default_bool
+ { \cs_set_eq:NN #1#3 }
+ {
+ \RenewDocumentCommand #1 {s+m}
+ {
+ \fnpct_handle_note:Nnnnn #2 { \q_no_value } { \q_no_value }
+ {##2} {##1}
+ }
+ }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_renew_and_mult_no_opt:NNN {Nc,ccc}
+
+% renew but don't create mult-variant:
+% \footnote[<mark>]{<text>} like:
+\cs_new_protected:Npn \fnpct_renew:NN #1#2
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \cs_new_eq:NN #2#1
+ \RenewDocumentCommand #1 {so+m}
+ { \fnpct_handle_note:Nnnnn #2 {##2} { \q_no_value } {##3} {##1} }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_renew:NN {cc,Nc}
+
+% new
+\cs_new_protected:Npn \fnpct_new:NN #1#2
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \NewDocumentCommand #1 {so+m}
+ { \fnpct_handle_note:Nnnnn #2 {##2} { \q_no_value } {##3} {##1} }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_new:NN {cc,Nc}
+
+% \note[<mark>][<oarg>]{<text>} like:
+\cs_new_protected:Npn \fnpct_renew_and_mult_opt:NNN #1#2#3
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \cs_new_eq:NN #2#1
+ \fnpct_create_mult_variant:NN #3#2
+ \bool_if:NTF \l__fnpct_multiple_default_bool
+ { \cs_set_eq:NN #1#3 }
+ {
+ \RenewDocumentCommand #1 {soo+m}
+ { \fnpct_handle_note:Nnnnn #2 {##2} {##3} {##4} {##1} }
+ }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_renew_and_mult_opt:NNN {ccc,Nc}
+
+% \note(<oarg>)[<mark>]{<text>} like:
+\cs_new_protected:Npn \fnpct_renew_and_mult_snotez:NNN #1#2#3
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \cs_new_eq:NN #2#1
+ \fnpct_create_mult_variant:NN #3#2
+ \bool_if:NTF \l__fnpct_multiple_default_bool
+ { \cs_set_eq:NN #1#3 }
+ {
+ \RenewDocumentCommand #1 {sd()o+m}
+ { \fnpct_handle_snote:Nnnnn #2 {##2} {##3} {##4} {##1} }
+ }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_renew_and_mult_snotez:NNN {ccc,Nc}
+
+\cs_new_protected:Npn \fnpct_renew_opt:NN #1#2
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \cs_new_eq:NN #2#1
+ \RenewDocumentCommand #1 {soo+m}
+ { \fnpct_handle_note:Nnnnn #2 {##2} {##3} {##4} {##1} }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_renew_opt:NN {cc,Nc}
+
+% \note{<arg>} like
+\cs_new_protected:Npn \fnpct_renew_no_opt:NN #1#2
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \cs_new_eq:NN #2#1
+ \RenewDocumentCommand #1 {s+m}
+ { \fnpct_handle_note:Nnnnn #2 { \q_no_value } { \q_no_value } {##2} {##1} }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_renew_no_opt:NN {cc,Nc}
+
+% \note like
+\cs_new_protected:Npn \fnpct_renew_no_arg:NN #1#2
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \cs_new_eq:NN #2#1
+ \RenewDocumentCommand #1 {s}
+ { \fnpct_handle_note:Nnnnn #2 { \q_no_value } { \q_no_value } { } {##1} }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_renew_no_arg:NN {cc}
+
+% create mult-variant
+\cs_new_protected:Npn \fnpct_create_mult_variant:NN #1#2
+ {
+ \NewDocumentCommand #1 {s+m}
+ { \fnpct_mult_note:Nnn #2 {##1} {##2} }
+ }
+
+% \footnotemark[<mark>] like commands:
+\cs_new_protected:Npn \fnpct_renew_mark:NN #1#2
+ {
+ \fnpct_if_adapted:NF #1
+ {
+ \fnpct_add_to_adapted:NN #1#2
+ \cs_new_eq:NN #2#1
+ \RenewDocumentCommand #1 {so}
+ { \fnpct_handle_note:Nnnnn #2 {##2} { \q_no_value } { } {##1} }
+ }
+ }
+\cs_generate_variant:Nn \fnpct_renew_mark:NN {cc,Nc}
+
+% user commands:
+\NewDocumentCommand \AdaptNote {mm}
+ {
+ \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w }
+ { \fnpct_message:nx {already-adapted} {#1} }
+ { \fnpct_renew_and_mult:NcN #1 { fnpct_orig_ \cs_to_str:N #1 :w } #2 }
+ }
+
+\NewDocumentCommand \AdaptNoteNoMult {m}
+ {
+ \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w }
+ { \fnpct_message:nx {already-adapted} {#1} }
+ { \fnpct_renew:Nc #1 { fnpct_orig_ \cs_to_str:N #1 :w } }
+ }
+
+\NewDocumentCommand \AdaptNoteOpt {mm}
+ {
+ \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w }
+ { \fnpct_message:nx {already-adapted} {#1} }
+ { \fnpct_renew_and_mult_opt:NcN #1 { fnpct_orig_ \cs_to_str:N #1 :w } #2 }
+ }
+
+\NewDocumentCommand \AdaptSnotez {mm}
+ {
+ \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w }
+ { \fnpct_message:nx {already-adapted} {#1} }
+ { \fnpct_renew_and_mult_snotez:NcN #1 { fnpct_orig_ \cs_to_str:N #1 :w } #2 }
+ }
+
+\NewDocumentCommand \AdaptNoteOptNoMult {m}
+ {
+ \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w }
+ { \fnpct_message:nx {already-adapted} {#1} }
+ { \fnpct_renew_opt:Nc #1 { fnpct_orig_ \cs_to_str:N #1 :w } }
+ }
+
+\NewDocumentCommand \AdaptNoteNoOpt {mm}
+ {
+ \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w }
+ { \fnpct_message:nx {already-adapted} {#1} }
+ { \fnpct_renew_and_mult_no_opt:NcN #1 { fnpct_orig_ \cs_to_str:N #1 :w } #2 }
+ }
+
+\NewDocumentCommand \AdaptNoteNoOptNoMult {m}
+ {
+ \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1:w }
+ { \fnpct_message:nx {already-adapted} {#1} }
+ { \fnpct_renew_no_opt:Nc #1 { fnpct_orig_ \cs_to_str:N #1:w } }
+ }
+
+\NewDocumentCommand \AdaptNoteMark {m}
+ {
+ \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w }
+ { \fnpct_message:nx {already-adapted} {#1} }
+ { \fnpct_renew_mark:Nc #1 { fnpct_orig_ \cs_to_str:N #1 :w } }
+ }
+
+% --------------------------------------------------------------------------
+% DO THE REDEFINING:
+%
+% `bigfoot' loads `manyfoot' which saves its footnote classes in a 2e list:
+\str_const:Nn \c__fnpct_footins_str {\footins}
+\str_remove_once:Nn \c__fnpct_footins_str {~}
+\cs_new_protected:Npn \__fnpct_grab_second:Nnw #1#2#3 \q_stop
+ {
+ \str_set:Nn \l__fnpct_tmpa_str {#3}
+ \str_remove_once:Nn \l__fnpct_tmpa_str {~}
+ \str_remove_once:NV \l__fnpct_tmpa_str \c__fnpct_footins_str
+ \seq_put_right:Nx #1 { \l__fnpct_tmpa_str }
+ }
+
+\cs_new_protected:Npn \__fnpct_get_fnclasses:NN #1#2
+ {
+ \seq_set_split:NnV \l__fnpct_tmpa_seq {\@elt } #1
+ \seq_pop_left:NN \l__fnpct_tmpa_seq \l__fnpct_tmpa_tl
+ \seq_map_inline:Nn \l__fnpct_tmpa_seq
+ { \__fnpct_grab_second:Nnw #2 ##1 \q_stop }
+ \seq_remove_duplicates:N #2
+ }
+
+% before we start make the testing more comfortable:
+\cs_new_protected:Npn \fnpct_treatment:nn #1#2
+ { \fnpct_if_package_loaded:nT {#1} {#2} }
+
+\cs_new_protected:Npn \fnpct_special_treatment:nn #1#2
+ { \fnpct_if_package_loaded:nTF {#1} {#2} { \AfterPackage* {#1} {#2} } }
+
+% and now get going:
+\AtBeginDocument
+ {
+ \fnpct_if_package_loaded:nTF {hyperref}
+ { \bool_set_true:N \l__fnpct_hyperref_bool }
+ {
+ \cs_if_exist:NF \AfterBeginDocument
+ { \cs_new:Npn \AfterBeginDocument #1 {#1} }
+ }
+ \AfterBeginDocument
+ {
+ \AdaptNote \footnote \multfootnote
+ \AdaptNoteMark \footnotemark
+ \cs_if_exist:NT \footref
+ { \AdaptNoteNoOptNoMult \footref }
+ \fnpct_new:NN \innernote \fnpct_inner_footnote:w
+ %% the `endnotes' package:
+ \fnpct_treatment:nn {endnotes}
+ {
+ \AdaptNote \endnote \multendnote
+ \AdaptNoteMark \endnotemark
+ }
+ %% the `enotez' package:
+ \fnpct_treatment:nn {enotez}
+ { \AdaptNote \endnote \multendnote }
+ %% the `snotez' package:
+ \fnpct_treatment:nn {snotez}
+ {
+ \legacy_if:nTF {snotez@dblarg}
+ { \AdaptNoteOpt \sidenote \multsidenote }
+ { \AdaptSnotez \sidenote \multsidenote }
+ \AdaptNoteMark \sidenotemark
+ }
+ %% the `parnotes' package:
+ \fnpct_treatment:nn {parnotes}
+ { \AdaptNote \parnote \multparnote }
+ %% the `pagenote' package:
+ \fnpct_treatment:nn {pagenote}
+ {
+ \bool_if:NTF \l__fnpct_makepagenote_bool
+ { \AdaptNote \pagenote \multpagenote }
+ { \msg_warning:nn {fnpct} {pagenote} }
+ }
+ %% the `tablefootnote' package:
+ \fnpct_treatment:nn {tablefootnote}
+ { \AdaptNote \tablefootnote \multtablefootnote }
+ %% the `manyfoot' package (also loaded by `bigfoot'):
+ \fnpct_treatment:nn {manyfoot}
+ {
+ \__fnpct_get_fnclasses:NN \MFL@list \l__fnpct_footnote_class_seq
+ \seq_map_inline:Nn \l__fnpct_footnote_class_seq
+ {
+ \str_if_eq:nnTF {#1} {default}
+ {
+ \cs_if_exist:NT \footnotedefault
+ {
+ \fnpct_renew_and_mult:ccc {footnote#1}
+ {fnpct_orig_footnote#1:w}
+ {multfootnote#1}
+ \fnpct_renew_mark:cc {footnotemark#1}
+ {fnpct_orig_footnotemark#1:w}
+ }
+ }
+ {
+ \fnpct_renew_and_mult:ccc {footnote#1}
+ {fnpct_orig_footnote#1:w}
+ {multfootnote#1}
+ \fnpct_renew_mark:cc {footnotemark#1}
+ {fnpct_orig_footnotemark#1:w}
+ }
+ }
+ }
+ %% the `bigfoot' package:
+ \fnpct_special_treatment:nn {bigfoot}
+ {
+ \cs_if_exist:NT \footnotedefault
+ {
+ % re-set basics:
+ \cs_set_eq:NN \footnote \footnotedefault
+ \cs_set_eq:NN \footnotemark \footnotemarkdefault
+ \cs_set_eq:NN \multfootnote \multfootnotedefault
+ }
+ }
+ %% the `fixfoot' package:
+ \fnpct_treatment:nn {fixfoot}
+ {
+ \prop_map_inline:Nn \l__fnpct_footnote_fixfoot_prop
+ { \fnpct_renew_no_arg:cc {#2} {fnpct_orig_fix_#2:w} }
+ }
+ %% the `sepfootnotes' package:
+ \fnpct_treatment:nn {sepfootnotes}
+ {
+ \prop_map_inline:Nn \l__fnpct_sepfootnote_foot_classes_prop
+ {
+ \fnpct_renew_and_mult_no_opt:ccc {#2note}
+ {fnpct_orig_sep_#2:w} {#2multnote}
+ \fnpct_renew_and_mult_no_opt:ccc {#2quicknote}
+ {fnpct_orig_sep_#2_quick:w} {#2multquicknote}
+ \fnpct_renew_no_opt:cc {#2notemark} {fnpct_orig_sep_#2_mark:w}
+ }
+ \prop_map_inline:Nn \l__fnpct_sepfootnote_end_classes_prop
+ {
+ \fnpct_renew_and_mult_no_opt:ccc {#2note}
+ {fnpct_orig_sep_#2:w} {#2multnote}
+ \fnpct_renew_no_opt:cc {#2notemark} {fnpct_orig_sep_#2_mark:w}
+ }
+ }
+ }
+ }
+
+% `fixfoot' package
+\fnpct_special_treatment:nn {fixfoot}
+ {
+ % hook into \DeclareFixedFootnote so we can redefine all footnote
+ % classes defined be users
+ \cs_new_eq:NN \fnpct_new_fixnote:w \DeclareFixedFootnote
+ \RenewDocumentCommand \DeclareFixedFootnote {smm}
+ {
+ \prop_put:Nnx \l__fnpct_footnote_fixfoot_prop {#2}
+ { \cs_to_str:N #2 }
+ \IfBooleanTF {#1}
+ { \fnpct_new_fixnote:w * {#2} {#3} }
+ { \fnpct_new_fixnote:w {#2} {#3} }
+ }
+ }
+
+% `pagenote' package:
+\fnpct_special_treatment:nn {pagenote}
+ {
+ \tl_put_left:Nn \makepagenote
+ { \bool_set_true:N \l__fnpct_makepagenote_bool }
+ }
+
+% `sepfootnotes' package:
+\fnpct_special_treatment:nn {sepfootnotes}
+ {
+ \cs_new_eq:NN \fnpct_orig_new_footnotes:w \newfootnotes
+ \cs_new_eq:NN \fnpct_orig_new_endnotes:n \newendnotes
+ \cs_new_eq:NN \fnpct_orig_new_symbolnotes:w \newsymbolfootnotes
+ \RenewDocumentCommand \newfootnotes {sm}
+ {
+ \prop_put:Nnn \l__fnpct_sepfootnote_foot_classes_prop {#2} {#2}
+ \IfBooleanTF {#1}
+ { \fnpct_orig_new_footnotes:w * {#2} }
+ { \fnpct_orig_new_footnotes:w {#2} }
+ }
+ \RenewDocumentCommand \newendnotes {m}
+ {
+ \prop_put:Nnn \l__fnpct_sepfootnote_end_classes_prop {#1} {#1}
+ \fnpct_orig_new_endnotes:n {#1}
+ }
+ \RenewDocumentCommand \newsymbolfootnotes {om}
+ {
+ \prop_put:Nnn \l__fnpct_sepfootnote_symbol_classes_prop {#2} {#2}
+ \IfNoValueTF {#1}
+ { \fnpct_orig_new_symbolnotes:w {#2} }
+ { \fnpct_orig_new_symbolnotes:w [#1] {#2} }
+ }
+ }
+
+% --------------------------------------------------------------------------
+\RequirePackage{translations}
+\AtBeginDocument{
+ \ifcurrentbaselanguage{French}
+ {
+ \AfterBeginDocument{
+ \cs_if_exist:NT \@footnotemarkORI
+ {
+ \let\@footnotemark\@footnotemarkORI
+ \setfnpct{
+ before-footnote-space =
+ \l__fnpct_french_before_footnote_space_dim
+ }
+ }
+ }
+ }{}
+}
+
+% --------------------------------------------------------------------------
+% SETUP COMMAND:
+\NewDocumentCommand \setfnpct {m}
+ { \keys_set:nn {fnpct} {#1} }
+
+\file_input_stop:
diff --git a/macros/latex/contrib/fnpct/fnpct-manual.cls b/macros/latex/contrib/fnpct/fnpct-manual.cls
index 95cfab1a77..05355a28b4 100644
--- a/macros/latex/contrib/fnpct/fnpct-manual.cls
+++ b/macros/latex/contrib/fnpct/fnpct-manual.cls
@@ -23,7 +23,7 @@
% The Current Maintainer of this work is Clemens Niederberger.
% --------------------------------------------------------------------------
\RequirePackage[multiple]{fnpct}
-\ProvidesClass{fnpct-manual}[2022/02/03]
+\ProvidesClass{fnpct-manual}[2022/02/27]
\LoadClass[load-preamble,add-index]{cnltx-doc}
\RequirePackage{imakeidx}
@@ -75,7 +75,8 @@
thempfootnote
} ,
makeindex-setup = {options={-s cnltx.ist},columns=3,columnsep=1em} ,
- index-setup = {othercode=\footnotesize,level=\addsec}
+ index-setup = {othercode=\footnotesize,level=\addsec},
+ quote-author-format = \upshape
}
\RequirePackage{array,booktabs}
@@ -330,5 +331,6 @@
\fnpct@add@version{2022-01-04}{1.0a}
\fnpct@add@version{2022-01-29}{1.0b}
\fnpct@add@version{2022-02-03}{1.1}
+\fnpct@add@version{2022-02-27}{1.1a}
\endinput
diff --git a/macros/latex/contrib/fnpct/fnpct-manual.pdf b/macros/latex/contrib/fnpct/fnpct-manual.pdf
index bd6098e43f..37670e195e 100644
--- a/macros/latex/contrib/fnpct/fnpct-manual.pdf
+++ b/macros/latex/contrib/fnpct/fnpct-manual.pdf
Binary files differ
diff --git a/macros/latex/contrib/fnpct/fnpct-manual.tex b/macros/latex/contrib/fnpct/fnpct-manual.tex
index 99968777c7..674b0e293f 100644
--- a/macros/latex/contrib/fnpct/fnpct-manual.tex
+++ b/macros/latex/contrib/fnpct/fnpct-manual.tex
@@ -37,10 +37,14 @@
before-footnote-space = 0.05em
}
+\usepackage[biblatex]{embrac}
+\ChangeEmph{[}[,.02em]{]}[.055em,-.08em]
+\ChangeEmph{(}[-.01em,.04em]{)}[.04em,-.05em]
+
\addbibresource{cnltx.bib}
\addbibresource{\jobname.bib}
\addbibresource{biblatex-examples.bib}
-\begin{filecontents*}{\jobname.bib}
+\begin{filecontents*}[overwrite]{\jobname.bib}
@book{bringhurst04,
title = {The Elements of Typographic Style},
author = {Robert Bringhurst},
@@ -49,6 +53,14 @@
isbn = {978-0-88179-205-5},
publisher = {Hartley \&\ Marks, Canada}
}
+@online{tex.sx:56094,
+ title = {Is there a way to automatically change kerning between
+ commas/full stops and footnote marks?} ,
+ author = {Christian} ,
+ date = {2012-05-16} ,
+ url = {https://tex.stackexchange.com/q/56094/5049} ,
+ visited = {2022-03-27}
+}
\end{filecontents*}
\AdaptNote\footcite{m}{#NOTE{#1}}
@@ -76,7 +88,9 @@
try to add it again to \fnpct. \par
Please read this manual carefully so you detect all changes. \par
\emph{I am sorry for the inconvenience but I am convinced that the new
- syntax is more powerful and more flexible in the long run.}
+ syntax is more powerful and more flexible in the long run.} \par
+ You can fall back to the last version~0 by saying
+ \cs*{usepackage}\Marg{fnpct}\Oarg{=v0}.
\end{bewareofthedog}
\section{License}
@@ -89,13 +103,13 @@ superscripts. This is not appropriate for all superscripts. Symbols must be
handled differently than numbers. And of course the amount depends on the
chosen font. \bringhurst\ says in \citetitle{bringhurst04}:
-\begin{cnltxquote}[\emph{\citetitle{bringhurst04}} \cite{bringhurst04}]
+\begin{cnltxquote}[\citetitle{bringhurst04} \emph{\cite{bringhurst04}}]
Superscripts frequently come at the ends of phrases or sentences. If they
are high above the line, they can be kerned over a comma or period, but this
may endanger readability, especially if the text is set in a modest size.
\end{cnltxquote}
-\fnpct\ can not make these decisions for you. It sets some initial values for
+\fnpct\ cannot make these decisions for you. It sets some initial values for
the comma and the full stop which looked good to me with the tested fonts.
Additionally it kerns the superscripts away from words when it follows
directly. The amounts of the kerning can be changed using options.
@@ -103,7 +117,10 @@ directly. The amounts of the kerning can be changed using options.
\fnpct\ also switches the order of the superscript and the following comma or
full stop. Additional punctuation marks can be added to the switching
behavior and the amount of kerning can be set for each punctuation mark
-individually. This behavior can also be turned off.
+individually. This behavior can be turned off.
+
+The package has been inspired by a question on the Q\&A site
+\url{tex.stackexchange.com}~\cite{tex.sx:56094}.
\subsection{New Behavior of \cs*{footnote}}
\subsubsection{Interaction With Punctuation}
@@ -485,7 +502,6 @@ way\footnote{Using \cs*{@mpfn} as counter name is essential to make ranges
\AdaptNote\footnote{o+m}[\@mpfn]{\IfNoValueTF{#1}{#NOTE{#2}}{#NOTE[#1]{#2}}}
\AdaptNote\footnotemark{o}{\IfNoValueTF{#1}{#NOTE}{#NOTE[#1]}}
\MultVariant\footnote
- % KOMA-Script's or memoir's \footref:
\AdaptNote\footref{m}{#NOTE{#1}}
\end{sourcecode}
You essentially have to rebuild the syntax of the original command and place
diff --git a/macros/latex/contrib/fnpct/fnpct.sty b/macros/latex/contrib/fnpct/fnpct.sty
index 175aa7c43f..d939f0a879 100644
--- a/macros/latex/contrib/fnpct/fnpct.sty
+++ b/macros/latex/contrib/fnpct/fnpct.sty
@@ -25,12 +25,14 @@
\RequirePackage {l3keys2e}
\ExplSyntaxOn
+\DeclareRelease {v0} {2019-10-05} {fnpct-2019-10-05.sty}
+
\DeclareHookRule {begindocument} {fnpct} {after} {hyperref}
-\tl_const:Nn \c_fnpct_date_tl {2022/02/03}
+\tl_const:Nn \c_fnpct_date_tl {2022/02/27}
\tl_const:Nn \c_fnpct_version_major_number_tl {1}
\tl_const:Nn \c_fnpct_version_minor_number_tl {1}
-\tl_const:Nn \c_fnpct_version_subrelease_tl {}
+\tl_const:Nn \c_fnpct_version_subrelease_tl {a}
\tl_const:Nx \c_fnpct_version_number_tl
{
\c_fnpct_version_major_number_tl .
@@ -49,6 +51,8 @@
{\c_fnpct_version_tl}
{\c_fnpct_info_tl}
+\DeclareCurrentRelease {v1} {\c_fnpct_date_tl}
+
% --------------------------------------------------------------------------
% scratch variables and variants of kernel functions:
\tl_new:N \l__fnpct_tmpa_tl
@@ -481,21 +485,9 @@
\cs_new_protected:Npn \fnpct_save_note_command:N #1
{
\cs_undefine:c {fnpct_original_ \__fnpct_remove_backslash:N #1 :w}
- \cs_if_exist:cTF { \__fnpct_remove_backslash:N #1 ~ code }
- {% xparse command
- \cs_new_eq:cc
- { fnpct_original_ \__fnpct_remove_backslash:N #1 :w }
- { \__fnpct_remove_backslash:N #1 ~ code }
- }
- {
- \cs_if_exist:cTF { \__fnpct_remove_backslash:N #1 ~ }
- {% robust command
- \cs_new_eq:cc
- { fnpct_original_ \__fnpct_remove_backslash:N #1 :w }
- { \__fnpct_remove_backslash:N #1 ~ }
- }
- { \cs_new_eq:cN { fnpct_original_ \__fnpct_remove_backslash:N #1 :w } #1 }
- }
+ \exp_args:Nc \DeclareCommandCopy
+ { fnpct_original_ \__fnpct_remove_backslash:N #1 :w }
+ #1
\seq_put_right:Nn \l__fnpct_adapted_commands_seq {#1}
\fnpct_set_counter_name:Nx #1 { \__fnpct_remove_backslash:N #1 }
}
@@ -696,11 +688,11 @@
{
\mode_if_horizontal:T
{
- \dim_compare:nNnTF \lastskip > {0pt}
- { \unskip \fnpct_unspace: }
+ \dim_compare:nNnTF {\lastskip} > {0pt}
+ { \tex_unskip:D \fnpct_unspace: }
{
- \int_compare:nNnT \lastpenalty > {0pt}
- { \unpenalty \fnpct_unspace: }
+ \int_compare:nNnT {\lastpenalty} > {0}
+ { \tex_unpenalty:D \fnpct_unspace: }
}
}
}
@@ -1092,8 +1084,8 @@
\fnpct_sepfootnote_new:n {#1}
}
}
-
-\hook_gput_code:nnn {begindocument} {fnpct}
+
+\hook_gput_code:nnn {begindocument/end} {fnpct}
{
\fnpct_if_package_loaded:nT {hyperref}
{
@@ -1130,9 +1122,7 @@
\fnpct_if_multiple:T { \MultVariant \footnote }
% TODO: should we adapt \thanks? Not with memoir, though...
% \AdaptNote \thanks {+m} { \cs_set_eq:NN \rlap \use:n #NOTE{#1} }
- %% KOMA-Script and memoir:
- \cs_if_exist:NT \footref
- { \AdaptNote \footref {m} { #NOTE{#1} } }
+ \AdaptNote \footref {m} { #NOTE{#1} }
%% snotez:
\fnpct_treatment:nn {snotez}
{
@@ -1304,8 +1294,9 @@
}
%% sepfootnotes
\fnpct_treatment:nn {sepfootnotes}
- {
+ {
\cs_set:Nn \__fnpct_sep_mark:n {\footnotemark}
+ \AdaptNote \sepfootnote {m} [\@mpfn] {#NOTE{#1}}
\seq_map_inline:Nn \l__fnpct_sepfootnotes_seq
{
\cs_if_eq:cNTF {#1notemark} \__fnpct_sep_mark:n
@@ -1429,3 +1420,9 @@ tablefootnote
see https://tex.stackexchange.com/questions/467561/
- new option `mult-variant-prefix'
- new option `unspace'
+2022/02/27 v1.1a - fix bug in \fnpct_unspace:
+ - fix bug in sepfootnotes adaption
+ - add package check file
+ - use \DeclareCommandCopy for saving the original note
+ commands
+ - use LaTeX's release mechanism
diff --git a/macros/latex/contrib/projlib/README.md b/macros/latex/contrib/projlib/README.md
index b6b8a3eae4..91c7d4dcfe 100644
--- a/macros/latex/contrib/projlib/README.md
+++ b/macros/latex/contrib/projlib/README.md
@@ -1,7 +1,29 @@
-<!-- Copyright (C) 2021 by Jinwen XU -->
+<!-- Copyright (C) 2021-2022 by Jinwen XU -->
# ProjLib Toolkit
+The bundle is dated 2022/02/27
+
+Old modules are still dated 2021/12/16 and will *not* be modified. When all the new modules are finished, old files shall be removed from this bundle.
+
+## **Important note**
+
+The whole project is being rewritten with `expl3`. New modules have the name with prefix "projlib-", and they are in many ways better and more powerful than their predecessors. Some basically finished new modules are included in the bundle but are not yet documented. Most importantly, they are not compatible with the old modules, so they cannot appear at the same time in the same document, which, unfortunately, means that (for now) they cannot be used in classes like `einfart` or `lebhart`, etc.
+
+The newly added modules include:
+- projlib-author
+- projlib-datetime
+- projlib-draft
+- projlib-font
+- projlib-language
+- projlib-logo
+- projlib-math
+- projlib-paper
+
+When all these modules are finished, the documentation will be updated accordingly.
+
+---
+
> **ProjLib** can be interpreted as "***Proj**ect **Lib**rary*" (in English), or as "***Proj**et **Lib**re*" (in French).
> Personally, I prefer the later one.
diff --git a/macros/latex/contrib/projlib/new-modules/projlib-author.sty b/macros/latex/contrib/projlib/new-modules/projlib-author.sty
new file mode 100644
index 0000000000..ef0906d249
--- /dev/null
+++ b/macros/latex/contrib/projlib/new-modules/projlib-author.sty
@@ -0,0 +1,312 @@
+\NeedsTeXFormat{LaTeX2e}[2020-10-01]
+\RequirePackage{l3keys2e}
+\ProvidesExplPackage
+ {projlib-author}
+ {2022/02/24} {}
+ {Enhanced author information block}
+
+\keys_define:nn { projlib-author }
+ {
+ , amsfashion .bool_set:N = \l__projlib_author_amsfashion_bool
+ , amsfashion .initial:n = { false }
+ , ams-fashion .bool_set:N = \l__projlib_author_amsfashion_bool
+ , unknown .code:n = {}
+ }
+\ProcessKeysOptions { projlib-author }
+
+\cs_if_exist:NT \subjclass { \endinput }
+
+\tl_new:N \g__projlib_author_font_author_tl
+\tl_new:N \g__projlib_author_font_address_tl
+\tl_new:N \g__projlib_author_font_curraddr_tl
+\tl_new:N \g__projlib_author_font_email_tl
+
+\tl_gset:Nn \g__projlib_author_font_author_tl { \normalsize \normalfont \scshape }
+\tl_gset:Nn \g__projlib_author_font_address_tl { \small \normalfont \itshape }
+\tl_gset:Nn \g__projlib_author_font_curraddr_tl { \small \normalfont \itshape }
+\tl_gset:Nn \g__projlib_author_font_email_tl { \small \normalfont \ttfamily }
+
+\dim_new:N \g__projlib_author_length_innersep_dim
+\dim_new:N \g__projlib_author_length_sep_dim
+\dim_new:N \g__projlib_author_length_aftersep_dim
+
+\dim_set:Nn \g__projlib_author_length_innersep_dim { 0.3 \baselineskip }
+\dim_set:Nn \g__projlib_author_length_sep_dim { 0.5 \baselineskip }
+\dim_set:Nn \g__projlib_author_length_aftersep_dim { 0.3 \baselineskip }
+
+
+\bool_new:N \g__projlib_author_affil_already_exist_bool
+
+\NewCommandCopy \projlib_author_backup_author:w \author
+\RenewDocumentCommand \author { m }
+ {
+ \hook_gput_code:nnn { cmd/@author/after } { projlib-author }
+ {
+ \phantom {A} \\[-\baselineskip]
+ \skip_vertical:n { \g__projlib_author_length_sep_dim }
+ { \g__projlib_author_font_author_tl #1 }
+ \bool_set_false:N \g__projlib_author_affil_already_exist_bool
+ \\ \phantom {}
+ }
+ }
+
+\ProvideDocumentCommand \address { m }
+ {
+ \hook_gput_code:nnn { cmd/@author/after } { projlib-author }
+ {
+ \phantom {A} \\[-\baselineskip]
+ \bool_if:NF \g__projlib_author_affil_already_exist_bool
+ {
+ \skip_vertical:n { \g__projlib_author_length_innersep_dim }
+ }
+ \bool_set_true:N \g__projlib_author_affil_already_exist_bool
+ { \g__projlib_author_font_address_tl #1 }
+ \\ \phantom {}
+ }
+ }
+
+\ProvideDocumentCommand \curraddr { m }
+ {
+ \hook_gput_code:nnn { cmd/@author/after } { projlib-author }
+ {
+ \phantom {A} \\[-\baselineskip]
+ \bool_if:NF \g__projlib_author_affil_already_exist_bool
+ {
+ \skip_vertical:n { \g__projlib_author_length_innersep_dim }
+ }
+ \bool_set_true:N \g__projlib_author_affil_already_exist_bool
+ { \g__projlib_author_font_curraddr_tl #1 }
+ \\ \phantom {}
+ }
+ }
+
+\ProvideDocumentCommand \email { m }
+ {
+ \hook_gput_code:nnn { cmd/@author/after } { projlib-author }
+ {
+ \phantom {A} \\[-\baselineskip]
+ \bool_if:NF \g__projlib_author_affil_already_exist_bool
+ {
+ \skip_vertical:n { \g__projlib_author_length_innersep_dim }
+ }
+ \bool_set_true:N \g__projlib_author_affil_already_exist_bool
+ { \g__projlib_author_font_email_tl #1 }
+ \\ \phantom {}
+ }
+ }
+
+\RequirePackage { regexpatch }
+
+\xpatchcmd { \@maketitle } { \begin{tabular}[t]{c} } {} {} { \FAIL }
+\xpatchcmd { \@maketitle } { \end{tabular} } {} {} { \FAIL }
+
+\tl_set:cn {@author} {}
+
+\hook_gput_code:nnn { cmd/@maketitle/before } { projlib-author }
+ {
+ \hook_gput_code:nnn { cmd/@author/before } { projlib-author }
+ {
+ \skip_vertical:n { - \g__projlib_author_length_sep_dim }
+ }
+ \hook_gput_code:nnn { cmd/@author/after } { projlib-author }
+ {
+ \skip_vertical:n { - \baselineskip }
+ \bool_if:NT \g__projlib_author_affil_already_exist_bool
+ {
+ \skip_vertical:n { \g__projlib_author_length_aftersep_dim }
+ }
+ }
+ }
+
+\bool_if:NT \l__projlib_author_amsfashion_bool
+ {
+ \RequirePackage{ projlib-language }
+
+ \tl_new:N \g_projlib_author_keywordsname_style_tl
+ \tl_gset:Nn \g_projlib_author_keywordsname_style_tl {}
+
+ \projlib_langauge_define_multilingual_text:Nn \keywordsname
+ {
+ EN = \textsc{Key~words~and~phrases} ,
+ FR = \textsc{Mots~et~phrases~clés} ,
+ DE = \textsc{Schlüsselwörter~und~Phrasen} ,
+ IT = \textsc{Parole~e~frasi~chiave} ,
+ PT = \textsc{Palavras~e~frases~chave} ,
+ BR = \textsc{Palavras~e~frases~chave} ,
+ ES = \textsc{Palabras~y~frases~clave} ,
+ CN = 关键词 ,
+ TC = 關鍵詞 ,
+ JP = キーワード ,
+ RU = Ключевые~слова~и~фразы ,
+ }
+
+ \tl_new:N \g__projlib_author_keywords_tl
+ \NewDocumentCommand \keywords { m }
+ {
+ \tl_gset:Nn \g__projlib_author_keywords_tl { #1 }
+ }
+
+ \tl_new:N \g__projlib_author_dedicatory_tl
+ \NewDocumentCommand \dedicatory { m }
+ {
+ \tl_gset:Nn \g__projlib_author_dedicatory_tl { #1 }
+ }
+
+ \hook_gput_code:nnn { begindocument/before } { projlib-author }
+ {
+ \NewCommandCopy \projlib_author_backup_footnotemark: \footnotemark
+ \tl_gset:Nn \footnotemark {}
+
+ \NewCommandCopy \projlib_author_backup_thanks:w \thanks
+ \cs_undefine:N \thanks
+
+ % measure the width of "*"
+ \hbox_set:Nn \l_tmpa_box { * }
+ \dim_const:Nn \c__projlib_author_length_width_of_asterisk_dim { \box_wd:N \l_tmpa_box }
+
+ \cs_new_nopar:Nn \projlib_author_pre_thanks:n
+ {
+ \projlib_author_backup_thanks:w
+ {
+ \skip_horizontal:n { - \c__projlib_author_length_width_of_asterisk_dim }
+ #1
+ }
+ }
+
+ \NewDocumentCommand \thanks { m }
+ {
+ \tl_if_blank:eF { #1 }
+ {
+ \hook_gput_code:nnn { cmd/@title/after } { projlib-author }
+ {
+ \projlib_author_pre_thanks:n { #1 }
+ }
+ }
+ }
+
+ \NewCommandCopy \projlib_author_backup_my_author:w \author
+ \RenewDocumentCommand \author { m }
+ {
+ \projlib_author_backup_my_author:w
+ {
+ \RenewCommandCopy \footnotemark \projlib_author_backup_footnotemark:
+ \RenewCommandCopy \thanks \projlib_author_backup_thanks:w
+ #1
+ \cs_undefine:N \footnotemark
+ }
+ }
+ }
+
+ \NewCommandCopy \projlib_author_backup_abstract: \abstract
+ \NewCommandCopy \projlib_author_backup_endabstract: \endabstract
+ \cs_undefine:N \abstract
+ \cs_undefine:N \endabstract
+
+ \bool_new:N \g__projlib_author_already_maketitle_bool
+ \bool_gset_false:N \g__projlib_author_already_maketitle_bool
+ \bool_new:N \g__projlib_author_abstract_before_maketitle_bool
+ \bool_gset_false:N \g__projlib_author_abstract_before_maketitle_bool
+
+ \RequirePackage{ scontents }
+ \newenvsc{abstract}[store-env=projlib-author-abstract,print-env=false]
+
+ \hook_gput_code:nnn { env/abstract/after } { projlib-author }
+ {
+ \bool_if:NTF \g__projlib_author_already_maketitle_bool
+ {
+ \projlib_author_backup_abstract:
+ \getstored{projlib-author-abstract}
+ \projlib_author_backup_endabstract:
+ }
+ {
+ \bool_gset_true:N \g__projlib_author_abstract_before_maketitle_bool
+ }
+ }
+
+ \hook_gput_code:nnn { begindocument/before } { projlib-author }
+ {
+ \makeatletter
+ \xpatchcmd { \@maketitle } { \@date }
+ {
+ \tl_if_blank:eTF { \@date } { \skip_vertical:n { -2 \baselineskip } } { \@date }
+ \tl_if_blank:eF { \g__projlib_author_dedicatory_tl }
+ {
+ \tl_if_blank:eT { \@date } { \skip_vertical:n { 2 \baselineskip } }
+ \par
+ \skip_vertical:n { -\parskip }
+ \skip_vertical:n { \baselineskip }
+ \textit{ \g__projlib_author_dedicatory_tl }
+ \tl_if_blank:eT { \@date }
+ {
+ \skip_vertical:n { -\parskip }
+ \skip_vertical:n { \dim_eval:n { -1.7 \baselineskip } }
+ }
+ }
+ } {} { \FAIL }
+ \makeatother
+
+ \hook_gput_code:nnn { cmd/maketitle/before } { projlib-author }
+ {
+ \tl_if_blank:eF { \g__projlib_author_keywords_tl }
+ {
+ \projlib_author_pre_thanks:n
+ {
+ \group_begin:
+ \g_projlib_author_keywordsname_style_tl
+ \keywordsname .
+ \group_end:
+ \c_space_tl
+ \g__projlib_author_keywords_tl
+ }
+ }
+ }
+
+ \hook_gput_code:nnn { cmd/maketitle/after } { projlib-author }
+ {
+ \bool_if:NT \g__projlib_author_abstract_before_maketitle_bool
+ {
+ \projlib_author_backup_abstract:
+ \getstored{projlib-author-abstract}
+ \projlib_author_backup_endabstract:
+ }
+ \cs_undefine:N \address
+ \cs_undefine:N \curraddr
+ \cs_undefine:N \email
+ \cs_undefine:N \dedicatory
+ \cs_undefine:N \keywords
+ \bool_gset_true:N \g__projlib_author_already_maketitle_bool
+ }
+ }
+
+ \tl_new:N \l_projlib_author_subjclassname_tl
+ \tl_new:N \l_projlib_author_subjclass_tl
+ \msg_new:nnn { projlib-author }
+ { unknown-msc-edition }
+ { Unknown~edition~(#1)~of~Mathematics~Subject~Classification;~using~'2020' }
+ \NewDocumentCommand \subjclass { O{2020} m }
+ {
+ \tl_set:Nn \l_projlib_author_subjclass_tl { #2 }
+ \tl_if_exist:cTF { subjclassname_#1 }
+ {
+ \tl_set:Nn \l_projlib_author_subjclassname_tl { \tl_use:c { subjclassname_#1 } }
+ }
+ {
+ \msg_warning:nnn { projlib-author } { unknown-msc-edition } { #1 }
+ \tl_set:Nn \l_projlib_author_subjclassname_tl { \tl_use:c { subjclassname_2020 } }
+ }
+ \projlib_author_pre_thanks:n
+ {
+ \textit{ \l_projlib_author_subjclassname_tl . }
+ \enspace
+ \l_projlib_author_subjclass_tl .
+ }
+ }
+ \tl_const:cn { subjclassname_1991 } { \textup{1991}~Mathematics~Subject~Classification }
+ \tl_const:cn { subjclassname_2000 } { \textup{2000}~Mathematics~Subject~Classification }
+ \tl_const:cn { subjclassname_2010 } { \textup{2010}~Mathematics~Subject~Classification }
+ \tl_const:cn { subjclassname_2020 } { \textup{2020}~Mathematics~Subject~Classification }
+ }
+
+\endinput
+%%
+%% End of file `projlib-author.sty'.
diff --git a/macros/latex/contrib/projlib/new-modules/projlib-datetime.sty b/macros/latex/contrib/projlib/new-modules/projlib-datetime.sty
new file mode 100644
index 0000000000..d96d31d93b
--- /dev/null
+++ b/macros/latex/contrib/projlib/new-modules/projlib-datetime.sty
@@ -0,0 +1,1645 @@
+\NeedsTeXFormat{LaTeX2e}[2020-10-01]
+\RequirePackage{l3keys2e}
+\ProvidesExplPackage
+ {projlib-datetime}
+ {2022/02/24} {}
+ {Convert numeric date-time string to natural language}
+
+\tl_new:N \g__projlib_datetime_input_format_tl
+\tl_gset:Nn \g__projlib_datetime_input_format_tl { year-month-day }
+
+\keys_define:nn { projlib-datetime }
+ {
+ , plain .bool_set:N = \l__projlib_datetime_plain_bool
+ , plain .initial:n = { false }
+ , year-month-day .code:n = { \tl_gset:Nn \g__projlib_datetime_input_format_tl { year-month-day } }
+ , month-day-year .code:n = { \tl_gset:Nn \g__projlib_datetime_input_format_tl { month-day-year } }
+ , day-month-year .code:n = { \tl_gset:Nn \g__projlib_datetime_input_format_tl { day-month-year } }
+ , year-month .code:n = { \tl_gset:Nn \g__projlib_datetime_input_format_tl { year-month-day } }
+ , month-day .code:n = { \tl_gset:Nn \g__projlib_datetime_input_format_tl { year-month-day } }
+ , month-year .code:n = { \tl_gset:Nn \g__projlib_datetime_input_format_tl { day-month-year } }
+ , day-month .code:n = { \tl_gset:Nn \g__projlib_datetime_input_format_tl { day-month-year } }
+ , unknown .code:n = {}
+ }
+\ProcessKeysOptions { projlib-datetime }
+
+\bool_if:NT \l__projlib_datetime_plain_bool
+ {
+ \hook_gput_code:nnn { begindocument/before } { projlib-datetime }
+ {
+ \cs_set:Nn \projlib_datetime_text_superscript:n { #1 }
+ }
+ }
+
+\tl_new:N \l__projlib_datetime_output_format_tl
+\keys_define:nn { projlib-datetime-command }
+ {
+ , year-month-day .code:n = { \tl_set:Nn \g__projlib_datetime_input_format_tl { year-month-day } }
+ , month-day-year .code:n = { \tl_set:Nn \g__projlib_datetime_input_format_tl { month-day-year } }
+ , day-month-year .code:n = { \tl_set:Nn \g__projlib_datetime_input_format_tl { day-month-year } }
+ , year-month .code:n = { \tl_set:Nn \g__projlib_datetime_input_format_tl { year-month-day } }
+ , month-day .code:n = { \tl_set:Nn \g__projlib_datetime_input_format_tl { year-month-day } }
+ , month-year .code:n = { \tl_set:Nn \g__projlib_datetime_input_format_tl { day-month-year } }
+ , day-month .code:n = { \tl_set:Nn \g__projlib_datetime_input_format_tl { day-month-year } }
+ , only-year-month .code:n = { \tl_set:Nn \l__projlib_datetime_output_format_tl { only-year-month } }
+ , only-month-day .code:n = { \tl_set:Nn \l__projlib_datetime_output_format_tl { only-month-day } }
+ , only-year .code:n = { \tl_set:Nn \l__projlib_datetime_output_format_tl { only-year } }
+ , only-month .code:n = { \tl_set:Nn \l__projlib_datetime_output_format_tl { only-month } }
+ , only-day .code:n = { \tl_set:Nn \l__projlib_datetime_output_format_tl { only-day } }
+ , unknown .code:n = {}
+ }
+
+\msg_new:nnnn { projlib-datetime }
+ { non-supported-date-format }
+ { The~date~format~"#1"~is~not~supported. }
+ { The~only~formats~available~are~"year-month-day",~ "month-day-year"~and~"day-month-year". }
+
+\cs_new:Nn \projlib_datetime_set_input_format:n
+ {
+ \str_case:nnF { #1 }
+ {
+ { year-month-day } { \tl_gset:Nn \g__projlib_datetime_input_format_tl { year-month-day } }
+ { month-day-year } { \tl_gset:Nn \g__projlib_datetime_input_format_tl { month-day-year } }
+ { day-month-year } { \tl_gset:Nn \g__projlib_datetime_input_format_tl { day-month-year } }
+ }
+ {
+ \msg_error:nnn { projlib-datetime } { non-supported-date-format } { #1 }
+ }
+ }
+
+\NewDocumentCommand \ProjLibSetDatetimeInputFormat { m }
+ {
+ \projlib_datetime_set_input_format:n { #1 }
+ }
+
+
+\seq_new:N \l__projlib_datetime_date_seq
+
+\cs_new_protected:Nn \projlib_datetime_text_superscript:n
+ {
+ \group_begin:
+ \small
+ \textsuperscript { #1 }
+ \group_end:
+ }
+
+
+\cs_new:Nn \__projlib_datetime_today:n
+ {
+ \str_case_e:nnF { \l__projlib_datetime_output_format_tl }
+ {
+ { only-year-month }
+ {
+ \cs_if_exist_use:c { projlib_datetime_display_year_month #1 _ \languagename :nn }
+ { \int_use:N \c_sys_year_int }
+ { \int_use:N \c_sys_month_int }
+ }
+ { only-month-day }
+ {
+ \cs_if_exist_use:c { projlib_datetime_display_month_day #1 _ \languagename :nn }
+ { \int_use:N \c_sys_month_int }
+ { \int_use:N \c_sys_day_int }
+ }
+ { only-year }
+ {
+ \cs_if_exist_use:cT { projlib_datetime_display_year #1 _ \languagename :n }
+ {
+ { \int_use:N \c_sys_year_int }
+ }
+ }
+ { only-month }
+ {
+ \cs_if_exist_use:cT { projlib_datetime_display_month #1 _ \languagename :n }
+ {
+ { \int_use:N \c_sys_month_int }
+ }
+ }
+ { only-day }
+ {
+ \cs_if_exist_use:cT { projlib_datetime_display_day #1 _ \languagename :n }
+ {
+ { \int_use:N \c_sys_day_int }
+ }
+ }
+ }
+ {
+ \cs_if_exist_use:c { projlib_datetime_display_year_month_day #1 _ \languagename :nnn }
+ { \int_use:N \c_sys_year_int }
+ { \int_use:N \c_sys_month_int }
+ { \int_use:N \c_sys_day_int }
+ }
+ }
+
+\NewDocumentCommand \ProjLibtoday { O{} }
+ {
+ \group_begin:
+ \keys_set:nn { projlib-datetime-command } { #1 }
+ \__projlib_datetime_today:n {}
+ \group_end:
+ }
+
+\NewDocumentCommand \ProjLibToday { O{} }
+ {
+ \group_begin:
+ \keys_set:nn { projlib-datetime-command } { #1 }
+ \__projlib_datetime_today:n { _uppercase }
+ \group_end:
+ }
+
+\ProvideDocumentCommand \Thedate {} { \ProjLibdate }
+\ProvideDocumentCommand \TheDate {} { \ProjLibDate }
+
+
+\cs_new_protected:Nn \projlib_datetime_display_year_month_day:n
+ {
+ \__projlib_datetime_display_year_month_day:en { #1 } {}
+ }
+
+\cs_new_protected:Nn \projlib_datetime_display_year_month_day_uppercase:n
+ {
+ \__projlib_datetime_display_year_month_day:en { #1 } { _uppercase }
+ }
+
+\cs_new_protected:Nn \__projlib_datetime_display_year_month_day:nn
+ {
+ \regex_match:nnTF { \A \d{4}-\d{1,2}-\d{1,2} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_year_month_day #2 _ \languagename :nnn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } } % year
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } } % month
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 3 } } } % day
+ }
+ {
+ \regex_match:nnTF { \A \d{4}-\d{1,2} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_year_month #2 _ \languagename :nn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } } % year
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } } % month
+ }
+ {
+ \regex_match:nnTF { \A \d{1,2}-\d{1,2} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_month_day #2 _ \languagename :nn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } } % month
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } } % day
+ }
+ {
+ #1
+ }
+ }
+ }
+ }
+\cs_generate_variant:Nn \__projlib_datetime_display_year_month_day:nn { en }
+
+
+\cs_new_protected:Nn \projlib_datetime_display_month_day_year:n
+{
+ \__projlib_datetime_display_month_day_year:en { #1 } {}
+}
+
+\cs_new_protected:Nn \projlib_datetime_display_month_day_year_uppercase:n
+{
+ \__projlib_datetime_display_month_day_year:en { #1 } { _uppercase }
+}
+
+\cs_new_protected:Nn \__projlib_datetime_display_month_day_year:nn
+{
+ \regex_match:nnTF { \A \d{1,2}-\d{1,2}-\d{4} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_month_day_year #2 _ \languagename :nnn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 3 } } } % year
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } } % month
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } } % day
+ }
+ {
+ \regex_match:nnTF { \A \d{1,2}-\d{4} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_year_month #2 _ \languagename :nn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } } % year
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } } % month
+ }
+ {
+ \regex_match:nnTF { \A \d{1,2}-\d{1,2} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_month_day #2 _ \languagename :nn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } } % month
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } } % day
+ }
+ {
+ #1
+ }
+ }
+ }
+}
+\cs_generate_variant:Nn \__projlib_datetime_display_month_day_year:nn { en }
+
+
+\cs_new_protected:Nn \projlib_datetime_display_day_month_year:n
+{
+ \__projlib_datetime_display_day_month_year:en { #1 } {}
+}
+
+\cs_new_protected:Nn \projlib_datetime_display_day_month_year_uppercase:n
+{
+ \__projlib_datetime_display_day_month_year:en { #1 } { _uppercase }
+}
+
+\cs_new_protected:Nn \__projlib_datetime_display_day_month_year:nn
+{
+ \regex_match:nnTF { \A \d{1,2}-\d{1,2}-\d{4} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_day_month_year #2 _ \languagename :nnn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 3 } } } % year
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } } % month
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } } % day
+ }
+ {
+ \regex_match:nnTF { \A \d{1,2}-\d{4} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_year_month #2 _ \languagename :nn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } } % year
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } } % month
+ }
+ {
+ \regex_match:nnTF { \A \d{1,2}-\d{1,2} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_month_day #2 _ \languagename :nn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } } % month
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } } % day
+ }
+ {
+ #1
+ }
+ }
+ }
+}
+\cs_generate_variant:Nn \__projlib_datetime_display_day_month_year:nn { en }
+
+
+\cs_new_protected:Nn \__projlib_datetime_date_parse_year_month_day:n
+ {
+ \regex_match:nnTF { \A \d{4}-\d{1,2}(|-\d{1,2}) \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ }
+ {
+ \regex_match:nnT { \A \d{1,2}-\d{1,2} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \seq_put_left:Nn \l__projlib_datetime_date_seq {}
+ }
+ }
+ }
+
+\cs_new_protected:Nn \__projlib_datetime_date_parse_month_day_year:n
+ {
+ \regex_match:nnTF { \A \d{1,2}(|-\d{1,2})-\d{4} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \seq_pop_right:NN \l__projlib_datetime_date_seq \l_tmpa_tl
+ \seq_put_left:NV \l__projlib_datetime_date_seq \l_tmpa_tl
+ }
+ {
+ \regex_match:nnT { \A \d{1,2}-\d{1,2} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \seq_put_left:Nn \l__projlib_datetime_date_seq {}
+ }
+ }
+ }
+
+\cs_new_protected:Nn \__projlib_datetime_date_parse_day_month_year:n
+ {
+ \regex_match:nnTF { \A \d{1,2}(|-\d{1,2})-\d{4} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \seq_reverse:N \l__projlib_datetime_date_seq
+ }
+ {
+ \regex_match:nnT { \A \d{1,2}-\d{1,2} \Z } { #1 }
+ {
+ \seq_set_split:Nnn \l__projlib_datetime_date_seq { - } { #1 }
+ \seq_reverse:N \l__projlib_datetime_date_seq
+ \seq_put_left:Nn \l__projlib_datetime_date_seq {}
+ }
+ }
+ }
+
+\cs_new:Nn \__projlib_datetime_input_format_to_str:
+ {
+ \str_case_e:nn { \g__projlib_datetime_input_format_tl }
+ {
+ { year-month-day } { year_month_day }
+ { month-day-year } { month_day_year }
+ { day-month-year } { day_month_year }
+ }
+ }
+
+\cs_new_protected:Nn \__projlib_datetime_display_date_according_to_format:nn
+ {
+ \cs_if_exist_use:c { __projlib_datetime_date_parse_ \__projlib_datetime_input_format_to_str: :n } { #1 }
+ \str_case_e:nnF { \l__projlib_datetime_output_format_tl }
+ {
+ { only-year-month }
+ {
+ \cs_if_exist_use:c { projlib_datetime_display_year_month #2 _ \languagename :nn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } }
+ }
+ { only-month-day }
+ {
+ \cs_if_exist_use:c { projlib_datetime_display_month_day #2 _ \languagename :nn }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 3 } } }
+ }
+ { only-year }
+ {
+ \cs_if_exist_use:c { projlib_datetime_display_year #2 _ \languagename :n }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } }
+ }
+ { only-month }
+ {
+ \cs_if_exist_use:c { projlib_datetime_display_month #2 _ \languagename :n }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } }
+ }
+ { only-day }
+ {
+ \cs_if_exist_use:c { projlib_datetime_display_day #2 _ \languagename :n }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 3 } } }
+ }
+ }
+ {
+ \cs_if_exist_use:c { projlib_datetime_display_ \__projlib_datetime_input_format_to_str: #2 :n } { #1 }
+ }
+ }
+
+\NewDocumentCommand \ProjLibdate { O{} m }
+ {
+ \group_begin:
+ \keys_set:nn { projlib-datetime-command } { #1 }
+ \__projlib_datetime_display_date_according_to_format:nn { #2 } {}
+ \group_end:
+ }
+
+\NewDocumentCommand \ProjLibDate { O{} m }
+ {
+ \group_begin:
+ \keys_set:nn { projlib-datetime-command } { #1 }
+ \__projlib_datetime_display_date_according_to_format:nn { #2 } { _uppercase }
+ \group_end:
+ }
+
+
+\int_new:N \l__projlib_datetime_day_of_week_day_int
+\int_new:N \l__projlib_datetime_day_of_week_month_int
+\int_new:N \l__projlib_datetime_day_of_week_year_int
+\int_new:N \l__projlib_datetime_day_of_week_century_int
+\int_new:N \l__projlib_datetime_day_of_week_result_int
+\cs_new:Nn \__projlib_datetime_calculate_day_of_week_with_year_month_day:nnn
+ {
+ \int_set:Nn \l__projlib_datetime_day_of_week_day_int { #3 }
+ \int_compare:nNnTF { #2 } < { 3 }
+ {
+ \int_set:Nn \l__projlib_datetime_day_of_week_year_int { \int_eval:n { #1 - 1 } }
+ }
+ {
+ \int_set:Nn \l__projlib_datetime_day_of_week_year_int { #1 }
+ }
+ \int_set:Nn \l__projlib_datetime_day_of_week_century_int { \int_eval:n { \l__projlib_datetime_day_of_week_year_int / 100 } }
+ \int_set:Nn \l__projlib_datetime_day_of_week_year_int { \int_mod:nn { \l__projlib_datetime_day_of_week_year_int } { 100 } }
+ \int_set:Nn \l__projlib_datetime_day_of_week_month_int
+ {
+ \int_case:nnF { #2 }
+ {
+ { 1 } { 0 }
+ { 2 } { 3 }
+ { 3 } { 2 }
+ { 4 } { 5 }
+ { 5 } { 0 }
+ { 6 } { 3 }
+ { 7 } { 5 }
+ { 8 } { 1 }
+ { 9 } { 4 }
+ { 10 } { 6 }
+ { 11 } { 2 }
+ { 12 } { 4 }
+ } { 0 }
+ }
+ \int_set:Nn \l__projlib_datetime_day_of_week_result_int
+ {
+ \int_eval:n
+ {
+ \l__projlib_datetime_day_of_week_day_int
+ + \l__projlib_datetime_day_of_week_month_int
+ +\l__projlib_datetime_day_of_week_year_int
+ + \int_div_truncate:nn { \l__projlib_datetime_day_of_week_year_int } { 4 }
+ + \int_div_truncate:nn { \l__projlib_datetime_day_of_week_century_int } { 4 }
+ - 2 * \l__projlib_datetime_day_of_week_century_int
+ }
+ }
+ \int_set:Nn \l__projlib_datetime_day_of_week_result_int
+ {
+ \int_mod:nn { \l__projlib_datetime_day_of_week_result_int } { 7 }
+ }
+ \int_compare:nNnT { \l__projlib_datetime_day_of_week_result_int } < { 1 }
+ {
+ \int_set:Nn \l__projlib_datetime_day_of_week_result_int
+ {
+ \int_eval:n { \l__projlib_datetime_day_of_week_result_int + 7 }
+ }
+ }
+ }
+
+
+\cs_new_protected:Nn \projlib_datetime_display_week:n
+ {
+ \__projlib_datetime_display_week_according_to_format:en { #1 } {}
+ }
+
+\cs_new_protected:Nn \projlib_datetime_display_week_uppercase:n
+ {
+ \__projlib_datetime_display_week_according_to_format:en { #1 } { _uppercase }
+ }
+
+\cs_new_protected:Nn \__projlib_datetime_display_week_according_to_format:nn
+ {
+ \projlib_datetime_calculate_week_according_to_format:n { #1 }
+ \cs_if_exist_use:c { projlib_datetime_display_week #2 _ \languagename :n } { \l__projlib_datetime_day_of_week_result_int }
+ }
+\cs_generate_variant:Nn \__projlib_datetime_display_week_according_to_format:nn { en }
+
+
+\cs_new_protected:Nn \projlib_datetime_calculate_week_according_to_format:n
+ {
+ \cs_if_exist_use:c { __projlib_datetime_date_parse_ \__projlib_datetime_input_format_to_str: :n } { #1 }
+ \__projlib_datetime_calculate_day_of_week_with_year_month_day:nnn
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 1 } } }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 2 } } }
+ { \int_eval:n { \seq_item:Nn \l__projlib_datetime_date_seq { 3 } } }
+ }
+
+
+% For each language, there are 7 groups of functions:
+% 1) \projlib_datetime_display_year_month_day_<languagename>:nnn
+% \projlib_datetime_display_year_month_day_uppercase_<languagename>:nnn
+% 2) \projlib_datetime_display_year_month_<languagename>:nn
+% \projlib_datetime_display_year_month_uppercase_<languagename>:nn
+% 3) \projlib_datetime_display_month_day_<languagename>:nn
+% \projlib_datetime_display_month_day_uppercase_<languagename>:nn
+% 4) \projlib_datetime_display_year_<languagename>:n
+% \projlib_datetime_display_year_uppercase_<languagename>:n
+% 5) \projlib_datetime_display_month_<languagename>:n
+% \projlib_datetime_display_month_uppercase_<languagename>:n
+% 6) \projlib_datetime_display_day_<languagename>:n
+% \projlib_datetime_display_day_uppercase_<languagename>:n
+% 7) \projlib_datetime_display_week_<languagename>:n
+% \projlib_datetime_display_week_uppercase_<languagename>:n
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_english:nnn
+ {
+ \projlib_datetime_display_month_day_english:nn { #2 } { #3 }
+ ,\nobreakspace
+ \projlib_datetime_display_year_english:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_english:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_english:nnn
+ {
+ \projlib_datetime_display_month_day_uppercase_english:nn { #2 } { #3 }
+ ,\nobreakspace
+ \projlib_datetime_display_year_english:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_english:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_english:nn
+ {
+ \projlib_datetime_display_month_english:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_english:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_english:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_english:nn
+ {
+ \projlib_datetime_display_month_uppercase_english:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_english:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_english:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_english:nn
+ {
+ \projlib_datetime_display_month_english:n { #1 }
+ \nobreakspace
+ \projlib_datetime_display_day_english:n { #2 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_english:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_english:nn
+ {
+ \projlib_datetime_display_month_uppercase_english:n { #1 }
+ \nobreakspace
+ \projlib_datetime_display_day_english:n { #2 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_english:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_english:n { #1 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_english:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_month_english:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { January }
+ { 2 } { February }
+ { 3 } { March }
+ { 4 } { April }
+ { 5 } { May }
+ { 6 } { June }
+ { 7 } { July }
+ { 8 } { August }
+ { 9 } { September }
+ { 10 } { October }
+ { 11 } { November }
+ { 12 } { December }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_month_uppercase_english:n
+ {
+ \projlib_datetime_display_month_english:n { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_day_english:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_english:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_english:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Monday }
+ { 2 } { Tuesday }
+ { 3 } { Wednesday }
+ { 4 } { Thursday }
+ { 5 } { Friday }
+ { 6 } { Saturday }
+ { 7 } { Sunday }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_english:n
+ {
+ \projlib_datetime_display_week_english:n { #1 }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_french:nnn
+ {
+ \projlib_datetime_display_month_day_french:nn { #2 } { #3 }
+ \nobreakspace
+ \projlib_datetime_display_year_french:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_french:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_french:nnn
+ {
+ \projlib_datetime_display_month_day_uppercase_french:nn { #2 } { #3 }
+ \nobreakspace
+ \projlib_datetime_display_year_french:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_french:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_french:nn
+ {
+ \projlib_datetime_display_month_french:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_french:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_french:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_french:nn
+ {
+ \projlib_datetime_display_month_uppercase_french:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_french:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_french:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_french:nn
+ {
+ \projlib_datetime_display_day_french:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_month_french:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_french:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_french:nn
+ {
+ \projlib_datetime_display_month_day_french:nn { #1 } { #2 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_french:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_french:n { #1 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_french:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_month_french:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { janvier }
+ { 2 } { février }
+ { 3 } { mars }
+ { 4 } { avril }
+ { 5 } { mai }
+ { 6 } { juin }
+ { 7 } { juillet }
+ { 8 } { août }
+ { 9 } { septembre }
+ { 10 } { octobre }
+ { 11 } { novembre }
+ { 12 } { décembre }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_month_uppercase_french:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Janvier }
+ { 2 } { Février }
+ { 3 } { Mars }
+ { 4 } { Avril }
+ { 5 } { Mai }
+ { 6 } { Juin }
+ { 7 } { Juillet }
+ { 8 } { Août }
+ { 9 } { Septembre }
+ { 10 } { Octobre }
+ { 11 } { Novembre }
+ { 12 } { Décembre }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_day_french:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { 1 \projlib_datetime_text_superscript:n { er } }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_day_uppercase_french:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { 1 \projlib_datetime_text_superscript:n { er } }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_french:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { lundi }
+ { 2 } { mardi }
+ { 3 } { mercredi }
+ { 4 } { jeudi }
+ { 5 } { vendredi }
+ { 6 } { samedi }
+ { 7 } { dimanche }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_french:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Lundi }
+ { 2 } { Mardi }
+ { 3 } { Mercredi }
+ { 4 } { Jeudi }
+ { 5 } { Vendredi }
+ { 6 } { Samedi }
+ { 7 } { Dimanche }
+ } { ??? }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_ngerman:nnn
+ {
+ \projlib_datetime_display_month_day_ngerman:nn { #2 } { #3 }
+ \nobreakspace
+ \projlib_datetime_display_year_ngerman:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_ngerman:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_ngerman:nnn
+ {
+ \projlib_datetime_display_month_day_uppercase_ngerman:nn { #2 } { #3 }
+ \nobreakspace
+ \projlib_datetime_display_year_ngerman:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_ngerman:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_ngerman:nn
+ {
+ \projlib_datetime_display_month_ngerman:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_ngerman:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_ngerman:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_ngerman:nn
+ {
+ \projlib_datetime_display_month_uppercase_ngerman:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_ngerman:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_ngerman:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_ngerman:nn
+ {
+ \projlib_datetime_display_day_ngerman:n { #2 }
+ .\nobreakspace
+ \projlib_datetime_display_month_ngerman:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_ngerman:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_ngerman:nn
+ {
+ \projlib_datetime_display_month_day_ngerman:nn { #1 } { #2 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_ngerman:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_ngerman:n { #1 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_ngerman:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_month_ngerman:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Januar }
+ { 2 } { Februar }
+ { 3 } { März }
+ { 4 } { April }
+ { 5 } { Mai }
+ { 6 } { Juni }
+ { 7 } { Juli }
+ { 8 } { August }
+ { 9 } { September }
+ { 10 } { Oktober }
+ { 11 } { November }
+ { 12 } { Dezember }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_month_uppercase_ngerman:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Januar }
+ { 2 } { Februar }
+ { 3 } { März }
+ { 4 } { April }
+ { 5 } { Mai }
+ { 6 } { Juni }
+ { 7 } { Juli }
+ { 8 } { August }
+ { 9 } { September }
+ { 10 } { Oktober }
+ { 11 } { November }
+ { 12 } { Dezember }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_day_ngerman:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_ngerman:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_ngerman:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Montag }
+ { 2 } { Dienstag }
+ { 3 } { Mittwoch }
+ { 4 } { Donnerstag }
+ { 5 } { Freitag }
+ { 6 } { Samstag }
+ { 7 } { Sonntag }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_ngerman:n
+ {
+ \projlib_datetime_display_week_ngerman:n { #1 }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_italian:nnn
+ {
+ \projlib_datetime_display_month_day_italian:nn { #2 } { #3 }
+ \nobreakspace
+ \projlib_datetime_display_year_italian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_italian:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_italian:nnn
+ {
+ \projlib_datetime_display_month_day_uppercase_italian:nn { #2 } { #3 }
+ \nobreakspace
+ \projlib_datetime_display_year_italian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_italian:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_italian:nn
+ {
+ \projlib_datetime_display_month_italian:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_italian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_italian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_italian:nn
+ {
+ \projlib_datetime_display_month_uppercase_italian:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_italian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_italian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_italian:nn
+ {
+ \projlib_datetime_display_day_italian:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_month_italian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_italian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_italian:nn
+ {
+ \projlib_datetime_display_month_day_italian:nn { #1 } { #2 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_italian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_italian:n { #1 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_italian:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_month_italian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { gennaio }
+ { 2 } { febbraio }
+ { 3 } { marzo }
+ { 4 } { aprile }
+ { 5 } { maggio }
+ { 6 } { giugno }
+ { 7 } { luglio }
+ { 8 } { agosto }
+ { 9 } { settembre }
+ { 10 } { ottobre }
+ { 11 } { novembre }
+ { 12 } { dicembre }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_month_uppercase_italian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Gennaio }
+ { 2 } { Febbraio }
+ { 3 } { Marzo }
+ { 4 } { Aprile }
+ { 5 } { Maggio }
+ { 6 } { Giugno }
+ { 7 } { Luglio }
+ { 8 } { Agosto }
+ { 9 } { Settembre }
+ { 10 } { Ottobre }
+ { 11 } { Novembre }
+ { 12 } { Dicembre }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_day_italian:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_italian:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_italian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { lunedì }
+ { 2 } { martedì }
+ { 3 } { mercoledì }
+ { 4 } { giovedì }
+ { 5 } { venerdì }
+ { 6 } { sabato }
+ { 7 } { domenica }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_italian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Lunedì }
+ { 2 } { Martedì }
+ { 3 } { Mercoledì }
+ { 4 } { Giovedì }
+ { 5 } { Venerdì }
+ { 6 } { Sabato }
+ { 7 } { Domenica }
+ } { ??? }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_portuguese:nnn
+ {
+ \projlib_datetime_display_month_day_portuguese:nn { #2 } { #3 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_year_portuguese:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_portuguese:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_portuguese:nnn
+ {
+ \projlib_datetime_display_month_day_uppercase_portuguese:nn { #2 } { #3 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_year_portuguese:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_portuguese:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_portuguese:nn
+ {
+ \projlib_datetime_display_month_portuguese:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_portuguese:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_portuguese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_portuguese:nn
+ {
+ \projlib_datetime_display_month_uppercase_portuguese:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_portuguese:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_portuguese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_portuguese:nn
+ {
+ \projlib_datetime_display_day_portuguese:n { #2 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_month_uppercase_portuguese:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_portuguese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_portuguese:nn
+ {
+ \projlib_datetime_display_month_day_portuguese:nn { #1 } { #2 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_portuguese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_portuguese:n { #1 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_portuguese:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_month_portuguese:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { janeiro }
+ { 2 } { fevereiro }
+ { 3 } { março }
+ { 4 } { abril }
+ { 5 } { maio }
+ { 6 } { junho }
+ { 7 } { julho }
+ { 8 } { agosto }
+ { 9 } { setembro }
+ { 10 } { outubro }
+ { 11 } { novembro }
+ { 12 } { dezembro }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_month_uppercase_portuguese:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Janeiro }
+ { 2 } { Fevereiro }
+ { 3 } { Março }
+ { 4 } { Abril }
+ { 5 } { Maio }
+ { 6 } { Junho }
+ { 7 } { Julho }
+ { 8 } { Agosto }
+ { 9 } { Setembro }
+ { 10 } { Outubro }
+ { 11 } { Novembro }
+ { 12 } { Dezembro }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_day_portuguese:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_portuguese:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_portuguese:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { segunda-feira }
+ { 2 } { terça-feira }
+ { 3 } { quarta-feira }
+ { 4 } { quinta-feira }
+ { 5 } { sexta-feira }
+ { 6 } { sábado }
+ { 7 } { domingo }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_portuguese:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Segunda-feira }
+ { 2 } { Terça-feira }
+ { 3 } { Quarta-feira }
+ { 4 } { Quinta-feira }
+ { 5 } { Sexta-feira }
+ { 6 } { Sábado }
+ { 7 } { Domingo }
+ } { ??? }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_brazilian:nnn
+ {
+ \projlib_datetime_display_month_day_brazilian:nn { #2 } { #3 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_year_brazilian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_brazilian:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_brazilian:nnn
+ {
+ \projlib_datetime_display_month_day_uppercase_brazilian:nn { #2 } { #3 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_year_brazilian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_brazilian:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_brazilian:nn
+ {
+ \projlib_datetime_display_month_brazilian:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_brazilian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_brazilian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_brazilian:nn
+ {
+ \projlib_datetime_display_month_uppercase_brazilian:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_brazilian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_brazilian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_brazilian:nn
+ {
+ \projlib_datetime_display_day_brazilian:n { #2 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_month_uppercase_brazilian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_brazilian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_brazilian:nn
+ {
+ \projlib_datetime_display_month_day_brazilian:nn { #1 } { #2 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_brazilian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_brazilian:n { #1 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_brazilian:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_month_brazilian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { janeiro }
+ { 2 } { fevereiro }
+ { 3 } { março }
+ { 4 } { abril }
+ { 5 } { maio }
+ { 6 } { junho }
+ { 7 } { julho }
+ { 8 } { agosto }
+ { 9 } { setembro }
+ { 10 } { outubro }
+ { 11 } { novembro }
+ { 12 } { dezembro }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_month_uppercase_brazilian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Janeiro }
+ { 2 } { Fevereiro }
+ { 3 } { Março }
+ { 4 } { Abril }
+ { 5 } { Maio }
+ { 6 } { Junho }
+ { 7 } { Julho }
+ { 8 } { Agosto }
+ { 9 } { Setembro }
+ { 10 } { Outubro }
+ { 11 } { Novembro }
+ { 12 } { Dezembro }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_day_brazilian:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_brazilian:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_brazilian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { segunda-feira }
+ { 2 } { terça-feira }
+ { 3 } { quarta-feira }
+ { 4 } { quinta-feira }
+ { 5 } { sexta-feira }
+ { 6 } { sábado }
+ { 7 } { domingo }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_brazilian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Segunda-feira }
+ { 2 } { Terça-feira }
+ { 3 } { Quarta-feira }
+ { 4 } { Quinta-feira }
+ { 5 } { Sexta-feira }
+ { 6 } { Sábado }
+ { 7 } { Domingo }
+ } { ??? }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_spanish:nnn
+ {
+ \projlib_datetime_display_month_day_spanish:nn { #2 } { #3 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_year_spanish:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_spanish:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_spanish:nnn
+ {
+ \projlib_datetime_display_month_day_uppercase_spanish:nn { #2 } { #3 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_year_spanish:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_spanish:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_spanish:nn
+ {
+ \projlib_datetime_display_month_spanish:n { #2 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_year_spanish:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_spanish:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_spanish:nn
+ {
+ \projlib_datetime_display_month_uppercase_spanish:n { #2 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_year_spanish:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_spanish:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_spanish:nn
+ {
+ \projlib_datetime_display_day_spanish:n { #2 }
+ \nobreakspace de \nobreakspace
+ \projlib_datetime_display_month_spanish:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_spanish:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_spanish:nn
+ {
+ \projlib_datetime_display_month_day_spanish:nn { #1 } { #2 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_spanish:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_spanish:n { #1 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_spanish:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_month_spanish:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { enero }
+ { 2 } { febrero }
+ { 3 } { marzo }
+ { 4 } { abril }
+ { 5 } { mayo }
+ { 6 } { junio }
+ { 7 } { julio }
+ { 8 } { agosto }
+ { 9 } { septiembre }
+ { 10 } { octubre }
+ { 11 } { noviembre }
+ { 12 } { diciembre }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_month_uppercase_spanish:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Enero }
+ { 2 } { Febrero }
+ { 3 } { Marzo }
+ { 4 } { Abril }
+ { 5 } { Mayo }
+ { 6 } { Junio }
+ { 7 } { Julio }
+ { 8 } { Agosto }
+ { 9 } { Septiembre }
+ { 10 } { Octubre }
+ { 11 } { Noviembre }
+ { 12 } { Diciembre }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_day_spanish:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_spanish:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_spanish:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { lunes }
+ { 2 } { martes }
+ { 3 } { miércoles }
+ { 4 } { jueves }
+ { 5 } { viernes }
+ { 6 } { sábado }
+ { 7 } { domingo }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_spanish:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Lunes }
+ { 2 } { Martes }
+ { 3 } { Miércoles }
+ { 4 } { Jueves }
+ { 5 } { Viernes }
+ { 6 } { Sábado }
+ { 7 } { Domingo }
+ } { ??? }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_schinese:nnn
+ {
+ #1 年 #2 月 #3 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_schinese:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_schinese:nnn
+ {
+ #1 年 #2 月 #3 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_schinese:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_schinese:nn
+ {
+ #1 年 #2 月
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_schinese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_schinese:nn
+ {
+ #1 年 #2 月
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_schinese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_schinese:nn
+ {
+ #1 月 #2 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_schinese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_schinese:nn
+ {
+ #1 月 #2 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_schinese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_schinese:n { #1 年 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_schinese:n { #1 年 }
+\cs_new:Nn \projlib_datetime_display_month_schinese:n { #1 月 }
+\cs_new:Nn \projlib_datetime_display_month_uppercase_schinese:n { #1 月 }
+\cs_new:Nn \projlib_datetime_display_day_schinese:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_schinese:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_schinese:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { 星期一 }
+ { 2 } { 星期二 }
+ { 3 } { 星期三 }
+ { 4 } { 星期四 }
+ { 5 } { 星期五 }
+ { 6 } { 星期六 }
+ { 7 } { 星期日 }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_schinese:n
+ {
+ \projlib_datetime_display_week_schinese:n { #1 }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_tchinese:nnn
+ {
+ #1 年 #2 月 #3 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_tchinese:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_tchinese:nnn
+ {
+ #1 年 #2 月 #3 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_tchinese:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_tchinese:nn
+ {
+ #1 年 #2 月
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_tchinese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_tchinese:nn
+ {
+ #1 年 #2 月
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_tchinese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_tchinese:nn
+ {
+ #1 月 #2 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_tchinese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_tchinese:nn
+ {
+ #1 月 #2 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_tchinese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_tchinese:n { #1 年 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_tchinese:n { #1 年 }
+\cs_new:Nn \projlib_datetime_display_month_tchinese:n { #1 月 }
+\cs_new:Nn \projlib_datetime_display_month_uppercase_tchinese:n { #1 月 }
+\cs_new:Nn \projlib_datetime_display_day_tchinese:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_tchinese:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_tchinese:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { 星期一 }
+ { 2 } { 星期二 }
+ { 3 } { 星期三 }
+ { 4 } { 星期四 }
+ { 5 } { 星期五 }
+ { 6 } { 星期六 }
+ { 7 } { 星期日 }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_tchinese:n
+ {
+ \projlib_datetime_display_week_tchinese:n { #1 }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_japanese:nnn
+ {
+ #1 年 #2 月 #3 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_japanese:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_japanese:nnn
+ {
+ #1 年 #2 月 #3 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_japanese:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_japanese:nn
+ {
+ #1 年 #2 月
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_japanese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_japanese:nn
+ {
+ #1 年 #2 月
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_japanese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_japanese:nn
+ {
+ #1 月 #2 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_japanese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_japanese:nn
+ {
+ #1 月 #2 日
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_japanese:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_japanese:n { #1 年 }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_japanese:n { #1 年 }
+\cs_new:Nn \projlib_datetime_display_month_japanese:n { #1 月 }
+\cs_new:Nn \projlib_datetime_display_month_uppercase_japanese:n { #1 月 }
+\cs_new:Nn \projlib_datetime_display_day_japanese:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_japanese:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_japanese:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { 月曜日 }
+ { 2 } { 火曜日 }
+ { 3 } { 水曜日 }
+ { 4 } { 木曜日 }
+ { 5 } { 金曜日 }
+ { 6 } { 土曜日 }
+ { 7 } { 日曜日 }
+ } { ??? }
+ }
+
+\cs_new:Nn \projlib_datetime_display_week_uppercase_japanese:n
+ {
+ \projlib_datetime_display_week_japanese:n { #1 }
+ }
+
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_russian:nnn
+ {
+ \projlib_datetime_display_month_day_russian:nn { #2 } { #3 }
+ \nobreakspace
+ \projlib_datetime_display_year_russian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_russian:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_day_uppercase_russian:nnn
+ {
+ \projlib_datetime_display_month_day_uppercase_russian:nn { #2 } { #3 }
+ \nobreakspace
+ \projlib_datetime_display_year_russian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_day_uppercase_russian:nnn { VVV, eee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_russian:nn
+ {
+ \projlib_datetime_display_month_russian:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_russian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_russian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_month_uppercase_russian:nn
+ {
+ \projlib_datetime_display_month_uppercase_russian:n { #2 }
+ \nobreakspace
+ \projlib_datetime_display_year_russian:n { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_year_month_uppercase_russian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_russian:nn
+ {
+ \projlib_datetime_display_day_russian:n { #2 }
+ \nobreakspace
+ \int_case:nnF { #1 }
+ {
+ { 1 } { января }
+ { 2 } { февраля }
+ { 3 } { марта }
+ { 4 } { апреля }
+ { 5 } { мая }
+ { 6 } { июня }
+ { 7 } { июля }
+ { 8 } { августа }
+ { 9 } { сентября }
+ { 10 } { октября }
+ { 11 } { ноября }
+ { 12 } { декабря }
+ } { #1 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_russian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_month_day_uppercase_russian:nn
+ {
+ \projlib_datetime_display_month_day_russian:nn { #1 } { #2 }
+ }
+\cs_generate_variant:Nn \projlib_datetime_display_month_day_uppercase_russian:nn { VV, ee }
+
+\cs_new:Nn \projlib_datetime_display_year_russian:n { #1 ~ г. }
+\cs_new:Nn \projlib_datetime_display_year_uppercase_russian:n { #1 ~ г. }
+
+\cs_new:Nn \projlib_datetime_display_month_russian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { январь }
+ { 2 } { февраль }
+ { 3 } { март }
+ { 4 } { апрель }
+ { 5 } { май }
+ { 6 } { июнь }
+ { 7 } { июль }
+ { 8 } { август }
+ { 9 } { сентябрь }
+ { 10 } { октябрь }
+ { 11 } { ноябрь }
+ { 12 } { декабрь }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_month_uppercase_russian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Январь }
+ { 2 } { Февраль }
+ { 3 } { Март }
+ { 4 } { Апрель }
+ { 5 } { Май }
+ { 6 } { Июнь }
+ { 7 } { Июль }
+ { 8 } { Август }
+ { 9 } { Сентябрь }
+ { 10 } { Октябрь }
+ { 11 } { Ноябрь }
+ { 12 } { Декабрь }
+ } { #1 }
+ }
+
+\cs_new:Nn \projlib_datetime_display_day_russian:n { #1 }
+\cs_new:Nn \projlib_datetime_display_day_uppercase_russian:n { #1 }
+
+\cs_new:Nn \projlib_datetime_display_week_russian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { понедельник }
+ { 2 } { вторник }
+ { 3 } { среда }
+ { 4 } { четверг }
+ { 5 } { пятница }
+ { 6 } { суббота }
+ { 7 } { воскресенье }
+ } { ??? }
+ }
+\cs_new:Nn \projlib_datetime_display_week_uppercase_russian:n
+ {
+ \int_case:nnF { #1 }
+ {
+ { 1 } { Понедельник }
+ { 2 } { Вторник }
+ { 3 } { Среда }
+ { 4 } { Четверг }
+ { 5 } { Пятница }
+ { 6 } { Суббота }
+ { 7 } { Воскресенье }
+ } { ??? }
+ }
+
+\endinput
+%%
+%% End of file `projlib-datetime.sty'.
diff --git a/macros/latex/contrib/projlib/new-modules/projlib-draft.sty b/macros/latex/contrib/projlib/new-modules/projlib-draft.sty
new file mode 100644
index 0000000000..6c7fe818c7
--- /dev/null
+++ b/macros/latex/contrib/projlib/new-modules/projlib-draft.sty
@@ -0,0 +1,140 @@
+\NeedsTeXFormat{LaTeX2e}[2020-10-01]
+\RequirePackage{l3keys2e}
+\ProvidesExplPackage
+ {projlib-draft}
+ {2022/02/24} {}
+ {Useful commands during draft stage}
+
+\keys_define:nn { projlib-draft }
+ {
+ , draft .bool_set:N = \l__projlib_draft_fast_bool
+ , draft .initial:n = { false }
+ , fast .bool_set:N = \l__projlib_draft_fast_bool
+ , unknown .code:n = {}
+ }
+\ProcessKeysOptions { projlib-draft }
+
+\RequirePackage { projlib-language }
+\RequirePackage { xcolor }
+
+\bool_if:NTF \l__projlib_draft_fast_bool
+ {
+ \NewDocumentCommand \projlib_draft_plain_box { O{-paper} m }
+ {
+ \group_begin:
+ \color { #1 }
+ \dim_set:Nn \fboxsep { 1.5pt }
+ \dim_set:Nn \fboxrule { 1.5pt }
+ \fbox { #2 }
+ \group_end:
+ }
+ }
+ {
+ \PassOptionsToPackage { many } { tcolorbox }
+ \RequirePackage { tcolorbox }
+ \NewTCBox \projlib_draft_plain_box { O{-paper} }
+ {
+ enhanced~jigsaw, breakable,
+ on~line, arc = 1.2pt, outer~arc = 1pt,
+ colframe = #1, colupper = #1, opacityback = 0,
+ boxsep = 1pt, boxrule = 1.2pt,
+ left = 1pt, right = 1pt, top = 0pt, bottom = 0pt,
+ }
+ }
+
+\projlib_langauge_define_multilingual_text:Nn \__dnf_text
+ {
+ EN = To\ be\ finished ,
+ FR = Pas\ encore\ fini ,
+ DE = Nicht\ beendet ,
+ IT = Per\ essere\ finito ,
+ PT = Não\ terminou ,
+ BR = Não\ terminou ,
+ ES = Estar\ terminado ,
+ CN = 这里尚未完成 ,
+ TC = 這裡尚未完成 ,
+ JP = 終わってない ,
+ RU = Не\ закончен ,
+ }
+
+\projlib_langauge_define_multilingual_text:Nn \__dnf_report_text
+ {
+ EN = { Below\ is\ a\ list\ of\ outstanding\ items,
+ \ followed\ by\ the\ corresponding\ page\ numbers. } ,
+ FR = { Voici\ la\ liste\ des\ éléments\ en\ suspens,
+ \ suivie\ des\ numéros\ de\ page\ correspondants. } ,
+ DE = { Unten\ ist\ eine\ Liste\ der\ ausstehenden\ Posten,
+ \ gefolgt\ von\ den\ entsprechenden\ Seitenzahlen. } ,
+ IT = { Qui\ sotto\ c'è\ una\ lista\ di\ voci\ in\ sospeso,
+ \ seguita\ dai\ numeri\ di\ pagina\ corrispondenti. } ,
+ PT = { Abaixo\ está\ uma\ lista\ de\ itens\ pendentes,
+ \ seguida\ dos\ números\ de\ página\ correspondentes. } ,
+ BR = { Abaixo\ está\ uma\ lista\ de\ itens\ pendentes,
+ \ seguida\ dos\ números\ de\ página\ correspondentes. } ,
+ ES = { Aquí está\ la\ lista\ de\ elementos\ pendientes,
+ \ seguida\ de\ los\ números\ de\ página\ correspondientes. } ,
+ CN = { 以下是未完成事项列表,括号内注明了对应的页码。 } ,
+ TC = { 以下是未完成事項的列表,括號內註明了對應頁碼。 } ,
+ JP = { 以下は、未解決の項目とそれに対応するページ番号の一覧です。 } ,
+ RU = { Ниже\ приведен\ список\ невыполненных\ пунктов
+ \ с\ соответствующими\ номерами\ страниц\ в\ скобках. } ,
+ }
+
+\tl_new:c { __dnf_font }
+\tl_gset:cn { __dnf_font } { \normalfont \ttfamily \bfseries \small }
+
+\definecolor { __dnf_color } { RGB } { 21, 122, 20 }
+
+\newcounter { __dnf_counter }
+
+\tl_new:N \g__projlib_draft_dnf_report_tl
+
+\NewDocumentCommand \dnf { d<> }
+ {
+ \noindent
+ \projlib_draft_plain_box [ __dnf_color ]
+ {
+ \stepcounter { __dnf_counter }
+ \__dnf_font
+ \__dnf_text
+ \c_space_tl
+ \# \the__dnf_counter
+ \IfNoValueF { #1 } { \, : ~ #1 }
+ \tl_gput_right:Nx \g__projlib_draft_dnf_report_tl
+ {
+ \# \,
+ \int_value:w \the__dnf_counter
+ \c_space_tl \c_space_tl
+ $\langle$
+ \skip_horizontal:n { 0.1em }
+ \cs_if_exist:cTF { hyperlink }
+ {
+ \exp_not:N \hyperlink { page.\thepage } { \thepage }
+ }
+ {
+ \thepage
+ }
+ \skip_horizontal:n { 0.1em }
+ $\rangle$
+ \c_space_tl
+ \IfNoValueF { #1 } { : ~ #1 }
+ \\
+ }
+ }
+ }
+
+\hook_gput_code:nnn { enddocument } { projlib-draft }
+ {
+ \tl_if_blank:VF \g__projlib_draft_dnf_report_tl
+ {
+ \clearpage \noindent
+ \color { __dnf_color }
+ \__dnf_font
+ \__dnf_report_text \\[0.75\baselineskip]
+ \g__projlib_draft_dnf_report_tl
+ }
+ }
+
+\endinput
+%%
+%% End of file `projlib-draft.sty'.
diff --git a/macros/latex/contrib/projlib/new-modules/projlib-font.sty b/macros/latex/contrib/projlib/new-modules/projlib-font.sty
new file mode 100644
index 0000000000..4b2e85f4dc
--- /dev/null
+++ b/macros/latex/contrib/projlib/new-modules/projlib-font.sty
@@ -0,0 +1,137 @@
+\NeedsTeXFormat{LaTeX2e}[2020-10-01]
+\RequirePackage{l3keys2e}
+\ProvidesExplPackage
+ {projlib-font}
+ {2022/02/26} {}
+ {Font selection and configuration}
+
+\keys_define:nn { projlib-font }
+ {
+ , useosf .bool_set:N = \l__projlib_font_useosf_bool
+ , useosf .initial:n = { false }
+ , old-style-figures.bool_set:N = \l__projlib_font_useosf_bool
+ , old~style~figures.bool_set:N = \l__projlib_font_useosf_bool
+ , old style figures.bool_set:N = \l__projlib_font_useosf_bool
+ , lmodern .bool_set:N = \l__projlib_font_lmodern_bool
+ , lmodern .initial:n = { false }
+ , latin-modern .bool_set:N = \l__projlib_font_lmodern_bool
+ , latin~modern .bool_set:N = \l__projlib_font_lmodern_bool
+ , latin modern .bool_set:N = \l__projlib_font_lmodern_bool
+ , palatino .bool_set:N = \l__projlib_font_palatino_bool
+ , palatino .initial:n = { false }
+ , times .bool_set:N = \l__projlib_font_times_bool
+ , times .initial:n = { false }
+ , garamond .bool_set:N = \l__projlib_font_garamond_bool
+ , garamond .initial:n = { false }
+ , noto .bool_set:N = \l__projlib_font_noto_bool
+ , noto .initial:n = { false }
+ , biolinum .bool_set:N = \l__projlib_font_biolinum_bool
+ , biolinum .initial:n = { false }
+ , unknown .code:n = {}
+ }
+\ProcessKeysOptions { projlib-font }
+
+\RequirePackage { setspace }
+
+\cs_new:Nn \projlib_font_set_linespacing_latin:n
+ {
+ \cs_if_exist:NTF \projlib_language_set_linespacing_latin:n
+ {
+ \projlib_language_set_linespacing_latin:n { #1 }
+ }
+ {
+ #1
+ }
+ }
+
+\bool_if:NT \l__projlib_font_lmodern_bool
+ {
+ \RequirePackage { amssymb }
+ \bool_if:NTF \l__projlib_font_useosf_bool
+ {
+ \RequirePackage { cfr-lm }
+ }
+ {
+ \RequirePackage { lmodern }
+ }
+ \projlib_font_set_linespacing_latin:n { \setstretch { 1.00 } }
+ }
+
+\bool_if:NT \l__projlib_font_palatino_bool
+ {
+ \RequirePackage { mathpazo }
+ \PassOptionsToPackage { defaultsups } { newpxtext }
+ \RequirePackage { newpxtext }
+ \cs_undefine:c { openbox }
+ \projlib_font_set_linespacing_latin:n { \setstretch { 1.07 } }
+ \bool_if:NT \l__projlib_font_useosf_bool { \useosf }
+ }
+
+\bool_if:NT \l__projlib_font_times_bool
+ {
+ \PassOptionsToPackage { defaultsups } { newtxtext }
+ \RequirePackage { newtxtext }
+ \RequirePackage { newtxmath }
+ \cs_undefine:c { openbox }
+ \projlib_font_set_linespacing_latin:n { \setstretch { 1.00 } }
+ \bool_if:NT \l__projlib_font_useosf_bool { \useosf }
+ }
+
+\bool_if:NT \l__projlib_font_garamond_bool
+ {
+ \bool_if:NF \l__projlib_font_useosf_bool
+ {
+ \PassOptionsToPackage { lining } { ebgaramond }
+ }
+ \RequirePackage { ebgaramond-maths }
+ \RequirePackage { ebgaramond }
+ \projlib_font_set_linespacing_latin:n { \setstretch { 1.00 } }
+ }
+
+\bool_if:NT \l__projlib_font_noto_bool
+ {
+ \bool_if:NT \l__projlib_font_useosf_bool
+ {
+ \PassOptionsToPackage { oldstyle } { notomath }
+ }
+ \RequirePackage { anyfontsize }
+ \RequirePackage { notomath }
+ \projlib_font_set_linespacing_latin:n { \setstretch { 1.00 } }
+ }
+
+\bool_if:NT \l__projlib_font_biolinum_bool
+ {
+ \sys_if_engine_pdftex:TF
+ {
+ \bool_if:NT \l__projlib_font_useosf_bool
+ {
+ \PassOptionsToPackage { osf } { biolinum }
+ }
+ \RequirePackage { biolinum }
+ \tl_gset:Nn \familydefault { \biolinum@family }
+ }
+ {
+ \PassOptionsToPackage { no-math } { fontspec }
+ \RequirePackage { fontspec }
+ \bool_if:NTF \l__projlib_font_useosf_bool
+ {
+ \setmainfont { LinBiolinum_R.otf } [
+ BoldFont = LinBiolinum_RB.otf ,
+ ItalicFont = LinBiolinum_RI.otf ,
+ BoldItalicFont = LinBiolinum_RBO.otf ,
+ Numbers = OldStyle , ]
+ }
+ {
+ \setmainfont { LinBiolinum_R.otf } [
+ BoldFont = LinBiolinum_RB.otf ,
+ ItalicFont = LinBiolinum_RI.otf ,
+ BoldItalicFont = LinBiolinum_RBO.otf , ]
+ }
+ }
+ \projlib_font_set_linespacing_latin:n { \setstretch { 1.05 } }
+ \RequirePackage [ italic, defaultmathsizes ] { mathastext }
+ }
+
+\endinput
+%%
+%% End of file `projlib-font.sty'.
diff --git a/macros/latex/contrib/projlib/new-modules/projlib-language.sty b/macros/latex/contrib/projlib/new-modules/projlib-language.sty
new file mode 100644
index 0000000000..6167de8d04
--- /dev/null
+++ b/macros/latex/contrib/projlib/new-modules/projlib-language.sty
@@ -0,0 +1,508 @@
+\NeedsTeXFormat{LaTeX2e}[2020-10-01]
+\RequirePackage{l3keys2e}
+\ProvidesExplPackage
+ {projlib-language}
+ {2022/02/26} {}
+ {Multi-language configuration}
+
+\clist_const:Nn \c__projlib_language_supported_clist
+ {
+ english ,
+ french ,
+ ngerman ,
+ italian ,
+ portuguese ,
+ brazilian ,
+ spanish ,
+ schinese ,
+ tchinese ,
+ japanese ,
+ russian ,
+ }
+
+\clist_new:N \g__projlib_language_babel_option_clist
+
+\str_new:N \g__projlib_language_main_str
+
+\bool_new:N \g__projlib_language_enabled_all_bool
+\bool_gset_true:N \g__projlib_language_enabled_all_bool
+
+\clist_map_inline:Nn \c__projlib_language_supported_clist
+ {
+ \bool_new:c { g__projlib_language_enabled_ #1 _bool }
+ \bool_gset_false:c { g__projlib_language_enabled_ #1 _bool }
+ }
+
+\cs_new:Nn \projlib_language_set_option_no_babel:n
+ {
+ \bool_gset_false:N \g__projlib_language_enabled_all_bool
+ \bool_gset_true:c { g__projlib_language_enabled_ #1 _bool }
+ \str_if_empty:NT \g__projlib_language_main_str
+ {
+ \str_gset:Nn \g__projlib_language_main_str { #1 }
+ }
+ }
+\cs_new:Nn \projlib_language_set_option:n
+ {
+ \projlib_language_set_option_no_babel:n { #1 }
+ \clist_gput_left:Nn \g__projlib_language_babel_option_clist { #1 }
+ }
+
+\keys_define:nn { projlib-language }
+ {
+ , EN .meta:n = { english }
+ , english .code:n = { \projlib_language_set_option:n { english } }
+ , English .meta:n = { english }
+ , FR .meta:n = { french }
+ , french .code:n = { \projlib_language_set_option:n { french } }
+ , French .meta:n = { french }
+ , DE .meta:n = { ngerman }
+ , german .meta:n = { ngerman }
+ , ngerman .code:n = { \projlib_language_set_option:n { ngerman } }
+ , German .meta:n = { ngerman }
+ , IT .meta:n = { italian }
+ , italian .code:n = { \projlib_language_set_option:n { italian } }
+ , Italian .meta:n = { italian }
+ , PT .meta:n = { portuguese }
+ , portuguese .code:n = { \projlib_language_set_option:n { portuguese } }
+ , Portuguese .meta:n = { portuguese }
+ , BR .meta:n = { brazilian }
+ , brazilian .code:n = { \projlib_language_set_option:n { brazilian } }
+ , Brazilian .meta:n = { brazilian }
+ , ES .meta:n = { spanish }
+ , spanish .code:n = { \clist_gput_left:Nn \g__projlib_language_babel_option_clist { es-notilde, es-noquoting }
+ \projlib_language_set_option:n { spanish } }
+ , Spanish .meta:n = { spanish }
+ , CN .meta:n = { schinese }
+ , chinese .meta:n = { schinese }
+ , schinese .code:n = { \projlib_language_set_option_no_babel:n { schinese } }
+ , simplifiedchinese .meta:n = { schinese }
+ , simplified-chinese .meta:n = { schinese }
+ , Chinese .meta:n = { schinese }
+ , SChinese .meta:n = { schinese }
+ , SimplifiedChinese .meta:n = { schinese }
+ , Simplified-Chinese .meta:n = { schinese }
+ , TC .meta:n = { tchinese }
+ , tchinese .code:n = { \projlib_language_set_option_no_babel:n { tchinese } }
+ , traditionalchinese .meta:n = { tchinese }
+ , traditional-chinese .meta:n = { tchinese }
+ , TChinese .meta:n = { tchinese }
+ , TraditionalChinese .meta:n = { tchinese }
+ , Traditional-Chinese .meta:n = { tchinese }
+ , JP .meta:n = { japanese }
+ , japanese .code:n = { \projlib_language_set_option:n { japanese } }
+ , Japanese .meta:n = { japanese }
+ , RU .meta:n = { russian }
+ , russian .code:n = { \projlib_language_set_option:n { russian } }
+ , Russian .meta:n = { russian }
+ , unknown .code:n = {}
+ }
+\ProcessKeysOptions { projlib-language }
+
+\cs_new_protected:Nn \projlib_language_clear_global_option:n
+ {
+ \clist_remove_all:cn { @classoptionslist } { #1 }
+ }
+
+\projlib_language_clear_global_option:n { chinese }
+\projlib_language_clear_global_option:n { Chinese }
+
+\clist_gput_right:Nn \g__projlib_language_babel_option_clist { shorthands = off }
+
+\str_if_empty:NT \g__projlib_language_main_str
+ {
+ \str_gset:Nn \g__projlib_language_main_str { english }
+ }
+
+\bool_if:NT \g__projlib_language_enabled_all_bool
+ {
+ \clist_map_inline:Nn \c__projlib_language_supported_clist
+ {
+ \bool_gset_true:c { g__projlib_language_enabled_ #1 _bool }
+ }
+ }
+
+
+\cs_new:Nn \projlib_language_str_to_languagename:n
+ {
+ \str_case_e:nnF { \str_foldcase:n { #1 } }
+ {
+ { all } { common }
+ { common } { common }
+ { cn } { schinese }
+ { chinese } { schinese }
+ { schinese } { schinese }
+ { simplifiedchinese } { schinese }
+ { simplified-chinese } { schinese }
+ { tc } { tchinese }
+ { tchinese } { tchinese }
+ { traditionalchinese } { tchinese }
+ { traditional-chinese} { tchinese }
+ { jp } { japanese }
+ { japanese } { japanese }
+ { en } { english }
+ { english } { english }
+ { fr } { french }
+ { french } { french }
+ { de } { ngerman }
+ { german } { ngerman }
+ { ngerman } { ngerman }
+ { it } { italian }
+ { italian } { italian }
+ { pt } { portuguese }
+ { portuguese } { portuguese }
+ { br } { brazilian }
+ { brazilian } { brazilian }
+ { es } { spanish }
+ { spanish } { spanish }
+ { ru } { russian }
+ { russian } { russian }
+ }
+ { #1 }
+ }
+
+\cs_new:Nn \projlib_language_str_to_languagetype:n
+ {
+ \str_case_e:nn { \str_foldcase:n { #1 } }
+ {
+ { cn } { cjk }
+ { chinese } { cjk }
+ { schinese } { cjk }
+ { simplifiedchinese } { cjk }
+ { simplified-chinese } { cjk }
+ { tc } { cjk }
+ { tchinese } { cjk }
+ { traditionalchinese } { cjk }
+ { traditional-chinese} { cjk }
+ { jp } { cjk }
+ { japanese } { cjk }
+ { en } { latin }
+ { english } { latin }
+ { fr } { latin }
+ { french } { latin }
+ { de } { latin }
+ { german } { latin }
+ { ngerman } { latin }
+ { it } { latin }
+ { italian } { latin }
+ { pt } { latin }
+ { portuguese } { latin }
+ { br } { latin }
+ { brazilian } { latin }
+ { es } { latin }
+ { spanish } { latin }
+ { ru } { latin }
+ { russian } { latin }
+ }
+ }
+
+\sys_if_engine_pdftex:T
+ {
+ \RequirePackage [T1] { fontenc }
+ }
+
+\bool_if:NTF \g__projlib_language_enabled_all_bool
+ {
+ \sys_if_engine_pdftex:F
+ {
+ \PassOptionsToPackage { japanese, russian } { babel }
+ }
+ \PassOptionsToPackage { italian, portuguese, brazilian, spanish, es-notilde, es-noquoting, ngerman, french, english, shorthands = off } { babel }
+ }
+ {
+ \PassOptionsToPackage { \g__projlib_language_babel_option_clist } { babel }
+ }
+
+\PassOptionsToPackage { italian, brazilian, spanish, ngerman, french, english } { cleveref }
+\RequirePackage { babel }
+
+
+\bool_if:NT \g__projlib_language_enabled_french_bool
+ {
+ \frenchsetup { PartNameFull = false }
+ }
+
+\bool_if:NT \g__projlib_language_enabled_portuguese_bool
+ {
+ \NewCommandCopy \__projlib_language_babel_ord: \ord
+ \cs_undefine:N \ord
+ \hook_gput_code:nnn { begindocument } { projlib-language }
+ {
+ \cs_if_exist:NT \ord
+ {
+ \NewCommandCopy \__projlib_language_user_ord: \ord
+ \cs_set_protected:Npn \ord
+ {
+ \mode_if_math:TF
+ { \__projlib_language_user_ord: }
+ { \__projlib_language_babel_ord: }
+ }
+ }
+ }
+ }
+
+\babelprovide { schinese }
+\babelprovide { tchinese }
+
+
+\NewCommandCopy \projlib_language_backup_today:n \today
+
+\tl_new:N \g_projlib_language_settings_common_tl
+\clist_map_inline:Nn \c__projlib_language_supported_clist
+ {
+ \tl_new:c { g_projlib_language_settings_ #1 _tl }
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_common_tl
+ {
+ \cs_if_exist:NT \ProjLibToday
+ {
+ \RenewCommandCopy \today \ProjLibToday
+ }
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_schinese_tl
+ {
+ \g_projlib_language_settings_common_tl
+ \tl_gset:Nn \languagename { schinese }
+ \tl_gset:cn { abstractname } { 摘要 }
+ \tl_gset:cn { proofname } { 证明 }
+ \tl_gset:cn { contentsname } { 目录 }
+ \tl_gset:cn { listfigurename } { 插图 }
+ \tl_gset:cn { listtablename } { 表格 }
+ \tl_gset:cn { figurename } { 图 }
+ \tl_gset:cn { tablename } { 表 }
+ \tl_gset:cn { indexname } { 索引 }
+ \tl_gset:cn { appendixname } { 附录 }
+ \tl_gset:cn { refname } { 参考文献 }
+ \tl_gset:cn { bibname } { 参考文献 }
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_tchinese_tl
+ {
+ \g_projlib_language_settings_common_tl
+ \tl_gset:Nn \languagename { tchinese }
+ \tl_gset:cn { abstractname } { 摘要 }
+ \tl_gset:cn { proofname } { 證明 }
+ \tl_gset:cn { contentsname } { 目錄 }
+ \tl_gset:cn { listfigurename } { 插圖 }
+ \tl_gset:cn { listtablename } { 表格 }
+ \tl_gset:cn { figurename } { 圖 }
+ \tl_gset:cn { tablename } { 表 }
+ \tl_gset:cn { indexname } { 索引 }
+ \tl_gset:cn { appendixname } { 附錄 }
+ \tl_gset:cn { refname } { 參考文獻 }
+ \tl_gset:cn { bibname } { 參考文獻 }
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_japanese_tl
+ {
+ \selectlanguage { japanese }
+ \g_projlib_language_settings_common_tl
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_english_tl
+ {
+ \selectlanguage { english }
+ \g_projlib_language_settings_common_tl
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_french_tl
+ {
+ \selectlanguage { french }
+ \g_projlib_language_settings_common_tl
+ \tl_gset:cn { frenchpartname } { Partie }
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_ngerman_tl
+ {
+ \selectlanguage { ngerman }
+ \g_projlib_language_settings_common_tl
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_italian_tl
+ {
+ \selectlanguage { italian }
+ \g_projlib_language_settings_common_tl
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_portuguese_tl
+ {
+ \selectlanguage { portuguese }
+ \g_projlib_language_settings_common_tl
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_brazilian_tl
+ {
+ \selectlanguage { brazilian }
+ \g_projlib_language_settings_common_tl
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_spanish_tl
+ {
+ \selectlanguage { spanish }
+ \g_projlib_language_settings_common_tl
+ }
+
+\tl_gput_right:Nn \g_projlib_language_settings_russian_tl
+ {
+ \selectlanguage { russian }
+ \g_projlib_language_settings_common_tl
+ }
+
+\NewDocumentCommand \AddLanguageSetting { O{} m }
+ {
+ \IfNoValueTF { #1 }
+ {
+ \tl_gput_right:Nn \g_projlib_language_settings_common_tl { #2 }
+ }
+ {
+ \tl_gput_right:cn { g_projlib_language_settings_ \projlib_language_str_to_languagename:n { #1 } _tl } { #2 }
+ }
+ }
+
+
+\RequirePackage { setspace }
+
+\tl_new:N \g_projlib_language_linespacing_latin_tl
+\tl_new:N \g_projlib_language_linespacing_cjk_tl
+
+\tl_gset:Nn \g_projlib_language_linespacing_latin_tl
+ {
+ \setstretch { 1.00 }
+ }
+\tl_gset:Nn \g_projlib_language_linespacing_cjk_tl
+ {
+ \onehalfspacing
+ }
+
+\cs_new:Nn \projlib_language_set_linespacing_latin:n
+ {
+ \tl_gset:Nn \g_projlib_language_linespacing_latin_tl { #1 }
+ }
+\cs_new:Nn \projlib_language_set_linespacing_cjk:n
+ {
+ \tl_gset:Nn \g_projlib_language_linespacing_cjk_tl { #1 }
+ }
+
+\tl_new:N \g_projlib_language_parindent_latin_tl
+\tl_new:N \g_projlib_language_parindent_cjk_tl
+
+\@ifclassloaded{beamer}
+ {
+ \tl_gset:Nn \g_projlib_language_parindent_latin_tl
+ {
+ \dim_set:Nn \parindent { 0em }
+ }
+ \tl_gset:Nn \g_projlib_language_parindent_cjk_tl
+ {
+ \dim_set:Nn \parindent { 0em }
+ }
+ }
+ {
+ \tl_gset:Nn \g_projlib_language_parindent_latin_tl
+ {
+ \dim_set:Nn \parindent { 1.5em }
+ }
+ \tl_gset:Nn \g_projlib_language_parindent_cjk_tl
+ {
+ \dim_set:Nn \parindent { 2em }
+ }
+ }
+
+\hook_gput_code:nnn { begindocument } { projlib-language }
+ {
+ \@ifpackageloaded{parskip}
+ {
+ \tl_gset:Nn \g_projlib_language_parindent_latin_tl
+ {
+ \dim_set:Nn \parindent { 0em }
+ }
+ \tl_gset:Nn \g_projlib_language_parindent_cjk_tl
+ {
+ \dim_set:Nn \parindent { 0em }
+ }
+ } {}
+ }
+
+\cs_new:Nn \projlib_language_set_parindent_latin:n
+ {
+ \tl_gset:Nn \g_projlib_language_parindent_latin_tl { #1 }
+ }
+\cs_new:Nn \projlib_language_set_parindent_cjk:n
+ {
+ \tl_gset:Nn \g_projlib_language_parindent_cjk_tl { #1 }
+ }
+
+\NewDocumentCommand \ProjLibLanguageSet { m m m }
+ % #1 = the thing to set
+ % #2 = language or type
+ % #3 = the setting
+ {
+ \use:c { projlib_language_set_ #1 _ #2 :n } { #3 }
+ }
+
+
+\cs_new:Nn \projlib_language_use_language_pure:n
+ {
+ \tl_use:c { g_projlib_language_settings_ \projlib_language_str_to_languagename:n { #1 } _tl }
+ }
+\cs_generate_variant:Nn \projlib_language_use_language_pure:n { e }
+
+\cs_new:Nn \projlib_language_use_language:n
+ {
+ \projlib_language_use_language_pure:n { #1 }
+ \tl_use:c { g_projlib_language_linespacing_ \projlib_language_str_to_languagetype:n { #1 } _tl }
+ \tl_use:c { g_projlib_language_parindent_ \projlib_language_str_to_languagetype:n { #1 } _tl }
+ }
+\cs_generate_variant:Nn \projlib_language_use_language:n { e }
+
+\NewDocumentCommand \UseLanguage { m }
+ {
+ \hook_gput_code:nnn { begindocument } { projlib-language }
+ {
+ \projlib_language_use_language:e { #1 }
+ }
+ }
+\NewDocumentCommand \UseOtherLanguage { m m }
+ {
+ \group_begin:
+ \projlib_language_use_language_pure:e { #1 }
+ #2
+ \group_end:
+ }
+
+\UseLanguage { \g__projlib_language_main_str }
+
+
+\cs_new:Nn \projlib_langauge_define_multilingual_text:Nn
+ { % #1 = command name (with backslash)
+ % #2 = key-value name configuration
+ \keyval_parse:nnn
+ {}
+ { \__projlib_langauge_define_multilingual_text_do:nnn { \cs_to_str:N #1 } }
+ { #2 }
+ \tl_gset:Nn #1
+ {
+ \tl_use:c { \cs_to_str:N #1 _projlib_language_text_ \languagename }
+ }
+ }
+\cs_generate_variant:Nn \projlib_langauge_define_multilingual_text:Nn { cn }
+
+\cs_new:Nn \__projlib_langauge_define_multilingual_text_do:nnn
+ { % #1 = command name
+ % #2 = language name
+ % #3 = the text
+ \exp_args:Ne \tl_gset:cn { #1 _projlib_language_text_ \projlib_language_str_to_languagename:n { #2 } } { #3 }
+ }
+
+\NewDocumentCommand \DefineMultilingualText { m m }
+ % #1 = command name
+ % #2 = key-value name configuration
+ {
+ \projlib_langauge_define_multilingual_text:Nn #1 { #2 }
+ }
+
+\endinput
+%%
+%% End of file `projlib-language.sty'.
diff --git a/macros/latex/contrib/projlib/new-modules/projlib-logo.sty b/macros/latex/contrib/projlib/new-modules/projlib-logo.sty
new file mode 100644
index 0000000000..d9ffb5c1ed
--- /dev/null
+++ b/macros/latex/contrib/projlib/new-modules/projlib-logo.sty
@@ -0,0 +1,100 @@
+\NeedsTeXFormat{LaTeX2e}[2020-10-01]
+\RequirePackage{l3keys2e}
+\ProvidesExplPackage
+ {projlib-logo}
+ {2022/02/24} {}
+ {The ProjLib logo}
+
+\keys_define:nn { projlib-logo }
+ {
+ , draft .bool_set:N = \l__projlib_logo_fast_bool
+ , draft .initial:n = { false }
+ , fast .bool_set:N = \l__projlib_logo_fast_bool
+ , unknown .code:n = {}
+ }
+\ProcessKeysOptions { projlib-logo }
+
+\bool_if:NT \l__projlib_logo_fast_bool
+ {
+ \DeclareRobustCommand * \ProjLib
+ {
+ \group_begin:
+ \normalfont ProjLib
+ \group_end:
+ }
+ \endinput
+ }
+
+\cs_if_exist:cTF { fontspec }
+ {
+ \newfontface \__projlib_logo_font: { LinBiolinum_R.otf }
+ }
+ {
+ \RequirePackage [T1] { fontenc }
+ \cs_gset:Nn \__projlib_logo_font:
+ { \fontfamily { LinuxBiolinumT-TLF } \selectfont }
+ }
+
+\tl_new:N \g__projlib_logo_sigma_j_tl
+\tl_gset:Nn \g__projlib_logo_sigma_j_tl
+ {
+ \vbox:n
+ {
+ \offinterlineskip
+ \hbox_overlap_center:n
+ {
+ \skip_horizontal:n { 0.75ex }
+ \hbox_set:Nn \l_tmpa_box { \usefont{U}{zeur}{b}{n} \symbol{"06} }
+ \box_rotate:Nn \l_tmpa_box { -10 }
+ \box_scale:Nnn \l_tmpa_box {.4} {.4}
+ \box_use:N \l_tmpa_box
+ }
+ \skip_vertical:n { 0.15ex }
+ \hbox:n { \j }
+ }
+ }
+
+% \RequirePackage { xcolor }
+% \cs_new_protected:Nn \projlib_logo_generic:n
+% {
+% \group_begin:
+% \normalfont
+% \__projlib_logo_font:
+% \textcolor { blue!55!cyan } { P }
+% \textcolor { blue!45!cyan } { r }
+% \textcolor { blue!30!cyan } { o }
+% \textcolor { green!70!black!70 } { #1 }
+% \textcolor { red!20!yellow } { L }
+% \textcolor { red!40!yellow } { i }
+% \textcolor { red!55!yellow } { b }
+% \group_end:
+% }
+\cs_new_protected:Nn \projlib_logo_generic:n
+ {
+ \group_begin:
+ \normalfont
+ \__projlib_logo_font:
+ \color_group_begin:
+ \color_select:n { blue!60!cyan } { P }
+ \color_select:n { blue!45!cyan } { r }
+ \color_select:n { blue!30!cyan } { o }
+ \color_select:n { green!70!black!70 } { #1 }
+ \color_select:n { red!20!yellow } { L }
+ \color_select:n { red!40!yellow } { i }
+ \color_select:n { red!60!yellow } { b }
+ \color_group_end:
+ \group_end:
+ }
+
+\DeclareRobustCommand * \ProjLib
+ {
+ \projlib_logo_generic:n { \g__projlib_logo_sigma_j_tl }
+ }
+\DeclareRobustCommand * \ProjLibText
+ {
+ \projlib_logo_generic:n { j }
+ }
+
+\endinput
+%%
+%% End of file `projlib-logo.sty'.
diff --git a/macros/latex/contrib/projlib/new-modules/projlib-math.sty b/macros/latex/contrib/projlib/new-modules/projlib-math.sty
new file mode 100644
index 0000000000..977a428003
--- /dev/null
+++ b/macros/latex/contrib/projlib/new-modules/projlib-math.sty
@@ -0,0 +1,234 @@
+\NeedsTeXFormat{LaTeX2e}[2020-10-01]
+\RequirePackage{l3keys2e}
+\ProvidesExplPackage
+ {projlib-math}
+ {2022/02/26} {}
+ {Efficient math setup}
+
+\keys_define:nn { projlib-math }
+ {
+ , unknown .code:n = {}
+ }
+\ProcessKeysOptions { projlib-math }
+
+\RequirePackage { mathtools }
+\RequirePackage { mathrsfs }
+\PassOptionsToPackage { warnings-off = { mathtools-colon, mathtools-overbracket } } { unicode-math }
+\@ifpackageloaded { unicode-math } {} { \RequirePackage { amssymb } }
+
+
+\msg_new:nnn { projlib-math }
+ { operator-not-define }
+ { The~math~operator~"\iow_char:N \\#1"~cannot~be~defined,~a~command~with~the~same~name~has~already~existed. }
+
+\keys_define:nn { projlib-math-operator }
+ {
+ , style .tl_set:N = \l__projlib_math_operator_style_tl
+ , unknown .code:n = {}
+ }
+
+\cs_new_protected:Nn \projlib_math_define_operator:nnn
+ % #1 = bool for star or empty
+ % #2 = list of operators
+ % #3 = the option
+ {
+ \tl_clear:N \l__projlib_math_operator_style_tl
+ \keys_set:nn { projlib-math-operator } { #3 }
+ \clist_map_inline:nn { #2 }
+ {
+ \str_if_in:nnTF { ##1 } { = }
+ {
+ \seq_set_split:Nnn \l_tmpa_seq { = } { ##1 }
+ \__projlib_math_define_operator:nee { #1 } { \seq_item:Nn \l_tmpa_seq { 1 } } { \seq_item:Nn \l_tmpa_seq { 2 } }
+ }
+ {
+ \__projlib_math_define_operator:nee { #1 } { ##1 } { ##1 }
+ }
+ }
+ }
+
+\cs_new_protected:Nn \__projlib_math_define_operator:nnn
+ % #1 = bool for star or empty
+ % #2 = the operator
+ % #3 = the content
+ {
+ \cs_if_exist:cTF { #2 }
+ {
+ \msg_warning:nnn { projlib-math } { operator-not-define } { #2 }
+ }
+ {
+ \bool_if:NTF #1
+ {
+ \exp_args:Nnf \use:n
+ { \exp_args:Nnc \use:n { \DeclareMathOperator* } { #2 } }
+ }
+ {
+ \exp_args:Nnf \use:n
+ { \exp_args:Nc \DeclareMathOperator { #2 } }
+ }
+ % This is the f-argument of \exp_args:Nnf :
+ {
+ \exp_args:NnV \use:nn
+ { \exp_stop_f: }
+ { \l__projlib_math_operator_style_tl } { #3 }
+ }
+ }
+ }
+\cs_generate_variant:Nn \__projlib_math_define_operator:nnn { nee }
+
+\NewDocumentCommand \DefineOperator { s O{} m O{} }
+ {
+ \tl_if_blank:nTF { #2 }
+ {
+ \projlib_math_define_operator:nnn { #1 } { #3 } { #4 }
+ }
+ {
+ \projlib_math_define_operator:nnn { #1 } { #3 } { #2 }
+ }
+ }
+
+
+\msg_new:nnn { projlib-math }
+ { shortcut-not-define }
+ { The~shortcut~"\iow_char:N \\#1"~cannot~be~defined,~a~command~with~the~same~name~has~already~existed. }
+
+\msg_new:nnn { projlib-math }
+ { shortcut-type-not-define }
+ { The~shortcut~type~"#1"~is~not~available,~a~command~with~the~same~name~has~already~existed. }
+
+\keys_define:nn { projlib-math-shortcut }
+ {
+ , prefix .clist_set:N = \l__projlib_math_shortcut_prefix_clist
+ , type .tl_set:N = \l__projlib_math_shortcut_type_tl
+ , style .tl_set:N = \l__projlib_math_shortcut_style_tl
+ , unknown .code:n = {}
+ }
+
+\cs_new_protected:Nn \projlib_math_define_shortcut:nnn
+ % #1 = bool for star or empty
+ % (currently the starred version is the same as normal version)
+ % #2 = list of shortcuts
+ % #3 = the option
+ {
+ \clist_clear:N \l__projlib_math_shortcut_prefix_clist
+ \tl_clear:N \l__projlib_math_shortcut_type_tl
+ \tl_clear:N \l__projlib_math_shortcut_style_tl
+ \keys_set:nn { projlib-math-shortcut } { #3 }
+ \tl_if_empty:NF \l__projlib_math_shortcut_type_tl
+ {
+ \cs_if_exist:cTF { \l__projlib_math_shortcut_type_tl }
+ {
+ \exp_args:Nne \use:n
+ { \msg_warning:nnn { projlib-math } { shortcut-type-not-define } }
+ { \l__projlib_math_shortcut_type_tl }
+ }
+ {
+ \exp_args:Nno \use:n
+ {
+ \exp_args:Nc \NewDocumentCommand { \l__projlib_math_shortcut_type_tl } { m }
+ }
+ {
+ \l__projlib_math_shortcut_style_tl { ##1 }
+ }
+ }
+ }
+ \clist_map_inline:nn { #2 }
+ {
+ \str_if_in:nnTF { ##1 } { = }
+ {
+ \seq_set_split:Nnn \l_tmpa_seq { = } { ##1 }
+ \clist_if_empty:NTF \l__projlib_math_shortcut_prefix_clist
+ {
+ \__projlib_math_define_shortcut:ee { \seq_item:Nn \l_tmpa_seq { 1 } } { \seq_item:Nn \l_tmpa_seq { 2 } }
+ }
+ {
+ \clist_map_inline:Nn \l__projlib_math_shortcut_prefix_clist
+ {
+ \__projlib_math_define_shortcut:ee { ####1 \seq_item:Nn \l_tmpa_seq { 1 } } { \seq_item:Nn \l_tmpa_seq { 2 } }
+ }
+ }
+ }
+ {
+ \clist_if_empty:NTF \l__projlib_math_shortcut_prefix_clist
+ {
+ \__projlib_math_define_shortcut:ee { ##1 } { ##1 }
+ }
+ {
+ \clist_map_inline:Nn \l__projlib_math_shortcut_prefix_clist
+ {
+ \__projlib_math_define_shortcut:ee { ####1 ##1 } { ##1 }
+ }
+ }
+ }
+ }
+ }
+
+\cs_new_protected:Nn \__projlib_math_define_shortcut:nn
+ % #1 = command name
+ % #2 = the content
+ {
+ \cs_if_exist:cTF { #1 }
+ {
+ \msg_warning:nnn { projlib-math } { shortcut-type-not-define } { #1 }
+ }
+ {
+ \exp_args:Nno \use:n
+ { \exp_args:Nc \NewDocumentCommand { #1 } {} }
+ { \l__projlib_math_shortcut_style_tl { #2 } }
+ }
+ }
+\cs_generate_variant:Nn \__projlib_math_define_shortcut:nn { ee }
+
+\NewDocumentCommand \DefineShortcut { s O{} m O{} }
+ {
+ \tl_if_blank:nTF { #2 }
+ {
+ \projlib_math_define_shortcut:nnn { #1 } { #3 } { #4 }
+ }
+ {
+ \projlib_math_define_shortcut:nnn { #1 } { #3 } { #2 }
+ }
+ }
+
+%===============================
+% -- Some possible operators --
+%===============================
+% \DefineOperator { ord, card, car, disc } [ style = \mathsf ]
+% \DefineOperator { id, Id, Ker, Image = Im } [ style = \mathsf ]
+% \DefineOperator { Orb, Stab, Fix } [ style = \mathsf ]
+% \DefineOperator { Hom, Aut, End, Mat } [ style = \mathsf ]
+% \DefineOperator { Spec, Spm, Ann } [ style = \mathsf ]
+% \DefineOperator*{ Lim, Max, Min } [ style = \mathsf ]
+
+%===============================
+% -- Some possible shortcuts --
+%===============================
+% \DefineShortcut [ type = category, style = \mathbf ]
+% {
+% Set, Grp, Ring, Top
+% }
+% \DefineShortcut [ style = \mathbb ]
+% {
+% N, Z, Q, R, C, F, A
+% }
+% \DefineShortcut [ prefix = bb, style = \mathbb ]
+% {
+% 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
+% }
+% \DefineShortcut [ prefix = { mf, frak }, style = \mathfrak ]
+% {
+% 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,
+% 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
+% }
+% \DefineShortcut [ prefix = { mc, cal }, style = \mathcal ]
+% {
+% 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
+% }
+% \DefineShortcut [ prefix = { ms, scr }, style = \mathscr ]
+% {
+% 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
+% }
+
+\endinput
+%%
+%% End of file `projlib-math.sty'.
diff --git a/macros/latex/contrib/projlib/new-modules/projlib-paper.sty b/macros/latex/contrib/projlib/new-modules/projlib-paper.sty
new file mode 100644
index 0000000000..5e1f9ac04e
--- /dev/null
+++ b/macros/latex/contrib/projlib/new-modules/projlib-paper.sty
@@ -0,0 +1,108 @@
+\NeedsTeXFormat{LaTeX2e}[2020-10-01]
+\RequirePackage{l3keys2e}
+\ProvidesExplPackage
+ {projlib-paper}
+ {2022/02/25} {}
+ {Paper style configuration}
+
+\keys_define:nn { projlib-paper }
+ {
+ , preview-mode .bool_set:N = \l__projlib_paper_preview_bool
+ , preview-mode .initial:n = { false }
+ , paperstyle .tl_set:N = \l__projlib_paper_paperstyle_tl
+ , paperstyle .initial:n = { normal }
+ , yellowpaper .meta:n = { paperstyle = yellow }
+ , yellow-paper .meta:n = { paperstyle = yellow }
+ , parchmentpaper .meta:n = { paperstyle = parchment }
+ , parchment-paper .meta:n = { paperstyle = parchment }
+ , greenpaper .meta:n = { paperstyle = green }
+ , green-paper .meta:n = { paperstyle = green }
+ , lightgraypaper .meta:n = { paperstyle = lightgray }
+ , lightgray-paper .meta:n = { paperstyle = lightgray }
+ , light-gray-paper .meta:n = { paperstyle = lightgray }
+ , graypaper .meta:n = { paperstyle = gray }
+ , gray-paper .meta:n = { paperstyle = gray }
+ , nordpaper .meta:n = { paperstyle = nord }
+ , nord-paper .meta:n = { paperstyle = nord }
+ , darkpaper .meta:n = { paperstyle = dark }
+ , dark-paper .meta:n = { paperstyle = dark }
+ , unknown .code:n = {}
+ }
+\ProcessKeysOptions { projlib-paper }
+
+\cs_new:Nn \projlib_paper_preview_disable:
+ {
+ \bool_set_false:c { l__projlib_paper_preview_bool }
+ }
+
+\hook_gput_code:nnn { begindocument/before } { projlib-paper }
+ {
+ \bool_if:NT \l__projlib_paper_preview_bool
+ {
+ \dim_set:Nn \paperwidth
+ {
+ \dim_eval:n { \textwidth * 21 / 20 }
+ }
+ \dim_set:Nn \paperheight
+ {
+ \dim_eval:n { ( \textheight + \headheight + \headsep + \footskip ) * 21 / 20 }
+ }
+ \dim_set:Nn \oddsidemargin
+ {
+ \dim_eval:n { ( \paperwidth - \textwidth ) / 2 - 1in }
+ }
+ \dim_set:Nn \evensidemargin
+ {
+ \dim_eval:n { ( \paperwidth - \textwidth ) / 2 - 1in }
+ }
+ \dim_set:Nn \topmargin
+ {
+ \dim_eval:n { ( \paperheight - \textheight - \headheight - \headsep - \footskip ) / 2 - 1in }
+ }
+ }
+ }
+
+\RequirePackage { xcolor }
+
+\definecolor { paper } { RGB } { 255, 255, 255 }
+\definecolor { main-text } { RGB } { 0, 0, 0 }
+
+\str_case_e:nn { \l__projlib_paper_paperstyle_tl }
+ {
+ { yellow } { \definecolor { paper } { RGB } { 247, 246, 233 } }
+ { parchment } { \definecolor { paper } { RGB } { 224, 204, 160 } }
+ { green } { \definecolor { paper } { RGB } { 207, 231, 207 } }
+ { lightgray } { \definecolor { paper } { RGB } { 236, 239, 244 }
+ \definecolor { main-text } { RGB } { 37, 41, 46 } }
+ { light-gray } { \definecolor { paper } { RGB } { 236, 239, 244 }
+ \definecolor { main-text } { RGB } { 37, 41, 46 } }
+ { gray } { \definecolor { paper } { RGB } { 127, 130, 136 }
+ \definecolor { main-text } { RGB } { 236, 239, 244 } }
+ { nord } { \definecolor { paper } { RGB } { 46, 52, 64 }
+ \definecolor { main-text } { RGB } { 214, 220, 230 } }
+ { dark } { \definecolor { paper } { RGB } { 28, 29, 33 }
+ \definecolor { main-text } { RGB } { 210, 213, 217 } }
+ }
+
+\cs_new:Nn \projlib_paper_set_page_color:n
+ {
+ \pagecolor { #1 }
+ }
+\cs_new:Nn \projlib_paper_set_text_color:n
+ {
+ \color { #1 }
+ \cs_gset_eq:cc { default@color } { current@color }
+ }
+
+\projlib_paper_set_page_color:n { paper }
+\projlib_paper_set_text_color:n { main-text }
+
+\hook_gput_code:nnn { begindocument/before } { projlib-paper }
+ {
+ \cs_if_exist:NT \tcbset
+ { \tcbset { colupper = main-text, collower = main-text } }
+ }
+
+\endinput
+%%
+%% End of file `projlib-paper.sty'.
diff --git a/macros/luatex/generic/luaotfload/NEWS b/macros/luatex/generic/luaotfload/NEWS
index 44b749c352..3945ef2f89 100644
--- a/macros/luatex/generic/luaotfload/NEWS
+++ b/macros/luatex/generic/luaotfload/NEWS
@@ -1,5 +1,11 @@
Change History
--------------
+2022-02-27 luaotfload v3.20
+
+ * A bug in luaotfload-tool has been corrected.
+ * The directory for the font name database has been corrected and no
+ longer uses the development directory.
+
2022-02-24 luaotfload v3.19
* When used with LuaTeX 1.15.0 or newer, variable fonts are supported
diff --git a/macros/luatex/generic/luaotfload/README.md b/macros/luatex/generic/luaotfload/README.md
index 565733e6c5..ff3302131f 100644
--- a/macros/luatex/generic/luaotfload/README.md
+++ b/macros/luatex/generic/luaotfload/README.md
@@ -1,8 +1,8 @@
# The Luaotfload Package
-VERSION: 3.19
+VERSION: 3.20
-DATE: 2022-02-24
+DATE: 2022-02-27
## Description
diff --git a/macros/luatex/generic/luaotfload/filegraph.pdf b/macros/luatex/generic/luaotfload/filegraph.pdf
index b455173c6b..d18d43f57d 100644
--- a/macros/luatex/generic/luaotfload/filegraph.pdf
+++ b/macros/luatex/generic/luaotfload/filegraph.pdf
Binary files differ
diff --git a/macros/luatex/generic/luaotfload/fontloader-basics-gen.lua b/macros/luatex/generic/luaotfload/fontloader-basics-gen.lua
index 1cc8537168..5a6e90ceee 100644
--- a/macros/luatex/generic/luaotfload/fontloader-basics-gen.lua
+++ b/macros/luatex/generic/luaotfload/fontloader-basics-gen.lua
@@ -189,7 +189,7 @@ local readables = { }
local usingjit = jit
if not caches.namespace or caches.namespace == "" or caches.namespace == "context" then
- caches.namespace = 'generic-dev'
+ caches.namespace = 'generic'
end
do
diff --git a/macros/luatex/generic/luaotfload/luaotfload-auxiliary.lua b/macros/luatex/generic/luaotfload/luaotfload-auxiliary.lua
index 86aff98fb4..379ad5bb78 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-auxiliary.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-auxiliary.lua
@@ -6,8 +6,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-auxiliary",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / auxiliary functions",
license = "GPL v2.0"
}
diff --git a/macros/luatex/generic/luaotfload/luaotfload-colors.lua b/macros/luatex/generic/luaotfload/luaotfload-colors.lua
index e0bd759de5..8371de7222 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-colors.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-colors.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-colors",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / color",
license = "GPL v2.0",
author = "Khaled Hosny, Elie Roux, Philipp Gesang, Dohyun Kim, David Carlisle",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-conf.pdf b/macros/luatex/generic/luaotfload/luaotfload-conf.pdf
index 9e3b6c6557..4abde8cbb9 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-conf.pdf
+++ b/macros/luatex/generic/luaotfload/luaotfload-conf.pdf
Binary files differ
diff --git a/macros/luatex/generic/luaotfload/luaotfload-configuration.lua b/macros/luatex/generic/luaotfload/luaotfload-configuration.lua
index 50618d72af..b970ce353e 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-configuration.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-configuration.lua
@@ -7,8 +7,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-configuration",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / config file reader",
license = "GPL v2.0"
}
diff --git a/macros/luatex/generic/luaotfload/luaotfload-database.lua b/macros/luatex/generic/luaotfload/luaotfload-database.lua
index 8f0ef41fc7..8d981a3f2c 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-database.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-database.lua
@@ -5,8 +5,8 @@
do -- block to avoid to many local variables error
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-database",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / database",
license = "GPL v2.0",
author = "Khaled Hosny, Elie Roux, Philipp Gesang, Marcel Krüger",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-diagnostics.lua b/macros/luatex/generic/luaotfload/luaotfload-diagnostics.lua
index 622714d14b..230384f981 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-diagnostics.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-diagnostics.lua
@@ -7,8 +7,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-diagnostics",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload-tool submodule / diagnostics",
license = "GPL v2.0"
}
diff --git a/macros/luatex/generic/luaotfload/luaotfload-dvi.lua b/macros/luatex/generic/luaotfload/luaotfload-dvi.lua
index 2f8abefa93..75f6564d42 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-dvi.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-dvi.lua
@@ -6,8 +6,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-dvi",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / DVI",
license = "GPL v2.0",
author = "Marcel Krüger",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-embolden.lua b/macros/luatex/generic/luaotfload/luaotfload-embolden.lua
index d0a2e60555..bca6eb45d8 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-embolden.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-embolden.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-embolden",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / embolden",
license = "GPL v2.0",
author = "Marcel Krüger"
diff --git a/macros/luatex/generic/luaotfload/luaotfload-fallback.lua b/macros/luatex/generic/luaotfload/luaotfload-fallback.lua
index a3f28e77a4..b45f454576 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-fallback.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-fallback.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-fallback",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / fallback",
license = "GPL v2.0",
author = "Marcel Krüger"
diff --git a/macros/luatex/generic/luaotfload/luaotfload-features.lua b/macros/luatex/generic/luaotfload/luaotfload-features.lua
index eecf1aa0f5..d297061a7d 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-features.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-features.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-features",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / features",
license = "GPL v2.0",
author = "Hans Hagen, Khaled Hosny, Elie Roux, Philipp Gesang, Marcel Krüger",
@@ -843,7 +843,10 @@ local normalize_lookup = setmetatable({}, {__index = function(t, f)
t[fontdir] = result
return result
end})
-local normalize_funcs = {
+-- When this is loaded as part of luaotfload-tool, then we can't access nodes
+-- and therefore uni_normalize doesn't exists. In that case we don't need it
+-- anyway, so just skip it then.
+local normalize_funcs = uni_normalize and {
nfc = uni_normalize.NFC,
nfd = uni_normalize.NFD,
nfkd = uni_normalize.NFKD,
diff --git a/macros/luatex/generic/luaotfload/luaotfload-filelist.lua b/macros/luatex/generic/luaotfload/luaotfload-filelist.lua
index abd6458db9..d0b1530830 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-filelist.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-filelist.lua
@@ -6,8 +6,8 @@
if luaotfload_module then luaotfload_module {
name = "luaotfload-filelist",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / filelist",
license = "GPL v2.0"
} end
diff --git a/macros/luatex/generic/luaotfload/luaotfload-glyphlist.lua b/macros/luatex/generic/luaotfload/luaotfload-glyphlist.lua
index ce3cc314b4..f291b2d60e 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-glyphlist.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-glyphlist.lua
@@ -1,8 +1,8 @@
if luaotfload_module then
luaotfload_module {
name = "luaotfload-glyphlist",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / autogenerated glyphlist",
copyright = "derived from https://raw.githubusercontent.com/adobe-type-tools/agl-aglfn/master/glyphlist.txt",
original = "Adobe Glyph List, version 2.0, September 20, 2002",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-harf-define.lua b/macros/luatex/generic/luaotfload/luaotfload-harf-define.lua
index d7ee7c11bb..add95cdf68 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-harf-define.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-harf-define.lua
@@ -5,8 +5,8 @@
do -- block to avoid to many local variables error
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-harf-define",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / HarfBuzz font loading",
license = "GPL v2.0",
author = "Khaled Hosny, Marcel Krüger",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-harf-plug.lua b/macros/luatex/generic/luaotfload/luaotfload-harf-plug.lua
index 634da68786..222c4334e8 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-harf-plug.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-harf-plug.lua
@@ -4,8 +4,8 @@
-----------------------------------------------------------------------
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-harf-plug",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / HarfBuzz shaping",
license = "GPL v2.0",
author = "Khaled Hosny, Marcel Krüger",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-harf-var-cff2.lua b/macros/luatex/generic/luaotfload/luaotfload-harf-var-cff2.lua
index 9c05b7a6c7..38fa6b57d9 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-harf-var-cff2.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-harf-var-cff2.lua
@@ -5,8 +5,8 @@
do
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-harf-var-cff2",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / CFF2 table processing",
license = "GPL v2.0",
author = "Marcel Krüger",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-harf-var-t2-writer.lua b/macros/luatex/generic/luaotfload/luaotfload-harf-var-t2-writer.lua
index f10bf6d860..5964b61865 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-harf-var-t2-writer.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-harf-var-t2-writer.lua
@@ -5,8 +5,8 @@
do
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-harf-var-t2-writer",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / Type 2 charstring writer",
license = "GPL v2.0",
author = "Marcel Krüger",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-harf-var-ttf.lua b/macros/luatex/generic/luaotfload/luaotfload-harf-var-ttf.lua
index 80cf6768ae..41fb412102 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-harf-var-ttf.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-harf-var-ttf.lua
@@ -5,8 +5,8 @@
do
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-harf-var-ttf",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / gvar table processing",
license = "GPL v2.0",
author = "Marcel Krüger",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-init.lua b/macros/luatex/generic/luaotfload/luaotfload-init.lua
index 73a6544e72..3d00816e1a 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-init.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-init.lua
@@ -7,8 +7,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-init",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / initialization",
license = "GPL v2.0"
}
diff --git a/macros/luatex/generic/luaotfload/luaotfload-latex.pdf b/macros/luatex/generic/luaotfload/luaotfload-latex.pdf
index b87b90e0c8..61e35f01a4 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-latex.pdf
+++ b/macros/luatex/generic/luaotfload/luaotfload-latex.pdf
Binary files differ
diff --git a/macros/luatex/generic/luaotfload/luaotfload-letterspace.lua b/macros/luatex/generic/luaotfload/luaotfload-letterspace.lua
index 2e69cb92a6..bb5a4a5e70 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-letterspace.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-letterspace.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-letterspace",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / letterspacing",
license = "GPL v2.0",
copyright = "PRAGMA ADE / ConTeXt Development Team",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-loaders.lua b/macros/luatex/generic/luaotfload/luaotfload-loaders.lua
index 5a84113a1f..ca3730f00d 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-loaders.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-loaders.lua
@@ -8,8 +8,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-loaders",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / callback handling",
license = "GPL v2.0"
}
diff --git a/macros/luatex/generic/luaotfload/luaotfload-log.lua b/macros/luatex/generic/luaotfload/luaotfload-log.lua
index 41f9f4b877..c4ecd75075 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-log.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-log.lua
@@ -6,8 +6,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-log",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / logging",
license = "GPL v2.0",
author = "Khaled Hosny, Elie Roux, Philipp Gesang",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-main.tex b/macros/luatex/generic/luaotfload/luaotfload-main.tex
index cdf5590057..d48481a744 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-main.tex
+++ b/macros/luatex/generic/luaotfload/luaotfload-main.tex
@@ -32,7 +32,7 @@
\beginfrontmatter
\setdocumenttitle {The \identifier{luaotfload} package}
- \setdocumentdate {2022-02-24 v3.19}
+ \setdocumentdate {2022-02-27 v3.20}
\setdocumentauthor {LaTeX3 Project\\
Elie Roux · Khaled Hosny · Philipp Gesang · Ulrike Fischer · Marcel Krüger\\
Home: \hyperlink {https://github.com/latex3/luaotfload}}
@@ -62,6 +62,14 @@ Supported is the \identifier{luatex} versions of a current TeXLive 2019 (and a c
\endsection
\beginsection{Changes}
+\beginsubsection {New in version 3.20 (by Ulrike Fischer/Marcel Krüger)}
+\begin{itemize}
+ \item A bug in luaotfload-tool has been corrected.
+ \item The directory for the font name database has been corrected and no
+ longer uses the development directory.
+\end{itemize}
+\endsubsection
+
\beginsubsection {New in version 3.19 (by Ulrike Fischer/Marcel Krüger)}
\begin{itemize}
\item When used with Lua\TeX\ 1.15.0 or newer, variable fonts are supported
diff --git a/macros/luatex/generic/luaotfload/luaotfload-multiscript.lua b/macros/luatex/generic/luaotfload/luaotfload-multiscript.lua
index ab688fe704..9b3b3fd836 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-multiscript.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-multiscript.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-multiscript",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / multiscript",
license = "GPL v2.0",
author = "Marcel Krüger"
diff --git a/macros/luatex/generic/luaotfload/luaotfload-notdef.lua b/macros/luatex/generic/luaotfload/luaotfload-notdef.lua
index 28a371dd35..e32521daea 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-notdef.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-notdef.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-notdef",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / notdef",
license = "GPL v2.0",
author = "Marcel Krüger"
diff --git a/macros/luatex/generic/luaotfload/luaotfload-parsers.lua b/macros/luatex/generic/luaotfload/luaotfload-parsers.lua
index 099363fad5..da41fa8b30 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-parsers.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-parsers.lua
@@ -7,8 +7,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-parsers",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / parsers",
license = "GPL v2.0"
}
diff --git a/macros/luatex/generic/luaotfload/luaotfload-resolvers.lua b/macros/luatex/generic/luaotfload/luaotfload-resolvers.lua
index a91216b79d..5d3f456739 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-resolvers.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-resolvers.lua
@@ -11,8 +11,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-resolvers",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / resolvers",
license = "GPL v2.0"
}
diff --git a/macros/luatex/generic/luaotfload/luaotfload-scripts.lua b/macros/luatex/generic/luaotfload/luaotfload-scripts.lua
index c3c918ef3b..2accacfd64 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-scripts.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-scripts.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-script",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / Script helpers",
license = "CC0 1.0 Universal",
author = "Marcel Krüger"
diff --git a/macros/luatex/generic/luaotfload/luaotfload-status.lua b/macros/luatex/generic/luaotfload/luaotfload-status.lua
index d722bfe03b..02286a891b 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-status.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-status.lua
@@ -54,46 +54,46 @@ return {
{ "fontloader-font-ini.lua", "5343de60613adb24cdd7a8d2ee1d153c" },
{ "fontloader-data-con.lua", "23a0f5386d46a1c157ae82a373e35fa0" },
{ "fontloader-reference.lua", "066680a4fe38fad14eb27f56d4616104" },
- { "fontloader-basics-gen.lua", "e470c13b1ed41e38932e93518219ad74" },
+ { "fontloader-basics-gen.lua", "4bc43036e9796fccb0b8869f4b05bf2e" },
{ "mktests", "918cb50be9ee8bd645ac1a27dc501e8c" },
- { "mkstatus", "3a71ea95e4daeac77ead843edb0ce283" },
+ { "mkstatus", "a49a287defff8b8241faedb7c6a45621" },
{ "mkcharacters", "bfca0e96e1e351afe7077502f9595af9" },
{ "mkglyphlist", "7a8a8fdc0eac7fac2f13ec8f4e8e7aa9" },
{ "mkimport", "7d62f1042e548f8b9368f26b9ba4be5c" },
- { "luaotfload-glyphlist.lua", "dc0ed15ca157aec56a509819d4c24046" },
+ { "luaotfload-glyphlist.lua", "37275559314b6ced2c01f0d0e58fd68f" },
{ "luaotfload-characters.lua", "76415ca3ede312cad6e09038323489ae" },
- { "luaotfload-dvi.lua", "f4e4d6cc443f477820521abcd87ef3f8" },
- { "luaotfload-tounicode.lua", "79795ade0ccdb99cd5e6cf989770db69" },
- { "luaotfload-unicode.lua", "68904dd143491dc0a4a1c0a063a118f9" },
- { "luaotfload-resolvers.lua", "1e844bc5d9b3d207f8914a11254afd67" },
- { "luaotfload-parsers.lua", "b0c087c6dbf75939d7be9523b7a0ca34" },
- { "luaotfload-fallback.lua", "88ed544bc0429f94606f72dace5704f0" },
- { "luaotfload-szss.lua", "7266e9d2122fb0785b595f9605627640" },
- { "luaotfload-scripts.lua", "c6c11811531c7e42194304d65bed7877" },
- { "luaotfload-multiscript.lua", "f9d7bedf75629d83f8d99397d15fdf86" },
- { "luaotfload-loaders.lua", "b2ef32297527eec59131de3b56f4d846" },
- { "luaotfload-harf-plug.lua", "7f3739c1b32de1f8e76275c317f5ed9a" },
- { "luaotfload-harf-define.lua", "1c2ce18281caede23d93c8b825df7239" },
- { "luaotfload-notdef.lua", "8b448aed817a764b675d1ead6bf932a7" },
- { "luaotfload-embolden.lua", "00ae296b36724c4f8dd4da62051039b2" },
- { "luaotfload-letterspace.lua", "f98fbda3ea174058b515b9234dfba495" },
- { "luaotfload-features.lua", "5c0d7d66953c7ecdd2a610d758d70f0d" },
- { "luaotfload-database.lua", "2ac2d6e5960257a7c5688c931bb91924" },
- { "luaotfload-configuration.lua", "05e6c5032b9ac6f05230d06c866161d6" },
- { "luaotfload-colors.lua", "3e1cd496c920d0e8296a9e99357f27eb" },
- { "luaotfload-auxiliary.lua", "32c6b48ab66a2ddf2a7d3063529e85f8" },
- { "luaotfload-filelist.lua", "b80ed418d9d297370182e9fcc9e497e5" },
- { "luaotfload-tool.lua", "2a6a1c510e44c4432724657259b12cbf" },
- { "luaotfload-diagnostics.lua", "e831068d26e0266cd081de8585c3cbc9" },
- { "luaotfload-log.lua", "889508edccd01035ecbeafdeeff60e35" },
- { "luaotfload-init.lua", "56ac12fac2e63d1a901463850062dd58" },
+ { "luaotfload-dvi.lua", "17763f980ea431ef33578799e36121bd" },
+ { "luaotfload-tounicode.lua", "861919847a52fcbe4e625c8cda7826a2" },
+ { "luaotfload-unicode.lua", "5c90a8c752518c7eae0aa21f5d4e6cb5" },
+ { "luaotfload-resolvers.lua", "a3a547c332dcc51885b6611debcecb53" },
+ { "luaotfload-parsers.lua", "90411c0301b37730abb40a8b49507c8f" },
+ { "luaotfload-fallback.lua", "febc05a8a5701b4e9b73a9a24e994f33" },
+ { "luaotfload-szss.lua", "256bc3115f09638b0acdf4f59b06e4d1" },
+ { "luaotfload-scripts.lua", "9356ba71cf8a174e8e592213aa680297" },
+ { "luaotfload-multiscript.lua", "8f77beea00a1319d53e10ce95f6cc561" },
+ { "luaotfload-loaders.lua", "b0b23c070c570d81a32913d2afc1bb2a" },
+ { "luaotfload-harf-plug.lua", "098a6a6f9c17d2a99a76ce890e8c9dc6" },
+ { "luaotfload-harf-define.lua", "0d8692db5d29664eb32e6c5fb25b049f" },
+ { "luaotfload-notdef.lua", "c323ab19ea5e81c5c9c4bbbc6d62839a" },
+ { "luaotfload-embolden.lua", "50aa1bdac170cdb287345b9532520cbf" },
+ { "luaotfload-letterspace.lua", "79d2efd35a43d1bfba84ce7c5a211680" },
+ { "luaotfload-features.lua", "2348361d796bde017da72421a0e68ce6" },
+ { "luaotfload-database.lua", "d37c8efaed4dbe9bb566cbd8e9ba9216" },
+ { "luaotfload-configuration.lua", "cfdee94e34f3294b8ef627086ac464a3" },
+ { "luaotfload-colors.lua", "01070f297400b9d0bb94a321b16a3cc6" },
+ { "luaotfload-auxiliary.lua", "2a69d5f008d6c79b703ea6bd88f545f3" },
+ { "luaotfload-filelist.lua", "261c4306787e506da0ca5c15b09913f5" },
+ { "luaotfload-tool.lua", "252bdfdf017c7b2a1e1a92d129de05c2" },
+ { "luaotfload-diagnostics.lua", "146886b07d0f4f57b82ed77407d32066" },
+ { "luaotfload-log.lua", "7856e6afce049f6196ef94b2d2057ff6" },
+ { "luaotfload-init.lua", "3c3b19cd2f8bbbd8bdbc99c50535bff7" },
{ "luaotfload-main.lua", "b19c9cc34cf1d676c39f872cfb41aef6" },
},
["notes"]={
["committer"]="Marcel Fabian Krüger <tex@2krueger.de>",
- ["description"]="v3.19/2022-02-24",
+ ["description"]="v3.20/2022-02-27",
["loader"]="fontloader-2022-02-24.lua",
- ["revision"]="ef3a55e0da3709e95551c62376f31149771dcc18",
- ["timestamp"]="2022-02-24 14:16:28 +0100",
+ ["revision"]="b6b01d231706a79c8456c4c890d62a65897b49e1",
+ ["timestamp"]="2022-02-27 14:15:30 +0100",
},
} \ No newline at end of file
diff --git a/macros/luatex/generic/luaotfload/luaotfload-suppress.lua b/macros/luatex/generic/luaotfload/luaotfload-suppress.lua
index e0f3989c22..b0ba8dccc1 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-suppress.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-suppress.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-suppress",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / suppress",
license = "GPL v2.0",
author = "Marcel Krüger"
diff --git a/macros/luatex/generic/luaotfload/luaotfload-szss.lua b/macros/luatex/generic/luaotfload/luaotfload-szss.lua
index dcd8eacf65..6786226257 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-szss.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-szss.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-szss",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / Special feature handling",
license = "GPL v2.0",
author = "Marcel Krüger"
diff --git a/macros/luatex/generic/luaotfload/luaotfload-t3.lua b/macros/luatex/generic/luaotfload/luaotfload-t3.lua
deleted file mode 100644
index 2588346724..0000000000
--- a/macros/luatex/generic/luaotfload/luaotfload-t3.lua
+++ /dev/null
@@ -1,142 +0,0 @@
------------------------------------------------------------------------
--- FILE: luaotfload-t3.lua
--- DESCRIPTION: part of luaotfload / Type 3
------------------------------------------------------------------------
-do -- block to avoid to many local variables error
- assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
- name = "luaotfload-t3",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
- description = "luaotfload submodule / Type 3 font handling",
- license = "GPL v2.0",
- author = "Marcel Krüger",
- copyright = "Luaotfload Development Team",
- }
-end
-
---[[
-local function u8_to_utf16be(s)
- local d = {}
- local i = 0
- for _, cp, cp2 in utf8.codes(s) do
- i = i + 1
- if cp > 0x10000 then
- cp = cp - 0x10000
- local high = (cp>>10) | 0xD800
- local low = (cp&0x3FF) | 0xDC00
- d[i] = string.format('%04X%04X', high, low)
- else
- d[i] = string.format('%04X', cp)
- end
- end
- return table.concat(d, '', 1, i)
-end
-]]
-
-local function t3factory(basename, designsize, callback)
- local count = 0
- local gid_to_t3 = {}
- local t3_to_gid = {}
- return function(size)
- local fids = {}
- return function(gid, width, unicode)
- local t3id = gid_to_t3[gid]
- if not t3id then
- t3id = count
- count = count + 1
- gid_to_t3[gid] = t3id
- t3_to_gid[t3id] = gid
- end
- local t3fid, t3cid = t3id >> 8, ~(t3id & 0xFF)
- local fid = fids[t3fid + 1]
- local fontdir = fid and font.getfont(fid)
- local characters = fontdir and fontdir.characters
- if not (characters and characters[t3cid]) then
- characters = characters or {}
- -- First create the character
- local char = {
- width = width,
- height = 0, -- We never look at these two anyway
- depth = 0,
- tounicode = unicode,
- }
- characters[t3cid] = char
- if fid then -- Font already exists
- font.addcharacters(fid, {
- characters = {
- [t3cid] = char,
- },
- })
- else
- fontdir = {
- name = basename .. '_' .. basesize .. '_' .. t3fid,
- format = 'type3',
- psname = 'none',
- filename = 'not_a_real_filename', -- Can't be null to ensure reuse
- fullname = basename .. basesize .. '_' .. t3fid,
- characters = characters,
- designsize = basesize,
- size = size,
- cidinfo = {}, -- Can't be null to ensure reuse
- attributes = '/FontDescriptor<<\z
- /Type/FontDescriptor\z
- /FontName/VirtualLuaFont\z
- /Flags 4\z
- /ItalicAngle 0\z
- >>', -- TODO: Needs work
- t3_handler = function(stage, _, cid)
- cid = cid and assert(t3_to_gid[(t3fid << 8) | ~cid])
- return callback(stage, cid)
- end
- }
- fid = font.define(fontdir)
- fids[t3fid + 1] = fid
- end
- end
- vf.font(fid)
- vf.char(t3cid)
- end
- end
-end
-
-luatexbase.add_to_callback('provide_charproc_data', function(stage, fid, ...)
- local fontdir = font.getfont(fid)
- if not fontdir then error'Unknown font' end
- local handler = fontdir.t3_handler
- if not handler then error'Missing t3handler in Type 3 font' end
- return handler(stage, fid, ...)
-end, 'luaotfload.t3')
-
-return t3factory
---[===[
- function callback(stage, cid)
- if stage == 1 then
- local char = assert(characters[cid])
- local head = assert(char.head)
- local resource = tex.saveboxresource(head, nil--[[attributes]], nil--[[resources]], true--[[immediate]], 0--[[mode]], nil--[[margin]])
- local name = string.format('/F%i', pdf.getxformname(resource))
- local resources = characters.resources
- if resources then
- characters.resources = string.format('%s %s %i 0 R', resources, name, resource)
- else
- characters.resources = string.format('%s %i 0 R', name, resource)
- end
- char.resource = name
- elseif stage == 2 then
- local char = assert(characters[cid])
- width = char.width/65781
- return pdf.obj{
- type = 'stream',
- immediate = true,
- -- objnum =
- -- attr =
- -- compresslevel =
- string = string.format('%g 0 d0 %s Do', width, char.resource)
- }, width
- elseif stage == 3 then
- return 65781/655360, string.format('/XObject<<%s>>', characters.resources)
- end
- print(stage, fid, cid)
- end,
- }
-]===]
diff --git a/macros/luatex/generic/luaotfload/luaotfload-tool.1 b/macros/luatex/generic/luaotfload/luaotfload-tool.1
index 3f2782eb18..d0f56801c6 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-tool.1
+++ b/macros/luatex/generic/luaotfload/luaotfload-tool.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH LUAOTFLOAD-TOOL 1 "2022-02-24" "3.19" "text processing"
+.TH LUAOTFLOAD-TOOL 1 "2022-02-27" "3.20" "text processing"
.SH NAME
luaotfload-tool \- generate and query the Luaotfload font names database
.
diff --git a/macros/luatex/generic/luaotfload/luaotfload-tool.lua b/macros/luatex/generic/luaotfload/luaotfload-tool.lua
index 808afe0313..6be778a69a 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-tool.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-tool.lua
@@ -9,8 +9,8 @@
local ProvidesLuaModule = {
name = "luaotfload-tool",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload-tool / database functionality",
license = "GPL v2.0"
}
diff --git a/macros/luatex/generic/luaotfload/luaotfload-tool.pdf b/macros/luatex/generic/luaotfload/luaotfload-tool.pdf
index a218f77367..4862fd03f1 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-tool.pdf
+++ b/macros/luatex/generic/luaotfload/luaotfload-tool.pdf
Binary files differ
diff --git a/macros/luatex/generic/luaotfload/luaotfload-tool.rst b/macros/luatex/generic/luaotfload/luaotfload-tool.rst
index 1eddd510f1..d407a5fba5 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-tool.rst
+++ b/macros/luatex/generic/luaotfload/luaotfload-tool.rst
@@ -6,9 +6,9 @@
generate and query the Luaotfload font names database
-----------------------------------------------------------------------
-:Date: 2022-02-24
+:Date: 2022-02-27
:Copyright: GPL v2.0
-:Version: 3.19
+:Version: 3.20
:Manual section: 1
:Manual group: text processing
diff --git a/macros/luatex/generic/luaotfload/luaotfload-tounicode.lua b/macros/luatex/generic/luaotfload/luaotfload-tounicode.lua
index 09ca40adbe..9ef921ffd0 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-tounicode.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-tounicode.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-tounicode",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / tounicode",
license = "GPL v2.0",
author = "Hans Hagen, Khaled Hosny, Elie Roux, Philipp Gesang, Marcel Krüger",
diff --git a/macros/luatex/generic/luaotfload/luaotfload-unicode.lua b/macros/luatex/generic/luaotfload/luaotfload-unicode.lua
index 25a7b2046e..d63d16989c 100644
--- a/macros/luatex/generic/luaotfload/luaotfload-unicode.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload-unicode.lua
@@ -5,8 +5,8 @@
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-unicode",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "luaotfload submodule / Unicode helpers",
license = "CC0 1.0 Universal",
author = "Marcel Krüger"
diff --git a/macros/luatex/generic/luaotfload/luaotfload.conf.5 b/macros/luatex/generic/luaotfload/luaotfload.conf.5
index f7d8aa8ae4..272a3abd54 100644
--- a/macros/luatex/generic/luaotfload/luaotfload.conf.5
+++ b/macros/luatex/generic/luaotfload/luaotfload.conf.5
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH LUAOTFLOAD.CONF 5 "2022-02-24" "3.19" "text processing"
+.TH LUAOTFLOAD.CONF 5 "2022-02-27" "3.20" "text processing"
.SH NAME
luaotfload.conf \- Luaotfload configuration file
.
diff --git a/macros/luatex/generic/luaotfload/luaotfload.conf.rst b/macros/luatex/generic/luaotfload/luaotfload.conf.rst
index 4e0347b907..c40088b5b5 100644
--- a/macros/luatex/generic/luaotfload/luaotfload.conf.rst
+++ b/macros/luatex/generic/luaotfload/luaotfload.conf.rst
@@ -6,9 +6,9 @@
Luaotfload configuration file
-----------------------------------------------------------------------
-:Date: 2022-02-24
+:Date: 2022-02-27
:Copyright: GPL v2.0
-:Version: 3.19
+:Version: 3.20
:Manual section: 5
:Manual group: text processing
diff --git a/macros/luatex/generic/luaotfload/luaotfload.lua b/macros/luatex/generic/luaotfload/luaotfload.lua
index f21ded5ae6..20eaad3eff 100644
--- a/macros/luatex/generic/luaotfload/luaotfload.lua
+++ b/macros/luatex/generic/luaotfload/luaotfload.lua
@@ -20,8 +20,8 @@ local authors = "\z
-- version number is used below!
local ProvidesLuaModule = {
name = "luaotfload",
- version = "3.19", --TAGVERSION
- date = "2022-02-24", --TAGDATE
+ version = "3.20", --TAGVERSION
+ date = "2022-02-27", --TAGDATE
description = "Lua based OpenType font support",
author = authors,
copyright = authors,
diff --git a/macros/luatex/generic/luaotfload/luaotfload.sty b/macros/luatex/generic/luaotfload/luaotfload.sty
index 7dbccb1f8c..2426c2e0f5 100644
--- a/macros/luatex/generic/luaotfload/luaotfload.sty
+++ b/macros/luatex/generic/luaotfload/luaotfload.sty
@@ -39,7 +39,7 @@
\fi
\ifdefined\ProvidesPackage
\ProvidesPackage{luaotfload}%
- [2022/02/24 v3.19 OpenType layout system]
+ [2022/02/27 v3.20 OpenType layout system]
\fi
\directlua{
if xpcall(function ()%
diff --git a/macros/luatex/generic/luaotfload/scripts-demo.pdf b/macros/luatex/generic/luaotfload/scripts-demo.pdf
index e119a1ef16..1f04d9d7bc 100644
--- a/macros/luatex/generic/luaotfload/scripts-demo.pdf
+++ b/macros/luatex/generic/luaotfload/scripts-demo.pdf
Binary files differ
diff --git a/macros/luatex/generic/luaotfload/shaper-demo-graphite.pdf b/macros/luatex/generic/luaotfload/shaper-demo-graphite.pdf
index ded892e99b..ce126ad4b1 100644
--- a/macros/luatex/generic/luaotfload/shaper-demo-graphite.pdf
+++ b/macros/luatex/generic/luaotfload/shaper-demo-graphite.pdf
Binary files differ
diff --git a/macros/luatex/generic/luaotfload/shaper-demo.pdf b/macros/luatex/generic/luaotfload/shaper-demo.pdf
index d889e43e79..18a3c1b69f 100644
--- a/macros/luatex/generic/luaotfload/shaper-demo.pdf
+++ b/macros/luatex/generic/luaotfload/shaper-demo.pdf
Binary files differ
diff --git a/macros/luatex/generic/lutabulartools/README.md b/macros/luatex/generic/lutabulartools/README.md
index 90b5a86b15..ba443802b8 100644
--- a/macros/luatex/generic/lutabulartools/README.md
+++ b/macros/luatex/generic/lutabulartools/README.md
@@ -1,12 +1,13 @@
# lutabulartools
-This LuaLaTeX package offers some useful macros for tabular matter. The `\MC` command it provides allows one to specify a multi-row and or multi-column cell with minimal syntax. The package loads `booktabs` and adds a gray midrule, and allows a user to specify multiple `\cmidrules` with one command.
+This LuaLaTeX package offers some useful macros for tabular matter. The `\MC` command it provides allows one to specify a multi-row and or multi-column cell with minimal syntax. The package loads `booktabs` and adds a gray midrule, and allows a user to specify multiple `\cmidrules` with one command. The [github repo](https://github.com/kalekje/lutabulartools) has an MS Excel
+macro-enabled spreadsheet named "LaTeX-Tabular-Generator.xlsm" to help make tables in LaTeX.
# License (MIT)
-Copyright (C) 2021 Kale Ewasiuk
+Copyright (C) 2021-2022 Kale Ewasiuk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/macros/luatex/generic/lutabulartools/lutabulartools.lua b/macros/luatex/generic/lutabulartools/lutabulartools.lua
index ca6b1aa0b4..30165f89a9 100644
--- a/macros/luatex/generic/lutabulartools/lutabulartools.lua
+++ b/macros/luatex/generic/lutabulartools/lutabulartools.lua
@@ -1,6 +1,6 @@
--% Kale Ewasiuk (kalekje@gmail.com)
---% 2021-11-07
---% Copyright (C) 2021 Kale Ewasiuk
+--% 2022-02-27
+--% Copyright (C) 2021-2022 Kale Ewasiuk
--%
--% Permission is hereby granted, free of charge, to any person obtaining a copy
--% of this software and associated documentation files (the "Software"), to deal
@@ -24,31 +24,19 @@
local ltt = {}
---todo clean up the code, make some stuff local
---todo maybe add the penlight lua table to latex tabular?
+ltt.col_spec1 = {} -- column spec if one column wide (since makcell nests a tabular, preserve col_spec below)
+ltt.col_spec = {} -- tab column spec if above 1
+ltt.col = '' -- current column spec, single char, only applies to tabular with more than 1 column
+ltt.col_num = 1 -- current column number
---integrate current cell
---todo check if S[] column cells work!!!
---todo might be smarter to just append this to tabular commands and read argument
--- Initialize counter variable
-ltt.NumTabCols = 0
-ltt.NumTabColsAbv = 0 -- num of tab columns if above 1
-ltt.TabColSpec = ''
-ltt.TabColSpecAbv = '' -- tab column spec if above 1
-ltt.CurTabColAbv = '' -- current tab column spec if above 1
-ltt.TabColNum = 0
-ltt.NumBkts = 0
-ltt.NumTabColsMX = 0
-
-
---todo re=enable
local glue_t, unset_t, tabskip_st = node.id'glue', node.id'unset'
local tabskip_st = table.swapped(node.subtypes'glue').tabskip
assert(tabskip_st)
-function ltt.get_TabColNum()
+function ltt.set_col_num()
+ -- register current column info (column number and specification)
local nest
for i = tex.nest.ptr, 1, -1 do
local tail = tex.nest[i].tail
@@ -62,57 +50,32 @@ function ltt.get_TabColNum()
for _, sub in node.traverse_id(unset_t, nest.head) do
col = col + sub + 1
end
- ltt.TabColNum = col
+ ltt.col_num = col
else
- ltt.TabColNum = 1
+ ltt.col_num = 1
end
- ltt.CurTabColAbv = ltt.TabColSpecAbv:sub(ltt.TabColNum,ltt.TabColNum)
- ltt.NumTabColsMX = math.max(ltt.NumTabColsMX, ltt.TabColNum) -- a different way to calculate total cols
- return ltt.TabColNum
+ ltt.col = ltt.col_spec[ltt.col_num]
end
-function ltt.get_TabRowNum()
- return tex.count['c@RowNumCnt'] -- access latex counter
-end
-
--- The next function computes the number of columns from
--- contents of string 'zz'
-function ltt.calc_NumTabCols( zz )
- --help_wrt(zz, 'col spec OG')
- -- -- NOT NEEDED: zz = zz:gsub ( "^.-(%b{}).-$", "%1" ) -- retain the first "{....}" substring
+function ltt.set_col_spec(zz)
+ -- contents of string 'zz'
+ -- register the table column specification
zz = zz:gsub ( "%*%s-{(%d-)}%s-(%b{})" , -- expand expressions such as "*{5}{l}" to "lllll"
function(y, z ) z = z:sub (2 , -2) return string.rep (z, y) end ) --
zz = zz:gsub ( "%b{}" , "" ) -- omit all stuff in curly braces and square
zz = zz:gsub ( "%b[]" , "" )
zz = zz:gsub ( "[@!|><%s%*\']" , "" ) -- some more characters to ignore
- -- todo: any columns defined that break into more than one column should be expanded here
- --help_wrt(zz, 'col spec CLEAN')
- ltt.TabColSpec = zz -- stripped column spec with no {} or <
- ltt.NumTabCols = string.len(ltt.TabColSpec)
- if ltt.NumTabCols > 1 then
- ltt.TabColSpecAbv = ltt.TabColSpec
- ltt.NumTabColsAbv = ltt.NumTabCols
+ zz = zz:gsub('%a', ltt.col_replaces) -- sub extra column
+ _col_spec = zz:totable() -- requires pl extras
+ --help_wrt(_col_spec, 'helpme')
+ if #_col_spec > 1 then
+ ltt.col_spec = _col_spec
+ else
+ ltt.col_spec1 = _col_spec
end
end
---todo fix square brackets after letters, expand multiple
---calc_NumTabCols('ss')
---calc_NumTabCols('*{6}{s}')
---calc_NumTabCols('l*{6}{l}')
---calc_NumTabCols('lll')
---calc_NumTabCols('ll[]')
---calc_NumTabCols('ll[]*{6}{l}')
---calc_NumTabCols('*{6}{l}')
---calc_NumTabCols('y*{6}{sq}x')
---print(_TabColSpec.. '<---Column spec')
-
---http://ctan.mirror.rafal.ca/macros/latex/contrib/multirow/multirow.pdf
---http://ctan.mirror.colo-serv.net/macros/latex/contrib/makecell/makecell.pdf
--- todo CONSIDER THIS
--- https://tex.stackexchange.com/questions/331716/newline-in-multirow-environment
-
-
--todo
-- if p{} column, and multirow is 1, use {=} instead of {*}
@@ -122,6 +85,8 @@ end
function ltt.MagicCell(s0,spec,mcspec,pre,content)
--
+ ltt.set_col_num() -- register current column number and column spec
+
local STR = ''
reset_bkt_cnt()
@@ -132,8 +97,8 @@ function ltt.MagicCell(s0,spec,mcspec,pre,content)
h, mcspec, c = ltt.get_HColSpec(h, mcspec, c) -- infer horizontal alignment, num columns
--help_wrt(_CurTabColAbv,'current column')
- if s0 == _xTrue or (pl.List({'S', 'Q', 'L', 'R'}):contains(ltt.CurTabColAbv) -- special columns for SI
- and c == '') then -- multicolumn cannot have {} around it todo test with siunitx, num rows, num columns
+ if s0 == _xTrue or (pl.List(ltt.SI_cols):contains(ltt.col) -- special columns for SI
+ and c == '') then -- multicolumn cannot have {} around it
STR = STR .. '{' -- multirow and makcell must have {} around it S column is used
add_bkt_cnt()
end
@@ -167,7 +132,7 @@ end
function ltt.parse_MagicCell_spec(spec)
- local mrowsym = '*'
+ local mrowsym = '*' -- * = natural width, = will match p{2cm} for example
local skipmakecell = false
if string.find(spec, '=') then
spec = spec:gsub('=', '')
@@ -175,9 +140,9 @@ function ltt.parse_MagicCell_spec(spec)
skipmakecell = true
end
- spec = spec:lower():gsub('%s','') -- take lower case and remove
+ spec = spec:lower():gsub('%s','') -- take lower case and remove space
local vh, rc = spec:gextract('%a') -- extract characters
- local v = vh:gfirst({'t', 'm', 'b'}) or 't'
+ local v = vh:gfirst({'t', 'm', 'b'}) or ltt.col_ver_repl[ltt.col] or 't'
local h = vh:gfirst({'l', 'c', 'r'}) or ''
v = v:gsub('m', 'c')
@@ -191,50 +156,71 @@ function ltt.parse_MagicCell_spec(spec)
end
-ltt.TabColMapping = { -- horizontal cell alignment that multicolumn should use if () or [hori] not passed to func
- l = 'l',
- c = 'c',
- r = 'r',
- p = 'p',
- P = 'c',
- X = 'l',
- Y = 'c',
- Z = 'l',
- N = 'c',
- L = 'l',
- R = 'r',
- C = 'c',
-}
-
function ltt.get_HColSpec(h, mcspec, c) -- take horizontal alignment
-- c is num columns, h is horizontal alginment,
--Assumes _TabColNum was calculated previosly
- ltt.get_TabColNum()
if c == '+' then -- fill row to end
- c = tostring(ltt.NumTabColsAbv - ltt.TabColNum + 1)
+ c = tostring(#ltt.col_spec - ltt.col_num + 1)
end
if h == '' then -- if horizontal not provided, use declared column
- h = ltt.TabColMapping[ltt.CurTabColAbv] or 'l'
+ h = ltt.col_hor_repl[ltt.col] or 'l'
end
if c ~= '' then -- only make new mcspec if column nums > 0
if mcspec == '' then -- and if no mcspec was passed
mcspec = h
- if ltt.TabColNum == 1 then -- if first column, auto detect padding
+ if ltt.col_num == 1 then -- if first column, auto detect padding
mcspec = '@{}'..mcspec
end
- if (ltt.TabColNum + tonumber(c) - 1) == ltt.NumTabColsAbv then -- if end on last column
+ if (ltt.col_num + tonumber(c) - 1) == #ltt.col_spec then -- if end on last column
mcspec = mcspec..'@{}'
end
else -- if mcspec if given, extract the alignment
- ltt.calc_NumTabCols(mcspec)
- h = ltt.TabColSpec -- get 1 character column spec from mcspec and override h
+ ltt.set_col_spec(mcspec)
+ h = ltt.col_spec1[1] -- get 1 character column spec from mcspec and override h
end
end
return h, mcspec, c
end
--- midrule stuff
+
+ltt.col_ver_repl = {
+m = 'm',
+M = 'm',
+b = 'b',
+}
+
+ltt.col_hor_repl = { -- horizontal cell alignment that multicolumn should use if () or [hori] not passed to func
+ l = 'l',
+ c = 'c',
+ r = 'r',
+ p = 'l',
+ P = 'c',
+ X = 'l',
+ Y = 'c',
+ Z = 'l',
+ N = 'c',
+ L = 'l',
+ R = 'r',
+ C = 'c',
+}
+
+-- allow user to place their own replacements in for a table, say if they define a column that expands to multiple
+ltt.col_replaces = {
+--x = 'lll'
+}
+
+ltt.SI_cols = {'S', 'N', 'Q', 'L', 'R'}
+--ltt.SI_cols = {'S'}
+--ltt.SI_cols = {'N'}
+
+
+
+
+
+-----
+--- midrule stuff
+-----
function ltt.get_midrule_col(s)
if string.find(s, '+') then
@@ -242,14 +228,11 @@ function ltt.get_midrule_col(s)
if (s == '') or (s == '0') then
s = 1
end
- s = tostring(ltt.NumTabColsAbv - tonumber(s) + 1) -- use number of tabular columns above 0,
+ s = tostring(#ltt.col_spec - tonumber(s) + 1) -- use number of tabular columns above 0,
end
return s
end
---todo if comma present, create multiple according to spec. Also allow LR in []
-
---- midrule stuff ---
function ltt.make1cmidrule(s, r, c, cmd) -- s=square r=round c=curly
cmd = '\\'..cmd
@@ -283,4 +266,16 @@ function ltt.makecmidrules(s, r, c, cmd)
end
-return ltt -- lutabulartools \ No newline at end of file
+--help_wrt('TEST COL ')
+--for _, s in ipairs{ 'll', '*{6}{s}', 'l*{6}{l}', 'lll', 'll[]', 'll[]*{6}{l}', '*{6}{l}', 'y*{6}{sq}x', } do
+-- ltt. set_col_spec(s)
+-- help_wrt(ltt.col_spec,s)
+--end
+
+return ltt -- lutabulartools
+
+
+
+--http://ctan.mirror.rafal.ca/macros/latex/contrib/multirow/multirow.pdf
+--http://ctan.mirror.colo-serv.net/macros/latex/contrib/makecell/makecell.pdf
+-- https://tex.stackexchange.com/questions/331716/newline-in-multirow-environment
diff --git a/macros/luatex/generic/lutabulartools/lutabulartools.pdf b/macros/luatex/generic/lutabulartools/lutabulartools.pdf
index 96488bd83a..770fb8045b 100644
--- a/macros/luatex/generic/lutabulartools/lutabulartools.pdf
+++ b/macros/luatex/generic/lutabulartools/lutabulartools.pdf
Binary files differ
diff --git a/macros/luatex/generic/lutabulartools/lutabulartools.sty b/macros/luatex/generic/lutabulartools/lutabulartools.sty
index b4eda9c3cf..bb72b660a8 100644
--- a/macros/luatex/generic/lutabulartools/lutabulartools.sty
+++ b/macros/luatex/generic/lutabulartools/lutabulartools.sty
@@ -1,6 +1,6 @@
% Kale Ewasiuk (kalekje@gmail.com)
-% 2021-11-07
-% Copyright (C) 2021 Kale Ewasiuk
+% 2022-02-27
+% Copyright (C) 2021-2022 Kale Ewasiuk
%
% Permission is hereby granted, free of charge, to any person obtaining a copy
% of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,7 @@
% OR OTHER DEALINGS IN THE SOFTWARE.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{lutabulartools}[2021-11-07]
+\ProvidesPackage{lutabulartools}[2022-02-27]
\RequirePackage{booktabs}
\RequirePackage{multirow}
@@ -38,12 +38,19 @@
\luadirect{lutabt = require('lutabulartools')}
+\newcolumntype{\ltt@ltrim}{@{}}
+\newcolumntype{\ltt@rtrim}{@{}}
+\DeclareOption{notrim}{
+ \newcolumntype{\ltt@ltrim}{}
+ \newcolumntype{\ltt@rtrim}{}
+}
-% DOES NOT WORK WITH colortbl package ???
%% adapted from
% https://tex.stackexchange.com/questions/182953/add-midrule-every-5-lines-automatically
+%% https://tex.stackexchange.com/questions/145084/what-is-the-function-of-noalign
+
\def\midruleXstep{5}
\def\midruleXrule{\gmidrule}
@@ -67,32 +74,18 @@
}
\newcommand*{\resetmidruleX}{\setcounter{midruleX}{0}} %%% todo maybe put noalign in here
-
\resetmidruleX
-%% https://tex.stackexchange.com/questions/145084/what-is-the-function-of-noalign
-%\begin{tabular}{rclc@{\midruleX}} % inject midrule
-%\toprule
-%Number & Student Id & Name & Score\\\midrule\resetmidruleX % reset to 0
-%1 & 14-001 & Andy & 75 \\
-%2 & & & \\
-%3 & & & \\
-%4 & & & \\
-%5 & & & \\
-%6 & & & \\
-%7 & & & \\
-%8 & & & \\
-%9 & & & \\\resetmidruleX
-%10 & & & \\%\noalign{\resetmidruleX} % noalig required to not cause error with bottomrule, but that's oka because it should be used on row 9 anyway
-%\bottomrule
-%\end{tabular}
+
%%%%
%%% gray midrule and gray cmidrule
-
-\providecommand{\gmidrule}{\arrayrulecolor{lightgray}\specialrule{\lightrulewidth}{0.4\aboverulesep}{0.6\belowrulesep}\arrayrulecolor{black}}
+\aboverulesep=0.0ex
+\belowrulesep=0.5ex
+\providecommand{\gmidrule}{\arrayrulecolor{lightgray}\specialrule{\lightrulewidth}{0.5\aboverulesep}{0.5\belowrulesep}\arrayrulecolor{black}}
+\cmidrulewidth=\lightrulewidth
%%% use gcmidrule for a gray "c" midrule
% Copy \cmidrule from
@@ -113,7 +106,7 @@
\global\advance\@cmidlb-\@cmidla
\global\@thisrulewidth=#3
\@setrulekerning{#4}
- \ifnum\@lastruleclass=\z@\vskip 0.4\aboverulesep\fi
+ \ifnum\@lastruleclass=\z@\vskip 0.5\aboverulesep\fi
\ifnum0=`{\fi}\@gtempa
\noalign{\ifnum0=`}\fi\futurenonspacelet\@tempa\@xgcmidrule}
\def\@xgcmidrule{%
@@ -123,7 +116,7 @@
\else \ifx\@tempa\morecmidrules
\vskip \cmidrulesep
\global\@lastruleclass=\@ne\else
- \vskip 0.6\belowrulesep
+ \vskip 0.5\belowrulesep
\global\@lastruleclass=\z@
\fi\fi
\ifnum0=`{\fi}
@@ -167,30 +160,17 @@
-
-
-%%% tabular commands
-
-\newcommand{\TabColNum}{\luadirect{tex.print(lutabt.get_TabColNum())}} % prints the current column number of tabular
-\newcommand{\NumTabCols}{\luadirect{tex.print(lutabt.NumTabCols)}} % prints the total num columns of tabular
-\newcounter{RowNumCnt}
-\setcounter{RowNumCnt}{1}
-
\let\oldtabular\tabular
\RenewExpandableDocumentCommand{\tabular}{ O{c} m }{%
- \setcounter{RowNumCnt}{1}%
- \luadirect{lutabt.calc_NumTabCols(\luastringN{#2})}%
- \luadirect{lutabt.NumTabColsMX = 0}% fall back if calc doesnt work for some reason
- \oldtabular[#1]{@{\stepcounter{RowNumCnt}}#2@{\luadirect{lutabt.get_TabColNum()}}}%
+ \luadirect{lutabt.set_col_spec(\luastringN{#2})}%
+ \oldtabular[#1]{\ltt@ltrim#2\ltt@rtrim}%
}
\expandafter\let\expandafter\oldtabulars\csname tabular*\endcsname
\expandafter\let\expandafter\endoldtabulars\csname endtabular*\endcsname
\RenewDocumentEnvironment{tabular*}{ m O{c} m }{%
- \setcounter{RowNumCnt}{1}%
- \luadirect{lutabt.calc_NumTabCols(\luastringN{#3})}%
- \luadirect{lutabt.NumTabColsMX = 0}% fall back if calc doesnt work for some reason
- \begin{oldtabulars}{#1}[#2]{@{\stepcounter{RowNumCnt}}#3@{\luadirect{lutabt.get_TabColNum()}}}
+ \luadirect{lutabt.set_col_spec(\luastringN{#3})}%
+ \begin{oldtabulars}{#1}[#2]{\ltt@ltrim#3\ltt@rtrim}%
}{%
\end{oldtabulars}%
}
@@ -211,7 +191,45 @@
)}}
+\NewDocumentCommand{\setMCrepl}{m m}{\luadirect{
+ lutabt.col_replaces.#1 = '#2'
+}}
+\NewDocumentCommand{\setMChordef}{ m m}{\luadirect{
+ lutabt.col_hor_repl.#1 = '#2'
+}}
+\NewDocumentCommand{\setMCverdef}{ m m}{\luadirect{
+ lutabt.col_ver_repl.#1 = '#2'
+}}
+\NewDocumentCommand{\addMCsicol}{ m }{\luadirect{
+ lutabt.SI_cols[\string##lutabt.SI_cols+1] = '#1'
+}}
+
\ProcessOptions
+
+
+
+
+
+
+%%% OLD STUFF,
+%% before I had a fall-back for counting columns (just calculate current column on last column once)
+%% and kept track of row numbers. I felt that this wasn't needed, so I've removed.
+%\newcommand{\TabColNum}{\luadirect{tex.print(lutabt.get_TabColNum())}} % prints the current column number of tabular
+%\newcommand{\NumTabCols}{\luadirect{tex.print(lutabt.NumTabCols)}} % prints the total num columns of tabular
+%\newcounter{RowNumCnt}
+%\setcounter{RowNumCnt}{1}
+% \setcounter{RowNumCnt}{1}%
+% \luadirect{lutabt.NumTabColsMX = 0}% fall back if calc doesnt work for some reason
+% \setcounter{RowNumCnt}{1}%
+% \luadirect{lutabt.NumTabColsMX = 0}% fall back if calc doesnt work for some reason
+%\DeclareOption{notrimx}{
+% \global\newcolumntype{\ltt@ltrim}{!{\stepcounter{RowNumCnt}}}
+%% \global\newcolumntype{\ltt@rtrim}{!{\luadirect{lutabt.get_TabColNum()}}}
+%}
+%\DeclareOption{trimx}{
+% \newcolumntype{\ltt@ltrim}{@{\stepcounter{RowNumCnt}}}
+%% \newcolumntype{\ltt@rtrim}{@{\luadirect{lutabt.get_TabColNum()}}}
+%}
diff --git a/macros/luatex/generic/lutabulartools/lutabulartools.tex b/macros/luatex/generic/lutabulartools/lutabulartools.tex
index 65c7f2f694..54af32f78d 100644
--- a/macros/luatex/generic/lutabulartools/lutabulartools.tex
+++ b/macros/luatex/generic/lutabulartools/lutabulartools.tex
@@ -1,6 +1,6 @@
% Kale Ewasiuk (kalekje@gmail.com)
-% 2021-11-07
-% Copyright (C) 2021 Kale Ewasiuk
+% 2022-02-27
+% Copyright (C) 2021-2022 Kale Ewasiuk
%
% Permission is hereby granted, free of charge, to any person obtaining a copy
% of this software and associated documentation files (the "Software"), to deal
@@ -61,11 +61,20 @@
\title{lutabulartools}
\subtitle{some useful tabular tools (LuaLaTeX-based)}
+\usepackage{tabularx}
+\usepackage{siunitx}
+
\begin{document}
+
+% todo: can improve naming... set col spec to a table instead of a string
+
+
\maketitle
lutabulartools is a package that contains a few useful macros to help with tables.
-Most functions require LuaLaTeX. The following packages are loaded:
+Most functions require LuaLaTeX.
+This package redefines the {\ttfamily tabular} and {\ttfamily tabular*} environments.
+The following packages are loaded:
\texttt{
{booktabs},
{multirow},
@@ -78,51 +87,148 @@ Most functions require LuaLaTeX. The following packages are loaded:
{penlight},
}
+\section{Options}
+The author likes tables with left and right-most columns flush to the end.
+Since the \cmd{tabular} env is redefined in this, the author took the liberty to
+automatically pad the \cmd{tabular} cell spec with \cmd{@{}} on each end.
+If you don't want this, you can pass \cmd{notrim} to the package. Or,
+you can manually fix it in a \cmd{tabular} with \cmd{!{}} like so:
+
+\begin{LTXexample}
+\begin{tabular}{!{}c!{}}\toprule
+Xyz\\\bottomrule \end{tabular}
+\end{LTXexample}
+
+
\section{\texttt{\textbackslash MC} -- Magic Cell}
\texttt{\textbackslash MC} (magic cell) combines the facilities of
\cmd{\multirow} and \cmd{\multicolumn} from the \texttt{multirow} package, and \cmd{\makcell} from the titular package.
With the help of LuaLaTeX, it takes an easy-to-use cell specification and employs said commands as required.
+\cmd{\MC} will not work properly if your table is only 1 column wide (you probably don't need \cmd{MC} in that case anyway...).
Here is the usage:
\texttt{\textbackslash MC * [cell spec] <cell format> (override multicolumn col) \{contents\} }
\llcmd{*}This will wrap the entire command in \{\}. This is necessary for \texttt{siunitx} single-column width columns.
-However, the \tMC\ command attempts to detect this automatically.
+However, the \cmd{\MC} command attempts to detect this automatically.
\llcmd{[cell spec]}%
Any letters placed in this argument are used for cell alignment.
You can use one of three: \qcmd{t}, \qcmd{m}, \qcmd{b} for top, middle, bottom (vertical alignment),
or \qcmd{l}, \qcmd{c}, \qcmd{r} for horizontal alignment.
-By default, \tMC\ will try to autodetect the horizontal alignment based on the current column.
+By default, \cmd{\MC} will try to autodetect the horizontal alignment based on the current column.
If it can't, it will be left-aligned.
-The default vertical alginment is top.
+The default vertical alginment is top. More on this in section \ref{s.def}.
This argument can also contain two integers, separated by a comma (if two are used).
\qcmd{C,R}, \qcmd{C}, or \qcmd{,R} are a valid inputs,
-where R=rows (int), and C=columns, (int).
+where \cmd{R}=rows (int), and \cmd{C}=columns, (int).
If you want a 1 column wide, multirow cell,
-you can pass \qcmd{,R}. These numbers can be negative.
-If no spec is passed, (argument empty), \tMC\
+you can pass \qcmd{,R}. These numbers can be negative (positive numbers occupy columns to the right
+and rows below, and negative numbers occupy columns to the left and rows above).
+If no spec is passed, (argument empty), \cmd{\MC}
acts like a \texttt{makecell}.
Additionally, you can pass \qcmd{+} in place of C (number of columns wide),
and it will make the cell width fill until the end of the current row.
Examples:\\
\qcmd{\MC[2,2]} means two columns wide, two rows tall.\\
-\qcmd{\MC[2,1]} or \qcmd{\MC[2]} or means two columns wide, one row tall.\\
+\qcmd{\MC[2,1]} or \qcmd{\MC[2]} means two columns wide, one row tall.\\
\qcmd{\MC[1,2]} or \qcmd{\MC[,2]} means one column wide, two rows tall.\\
+\qcmd{\MC[+,2]}, if placed in the first column, occupies the entire row and is two rows tall.\\
+\qcmd{\MC[+,2]}, if placed in the second column, occupies the second column to the end of the table and is two rows tall.\\
In any of these examples, you can place the alignment letters anywhere.
+So, \qcmd{\MC[l1,2b]} and \qcmd{\MC[1,2 lb]} are both left-bottom aligned (spaces are ignored).
\llcmd{(override}%
You may want to adjust the column specification of a multicolumn cell,\\
\llcmd{multicolumn)}for example, using
-\texttt{@\{\}c@\{\}} to remove padding between the cell.
+\texttt{(@\{\}c@\{\})} to remove padding between the cell.
+
\llcmd{<cell format>}%
You can place formatting like \cmd{\bfseries} here.
-Here's an example.
+
+\subsection{Defaults}\label{s.def}
+The \cmd{tabular[*]} environment is re-defined to use Lua pattern matching
+to parse the column specification of the table. This is done to determine how many columns there are,
+and what the current column type is, even if specifications like \cmd{r@{.}l*{3}{r}} are used.
+If you have defined a column that expands many, you should register it with
+\cmd{\setMCrepl{?}{??}} where \cmd{?} is your column and \cmd{??} is what it expands to.
+You can also specify default horizontal and vertical alignments (ie if alignment not passed to \cmd{\MC})
+for an arbitrary column by \cmd{\setMChordef{?}{l|r|c}} and\\
+\cmd{\setMChordef{?}{t|m|b}}, where \cmd{?} is the column.
+To add a column that should be surrounded by brackets for \cmd{siunitx} purposes, do so with \cmd{\addMCsicol{?}}.
+S is included by default.
+
+
+\newpage
+
+\subsection{Examples}
+
+\let\nl\\
+
+
+\subsubsection{A good use for headers}
+\begin{LTXexample}
+\begin{tabular}{ l l l }\toprule
+\MC[+m]<\itshape>{A Decent
+ Example}\\\midrule
+ & \MC[2m]{Heading} \\\cmidrule{2-}
+\MC[b,-2]{Multi\\Line} & A & B \\\midrule
+\end{tabular}
+\end{LTXexample}
+
+\subsubsection{A small example}
+
+
+\begin{LTXexample}
+\begin{tabular}{ l l l }\midrule
+ A & \MC[mc2,2]{Lttrs} \\
+ B & \\
+ 1 & A & B \\
+\end{tabular}
+\end{LTXexample}
+
+
+\subsubsection{A small bad example}
+Notice that the top-aligned p-column
+doesn't play particularly well with the middle aligned \cmd{\MC}
+\begin{LTXexample}
+\begin{tabular}{ p{1cm} l }\toprule
+ hello\newline world
+ & \MC[mr]{11\\2} \\
+\end{tabular}
+\end{LTXexample}
+
+
+\subsubsection{If you insist on vertical lines}
+\begin{LTXexample}
+\begin{tabular}{|c|c|c|} \hline
+1 & 2 & 3\\\hline
+4 & \MC[2,2cm](@{}c@{}|)%
+ <\ttfamily>{5}\\\cline{1-1}
+ & \MC[2](r|){} \\\hline%hacky fix
+6 & 7 & 8\\\hline
+\end{tabular}
+\end{LTXexample}
+
+
+\subsubsection{A perhaps useful example}
+
+\begin{LTXexample}
+\begin{tabularx}{\linewidth}{S[table-format=2.1,table-alignment=left]X}
+% err & ... \\% ERROR, not wrap
+ \MC{Error,\%} & Comment \\% MC helps
+ 3.1 & \MC[,2]{multi-line\\comment}\\
+ 10.1& \\
+ \MC[2c]{... ...} \\
+\end{tabularx}
+\end{LTXexample}
+
+\subsubsection{A messy example}
\begin{LTXexample}
\begin{tabular}{| c | c | c | c | c | c |}\toprule
@@ -136,23 +242,39 @@ Here's an example.
\end{LTXexample}
+
+
+
%%% https://tex.stackexchange.com/questions/287346/width-of-column-after-multicolumn-header
%\textbackslash MC[<spec>]{<contents}
-\subsection{Notes}
-This package redefines the {\ttfamily tabular} and {\ttfamily tabular*} environments.
-It uses Lua pattern matching to parse the column specification of the table to know how many columns there are,
-and what the current column type is. If you have defined a column that creates many, it will not work.
-This will be worked out in later package revisions.
-%But, to know how many columns there are you, you will have
-%either have to manually set todo the lua variable before the table, or,
-%in the last column of the first row, \texttt{get\_tab\_colnum ()}.
-%The above Lua function can be used to get the current column number.
-%You will also remove the ability for this package to know what the current column spec is
+
+%\subsubsection{And another one}
+%
+%\begin{LTXexample}
+%\setMChordef{l}{r}
+%\setMCverdef{l}{b}
+%% change deflt alignment ^
+%\newcolumntype{e}{ll}
+%\setMCrepl{e}{ll}
+%% e -> l l ^
+%\begin{tabular}{l l l e}\toprule
+%aaaaa & bbb & ccc & eee & fff\\
+%\MC{aaaaa}\\
+%\MC{a}\\\midrule
+%\MC{a\\bbbbb} & x\\\midrule
+%\makecell[br]{a\\b} & x\\\midrule
+%\MC[+]{--- ---}\\
+%\end{tabular}
+%\end{LTXexample}
+%% WHY ISNT THIS RIGHT?
+\newpage
+
+
\section{Some additional rules}
This package also redefines the \texttt{booktabs} midrules.\\
\llcmd{\gmidrule}is a full gray midrule.\\
@@ -171,11 +293,23 @@ override it for a specific rule by placing \qcmd{r} or \qcmd{l} with the span sp
Here's an example:
\begin{LTXexample}
-\begin{tabular}{c c c c c c}\toprule
- 1 & 2 & 3 & 4 & 5 & 6\\ \cmidrule{+1} % rule on last column
- 1 & 2 & 3 & 4 & 5 & 6\\ \cmidrules{1,3-+3,+} % rule on first col, third to third last col, and last col
- 1 & 2 & 3 & 4 & 5 & 6\\ \cmidrules{1,3-+3rl,+} % same as above, but trim middle
- 1 & 2 & 3 & 4 & 5 & 6\\ \cmidrules(l){1,r3-+3,+1}% trim left for all, but only trim right for middle rule
+\begin{tabular}{c c c c c c}
+ 1 & 2 & 3 & 4 & 5 & 6\\
+ \cmidrule{+1} % rule on last column
+ 1 & 2 & 3 & 4 & 5 & 6\\
+ \cmidrules{1,3-+3,+} % rule on first col, third to third last col, and last col
+ 1 & 2 & 3 & 4 & 5 & 6\\
+ \cmidrules{1,3-+3rl,+} % same as above, but trim middle
+ 1 & 2 & 3 & 4 & 5 & 6\\
+ \cmidrules(l){1,r3-+3,+1}% trim left for all, but only trim right for middle rule
+ 1 & 2 & 3 & 4 & 5 & 6\\
+ \gcmidrule{+1} % rule on last column
+ 1 & 2 & 3 & 4 & 5 & 6\\
+ \gcmidrules{1,3-+3,+} % rule on first col, third to third last col, and last col
+ 1 & 2 & 3 & 4 & 5 & 6\\
+ \gcmidrules{1,3-+3rl,+} % same as above, but trim middle
+ 1 & 2 & 3 & 4 & 5 & 6\\
+ \gcmidrules(l){1,r3-+3,+1}% trim left for all, but only trim right for middle rule
\end{tabular}
\end{LTXexample}
@@ -192,13 +326,15 @@ You can change the step size and what kind of midrule you prefer.
\end{verbatim}
-Usage: Insert midrulex at the end of each row using the column spec.
+To use, insert \cmd{@{\midrulex}} at the end of each row using the tabular column spec.
Before you want counting to begin, you should
-apply resetmidruleX (also to avoid header rows).
+apply \cmd{\resetmidruleX} to avoid header rows being counted.
+Use \cmd{\noalign{\resetmiduleX}} if you need place a rule on the same line or in a cell.
+
\begin{LTXexample}
-\def\midruleXstep{4}
-\def\midruleXrule{\cmidrules{1,3-4}}
+\def\midruleXstep{3}
+\def\midruleXrule{\cmidrules{1,3-4r}}
\begin{tabular}{rclc@{\midruleX}}
% ^^^ inject midrule
\toprule
diff --git a/macros/luatex/generic/penlight/penlight.lua b/macros/luatex/generic/penlight/penlight.lua
index bea9316071..4b2436be1b 100644
--- a/macros/luatex/generic/penlight/penlight.lua
+++ b/macros/luatex/generic/penlight/penlight.lua
@@ -21,7 +21,7 @@ do
--
-- This implements some useful things on [LOM](http://matthewwild.co.uk/projects/luaexpat/lom.html) documents, such as returned by `lxp.lom.parse`.
-- In particular, it can convert LOM back into XML text, with optional pretty-printing control.
--- It is s based on stanza.lua from [Prosody](http://hg.prosody.im/trunk/file/4621c92d2368/util/stanza.lua)
+-- It is based on stanza.lua from [Prosody](http://hg.prosody.im/trunk/file/4621c92d2368/util/stanza.lua)
--
-- > d = xml.parse "<nodes><node id='1'>alice</node></nodes>"
-- > = d
@@ -49,493 +49,880 @@ do
-- @module pl.xml
local utils = require 'pl.utils'
-local split = utils.split;
-local t_insert = table.insert;
-local t_concat = table.concat;
-local t_remove = table.remove;
-local s_match = string.match;
-local tostring = tostring;
-local setmetatable = setmetatable;
-local getmetatable = getmetatable;
-local pairs = pairs;
-local ipairs = ipairs;
-local type = type;
-local next = next;
-local print = print;
-local unpack = utils.unpack;
-local s_gsub = string.gsub;
-local s_find = string.find;
-local pcall,require,io = pcall,require,io
+local split = utils.split
+local t_insert = table.insert
+local t_concat = table.concat
+local t_remove = table.remove
+local s_match = string.match
+local tostring = tostring
+local setmetatable = setmetatable
+local getmetatable = getmetatable
+local pairs = pairs
+local ipairs = ipairs
+local type = type
+local next = next
+local print = print
+local unpack = utils.unpack
+local s_gsub = string.gsub
+local s_sub = string.sub
+local s_find = string.find
+local pcall = pcall
+local require = require
+
+
+utils.raise_deprecation {
+ source = "Penlight " .. utils._VERSION,
+ message = "the contents of module 'pl.xml' has been deprecated, please use a more specialized library instead",
+ version_removed = "2.0.0",
+ deprecated_after = "1.11.0",
+ no_trace = true,
+}
+
+
local _M = {}
local Doc = { __type = "doc" };
Doc.__index = Doc;
+
+local function is_text(s) return type(s) == 'string' end
+local function is_tag(d) return type(d) == 'table' and is_text(d.tag) end
+
+
+
--- create a new document node.
--- @param tag the tag name
--- @param attr optional attributes (table of name-value pairs)
+-- @tparam string tag the tag name
+-- @tparam[opt={}] table attr attributes (table of name-value pairs)
+-- @return the Node object
+-- @see xml.elem
+-- @usage
+-- local doc = xml.new("main", { hello = "world", answer = "42" })
+-- print(doc) --> <main hello='world' answer='42'/>
function _M.new(tag, attr)
- local doc = { tag = tag, attr = attr or {}, last_add = {}};
- return setmetatable(doc, Doc);
+ if type(tag) ~= "string" then
+ error("expected 'tag' to be a string value, got: " .. type(tag), 2)
+ end
+ attr = attr or {}
+ if type(attr) ~= "table" then
+ error("expected 'attr' to be a table value, got: " .. type(attr), 2)
+ end
+
+ local doc = { tag = tag, attr = attr, last_add = {}};
+ return setmetatable(doc, Doc);
end
---- parse an XML document. By default, this uses lxp.lom.parse, but
--- falls back to basic_parse, or if use_basic is true
--- @param text_or_file file or string representation
+
+--- parse an XML document. By default, this uses lxp.lom.parse, but
+-- falls back to basic_parse, or if `use_basic` is truthy
+-- @param text_or_filename file or string representation
-- @param is_file whether text_or_file is a file name or not
-- @param use_basic do a basic parse
-- @return a parsed LOM document with the document metatatables set
-- @return nil, error the error can either be a file error or a parse error
-function _M.parse(text_or_file, is_file, use_basic)
- local parser,status,lom
- if use_basic then parser = _M.basic_parse
+function _M.parse(text_or_filename, is_file, use_basic)
+ local parser,status,lom
+ if use_basic then
+ parser = _M.basic_parse
+ else
+ status,lom = pcall(require,'lxp.lom')
+ if not status then
+ parser = _M.basic_parse
else
- status,lom = pcall(require,'lxp.lom')
- if not status then parser = _M.basic_parse else parser = lom.parse end
+ parser = lom.parse
end
- if is_file then
- local f,err = io.open(text_or_file)
- if not f then return nil,err end
- text_or_file = f:read '*a'
- f:close()
+ end
+
+ if is_file then
+ local text_or_filename, err = utils.readfile(text_or_filename)
+ if not text_or_filename then
+ return nil, err
end
- local doc,err = parser(text_or_file)
- if not doc then return nil,err end
- if lom then
- _M.walk(doc,false,function(_,d)
- setmetatable(d,Doc)
- end)
+ end
+
+ local doc, err = parser(text_or_filename)
+ if not doc then
+ return nil, err
+ end
+
+ if lom then
+ _M.walk(doc, false, function(_, d)
+ setmetatable(d, Doc)
+ end)
+ end
+ return doc
+end
+
+
+--- Create a Node with a set of children (text or Nodes) and attributes.
+-- @tparam string tag a tag name
+-- @tparam table|string items either a single child (text or Node), or a table where the hash
+-- part is the attributes and the list part is the children (text or Nodes).
+-- @return the new Node
+-- @see xml.new
+-- @see xml.tags
+-- @usage
+-- local doc = xml.elem("top", "hello world") -- <top>hello world</top>
+-- local doc = xml.elem("main", xml.new("child")) -- <main><child/></main>
+-- local doc = xml.elem("main", { "this ", "is ", "nice" }) -- <main>this is nice</main>
+-- local doc = xml.elem("main", { xml.new "this",
+-- xml.new "is",
+-- xml.new "nice" }) -- <main><this/><is/><nice/></main>
+-- local doc = xml.elem("main", { hello = "world" }) -- <main hello='world'/>
+-- local doc = xml.elem("main", {
+-- "prefix",
+-- xml.elem("child", { "this ", "is ", "nice"}),
+-- "postfix",
+-- attrib = "value"
+-- }) -- <main attrib='value'>prefix<child>this is nice</child>postfix</main>"
+function _M.elem(tag, items)
+ local s = _M.new(tag)
+ if is_text(items) then items = {items} end
+ if is_tag(items) then
+ t_insert(s,items)
+ elseif type(items) == 'table' then
+ for k,v in pairs(items) do
+ if is_text(k) then
+ s.attr[k] = v
+ t_insert(s.attr,k)
+ else
+ s[k] = v
+ end
+ end
+ end
+ return s
+end
+
+
+--- given a list of names, return a number of element constructors.
+-- If passing a comma-separated string, then whitespace surrounding the values
+-- will be stripped.
+--
+-- The returned constructor functions are a shortcut to `xml.elem` where you
+-- no longer provide the tag-name, but only the `items` table.
+-- @tparam string|table list a list of names, or a comma-separated string.
+-- @return (multiple) constructor functions; `function(items)`. For the `items`
+-- parameter see `xml.elem`.
+-- @see xml.elem
+-- @usage
+-- local new_parent, new_child = xml.tags 'mom, kid'
+-- doc = new_parent {new_child 'Bob', new_child 'Annie'}
+-- -- <mom><kid>Bob</kid><kid>Annie</kid></mom>
+function _M.tags(list)
+ local ctors = {}
+ if is_text(list) then
+ list = split(list:match("^%s*(.-)%s*$"),'%s*,%s*')
+ end
+ for i,tag in ipairs(list) do
+ local function ctor(items)
+ return _M.elem(tag,items)
end
- return doc
+ ctors[i] = ctor
+ end
+ return unpack(ctors)
end
----- convenient function to add a document node, This updates the last inserted position.
--- @param tag a tag name
--- @param attrs optional set of attributes (name-string pairs)
+
+--- Adds a document Node, at current position.
+-- This updates the last inserted position to the new Node.
+-- @tparam string tag the tag name
+-- @tparam[opt={}] table attrs attributes (table of name-value pairs)
+-- @return the current node (`self`)
+-- @usage
+-- local doc = xml.new("main")
+-- doc:addtag("penlight", { hello = "world"})
+-- doc:addtag("expat") -- added to 'penlight' since position moved
+-- print(doc) --> <main><penlight hello='world'><expat/></penlight></main>
function Doc:addtag(tag, attrs)
- local s = _M.new(tag, attrs);
- (self.last_add[#self.last_add] or self):add_direct_child(s);
- t_insert(self.last_add, s);
- return self;
+ local s = _M.new(tag, attrs)
+ self:add_child(s)
+ t_insert(self.last_add, s)
+ return self
end
---- convenient function to add a text node. This updates the last inserted position.
--- @param text a string
+
+--- Adds a text node, at current position.
+-- @tparam string text a string
+-- @return the current node (`self`)
+-- @usage
+-- local doc = xml.new("main")
+-- doc:text("penlight")
+-- doc:text("expat")
+-- print(doc) --> <main><penlightexpat</main>
function Doc:text(text)
- (self.last_add[#self.last_add] or self):add_direct_child(text);
- return self;
+ self:add_child(text)
+ return self
end
----- go up one level in a document
+
+--- Moves current position up one level.
+-- @return the current node (`self`)
function Doc:up()
- t_remove(self.last_add);
- return self;
+ t_remove(self.last_add)
+ return self
end
+
+--- Resets current position to top level.
+-- Resets to the `self` node.
+-- @return the current node (`self`)
function Doc:reset()
- local last_add = self.last_add;
- for i = 1,#last_add do
- last_add[i] = nil;
- end
- return self;
+ local last_add = self.last_add
+ for i = 1,#last_add do
+ last_add[i] = nil
+ end
+ return self
end
---- append a child to a document directly.
+
+--- Append a child to the currrent Node (ignoring current position).
-- @param child a child node (either text or a document)
+-- @return the current node (`self`)
+-- @usage
+-- local doc = xml.new("main")
+-- doc:add_direct_child("dog")
+-- doc:add_direct_child(xml.new("child"))
+-- doc:add_direct_child("cat")
+-- print(doc) --> <main>dog<child/>cat</main>
function Doc:add_direct_child(child)
- t_insert(self, child);
+ t_insert(self, child)
+ return self
end
---- append a child to a document at the last element added
+
+--- Append a child at the current position (without changing position).
-- @param child a child node (either text or a document)
+-- @return the current node (`self`)
+-- @usage
+-- local doc = xml.new("main")
+-- doc:addtag("one")
+-- doc:add_child(xml.new("item1"))
+-- doc:add_child(xml.new("item2"))
+-- doc:add_child(xml.new("item3"))
+-- print(doc) --> <main><one><item1/><item2/><item3/></one></main>
function Doc:add_child(child)
- (self.last_add[#self.last_add] or self):add_direct_child(child);
- return self;
+ (self.last_add[#self.last_add] or self):add_direct_child(child)
+ return self
end
+
--accessing attributes: useful not to have to expose implementation (attr)
--but also can allow attr to be nil in any future optimizations
---- set attributes of a document node.
--- @param t a table containing attribute/value pairs
-function Doc:set_attribs (t)
- for k,v in pairs(t) do
- self.attr[k] = v
- end
+
+--- Set attributes of a document node.
+-- Will add/overwite values, but will not remove existing ones.
+-- Operates on the Node itself, will not take position into account.
+-- @tparam table t a table containing attribute/value pairs
+-- @return the current node (`self`)
+function Doc:set_attribs(t)
+ -- TODO: keep array part in sync
+ for k,v in pairs(t) do
+ self.attr[k] = v
+ end
+ return self
end
---- set a single attribute of a document node.
+
+--- Set a single attribute of a document node.
+-- Operates on the Node itself, will not take position into account.
-- @param a attribute
--- @param v its value
+-- @param v its value, pass in `nil` to delete the attribute
+-- @return the current node (`self`)
function Doc:set_attrib(a,v)
- self.attr[a] = v
+ -- TODO: keep array part in sync
+ self.attr[a] = v
+ return self
end
---- access the attributes of a document node.
+
+--- Gets the attributes of a document node.
+-- Operates on the Node itself, will not take position into account.
+-- @return table with attributes (attribute/value pairs)
function Doc:get_attribs()
- return self.attr
+ return self.attr
end
-local function is_text(s) return type(s) == 'string' end
---- function to create an element with a given tag name and a set of children.
--- @param tag a tag name
--- @param items either text or a table where the hash part is the attributes and the list part is the children.
-function _M.elem(tag,items)
- local s = _M.new(tag)
- if is_text(items) then items = {items} end
- if _M.is_tag(items) then
- t_insert(s,items)
- elseif type(items) == 'table' then
- for k,v in pairs(items) do
- if is_text(k) then
- s.attr[k] = v
- t_insert(s.attr,k)
- else
- s[k] = v
- end
- end
- end
- return s
-end
---- given a list of names, return a number of element constructors.
--- @param list a list of names, or a comma-separated string.
--- @usage local parent,children = doc.tags 'parent,children' <br>
--- doc = parent {child 'one', child 'two'}
-function _M.tags(list)
- local ctors = {}
- if is_text(list) then list = split(list,'%s*,%s*') end
- for _,tag in ipairs(list) do
- local ctor = function(items) return _M.elem(tag,items) end
- t_insert(ctors,ctor)
- end
- return unpack(ctors)
-end
+local template_cache do
+ local templ_cache = {}
-local templ_cache = {}
-
-local function template_cache (templ)
+ -- @param templ a template, a string being valid xml to be parsed, or a Node object
+ function template_cache(templ)
if is_text(templ) then
- if templ_cache[templ] then
- templ = templ_cache[templ]
- else
- local str,err = templ
- templ,err = _M.parse(str,false,true)
- if not templ then return nil,err end
- templ_cache[str] = templ
+ if templ_cache[templ] then
+ -- cache hit
+ return templ_cache[templ]
+
+ else
+ -- parse and cache
+ local ptempl, err = _M.parse(templ,false,true)
+ if not ptempl then
+ return nil, err
end
- elseif not _M.is_tag(templ) then
- return nil, "template is not a document"
+ templ_cache[templ] = ptempl
+ return ptempl
+ end
end
- return templ
+
+ if is_tag(templ) then
+ return templ
+ end
+
+ return nil, "template is not a document"
+ end
end
-local function is_data(data)
+
+do
+ local function is_data(data)
return #data == 0 or type(data[1]) ~= 'table'
-end
+ end
-local function prepare_data(data)
+
+ local function prepare_data(data)
-- a hack for ensuring that $1 maps to first element of data, etc.
-- Either this or could change the gsub call just below.
for i,v in ipairs(data) do
- data[tostring(i)] = v
+ data[tostring(i)] = v
+ end
+ end
+
+ --- create a substituted copy of a document,
+ -- @param template may be a document or a string representation which will be parsed and cached
+ -- @param data a table of name-value pairs or a list of such tables
+ -- @return an XML document
+ function Doc.subst(template, data)
+ if type(data) ~= 'table' or not next(data) then
+ return nil, "data must be a non-empty table"
end
-end
---- create a substituted copy of a document,
--- @param templ may be a document or a string representation which will be parsed and cached
--- @param data a table of name-value pairs or a list of such tables
--- @return an XML document
-function Doc.subst(templ, data)
- local err
- if type(data) ~= 'table' or not next(data) then return nil, "data must be a non-empty table" end
if is_data(data) then
- prepare_data(data)
+ prepare_data(data)
end
- templ,err = template_cache(templ)
- if err then return nil, err end
+
+ local templ, err = template_cache(template)
+ if err then
+ return nil, err
+ end
+
local function _subst(item)
- return _M.clone(templ,function(s)
- return s:gsub('%$(%w+)',item)
- end)
+ return _M.clone(templ, function(s)
+ return s:gsub('%$(%w+)', item)
+ end)
end
- if is_data(data) then return _subst(data) end
+
+ if is_data(data) then
+ return _subst(data)
+ end
+
local list = {}
- for _,item in ipairs(data) do
- prepare_data(item)
- t_insert(list,_subst(item))
+ for _, item in ipairs(data) do
+ prepare_data(item)
+ t_insert(list, _subst(item))
end
+
if data.tag then
- list = _M.elem(data.tag,list)
+ list = _M.elem(data.tag,list)
end
return list
+ end
end
---- get the first child with a given tag name.
+--- Return the first child with a given tag name (non-recursive).
-- @param tag the tag name
+-- @return the child Node found or `nil` if not found
function Doc:child_with_name(tag)
- for _, child in ipairs(self) do
- if child.tag == tag then return child; end
+ for _, child in ipairs(self) do
+ if child.tag == tag then
+ return child
end
+ end
end
-local _children_with_name
-function _children_with_name(self,tag,list,recurse)
- for _, child in ipairs(self) do if type(child) == 'table' then
- if child.tag == tag then t_insert(list,child) end
- if recurse then _children_with_name(child,tag,list,recurse) end
- end end
-end
---- get all elements in a document that have a given tag.
--- @param tag a tag name
--- @param dont_recurse optionally only return the immediate children with this tag name
--- @return a list of elements
-function Doc:get_elements_with_name(tag,dont_recurse)
+do
+ -- @param self document node to traverse
+ -- @param tag tag-name to look for
+ -- @param list array table to add the matching ones to
+ -- @param recurse if truthy, recursivly search the node
+ local function _children_with_name(self, tag, list, recurse)
+ -- TODO: protect against recursion
+ for _, child in ipairs(self) do
+ if type(child) == 'table' then
+ if child.tag == tag then
+ t_insert(list, child)
+ end
+ if recurse then
+ _children_with_name(child, tag, list, recurse)
+ end
+ end
+ end
+ end
+
+ --- Returns all elements in a document that have a given tag.
+ -- @tparam string tag a tag name
+ -- @tparam[opt=false] boolean dont_recurse optionally only return the immediate children with this tag name
+ -- @return a list of elements found, list will be empty if none was found.
+ function Doc:get_elements_with_name(tag, dont_recurse)
local res = {}
- _children_with_name(self,tag,res,not dont_recurse)
+ _children_with_name(self, tag, res, not dont_recurse)
return res
+ end
end
--- iterate over all children of a document node, including text nodes.
+
+
+--- Iterator over all children of a document node, including text nodes.
+-- This function is not recursive, so returns only direct child nodes.
+-- @return iterator that returns a single Node per iteration.
function Doc:children()
- local i = 0;
- return function (a)
- i = i + 1
- return a[i];
- end, self, i;
+ local i = 0;
+ return function (a)
+ i = i + 1
+ return a[i];
+ end, self, i;
end
--- return the first child element of a node, if it exists.
+
+--- Return the first child element of a node, if it exists.
+-- This will skip text nodes.
+-- @return first child Node or `nil` if there is none.
function Doc:first_childtag()
- if #self == 0 then return end
- for _,t in ipairs(self) do
- if type(t) == 'table' then return t end
+ if #self == 0 then
+ return
+ end
+ for _, t in ipairs(self) do
+ if is_tag(t) then
+ return t
end
+ end
end
+
+--- Iterator that matches tag names, and a namespace (non-recursive).
+-- @tparam[opt=nil] string tag tag names to return. Returns all tags if not provided.
+-- @tparam[opt=nil] string xmlns the namespace value ('xmlns' attribute) to return. If not
+-- provided will match all namespaces.
+-- @return iterator that returns a single Node per iteration.
function Doc:matching_tags(tag, xmlns)
- xmlns = xmlns or self.attr.xmlns;
- local tags = self;
- local start_i, max_i, v = 1, #tags;
- return function ()
- for i=start_i,max_i do
- v = tags[i];
- if (not tag or v.tag == tag)
- and (not xmlns or xmlns == v.attr.xmlns) then
- start_i = i+1;
- return v;
- end
- end
- end, tags, start_i;
+ -- TODO: this doesn't make sense??? namespaces are not "xmnls", as matched below
+ -- but "xmlns:name"... so should be a string-prefix match if anything...
+ xmlns = xmlns or self.attr.xmlns;
+ local tags = self
+ local next_i = 1
+ local max_i = #tags
+ local node
+ return function ()
+ for i = next_i, max_i do
+ node = tags[i];
+ if (not tag or node.tag == tag) and
+ (not xmlns or xmlns == node.attr.xmlns) then
+ next_i = i + 1
+ return node
+ end
+ end
+ end, tags, next_i
end
---- iterate over all child elements of a document node.
+
+--- Iterator over all child tags of a document node. This will skip over
+-- text nodes.
+-- @return iterator that returns a single Node per iteration.
function Doc:childtags()
- local i = 0;
- return function (a)
- local v
- repeat
- i = i + 1
- v = self[i]
- if v and type(v) == 'table' then return v; end
- until not v
- end, self[1], i;
+ local i = 0;
+ return function (a)
+ local v
+ repeat
+ i = i + 1
+ v = self[i]
+ if v and type(v) == 'table' then
+ return v
+ end
+ until not v
+ end, self[1], i;
end
---- visit child element of a node and call a function, possibility modifying the document.
--- @param callback a function passed the node (text or element). If it returns nil, that node will be removed.
--- If it returns a value, that will replace the current node.
+
+--- Visit child Nodes of a node and call a function, possibly modifying the document.
+-- Text elements will be skipped.
+-- This is not recursive, so only direct children will be passed.
+-- @tparam function callback a function with signature `function(node)`, passed the node.
+-- The element will be updated with the returned value, or deleted if it returns `nil`.
function Doc:maptags(callback)
- local is_tag = _M.is_tag
- local i = 1;
- while i <= #self do
- if is_tag(self[i]) then
- local ret = callback(self[i]);
- if ret == nil then
- t_remove(self, i);
- else
- self[i] = ret;
- i = i + 1;
- end
- end
+ local i = 1;
+
+ while i <= #self do
+ if is_tag(self[i]) then
+ local ret = callback(self[i]);
+ if ret == nil then
+ -- remove it
+ t_remove(self, i);
+
+ else
+ -- update it
+ self[i] = ret;
+ i = i + 1;
+ end
+ else
+ i = i + 1
end
- return self;
+ end
+
+ return self;
end
-local xml_escape
+
do
- local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
- function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
- _M.xml_escape = xml_escape;
+ local escape_table = {
+ ["'"] = "&apos;",
+ ['"'] = "&quot;",
+ ["<"] = "&lt;",
+ [">"] = "&gt;",
+ ["&"] = "&amp;",
+ }
+
+ --- Escapes a string for safe use in xml.
+ -- Handles quotes(single+double), less-than, greater-than, and ampersand.
+ -- @tparam string str string value to escape
+ -- @return escaped string
+ -- @usage
+ -- local esc = xml.xml_escape([["'<>&]]) --> "&quot;&apos;&lt;&gt;&amp;"
+ function _M.xml_escape(str)
+ return (s_gsub(str, "['&<>\"]", escape_table))
+ end
+end
+local xml_escape = _M.xml_escape
+
+do
+ local escape_table = {
+ quot = '"',
+ apos = "'",
+ lt = "<",
+ gt = ">",
+ amp = "&",
+ }
+
+ --- Unescapes a string from xml.
+ -- Handles quotes(single+double), less-than, greater-than, and ampersand.
+ -- @tparam string str string value to unescape
+ -- @return unescaped string
+ -- @usage
+ -- local unesc = xml.xml_escape("&quot;&apos;&lt;&gt;&amp;") --> [["'<>&]]
+ function _M.xml_unescape(str)
+ return (str:gsub( "&(%a+);", escape_table))
+ end
end
+local xml_unescape = _M.xml_unescape
-- pretty printing
-- if indent, then put each new tag on its own line
-- if attr_indent, put each new attribute on its own line
-local function _dostring(t, buf, self, xml_escape, parentns, idn, indent, attr_indent)
- local nsid = 0;
- local tag = t.tag
- local lf,alf = ""," "
- if indent then lf = '\n'..idn end
- if attr_indent then alf = '\n'..idn..attr_indent end
- t_insert(buf, lf.."<"..tag);
- local function write_attr(k,v)
- if s_find(k, "\1", 1, true) then
- local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$");
- nsid = nsid + 1;
- t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'");
- elseif not(k == "xmlns" and v == parentns) then
- t_insert(buf, alf..k.."='"..xml_escape(v).."'");
- end
- end
- -- it's useful for testing to have predictable attribute ordering, if available
- if #t.attr > 0 then
- for _,k in ipairs(t.attr) do
- write_attr(k,t.attr[k])
- end
- else
- for k, v in pairs(t.attr) do
- write_attr(k,v)
- end
+local function _dostring(t, buf, parentns, block_indent, tag_indent, attr_indent)
+ local nsid = 0
+ local tag = t.tag
+
+ local lf = ""
+ if tag_indent then
+ lf = '\n'..block_indent
+ end
+
+ local alf = " "
+ if attr_indent then
+ alf = '\n'..block_indent..attr_indent
+ end
+
+ t_insert(buf, lf.."<"..tag)
+
+ local function write_attr(k,v)
+ if s_find(k, "\1", 1, true) then
+ nsid = nsid + 1
+ local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$")
+ t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'")
+
+ elseif not (k == "xmlns" and v == parentns) then
+ t_insert(buf, alf..k.."='"..xml_escape(v).."'");
end
- local len,has_children = #t;
- if len == 0 then
- local out = "/>"
- if attr_indent then out = '\n'..idn..out end
- t_insert(buf, out);
- else
- t_insert(buf, ">");
- for n=1,len do
- local child = t[n];
- if child.tag then
- self(child, buf, self, xml_escape, t.attr.xmlns,idn and idn..indent, indent, attr_indent );
- has_children = true
- else -- text element
- t_insert(buf, xml_escape(child));
- end
- end
- t_insert(buf, (has_children and lf or '').."</"..tag..">");
+ end
+
+ -- it's useful for testing to have predictable attribute ordering, if available
+ if #t.attr > 0 then
+ -- TODO: the key-value list is leading, what if they are not in-sync
+ for _,k in ipairs(t.attr) do
+ write_attr(k,t.attr[k])
+ end
+ else
+ for k, v in pairs(t.attr) do
+ write_attr(k,v)
+ end
+ end
+
+ local len = #t
+ local has_children
+
+ if len == 0 then
+ t_insert(buf, attr_indent and '\n'..block_indent.."/>" or "/>")
+
+ else
+ t_insert(buf, ">");
+
+ for n = 1, len do
+ local child = t[n]
+
+ if child.tag then
+ has_children = true
+ _dostring(child, buf, t.attr.xmlns, block_indent and block_indent..tag_indent, tag_indent, attr_indent)
+
+ else
+ -- text element
+ t_insert(buf, xml_escape(child))
+ end
end
+
+ t_insert(buf, (has_children and lf or '').."</"..tag..">");
+ end
end
----- pretty-print an XML document
---- @param t an XML document
---- @param idn an initial indent (indents are all strings)
---- @param indent an indent for each level
---- @param attr_indent if given, indent each attribute pair and put on a separate line
---- @param xml force prefacing with default or custom <?xml...>
---- @return a string representation
-function _M.tostring(t,idn,indent, attr_indent, xml)
- local buf = {};
- if xml then
- if type(xml) == "string" then
- buf[1] = xml
- else
- buf[1] = "<?xml version='1.0'?>"
- end
+--- Function to pretty-print an XML document.
+-- @param doc an XML document
+-- @tparam[opt] string|int b_ind an initial block-indent (required when `t_ind` is set)
+-- @tparam[opt] string|int t_ind an tag-indent for each level (required when `a_ind` is set)
+-- @tparam[opt] string|int a_ind if given, indent each attribute pair and put on a separate line
+-- @tparam[opt] string|bool xml_preface force prefacing with default or custom <?xml...>, if truthy then `&lt;?xml version='1.0'?&gt;` will be used as default.
+-- @return a string representation
+-- @see Doc:tostring
+function _M.tostring(doc, b_ind, t_ind, a_ind, xml_preface)
+ local buf = {}
+
+ if type(b_ind) == "number" then b_ind = (" "):rep(b_ind) end
+ if type(t_ind) == "number" then t_ind = (" "):rep(t_ind) end
+ if type(a_ind) == "number" then a_ind = (" "):rep(a_ind) end
+
+ if xml_preface then
+ if type(xml_preface) == "string" then
+ buf[1] = xml_preface
+ else
+ buf[1] = "<?xml version='1.0'?>"
end
- _dostring(t, buf, _dostring, xml_escape, nil,idn,indent, attr_indent);
- return t_concat(buf);
+ end
+
+ _dostring(doc, buf, nil, b_ind, t_ind, a_ind, xml_preface)
+
+ return t_concat(buf)
end
+
Doc.__tostring = _M.tostring
---- get the full text value of an element
+
+--- Method to pretty-print an XML document.
+-- Invokes `xml.tostring`.
+-- @tparam[opt] string|int b_ind an initial indent (required when `t_ind` is set)
+-- @tparam[opt] string|int t_ind an indent for each level (required when `a_ind` is set)
+-- @tparam[opt] string|int a_ind if given, indent each attribute pair and put on a separate line
+-- @tparam[opt="&lt;?xml version='1.0'?&gt;"] string xml_preface force prefacing with default or custom <?xml...>
+-- @return a string representation
+-- @see xml.tostring
+function Doc:tostring(b_ind, t_ind, a_ind, xml_preface)
+ return _M.tostring(self, b_ind, t_ind, a_ind, xml_preface)
+end
+
+
+--- get the full text value of an element.
+-- @return a single string with all text elements concatenated
+-- @usage
+-- local doc = xml.new("main")
+-- doc:text("one")
+-- doc:add_child(xml.elem "two")
+-- doc:text("three")
+--
+-- local t = doc:get_text() --> "onethree"
function Doc:get_text()
- local res = {}
- for i,el in ipairs(self) do
- if is_text(el) then t_insert(res,el) end
- end
- return t_concat(res);
+ local res = {}
+ for i,el in ipairs(self) do
+ if is_text(el) then t_insert(res,el) end
+ end
+ return t_concat(res);
end
---- make a copy of a document
--- @param doc the original document
--- @param strsubst an optional function for handling string copying which could do substitution, etc.
-function _M.clone(doc, strsubst)
- local lookup_table = {};
- local function _copy(object,kind,parent)
- if type(object) ~= "table" then
- if strsubst and is_text(object) then return strsubst(object,kind,parent)
- else return object
- end
- elseif lookup_table[object] then
- return lookup_table[object]
- end
- local new_table = {};
- lookup_table[object] = new_table
- local tag = object.tag
- new_table.tag = _copy(tag,'*TAG',parent)
- if object.attr then
- local res = {}
- for attr,value in pairs(object.attr) do
- res[attr] = _copy(value,attr,object)
- end
- new_table.attr = res
- end
- for index = 1,#object do
- local v = _copy(object[index],'*TEXT',object)
- t_insert(new_table,v)
+
+do
+ local function _copy(object, kind, parent, strsubst, lookup_table)
+ if type(object) ~= "table" then
+ if strsubst and is_text(object) then
+ return strsubst(object, kind, parent)
+ else
+ return object
+ end
+ end
+
+ if lookup_table[object] then
+ error("recursion detected")
+ end
+ lookup_table[object] = true
+
+ local new_table = {}
+ lookup_table[object] = new_table
+
+ local tag = object.tag
+ new_table.tag = _copy(tag, '*TAG', parent, strsubst, lookup_table)
+
+ if object.attr then
+ local res = {}
+ for attr, value in pairs(object.attr) do
+ if type(attr) == "string" then
+ res[attr] = _copy(value, attr, object, strsubst, lookup_table)
end
- return setmetatable(new_table, getmetatable(object))
+ end
+ new_table.attr = res
end
- return _copy(doc)
+ for index = 1, #object do
+ local v = _copy(object[index], '*TEXT', object, strsubst, lookup_table)
+ t_insert(new_table,v)
+ end
+
+ return setmetatable(new_table, getmetatable(object))
+ end
+
+ --- Returns a copy of a document.
+ -- The `strsubst` parameter is a callback with signature `function(object, kind, parent)`.
+ --
+ -- Param `kind` has the following values, and parameters:
+ --
+ -- - `"*TAG"`: `object` is the tag-name, `parent` is the Node object. Returns the new tag name.
+ --
+ -- - `"*TEXT"`: `object` is the text-element, `parent` is the Node object. Returns the new text value.
+ --
+ -- - other strings not prefixed with `*`: `kind` is the attribute name, `object` is the
+ -- attribute value, `parent` is the Node object. Returns the new attribute value.
+ --
+ -- @tparam Node|string doc a Node object or string (text node)
+ -- @tparam[opt] function strsubst an optional function for handling string copying
+ -- which could do substitution, etc.
+ -- @return copy of the document
+ -- @see Doc:filter
+ function _M.clone(doc, strsubst)
+ return _copy(doc, nil, nil, strsubst, {})
+ end
end
+
+--- Returns a copy of a document.
+-- This is the method version of `xml.clone`.
+-- @see xml.clone
+-- @name Doc:filter
+-- @tparam[opt] function strsubst an optional function for handling string copying
Doc.filter = _M.clone -- also available as method
---- compare two documents.
--- @param t1 any value
--- @param t2 any value
-function _M.compare(t1,t2)
+do
+ local function _compare(t1, t2, recurse_check)
+
local ty1 = type(t1)
local ty2 = type(t2)
- if ty1 ~= ty2 then return false, 'type mismatch' end
+
+ if ty1 ~= ty2 then
+ return false, 'type mismatch'
+ end
+
if ty1 == 'string' then
- return t1 == t2 and true or 'text '..t1..' ~= text '..t2
+ if t1 == t2 then
+ return true
+ else
+ return false, 'text '..t1..' ~= text '..t2
+ end
+ end
+
+ if ty1 ~= 'table' or ty2 ~= 'table' then
+ return false, 'not a document'
+ end
+
+ if recurse_check[t1] then
+ return false, "recursive document"
+ end
+ recurse_check[t1] = true
+
+ if t1.tag ~= t2.tag then
+ return false, 'tag '..t1.tag..' ~= tag '..t2.tag
+ end
+
+ if #t1 ~= #t2 then
+ return false, 'size '..#t1..' ~= size '..#t2..' for tag '..t1.tag
end
- if ty1 ~= 'table' or ty2 ~= 'table' then return false, 'not a document' end
- if t1.tag ~= t2.tag then return false, 'tag '..t1.tag..' ~= tag '..t2.tag end
- if #t1 ~= #t2 then return false, 'size '..#t1..' ~= size '..#t2..' for tag '..t1.tag end
+
-- compare attributes
for k,v in pairs(t1.attr) do
- if t2.attr[k] ~= v then return false, 'mismatch attrib' end
+ local t2_value = t2.attr[k]
+ if type(k) == "string" then
+ if t2_value ~= v then return false, 'mismatch attrib' end
+ else
+ if t2_value ~= nil and t2_value ~= v then return false, "mismatch attrib order" end
+ end
end
for k,v in pairs(t2.attr) do
- if t1.attr[k] ~= v then return false, 'mismatch attrib' end
+ local t1_value = t1.attr[k]
+ if type(k) == "string" then
+ if t1_value ~= v then return false, 'mismatch attrib' end
+ else
+ if t1_value ~= nil and t1_value ~= v then return false, "mismatch attrib order" end
+ end
end
+
-- compare children
- for i = 1,#t1 do
- local yes,err = _M.compare(t1[i],t2[i])
- if not yes then return err end
+ for i = 1, #t1 do
+ local ok, err = _compare(t1[i], t2[i], recurse_check)
+ if not ok then
+ return ok, err
+ end
end
return true
+ end
+
+ --- Compare two documents or elements.
+ -- Equality is based on tag, child nodes (text and tags), attributes and order
+ -- of those (order only fails if both are given, and not equal).
+ -- @tparam Node|string t1 a Node object or string (text node)
+ -- @tparam Node|string t2 a Node object or string (text node)
+ -- @treturn boolean `true` when the Nodes are equal.
+ function _M.compare(t1,t2)
+ return _compare(t1, t2, {})
+ end
end
+
--- is this value a document element?
-- @param d any value
-function _M.is_tag(d)
- return type(d) == 'table' and is_text(d.tag)
-end
+-- @treturn boolean `true` if it is a `table` with property `tag` being a string value.
+-- @name is_tag
+_M.is_tag = is_tag
+
---- call the desired function recursively over the document.
--- @param doc the document
--- @param depth_first visit child notes first, then the current node
--- @param operation a function which will receive the current tag name and current node.
-function _M.walk (doc, depth_first, operation)
- if not depth_first then operation(doc.tag,doc) end
+do
+ local function _walk(doc, depth_first, operation, recurse_check)
+ if not depth_first then operation(doc.tag, doc) end
for _,d in ipairs(doc) do
- if _M.is_tag(d) then
- _M.walk(d,depth_first,operation)
- end
+ if is_tag(d) then
+ assert(not recurse_check[d], "recursion detected")
+ recurse_check[d] = true
+ _walk(d, depth_first, operation, recurse_check)
+ end
end
- if depth_first then operation(doc.tag,doc) end
+ if depth_first then operation(doc.tag, doc) end
+ end
+
+ --- Calls a function recursively over Nodes in the document.
+ -- Will only call on tags, it will skip text nodes.
+ -- The function signature for `operation` is `function(tag_name, Node)`.
+ -- @tparam Node|string doc a Node object or string (text node)
+ -- @tparam boolean depth_first visit child nodes first, then the current node
+ -- @tparam function operation a function which will receive the current tag name and current node.
+ function _M.walk(doc, depth_first, operation)
+ return _walk(doc, depth_first, operation, {})
+ end
end
+
local html_empty_elements = { --lists all HTML empty (void) elements
br = true,
img = true,
@@ -553,13 +940,10 @@ local html_empty_elements = { --lists all HTML empty (void) elements
embed = true,
}
-local escapes = { quot = "\"", apos = "'", lt = "<", gt = ">", amp = "&" }
-local function unescape(str) return (str:gsub( "&(%a+);", escapes)); end
-
--- Parse a well-formed HTML file as a string.
-- Tags are case-insenstive, DOCTYPE is ignored, and empty elements can be .. empty.
-- @param s the HTML
-function _M.parsehtml (s)
+function _M.parsehtml(s)
return _M.basic_parse(s,false,true)
end
@@ -567,9 +951,7 @@ end
-- @param s the XML document to be parsed.
-- @param all_text if true, preserves all whitespace. Otherwise only text containing non-whitespace is included.
-- @param html if true, uses relaxed HTML rules for parsing
-function _M.basic_parse(s,all_text,html)
- local t_insert,t_remove = table.insert,table.remove
- local s_find,s_sub = string.find,string.sub
+function _M.basic_parse(s, all_text, html)
local stack = {}
local top = {}
@@ -577,12 +959,12 @@ function _M.basic_parse(s,all_text,html)
local arg = {}
s:gsub("([%w:%-_]+)%s*=%s*([\"'])(.-)%2", function (w, _, a)
if html then w = w:lower() end
- arg[w] = unescape(a)
+ arg[w] = xml_unescape(a)
end)
if html then
s:gsub("([%w:%-_]+)%s*=%s*([^\"']+)%s*", function (w, a)
w = w:lower()
- arg[w] = unescape(a)
+ arg[w] = xml_unescape(a)
end)
end
return arg
@@ -617,7 +999,7 @@ function _M.basic_parse(s,all_text,html)
if html_empty_elements[label] then empty = "/" end
end
if all_text or not s_find(text, "^%s*$") then
- t_insert(top, unescape(text))
+ t_insert(top, xml_unescape(text))
end
if empty == "/" then -- empty element tag
t_insert(top, setmetatable({tag=label, attr=parseargs(xarg), empty=1},Doc))
@@ -640,7 +1022,7 @@ function _M.basic_parse(s,all_text,html)
end
local text = s_sub(s, i)
if all_text or not s_find(text, "^%s*$") then
- t_insert(stack[#stack], unescape(text))
+ t_insert(stack[#stack], xml_unescape(text))
end
if #stack > 1 then
error("unclosed "..stack[#stack].tag)
@@ -649,145 +1031,151 @@ function _M.basic_parse(s,all_text,html)
return is_text(res[1]) and res[2] or res[1]
end
-local function empty(attr) return not attr or not next(attr) end
-local function is_element(d) return type(d) == 'table' and d.tag ~= nil end
+do
+ local match do
--- returns the key,value pair from a table if it has exactly one entry
-local function has_one_element(t)
- local key,value = next(t)
- if next(t,key) ~= nil then return false end
- return key,value
-end
+ local function empty(attr) return not attr or not next(attr) end
-local function append_capture(res,tbl)
- if not empty(tbl) then -- no point in capturing empty tables...
- local key
- if tbl._ then -- if $_ was set then it is meant as the top-level key for the captured table
- key = tbl._
- tbl._ = nil
- if empty(tbl) then return end
- end
- -- a table with only one pair {[0]=value} shall be reduced to that value
- local numkey,val = has_one_element(tbl)
- if numkey == 0 then tbl = val end
- if key then
- res[key] = tbl
- else -- otherwise, we append the captured table
- t_insert(res,tbl)
- end
+ local append_capture do
+ -- returns the key,value pair from a table if it has exactly one entry
+ local function has_one_element(t)
+ local key,value = next(t)
+ if next(t,key) ~= nil then return false end
+ return key,value
+ end
+
+ function append_capture(res,tbl)
+ if not empty(tbl) then -- no point in capturing empty tables...
+ local key
+ if tbl._ then -- if $_ was set then it is meant as the top-level key for the captured table
+ key = tbl._
+ tbl._ = nil
+ if empty(tbl) then return end
+ end
+ -- a table with only one pair {[0]=value} shall be reduced to that value
+ local numkey,val = has_one_element(tbl)
+ if numkey == 0 then tbl = val end
+ if key then
+ res[key] = tbl
+ else -- otherwise, we append the captured table
+ t_insert(res,tbl)
+ end
+ end
+ end
end
-end
-local function make_number(pat)
- if pat:find '^%d+$' then -- $1 etc means use this as an array location
- pat = tonumber(pat)
+ local function make_number(pat)
+ if pat:find '^%d+$' then -- $1 etc means use this as an array location
+ pat = tonumber(pat)
+ end
+ return pat
end
- return pat
-end
-local function capture_attrib(res,pat,value)
- pat = make_number(pat:sub(2))
- res[pat] = value
- return true
-end
+ local function capture_attrib(res,pat,value)
+ pat = make_number(pat:sub(2))
+ res[pat] = value
+ return true
+ end
-local match
-function match(d,pat,res,keep_going)
- local ret = true
- if d == nil then d = '' end --return false end
- -- attribute string matching is straight equality, except if the pattern is a $ capture,
- -- which always succeeds.
- if is_text(d) then
- if not is_text(pat) then return false end
- if _M.debug then print(d,pat) end
- if pat:find '^%$' then
- return capture_attrib(res,pat,d)
+ function match(d,pat,res,keep_going)
+ local ret = true
+ if d == nil then d = '' end --return false end
+ -- attribute string matching is straight equality, except if the pattern is a $ capture,
+ -- which always succeeds.
+ if is_text(d) then
+ if not is_text(pat) then return false end
+ if _M.debug then print(d,pat) end
+ if pat:find '^%$' then
+ return capture_attrib(res,pat,d)
+ else
+ return d == pat
+ end
else
- return d == pat
- end
- else
- if _M.debug then print(d.tag,pat.tag) end
- -- this is an element node. For a match to succeed, the attributes must
- -- match as well.
- -- a tagname in the pattern ending with '-' is a wildcard and matches like an attribute
- local tagpat = pat.tag:match '^(.-)%-$'
- if tagpat then
- tagpat = make_number(tagpat)
- res[tagpat] = d.tag
- end
- if d.tag == pat.tag or tagpat then
-
- if not empty(pat.attr) then
- if empty(d.attr) then ret = false
- else
- for prop,pval in pairs(pat.attr) do
- local dval = d.attr[prop]
- if not match(dval,pval,res) then ret = false; break end
- end
- end
+ if _M.debug then print(d.tag,pat.tag) end
+ -- this is an element node. For a match to succeed, the attributes must
+ -- match as well.
+ -- a tagname in the pattern ending with '-' is a wildcard and matches like an attribute
+ local tagpat = pat.tag:match '^(.-)%-$'
+ if tagpat then
+ tagpat = make_number(tagpat)
+ res[tagpat] = d.tag
end
- -- the pattern may have child nodes. We match partially, so that {P1,P2} shall match {X,P1,X,X,P2,..}
- if ret and #pat > 0 then
- local i,j = 1,1
- local function next_elem()
- j = j + 1 -- next child element of data
- if is_text(d[j]) then j = j + 1 end
- return j <= #d
- end
- repeat
- local p = pat[i]
- -- repeated {{<...>}} patterns shall match one or more elements
- -- so e.g. {P+} will match {X,X,P,P,X,P,X,X,X}
- if is_element(p) and p.repeated then
- local found
- repeat
- local tbl = {}
- ret = match(d[j],p,tbl,false)
- if ret then
- found = false --true
- append_capture(res,tbl)
- end
- until not next_elem() or (found and not ret)
- i = i + 1
+ if d.tag == pat.tag or tagpat then
+
+ if not empty(pat.attr) then
+ if empty(d.attr) then ret = false
else
- ret = match(d[j],p,res,false)
- if ret then i = i + 1 end
+ for prop,pval in pairs(pat.attr) do
+ local dval = d.attr[prop]
+ if not match(dval,pval,res) then ret = false; break end
+ end
end
- until not next_elem() or i > #pat -- run out of elements or patterns to match
- -- if every element in our pattern matched ok, then it's been a successful match
- if i > #pat then return true end
+ end
+ -- the pattern may have child nodes. We match partially, so that {P1,P2} shall match {X,P1,X,X,P2,..}
+ if ret and #pat > 0 then
+ local i,j = 1,1
+ local function next_elem()
+ j = j + 1 -- next child element of data
+ if is_text(d[j]) then j = j + 1 end
+ return j <= #d
+ end
+ repeat
+ local p = pat[i]
+ -- repeated {{<...>}} patterns shall match one or more elements
+ -- so e.g. {P+} will match {X,X,P,P,X,P,X,X,X}
+ if is_tag(p) and p.repeated then
+ local found
+ repeat
+ local tbl = {}
+ ret = match(d[j],p,tbl,false)
+ if ret then
+ found = false --true
+ append_capture(res,tbl)
+ end
+ until not next_elem() or (found and not ret)
+ i = i + 1
+ else
+ ret = match(d[j],p,res,false)
+ if ret then i = i + 1 end
+ end
+ until not next_elem() or i > #pat -- run out of elements or patterns to match
+ -- if every element in our pattern matched ok, then it's been a successful match
+ if i > #pat then return true end
+ end
+ if ret then return true end
+ else
+ ret = false
end
- if ret then return true end
- else
- ret = false
- end
- -- keep going anyway - look at the children!
- if keep_going then
- for child in d:childtags() do
- ret = match(child,pat,res,keep_going)
- if ret then break end
+ -- keep going anyway - look at the children!
+ if keep_going then
+ for child in d:childtags() do
+ ret = match(child,pat,res,keep_going)
+ if ret then break end
+ end
end
end
+ return ret
end
- return ret
-end
+ end
-function Doc:match(pat)
- local err
- pat,err = template_cache(pat)
- if not pat then return nil, err end
- _M.walk(pat,false,function(_,d)
- if is_text(d[1]) and is_element(d[2]) and is_text(d[3]) and
- d[1]:find '%s*{{' and d[3]:find '}}%s*' then
- t_remove(d,1)
- t_remove(d,2)
- d[1].repeated = true
- end
- end)
+ --- does something...
+ function Doc:match(pat)
+ local err
+ pat,err = template_cache(pat)
+ if not pat then return nil, err end
+ _M.walk(pat,false,function(_,d)
+ if is_text(d[1]) and is_tag(d[2]) and is_text(d[3]) and
+ d[1]:find '%s*{{' and d[3]:find '}}%s*' then
+ t_remove(d,1)
+ t_remove(d,2)
+ d[1].repeated = true
+ end
+ end)
- local res = {}
- local ret = match(self,pat,res,true)
- return res,ret
+ local res = {}
+ local ret = match(self,pat,res,true)
+ return res,ret
+ end
end
@@ -1416,14 +1804,14 @@ do
error('bad cell specifier: '..s)
end
- --- parse a spreadsheet range.
- -- The range can be specified either as 'A1:B2' or 'R1C1:R2C2';
- -- a special case is a single element (e.g 'A1' or 'R1C1')
+ --- parse a spreadsheet range or cell.
+ -- The range/cell can be specified either as 'A1:B2' or 'R1C1:R2C2' or for
+ -- single cells as 'A1' or 'R1C1'.
-- @string s a range (case insensitive).
-- @treturn int start row
-- @treturn int start col
- -- @treturn int end row
- -- @treturn int end col
+ -- @treturn int end row (or `nil` if the range was a single cell)
+ -- @treturn int end col (or `nil` if the range was a single cell)
function array2d.parse_range (s)
assert_arg(1,s,'string')
s = s:upper()
@@ -1439,15 +1827,11 @@ do
end
end
---- get a slice of a 2D array using spreadsheet range notation. @see parse_range
--- @array2d t a 2D array
--- @string rstr range expression
--- @return a slice
--- @see array2d.parse_range
--- @see array2d.slice
-function array2d.range (t,rstr)
- assert_arg(1,t,'table')
- return array2d.slice(t,array2d.parse_range(rstr))
+--- get a slice of a 2D array.
+-- Same as `slice`.
+-- @see slice
+function array2d.range (...)
+ return array2d.slice(...)
end
local default_range do
@@ -1465,12 +1849,16 @@ local default_range do
-- Negative indices will be counted from the end, too low, or too high
-- will be limited by the array sizes.
-- @array2d t a 2D array
- -- @int i1 start row (default 1)
- -- @int j1 start col (default 1)
- -- @int i2 end row (default N)
- -- @int j2 end col (default M)
- -- return i1, j1, i2, j2
+ -- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
+ -- @tparam[opt=1] int j1 start col
+ -- @tparam[opt=N] int i2 end row
+ -- @tparam[opt=M] int j2 end col
+ -- @see parse_range
+ -- @return i1, j1, i2, j2
function array2d.default_range (t,i1,j1,i2,j2)
+ if (type(i1) == 'string') and not (j1 or i2 or j2) then
+ i1, j1, i2, j2 = array2d.parse_range(i1)
+ end
local nr, nc = array2d.size(t)
i1 = norm_value(i1 or 1, nr)
j1 = norm_value(j1 or 1, nc)
@@ -1484,10 +1872,11 @@ end
--- get a slice of a 2D array. Note that if the specified range has
-- a 1D result, the rank of the result will be 1.
-- @array2d t a 2D array
--- @int i1 start row (default 1)
--- @int j1 start col (default 1)
--- @int i2 end row (default N)
--- @int j2 end col (default M)
+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
+-- @tparam[opt=1] int j1 start col
+-- @tparam[opt=N] int i2 end row
+-- @tparam[opt=M] int j2 end col
+-- @see parse_range
-- @return an array, 2D in general but 1D in special cases.
function array2d.slice (t,i1,j1,i2,j2)
assert_arg(1,t,'table')
@@ -1513,10 +1902,11 @@ end
--- set a specified range of an array to a value.
-- @array2d t a 2D array
-- @param value the value (may be a function, called as `val(i,j)`)
--- @int i1 start row (default 1)
--- @int j1 start col (default 1)
--- @int i2 end row (default N)
--- @int j2 end col (default M)
+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
+-- @tparam[opt=1] int j1 start col
+-- @tparam[opt=N] int i2 end row
+-- @tparam[opt=M] int j2 end col
+-- @see parse_range
-- @see tablex.set
function array2d.set (t,value,i1,j1,i2,j2)
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
@@ -1537,10 +1927,11 @@ end
-- @array2d t a 2D array
-- @param f a file object (default stdout)
-- @string fmt a format string (default is just to use tostring)
--- @int i1 start row (default 1)
--- @int j1 start col (default 1)
--- @int i2 end row (default N)
--- @int j2 end col (default M)
+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
+-- @tparam[opt=1] int j1 start col
+-- @tparam[opt=N] int i2 end row
+-- @tparam[opt=M] int j2 end col
+-- @see parse_range
function array2d.write (t,f,fmt,i1,j1,i2,j2)
assert_arg(1,t,'table')
f = f or stdout
@@ -1560,10 +1951,11 @@ end
-- @array2d t 2D array
-- @func row_op function to call on each value; `row_op(row,j)`
-- @func end_row_op function to call at end of each row; `end_row_op(i)`
--- @int i1 start row (default 1)
--- @int j1 start col (default 1)
--- @int i2 end row (default N)
--- @int j2 end col (default M)
+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
+-- @tparam[opt=1] int j1 start col
+-- @tparam[opt=N] int i2 end row
+-- @tparam[opt=M] int j2 end col
+-- @see parse_range
function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2)
assert_arg(1,t,'table')
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
@@ -1581,10 +1973,11 @@ end
-- @int di start row in dest
-- @int dj start col in dest
-- @array2d src a 2D array
--- @int i1 start row (default 1)
--- @int j1 start col (default 1)
--- @int i2 end row (default N)
--- @int j2 end col (default M)
+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
+-- @tparam[opt=1] int j1 start col
+-- @tparam[opt=N] int i2 end row
+-- @tparam[opt=M] int j2 end col
+-- @see parse_range
function array2d.move (dest,di,dj,src,i1,j1,i2,j2)
assert_arg(1,dest,'table')
assert_arg(4,src,'table')
@@ -1604,10 +1997,11 @@ end
--- iterate over all elements in a 2D array, with optional indices.
-- @array2d a 2D array
-- @bool indices with indices (default false)
--- @int i1 start row (default 1)
--- @int j1 start col (default 1)
--- @int i2 end row (default N)
--- @int j2 end col (default M)
+-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
+-- @tparam[opt=1] int j1 start col
+-- @tparam[opt=N] int i2 end row
+-- @tparam[opt=M] int j2 end col
+-- @see parse_range
-- @return either `value` or `i,j,value` depending on the value of `indices`
function array2d.iter(a,indices,i1,j1,i2,j2)
assert_arg(1,a,'table')
@@ -1647,7 +2041,7 @@ function array2d.columns(a)
end
--- iterate over all rows.
--- Returns a copy of the row, for read-only purrposes directly iterating
+-- Returns a copy of the row, for read-only purposes directly iterating
-- is more performant; `ipairs(a)`
-- @array2d a a 2D array
-- @return row, row-index
@@ -2129,7 +2523,7 @@ end
-- See `utils.unpack` for a version that is nil-safe.
-- @param t table to unpack
-- @param[opt] i index from which to start unpacking, defaults to 1
--- @param[opt] t index of the last element to unpack, defaults to #t
+-- @param[opt] j index of the last element to unpack, defaults to #t
-- @return multiple return values from the table
-- @function table.unpack
-- @see utils.unpack
@@ -2191,7 +2585,7 @@ end
-- @param ... any arguments
if not warn then -- luacheck: ignore
local enabled = false
- function warn(arg1, ...) -- luacheck: ignore
+ local function warn(arg1, ...) -- luacheck: ignore
if type(arg1) == "string" and arg1:sub(1, 1) == "@" then
-- control message
if arg1 == "@on" then
@@ -2209,6 +2603,8 @@ if not warn then -- luacheck: ignore
io.stderr:write("\n")
end
end
+ -- use rawset to bypass OpenResty's protection of global scope
+ rawset(_G, "warn", warn)
end
return compat
@@ -4122,7 +4518,7 @@ end
--- Return a list of all file names within an array which match a pattern.
-- @tab filenames An array containing file names.
--- @string pattern A shell pattern.
+-- @string pattern A shell pattern (see `fnmatch`).
-- @treturn List(string) List of matching file names.
-- @raise dir and mask must be strings
function dir.filter(filenames,pattern)
@@ -4152,11 +4548,12 @@ local function _listfiles(dirname,filemode,match)
end
--- return a list of all files in a directory which match a shell pattern.
--- @string dirname A directory. If not given, all files in current directory are returned.
--- @string mask A shell pattern. If not given, all files are returned.
+-- @string[opt='.'] dirname A directory.
+-- @string[opt] mask A shell pattern (see `fnmatch`). If not given, all files are returned.
-- @treturn {string} list of files
-- @raise dirname and mask must be strings
function dir.getfiles(dirname,mask)
+ dirname = dirname or '.'
assert_dir(1,dirname)
if mask then assert_string(2,mask) end
local match
@@ -4170,10 +4567,11 @@ function dir.getfiles(dirname,mask)
end
--- return a list of all subdirectories of the directory.
--- @string dirname A directory
+-- @string[opt='.'] dirname A directory.
-- @treturn {string} a list of directories
--- @raise dir must be a a valid directory
+-- @raise dir must be a valid directory
function dir.getdirectories(dirname)
+ dirname = dirname or '.'
assert_dir(1,dirname)
return _listfiles(dirname,false)
end
@@ -4567,13 +4965,14 @@ function dir.dirtree( d )
end
---- Recursively returns all the file starting at _path_. It can optionally take a shell pattern and
--- only returns files that match _shell_pattern_. If a pattern is given it will do a case insensitive search.
--- @string start_path A directory. If not given, all files in current directory are returned.
--- @string shell_pattern A shell pattern. If not given, all files are returned.
--- @treturn List(string) containing all the files found recursively starting at _path_ and filtered by _shell_pattern_.
+--- Recursively returns all the file starting at 'path'. It can optionally take a shell pattern and
+-- only returns files that match 'shell_pattern'. If a pattern is given it will do a case insensitive search.
+-- @string[opt='.'] start_path A directory.
+-- @string[opt='*'] shell_pattern A shell pattern (see `fnmatch`).
+-- @treturn List(string) containing all the files found recursively starting at 'path' and filtered by 'shell_pattern'.
-- @raise start_path must be a directory
function dir.getallfiles( start_path, shell_pattern )
+ start_path = start_path or '.'
assert_dir(1,start_path)
shell_pattern = shell_pattern or "*"
@@ -5113,7 +5512,7 @@ return function(env)
if prevenvmt then
_prev_index = prevenvmt.__index
if prevenvmt.__newindex then
- gmt.__index = prevenvmt.__newindex
+ gmt.__newindex = prevenvmt.__newindex
end
end
@@ -5634,7 +6033,7 @@ function lapp.process_options_string(str,args)
local enump = '|' .. enums .. '|'
vtype = 'string'
constraint = function(s)
- lapp.assert(enump:match('|'..s..'|'),
+ lapp.assert(enump:find('|'..s..'|', 1, true),
"value '"..s.."' not in "..enums
)
end
@@ -10428,9 +10827,10 @@ end,
--
-- See @{03-strings.md|the Guide}
--
--- Dependencies: `pl.utils`
+-- Dependencies: `pl.utils`, `pl.types`
-- @module pl.stringx
local utils = require 'pl.utils'
+local is_callable = require 'pl.types'.is_callable
local string = string
local find = string.find
local type,setmetatable,ipairs = type,setmetatable,ipairs
@@ -10441,10 +10841,13 @@ local sub = string.sub
local reverse = string.reverse
local concat = table.concat
local append = table.insert
+local remove = table.remove
local escape = utils.escape
local ceil, max = math.ceil, math.max
local assert_arg,usplit = utils.assert_arg,utils.split
local lstrip
+local unpack = utils.unpack
+local pack = utils.pack
local function assert_string (n,s)
assert_arg(n,s,'string')
@@ -10489,7 +10892,8 @@ function stringx.isalnum(s)
return find(s,'^%w+$') == 1
end
---- does s only contain spaces?
+--- does s only contain whitespace?
+-- Matches on pattern '%s' so matches space, newline, tabs, etc.
-- @string s a string
function stringx.isspace(s)
assert_string(1,s)
@@ -10631,10 +11035,14 @@ end
-- @usage stringx.expandtabs('\tone,two,three', 4) == ' one,two,three'
-- @usage stringx.expandtabs(' \tone,two,three', 4) == ' one,two,three'
function stringx.expandtabs(s,tabsize)
- assert_string(1,s)
- tabsize = tabsize or 8
- return (s:gsub("([^\t\r\n]*)\t", function(before_tab)
+ assert_string(1,s)
+ tabsize = tabsize or 8
+ return (s:gsub("([^\t\r\n]*)\t", function(before_tab)
+ if tabsize == 0 then
+ return before_tab
+ else
return before_tab .. (" "):rep(tabsize - #before_tab % tabsize)
+ end
end))
end
@@ -10808,36 +11216,37 @@ local function _strip(s,left,right,chrs)
return sub(s,f,t)
end
---- trim any whitespace on the left of s.
+--- trim any characters on the left of s.
-- @string s the string
-- @string[opt='%s'] chrs default any whitespace character,
--- but can be a string of characters to be trimmed
+-- but can be a string of characters to be trimmed
function stringx.lstrip(s,chrs)
assert_string(1,s)
return _strip(s,true,false,chrs)
end
lstrip = stringx.lstrip
---- trim any whitespace on the right of s.
+--- trim any characters on the right of s.
-- @string s the string
-- @string[opt='%s'] chrs default any whitespace character,
--- but can be a string of characters to be trimmed
+-- but can be a string of characters to be trimmed
function stringx.rstrip(s,chrs)
assert_string(1,s)
return _strip(s,false,true,chrs)
end
---- trim any whitespace on both left and right of s.
+--- trim any characters on both left and right of s.
-- @string s the string
-- @string[opt='%s'] chrs default any whitespace character,
--- but can be a string of characters to be trimmed
+-- but can be a string of characters to be trimmed
+-- @usage stringx.strip(' --== Hello ==-- ', "- =") --> 'Hello'
function stringx.strip(s,chrs)
assert_string(1,s)
return _strip(s,true,true,chrs)
end
---- Partioning Strings
--- @section partioning
+--- Partitioning Strings
+-- @section partitioning
--- split a string using a pattern. Note that at least one value will be returned!
-- @string s the string
@@ -10864,7 +11273,7 @@ end
--- partition the string using first occurance of a delimiter
-- @string s the string
--- @string ch delimiter
+-- @string ch delimiter (match as plain string, no patterns)
-- @return part before ch
-- @return ch
-- @return part after ch
@@ -10878,7 +11287,7 @@ end
--- partition the string p using last occurance of a delimiter
-- @string s the string
--- @string ch delimiter
+-- @string ch delimiter (match as plain string, no patterns)
-- @return part before ch
-- @return ch
-- @return part after ch
@@ -10904,6 +11313,250 @@ function stringx.at(s,idx)
return sub(s,idx,idx)
end
+
+--- Text handling
+-- @section text
+
+
+--- indent a multiline string.
+-- @tparam string s the (multiline) string
+-- @tparam integer n the size of the indent
+-- @tparam[opt=' '] string ch the character to use when indenting
+-- @return indented string
+function stringx.indent (s,n,ch)
+ assert_arg(1,s,'string')
+ assert_arg(2,n,'number')
+ local lines = usplit(s ,'\n')
+ local prefix = string.rep(ch or ' ',n)
+ for i, line in ipairs(lines) do
+ lines[i] = prefix..line
+ end
+ return concat(lines,'\n')..'\n'
+end
+
+
+--- dedent a multiline string by removing any initial indent.
+-- useful when working with [[..]] strings.
+-- Empty lines are ignored.
+-- @tparam string s the (multiline) string
+-- @return a string with initial indent zero.
+-- @usage
+-- local s = dedent [[
+-- One
+--
+-- Two
+--
+-- Three
+-- ]]
+-- assert(s == [[
+-- One
+--
+-- Two
+--
+-- Three
+-- ]])
+function stringx.dedent (s)
+ assert_arg(1,s,'string')
+ local lst = usplit(s,'\n')
+ if #lst>0 then
+ local ind_size = math.huge
+ for i, line in ipairs(lst) do
+ local i1, i2 = lst[i]:find('^%s*[^%s]')
+ if i1 and i2 < ind_size then
+ ind_size = i2
+ end
+ end
+ for i, line in ipairs(lst) do
+ lst[i] = lst[i]:sub(ind_size, -1)
+ end
+ end
+ return concat(lst,'\n')..'\n'
+end
+
+
+
+do
+ local buildline = function(words, size, breaklong)
+ -- if overflow is set, a word longer than size, will overflow the size
+ -- otherwise it will be chopped in line-length pieces
+ local line = {}
+ if #words[1] > size then
+ -- word longer than line
+ if not breaklong then
+ line[1] = words[1]
+ remove(words, 1)
+ else
+ line[1] = words[1]:sub(1, size)
+ words[1] = words[1]:sub(size + 1, -1)
+ end
+ else
+ local len = 0
+ while words[1] and (len + #words[1] <= size) or
+ (len == 0 and #words[1] == size) do
+ if words[1] ~= "" then
+ line[#line+1] = words[1]
+ len = len + #words[1] + 1
+ end
+ remove(words, 1)
+ end
+ end
+ return stringx.strip(concat(line, " ")), words
+ end
+
+ --- format a paragraph into lines so that they fit into a line width.
+ -- It will not break long words by default, so lines can be over the length
+ -- to that extent.
+ -- @tparam string s the string to format
+ -- @tparam[opt=70] integer width the margin width
+ -- @tparam[opt=false] boolean breaklong if truthy, words longer than the width given will be forced split.
+ -- @return a list of lines (List object), use `fill` to return a string instead of a `List`.
+ -- @see pl.List
+ -- @see fill
+ stringx.wrap = function(s, width, breaklong)
+ s = s:gsub('\n',' ') -- remove line breaks
+ s = stringx.strip(s) -- remove leading/trailing whitespace
+ if s == "" then
+ return { "" }
+ end
+ width = width or 70
+ local out = {}
+ local words = usplit(s, "%s")
+ while words[1] do
+ out[#out+1], words = buildline(words, width, breaklong)
+ end
+ return makelist(out)
+ end
+end
+
+--- format a paragraph so that it fits into a line width.
+-- @tparam string s the string to format
+-- @tparam[opt=70] integer width the margin width
+-- @tparam[opt=false] boolean breaklong if truthy, words longer than the width given will be forced split.
+-- @return a string, use `wrap` to return a list of lines instead of a string.
+-- @see wrap
+function stringx.fill (s,width,breaklong)
+ return concat(stringx.wrap(s,width,breaklong),'\n') .. '\n'
+end
+
+--- Template
+-- @section Template
+
+
+local function _substitute(s,tbl,safe)
+ local subst
+ if is_callable(tbl) then
+ subst = tbl
+ else
+ function subst(f)
+ local s = tbl[f]
+ if not s then
+ if safe then
+ return f
+ else
+ error("not present in table "..f)
+ end
+ else
+ return s
+ end
+ end
+ end
+ local res = gsub(s,'%${([%w_]+)}',subst)
+ return (gsub(res,'%$([%w_]+)',subst))
+end
+
+
+
+local Template = {}
+stringx.Template = Template
+Template.__index = Template
+setmetatable(Template, {
+ __call = function(obj,tmpl)
+ return Template.new(tmpl)
+ end
+})
+
+--- Creates a new Template class.
+-- This is a shortcut to `Template.new(tmpl)`.
+-- @tparam string tmpl the template string
+-- @function Template
+-- @treturn Template
+function Template.new(tmpl)
+ assert_arg(1,tmpl,'string')
+ local res = {}
+ res.tmpl = tmpl
+ setmetatable(res,Template)
+ return res
+end
+
+--- substitute values into a template, throwing an error.
+-- This will throw an error if no name is found.
+-- @tparam table tbl a table of name-value pairs.
+-- @return string with place holders substituted
+function Template:substitute(tbl)
+ assert_arg(1,tbl,'table')
+ return _substitute(self.tmpl,tbl,false)
+end
+
+--- substitute values into a template.
+-- This version just passes unknown names through.
+-- @tparam table tbl a table of name-value pairs.
+-- @return string with place holders substituted
+function Template:safe_substitute(tbl)
+ assert_arg(1,tbl,'table')
+ return _substitute(self.tmpl,tbl,true)
+end
+
+--- substitute values into a template, preserving indentation. <br>
+-- If the value is a multiline string _or_ a template, it will insert
+-- the lines at the correct indentation. <br>
+-- Furthermore, if a template, then that template will be substituted
+-- using the same table.
+-- @tparam table tbl a table of name-value pairs.
+-- @return string with place holders substituted
+function Template:indent_substitute(tbl)
+ assert_arg(1,tbl,'table')
+ if not self.strings then
+ self.strings = usplit(self.tmpl,'\n')
+ end
+
+ -- the idea is to substitute line by line, grabbing any spaces as
+ -- well as the $var. If the value to be substituted contains newlines,
+ -- then we split that into lines and adjust the indent before inserting.
+ local function subst(line)
+ return line:gsub('(%s*)%$([%w_]+)',function(sp,f)
+ local subtmpl
+ local s = tbl[f]
+ if not s then error("not present in table "..f) end
+ if getmetatable(s) == Template then
+ subtmpl = s
+ s = s.tmpl
+ else
+ s = tostring(s)
+ end
+ if s:find '\n' then
+ local lines = usplit(s, '\n')
+ for i, line in ipairs(lines) do
+ lines[i] = sp..line
+ end
+ s = concat(lines, '\n') .. '\n'
+ end
+ if subtmpl then
+ return _substitute(s, tbl)
+ else
+ return s
+ end
+ end)
+ end
+
+ local lines = {}
+ for i, line in ipairs(self.strings) do
+ lines[i] = subst(line)
+ end
+ return concat(lines,'\n')..'\n'
+end
+
+
+
--- Miscelaneous
-- @section misc
@@ -10936,79 +11589,148 @@ end
stringx.capitalize = stringx.title
-local ellipsis = '...'
-local n_ellipsis = #ellipsis
-
---- Return a shortened version of a string.
--- Fits string within w characters. Removed characters are marked with ellipsis.
--- @string s the string
--- @int w the maxinum size allowed
--- @bool tail true if we want to show the end of the string (head otherwise)
--- @usage ('1234567890'):shorten(8) == '12345...'
--- @usage ('1234567890'):shorten(8, true) == '...67890'
--- @usage ('1234567890'):shorten(20) == '1234567890'
-function stringx.shorten(s,w,tail)
- assert_string(1,s)
- if #s > w then
- if w < n_ellipsis then return ellipsis:sub(1,w) end
- if tail then
- local i = #s - w + 1 + n_ellipsis
- return ellipsis .. s:sub(i)
- else
- return s:sub(1,w-n_ellipsis) .. ellipsis
- end
- end
- return s
+do
+ local ellipsis = '...'
+ local n_ellipsis = #ellipsis
+
+ --- Return a shortened version of a string.
+ -- Fits string within w characters. Removed characters are marked with ellipsis.
+ -- @string s the string
+ -- @int w the maxinum size allowed
+ -- @bool tail true if we want to show the end of the string (head otherwise)
+ -- @usage ('1234567890'):shorten(8) == '12345...'
+ -- @usage ('1234567890'):shorten(8, true) == '...67890'
+ -- @usage ('1234567890'):shorten(20) == '1234567890'
+ function stringx.shorten(s,w,tail)
+ assert_string(1,s)
+ if #s > w then
+ if w < n_ellipsis then return ellipsis:sub(1,w) end
+ if tail then
+ local i = #s - w + 1 + n_ellipsis
+ return ellipsis .. s:sub(i)
+ else
+ return s:sub(1,w-n_ellipsis) .. ellipsis
+ end
+ end
+ return s
+ end
end
---- Utility function that finds any patterns that match a long string's an open or close.
--- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with.
--- Right now, it simply returns the greatest number of them found.
--- @param s The string
--- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches.
-local function has_lquote(s)
- local lstring_pat = '([%[%]])(=*)%1'
- local equals, new_equals, _
- local finish = 1
- repeat
- _, finish, _, new_equals = s:find(lstring_pat, finish)
- if new_equals then
- equals = max(equals or 0, #new_equals)
- end
- until not new_equals
- return equals
+do
+ -- Utility function that finds any patterns that match a long string's an open or close.
+ -- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with.
+ -- Right now, it simply returns the greatest number of them found.
+ -- @param s The string
+ -- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches.
+ local function has_lquote(s)
+ local lstring_pat = '([%[%]])(=*)%1'
+ local equals, new_equals, _
+ local finish = 1
+ repeat
+ _, finish, _, new_equals = s:find(lstring_pat, finish)
+ if new_equals then
+ equals = max(equals or 0, #new_equals)
+ end
+ until not new_equals
+
+ return equals
+ end
+
+ --- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result.
+ -- @param s The string to be quoted.
+ -- @return The quoted string.
+ function stringx.quote_string(s)
+ assert_string(1,s)
+ -- Find out if there are any embedded long-quote sequences that may cause issues.
+ -- This is important when strings are embedded within strings, like when serializing.
+ -- Append a closing bracket to catch unfinished long-quote sequences at the end of the string.
+ local equal_signs = has_lquote(s .. "]")
+
+ -- Note that strings containing "\r" can't be quoted using long brackets
+ -- as Lua lexer converts all newlines to "\n" within long strings.
+ if (s:find("\n") or equal_signs) and not s:find("\r") then
+ -- If there is an embedded sequence that matches a long quote, then
+ -- find the one with the maximum number of = signs and add one to that number.
+ equal_signs = ("="):rep((equal_signs or -1) + 1)
+ -- Long strings strip out leading newline. We want to retain that, when quoting.
+ if s:find("^\n") then s = "\n" .. s end
+ local lbracket, rbracket =
+ "[" .. equal_signs .. "[",
+ "]" .. equal_signs .. "]"
+ s = lbracket .. s .. rbracket
+ else
+ -- Escape funny stuff. Lua 5.1 does not handle "\r" correctly.
+ s = ("%q"):format(s):gsub("\r", "\\r")
+ end
+ return s
+ end
end
---- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result.
--- @param s The string to be quoted.
--- @return The quoted string.
-function stringx.quote_string(s)
- assert_string(1,s)
- -- Find out if there are any embedded long-quote sequences that may cause issues.
- -- This is important when strings are embedded within strings, like when serializing.
- -- Append a closing bracket to catch unfinished long-quote sequences at the end of the string.
- local equal_signs = has_lquote(s .. "]")
-
- -- Note that strings containing "\r" can't be quoted using long brackets
- -- as Lua lexer converts all newlines to "\n" within long strings.
- if (s:find("\n") or equal_signs) and not s:find("\r") then
- -- If there is an embedded sequence that matches a long quote, then
- -- find the one with the maximum number of = signs and add one to that number.
- equal_signs = ("="):rep((equal_signs or -1) + 1)
- -- Long strings strip out leading newline. We want to retain that, when quoting.
- if s:find("^\n") then s = "\n" .. s end
- local lbracket, rbracket =
- "[" .. equal_signs .. "[",
- "]" .. equal_signs .. "]"
- s = lbracket .. s .. rbracket
+
+--- Python-style formatting operator.
+-- Calling `text.format_operator()` overloads the % operator for strings to give
+-- Python/Ruby style formated output.
+-- This is extended to also do template-like substitution for map-like data.
+--
+-- Note this goes further than the original, and will allow these cases:
+--
+-- 1. a single value
+-- 2. a list of values
+-- 3. a map of var=value pairs
+-- 4. a function, as in gsub
+--
+-- For the second two cases, it uses $-variable substituion.
+--
+-- When called, this function will monkey-patch the global `string` metatable by
+-- adding a `__mod` method.
+--
+-- See <a href="http://lua-users.org/wiki/StringInterpolation">the lua-users wiki</a>
+--
+-- @usage
+-- require 'pl.text'.format_operator()
+-- local out1 = '%s = %5.3f' % {'PI',math.pi} --> 'PI = 3.142'
+-- local out2 = '$name = $value' % {name='dog',value='Pluto'} --> 'dog = Pluto'
+function stringx.format_operator()
+
+ local format = string.format
+
+ -- a more forgiving version of string.format, which applies
+ -- tostring() to any value with a %s format.
+ local function formatx (fmt,...)
+ local args = pack(...)
+ local i = 1
+ for p in fmt:gmatch('%%.') do
+ if p == '%s' and type(args[i]) ~= 'string' then
+ args[i] = tostring(args[i])
+ end
+ i = i + 1
+ end
+ return format(fmt,unpack(args))
+ end
+
+ local function basic_subst(s,t)
+ return (s:gsub('%$([%w_]+)',t))
+ end
+
+ getmetatable("").__mod = function(a, b)
+ if b == nil then
+ return a
+ elseif type(b) == "table" and getmetatable(b) == nil then
+ if #b == 0 then -- assume a map-like table
+ return _substitute(a,b,true)
+ else
+ return formatx(a,unpack(b))
+ end
+ elseif type(b) == 'function' then
+ return basic_subst(a,b)
else
- -- Escape funny stuff. Lua 5.1 does not handle "\r" correctly.
- s = ("%q"):format(s):gsub("\r", "\\r")
+ return formatx(a,b)
end
- return s
+ end
end
+--- import the stringx functions into the global string (meta)table
function stringx.import()
utils.import(stringx,string)
end
@@ -12413,246 +13135,26 @@ end,
-- libraries, see string.Template). It also provides similar functions to those
-- found in the textwrap module.
--
--- See @{03-strings.md.String_Templates|the Guide}.
---
--- Calling `text.format_operator()` overloads the % operator for strings to give Python/Ruby style formated output.
--- This is extended to also do template-like substitution for map-like data.
+-- IMPORTANT: this module has been deprecated and will be removed in a future
+-- version (2.0). The contents of this module have moved to the `pl.stringx`
+-- module.
--
--- > require 'pl.text'.format_operator()
--- > = '%s = %5.3f' % {'PI',math.pi}
--- PI = 3.142
--- > = '$name = $value' % {name='dog',value='Pluto'}
--- dog = Pluto
+-- See @{03-strings.md.String_Templates|the Guide}.
--
--- Dependencies: `pl.utils`, `pl.types`
+-- Dependencies: `pl.stringx`, `pl.utils`
-- @module pl.text
-local gsub = string.gsub
-local concat,append = table.concat,table.insert
-local utils = require 'pl.utils'
-local bind1,usplit,assert_arg = utils.bind1,utils.split,utils.assert_arg
-local is_callable = require 'pl.types'.is_callable
-local unpack = utils.unpack
-
-local text = {}
-
-
-local function makelist(l)
- return setmetatable(l, require('pl.List'))
-end
-
-local function lstrip(str) return (str:gsub('^%s+','')) end
-local function strip(str) return (lstrip(str):gsub('%s+$','')) end
-local function split(s,delim) return makelist(usplit(s,delim)) end
-
-local function imap(f,t,...)
- local res = {}
- for i = 1,#t do res[i] = f(t[i],...) end
- return res
-end
-
-local function _indent (s,sp)
- local sl = split(s,'\n')
- return concat(imap(bind1('..',sp),sl),'\n')..'\n'
-end
-
---- indent a multiline string.
--- @param s the string
--- @param n the size of the indent
--- @param ch the character to use when indenting (default ' ')
--- @return indented string
-function text.indent (s,n,ch)
- assert_arg(1,s,'string')
- assert_arg(2,n,'number')
- return _indent(s,string.rep(ch or ' ',n))
-end
-
---- dedent a multiline string by removing any initial indent.
--- useful when working with [[..]] strings.
--- @param s the string
--- @return a string with initial indent zero.
-function text.dedent (s)
- assert_arg(1,s,'string')
- local sl = split(s,'\n')
- local _,i2 = (#sl>0 and sl[1] or ''):find('^%s*')
- sl = imap(string.sub,sl,i2+1)
- return concat(sl,'\n')..'\n'
-end
-
---- format a paragraph into lines so that they fit into a line width.
--- It will not break long words, so lines can be over the length
--- to that extent.
--- @param s the string
--- @param width the margin width, default 70
--- @return a list of lines (List object)
--- @see pl.List
-function text.wrap (s,width)
- assert_arg(1,s,'string')
- width = width or 70
- s = s:gsub('\n',' ')
- local i,nxt = 1
- local lines,line = {}
- while i < #s do
- nxt = i+width
- if s:find("[%w']",nxt) then -- inside a word
- nxt = s:find('%W',nxt+1) -- so find word boundary
- end
- line = s:sub(i,nxt)
- i = i + #line
- append(lines,strip(line))
- end
- return makelist(lines)
-end
-
---- format a paragraph so that it fits into a line width.
--- @param s the string
--- @param width the margin width, default 70
--- @return a string
--- @see wrap
-function text.fill (s,width)
- return concat(text.wrap(s,width),'\n') .. '\n'
-end
-
-local Template = {}
-text.Template = Template
-Template.__index = Template
-setmetatable(Template, {
- __call = function(obj,tmpl)
- return Template.new(tmpl)
- end})
-
-function Template.new(tmpl)
- assert_arg(1,tmpl,'string')
- local res = {}
- res.tmpl = tmpl
- setmetatable(res,Template)
- return res
-end
-
-local function _substitute(s,tbl,safe)
- local subst
- if is_callable(tbl) then
- subst = tbl
- else
- function subst(f)
- local s = tbl[f]
- if not s then
- if safe then
- return f
- else
- error("not present in table "..f)
- end
- else
- return s
- end
- end
- end
- local res = gsub(s,'%${([%w_]+)}',subst)
- return (gsub(res,'%$([%w_]+)',subst))
-end
-
---- substitute values into a template, throwing an error.
--- This will throw an error if no name is found.
--- @param tbl a table of name-value pairs.
-function Template:substitute(tbl)
- assert_arg(1,tbl,'table')
- return _substitute(self.tmpl,tbl,false)
-end
-
---- substitute values into a template.
--- This version just passes unknown names through.
--- @param tbl a table of name-value pairs.
-function Template:safe_substitute(tbl)
- assert_arg(1,tbl,'table')
- return _substitute(self.tmpl,tbl,true)
-end
-
---- substitute values into a template, preserving indentation. <br>
--- If the value is a multiline string _or_ a template, it will insert
--- the lines at the correct indentation. <br>
--- Furthermore, if a template, then that template will be subsituted
--- using the same table.
--- @param tbl a table of name-value pairs.
-function Template:indent_substitute(tbl)
- assert_arg(1,tbl,'table')
- if not self.strings then
- self.strings = split(self.tmpl,'\n')
- end
- -- the idea is to substitute line by line, grabbing any spaces as
- -- well as the $var. If the value to be substituted contains newlines,
- -- then we split that into lines and adjust the indent before inserting.
- local function subst(line)
- return line:gsub('(%s*)%$([%w_]+)',function(sp,f)
- local subtmpl
- local s = tbl[f]
- if not s then error("not present in table "..f) end
- if getmetatable(s) == Template then
- subtmpl = s
- s = s.tmpl
- else
- s = tostring(s)
- end
- if s:find '\n' then
- s = _indent(s,sp)
- end
- if subtmpl then return _substitute(s,tbl)
- else return s
- end
- end)
- end
- local lines = imap(subst,self.strings)
- return concat(lines,'\n')..'\n'
-end
-
-------- Python-style formatting operator ------
--- (see <a href="http://lua-users.org/wiki/StringInterpolation">the lua-users wiki</a>) --
-
-function text.format_operator()
-
- local format = string.format
+local utils = require("pl.utils")
- -- a more forgiving version of string.format, which applies
- -- tostring() to any value with a %s format.
- local function formatx (fmt,...)
- local args = {...}
- local i = 1
- for p in fmt:gmatch('%%.') do
- if p == '%s' and type(args[i]) ~= 'string' then
- args[i] = tostring(args[i])
- end
- i = i + 1
- end
- return format(fmt,unpack(args))
- end
-
- local function basic_subst(s,t)
- return (s:gsub('%$([%w_]+)',t))
- end
-
- -- Note this goes further than the original, and will allow these cases:
- -- 1. a single value
- -- 2. a list of values
- -- 3. a map of var=value pairs
- -- 4. a function, as in gsub
- -- For the second two cases, it uses $-variable substituion.
- getmetatable("").__mod = function(a, b)
- if b == nil then
- return a
- elseif type(b) == "table" and getmetatable(b) == nil then
- if #b == 0 then -- assume a map-like table
- return _substitute(a,b,true)
- else
- return formatx(a,unpack(b))
- end
- elseif type(b) == 'function' then
- return basic_subst(a,b)
- else
- return formatx(a,b)
- end
- end
-end
+utils.raise_deprecation {
+ source = "Penlight " .. utils._VERSION,
+ message = "the contents of module 'pl.text' has moved into 'pl.stringx'",
+ version_removed = "2.0.0",
+ deprecated_after = "1.11.0",
+ no_trace = true,
+}
-return text
+return require "pl.stringx"
end,
@@ -12919,7 +13421,10 @@ local format = string.format
local compat = require 'pl.compat'
local stdout = io.stdout
local append = table.insert
+local concat = table.concat
local _unpack = table.unpack -- always injected by 'compat'
+local find = string.find
+local sub = string.sub
local is_windows = compat.is_windows
local err_mode = 'default'
@@ -12928,7 +13433,7 @@ local operators
local _function_factories = {}
-local utils = { _VERSION = "1.11.0" }
+local utils = { _VERSION = "1.12.0" }
for k, v in pairs(compat) do utils[k] = v end
--- Some standard patterns
@@ -12970,7 +13475,7 @@ utils.pack = table.pack -- added here to be symmetrical with unpack
-- that this one DOES honor the `n` field in the table `t`, such that it is 'nil-safe'.
-- @param t table to unpack
-- @param[opt] i index from which to start unpacking, defaults to 1
--- @param[opt] t index of the last element to unpack, defaults to `t.n` or `#t`
+-- @param[opt] j index of the last element to unpack, defaults to `t.n` or else `#t`
-- @return multiple return values from the table
-- @function utils.unpack
-- @see compat.unpack
@@ -13156,6 +13661,68 @@ function utils.assert_arg (n,val,tp,verify,msg,lev)
return val
end
+--- creates an Enum table.
+-- This helps prevent magic strings in code by throwing errors for accessing
+-- non-existing values.
+--
+-- Calling on the object does the same, but returns a soft error; `nil + err`.
+--
+-- The values are equal to the keys. The enum object is
+-- read-only.
+-- @param ... strings that make up the enumeration.
+-- @return Enum object
+-- @usage -- accessing at runtime
+-- local obj = {}
+-- obj.MOVEMENT = utils.enum("FORWARD", "REVERSE", "LEFT", "RIGHT")
+--
+-- if current_movement == obj.MOVEMENT.FORWARD then
+-- -- do something
+--
+-- elseif current_movement == obj.MOVEMENT.REVERES then
+-- -- throws error due to typo 'REVERES', so a silent mistake becomes a hard error
+-- -- "'REVERES' is not a valid value (expected one of: 'FORWARD', 'REVERSE', 'LEFT', 'RIGHT')"
+--
+-- end
+-- @usage -- validating user-input
+-- local parameter = "...some user provided option..."
+-- local ok, err = obj.MOVEMENT(parameter) -- calling on the object
+-- if not ok then
+-- print("bad 'parameter', " .. err)
+-- os.exit(1)
+-- end
+function utils.enum(...)
+ local lst = utils.pack(...)
+ utils.assert_arg(1, lst[1], "string") -- at least 1 string
+
+ local enum = {}
+ for i, value in ipairs(lst) do
+ utils.assert_arg(i, value, "string")
+ enum[value] = value
+ end
+
+ local valid = "(expected one of: '" .. concat(lst, "', '") .. "')"
+ setmetatable(enum, {
+ __index = function(self, key)
+ error(("'%s' is not a valid value %s"):format(tostring(key), valid), 2)
+ end,
+ __newindex = function(self, key, value)
+ error("the Enum object is read-only", 2)
+ end,
+ __call = function(self, key)
+ if type(key) == "string" then
+ local v = rawget(self, key)
+ if v then
+ return v
+ end
+ end
+ return nil, ("'%s' is not a valid value %s"):format(tostring(key), valid)
+ end
+ })
+
+ return enum
+end
+
+
--- process a function argument.
-- This is used throughout Penlight and defines what is meant by a function:
-- Something that is callable, or an operator string as defined by <code>pl.operator</code>,
@@ -13367,7 +13934,7 @@ function utils.quote_arg(argument)
r[i] = utils.quote_arg(arg)
end
- return table.concat(r, " ")
+ return concat(r, " ")
end
-- only a single argument
if is_windows then
@@ -13436,7 +14003,6 @@ end
-- @see splitv
function utils.split(s,re,plain,n)
utils.assert_string(1,s)
- local find,sub,append = string.find, string.sub, table.insert
local i1,ls = 1,{}
if not re then re = '%s+' end
if re == '' then return {s} end
diff --git a/macros/luatex/generic/penlight/penlight.pdf b/macros/luatex/generic/penlight/penlight.pdf
index 1edb8bf2b5..a8e166a1ae 100644
--- a/macros/luatex/generic/penlight/penlight.pdf
+++ b/macros/luatex/generic/penlight/penlight.pdf
Binary files differ
diff --git a/macros/luatex/generic/penlight/penlight.sty b/macros/luatex/generic/penlight/penlight.sty
index 9f0ead874f..b9f5a9490a 100644
--- a/macros/luatex/generic/penlight/penlight.sty
+++ b/macros/luatex/generic/penlight/penlight.sty
@@ -1,5 +1,5 @@
% Kale Ewasiuk (kalekje@gmail.com)
-% 2021-12-15
+% 2022-02-27
% Copyright (C) 2021 Kale Ewasiuk
%
% Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -22,7 +22,7 @@
% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
% OR OTHER DEALINGS IN THE SOFTWARE.
-\ProvidesPackage{penlight}[2021-12-15]
+\ProvidesPackage{penlight}[2022-02-27]
\RequirePackage{luacode}
@@ -34,9 +34,15 @@
penlight = require('penlight')
__PENLIGHT__ = 'penlight'
}}
+
+
\DeclareOption{stringx}{\luadirect{_G[__PENLIGHT__].stringx.import()}}
-\DeclareOption{format}{\luadirect{_G[__PENLIGHT__].text.format_operator()}}
+\DeclareOption{format}{\luadirect{_G[__PENLIGHT__].stringx.format_operator()}}
\DeclareOption{func}{\luadirect{_G[__PENLIGHT__].utils.import(__PENLIGHT__..'.func')}}
\DeclareOption{extras}{\luadirect{require('penlightextras')}}
+\DeclareOption{extrasnoglobals}{
+ __PL_NO_GLOBALS__ = true
+ \luadirect{require('penlightextras')
+ }}
\ProcessOptions*\relax
diff --git a/macros/luatex/generic/penlight/penlight.tex b/macros/luatex/generic/penlight/penlight.tex
index 79a5d08589..176dc4e557 100644
--- a/macros/luatex/generic/penlight/penlight.tex
+++ b/macros/luatex/generic/penlight/penlight.tex
@@ -1,5 +1,5 @@
% Kale Ewasiuk (kalekje@gmail.com)
-% 2021-12-15
+% 2022-02-27
% Copyright (C) 2021 Kale Ewasiuk
%
% Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,7 +25,7 @@
\documentclass[11pt,parskip=half]{scrartcl}
\setlength{\parindent}{0ex}
-\newcommand{\llcmd}[1]{\leavevmode\llap{\texttt{\detokenize{#1}\ }}}
+\newcommand{\llcmd}[1]{\leavevmode\llap{\texttt{\detokenize{#1}}}}
\newcommand{\cmd}[1]{\texttt{\detokenize{#1}}}
\newcommand{\qcmd}[1]{``\cmd{#1}''}
\usepackage{url}
@@ -52,7 +52,7 @@
\date{\today}
-\RequirePackage{penlight}
+\RequirePackage[pl,extras]{penlight}
\title{penlight}
\subtitle{Lua libraries for use in LuaLaTeX}
@@ -60,7 +60,7 @@
\maketitle
The official documentation for the Lua library can be found here:\\
- \mbox{\url{https://stevedonovan.github.io/Penlight/api/manual/01-introduction.md.html#}}
+ \mbox{\url{https://lunarmodules.github.io/Penlight}}
\\
\subsection*{Required Package Option}
@@ -70,48 +70,91 @@
\texttt{penlight.XYZ} or \texttt{pl.XYZ}
+
+ \subsection*{texlua usage}
+If you want to use Penlight (and extras) with the \texttt{texlua} intrepreter (no document made, only for Lua files, useful for testing),
+you can access it by setting \cmd{__SKIP_TEX__ = true} and adding the package to path. For example:
+ \begin{verbatim}
+package.path = package.path .. ';'..'path/to/texmf/tex/latex/penlight/?.lua'
+penlight = require('penlight')
+-- below is optional
+__SKIP_TEX__ = true --only required if you want to use
+ --penlightextras without a LaTeX run
+-- __PL_NO_GLOBALS__ = true -- optional, skip global definitions
+require('penlightextras')
+\end{verbatim}
- \subsection*{Additional Package Options}
+\pagebreak
+
+
+\subsection*{Additional Package Options}
\noindent
- \begin{tabular}{lp{4.5in}}
+ \hspace*{-6ex}\begin{tabular}{lp{4.8in}}
\texttt{stringx} & will import additional string functions into the string meta table.\\
& this will be ran in pre-amble: \texttt{require('pl.stringx').import()}\\
- & \hspace*{-4em}\url{https://stevedonovan.github.io/Penlight/api/libraries/pl.stringx.html}\\\\
\texttt{format} & allows \% operator for Python-style string formating\\
- & this will be ran in pre-amble: \texttt{require('pl.text').format\_operator()}\\
- & \hspace*{-4em}\url{https://stevedonovan.github.io/Penlight/api/libraries/pl.text.html}\\\\
+ & this will be ran in pre-amble: \texttt{require('pl.stringx').format\_operator()}\\
+ & \mbox{\url{https://lunarmodules.github.io/Penlight/libraries/pl.stringx.html#format_operator}}
+ \\
\texttt{func} & allows placehold expressions eg. \texttt{\_1+1} to be used \\
& this will be ran in pre-amble: \texttt{penlight.utils.import('pl.func')}\\
- & \hspace*{-4em}\url{https://stevedonovan.github.io/Penlight/api/libraries/pl.func.html}\\\\
- \texttt{extras} & adds some additional functions. Adds functions to some Penlight sub-modules. The following Lua globals will be defined:
- \texttt{close\_bkt\_cnt,
- add\_bkt\_cnt,
- reset\_bkt\_cnt,
- \_NumBkts,
- \_xTrue,
- \_xFalse,
- \_xNoValue,
- \_\_SKIP\_TEX\_\_,
- hasval,
- mod, mod2,
- }\\
+ & \url{https://lunarmodules.github.io/Penlight/libraries/pl.func}\\
+ \texttt{\llap{extras}noglobals} & does the above three (\cmd{func,stringx,format}); adds some additional functions to \cmd{penlight} module; and adds the \cmd{pl.tex} sub-module.\\
+ \texttt{extras} & does the above \texttt{extrasnoglobals} but makes many of the functions global variables.
\end{tabular}
- \\\\
-
- If you want to use Penlight (and extras) with the \texttt{texlua} intrepreter (no document made, only for Lua files, useful for testing),
- you can access it by the following:
- \begin{verbatim}
- package.path = package.path .. ';'..'path/to/texmf/tex/latex/penlight/?.lua'
- penlight = require('penlight')
- __SKIP_TEX__ = true --only required if you want to use
- --penlightextras without a LaTeX run
- require('penlightextras')
- \end{verbatim}
+\subsection*{Extras}
+
+If \cmd{extras} is used, the following Lua globals will be defined:\\
+
+\subsubsection*{Misc stuff}
+\llcmd{__SKIP_TEX__} If using package with \cmd{texlua}, set this global before loading \cmd{penlight}\\
+\llcmd{__PL_NO_}\cmd{GLOBALS__} If using package with \cmd{texlua} and you don't want to set some globals (described in next sections), set this global before to \cmd{true} loading \cmd{penlight}\\
+\llcmd{hasval(x)} Python-like boolean testing\\
+\llcmd{COMP'xyz'()} Python-like comprehensions:\\\url{https://lunarmodules.github.io/Penlight/libraries/pl.comprehension.html}\\
+\llcmd{math.mod(n,d)}, \cmd{math.mod2(n)} math modulous\\
+\llcmd{string.}\cmd{totable(s)} string a table of characters\\
+\llcmd{kpairs(t), }\cmd{npairs(t)} iterate over keys only, or include nil value from table ipairs\\
+
+\llcmd{pl.utils.}\cmd{filterfiles}\cmd{(dir,filt,rec)} Get files from dir and apply glob-like filters. Set rec to \cmd{true} to include sub directories\\
+
+\pagebreak
+
+\subsubsection*{\cmd{pl.tex.} module is added}
+\llcmd{add_bkt}\cmd{_cnt(n), }\cmd{close_bkt_cnt(n), reset_bkt_cnt} functions to keep track of adding curly brackets as strings. \cmd{add} will return \cmd{n} (default 1) \{'s and increment a counter. \cmd{close} will return \cmd{n} \}'s (default will close all brackets) and decrement.\\
+\llcmd{_NumBkts} internal integer for tracking the number of brackets\\
+\llcmd{opencmd(cs)} prints \cmd{\cs}\{ and adds to the bracket counters.\\
+\\
+\llcmd{_xNoValue,}\cmd{_xTrue,_xFalse}: \cmd{xparse} equivalents for commands\\
+\\
+\llcmd{prt(x),prtn(x)} print without or with a newline at end. Tries to help with special characters or numbers printing.\\
+\llcmd{prtl(l),prtt(t)} print a literal string, or table\\
+\llcmd{wrt(x), wrtn(x)} write to log\\
+\llcmd{help_wrt}\cmd{(s1, s2)} pretty-print something to console. S2 is a flag to help you find.\\
+\llcmd{prt_array2d(tt)} pretty print a 2d array\\
+\\
+\llcmd{pkgwarn}\cmd{(pkg, msg1, msg2)} throw a package warning\\
+\llcmd{pkgerror}\cmd{(pkg, msg1, msg2, stop)} throw a package error. If stop is true, immediately ceases compile.\\
+\\
+\llcmd{defcmd}\cmd{(cs, val)} like \cmd{\gdef}\\
+\llcmd{newcmd}\cmd{(cs, val)} like \cmd{\newcommand}\\
+\llcmd{renewcmd}\cmd{(cs, val)} like \cmd{\renewcommand}\\
+\llcmd{prvcmd}\cmd{(cs, val)} like \cmd{\providecommand}\\
+\llcmd{deccmd}\cmd{(cs, dft, overwrite)} declare a command. If \cmd{dft} (default) is \cmd{nil}, \cmd{cs} is set
+to a package warning saying \cmd{'cs' was declared and used in document, but never set}. If \cmd{overwrite}
+is true, it will overwrite an existing command (using \cmd{defcmd}), otherwise, it will throw error like \cmd{newcmd}.\\
+
+
+
+\subsubsection*{global extras}
+If \cmd{extras} is used and NOT \cmd{extrasnoglobals}, then some globals are set.\\
+All \cmd{pl.tex} modules are added.\\
+\cmd{hasval}, \cmd{COMP}, \cmd{kpairs}, \cmd{npairs} are globals.\\
+\cmd{pl.tablex} functions are added to the \cmd{table} table.\\
\section*{}
Disclaimer: I am not the author of the Lua Penlight library.
diff --git a/macros/luatex/generic/penlight/penlightextras.lua b/macros/luatex/generic/penlight/penlightextras.lua
index 3479eb3fa0..0cdec03763 100644
--- a/macros/luatex/generic/penlight/penlightextras.lua
+++ b/macros/luatex/generic/penlight/penlightextras.lua
@@ -1,172 +1,264 @@
+
+__SKIP_TEX__ = __SKIP_TEX__ or false --if declared true before here, it will use regular print functions
+-- (for troubleshooting with texlua instead of actual use in lua latex)
+
+__PL_NO_GLOBALS__ = __PL_NO_GLOBALS__ or false
+
-- requires penlight
local pl = _G['penlight'] or _G['pl'] -- penlight for this namespace is pl
-local bind = bind or pl.func.bind
--- some bonus string operations, % text operator, and functional programmng
+
+-- some bonus string operations, % text operator, and functional programming
pl.stringx.import()
pl.text.format_operator()
-pl.utils.import('pl.func')
+pl.utils.import('pl.func') -- allow placeholder expressions _1 +1 etc.
+
+pl.COMP = require'pl.comprehension'.new() -- for comprehensions
+
+-- http://lua-users.org/wiki/SplitJoin -- todo read me!!
+
+pl.tex = {} -- adding a sub-module for tex related stuff
+
+local bind = bind or pl.func.bind
+
+
+function pl.hasval(x) -- if something has value
+ if (type(x) == 'function') or (type(x) == 'CFunction') or (type(x) == 'userdata') then
+ return true
+ elseif (x == nil) or (x == false) or (x == 0) or (x == '') or (x == {}) then
+ return false
+ elseif (type(x) ~= 'boolean') and (type(x) ~= 'number') and (type(x) ~= 'string') then -- something else? maybe ths not needed
+ if #x == 0 then -- one more check, probably no needed though, I was trying to cover other classes but they all tables
+ return false
+ else
+ return true
+ end
+ end
+ return true
+end
+
+
+-- Some simple and helpful LaTeX functions -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+-- xparse defaults
+pl.tex._xTrue = '\\BooleanTrue '
+pl.tex._xFalse = '\\BooleanFalse '
+pl.tex._xNoValue = '-NoValue-'
+
+--Generic LuaLaTeX utilities for print commands or environments
+
+if not __SKIP_TEX__ then
+ local function check_special_chars(s) -- todo extend to toher special chars?
+ if type(s) == 'string' then
+ if string.find(s, '[\n\r\t\0]') then
+ pl.tex.pkgwarn('penlight', 'printing string with special (eg. newline) char, possible unexpected behaviour on string: '..s)
+ end
+ end
+ end
-function help_wrt(s1, s2) -- helpful printing, makes it easy to debug, s1 is object, s2 is note
- local wrt = wrt or texio.write_nl
- local wrt = wrt or print
+ -- NOTE: usage is a bit different than default. If number is first arg, you CANT change catcode.
+ -- We don't need that under normal use, use tex.print or tex.sprint if you need
+ function pl.tex.prt(s, ...) -- print something, no new line after
+ check_special_chars(s)
+ if type(s) == 'number' then s = tostring(s) end
+ tex.sprint(s, ...) --can print lists as well, but will NOT put new line between them or anything printed
+ end
+
+ function pl.tex.prtn(s, ...) -- print with new line after, can print lists or nums. C-function not in Lua, apparantly
+ s = s or ''
+ check_special_chars(s)
+ if type(s) == 'number' then s = tostring(s) end
+ tex.print(s, ...)
+ end
+
+ pl.tex.wrt = texio.write
+ pl.tex.wrtn = texio.write_nl
+else
+ pl.tex.prt = io.write
+ pl.tex.prtn = print --print with new line
+ pl.tex.wrt = io.write
+ pl.tex.wrtn = io.write_nl
+end
+
+function pl.tex.prtl(str) -- prints a literal/lines string in latex, adds new line between them
+ for line in str:gmatch"[^\n]*" do -- gets all characters up to a new line
+ pl.tex.prtn(line)
+ end
+end
+
+-- todo option to specify between character? one for first table, on for recursives?
+function pl.tex.prtt(tab, d1, d2) -- prints a table with new line between each item
+ d1 = d1 or ''
+ d2 = d2 or '\\leavevmode\\\\'
+ for _, t in pairs(tab) do --
+ if type(t) ~= 'table' then
+ if d1 == '' then
+ pl.tex.prtn(t)
+ else
+ pl.tex.prt(t, d1)
+ end
+ else
+ pl.tex.prtn(d2)
+ pl.tex.prtt(t,d1,d2)
+ end
+ end
+end
+
+function pl.tex.help_wrt(s1, s2) -- helpful printing, makes it easy to debug, s1 is object, s2 is note
+ local wrt2 = wrt or texio.write_nl or print
s2 = s2 or ''
- wrt('\nvvvvv '..s2..'\n')
+ wrt2('\nvvvvv '..s2..'\n')
if type(s1) == 'table' then
- wrt(pl.pretty.write(s1))
+ wrt2(pl.pretty.write(s1))
else
- wrt(tostring(s1))
+ wrt2(tostring(s1))
end
- wrt('\n^^^^^\n')
+ wrt2('\n^^^^^\n')
end
-function prt_array2d(t)
+function pl.tex.prt_array2d(t)
for _, r in ipairs(t) do
local s = ''
for _, v in ipairs(r) do
s = s.. tostring(v)..', '
end
- print(s)
+ pl.tex.prt(s)
+ pl.tex.prt('\n')
end
end
-- -- -- -- --
-
--- -- -- -- functions below are helpers for arrays and 2d
-
-local function compare_elements(a, b, op, ele)
- op = op or pl.oper.gt
- ele = ele or 1
- return op(a[ele], b[ele])
+function pl.tex.pkgwarn(pkg, msg1, msg2)
+ pkg = pkg or ''
+ msg1 = msg1 or ''
+ msg2 = msg2 or ''
+ tex.sprint('\\PackageWarning{'..pkg..'}{'..msg1..'}{'..msg2..'}')
end
-local function comp_2ele_func(op, ele) -- make a 2 element comparison function,
- --sort with function on element nnum
- return bind(compare_elements, _1, _2, op, ele)
+function pl.tex.pkgerror(pkg, msg1, msg2, stop)
+ pkg = pkg or ''
+ msg1 = msg1 or ''
+ msg2 = msg2 or ''
+ stop = pl.hasval(stop)
+ tex.sprint('\\PackageError{'..pkg..'}{'..msg1..'}{'..msg2..'}')
+ if stop then tex.sprint('\\stop') end -- stop on the spot (say that 10 times)
end
--- -- -- --
+--definition helpers -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+function pl.tex.defcmd(cs, val) -- simple definitions
+ val = val or ''
+ token.set_macro(cs, val, 'global')
+end
-local function check_index(ij, rc) -- converts array index to positive value if negative
- if type(ij) ~= 'number' then
- return 1
+function pl.tex.prvcmd(cs, val) -- provide command via lua
+ if token.is_defined(cs) then
+ -- do nothing if token is defined already --pkgwarn('penlight', 'Definition '..cs..' is being overwritten')
else
- if ij < 0 then
- ij = rc + ij + 1
- elseif ij > rc then
- ij = rc
- end
- return ij
+ pl.tex.defcmd(cs, val)
end
end
-local function check_slice(M, i1, j1, i2, j2) -- ensure a slice is valid; i.e. all positive numbers
- r, c = pl.array2d.size(M)
- i1 = check_index(i1, r)
- i2 = check_index(i2, r)
- j1 = check_index(j1, c)
- j2 = check_index(j2, c)
- return i1, j1, i2, j2
-end
-local function check_func(func) -- check if a function is a PE, if so, make it a function
- if type(func) ~= 'function' then
- __func = I(func)
+function pl.tex.newcmd(cs, val) -- provide command via lua
+ if token.is_defined(cs) then
+ pl.tex.pkgerror('penlight: newcmd',cs..' already defined')
+ else
+ pl.tex.defcmd(cs, val)
end
- return __func
end
-
--- -- -- -- -- -- --
--- -- -- -- functions below extend the array2d module
-
-
-function pl.array2d.map_slice1(func, L, i1, i2) -- map a function to a slice of an array, can use PlcExpr
- i2 = i2 or i1
- local len = #L
- i1 = check_index(i1, len)
- i2 = check_index(i2, len)
- func = check_func(func)
- for i in pl.seq.range(i1,i2) do
- L[i] = func(L[i])
- end
- return L
+function pl.tex.renewcmd(cs, val) -- provide command via lua
+ if token.is_defined(cs) then
+ pl.tex.defcmd(cs, val)
+ else
+ pl.tex.pkgerror('penlight: renewcmd',cs..' not defined')
+ end
end
-function pl.array2d.map_slice2(func, M, i1, j1, i2, j2) -- map a function to a slice of a Matrix
- i1, j1, i2, j2 = check_slice(M, i1, j1, i2, j2)
- --for i,j in array2d.iter(M, true, i1, j1, i2, j2) do --todo this did not work, penlight may have fixed this
- func = check_func(func)
- for i in pl.seq.range(i1,i2) do
- for j in pl.seq.range(j1,j2) do
- M[i][j] = func(M[i][j])
- end
+function pl.tex.deccmd(cs, def, overwrite) -- declare a definition, placeholder throws an error if it used but not set!
+ overwrite = pl.hasval(overwrite)
+ local decfun
+ if overwrite then decfun = pl.tex.defcmd else decfun = pl.tex.newcmd end
+ if def == nil then
+ decfun(cs, pkgerror('penlight', cs..' was declared and used in document, but never set'))
+ else
+ decfun(cs, def)
end
- return M
end
-function pl.array2d.map_columns(func, M, j1, j2) -- map function to columns of matrix
- j2 = j2 or j1
- return map_slice2(func, M, 1, j1, -1, j2)
-end
-function pl.array2d.map_rows(func, M, i1, i2) -- map function to rows of matrix
- i2 = i2 or i1
- return map_slice2(func, M, i1, 1, i2, -1)
-end
+--
+-- -- todo add and improve this, options for args?
+--local function defcmd_nest(cs) -- for option if you'd like your commands under a parent ex. \csparent{var}
+-- tex.print('\\gdef\\'..cs..'#1{\\csname '..var..'--#1--\\endcsname}')
+--end
+--
+--
+--local function defcmd(cs, val, nargs)
+-- if (nargs == nil) or (args == 0) then
+-- token.set_macro(cs, tostring(val), 'global')
+-- else
+-- local args = '#1'
+-- tex.print('\\gdef\\'..cs..args..'{'..val..'}')
+-- -- todo https://tex.stackexchange.com/questions/57551/create-a-capitalized-macro-token-using-csname
+-- -- \expandafter\gdef\csname Two\endcsname#1#2{1:#1, two:#2} --todo do it like this
+-- end
+--end
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--- -- -- -- -- -- -- --
-function pl.array2d.sortOP(M, op, ele) -- sort a 2d array based on operator criteria, ele is column, ie sort on which element
- M_new = {}
- for row in pl.seq.sort(M, comp_2ele_func(op, ele)) do
- M_new[#M_new+1] = row
- end
- return M_new
+
+-- when nesting commands, this makes it helpful to not worry about brackets
+pl.tex._NumBkts = 0
+--prt(opencmd('textbf')..opencmd('texttt')..'bold typwriter'..close_bkt_cnt())
+
+function pl.tex.opencmd(cmd)
+ return '\\'..cmd..add_bkt_cnt()
end
-function pl.array2d.like(M1, v)
- v = v or 0
- r, c = pl.array2d.size(M1)
- return pl.array2d.new(r,c,v)
+function pl.tex.reset_bkt_cnt(n)
+ n = n or 0
+ _NumBkts = n
end
-function pl.array2d.from_table(t) -- turns a labelled table to a 2d, label-free array
- t_new = {}
- for k, v in pairs(t) do
- if type(v) == 'table' then
- t_new_row = {k}
- for _, v_ in ipairs(v) do
- t_new_row[#t_new_row+1] = v_
- end
- t_new[#t_new+1] = t_new_row
- else
- t_new[#t_new+1] = {k, v}
- end
- end
- return t_new
+function pl.tex.add_bkt_cnt(n)
+ -- add open bracket n times, returns brackets
+ n = n or 1
+ _NumBkts = _NumBkts + n
+ return ('{'):rep(n)
end
-function pl.array2d.toTeX(M, EL) --puts & between columns, can choose to end line with \\ if EL is true (end-line)
- EL = EL or false
- if EL then EL = '\\\\' else EL = '' end
- return pl.array2d.reduce2(_1..EL.._2, _1..'&'.._2, M)..EL
+function pl.tex.close_bkt_cnt(n)
+ n = n or _NumBkts
+ local s = ('}'):rep(n)
+ _NumBkts = _NumBkts - n
+ return s
end
--- -- -- -- -- -- --
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+
--- -- -- -- -- -- -- -- -- -- -- -- functions below extend the operator module
-function pl.operator.strgt(a,b) return tostring(a) > tostring(b) end
-function pl.operator.strlt(a,b) return tostring(a) < tostring(b) end
+-- -- -- -- math stuff
+function math.mod(a, b) -- math modulo, return remainder only
+ return a - (math.floor(a/b)*b)
+end
+
+function math.mod2(a) -- math modulo 2
+ return math.mod(a,2)
+end
+
-- -- -- -- string stuff
@@ -184,14 +276,13 @@ local number = P{"number",
}
-local mt = getmetatable("") -- register functions with str
-
+local str_mt = getmetatable("") -- register functions with str
-function mt.__index.gnum(s)
+function str_mt.__index.gnum(s)
return number:match(s)
end
-function mt.__index.gextract(s, pat) --extract a pattern from string, returns both
+function str_mt.__index.gextract(s, pat) --extract a pattern from string, returns both
local s_extr = ''
local s_rem = s
for e in s:gmatch(pat) do
@@ -201,7 +292,7 @@ function mt.__index.gextract(s, pat) --extract a pattern from string, returns bo
return s_extr, s_rem
end
-function mt.__index.gfirst(s, t) -- get the first pattern found from a table of pattern
+function str_mt.__index.gfirst(s, t) -- get the first pattern found from a table of pattern
for _, pat in pairs(t) do
if string.find(s, pat) then
return pat
@@ -209,7 +300,7 @@ function mt.__index.gfirst(s, t) -- get the first pattern found from a table of
end
end
-function mt.__index.appif(S, W, B, O) --append W ord to S tring if B oolean true, otherwise O ther
+function str_mt.__index.appif(S, W, B, O) --append W ord to S tring if B oolean true, otherwise O ther
--append Word to String
if B then --if b is true
S = S .. W
@@ -221,155 +312,394 @@ function mt.__index.appif(S, W, B, O) --append W ord to S tring if B oolean true
end
-
--- -- -- -- -- math stuffs
-
-function mod(a, b) -- math modulo, return remainder only
- return a - (math.floor(a/b)*b)
+ function str_mt.__index.containsany(s, exp)
+ if type(exp) ~= 'table' then exp = {exp} end
+ for _, e in ipairs(exp) do
+ if s:find(e) then return true end
+ end
+ return false
end
-function mod2(a) -- math modulo 2
- return mod(a,2)
+function str_mt.__index.containsanycase(s, exp)
+ if type(exp) ~= 'table' then exp = {exp} end
+ for _, e in ipairs(exp) do
+ if s:lower():find(e:lower()) then return true end
+ end
+ return false
end
-function hasval(x) -- if something has value
- if (type(x) == 'function') then
- return true
- elseif (x == nil) or (x == false) or (x == 0) or (x == '') then
- return false
- elseif (type(x) ~= 'boolean') and (type(x) ~= 'number') and (type(x) ~= 'string') then
- if #x == 0 then
- return false
- else
- return true
- end
+function str_mt.__index.totable(str)
+ local t = {}
+ for i = 1, #str do
+ t[i] = str:sub(i, i)
end
- return true
+ return t
end
+-- -- -- -- function stuff
+
+function pl.clone_function(fn)
+ local dumped = string.dump(fn)
+ local cloned = loadstring(dumped)
+ local i = 1
+ while true do
+ local name = debug.getupvalue(fn, i)
+ if not name then
+ break
+ end
+ debug.upvaluejoin(cloned, i, fn, i)
+ i = i + 1
+ end
+ return cloned
+end
+-- -- -- -- -- -- -- -- -- -- -- -- functions below extend the operator module
--- Some simple and helpful LaTeX functions
--- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+function pl.operator.strgt(a,b) return tostring(a) > tostring(b) end
+function pl.operator.strlt(a,b) return tostring(a) < tostring(b) end
---Generic LuaLaTeX utilities for print commands or environments
--- http://lua-users.org/wiki/SplitJoin -- todo read me!!
-__SKIP_TEX__ = __SKIP_TEX__ or false --if declared true before here, it will use regular print functions
--- (for troubleshooting with texlua)
--- xparse defaults
-_xTrue = '\\BooleanTrue '
-_xFalse = '\\BooleanFalse '
-_xNoValue = '-NoValue-'
+-- -- -- -- functions below are helpers for arrays and 2d
-if not __SKIP_TEX__ then
- prt = tex.sprint --can print lists, but will NOT put new line between them
- prtn = tex.print --can print lists and will put new line. C-function not in Lua. think P rint R return
- wrt = texio.write
- wrtn = texio.write_nl
-else
- prt = io.write
- prtn = print --print with new line
- wrt = io.write
- wrtn = io.write_nl
+local function compare_elements(a, b, op, ele)
+ op = op or pl.oper.gt
+ ele = ele or 1
+ return op(a[ele], b[ele])
end
-function prtl(str) -- prints a literal string to latex, adds new line between them
- for line in str:gmatch"[^\n]*" do -- gets all characters up to a new line
- prtn(line)
- end
+local function comp_2ele_func(op, ele) -- make a 2 element comparison function,
+ --sort with function on element nnum
+ return bind(compare_elements, _1, _2, op, ele)
end
-_NumBkts = 0
-function reset_bkt_cnt(n)
- n = n or 0
- _NumBkts = n
-end
-function add_bkt_cnt(n)
- -- add open bracket n times, returns brackets
- n = n or 1
- _NumBkts = _NumBkts + n
- return ('{'):rep(n)
+
+-- table stuff below
+
+
+function pl.tablex.map_slice(func, T, j1, j2)
+ if type(j1) == 'string' then
+ return pl.array2d.map_slice(func, {T}, ','..j1)[1]
+ else
+ return pl.array2d.map_slice(func, {T}, 1, j1, 1, j2)[1]
+ end
end
-function close_bkt_cnt()
- local s = ('}'):rep(_NumBkts)
- reset_bkt_cnt()
- return s
+pl.array2d.map_slice1 = pl.tablex.map_slice
+
+
+-- todo option for multiple filters with AND logic, like the filter files??
+function pl.tablex.filterstr(t, exp, case)
+ -- case = case sensitive
+ case = hasval(case)
+ -- apply lua patterns to a table to filter iter
+ -- str or table of str's can be passed, OR logic is used if table is passed
+ if case then
+ return pl.tablex.filter(t, bind(string.containsany,_1,exp))
+ else
+ return pl.tablex.filter(t, bind(string.containsanycase,_1,exp))
+ end
end
+function pl.utils.filterfiles(...)
+ -- f1 is a series of filtering patterns, or condition
+ -- f2 is a series of filtering patters, or condition
+ -- (f1_a or f2_...) and (f2 .. ) must match
+ local args = table.pack(...)
+ -- todo -- check where boolean is for recursive or not, set starting argument
+ -- this could allow one to omit dir
+ -- todo if no boolean at all, assume dir = '.' and r = false
+ -- if boolean given, assume dir = '.'
+ local nstart = 3
+ local r = args[2]
+ local dir = args[1]
+ if type(args[1]) == 'boolean' then
+ dir = '.'
+ r = args[1]
+ nstart = 2
+ elseif type(args[2]) ~= 'boolean' then
+ dir = '.'
+ r = false
+ nstart = 1
+ end
+ local files
+ if r then files = pl.dir.getallfiles(dir)
+ else files = pl.dir.getfiles(dir)
+ end
+ for i=nstart,args.n do
+ files = pl.tablex.filter(files, pl.func.compose(bind(string.containsanycase,_1, args[i]), pl.path.basename))
+ end
+ return files
+end
+-- -- -- -- -- -- -- -- functions below extend the array2d module
---definition helpers -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--- todo add and improve this
-local function defcmd_nest(cs) -- for option if you'd like your commands under a parent ex. \csparent{var}
- tex.print('\\gdef\\'..cs..'#1{\\csname '..var..'--#1--\\endcsname}')
+function pl.array2d.map_slice(func, M, i1, j1, i2, j2) -- map a function to a slice of a Matrix
+ func = pl.utils.function_arg(1, func)
+ for i,j in pl.array2d.iter(M, true, i1, j1, i2, j2) do
+ M[i][j] = func(M[i][j])
+ end
+ return M
end
+pl.array2d.map_slice2 = pl.array2d.map_slice
-local function defcmd(cs, val, nargs)
- if (nargs == nil) or (args == 0) then
- token.set_macro(cs, tostring(val), 'global')
+function pl.array2d.map_cols(func, M, j1, j2) -- map function to columns of matrix
+ if type(j1) == 'string' then
+ return pl.array2d.map_slice(func, M, ','..j1)
else
- local args = '#1'
- tex.print('\\gdef\\'..cs..args..'{'..val..'}')
- -- todo https://tex.stackexchange.com/questions/57551/create-a-capitalized-macro-token-using-csname
- -- \expandafter\gdef\csname Two\endcsname#1#2{1:#1, two:#2} --todo do it like this
+ j2 = j2 or -1
+ return pl.array2d.map_slice(func, M, 1, j1, -1, j2)
end
end
+pl.array2d.map_columns = pl.array2d.map_cols
-local function prvcmd(cs, val) -- provide command via lua
- if token.is_defined(cs) then
- tex.print('\\PackageWarning{YAMLvars}{Variable '..cs..' already defined, could not declare}{}')
+function pl.array2d.map_rows(func, M, i1, i2) -- map function to rows of matrix
+ if type(i1) == 'string' then
+ return pl.array2d.map_slice(func, M, i1)
else
- defcmd(cs, val)
+ i2 = i2 or -1
+ return pl.array2d.map_slice(func, M, i1, 1, i2, -1)
end
end
-local function newcmd(cs, val) -- provide command via lua
- if token.is_defined(cs) then
- tex.print('\\PackageError{luadefs}{Command '..cs..' already defined}{}')
- else
- defcmd(cs, val)
+-- -- -- -- -- -- -- --
+
+function pl.array2d.sortOP(M, op, ele) -- sort a 2d array based on operator criteria, ele is column, ie sort on which element
+ M_new = {}
+ for row in pl.seq.sort(M, comp_2ele_func(op, ele)) do
+ M_new[#M_new+1] = row
+ end
+ return M_new
+end
+
+function pl.array2d.like(M1, v)
+ v = v or 0
+ r, c = pl.array2d.size(M1)
+ return pl.array2d.new(r,c,v)
+end
+
+function pl.array2d.from_table(t) -- turns a labelled table to a 2d, label-free array
+ t_new = {}
+ for k, v in pairs(t) do
+ if type(v) == 'table' then
+ t_new_row = {k}
+ for _, v_ in ipairs(v) do
+ t_new_row[#t_new_row+1] = v_
+ end
+ t_new[#t_new+1] = t_new_row
+ else
+ t_new[#t_new+1] = {k, v}
+ end
end
+ return t_new
end
-local function renewcmd(cs, val) -- provide command via lua
- if token.is_defined(cs) then
- defcmd(cs, val)
+function pl.array2d.toTeX(M, EL) --puts & between columns, can choose to end line with \\ if EL is true (end-line)
+ EL = EL or false
+ if EL then EL = '\\\\' else EL = '' end
+ return pl.array2d.reduce2(_1..EL.._2, _1..'&'.._2, M)..EL
+end
+
+
+local function parse_numpy1d(i1, i2, iS)
+ i1 = tonumber(i1)
+ i2 = tonumber(i2)
+ if iS == ':' then
+ if i1 == nil then i1 = 1 end
+ if i2 == nil then i2 = -1 end
else
- tex.print('\\PackageError{luadefs}{Command '..cs..' already defined}{}')
+ if i1 == nil then
+ i1 = 1
+ i2 = -1
+ else
+ i2 = i1
+ end
end
+ return i1, i2
end
-local function deccmd(cs, def)
- if def == nil then
- prvcmd(cs, '\\PackageError{luadefs}{Command "'..cs..'" was declared and used but, not set}{}')
- else
- prvcmd(cs, def)
+function pl.array2d.parse_numpy2d_str(s)
+ s = s:gsub('%s+', '')
+ _, _, i1, iS, i2, j1, jS, j2 = string.find(s, "(%-?%d*)(:?)(%-?%d*),?(%-?%d*)(:?)(%-?%d*)")
+ i1, i2 = parse_numpy1d(i1, i2, iS)
+ j1, j2 = parse_numpy1d(j1, j2, jS)
+ return i1, j1, i2, j2
+end
+
+
+local _parse_range = pl.clone_function(pl.array2d.parse_range)
+
+function pl.array2d.parse_range(s) -- edit parse range to do numpy string if no letter passed
+ pl.utils.assert_arg(1,s,'string')
+ if not s:find'%a' then
+ return pl.array2d.parse_numpy2d_str(s)
end
+ return _parse_range(s)
end
--- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+
+
+
+if not __PL_NO_GLOBALS__ then
+ -- iterators
+ kpairs = pl.utils.kpairs
+ npairs = pl.utils.npairs
+ --enum = utils.enum
+
+ for k,v in pairs(pl.tablex) do -- extend the table table to contain tablex functions
+ _G['table'][k] = v
+ end
+
+ hasval = pl.hasval
+ COMP = pl.COMP
+
+ -- shortcuts
+-- http://stevedonovan.github.io/Penlight/api/libraries/pl.utils.html
+ pl.writefile = pl.utils.writefile
+ pl.readfile = pl.utils.readfile
+ pl.readlines = pl.utils.readfile
+ pl.filterfiles = pl.utils.filterfiles
+
+ pl.a2 = pl.array2d
+ pl.tbl = pl.tablex
+
+
+ for k,v in pairs(pl.tex) do -- make tex functions global
+ _G[k] = v
+ end
+
+ --_xTrue = pl.tex._xTrue
+ --_xFalse = pl.tex._xFalse
+ --_xNoValue = pl.tex._xNoValue
+ --
+ --prt = pl.tex.prt
+ --prtn = pl.tex.prtn
+ --wrt = pl.tex.wrt
+ --wrtn = pl.tex.wrtn
+ --
+ --prtl = pl.tex.prtl
+ --prtt = pl.tex.prtt
+ --
+ --help_wrt = pl.tex.help_wrt
+ --prt_array2d = pl.tex.prt_array2d
+ --
+ --pkgwarn = pl.tex.pkgwarn
+ --pkgerror = pl.tex.pkgerror
+ --
+ --defcmd = pl.tex.defcmd
+ --prvcmd = pl.tex.prvcmd
+ --newcmd = pl.tex.newcmd
+ --renewcmd = pl.tex.renewcmd
+ --deccmd = pl.tex.deccmd
+ --
+ --_NumBkts = pl.tex._NumBkts
+ --opencmd = pl.tex.opencmd
+ --reset_bkt_cnt = pl.tex.reset_bkt_cnt
+ --add_bkt_cnt = pl.tex.add_bkt_cnt
+ --close_bkt_cnt = pl.tex.close_bkt_cnt
+
+end
+
+
+
+
+
+-- graveyard
+
+
+-- luakeys parses individual keys as ipairs, this changes the list to a pure map
+--function pl.luakeystomap(t)
+-- local t_new = {}
+-- for k, v in pairs(t) do
+-- if type(k) == 'number' then
+-- t_new[v] = true
+-- else
+-- t_new[k] = v
+-- end
+-- end
+-- return t_new
+--end
+--if luakeys then -- if luakeys is already loaded
+-- function luakeys.parseN(s, ...)
+-- local t = luakeys.parse(s,...)
+-- t = pl.luakeystomap(t)
+-- return t
+-- end
+--end
+-- might not be needed
+
+
+ --local func = check_func(func)
+--local function check_func(func) -- check if a function is a PE, if so, make it a function
+-- if type(func) ~= 'function' then
+-- return I(func)
+-- end
+-- return func
+--end
+
+-- -- -- -- -- -- --
+-- -- -- -- functions below extend the array2d module
+
+
+--function pl.array2d.map_slice1(func, L, i1, i2) -- map a function to a slice of an array, can use PlcExpr
+-- i2 = i2 or i1
+-- local len = #L
+-- i1 = check_index(i1, len)
+-- i2 = check_index(i2, len)
+-- func = check_func(func)
+-- for i in pl.seq.range(i1,i2) do
+-- L[i] = func(L[i])
+-- end
+-- return L
+--end
+
+ -- used this below when iter was not working..
+ --i1, j1, i2, j2 = check_slice(M, i1, j1, i2, j2)
+ --for i in pl.seq.range(i1,i2) do
+ -- for j in pl.seq.range(j1,j2) do
+ --end
+ -- penlight may have fixed this
+--local function check_index(ij, rc) -- converts array index to positive value if negative
+-- if type(ij) ~= 'number' then
+-- return 1
+-- else
+-- if ij < 0 then
+-- ij = rc + ij + 1
+-- elseif ij > rc then
+-- ij = rc
+-- elseif ij == 0 then
+-- ij = 1
+-- end
+-- return ij
+-- end
+--end
+--local function check_slice(M, i1, j1, i2, j2) -- ensure a slice is valid; i.e. all positive numbers
+-- r, c = pl.array2d.size(M)
+-- i1 = check_index(i1 or 1, r)
+-- i2 = check_index(i2 or r, r)
+-- j1 = check_index(j1 or 1, c)
+-- j2 = check_index(j2 or c, c)
+-- return i1, j1, i2, j2
+--end
+
diff --git a/macros/xetex/latex/book-of-common-prayer/README.md b/macros/unicodetex/latex/book-of-common-prayer/README.md
index a0671e1d95..345d52cb67 100644
--- a/macros/xetex/latex/book-of-common-prayer/README.md
+++ b/macros/unicodetex/latex/book-of-common-prayer/README.md
@@ -1,6 +1,6 @@
-book-of-common-prayer (v1.0.0): Liturgical typesetting in the style of the 1979 Book of Common Prayer.
+book-of-common-prayer (v1.1.0): Liturgical typesetting in the style of the 1979 Book of Common Prayer.
===
-Copyright (C) 2021 Arlie Coles
+Copyright (C) 2022 Arlie Coles
This package can be redistributed and/or modified under the terms
of the LaTeX Project Public License 1.3c.
===
@@ -12,6 +12,6 @@ This package is designed to work with the Sabon font, but it is not necessary to
* Arial Unicode MS
* Junicode
-You should compile documents that use this package with XeLaTeX.
+You should compile documents that use this package with XeLaTeX or LuaLaTeX.
This material is subject to the LaTeX Project Public License 1.3c.
diff --git a/macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.pdf b/macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.pdf
new file mode 100644
index 0000000000..896602974f
--- /dev/null
+++ b/macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.pdf
Binary files differ
diff --git a/macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.sty b/macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.sty
index 168e95e56a..68189ee7db 100644
--- a/macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.sty
+++ b/macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.sty
@@ -1,7 +1,7 @@
% Author: Arlie Coles
% License: LaTeX Project Public License 1.3c
\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{book-of-common-prayer}[2021/06/27 Liturgical documents in the style of the Book of Common Prayer]
+\ProvidesPackage{book-of-common-prayer}[2022/02/26 Liturgical documents in the style of the Book of Common Prayer]
\RequirePackage[no-math]{fontspec}
\RequirePackage[left=0.5in, right=0.5in, vmargin=0.5in]{geometry}
@@ -20,6 +20,7 @@
\RequirePackage{pgf}
\RequirePackage{pgfopts}
\RequirePackage{bilingualpages}
+\RequirePackage{changepage}
% The BCP font is Sabon, so allow this option
\DeclareOption{sabon}{\setmainfont[Ligatures=TeX]{Sabon LT Std}}
@@ -49,8 +50,9 @@
\newcommand{\tab}{\hspace*{1em}}
\newcommand{\blankline}{\vspace{\parskip}}
\newcommand{\deleteline}{\vspace{-\parskip}}
+\newcommand{\spacer}{\vspace{0.1cm}} % A bit of breathing room between responses
-% Cover image
+% Cover image with border around it
\usepackage{graphicx}
\newcommand{\munepsfig}[3][scale=1.0]{% <===============================
\begin{figure}[!htbp]
@@ -110,7 +112,10 @@
% Roles to go with the responses - the people are bolded
\newcommand{\priest}[1]{\textit{Priest} & #1 \\}
\newcommand{\deacon}[1]{\textit{Deacon} & #1 \\}
+\newcommand{\subdeacon}[1]{\textit{Subdeacon} & #1 \\}
\newcommand{\officiant}[1]{\textit{Officiant} & #1 \\}
+\newcommand{\lector}[1]{\textit{Lector} & #1 \\}
+\newcommand{\epistoler}[1]{\textit{Epistoler} & #1 \\}
\newcommand{\people}[1]{\textit{People} & \textbf{#1} \\}
\newcommand{\servers}[1]{\textit{Servers} & #1 \\}
\newcommand{\contd}[1]{& #1 \\}
@@ -213,3 +218,27 @@
\par\nointerlineskip \vspace{.5\baselineskip}
}
\newcommand{\sectionline}{\makesectionline{black}{88}}
+
+% PSALMS
+% Command for the two-part structure of a psalm verse
+\newcommand{\psalmverse}[2]{
+ % Adjust parskip; no blank lines in between
+ \setlength{\parskip}{0pt}
+
+ % Render the first part
+ #1 *
+ % Hang in the second part
+ \begin{adjustwidth}{2em}{0pt}
+ #2
+ \end{adjustwidth}
+
+ % Put parskip back
+ \setlength{\parskip}{12pt}
+}
+
+
+% Cont'd marker at bottom of page
+\newcommand{\continued}{
+ \vspace*{\fill}
+ \instruct{\hfill \small \textit{(cont'd)}}
+}
diff --git a/macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.tex b/macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.tex
index c7338e5f48..61b18b9a09 100644
--- a/macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.tex
+++ b/macros/unicodetex/latex/book-of-common-prayer/book-of-common-prayer.tex
@@ -26,12 +26,15 @@
\title{\texttt{book-of-common-prayer}\\
\Large Typesetting for liturgical documents in the style of the 1979 Book of Common Prayer \\
}
-\date{July 2021 \\ \textit{Version 1.0.0}}
+\date{February 2022 \\ \textit{Version 1.1.0}}
\author{Arlie Coles}
\begin{document}
\maketitle
+\tableofcontents
+
+\clearpage
\section{Introduction}
\texttt{book-of-common-prayer} is a \LaTeX\ package for liturgical documents in the style of the 1979 Book of Common Prayer\footnote{\url{http://justus.anglican.org/resources/bcp/formatted_1979.htm}}. It provides special typesetting tools for common liturgical situations (e.g. versicle and response, longer prayers, etc.) as well as formatting specifications for an entire document (e.g. font face, section headers, margins, etc.).
@@ -47,7 +50,13 @@ The standard font used in the 1979 Book of Common Prayer is Sabon\footnote{\url{
You should also have the fonts Arial Unicode MS\footnote{\url{https://docs.microsoft.com/en-us/typography/font-list/arial-unicode-ms}} and Junicode\footnote{\url{https://junicode.sourceforge.io/}} installed on your computer. These two fonts provide some special symbols used by \texttt{book-of-common-prayer}.
-Since \texttt{book-of-common-prayer} uses special fonts, you should compile your document with the Xe\LaTeX\ compiler.
+Since \texttt{book-of-common-prayer} uses special fonts, you should compile your document with the Lua\LaTeX\ or Xe\LaTeX\ compiler.
+
+\subsection{Music}
+
+\texttt{book-of-common-prayer} works with Lilypond\footnote{\url{http://lilypond.org/}} for inline music notation. If you have musical scores you would like to integrate into your document, you should use the \texttt{lyluatex} package by writing \lstinline|\usepackage{lyluatex}| in the preamble of your document.
+
+To compile your document with Lilypond scores, you must use Lua\LaTeX, e.g. \lstinline{lualatex -shell-escape your_document.tex}.
\subsection{Page setup}
@@ -91,6 +100,7 @@ There are also some special commands to fine-tune formatting when needed (usuall
\item \lstinline{\blankline} forces a blank line (similar to pressing \lstinline{Enter} twice in Microsoft Word). Usually, \LaTeX\ manages this type of spacing for you, but you can use this command if you need more manual control.
\item \lstinline{\deleteline} deletes an empty line's space (similar to backspacing over a blank line in Microsoft Word).
\item \lstinline{\tab} creates an indent (similar to pressing \lstinline{tab} in Microsoft Word).
+ \item \lstinline{\spacer} creates a very small vertical space when this kind of fine control is desired.
\end{itemize}
\subsection{Text formatting}
@@ -187,7 +197,8 @@ To put a box around some text, and italicize the text inside, we can use \lstinl
\caption{Boxes around text.}
\end{table}
-\blankline
+\continued
+\clearpage
\subsection{Environments}
Several environments are available in \texttt{book-of-common-prayer}. These are used for common formatting blocks, such as versicle-and-response exchanges or longer prayers.
@@ -198,8 +209,10 @@ Table \ref{tab:env_responses} shows how to use the \lstinline{responses} environ
Supported roles are \lstinline{priest}, \lstinline{deacon}, \lstinline{officiant}, \lstinline{servers}, and \lstinline{people}. Support for roles in French is also available (as \lstinline{pretre}, \lstinline{diacre}, \lstinline{officiant}, \lstinline{servants}, and \lstinline{peuple} respectively). The text associated with the \lstinline{people}/\lstinline{peuple} role is always bolded.
-\pagebreak
-\begin{table}[h]
+Other supported roles that will render as non-bolded are: \lstinline{subdeacon}, \lstinline{epistoler}, \lstinline{lector}.
+
+% \pagebreak
+\begin{table}[h!]
\centering
\begin{tabular}{ll}
\toprule
@@ -276,7 +289,7 @@ Table \ref{tab:env_vresponses} shows how to use the \lstinline{vresponses} envir
Sometimes, if the response is long, we might want to format it further. We can do this by placing the response inside of an \lstinline|\rlong{}| bracketing. We can then use line breaks (\lstinline{\\}) and tabs (\lstinline{\tab}) to format the response as we like. Table \ref{tab:env_vresponses_rlong} shows an example of this.
-% \clearpage
+\clearpage
\begin{table}[h!]
\centering
\begin{tabular}{ll}
@@ -462,5 +475,53 @@ Table \ref{tab:env_threecolprayer} shows how to use the \lstinline{threecolpraye
\end{table}
+\subsubsection{Psalm typesetting}
+
+It is possible to typeset psalms with a combination of an enumerated list and \lstinline{\psalmverse}, as shown in Table \ref{tab:psalms}.
+
+\begin{table}[h]
+\centering
+\begin{xtabular}{p{.45\textwidth}p{.45\textwidth}}
+\toprule
+\LaTeX\ code & Formatted output \\
+\midrule
+\begin{lstlisting}
+\begin{enumerate}[
+ leftmargin=*,
+ label=\textsc{\arabic*}
+ ]
+ \item \psalmverse
+ {The Lord is my shepherd:}
+ {Therefore can I lack nothing.}
+ \item \psalmverse
+ {He shall feed me
+ in a green pasture:}
+ {and lead me forth beside
+ the waters of comfort.}
+\end{enumerate}
+\end{lstlisting}
+&
+\sabon{
+\begin{enumerate}[
+ leftmargin=*,
+ label=\textsc{\arabic*}
+ ]
+ \item \psalmverse
+ {The Lord is my shepherd:}
+ {Therefore can I lack nothing.}
+ \item \psalmverse
+ {He shall feed me
+ in a green pasture:}
+ {and lead me forth beside
+ the waters of comfort.}
+\end{enumerate}
+}
+\\
+\bottomrule
+\end{xtabular}
+\caption{Typesetting psalms with an enumerated list and \lstinline{\\psalmverse}.}
+\label{tab:psalms}
+\end{table}
+
\end{document}
diff --git a/macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.pdf b/macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.pdf
deleted file mode 100644
index 89e27100d8..0000000000
--- a/macros/xetex/latex/book-of-common-prayer/book-of-common-prayer.pdf
+++ /dev/null
Binary files differ