diff --git a/README.md b/README.md index ed93487..f6d817a 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ are regenerated on each release — patches cannot be merged directly. Report problems (or send patches) to the author; accepted changes are applied to the development tree and appear in a following snapshot. -This snapshot was assembled from development commit `07ce5ea86a0a`. +This snapshot was assembled from development commit `61e598698891`. ## License diff --git a/com/kdesc.cpp b/com/kdesc.cpp index eec6780..0f25e8f 100644 --- a/com/kdesc.cpp +++ b/com/kdesc.cpp @@ -161,10 +161,11 @@ int main(int argc, char* argv[]) // rather than verbosity levels. args.var("k", "Show klammers. With , only those whose name or description contains .", "text"); args.flag("c", "Show the codes for accented vowels and other special characters"); + args.flag("u", "Describe including Unicode characters in KLammertext"); args.flag("t", "Show the targets for Klammertext output"); args.opt("i", "Input filename to analyze containing klammer and other definitions", "filename", "", "'text'"); args.var("klammersets", "List the available klammersets, or load one or more for analysis. Enter \"--klammersets help\" for details.", "symbols"); - args.var("font", "List installed fonts. Enter \"--font help\" for font maintenance commands."); + args.var("typeface", "List installed typefaces. Enter \"--typeface help\" for typeface maintenance commands."); args.flag("argtypes", "Show the klammer argument types"); args.var("katoms", "Show the katom types. Enter \"--katoms help\" for details."); args.flag("rewrite", "Show the katom rewrite patterns"); @@ -192,6 +193,10 @@ int main(int argc, char* argv[]) std::cout << "\n"; } + if (p("u")) { + unicode_examples(); + } + if (p("argtypes")) { Argtype_registry argtypes; std::cout << boldblack << "\nStandard klammer argument types\n" << black; @@ -219,8 +224,8 @@ int main(int argc, char* argv[]) // The --font subcommands operate on the Klammertext font store // (infrastructure) and load no klammer set. - if (args.given("font")) { - font_command(option_words(args, "font")); + if (args.given("typeface")) { + font_command(option_words(args, "typeface")); return 0; } diff --git a/doc/install/macos_container_install.md b/doc/install/macos_container_install.md index b954d05..b7fcae4 100644 --- a/doc/install/macos_container_install.md +++ b/doc/install/macos_container_install.md @@ -128,7 +128,7 @@ That's it — you're running Klammertext. others) are built in, so PDFs work with no internet connection. Font resolution is entirely offline: asking for a font that isn't installed lists the available fonts, and additional fonts are installed from font - files you already have with `kdesc --font install ` (`kdesc --font + files you already have with `kdesc --typeface install ` (`kdesc --typeface help` explains). - **Updating later.** When a new version is announced, run `klammertext-update` in Terminal. diff --git a/fnt/crimson-pro/OFL.txt b/fnt/crimson-pro/OFL.txt new file mode 100644 index 0000000..d791182 --- /dev/null +++ b/fnt/crimson-pro/OFL.txt @@ -0,0 +1,95 @@ +Copyright 2018 The Crimson Pro Project Authors (https://github.com/Fonthausen/CrimsonPro) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + +(The copyright line above and the license URL are the font's own, read +from its name table; the license text below is the canonical OFL 1.1.) + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fnt/eb-garamond/OFL.txt b/fnt/eb-garamond/OFL.txt new file mode 100644 index 0000000..2505864 --- /dev/null +++ b/fnt/eb-garamond/OFL.txt @@ -0,0 +1,95 @@ +Copyright 2017 The EB Garamond Project Authors (https://github.com/octaviopardo/EBGaramond12) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + +(The copyright line above and the license URL are the font's own, read +from its name table; the license text below is the canonical OFL 1.1.) + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fnt/inconsolata/OFL.txt b/fnt/inconsolata/OFL.txt new file mode 100644 index 0000000..65b4dd1 --- /dev/null +++ b/fnt/inconsolata/OFL.txt @@ -0,0 +1,95 @@ +Copyright 2006 The Inconsolata Project Authors (https://github.com/cyrealtype/Inconsolata) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +(The copyright line above and the license URL are the font's own, read +from its name table; the license text below is the canonical OFL 1.1.) + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fnt/libre-baskerville/OFL.txt b/fnt/libre-baskerville/OFL.txt new file mode 100644 index 0000000..19e4ba0 --- /dev/null +++ b/fnt/libre-baskerville/OFL.txt @@ -0,0 +1,95 @@ +Copyright (c) 2012, Pablo Impallari (www.impallari.com|impallari@gmail.com), Copyright (c) 2012, Rodrigo Fuenzalida (www.rfuenzalida.com|hello@rfuenzalida.com), with Reserved Font Name Libre Baskerville. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +(The copyright line above and the license URL are the font's own, read +from its name table; the license text below is the canonical OFL 1.1.) + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fnt/noto-sans-cuneiform.css b/fnt/noto-sans-cuneiform.css new file mode 100644 index 0000000..8a98f1c --- /dev/null +++ b/fnt/noto-sans-cuneiform.css @@ -0,0 +1,7 @@ + +@font-face { + font-family: 'Noto Sans Cuneiform'; + font-style: normal; + font-weight: 400; + src: url('noto-sans-cuneiform/Regular.ttf') format('truetype'); +} diff --git a/fnt/noto-sans-cuneiform/OFL.txt b/fnt/noto-sans-cuneiform/OFL.txt new file mode 100644 index 0000000..8dc9f4a --- /dev/null +++ b/fnt/noto-sans-cuneiform/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/cuneiform) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fnt/noto-sans-cuneiform/Regular.ttf b/fnt/noto-sans-cuneiform/Regular.ttf new file mode 100644 index 0000000..e5b3b87 Binary files /dev/null and b/fnt/noto-sans-cuneiform/Regular.ttf differ diff --git a/fnt/noto-sans-egyptian-hieroglyphs.css b/fnt/noto-sans-egyptian-hieroglyphs.css new file mode 100644 index 0000000..cdbad0c --- /dev/null +++ b/fnt/noto-sans-egyptian-hieroglyphs.css @@ -0,0 +1,7 @@ + +@font-face { + font-family: 'Noto Sans Egyptian Hieroglyphs'; + font-style: normal; + font-weight: 400; + src: url('noto-sans-egyptian-hieroglyphs/Regular.ttf') format('truetype'); +} diff --git a/fnt/noto-sans-egyptian-hieroglyphs/OFL.txt b/fnt/noto-sans-egyptian-hieroglyphs/OFL.txt new file mode 100644 index 0000000..791fd9e --- /dev/null +++ b/fnt/noto-sans-egyptian-hieroglyphs/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/egyptian-hieroglyphs) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fnt/noto-sans-egyptian-hieroglyphs/Regular.ttf b/fnt/noto-sans-egyptian-hieroglyphs/Regular.ttf new file mode 100644 index 0000000..1a9b253 Binary files /dev/null and b/fnt/noto-sans-egyptian-hieroglyphs/Regular.ttf differ diff --git a/fnt/open-sans/OFL.txt b/fnt/open-sans/OFL.txt new file mode 100644 index 0000000..7a1ecb6 --- /dev/null +++ b/fnt/open-sans/OFL.txt @@ -0,0 +1,95 @@ +Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +(The copyright line above and the license URL are the font's own, read +from its name table; the license text below is the canonical OFL 1.1.) + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fnt/source-code-pro/OFL.txt b/fnt/source-code-pro/OFL.txt new file mode 100644 index 0000000..1ae0126 --- /dev/null +++ b/fnt/source-code-pro/OFL.txt @@ -0,0 +1,99 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), +with Reserved Font Name 'Source'. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + +(The copyright line above is the font's own, read from its name table. +This binary carries no license record of its own -- it predates the +practice -- so the license is stated from the upstream release it comes +from: https://github.com/adobe-fonts/source-code-pro, whose LICENSE.md +is the OFL 1.1 text below.) + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fnt/source-sans-3/OFL.txt b/fnt/source-sans-3/OFL.txt new file mode 100644 index 0000000..f000a51 --- /dev/null +++ b/fnt/source-sans-3/OFL.txt @@ -0,0 +1,95 @@ +© 2023 Adobe (http://www.adobe.com/), with Reserved Font Name ‘Source’ + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +(The copyright line above and the license URL are the font's own, read +from its name table; the license text below is the canonical OFL 1.1.) + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/mac/argv.h b/mac/argv.h index 776c8b1..3338bf7 100644 --- a/mac/argv.h +++ b/mac/argv.h @@ -63,11 +63,11 @@ public: // next -/-- token (zero or more). get(name) returns the words // space-joined; given(name) distinguishes "--name with no words" // from an absent --name. Used for subcommand-style interfaces - // (kdesc --font install ). + // (kdesc --typeface install ). // A variadic option: the words after the flag, up to the next one. // `parameter` is shown in the usage line as "[]" -- an option // whose words are a free argument (a search phrase) should name it, so - // the usage says what may follow; a subcommand-style option (--font, + // the usage says what may follow; a subcommand-style option (--typeface, // --coverage) leaves it empty and documents its words in its own help. void var(const std::string& name, const std::string& desc, const std::string& parameter = ""); diff --git a/mac/character.cpp b/mac/character.cpp index 429a869..1d52999 100644 --- a/mac/character.cpp +++ b/mac/character.cpp @@ -2,9 +2,26 @@ #include "util.h" #include "character.h" +#include "precomposed.h" +#include "error.h" +#include "target.h" #include "log.h" #include "show.h" +// A base letter composed with a combining mark. The PRECOMPOSED (NFC) +// code point is emitted where one exists -- that is the byte sequence +// editors and input methods produce for direct entry, so ^a" and a typed +// ä compare equal (search, copy-paste); the tables formerly emitted +// base + combining mark, which renders identically but is a different +// string. Pairs with no precomposed form keep the combining fallback. +// The table is GENERATED: doc/make_precomposed_table.py -> precomposed.h. +static std::string compose(const std::string& base, const std::string& mark_cp) +{ + auto it = precomposed.find(base + mark_cp); + if (it != precomposed.end()) return it->second; + return base + utf8char(std::stoi(mark_cp, nullptr, 16)); +} + inline std::string klammertext_special_characters { "@|*^#" }; @@ -44,6 +61,24 @@ std::string utf8char(int cp) } +bool valid_codepoint(int cp) +{ + if (cp < 0 || cp > 0x10FFFF) return false; + if (0xD800 <= cp && cp <= 0xDFFF) return false; // surrogates + if (0xFDD0 <= cp && cp <= 0xFDEF) return false; // noncharacters + if ((cp & 0xFFFE) == 0xFFFE) return false; // U+xFFFE / U+xFFFF + return true; +} + +std::string invalid_codepoint_reason(int cp) +{ + if (cp < 0 || cp > 0x10FFFF) + return "beyond U+10FFFF, the top of the Unicode range"; + if (0xD800 <= cp && cp <= 0xDFFF) + return "a surrogate (U+D800-U+DFFF), not a character"; + return "a noncharacter, reserved by Unicode for internal use"; +} + std::string unicode_hex_to_char(std::string s, int width=4) //, std::string marker) { (void)K::log(4, s); @@ -82,7 +117,7 @@ std::string process_diacritics(std::string s) std::regex hit { regex_escape((*p)[0].str()) }; std::string ch = (*p)[1].str(); std::string d = (*p)[2].str(); - result = std::regex_replace(result, hit, ch + unicode_hex_to_char(diacritics[d].first)); + result = std::regex_replace(result, hit, compose(ch, diacritics[d].first)); } return result; } @@ -124,15 +159,14 @@ std::string process_pinyin(std::string s) std::string vowel = (*p)[1].str(); std::string tone = (*p)[2].str(); result = std::regex_replace(result, hit, - vowel + unicode_hex_to_char(pinyin_tones[tone].first)); + compose(vowel, pinyin_tones[tone].first)); } return result; } -std::string process_unicode_codepoint(std::string s) +std::string process_unicode_codepoint(std::string s, const Locator& loc = Locator::none()) { (void)K::log(4); - //return std::regex_replace(s, unicode_re, hidehat + "$1" + hidehat); std::string result {s}; std::sregex_iterator end {}; for (std::sregex_iterator p { s.begin(), s.end(), unicode_re }; p!= end; ++p) { @@ -141,8 +175,22 @@ std::string process_unicode_codepoint(std::string s) // its argument at a fixed width, and its 4-digit default truncated a // 5-digit code point to its first four digits (^13000^ rendered as // U+1300 followed by a literal "0"). + int cp = std::stoi((*p)[1].str(), nullptr, 16); + // An invalid code point is a located ERROR, not text in the document + // (the no-warn-and-continue policy): a generator's typo must not + // produce a finished-looking document with an apology embedded + // where a character should be. + if (!valid_codepoint(cp)) { + throw Parsing_error("Invalid Unicode code point \"" + (*p)[0].str() + + "\": " + invalid_codepoint_reason(cp), loc); + } + // A code point that lands on a Klammertext structural character + // (@ | # ^ : *) is hidden as its KTESC marker, exactly as the + // quoted form of that character would be -- a bare "@" from + // ^0040^ would otherwise be re-interpreted when the text is + // re-katomized (@document :text, the @eval read-back). result = std::regex_replace(result, hit_re, - utf8char(std::stoi((*p)[1].str(), nullptr, 16))); + hide_structural_characters(utf8char(cp))); } return result; @@ -154,7 +202,7 @@ std::string process_unicode_codepoint(std::string s) -std::string encode(const std::string& s) +std::string encode(const std::string& s, const Locator& loc) { if (s.find("^") == std::string::npos) return s; @@ -177,7 +225,7 @@ std::string encode(const std::string& s) result = process_extended_latin(result); if (dbg) std::cout << "extended_latin: " << result << "\n"; - result = process_unicode_codepoint(result); + result = process_unicode_codepoint(result, loc); if (dbg) std::cout << "unicode: " << result << "\n"; result = process_diacritics(result); @@ -225,7 +273,7 @@ void diacritics_examples(const std::string& kt_filename) << " @i-Displayed | @i-Written | @i-Name\n"; else std::cout << boldblack - << "\nDiacritics (with typical base characters)\n" << black; + << "\nDiacritics (with typical base characters)\n\n" << black; std::string line_sep = "|| @line@ "; for (char symbol : diacritic_symbols_order) { @@ -260,7 +308,7 @@ void extended_latin_examples(const std::string& kt_filename) kt << "@table :caption " << title << " |\n" << " @i-Displayed | @i-Written | @i-Name\n"; else - std::cout << "\n" << boldblack << title << black << "\n"; + std::cout << "\n" << boldblack << title << black << "\n\n"; std::string line_sep = "|| @line@ "; for (const std::string& symbol : extended_latin_symbols) { @@ -291,7 +339,7 @@ void pinyin_examples(const std::string& kt_filename) << " @i-Displayed | @i-Written | @i-Name\n"; else std::cout << boldblack - << "\nMandarin pinyin tones (using vowel \"a\")\n" << black; + << "\nMandarin pinyin tones (using vowel \"a\")\n\n" << black; std::string line_sep = "|| @line@ "; for (auto [symbol, codename] : pinyin_tones) { @@ -309,6 +357,45 @@ void pinyin_examples(const std::string& kt_filename) write_kt_example_file(kt, kt_filename); } + +void unicode_examples(const std::string& kt_filename) +{ + std::stringstream kt {}; + bool write_kt_file = kt_filename.size() != 0; + + std::cout << boldblack << "\nIncluding Unicode in Klammertext files\n\n" << black + << "To specify a wide variety of different letter forms, the Unicode Standard\n" + << "defines a large set of \"code points\" organized by groups. To include a\n" + << "Unicode character in Klammertext, you surround its hexadecimal number with\n" + << "the \"^\" character. For example, the Cyrillic captial letter \"I\" is \"И\",\n" + << "included in Klammertext as ^0418^.\n\n" + << "To search for Unicode characters by their description, see:\n" + << " https://www.fileformat.info/info/unicode/char/search.htm\n"; + + std::cout << "\n⟐ Examples of Chinese characters\n\n" + << " Pinyin\n"; + for (auto chr : dao) { + std::string code = "^" + chr[1] + "^"; + std::cout << " " << process_unicode_codepoint(code) << " " + << code << " " + << chr[0] << "\n"; + } + + std::cout << "\n⟐ Examples of Egyptian hieroglyphs\n\n"; + std::cout << std::setw(16) << " " << "Sound Gardiner\n"; + for (auto glyph : ptolemy) { + std::cout << " " << std::setw(4) << process_unicode_codepoint("^" + glyph[2] + "^") << " " + << "^" + glyph[2] + "^ " + << std::setw(4) + << glyph[1] << " " + << std::setw(4) + << glyph[0] << "\n"; + } + std::cout << "\nSee: https://en.wikipedia.org/wiki/List_of_Egyptian_hieroglyphs\n" + << "The Unicode codepoint in the second column of the \"List of hieroglyphs\"\n" + << "table is the number in the ^...^ code used by Klammertext.\n\n"; +} + void show_special_characters() { std::cout << std::setfill(' '); diff --git a/mac/character.h b/mac/character.h index 0e9b345..2e757ce 100644 --- a/mac/character.h +++ b/mac/character.h @@ -11,6 +11,8 @@ #include #include +#include "locator.h" + // ^UUUU^ — a Unicode code point in hex, 4 to 6 digits (^263A^ is the BMP, // ^13000^ EGYPTIAN HIEROGLYPH A001, ^10FFFD^ the top of the range), or a // single digit. Lengths 2-3 are NOT accepted: two- and three-letter @@ -89,21 +91,61 @@ std::map> pinyin_tones { {"3", {"030C", "falling-rising"}}, {"4", {"0300", "falling"}}}; +inline +std::vector> ptolemy { + {"Q3", "P", "132AA"}, + {"X1", "t", "133CF"}, + {"V4", "o/wa", "13371"}, + {"E23", "l/r", "130ED"}, + {"Aa15", "m", "1340D"}, + {"M17A", "e/y", "131CC"}, + {"S29", "s", "132F4"}}; + +/* + 道 (dào): U+9053 + 可 (kě): U+53EF + 道 (dào): U+9053 + 非 (fēi): U+975E + 常 (cháng): U+5E38 + 道 (dào): U+9053 +*/ + +inline +std::vector> dao { + {"dào", "9053"}, + {"kě", "53EF"}, + {"dào", "9053"}, + {"fēi", "975E"}, + {"cháng", "5E38"}, + {"dào", "9053"}}; inline bool is_tty() { return isatty(fileno(stdout)); } -//const char* italic_on() { return tty() ? "\033[3m" : ""; } -//const char* italic_off() { return tty() ? "\033[0m" : ""; } inline const std::string italic_on() { return is_tty() ? "\033[3m" : ""; } inline const std::string italic_off() { return is_tty() ? "\033[0m" : ""; } +std::string encode(const std::string& s, const Locator& loc); -std::string encode(const std::string& s); +// UTF-8 encoding of one code point (to U+10FFFF; surrogates and values +// above the range yield an "Invalid Unicode: N" text). Used by the +// character tables here and by the ^UUUU UUUU ...^ sequence pass +// (process_unicode_sequences in katom_list.cpp). +std::string utf8char(int cp); + +// Whether cp may be written in a ^...^ form: within the Unicode range, +// not a surrogate, and not a NONCHARACTER (U+FDD0-U+FDEF and the last two +// code points of every plane) -- the standard reserves those for internal +// use, never for interchange. An invalid code point in a document is a +// located Parsing_error (the no-warn-and-continue policy; the in-band +// "Invalid Unicode" text is no longer reachable from input). +bool valid_codepoint(int cp); +std::string invalid_codepoint_reason(int cp); void diacritics_examples(const std::string& kt_filename = ""); void extended_latin_examples(const std::string& kt_filename = ""); void pinyin_examples(const std::string& kt_filename = ""); +void unicode_examples(const std::string& kt_filename = ""); void show_special_characters(); diff --git a/mac/font_store.cpp b/mac/font_store.cpp index be25dbf..05d0836 100644 --- a/mac/font_store.cpp +++ b/mac/font_store.cpp @@ -206,7 +206,7 @@ static void extract_font_metrics(Resolved_font& font) } } -Resolved_font resolve_font(const std::string& family_name) +Resolved_font resolve_font(const std::string& family_name, const Locator& loc) { if (family_name.empty()) return {}; @@ -222,26 +222,26 @@ Resolved_font resolve_font(const std::string& family_name) if (!font.family_name.empty()) { extract_font_metrics(font); // A DERIVED value: an installed font may shadow a default one, and - // which directory won is not readable from ":serif_font Whatever". + // which directory won is not readable from ":serif_typeface Whatever". // The commonest font question -- "why does it not look like the // one I installed?" -- is answered by this line. - (void)K::log(1, "Font \"" + family_name + "\": " + base + "/" + dir_name); + (void)K::log(1, "Typeface \"" + family_name + "\": " + base + "/" + dir_name); return font; } } std::stringstream ss {}; - ss << "The font \"" << family_name << "\" is not installed.\n\n" - << "Available fonts:\n"; + ss << "The typeface \"" << family_name << "\" is not installed.\n\n" + << "Available typefaces:\n"; for (const std::string& name : available_font_families()) { ss << " " << name << "\n"; } - ss << "\nFonts are searched in the directories of the KLAMMERTEXT_FONTS\n" + ss << "\nTypefaces are searched in the directories of the KLAMMERTEXT_FONTS\n" << "environment variable (colon-separated; default $HOME/.klammertext/fonts)\n" - << "and then in the default font set. To install a font,\n" + << "and then in the default set. To install a typeface,\n" << "place its files as /" << dir_name << "/*.ttf with a\n" << "/" << dir_name << ".css declaring its @font-face variants."; - throw Argument_error(ss.str()); + throw Argument_error(ss.str(), loc); } diff --git a/mac/font_store.h b/mac/font_store.h index 5e4ee49..5b951b8 100644 --- a/mac/font_store.h +++ b/mac/font_store.h @@ -3,6 +3,7 @@ // The Klammertext font store (infrastructure; see font_store.cpp). #include +#include "locator.h" #include struct Resolved_font { @@ -27,7 +28,11 @@ std::vector installed_font_dirs(); std::string default_font_dir(); // Every installable font family found across those directories. std::vector available_font_families(); -Resolved_font resolve_font(const std::string& family_name); +// The Locator is the DOCUMENT location to blame when the typeface is not +// installed: without it the "not installed" error cites font_store.cpp, +// a source location no user can act on (the no-.cpp-location rule). +Resolved_font resolve_font(const std::string& family_name, + const Locator& loc = Locator::none()); void install_resolved_font(const Resolved_font& font, std::string output_dir); // One font file classified by its internal metadata (name table, OS/2). diff --git a/mac/katom_list.cpp b/mac/katom_list.cpp index 3fcd3bb..375ac56 100644 --- a/mac/katom_list.cpp +++ b/mac/katom_list.cpp @@ -281,11 +281,164 @@ void encode_nonascii_characters(katom_list& katoms) { for (Katom& k : katoms) { if (is_nonascii(k)) { - k.m_text = encode(k.m_text); + k.m_text = encode(k.m_text, k.m_loc); } } } +// Unicode sequences + +// ^UUUU UUUU ...^ -- a whitespace-delimited sequence of Unicode code +// points between ONE pair of carets: ^9053 53EF 9053^ renders the three +// characters adjacently. The single-token form ^UUUU^ is the katomizer's +// nonascii katom, resolved by encode(); a sequence cannot be a katom at +// all, because whitespace is preserved as separate katoms and no katom +// spans it. So the tokens arrive as word katoms -- the opener "^"+hex, +// the closer hex+"^", bare hex between, separated by SPACE katoms only (a +// newline aborts: a sequence is one line; so does any other katom, which +// is also what enforces "no space inside the delimiters' elbows": a bare +// "^" is not an opener) -- and are assembled here. Each token is 4-6 hex +// digits, the same rule as the single form. The rendered characters +// replace the opening katom's text; the other members are marked +// replaced. Skipped inside @@/@@@ definition spans and +// @eval/@read/@cond argument spans (code keeps its carets -- the rule the +// string-level single-form pass cannot honor) and for literal katoms +// (verbatim content; mark_literal_* run before this). +namespace { + +bool hex_token(const std::string& s) +{ + if (s.size() < 4 || s.size() > 6) return false; + for (char c : s) { + bool hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); + if (!hex) return false; + } + return true; +} + +// A sequence token is a 4-6-digit hex code point or a SINGLE character +// standing for itself ("^" excluded -- a lone caret cannot say which +// caret closes). The single-character reading exists only inside a +// sequence; ^s^ alone stays the mnemonic. +bool shape_token(const std::string& s) +{ + return hex_token(s) || (s.size() == 1 && s[0] != '^'); +} + +// Render one scanned token. A hex token is validated -- an invalid code +// point is a located error, not text in the document -- and a structural +// character in the result is hidden as its KTESC marker. A single +// character behaves exactly as if quoted with "^": alphanumerics stand +// bare, anything else becomes its marker. +std::string render_token(const std::string& s, const Locator& loc) +{ + if (hex_token(s)) { + int cp = std::stoi(s, nullptr, 16); + if (!valid_codepoint(cp)) { + throw Parsing_error( + "Invalid Unicode code point \"" + s + "\" in a ^...^ sequence: " + + invalid_codepoint_reason(cp), loc); + } + return hide_structural_characters(utf8char(cp)); + } + char c = s[0]; + bool alnum = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z'); + return alnum ? s : Target::escape_marker(s); +} + +} // namespace + +void process_unicode_sequences(katom_list& katoms) +{ + (void)K::log(4); + int definition_depth = 0; + int code_depth = 0; // inside an @eval/@read/@cond span + std::vector apply_is_code; // one entry per open application + for (size_t i = 0; i < katoms.size(); ++i) { + Katom& k = katoms[i]; + switch (k.m_type) { + case katom_t::define_begin: + case katom_t::machine_begin: + ++definition_depth; + continue; + case katom_t::define_end: + case katom_t::machine_end: + if (definition_depth > 0) --definition_depth; + continue; + case katom_t::eval_begin: + case katom_t::read_begin: + case katom_t::cond_begin: + apply_is_code.push_back(true); + ++code_depth; + continue; + case katom_t::apply_begin: + apply_is_code.push_back(false); + continue; + case katom_t::apply_end: + if (!apply_is_code.empty()) { + if (apply_is_code.back()) --code_depth; + apply_is_code.pop_back(); + } + continue; + default: + break; + } + if (definition_depth > 0 || code_depth > 0) continue; + if (k.m_type != katom_t::word && k.m_type != katom_t::text) continue; + // The opener: "^" + one token, nothing else. + if (k.m_text.size() < 2 || k.m_text[0] != '^') continue; + if (!shape_token(k.m_text.substr(1))) continue; + // Phase 1 -- SCAN by shape only, no validation: an unclosed run + // must fall through untouched (it may be innocent prose about hex), + // so an invalid code point may only error once the sequence has + // actually closed. Tokens are word katoms separated by SPACE + // katoms; any other katom (a newline included: one line only) + // aborts. + std::vector> tokens { + { k.m_text.substr(1), &k.m_loc } }; + std::vector members {}; + bool closed = false; + size_t j = i + 1; + for (; j < katoms.size(); ++j) { + Katom& t = katoms[j]; + if (t.m_type == katom_t::space) { + members.push_back(j); + continue; + } + if (t.m_type != katom_t::word && t.m_type != katom_t::text) break; + const std::string& s = t.m_text; + if (s.size() >= 2 && s.back() == '^' + && shape_token(s.substr(0, s.size() - 1))) { + tokens.push_back({ s.substr(0, s.size() - 1), &t.m_loc }); + members.push_back(j); + closed = true; + ++j; + break; + } + if (shape_token(s)) { + tokens.push_back({ s, &t.m_loc }); + members.push_back(j); + continue; + } + break; + } + if (!closed) continue; + // Phase 2 -- render, validating each code point at its own katom. + std::string rendered {}; + for (const auto& [text, loc] : tokens) { + rendered += render_token(text, *loc); + } + k.m_text = rendered; + k.m_unparsed = false; + for (size_t m : members) { + mark_as_replaced(katoms[m]); + } + i = j - 1; + } +} + // Literal void mark_literal_katoms(katom_list& katoms) diff --git a/mac/katom_list.h b/mac/katom_list.h index 7b4f2cd..59eb14f 100644 --- a/mac/katom_list.h +++ b/mac/katom_list.h @@ -46,6 +46,7 @@ find_span_katoms( std::vector::iterator kbegin, std::vector::iterator kend, const Katom& begin, const Katom& end); void encode_nonascii_characters(std::vector& katoms); +void process_unicode_sequences(std::vector& katoms); void mark_literal_katoms(std::vector& katoms); void hide_special_katoms(std::vector& katoms); void mark_ignored_katoms(std::vector& katoms); diff --git a/mac/locator.cpp b/mac/locator.cpp index 01a98c4..beb5e1b 100644 --- a/mac/locator.cpp +++ b/mac/locator.cpp @@ -135,3 +135,25 @@ std::string locator_summary(std::vector locators) } return ""; } + +// Parse "[:.]" -- see the declaration in locator.h. +// Scanned from the RIGHT, because a pathname may itself contain ":" and +// "." while the line and column never do. +Locator Locator::from_string(const std::string& s) +{ + if (s.size() < 6 || s.front() != '[' || s.back() != ']') + return Locator::none(); + std::string body = s.substr(1, s.size() - 2); + size_t dot = body.rfind('.'); + if (dot == std::string::npos) return Locator::none(); + size_t colon = body.rfind(':', dot); + if (colon == std::string::npos) return Locator::none(); + try { + int line = std::stoi(body.substr(colon + 1, dot - colon - 1)); + int chr = std::stoi(body.substr(dot + 1)); + // str() prints m_chr + 1; undo that so a round trip is exact. + return Locator(fs::path(body.substr(0, colon)), line, chr - 1); + } catch (const std::exception&) { + return Locator::none(); + } +} diff --git a/mac/locator.h b/mac/locator.h index 5963908..c64866e 100644 --- a/mac/locator.h +++ b/mac/locator.h @@ -32,6 +32,13 @@ public: // errors (found by the error gallery, 2026-08-22). Locator() is for // logging; errors either carry a real document location or this. static Locator none() { return Locator(fs::path{}, -1, -1); } + // The inverse of str(): "[:.]" back to a Locator. + // A klammer implementation receives its position as the STATE value + // K_loc, which is that string; without this it can only pass a + // default-constructed Locator, which captures the C++ source location + // and prints a file no user can act on. Anything unparseable gives + // none() -- a missing location beats a wrong one. + static Locator from_string(const std::string& s); std::string str(bool relative = false) const; std::string desc(bool relative = false) const; std::string abbrev(bool include_chr=true) const; diff --git a/mac/machine.cpp b/mac/machine.cpp index 5a373a6..5d0c027 100644 --- a/mac/machine.cpp +++ b/mac/machine.cpp @@ -435,6 +435,11 @@ void Machine::process_katoms( { mark_literal_klammer_content(katoms); if (literal) mark_literal_katoms(katoms); + // Sequences BEFORE the quoted-punctuation hiding: a single-character + // token such as the "-" in ^9053 - 53EF^ must reach the sequence pass + // as written; an unmatched run falls through untouched and the hiding + // then treats its ^P pairs normally. + if (nonascii) process_unicode_sequences(katoms); hide_special_katoms(katoms); if (nonascii) encode_nonascii_characters(katoms); if (ignore) mark_ignored_katoms(katoms); diff --git a/mac/precomposed.h b/mac/precomposed.h new file mode 100644 index 0000000..676c1ca --- /dev/null +++ b/mac/precomposed.h @@ -0,0 +1,250 @@ +#pragma once +// GENERATED by doc/make_precomposed_table.py -- do not edit. +// (base letter + combining code point) -> precomposed UTF-8. +#include +#include + +inline +const std::map precomposed { + {"a0300", "\xc3\xa0"}, // LATIN SMALL LETTER A WITH GRAVE + {"a0301", "\xc3\xa1"}, // LATIN SMALL LETTER A WITH ACUTE + {"a0302", "\xc3\xa2"}, // LATIN SMALL LETTER A WITH CIRCUMFLEX + {"a0303", "\xc3\xa3"}, // LATIN SMALL LETTER A WITH TILDE + {"a0304", "\xc4\x81"}, // LATIN SMALL LETTER A WITH MACRON + {"a0308", "\xc3\xa4"}, // LATIN SMALL LETTER A WITH DIAERESIS + {"a0306", "\xc4\x83"}, // LATIN SMALL LETTER A WITH BREVE + {"a0307", "\xc8\xa7"}, // LATIN SMALL LETTER A WITH DOT ABOVE + {"a030A", "\xc3\xa5"}, // LATIN SMALL LETTER A WITH RING ABOVE + {"a030C", "\xc7\x8e"}, // LATIN SMALL LETTER A WITH CARON + {"b0307", "\xe1\xb8\x83"}, // LATIN SMALL LETTER B WITH DOT ABOVE + {"c0301", "\xc4\x87"}, // LATIN SMALL LETTER C WITH ACUTE + {"c0302", "\xc4\x89"}, // LATIN SMALL LETTER C WITH CIRCUMFLEX + {"c0307", "\xc4\x8b"}, // LATIN SMALL LETTER C WITH DOT ABOVE + {"c030C", "\xc4\x8d"}, // LATIN SMALL LETTER C WITH CARON + {"c0327", "\xc3\xa7"}, // LATIN SMALL LETTER C WITH CEDILLA + {"d0307", "\xe1\xb8\x8b"}, // LATIN SMALL LETTER D WITH DOT ABOVE + {"d030C", "\xc4\x8f"}, // LATIN SMALL LETTER D WITH CARON + {"d0327", "\xe1\xb8\x91"}, // LATIN SMALL LETTER D WITH CEDILLA + {"e0300", "\xc3\xa8"}, // LATIN SMALL LETTER E WITH GRAVE + {"e0301", "\xc3\xa9"}, // LATIN SMALL LETTER E WITH ACUTE + {"e0302", "\xc3\xaa"}, // LATIN SMALL LETTER E WITH CIRCUMFLEX + {"e0303", "\xe1\xba\xbd"}, // LATIN SMALL LETTER E WITH TILDE + {"e0304", "\xc4\x93"}, // LATIN SMALL LETTER E WITH MACRON + {"e0308", "\xc3\xab"}, // LATIN SMALL LETTER E WITH DIAERESIS + {"e0306", "\xc4\x95"}, // LATIN SMALL LETTER E WITH BREVE + {"e0307", "\xc4\x97"}, // LATIN SMALL LETTER E WITH DOT ABOVE + {"e030C", "\xc4\x9b"}, // LATIN SMALL LETTER E WITH CARON + {"e0327", "\xc8\xa9"}, // LATIN SMALL LETTER E WITH CEDILLA + {"f0307", "\xe1\xb8\x9f"}, // LATIN SMALL LETTER F WITH DOT ABOVE + {"g0301", "\xc7\xb5"}, // LATIN SMALL LETTER G WITH ACUTE + {"g0302", "\xc4\x9d"}, // LATIN SMALL LETTER G WITH CIRCUMFLEX + {"g0304", "\xe1\xb8\xa1"}, // LATIN SMALL LETTER G WITH MACRON + {"g0306", "\xc4\x9f"}, // LATIN SMALL LETTER G WITH BREVE + {"g0307", "\xc4\xa1"}, // LATIN SMALL LETTER G WITH DOT ABOVE + {"g030C", "\xc7\xa7"}, // LATIN SMALL LETTER G WITH CARON + {"g0327", "\xc4\xa3"}, // LATIN SMALL LETTER G WITH CEDILLA + {"h0302", "\xc4\xa5"}, // LATIN SMALL LETTER H WITH CIRCUMFLEX + {"h0308", "\xe1\xb8\xa7"}, // LATIN SMALL LETTER H WITH DIAERESIS + {"h0307", "\xe1\xb8\xa3"}, // LATIN SMALL LETTER H WITH DOT ABOVE + {"h030C", "\xc8\x9f"}, // LATIN SMALL LETTER H WITH CARON + {"h0327", "\xe1\xb8\xa9"}, // LATIN SMALL LETTER H WITH CEDILLA + {"i0300", "\xc3\xac"}, // LATIN SMALL LETTER I WITH GRAVE + {"i0301", "\xc3\xad"}, // LATIN SMALL LETTER I WITH ACUTE + {"i0302", "\xc3\xae"}, // LATIN SMALL LETTER I WITH CIRCUMFLEX + {"i0303", "\xc4\xa9"}, // LATIN SMALL LETTER I WITH TILDE + {"i0304", "\xc4\xab"}, // LATIN SMALL LETTER I WITH MACRON + {"i0308", "\xc3\xaf"}, // LATIN SMALL LETTER I WITH DIAERESIS + {"i0306", "\xc4\xad"}, // LATIN SMALL LETTER I WITH BREVE + {"i030C", "\xc7\x90"}, // LATIN SMALL LETTER I WITH CARON + {"j0302", "\xc4\xb5"}, // LATIN SMALL LETTER J WITH CIRCUMFLEX + {"j030C", "\xc7\xb0"}, // LATIN SMALL LETTER J WITH CARON + {"k0301", "\xe1\xb8\xb1"}, // LATIN SMALL LETTER K WITH ACUTE + {"k030C", "\xc7\xa9"}, // LATIN SMALL LETTER K WITH CARON + {"k0327", "\xc4\xb7"}, // LATIN SMALL LETTER K WITH CEDILLA + {"l0301", "\xc4\xba"}, // LATIN SMALL LETTER L WITH ACUTE + {"l030C", "\xc4\xbe"}, // LATIN SMALL LETTER L WITH CARON + {"l0327", "\xc4\xbc"}, // LATIN SMALL LETTER L WITH CEDILLA + {"m0301", "\xe1\xb8\xbf"}, // LATIN SMALL LETTER M WITH ACUTE + {"m0307", "\xe1\xb9\x81"}, // LATIN SMALL LETTER M WITH DOT ABOVE + {"n0300", "\xc7\xb9"}, // LATIN SMALL LETTER N WITH GRAVE + {"n0301", "\xc5\x84"}, // LATIN SMALL LETTER N WITH ACUTE + {"n0303", "\xc3\xb1"}, // LATIN SMALL LETTER N WITH TILDE + {"n0307", "\xe1\xb9\x85"}, // LATIN SMALL LETTER N WITH DOT ABOVE + {"n030C", "\xc5\x88"}, // LATIN SMALL LETTER N WITH CARON + {"n0327", "\xc5\x86"}, // LATIN SMALL LETTER N WITH CEDILLA + {"o0300", "\xc3\xb2"}, // LATIN SMALL LETTER O WITH GRAVE + {"o0301", "\xc3\xb3"}, // LATIN SMALL LETTER O WITH ACUTE + {"o0302", "\xc3\xb4"}, // LATIN SMALL LETTER O WITH CIRCUMFLEX + {"o0303", "\xc3\xb5"}, // LATIN SMALL LETTER O WITH TILDE + {"o0304", "\xc5\x8d"}, // LATIN SMALL LETTER O WITH MACRON + {"o0308", "\xc3\xb6"}, // LATIN SMALL LETTER O WITH DIAERESIS + {"o0306", "\xc5\x8f"}, // LATIN SMALL LETTER O WITH BREVE + {"o0307", "\xc8\xaf"}, // LATIN SMALL LETTER O WITH DOT ABOVE + {"o030C", "\xc7\x92"}, // LATIN SMALL LETTER O WITH CARON + {"o030B", "\xc5\x91"}, // LATIN SMALL LETTER O WITH DOUBLE ACUTE + {"p0301", "\xe1\xb9\x95"}, // LATIN SMALL LETTER P WITH ACUTE + {"p0307", "\xe1\xb9\x97"}, // LATIN SMALL LETTER P WITH DOT ABOVE + {"r0301", "\xc5\x95"}, // LATIN SMALL LETTER R WITH ACUTE + {"r0307", "\xe1\xb9\x99"}, // LATIN SMALL LETTER R WITH DOT ABOVE + {"r030C", "\xc5\x99"}, // LATIN SMALL LETTER R WITH CARON + {"r0327", "\xc5\x97"}, // LATIN SMALL LETTER R WITH CEDILLA + {"s0301", "\xc5\x9b"}, // LATIN SMALL LETTER S WITH ACUTE + {"s0302", "\xc5\x9d"}, // LATIN SMALL LETTER S WITH CIRCUMFLEX + {"s0307", "\xe1\xb9\xa1"}, // LATIN SMALL LETTER S WITH DOT ABOVE + {"s030C", "\xc5\xa1"}, // LATIN SMALL LETTER S WITH CARON + {"s0327", "\xc5\x9f"}, // LATIN SMALL LETTER S WITH CEDILLA + {"t0308", "\xe1\xba\x97"}, // LATIN SMALL LETTER T WITH DIAERESIS + {"t0307", "\xe1\xb9\xab"}, // LATIN SMALL LETTER T WITH DOT ABOVE + {"t030C", "\xc5\xa5"}, // LATIN SMALL LETTER T WITH CARON + {"t0327", "\xc5\xa3"}, // LATIN SMALL LETTER T WITH CEDILLA + {"u0300", "\xc3\xb9"}, // LATIN SMALL LETTER U WITH GRAVE + {"u0301", "\xc3\xba"}, // LATIN SMALL LETTER U WITH ACUTE + {"u0302", "\xc3\xbb"}, // LATIN SMALL LETTER U WITH CIRCUMFLEX + {"u0303", "\xc5\xa9"}, // LATIN SMALL LETTER U WITH TILDE + {"u0304", "\xc5\xab"}, // LATIN SMALL LETTER U WITH MACRON + {"u0308", "\xc3\xbc"}, // LATIN SMALL LETTER U WITH DIAERESIS + {"u0306", "\xc5\xad"}, // LATIN SMALL LETTER U WITH BREVE + {"u030A", "\xc5\xaf"}, // LATIN SMALL LETTER U WITH RING ABOVE + {"u030C", "\xc7\x94"}, // LATIN SMALL LETTER U WITH CARON + {"u030B", "\xc5\xb1"}, // LATIN SMALL LETTER U WITH DOUBLE ACUTE + {"v0303", "\xe1\xb9\xbd"}, // LATIN SMALL LETTER V WITH TILDE + {"w0300", "\xe1\xba\x81"}, // LATIN SMALL LETTER W WITH GRAVE + {"w0301", "\xe1\xba\x83"}, // LATIN SMALL LETTER W WITH ACUTE + {"w0302", "\xc5\xb5"}, // LATIN SMALL LETTER W WITH CIRCUMFLEX + {"w0308", "\xe1\xba\x85"}, // LATIN SMALL LETTER W WITH DIAERESIS + {"w0307", "\xe1\xba\x87"}, // LATIN SMALL LETTER W WITH DOT ABOVE + {"w030A", "\xe1\xba\x98"}, // LATIN SMALL LETTER W WITH RING ABOVE + {"x0308", "\xe1\xba\x8d"}, // LATIN SMALL LETTER X WITH DIAERESIS + {"x0307", "\xe1\xba\x8b"}, // LATIN SMALL LETTER X WITH DOT ABOVE + {"y0300", "\xe1\xbb\xb3"}, // LATIN SMALL LETTER Y WITH GRAVE + {"y0301", "\xc3\xbd"}, // LATIN SMALL LETTER Y WITH ACUTE + {"y0302", "\xc5\xb7"}, // LATIN SMALL LETTER Y WITH CIRCUMFLEX + {"y0303", "\xe1\xbb\xb9"}, // LATIN SMALL LETTER Y WITH TILDE + {"y0304", "\xc8\xb3"}, // LATIN SMALL LETTER Y WITH MACRON + {"y0308", "\xc3\xbf"}, // LATIN SMALL LETTER Y WITH DIAERESIS + {"y0307", "\xe1\xba\x8f"}, // LATIN SMALL LETTER Y WITH DOT ABOVE + {"y030A", "\xe1\xba\x99"}, // LATIN SMALL LETTER Y WITH RING ABOVE + {"z0301", "\xc5\xba"}, // LATIN SMALL LETTER Z WITH ACUTE + {"z0302", "\xe1\xba\x91"}, // LATIN SMALL LETTER Z WITH CIRCUMFLEX + {"z0307", "\xc5\xbc"}, // LATIN SMALL LETTER Z WITH DOT ABOVE + {"z030C", "\xc5\xbe"}, // LATIN SMALL LETTER Z WITH CARON + {"A0300", "\xc3\x80"}, // LATIN CAPITAL LETTER A WITH GRAVE + {"A0301", "\xc3\x81"}, // LATIN CAPITAL LETTER A WITH ACUTE + {"A0302", "\xc3\x82"}, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX + {"A0303", "\xc3\x83"}, // LATIN CAPITAL LETTER A WITH TILDE + {"A0304", "\xc4\x80"}, // LATIN CAPITAL LETTER A WITH MACRON + {"A0308", "\xc3\x84"}, // LATIN CAPITAL LETTER A WITH DIAERESIS + {"A0306", "\xc4\x82"}, // LATIN CAPITAL LETTER A WITH BREVE + {"A0307", "\xc8\xa6"}, // LATIN CAPITAL LETTER A WITH DOT ABOVE + {"A030A", "\xc3\x85"}, // LATIN CAPITAL LETTER A WITH RING ABOVE + {"A030C", "\xc7\x8d"}, // LATIN CAPITAL LETTER A WITH CARON + {"B0307", "\xe1\xb8\x82"}, // LATIN CAPITAL LETTER B WITH DOT ABOVE + {"C0301", "\xc4\x86"}, // LATIN CAPITAL LETTER C WITH ACUTE + {"C0302", "\xc4\x88"}, // LATIN CAPITAL LETTER C WITH CIRCUMFLEX + {"C0307", "\xc4\x8a"}, // LATIN CAPITAL LETTER C WITH DOT ABOVE + {"C030C", "\xc4\x8c"}, // LATIN CAPITAL LETTER C WITH CARON + {"C0327", "\xc3\x87"}, // LATIN CAPITAL LETTER C WITH CEDILLA + {"D0307", "\xe1\xb8\x8a"}, // LATIN CAPITAL LETTER D WITH DOT ABOVE + {"D030C", "\xc4\x8e"}, // LATIN CAPITAL LETTER D WITH CARON + {"D0327", "\xe1\xb8\x90"}, // LATIN CAPITAL LETTER D WITH CEDILLA + {"E0300", "\xc3\x88"}, // LATIN CAPITAL LETTER E WITH GRAVE + {"E0301", "\xc3\x89"}, // LATIN CAPITAL LETTER E WITH ACUTE + {"E0302", "\xc3\x8a"}, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX + {"E0303", "\xe1\xba\xbc"}, // LATIN CAPITAL LETTER E WITH TILDE + {"E0304", "\xc4\x92"}, // LATIN CAPITAL LETTER E WITH MACRON + {"E0308", "\xc3\x8b"}, // LATIN CAPITAL LETTER E WITH DIAERESIS + {"E0306", "\xc4\x94"}, // LATIN CAPITAL LETTER E WITH BREVE + {"E0307", "\xc4\x96"}, // LATIN CAPITAL LETTER E WITH DOT ABOVE + {"E030C", "\xc4\x9a"}, // LATIN CAPITAL LETTER E WITH CARON + {"E0327", "\xc8\xa8"}, // LATIN CAPITAL LETTER E WITH CEDILLA + {"F0307", "\xe1\xb8\x9e"}, // LATIN CAPITAL LETTER F WITH DOT ABOVE + {"G0301", "\xc7\xb4"}, // LATIN CAPITAL LETTER G WITH ACUTE + {"G0302", "\xc4\x9c"}, // LATIN CAPITAL LETTER G WITH CIRCUMFLEX + {"G0304", "\xe1\xb8\xa0"}, // LATIN CAPITAL LETTER G WITH MACRON + {"G0306", "\xc4\x9e"}, // LATIN CAPITAL LETTER G WITH BREVE + {"G0307", "\xc4\xa0"}, // LATIN CAPITAL LETTER G WITH DOT ABOVE + {"G030C", "\xc7\xa6"}, // LATIN CAPITAL LETTER G WITH CARON + {"G0327", "\xc4\xa2"}, // LATIN CAPITAL LETTER G WITH CEDILLA + {"H0302", "\xc4\xa4"}, // LATIN CAPITAL LETTER H WITH CIRCUMFLEX + {"H0308", "\xe1\xb8\xa6"}, // LATIN CAPITAL LETTER H WITH DIAERESIS + {"H0307", "\xe1\xb8\xa2"}, // LATIN CAPITAL LETTER H WITH DOT ABOVE + {"H030C", "\xc8\x9e"}, // LATIN CAPITAL LETTER H WITH CARON + {"H0327", "\xe1\xb8\xa8"}, // LATIN CAPITAL LETTER H WITH CEDILLA + {"I0300", "\xc3\x8c"}, // LATIN CAPITAL LETTER I WITH GRAVE + {"I0301", "\xc3\x8d"}, // LATIN CAPITAL LETTER I WITH ACUTE + {"I0302", "\xc3\x8e"}, // LATIN CAPITAL LETTER I WITH CIRCUMFLEX + {"I0303", "\xc4\xa8"}, // LATIN CAPITAL LETTER I WITH TILDE + {"I0304", "\xc4\xaa"}, // LATIN CAPITAL LETTER I WITH MACRON + {"I0308", "\xc3\x8f"}, // LATIN CAPITAL LETTER I WITH DIAERESIS + {"I0306", "\xc4\xac"}, // LATIN CAPITAL LETTER I WITH BREVE + {"I0307", "\xc4\xb0"}, // LATIN CAPITAL LETTER I WITH DOT ABOVE + {"I030C", "\xc7\x8f"}, // LATIN CAPITAL LETTER I WITH CARON + {"J0302", "\xc4\xb4"}, // LATIN CAPITAL LETTER J WITH CIRCUMFLEX + {"K0301", "\xe1\xb8\xb0"}, // LATIN CAPITAL LETTER K WITH ACUTE + {"K030C", "\xc7\xa8"}, // LATIN CAPITAL LETTER K WITH CARON + {"K0327", "\xc4\xb6"}, // LATIN CAPITAL LETTER K WITH CEDILLA + {"L0301", "\xc4\xb9"}, // LATIN CAPITAL LETTER L WITH ACUTE + {"L030C", "\xc4\xbd"}, // LATIN CAPITAL LETTER L WITH CARON + {"L0327", "\xc4\xbb"}, // LATIN CAPITAL LETTER L WITH CEDILLA + {"M0301", "\xe1\xb8\xbe"}, // LATIN CAPITAL LETTER M WITH ACUTE + {"M0307", "\xe1\xb9\x80"}, // LATIN CAPITAL LETTER M WITH DOT ABOVE + {"N0300", "\xc7\xb8"}, // LATIN CAPITAL LETTER N WITH GRAVE + {"N0301", "\xc5\x83"}, // LATIN CAPITAL LETTER N WITH ACUTE + {"N0303", "\xc3\x91"}, // LATIN CAPITAL LETTER N WITH TILDE + {"N0307", "\xe1\xb9\x84"}, // LATIN CAPITAL LETTER N WITH DOT ABOVE + {"N030C", "\xc5\x87"}, // LATIN CAPITAL LETTER N WITH CARON + {"N0327", "\xc5\x85"}, // LATIN CAPITAL LETTER N WITH CEDILLA + {"O0300", "\xc3\x92"}, // LATIN CAPITAL LETTER O WITH GRAVE + {"O0301", "\xc3\x93"}, // LATIN CAPITAL LETTER O WITH ACUTE + {"O0302", "\xc3\x94"}, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX + {"O0303", "\xc3\x95"}, // LATIN CAPITAL LETTER O WITH TILDE + {"O0304", "\xc5\x8c"}, // LATIN CAPITAL LETTER O WITH MACRON + {"O0308", "\xc3\x96"}, // LATIN CAPITAL LETTER O WITH DIAERESIS + {"O0306", "\xc5\x8e"}, // LATIN CAPITAL LETTER O WITH BREVE + {"O0307", "\xc8\xae"}, // LATIN CAPITAL LETTER O WITH DOT ABOVE + {"O030C", "\xc7\x91"}, // LATIN CAPITAL LETTER O WITH CARON + {"O030B", "\xc5\x90"}, // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE + {"P0301", "\xe1\xb9\x94"}, // LATIN CAPITAL LETTER P WITH ACUTE + {"P0307", "\xe1\xb9\x96"}, // LATIN CAPITAL LETTER P WITH DOT ABOVE + {"R0301", "\xc5\x94"}, // LATIN CAPITAL LETTER R WITH ACUTE + {"R0307", "\xe1\xb9\x98"}, // LATIN CAPITAL LETTER R WITH DOT ABOVE + {"R030C", "\xc5\x98"}, // LATIN CAPITAL LETTER R WITH CARON + {"R0327", "\xc5\x96"}, // LATIN CAPITAL LETTER R WITH CEDILLA + {"S0301", "\xc5\x9a"}, // LATIN CAPITAL LETTER S WITH ACUTE + {"S0302", "\xc5\x9c"}, // LATIN CAPITAL LETTER S WITH CIRCUMFLEX + {"S0307", "\xe1\xb9\xa0"}, // LATIN CAPITAL LETTER S WITH DOT ABOVE + {"S030C", "\xc5\xa0"}, // LATIN CAPITAL LETTER S WITH CARON + {"S0327", "\xc5\x9e"}, // LATIN CAPITAL LETTER S WITH CEDILLA + {"T0307", "\xe1\xb9\xaa"}, // LATIN CAPITAL LETTER T WITH DOT ABOVE + {"T030C", "\xc5\xa4"}, // LATIN CAPITAL LETTER T WITH CARON + {"T0327", "\xc5\xa2"}, // LATIN CAPITAL LETTER T WITH CEDILLA + {"U0300", "\xc3\x99"}, // LATIN CAPITAL LETTER U WITH GRAVE + {"U0301", "\xc3\x9a"}, // LATIN CAPITAL LETTER U WITH ACUTE + {"U0302", "\xc3\x9b"}, // LATIN CAPITAL LETTER U WITH CIRCUMFLEX + {"U0303", "\xc5\xa8"}, // LATIN CAPITAL LETTER U WITH TILDE + {"U0304", "\xc5\xaa"}, // LATIN CAPITAL LETTER U WITH MACRON + {"U0308", "\xc3\x9c"}, // LATIN CAPITAL LETTER U WITH DIAERESIS + {"U0306", "\xc5\xac"}, // LATIN CAPITAL LETTER U WITH BREVE + {"U030A", "\xc5\xae"}, // LATIN CAPITAL LETTER U WITH RING ABOVE + {"U030C", "\xc7\x93"}, // LATIN CAPITAL LETTER U WITH CARON + {"U030B", "\xc5\xb0"}, // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE + {"V0303", "\xe1\xb9\xbc"}, // LATIN CAPITAL LETTER V WITH TILDE + {"W0300", "\xe1\xba\x80"}, // LATIN CAPITAL LETTER W WITH GRAVE + {"W0301", "\xe1\xba\x82"}, // LATIN CAPITAL LETTER W WITH ACUTE + {"W0302", "\xc5\xb4"}, // LATIN CAPITAL LETTER W WITH CIRCUMFLEX + {"W0308", "\xe1\xba\x84"}, // LATIN CAPITAL LETTER W WITH DIAERESIS + {"W0307", "\xe1\xba\x86"}, // LATIN CAPITAL LETTER W WITH DOT ABOVE + {"X0308", "\xe1\xba\x8c"}, // LATIN CAPITAL LETTER X WITH DIAERESIS + {"X0307", "\xe1\xba\x8a"}, // LATIN CAPITAL LETTER X WITH DOT ABOVE + {"Y0300", "\xe1\xbb\xb2"}, // LATIN CAPITAL LETTER Y WITH GRAVE + {"Y0301", "\xc3\x9d"}, // LATIN CAPITAL LETTER Y WITH ACUTE + {"Y0302", "\xc5\xb6"}, // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX + {"Y0303", "\xe1\xbb\xb8"}, // LATIN CAPITAL LETTER Y WITH TILDE + {"Y0304", "\xc8\xb2"}, // LATIN CAPITAL LETTER Y WITH MACRON + {"Y0308", "\xc5\xb8"}, // LATIN CAPITAL LETTER Y WITH DIAERESIS + {"Y0307", "\xe1\xba\x8e"}, // LATIN CAPITAL LETTER Y WITH DOT ABOVE + {"Z0301", "\xc5\xb9"}, // LATIN CAPITAL LETTER Z WITH ACUTE + {"Z0302", "\xe1\xba\x90"}, // LATIN CAPITAL LETTER Z WITH CIRCUMFLEX + {"Z0307", "\xc5\xbb"}, // LATIN CAPITAL LETTER Z WITH DOT ABOVE + {"Z030C", "\xc5\xbd"}, // LATIN CAPITAL LETTER Z WITH CARON +}; diff --git a/sks/code/code_block.py b/sks/code/code_block.py index c70b313..d55bd6d 100644 --- a/sks/code/code_block.py +++ b/sks/code/code_block.py @@ -278,8 +278,8 @@ class Code(klammer_base.Klammer_base): return measure + L.add_caption( boxed, "Listing", self.number, self.caption, width, hpos=self.hpos, side=self.caption_side, - font_symbol=self.caption_font, - font_size=self.caption_font_size, offset=self.offset) + font_symbol=self.caption_style, + font_size=self.caption_size, offset=self.offset) return measure + L.caption_wrapper(boxed, self.hpos, offset=self.offset) @@ -298,7 +298,7 @@ class Code(klammer_base.Klammer_base): if captioned: caption = L.make_caption_text( self.number, "Listing", self.caption, - self.caption_font, self.caption_font_size) + self.caption_style, self.caption_size) # \nobreak: a caption must not be separated from its listing by # a page break, even though the listing itself may break. block = (f"\\noindent\\hspace*{{{shift}}}" @@ -327,8 +327,8 @@ class Code(klammer_base.Klammer_base): if self.number or self.caption: return html_util.add_caption( listing, "Listing", self.number, self.caption, - self.caption_font, self.hpos, self.caption_side, True, - self.caption_font_size, offset=self.offset) + self.caption_style, self.hpos, self.caption_side, True, + self.caption_size, offset=self.offset) return html_util.hpos_container(listing, self.hpos, self.offset).str() diff --git a/sks/document/document.k b/sks/document/document.k index 8d50b88..765b123 100644 --- a/sks/document/document.k +++ b/sks/document/document.k @@ -40,10 +40,10 @@ :include_sks_js.bool true :include_fonts.bool true -:serif_font Crimson Pro -:sans_font Open Sans -:mono_font Inconsolata -:font_scale 1.0 +:serif_typeface Crimson Pro +:sans_typeface Open Sans +:mono_typeface Inconsolata +:text_scale 1.0 :landscape.bool.tex false :paper_size..tex a4paper diff --git a/sks/document/document_class.cpp b/sks/document/document_class.cpp index e21bc9a..c54cae6 100644 --- a/sks/document/document_class.cpp +++ b/sks/document/document_class.cpp @@ -31,6 +31,10 @@ Document_class::Document_class(Machine& machine) : Klammer_base(machine) // show("document in main SKS"); m_machine = machine; sloc = get("K_loc"); + // The klammer's own position, so an argument error points at the + // @document application rather than at document_class.cpp: a + // default-constructed Locator captures the C++ source location. + loc = Locator::from_string(sloc); m_title = get("title"); m_subtitle = get("subtitle"); m_page_title = get("page_title"); @@ -77,17 +81,17 @@ Document_class::Document_class(Machine& machine) : Klammer_base(machine) s = s.substr(1, s.size() - 2); return s; }; - m_serif_font = strip_quotes(get("serif_font")); - m_sans_font = strip_quotes(get("sans_font")); - m_mono_font = strip_quotes(get("mono_font")); - m_font_scale = stof(get("font_scale")); + m_serif_typeface = strip_quotes(get("serif_typeface")); + m_sans_typeface = strip_quotes(get("sans_typeface")); + m_mono_typeface = strip_quotes(get("mono_typeface")); + m_text_scale = stof(get("text_scale")); - if (!m_serif_font.empty()) - m_resolved_serif = resolve_font(m_serif_font); - if (!m_sans_font.empty()) - m_resolved_sans = resolve_font(m_sans_font); - if (!m_mono_font.empty()) - m_resolved_mono = resolve_font(m_mono_font); + if (!m_serif_typeface.empty()) + m_resolved_serif = resolve_font(m_serif_typeface, loc); + if (!m_sans_typeface.empty()) + m_resolved_sans = resolve_font(m_sans_typeface, loc); + if (!m_mono_typeface.empty()) + m_resolved_mono = resolve_font(m_mono_typeface, loc); paper_size = get("paper_size"); landscape = strbool(get("landscape"), loc); leading = stof(get("leading")); @@ -207,6 +211,135 @@ void write_file(const std::string& filename, const std::string& contents, bool w } +// --------------------------------------------------------------------- +// Typeface markers (@typeface; see the marker note in sks/font/font.py) +// +// Hand-rolled scanning, no std::regex: these run over the whole assembled +// document and libstdc++'s regex executor recurses per character (the +// documented SIGSEGV hazard). +// --------------------------------------------------------------------- + +namespace { + +const std::string typeface_tag { "__TYPEFACE__" }; + +// The next marker at or after `from`. Returns false when there is none. +// A marker is __TYPEFACE______ : the two fields are taken up +// to the next "__", so neither may contain one (a typeface name with a +// double underscore is not a thing, and the scale is a number). +bool next_typeface_marker(const std::string& text, size_t from, + size_t& start, size_t& end, + std::string& name, std::string& scale) +{ + start = text.find(typeface_tag, from); + if (start == std::string::npos) return false; + size_t name_at = start + typeface_tag.size(); + size_t name_end = text.find("__", name_at); + if (name_end == std::string::npos) return false; + size_t scale_at = name_end + 2; + size_t scale_end = text.find("__", scale_at); + if (scale_end == std::string::npos) return false; + name = text.substr(name_at, name_end - name_at); + scale = text.substr(scale_at, scale_end - scale_at); + end = scale_end + 2; + return true; +} + +// The fontspec command name for the nth distinct (typeface, scale) pair. +// A TeX command name may hold only letters, and a typeface name may hold +// anything -- so the pairs are NUMBERED rather than transliterated, and +// the number is turned into letters. Nothing outside this file needs to +// know the rule: the klammer leaves a marker and this replaces it, so +// there is no second implementation to keep in step. +std::string typeface_command(size_t index) +{ + std::string suffix {}; + do { + suffix = char('a' + (index % 26)) + suffix; + index /= 26; + } while (index > 0); + return "\\ktface" + suffix; +} + +} // namespace + +void Document_class::collect_typefaces(const std::string& text) +{ + size_t pos = 0, start = 0, end = 0; + std::string name {}, scale {}; + while (next_typeface_marker(text, pos, start, end, name, scale)) { + std::pair entry { name, scale }; + if (std::find(m_extra_typefaces.begin(), m_extra_typefaces.end(), entry) + == m_extra_typefaces.end()) { + m_extra_typefaces.push_back(entry); + // Resolve ONCE per distinct typeface: resolve_font() throws a + // located error naming the available typefaces if the store + // does not have it, which is the whole error path for a + // misspelled or uninstalled typeface. + bool seen = false; + for (const auto& rf : m_resolved_extra) + if (rf.family_name == name) seen = true; + if (!seen) { + // What a @typeface klammer asked for, and what the store + // resolved it to: a decision the user cannot read off + // their own input, so "-v 1". + Resolved_font rf = resolve_font(name, loc); + (void)K::log(1, "Typeface \"" + name + "\" resolved to:", + rf.font_dir); + m_resolved_extra.push_back(rf); + } + } + pos = end; + } +} + +std::string Document_class::strip_typeface_markers(const std::string& text) +{ + std::string result {}; + size_t pos = 0, start = 0, end = 0; + std::string name {}, scale {}; + while (next_typeface_marker(text, pos, start, end, name, scale)) { + result += text.substr(pos, start - pos); + pos = end; + } + result += text.substr(pos); + return result; +} + +std::string Document_class::resolve_typeface_markers(const std::string& text) +{ + std::string result {}; + size_t pos = 0, start = 0, end = 0; + std::string name {}, scale {}; + while (next_typeface_marker(text, pos, start, end, name, scale)) { + result += text.substr(pos, start - pos); + auto entry = std::make_pair(name, scale); + auto it = std::find(m_extra_typefaces.begin(), m_extra_typefaces.end(), entry); + if (it != m_extra_typefaces.end()) + result += typeface_command(it - m_extra_typefaces.begin()); + pos = end; + } + result += text.substr(pos); + return result; +} + +std::string Document_class::typeface_preamble() +{ + std::stringstream ss {}; + for (size_t i = 0; i < m_extra_typefaces.size(); i++) { + const auto& [name, scale] = m_extra_typefaces[i]; + const Resolved_font* rf = nullptr; + for (const auto& f : m_resolved_extra) + if (f.family_name == name) rf = &f; + if (!rf) continue; + // \newfontfamily declares a switch command, unlike \setmainfont, + // which changes the document's font permanently. + ss << latex::font_command("newfontfamily" + typeface_command(i), + *rf, stof(scale)); + } + return ss.str(); +} + /* void output_msg(const std::string& msg) { diff --git a/sks/document/document_class.h b/sks/document/document_class.h index 3d795fa..065e25d 100644 --- a/sks/document/document_class.h +++ b/sks/document/document_class.h @@ -121,14 +121,35 @@ public: strings_t m_font_dirs {}; - std::string m_serif_font {}; - std::string m_sans_font {}; - std::string m_mono_font {}; - float m_font_scale = 1.0f; + std::string m_serif_typeface {}; + std::string m_sans_typeface {}; + std::string m_mono_typeface {}; + float m_text_scale = 1.0f; Resolved_font m_resolved_serif {}; Resolved_font m_resolved_sans {}; Resolved_font m_resolved_mono {}; + // Typefaces requested by @typeface klammers inside the document, in + // first-appearance order, each with the scale it was asked for. A + // klammer cannot declare a typeface itself (see font.py's + // typeface_marker), so it leaves a marker and @document collects them + // here, resolves and localizes each, declares it, and replaces every + // marker with the target's inline switch. + // SYNC: typeface_marker() in sks/font/font.py builds this marker. + std::vector> m_extra_typefaces {}; + std::vector m_resolved_extra {}; + // Collect the (typeface, scale) pairs of `text` into + // m_extra_typefaces, resolving each typeface once. Returns nothing; + // the markers are replaced by the per-target functions below. + void collect_typefaces(const std::string& text); + // html: the marker carries no inline switch (the span holds the family + // name), so each is replaced by nothing. + std::string strip_typeface_markers(const std::string& text); + // tex: each distinct (typeface, scale) gets a \newfontfamily command in + // the preamble; the marker is replaced by that command. + std::string resolve_typeface_markers(const std::string& text); + std::string typeface_preamble(); + std::string prolog {}; std::string paper_size {}; bool m_two_column = false; diff --git a/sks/document/document_html.cpp b/sks/document/document_html.cpp index 5567cb6..1593a5c 100644 --- a/sks/document/document_html.cpp +++ b/sks/document/document_html.cpp @@ -93,24 +93,28 @@ strings_t Document_class::resolved_font_names() add(m_resolved_serif); add(m_resolved_sans); add(m_resolved_mono); + // Typefaces a @typeface klammer asked for: each needs its own + // to the localized css, which is what carries the @font-face. + for (const auto& rf : m_resolved_extra) + add(rf); return result; } std::string Document_class::font_definitions() { std::stringstream ss {}; - if (!m_serif_font.empty() || !m_sans_font.empty() || !m_mono_font.empty()) { + if (!m_serif_typeface.empty() || !m_sans_typeface.empty() || !m_mono_typeface.empty()) { // Family names are quoted: an unquoted name with a digit-initial // word ("Source Sans 3") is invalid CSS, and a font-family using // var() with such a value computes to inherit, silently losing // the font. ss << ":root {\n"; - if (!m_serif_font.empty()) - ss << " --serif: \"" << m_serif_font << "\", serif;\n"; - if (!m_sans_font.empty()) - ss << " --sans-serif: \"" << m_sans_font << "\", sans-serif;\n"; - if (!m_mono_font.empty()) - ss << " --monospace: \"" << m_mono_font << "\", monospace;\n"; + if (!m_serif_typeface.empty()) + ss << " --serif: \"" << m_serif_typeface << "\", serif;\n"; + if (!m_sans_typeface.empty()) + ss << " --sans-serif: \"" << m_sans_typeface << "\", sans-serif;\n"; + if (!m_mono_typeface.empty()) + ss << " --monospace: \"" << m_mono_typeface << "\", monospace;\n"; ss << "}\n"; } // Font-size normalization across families is no longer emitted from @@ -120,11 +124,11 @@ std::string Document_class::font_definitions() // factors did from build-time OS/2 metrics, but applied to EVERY family // switch instead of the four CSS sites that remembered to multiply. // The metric extraction in the font store remains (the tex path and - // kdesc --font still use it). + // kdesc --typeface still use it). // Global font scale: applied to body font-size - if (m_font_scale != 1.0f) { + if (m_text_scale != 1.0f) { char buf[16]; - std::snprintf(buf, sizeof(buf), "%.4f", m_font_scale); + std::snprintf(buf, sizeof(buf), "%.4f", m_text_scale); ss << "body { font-size: " << buf << "rem; }\n"; } return ss.str(); @@ -743,6 +747,22 @@ std::string Document_class::html() std::string output_directory = create_html_output_directories(); save_string_input_as_file(); process_input_files(); + // @typeface markers are collected AFTER processing (a klammer cannot + // declare a typeface itself) but BEFORE the page is assembled, so the + // localized files and the to each typeface's css are in place + // when make_*_html_page() runs. The markers survive into the page + // CACHE deliberately: a cached file must still declare its typefaces + // on a later run, so the stripping happens here, on the assembled + // components, not before write_to_cache(). + for (auto& [basename, text] : m_file_components) { + collect_typefaces(text); + } + for (const auto& rf : m_resolved_extra) { + install_resolved_font(rf, output_directory); + } + for (auto& [basename, text] : m_file_components) { + text = strip_typeface_markers(text); + } auto headings = insert_section_numbers(); insert_section_numbers("___NUM2___", false); insert_html_caption_numbers(); diff --git a/sks/document/document_latex.cpp b/sks/document/document_latex.cpp index 31b94be..505f482 100644 --- a/sks/document/document_latex.cpp +++ b/sks/document/document_latex.cpp @@ -72,6 +72,14 @@ std::string Document_class::tex() } latex_text = join(lines, "\n"); latex_text = string_replace(latex_text, "___AT___", "^@"); + // @typeface: collect what the document asked for, declare each as a + // \newfontfamily switch command in the preamble, and replace every + // marker with its command. The declarations are appended to the + // prolog, which latex::page emits AFTER \setmainfont -- so fontspec is + // loaded and \newfontfamily is valid there. + collect_typefaces(latex_text); + latex_text = resolve_typeface_markers(latex_text); + prolog += typeface_preamble(); std::string structure_name = m_structure == docstruct_t::book ? "book" : m_structure == docstruct_t::article ? "article" : "plain"; @@ -81,5 +89,5 @@ std::string Document_class::tex() point_size, ragged_right, cover, landscape, latex_text, m_resolved_serif, m_resolved_sans, m_resolved_mono, - m_font_scale); + m_text_scale); } diff --git a/sks/font/font.k b/sks/font/font.k index ff71afa..45dbee5 100644 --- a/sks/font/font.k +++ b/sks/font/font.k @@ -2,28 +2,44 @@ @@@state font_dirs :desc Directories containing local fonts @@@ -@@@argtype font | - an abbreivation for a font +@@@argtype style_code | + a style code: one letter naming a type style :pattern r^|i^|b^|s^|t^|c @@@ -@@@argtype font_list | - a list of font symbols. One or more of 'r' (roman), 'i' (italic), +@@@argtype style_code_list | + a list of style codes. One or more of 'r' (roman), 'i' (italic), 'b' (bold), 's' (sans-serif), 't' (typewriter or monospace) or 'c' - (code) for each cell in a row. If there are fewer font symbols than - cells in a row, the last symbol is repeated. Extra symbols are + (code) for each cell in a row. If there are fewer style codes than + cells in a row, the last code is repeated. Extra codes are ignored. - :pattern ('font'^|\s)+ + :pattern ('style_code'^|\s)+ :python_cast (lambda s : s.split()) :default r @@@ -@@font.k fontname : Switch to an available font for PDF output. @@ +#[ +@font was retired 2026-08-25 and replaced by @typeface. It switched the +main font PERMANENTLY (a bare \setmainfont, no group) for tex, and for +html and txt its body was "*fontname*" -- so it printed the NAME of the +font as document text instead of setting anything. @typeface is the +temporary, all-target replacement. +]# -@@font.html :: *fontname* @@ -@@font.tex :: \setmainfont[Ligatures=TeX]{*fontname*} @@ -# @@font.tex : \setmainfont[Ligatures=TeX,Numbers=Lining]{*fontname*} @@ -@@font.txt :: *fontname* @@ +@@typeface.k name | text :scale.float 1.0 : +A temporary change of typeface for *text*: the typeface *name* is +resolved in the Klammertext font store (the directories of +KLAMMERTEXT_FONTS, then the distribution's own), and the document +declares and localizes it automatically -- writing this klammer is the +only declaration needed. *scale* multiplies the surrounding text size, +which a script whose glyphs are drawn small usually wants. + +The change ends with *text*: nothing after it is affected. Under the +txt target a typeface cannot be expressed at all, so *text* is emitted +unchanged. +@@ + +@@typeface.html,tex,txt :: @eval font.Typeface(K) @ @@ @@r.k text : Use the current Roman font for *text* @@ @@r.html :: *text* @@ @@ -74,10 +90,21 @@ \setmainfont{Garamond Libre} @@ -@@cjk.k text : Switch to font that supports CJK for *text* @@ -@@cjk.html :: *cjk* @@ -@@cjk.tex :: { #- @font Noto Sans CJK SC @ *cjk* #- } @@ -@@cjk.txt :: *cjk* @@ +#[ +Before 2026-08-25 every one of these bodies substituted "*cjk*" while the +declaration named the parameter "text", so @cjk raised "Variable "cjk" not +defined" under EVERY target -- it had never worked. Rewritten on +@typeface, which is what it was reaching for. + +The typeface it names is NOT part of the distribution (a usable Noto Sans +CJK is some 16-20 MB per weight, and Chinese line breaking needs more +than a typeface -- xeCJK for the tex path), so this klammer errors with +the list of available typefaces until one is installed with +"kdesc --typeface install". That is the honest behaviour: the klammer +states what it needs and the store reports what it has. +]# +@@cjk.k text : Set *text* in a typeface that supports CJK @@ +@@cjk.html,tex,txt :: @typeface Noto Sans CJK SC | *text* @ @@ @@fraktur.k s : Set text *s* in Fraktur @@ @@fraktur.tex :: \textfrak{*s*} @@ diff --git a/sks/font/font.py b/sks/font/font.py index 919b312..af9f9bb 100644 --- a/sks/font/font.py +++ b/sks/font/font.py @@ -23,6 +23,63 @@ class Subsuper(klammer_base.Klammer_base): else: return f"${self.base}^{self.script}$" +# The typeface-declaration marker. A klammer cannot declare a typeface +# itself: in html the @font-face must be in the page's css and the files +# localized into /fonts/, and in tex the \newfontfamily must be in +# the preamble -- both are @document's, and @document runs AFTER the +# klammers it contains (application is post-order). So @typeface leaves +# this marker where the typeface is needed, and @document collects the +# markers, resolves and localizes each typeface, declares it, and replaces +# the marker with whatever inline switch the target needs (a font-switch +# command for tex, nothing for html, which carries the family name in the +# span's own style). Same shape as the __CAPTION__ markers. +# +# SYNC: typeface_marker_rgx in sks/document/document_class.h -- a C++/Python +# pair, so the constant cannot be single-sourced the way a .k/.py pair can +# (TODO #47); change both or neither. +def typeface_marker(name, scale): + return f"__TYPEFACE__{name}__{scale}__" + +class Typeface(klammer_base.Klammer_base): + """A temporary typeface change (@typeface). See sks/font/font.k.""" + + def __init__(self, K): + super().__init__(K) + + def html(self): + # font-size-adjust is switched OFF for the span: the document + # normalizes families to a common x-height (font.css), which is + # meaningless for a script whose glyphs have no x-height at all -- + # hieroglyphs, cuneiform -- and would resize them unpredictably. + # The family name is quoted -- an unquoted digit-initial word + # ("Source Sans 3") is invalid CSS -- with SINGLE quotes, because + # this sits inside a double-quoted html attribute. + style = (f"font-family: '{self.name}', serif; " + f"font-size-adjust: none") + if float(self.scale) != 1.0: + style += f"; font-size: {float(self.scale)}em" + # The STYLE is markup, so its typographically active characters are + # hidden: the html target's transforms turn a single quote into a + # curly one, which broke the css font-family value. The TEXT is + # writer content and is deliberately left alone -- its quotes and + # dashes should be transformed. + return (typeface_marker(self.name, self.scale) + + f'' + + f'{self.text}') + + def tex(self): + # The marker becomes the font-switch command; the group is what + # makes the change temporary (the retired @font emitted a bare + # \setmainfont, which changed the font for the rest of the + # document). + return "{" + typeface_marker(self.name, self.scale) + " " + self.text + "}" + + def txt(self): + # A typeface cannot be expressed in plain text. Declared in the + # .k description, so this is documented behaviour rather than a + # silent per-target divergence. + return self.text + def html_fontify(text, font_symbol, font_size): cls = {"r" : "", "i" :"ritalic", diff --git a/sks/image/image.py b/sks/image/image.py index 6678ed9..6cfbd24 100644 --- a/sks/image/image.py +++ b/sks/image/image.py @@ -102,8 +102,8 @@ class Image(klammer_base.Klammer_base): self.caption += self.file_error_message result = html_util.add_caption( - result, "Figure", self.number, self.caption, self.caption_font, - self.hpos, self.caption_side, False, self.caption_font_size, + result, "Figure", self.number, self.caption, self.caption_style, + self.hpos, self.caption_side, False, self.caption_size, offset=self.offset) result = E("div").body(result).cls("image_margin") diff --git a/sks/image/image_grid.py b/sks/image/image_grid.py index 4581966..8cdc0b4 100644 --- a/sks/image/image_grid.py +++ b/sks/image/image_grid.py @@ -15,7 +15,7 @@ class Kargs: def __init__(self, target, K, **kwargs): self.__dict__ = kwargs self.center = False - self.caption_font = "i" + self.caption_style = "i" self.caption_side = "bottom" self.caption_side_center = False self.Image_search_path = K.Image_search_path @@ -40,7 +40,7 @@ class Kargs: self.border = False self.vmargin = False self.abswidth = 0.0 - self.caption_font_size = .9 + self.caption_size = .9 def img(K, target, basename, caption, width): result = image.Image( diff --git a/sks/kutil/kutil.k b/sks/kutil/kutil.k index b283518..596e519 100644 --- a/sks/kutil/kutil.k +++ b/sks/kutil/kutil.k @@ -163,8 +163,8 @@ other structures. The ^:offset is used only if the ^:hpos value is "left" or :caption :number.bool true :caption_side.side - :caption_font.font i - :caption_font_size.float .9 + :caption_style.style_code i + :caption_size.float .9 : Arguments that define a caption for a block element. @@ diff --git a/sks/list/list.k b/sks/list/list.k index db331c9..464f80f 100755 --- a/sks/list/list.k +++ b/sks/list/list.k @@ -9,7 +9,7 @@ @@ol.k list_items.rest :cmp.bool false :initial.int 1 : Ordered list, with numbered list elements. @@ @@ol.html,tex,txt :: @eval list.List(K, "ol") eval@ @@ -@@define.k descriptions.rest(2) :font i : A list of words or phrases and their definitions or descriptions @@ +@@define.k descriptions.rest(2) :style.style_code i : A list of words or phrases and their definitions or descriptions @@ @@define.html,tex :: @eval list.Define(K) eval@ @@ @@columns.k items :n.int 2 : Create two or more columns from the text @@ diff --git a/sks/list/list.py b/sks/list/list.py index 526a062..52133f8 100644 --- a/sks/list/list.py +++ b/sks/list/list.py @@ -66,7 +66,7 @@ class Define(klammer_base.Klammer_base): "b" : f"{s}", "t" : f'{s}', "c" : f'{s}', - "s" : f'{s}'}[self.font] + "s" : f'{s}'}[self.style] result = "\n\n".join([f"
{fontify(term)}
\n
{definition}
" for term,definition in self.items]) result = f"
\n{result}\n
" @@ -79,7 +79,7 @@ class Define(klammer_base.Klammer_base): 'b' : r'\normalfont\bfseries', 't' : r'\normalfont\ttfamily', 'c' : r'\normalfont\ttfamily', - 's' : r'\normalfont\sffamily'}[self.font] + 's' : r'\normalfont\sffamily'}[self.style] return f"{font} {s}" # The "\hfill\newline" was to force the description to the next line even if the item was small: # result = "\n\n".join([f"\\item[{{{fontify(term)}}}]\\hfill\n\\newline {definition}" diff --git a/sks/table/table.k b/sks/table/table.k index 074fd7a..93da7cb 100644 --- a/sks/table/table.k +++ b/sks/table/table.k @@ -206,8 +206,8 @@ :grid.bool false :cell_hpos.cell_hpos :justify.table_justify - :header_font.font i - :font.font_list + :header_style.style_code i + :style.style_code_list :colspan.table_span :rowspan.table_span :calc.table_calc diff --git a/sks/table/table.py b/sks/table/table.py index e3533ea..97e43f7 100644 --- a/sks/table/table.py +++ b/sks/table/table.py @@ -84,7 +84,7 @@ class Table(klammer_base.Klammer_base): self.apply_formats() self.cell_hpos = extend(parse_hpos(self.K_target, self.cell_hpos), self.row_size) #self.cell_hpos = self.cell_hpos.split(";") - self.font = extend(self.font, self.row_size) + self.style = extend(self.style, self.row_size) # In html, a 'fit' column mixed with sized columns must be clamped # to its widest entry (tex's \widthof semantics) or it absorbs the # window width: its cells get nowrap (class Wfit; see table.css). @@ -445,7 +445,7 @@ class Table(klammer_base.Klammer_base): self.column_widths = [] for col_i in range(len(self.cells[0])): longest = "" - longest_font = "r" + longest_style = "r" for row_i in range(len(self.cells)): cell = self.cells[row_i][col_i] # A colspan anchor's text spans several columns and must @@ -456,9 +456,9 @@ class Table(klammer_base.Klammer_base): longest_in_line = lines[-1] if len(longest_in_line) > len(longest): longest = longest_in_line - longest_font = cell.font - if longest_font != "r": - longest = font.tex_fontify(longest, longest_font, 1.0) + longest_style = cell.font + if longest_style != "r": + longest = font.tex_fontify(longest, longest_style, 1.0) self.column_widths.append(longest) # ---- :hpos ------------------------------------------------------------- @@ -500,9 +500,9 @@ class Table(klammer_base.Klammer_base): for cell_i, cell in enumerate(row): rspan = self.span_count(self.s_rowspan, cell_i, row_i) cspan = self.span_count(self.s_colspan, row_i, cell_i) - font = self.font[cell_i] + font = self.style[cell_i] if row_i == 0 and self.header: - font = self.header_font + font = self.header_style # A span anchor's right and bottom borders come from the # boundary at the END of the merged region. right_i = cell_i + max(cspan, 1) @@ -589,9 +589,9 @@ class Table(klammer_base.Klammer_base): if self.number or self.caption: result = html_util.add_caption( result, "Table", self.number, self.caption, - self.caption_font, hpos=self.hpos, + self.caption_style, hpos=self.hpos, side=self.caption_side, - font_size=self.caption_font_size, max_width="100%", + font_size=self.caption_size, max_width="100%", offset=self.offset) else: result.attr("style", "max-width: 100%") @@ -606,9 +606,9 @@ class Table(klammer_base.Klammer_base): if table_width: result.attr("style", f"{layout}width: 100%") result = html_util.add_caption( - result, "Table", self.number, self.caption, self.caption_font, + result, "Table", self.number, self.caption, self.caption_style, hpos=self.hpos, side=self.caption_side, - font_size=self.caption_font_size, width=table_width, + font_size=self.caption_size, width=table_width, offset=self.offset) else: # An uncaptioned table still gets the position container, so @@ -757,7 +757,7 @@ class Table(klammer_base.Klammer_base): def make_caption(self): column_count = str(len(self.rows[0])) caption = latex_util.make_caption_text( - self.number, "Table", self.caption, self.caption_font, self.caption_font_size) + self.number, "Table", self.caption, self.caption_style, self.caption_size) result = "\\multicolumn{" + column_count + "}{c}{\\parbox{\\tableboxwidth}{" result += "\\vspace*{8pt}\\centering\\small\\em " result += caption @@ -836,8 +836,8 @@ class Table(klammer_base.Klammer_base): result = latex_util.add_caption( result, "Table", self.number, self.caption, "\\tableboxwidth", hpos=self.hpos, side=self.caption_side, - font_symbol=self.caption_font, - font_size=self.caption_font_size, offset=self.offset) + font_symbol=self.caption_style, + font_size=self.caption_size, offset=self.offset) else: result = latex_util.caption_wrapper(result, self.hpos, offset=self.offset) result = measure + result diff --git a/sks/target/latex_util.cpp b/sks/target/latex_util.cpp index 07a5526..514b2d2 100644 --- a/sks/target/latex_util.cpp +++ b/sks/target/latex_util.cpp @@ -163,10 +163,10 @@ namespace latex { const std::string& cover, bool landscape, const std::string& body, - Resolved_font serif_font, - Resolved_font sans_font, - Resolved_font mono_font, - float font_scale) + Resolved_font serif_typeface, + Resolved_font sans_typeface, + Resolved_font mono_typeface, + float text_scale) { (void)K::log(3); bool book_format = (structure == "book"); @@ -202,10 +202,10 @@ namespace latex { // x-height: serif_xh / other_xh (matches lowercase) // cap-height: serif_ch / other_ch (matches capitals) // average: mean(serif_xh,serif_ch) / mean(other_xh,other_ch) - float serif_xh = serif_font.xheight_ratio; - float serif_ch = serif_font.capheight_ratio; + float serif_xh = serif_typeface.xheight_ratio; + float serif_ch = serif_typeface.capheight_ratio; float serif_avg = (serif_xh + serif_ch) / 2.0f; - auto font_scale_fn = [&](const Resolved_font& other) -> float { + auto text_scale_fn = [&](const Resolved_font& other) -> float { float other_avg = (other.xheight_ratio + other.capheight_ratio) / 2.0f; if (serif_avg > 0.0f && other_avg > 0.0f) // return serif_xh / other.xheight_ratio; // x-height @@ -213,21 +213,21 @@ namespace latex { return serif_avg / other_avg; // average return 0.0f; }; - // Global font_scale multiplies all font sizes uniformly - float gs = (font_scale != 1.0f) ? font_scale : 0.0f; - float sans_s = font_scale_fn(sans_font); - float mono_s = font_scale_fn(mono_font); + // Global text_scale multiplies all font sizes uniformly + float gs = (text_scale != 1.0f) ? text_scale : 0.0f; + float sans_s = text_scale_fn(sans_typeface); + float mono_s = text_scale_fn(mono_typeface); // Apply global scale: multiply into per-font scale, or use alone if (gs > 0.0f) { - sans_s = (sans_s > 0.0f) ? sans_s * font_scale : font_scale; - mono_s = (mono_s > 0.0f) ? mono_s * font_scale : font_scale; + sans_s = (sans_s > 0.0f) ? sans_s * text_scale : text_scale; + mono_s = (mono_s > 0.0f) ? mono_s * text_scale : text_scale; } - ss << font_command("setmainfont", serif_font, gs); - ss << font_command("setsansfont", sans_font, sans_s); - ss << font_command("setmonofont", mono_font, mono_s); + ss << font_command("setmainfont", serif_typeface, gs); + ss << font_command("setsansfont", sans_typeface, sans_s); + ss << font_command("setmonofont", mono_typeface, mono_s); - // Scale leading proportionally with font_scale - float effective_leading = leading * ((font_scale != 1.0f) ? font_scale : 1.0f); + // Scale leading proportionally with text_scale + float effective_leading = leading * ((text_scale != 1.0f) ? text_scale : 1.0f); ss << "\n\\setstretch{" << effective_leading << "}\n"; if (ragged_right) ss << "\\raggedright\n"; diff --git a/sks/target/latex_util.h b/sks/target/latex_util.h index 1380ece..2711041 100644 --- a/sks/target/latex_util.h +++ b/sks/target/latex_util.h @@ -6,6 +6,14 @@ namespace latex { +// A fontspec declaration: "\[Path=...,Scale=...]{}". +// The command is "setmainfont"/"setsansfont"/"setmonofont" for the +// document's three role typefaces, and "newfontfamily\ktface" for a +// typeface a @typeface klammer asked for (a switch command rather than a +// permanent change). +std::string font_command(const std::string& command, const Resolved_font& font, + float scale); + std::string page(const std::string& structure, const std::string& title, const std::string& subtitle, @@ -23,10 +31,10 @@ std::string page(const std::string& structure, const std::string& cover, bool landscape, const std::string& body, - Resolved_font serif_font = {}, - Resolved_font sans_font = {}, - Resolved_font mono_font = {}, - float font_scale = 1.0f); + Resolved_font serif_typeface = {}, + Resolved_font sans_typeface = {}, + Resolved_font mono_typeface = {}, + float text_scale = 1.0f); } void check_for_xelatex(); diff --git a/tst/character_test.sh b/tst/character_test.sh index d7d725d..bd76769 100755 --- a/tst/character_test.sh +++ b/tst/character_test.sh @@ -2,8 +2,12 @@ # # character_test.sh — Regression tests for the ^-character forms the engine # resolves at read time: the ^UUUU^ Unicode code point (4-6 hex digits, -# since 2026-08-23 covering the full range to U+10FFFF), and the character -# tables (mnemonics, diacritics) it must not disturb. +# since 2026-08-23 covering the full range to U+10FFFF), the +# ^UUUU UUUU ...^ SEQUENCE form (added 2026-08-25: one caret pair around +# whitespace-delimited tokens, assembled at katom level by +# process_unicode_sequences — so, unlike the string-level single form, it +# is skipped inside @eval/@read/@cond spans and for literal content), and +# the character tables (mnemonics, diacritics) it must not disturb. # # SKS-independent: character handling is katomizer/engine machinery # (mac/ktype.h nonascii katom, mac/character.cpp), so everything runs with @@ -39,6 +43,26 @@ reset=$'\033[0m' trim() { awk '{ sub(/[ \t\r]+$/, "") } { line[NR]=$0 } END { f=1; while (f<=NR && line[f]=="") f++; l=NR; while (l>=1 && line[l]=="") l--; for (i=f;i<=l;i++) print line[i] }'; } +# check_err NAME PHRASE INPUT — the render must FAIL (nonzero exit) with +# PHRASE on stderr. Matched wrap-insensitively: error messages are +# justified to 80 columns and may wrap anywhere. +check_err() { + local name="$1" phrase="$2" input="$3" + local err status + err=$("$KTEXT" --klammersets none -s "$input" -d 2>&1 >/dev/null); status=$? + err=$(printf '%s' "$err" | tr '\n' ' ' | tr -s ' ') + if [ $status -eq 0 ]; then + echo "${red}FAIL${reset} $name — expected an error, got exit 0" + FAIL=$((FAIL+1)); return + fi + case "$err" in + *"$phrase"*) echo "${green}PASS${reset} $name"; PASS=$((PASS+1));; + *) echo "${red}FAIL${reset} $name" + echo " wanted phrase: [$phrase]" + echo " stderr: $(echo "$err" | head -c 200)"; FAIL=$((FAIL+1));; + esac +} + # check NAME EXPECTED INPUT — render INPUT (no klammer set, default # target); exit 0 and stdout == EXPECTED. check() { @@ -66,14 +90,42 @@ check " 1. 4-digit BMP code point" $'A ☺ B' 'A ^263A^ B' check " 2. 5-digit code point, whole" $'A \xf0\x93\x80\x80 B' 'A ^13000^ B' check " 3. 5-digit emoji" $'\xf0\x9f\x98\x80' '^1F600^' check " 4. 6-digit top of the range" $'\xf4\x8f\xbf\xbd' '^10FFFD^' -check " 5. above U+10FFFF is invalid" 'Invalid Unicode: 16777215' '^FFFFFF^' -check " 6. surrogate is invalid" 'Invalid Unicode: 55296' '^D800^' +# An invalid code point is a located ERROR (2026-08-25, the +# no-warn-and-continue policy) -- it was in-band "Invalid Unicode" text in +# the document before, which is the wrong channel for a generator's typo. +check_err " 5. above U+10FFFF is an error" 'beyond U+10FFFF' '^FFFFFF^' +check_err " 6. surrogate is an error" 'a surrogate' '^D800^' +check_err " 6a. noncharacter is an error" 'a noncharacter' '^FDD0^' check " 7. mnemonic undisturbed" 'ß' '^s^' -# The diacritic tables compose base + COMBINING mark (a U+0308), not the -# precomposed letter -- the expected string is built the same way. -check " 8. diacritic undisturbed" $'Ma\xcc\x88dchen' 'M^a"dchen' +# The tables emit the PRECOMPOSED (NFC) character where one exists (since +# 2026-08-25; they formerly emitted base + combining mark) -- the same +# bytes an editor's direct entry produces, so ^a" and a typed ä compare +# equal in search and copy-paste. The literal ä below is precomposed. +check " 8. diacritic is the precomposed character" 'Mädchen' 'M^a"dchen' check " 9. code point beside quoted punctuation" $'☺ --' '^263A^ ^-^-' +echo +echo "-- sequences: ^UUUU UUUU ...^ (one caret pair, tokens 4-6 hex digits," +echo " no space at the elbows, one line; process_unicode_sequences)" +check "10. CJK sequence" $'X \xe9\x81\x93\xe5\x8f\xaf\xe9\x81\x93\xe9\x9d\x9e\xe5\xb8\xb8\xe9\x81\x93 Y' 'X ^9053 53EF 9053 975E 5E38 9053^ Y' +check "11. mixed widths" $'\xe2\x98\xba\xf0\x93\x80\x80\xf0\x9f\x98\x80' '^263A 13000 1F600^' +check "12. space after opening caret: not a sequence" 'A ^ 263A 263A^ B' 'A ^ 263A 263A^ B' +check "13. space before closing caret: not a sequence" 'A ^263A 263A ^ B' 'A ^263A 263A ^ B' +check "14. newline aborts (one line only)" $'A ^263A\n263A^ B' $'A ^263A\n263A^ B' +check "15. short token aborts whole sequence" 'A ^263A 263^ B' 'A ^263A 263^ B' +check "16. @eval code keeps its carets" '11' '@eval len("^263A 263A^") @' +check "17. literal span: sequence stays verbatim" 'x ^263A 263A^ y' "^'x ^263A 263A^ y'^" +check "17a. single-character token stands for itself" $'\xe9\x81\x93x\xe5\x8f\xaf' '^9053 x 53EF^' +check "17b. non-alnum single token behaves as quoted" '☺-☺' '^263A - 263A^' +check "17c. structural code points are re-read-safe" '@@' '^0040 0040^' +check_err "17d. invalid code point in a CLOSED sequence" 'in a ^...^ sequence' '^263A FFFFFF^' +check "17e. unclosed hex-like prose is untouched" 'about ^263A FFFFFF prose' 'about ^263A FFFFFF prose' + +echo +echo "-- direct entry and NFC convergence" +check "18. pinyin tone is precomposed" 'ā' '^a1' +check "19. typed UTF-8 passes through byte-identical" 'Mädchen naïveté ü — done' 'Mädchen naïveté ü — done' + echo echo "=================================================================" echo "${bold}Results: $PASS passed, $FAIL failed${reset}" diff --git a/tst/kdesc_test.sh b/tst/kdesc_test.sh index 60e7572..304833b 100755 --- a/tst/kdesc_test.sh +++ b/tst/kdesc_test.sh @@ -83,6 +83,18 @@ shows " 1. -k lists klammers" '@table' "$KDESC" -k shows " 2. -t lists targets" 'html' "$KDESC" -t shows " 3. -c lists character codes" '.' "$KDESC" -c shows " 4. -i reads the named input" '@x' "$KDESC" -i "$DIR/split.k" -k +# -u describes how to write a Unicode character. Asserted by OUTCOME, on +# the two things the section must carry however its prose is rewritten: +# the ^...^ notation it teaches, and a rendered character beside its code +# point -- the whole point of the section is that a reader sees the glyph +# they are about to write. It does NOT pin the prose, which is meant to +# grow (as of 2026-08-25 the section predates the sequence form and the +# typeface klammer, and does not yet mention either). +shows " 4a. -u describes the Unicode syntax" '\^0418\^' "$KDESC" -u +# The rendered glyph, as UTF-8 BYTE escapes: macOS ships bash 3.2, which +# expands $'\xHH' but not $'\U...' (that difference failed this suite's +# sibling on Olion, 2026-08-23). +shows " 4b. ... and shows the characters" $'\xf0\x93\x8a\xaa' "$KDESC" -u echo echo "-- the multi-letter topics --" diff --git a/tst/kdiag_test.sh b/tst/kdiag_test.sh index 141ef5d..d72528e 100755 --- a/tst/kdiag_test.sh +++ b/tst/kdiag_test.sh @@ -281,7 +281,9 @@ shows "35. --ws applies whitespace operators" 'ab' --ws 'a#- b' # DIAERESIS, not the precomposed U+00FC. The diacritic FOLLOWS the base # letter -- that is the design of the ^ codes -- so the two spellings look # identical in an editor and only one of them matches. -shows "36. --nonascii decodes to a combining sequence" 'ü' --nonascii '^u"' +# Precomposed since 2026-08-25: the tables emit NFC, the same bytes as +# direct entry (formerly base + combining mark). +shows "36. --nonascii decodes to the precomposed character" 'ü' --nonascii '^u"' shows "37. --literal marks a literal span" 'x' --literal "^'x'^" # Evaluating needs a target: Eval::eval re-reads its result under K_target, and # kdiag has no target argument, so with the variable unset EVERY @eval died