diff --git a/SPEC.md b/SPEC.md index aff0467..0059bc0 100644 --- a/SPEC.md +++ b/SPEC.md @@ -73,4 +73,6 @@ The v1 client presents one reader screen: - Each library page carries a search box that narrows it by title or address, and each row carries its own actions: remove a bookmark, leave a site or check it now, forget a page. History lists one row per address, headed by the most recent visit, rather than repeating a page once per visit. Clearing history takes a second step on a page the reader can simply leave, since it cannot be undone. - A change is a link too: following `mews:do/` applies it and rerenders the page in place without adding a history entry. Only a link followed from one of the client's own pages is honored, so a fetched page cannot clear history or change a setting on the reader's behalf (spec 8.1). A form on one of these pages is routed the same way rather than sent to the network. - Our own pages are built from the same blocks as a page but are interface, not an article, so the renderer lays them out differently: wider before they stop growing, denser, rows divided by a line instead of bulleted, section headings quieter, a note under a row set smaller and dimmer, links carried by weight rather than an underline, and a one-field form on a single line. -- Fonts are bundled, and v1 ships Atkinson Hyperlegible. The reader's typeface applies to the whole app, chrome included, so the address bar and the page below it are one face. Adding a font is one TTF in `app/assets/fonts/`, one `fonts` entry in `app/pubspec.yaml`, one option on the settings page, and one label in `app/locales/en-US/main.ftl`. +- Fonts are bundled, never fetched. The reader picks between two sans faces, Atkinson Hyperlegible and Luciole, and two serifs, Literata and Charis SIL; all four carry regular, italic, bold and bold italic, which the renderer needs for emphasis. The chosen face applies to the whole app, chrome included, so the address bar and the page below it are one face. +- A character the chosen face has no glyph for is looked for in that face's own fallbacks before the platform is asked. Hebrew always comes from a bundled Hebrew face matched to the shape of the chosen one, so it never depends on what the device has installed. The serifs also cover Greek, Cyrillic and a wide Latin range and fall back to each other; the two sans faces are narrow and have no bundled sans to turn to, so for those the platform answers, and it answers a sans with a sans. Scripts nothing bundled covers, such as Arabic and the CJK languages, always come from the platform. +- Adding a font is four TTFs in `app/assets/fonts/` with its licence beside them, one `fonts` entry in `app/pubspec.yaml`, one entry in `app/lib/application/fonts.dart`, and one label in `app/locales/en-US/main.ftl`. A family named in the registry but missing from pubspec would render in the platform's font without saying so, so `app/test/fonts_test.dart` checks that the two agree. diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/AtkinsonHyperlegible-OFL.txt similarity index 100% rename from app/assets/fonts/OFL.txt rename to app/assets/fonts/AtkinsonHyperlegible-OFL.txt diff --git a/app/assets/fonts/Charis-Bold.ttf b/app/assets/fonts/Charis-Bold.ttf new file mode 100644 index 0000000..344e56e Binary files /dev/null and b/app/assets/fonts/Charis-Bold.ttf differ diff --git a/app/assets/fonts/Charis-BoldItalic.ttf b/app/assets/fonts/Charis-BoldItalic.ttf new file mode 100644 index 0000000..44115cb Binary files /dev/null and b/app/assets/fonts/Charis-BoldItalic.ttf differ diff --git a/app/assets/fonts/Charis-Italic.ttf b/app/assets/fonts/Charis-Italic.ttf new file mode 100644 index 0000000..d413e0a Binary files /dev/null and b/app/assets/fonts/Charis-Italic.ttf differ diff --git a/app/assets/fonts/Charis-OFL.txt b/app/assets/fonts/Charis-OFL.txt new file mode 100644 index 0000000..12d573c --- /dev/null +++ b/app/assets/fonts/Charis-OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 1997-2025 SIL Global (https://www.sil.org/) +with Reserved Font Names "Charis" and "SIL". + +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 + + +----------------------------------------------------------- +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/app/assets/fonts/Charis-Regular.ttf b/app/assets/fonts/Charis-Regular.ttf new file mode 100644 index 0000000..a4e75a4 Binary files /dev/null and b/app/assets/fonts/Charis-Regular.ttf differ diff --git a/app/assets/fonts/Literata-Bold.ttf b/app/assets/fonts/Literata-Bold.ttf new file mode 100644 index 0000000..d39f653 Binary files /dev/null and b/app/assets/fonts/Literata-Bold.ttf differ diff --git a/app/assets/fonts/Literata-BoldItalic.ttf b/app/assets/fonts/Literata-BoldItalic.ttf new file mode 100644 index 0000000..9f69c33 Binary files /dev/null and b/app/assets/fonts/Literata-BoldItalic.ttf differ diff --git a/app/assets/fonts/Literata-Italic.ttf b/app/assets/fonts/Literata-Italic.ttf new file mode 100644 index 0000000..ebb050c Binary files /dev/null and b/app/assets/fonts/Literata-Italic.ttf differ diff --git a/app/assets/fonts/Literata-OFL.txt b/app/assets/fonts/Literata-OFL.txt new file mode 100644 index 0000000..9d3b4e3 --- /dev/null +++ b/app/assets/fonts/Literata-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2017 The Literata Project Authors (https://github.com/googlefonts/literata) + +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 + + +----------------------------------------------------------- +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/app/assets/fonts/Literata-Regular.ttf b/app/assets/fonts/Literata-Regular.ttf new file mode 100644 index 0000000..b0e7f7a Binary files /dev/null and b/app/assets/fonts/Literata-Regular.ttf differ diff --git a/app/assets/fonts/Luciole-Bold.ttf b/app/assets/fonts/Luciole-Bold.ttf new file mode 100644 index 0000000..f4272f1 Binary files /dev/null and b/app/assets/fonts/Luciole-Bold.ttf differ diff --git a/app/assets/fonts/Luciole-BoldItalic.ttf b/app/assets/fonts/Luciole-BoldItalic.ttf new file mode 100644 index 0000000..ea9357a Binary files /dev/null and b/app/assets/fonts/Luciole-BoldItalic.ttf differ diff --git a/app/assets/fonts/Luciole-Italic.ttf b/app/assets/fonts/Luciole-Italic.ttf new file mode 100644 index 0000000..be2ea7c Binary files /dev/null and b/app/assets/fonts/Luciole-Italic.ttf differ diff --git a/app/assets/fonts/Luciole-README.md b/app/assets/fonts/Luciole-README.md new file mode 100644 index 0000000..53d07ab --- /dev/null +++ b/app/assets/fonts/Luciole-README.md @@ -0,0 +1,126 @@ +Luciole package +=============== + +## Description + +This package provides four Unicode Math text fonts `Luciole-*.ttf` and +a companion maths font `Luciole-Math.otf`. +They have been developed explicitly for visually impaired people +and are the result of a collaboration between the Regional Technical Center +for Visual Impairment, the type-design studio typographies.fr and +the mathematician Daniel Flipo. The Luciole project received a grant from +the Swiss Ceres Foundation and the PEP69 association, and support from +the DIPHE laboratory at the Université Lumière Lyon 2 and the association +GUTenberg. + +## Contents + +* Luciole-Regular.ttf, Luciole-Regular-Italic.ttf, Luciole-Bold.ttf and + Luciole-BoldItalic.ttf TrueType Text fonts +* Luciole-Math.otf OpenType Math font +* Luciole-Math-Bold.otf Bold variant (limited coverage) +* luciole-math.sty LaTeX style file for Luciole-Math.otf +* Luciole.fontspec Defaults for Text fonts +* Luciole-Doc.pdf Documentation in PDF format +* Luciole-Doc.ltx Source of Luciole-Math.pdf +* unimath-luciole.pdf Modified version of unimath-symbols.pdf + showing available Luciole-Math symbols compared + to other Maths fonts, +* unimath-luciole.ltx Source of unimath-luciole.pdf +* README.md (this file) + +## Installation + +This package `Luciole’ is meant to be installed automatically by TeXLive, +MikTeX, etc. Otherwise, can be installed under TEXMFHOME or TEXMFLOCAL, f.i. +text and math fonts in directory texmf-local/fonts/truetype/public/luciole/, +and luciole-math.sty, Luciole.fontspec in directory +texmf-local/tex/latex/luciole/. +Documentation files and their sources can go to directory +texmf-local/doc/fonts/public/luciole/ + +Don't forget to rebuild the file database (mktexlsr or so) if you install +under TEXMFLOCAL. + +Finally, make the system font database aware of the Luciole fonts +(fontconfig under Linux). + +## License + +* The Text fonts `Luciole-*.ttf` are licensed under the Creative Common CC-BY +Font License, +* The font `Luciole-Math.otf` is licensed under the SIL Open Font License, +Version 1.1. This license is available with a FAQ at: +http://scripts.sil.org/OFL +* The other files are distributed under the terms of the LaTeX Project +Public License from CTAN archives in directory macros/latex/base/lppl.txt. +Either version 1.3c or, at your option, any later version. + +## Changes + +* First public version: 0.40 + +* v0.50 + - internal: \widearc reencoded for compatibility with my other math fonts. + - cv00 (unavailable) replaced by cv11 to access slashed zeros. + - `luciole-math.sty` tunes \TeX, \LaTeX, \LaTeXe logos for the Luciole fonts; + option "no-logos" added to prohibit their redefinition. + +* v0.55 + - Internal: \std@equal redefined, redefinition of \std@minus removed. + - Most horizontal arrows and harpoons are now stretchable. + - Fixed kernings between ([{ and \mij, \mity. + - Math table fixed for glyphs horizontal and vertical composition. + +* v0.60 + - Improved design of large parentheses. + - Invisible Math operators (U-2061 to U+2064) added. + - Compatibility with Typst v0.14: all glyphs used in vertical compositions + now sit on the baseline. + - Documentation fixes. + +* v0.70 + - Script variant added for calligraphic capitals. + - Circle, diamond and lozenge operators : sizes enlarged. + - Brackets redesigned. + - Math table Sub/Superscripts parameters tuned, see + https://tex.stackexchange.com/questions/759695/ + - Math table fraction parameters tuned. + - Fixed spaces' width (U+2000 to U+200B and U+205F). + - Variation Selectors (U-FE00 and U+FE01) added; they work on all Math + symbols listed on + https://www.unicode.org/Public/UCD/latest/ucd/StandardizedVariants.txt + which are available in Luciole Math fonts (U+FE01 provides access to + script variants of the default calligraphic capitals (Chancery like). + - Documentation fixes. + +* v0.71 + - Superscripts for calligraphic capitals redesigned. + +* v0.75 + - Text fonts updated to version 2.001. + - \setminus (U+2113) and \smallsetminus (U+E074) have now different glyphs. + - \ell (U+2113) is now upright. \mscrl (U+1D4C1) and \mbfscrl (U+1D4F5) + added; they are slanted versions of \ell and \mbfell (U+E055). + - New CharacterVariants cv12 and cv13 provide alternative shapes + for the lowercase l which might be confused with uppercase I (thanks + to Baptiste Auguié for suggesting this). + - New CharacterVariant cv14 provides an alternative shape for 'a'. + - Glyphs \mscr (U+1D4C7) and \mbfscr (U+1D4FB) added for Griffith's + curly 'r'. + - Over eighty Math symbols added to Luciole-Math-Bold. + +* v0.76 + - Lowercase \gamma (regular, bold, italic, bolditalic) redesigned + to avoid confusion with y (suggested by Baptiste Auguié). + +--- +Copyright 2024-2026 Laurent Bourcellier, Jonathan Fabreguettes, Daniel Flipo +E-mail: daniel (dot) flipo (at) free (dot) fr + +## Attribution for this bundle + +Luciole by Laurent Bourcellier and Jonathan Perez (typographies.fr) with the +Centre Technique Régional pour la Déficience Visuelle, licensed CC BY 4.0: +https://creativecommons.org/licenses/by/4.0/ +Project home: https://luciole-vision.com/ diff --git a/app/assets/fonts/Luciole-Regular.ttf b/app/assets/fonts/Luciole-Regular.ttf new file mode 100644 index 0000000..ae0476b Binary files /dev/null and b/app/assets/fonts/Luciole-Regular.ttf differ diff --git a/app/assets/fonts/NotoHebrew-OFL.txt b/app/assets/fonts/NotoHebrew-OFL.txt new file mode 100644 index 0000000..56662b6 --- /dev/null +++ b/app/assets/fonts/NotoHebrew-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/hebrew) + +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/app/assets/fonts/NotoSansHebrew-Bold.ttf b/app/assets/fonts/NotoSansHebrew-Bold.ttf new file mode 100644 index 0000000..a4b1e85 Binary files /dev/null and b/app/assets/fonts/NotoSansHebrew-Bold.ttf differ diff --git a/app/assets/fonts/NotoSansHebrew-Regular.ttf b/app/assets/fonts/NotoSansHebrew-Regular.ttf new file mode 100644 index 0000000..6c71d56 Binary files /dev/null and b/app/assets/fonts/NotoSansHebrew-Regular.ttf differ diff --git a/app/assets/fonts/NotoSerifHebrew-Bold.ttf b/app/assets/fonts/NotoSerifHebrew-Bold.ttf new file mode 100644 index 0000000..0daf1ce Binary files /dev/null and b/app/assets/fonts/NotoSerifHebrew-Bold.ttf differ diff --git a/app/assets/fonts/NotoSerifHebrew-Regular.ttf b/app/assets/fonts/NotoSerifHebrew-Regular.ttf new file mode 100644 index 0000000..58499ec Binary files /dev/null and b/app/assets/fonts/NotoSerifHebrew-Regular.ttf differ diff --git a/app/lib/application/controllers/reader_settings_controller.dart b/app/lib/application/controllers/reader_settings_controller.dart index f35ba4f..cac0415 100644 --- a/app/lib/application/controllers/reader_settings_controller.dart +++ b/app/lib/application/controllers/reader_settings_controller.dart @@ -1,6 +1,7 @@ import 'package:signals/signals.dart'; import '../../database/database.dart'; +import '../fonts.dart'; /// The reader's presentation choices (spec 8.2) plus the per-app limits they /// move within. Values persist through ReaderSettings in the database. @@ -19,7 +20,7 @@ class ReaderSettingsController { static const themeModes = ['light', 'dark', 'system']; static const variants = ['site', 'warm', 'cool', 'green', 'mono']; - final font = signal('Atkinson Hyperlegible'); + final font = signal(defaultFontFamily); final fontScale = signal(1.0); final themeMode = signal('system'); final variant = signal('site'); @@ -34,7 +35,11 @@ class ReaderSettingsController { } Future setFont(String family) async { - if (family.isEmpty || family == font.value) return false; + // Only a family this build bundles: an unknown one would silently render + // in the platform's font instead. + if (!readerFonts.any((f) => f.family == family) || family == font.value) { + return false; + } font.value = family; await _persist(); return true; diff --git a/app/lib/application/fonts.dart b/app/lib/application/fonts.dart new file mode 100644 index 0000000..39bd99b --- /dev/null +++ b/app/lib/application/fonts.dart @@ -0,0 +1,60 @@ +/// The typefaces bundled with the client (spec 8.2: the reader chooses). +/// Pure data, shared by the settings page that offers them and the renderer +/// that applies them. +library; + +/// One bundled family: the name used in pubspec.yaml and stored in settings, +/// the id of its display name in the locale file, and where to look when it +/// has no glyph for a character. +typedef ReaderFont = ({String family, String labelId, List fallback}); + +/// Offered in this order: the two sans faces first, then the two serifs. +/// Adding a font is four files in assets/fonts, one `fonts` entry in +/// pubspec.yaml, one entry here, one label in the locale file, and its licence +/// beside the files. +/// +/// Each face names where to look for a character it has no glyph for. Hebrew +/// comes from a bundled Hebrew face, matched to the shape of the chosen one, +/// so it never depends on what the device happens to have installed. The +/// serifs also cover Greek, Cyrillic and a wide Latin range, so each falls +/// back to the other; the sans faces are narrow and have no bundled sans to +/// turn to, so for those the platform answers, and it answers a sans with a +/// sans. Scripts nothing here covers, such as Arabic and the CJK languages, +/// always come from the platform. +const readerFonts = [ + ( + family: 'Atkinson Hyperlegible', + labelId: 'settings-font-atkinson', + fallback: [hebrewSans], + ), + ( + family: 'Luciole', + labelId: 'settings-font-luciole', + fallback: [hebrewSans], + ), + ( + family: 'Literata', + labelId: 'settings-font-literata', + fallback: ['Charis SIL', hebrewSerif], + ), + ( + family: 'Charis SIL', + labelId: 'settings-font-charis', + fallback: ['Literata', hebrewSerif], + ), +]; + +/// Bundled to be fallen back on, never offered: the reader picks a face for +/// the Latin text they are reading, and Hebrew follows its shape. +const hebrewSans = 'Noto Sans Hebrew'; +const hebrewSerif = 'Noto Serif Hebrew'; + +const defaultFontFamily = 'Atkinson Hyperlegible'; + +/// Where to look after [family], or nothing when it is not one of ours. +List fallbackFor(String family) { + for (final font in readerFonts) { + if (font.family == family) return font.fallback; + } + return const []; +} diff --git a/app/lib/application/pages/settings_page.dart b/app/lib/application/pages/settings_page.dart index 2d4d7ac..663ad71 100644 --- a/app/lib/application/pages/settings_page.dart +++ b/app/lib/application/pages/settings_page.dart @@ -1,17 +1,11 @@ import '../../l10n/strings.dart'; import '../../mews/document.dart'; +import '../fonts.dart'; import '../controllers/reader_settings_controller.dart' show ReaderSettingsController; import 'mews_urls.dart'; import 'page_blocks.dart'; -/// The bundled typefaces the reader can pick. Adding a font is one TTF in -/// assets/fonts, one entry in pubspec.yaml, one entry here and one label in -/// the locale file. -const _fonts = <({String family, String labelId})>[ - (family: 'Atkinson Hyperlegible', labelId: 'settings-font-atkinson'), -]; - /// Message ids for the values the controller accepts. Kept literal so every /// id in the locale file can be found by searching for it. const _themeLabels = { @@ -43,7 +37,7 @@ Future buildSettingsPage( ...chrome(title: title, current: url, strings: strings), HeadingBlock(2, [MewsText(strings.t('settings-font-heading'))]), choiceRow([ - for (final font in _fonts) + for (final font in readerFonts) ( href: MewsUrls.action('set-font', {'value': font.family}), label: strings.t(font.labelId), diff --git a/app/lib/presentation/app.dart b/app/lib/presentation/app.dart index 584ce00..1b1313c 100644 --- a/app/lib/presentation/app.dart +++ b/app/lib/presentation/app.dart @@ -9,6 +9,7 @@ import '../application/controllers/bookmarks_controller.dart'; import '../application/controllers/browser_controller.dart'; import '../application/controllers/reader_settings_controller.dart'; import '../application/controllers/subscriptions_controller.dart'; +import '../application/fonts.dart'; import '../application/internal_actions.dart'; import '../application/internal_pages.dart'; import '../database/database.dart'; @@ -171,6 +172,7 @@ class _ThemedApp extends StatelessWidget { // The chrome reads in the typeface the reader chose for pages, so the // address bar and the page below it are not two different faces. fontFamily: fontFamily, + fontFamilyFallback: fallbackFor(fontFamily), scaffoldBackgroundColor: palette.background, // Selection highlights and the caret follow the palette too. textSelectionTheme: TextSelectionThemeData( diff --git a/app/lib/presentation/reader/mews_page_view.dart b/app/lib/presentation/reader/mews_page_view.dart index d50830f..f902da5 100644 --- a/app/lib/presentation/reader/mews_page_view.dart +++ b/app/lib/presentation/reader/mews_page_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import '../../l10n/strings.dart'; +import '../../application/fonts.dart'; import '../../mews/document.dart'; import 'palettes.dart'; @@ -117,6 +118,7 @@ class _MewsPageViewState extends State { style: TextStyle( color: palette.foreground.withValues(alpha: 0.7), fontFamily: widget.fontFamily, + fontFamilyFallback: fallbackFor(widget.fontFamily), fontSize: base * 0.85, ), ), @@ -129,6 +131,7 @@ class _MewsPageViewState extends State { final foreground = TextStyle( color: palette.foreground, fontFamily: widget.fontFamily, + fontFamilyFallback: fallbackFor(widget.fontFamily), fontSize: base, height: 1.5, ); diff --git a/app/locales/en-US/main.ftl b/app/locales/en-US/main.ftl index a3cae99..a4ff88f 100644 --- a/app/locales/en-US/main.ftl +++ b/app/locales/en-US/main.ftl @@ -120,6 +120,9 @@ subscriptions-tagged = Showing sites tagged { $tag } settings-page-title = Settings settings-font-heading = Typeface settings-font-atkinson = Atkinson Hyperlegible +settings-font-luciole = Luciole +settings-font-literata = Literata +settings-font-charis = Charis SIL settings-size-heading = Text size settings-size-decrease = Smaller text settings-size-increase = Larger text diff --git a/app/pubspec.yaml b/app/pubspec.yaml index ef9aefa..68cd453 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -46,3 +46,43 @@ flutter: - asset: assets/fonts/AtkinsonHyperlegible-BoldItalic.ttf weight: 700 style: italic + - family: Luciole + fonts: + - asset: assets/fonts/Luciole-Regular.ttf + - asset: assets/fonts/Luciole-Italic.ttf + style: italic + - asset: assets/fonts/Luciole-Bold.ttf + weight: 700 + - asset: assets/fonts/Luciole-BoldItalic.ttf + weight: 700 + style: italic + - family: Literata + fonts: + - asset: assets/fonts/Literata-Regular.ttf + - asset: assets/fonts/Literata-Italic.ttf + style: italic + - asset: assets/fonts/Literata-Bold.ttf + weight: 700 + - asset: assets/fonts/Literata-BoldItalic.ttf + weight: 700 + style: italic + - family: Charis SIL + fonts: + - asset: assets/fonts/Charis-Regular.ttf + - asset: assets/fonts/Charis-Italic.ttf + style: italic + - asset: assets/fonts/Charis-Bold.ttf + weight: 700 + - asset: assets/fonts/Charis-BoldItalic.ttf + weight: 700 + style: italic + - family: Noto Sans Hebrew + fonts: + - asset: assets/fonts/NotoSansHebrew-Regular.ttf + - asset: assets/fonts/NotoSansHebrew-Bold.ttf + weight: 700 + - family: Noto Serif Hebrew + fonts: + - asset: assets/fonts/NotoSerifHebrew-Regular.ttf + - asset: assets/fonts/NotoSerifHebrew-Bold.ttf + weight: 700 diff --git a/app/test/fonts_test.dart b/app/test/fonts_test.dart new file mode 100644 index 0000000..927698b --- /dev/null +++ b/app/test/fonts_test.dart @@ -0,0 +1,98 @@ +import 'dart:io'; + +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:mews/application/controllers/reader_settings_controller.dart'; +import 'package:mews/application/fonts.dart'; +import 'package:mews/database/database.dart'; + +/// The families pubspec.yaml declares, with the asset paths of each. +Map> _declaredFonts() { + final lines = File('pubspec.yaml').readAsLinesSync(); + final declared = >{}; + String? family; + for (final line in lines) { + final name = RegExp(r'^ - family: (.+)$').firstMatch(line); + if (name != null) { + family = name.group(1)!.trim(); + declared[family] = []; + continue; + } + final asset = RegExp(r'^ - asset: (.+)$').firstMatch(line); + if (asset != null && family != null) { + declared[family]!.add(asset.group(1)!.trim()); + } + } + return declared; +} + +void main() { + test('every offered typeface is bundled under the name it is stored by', () { + final declared = _declaredFonts(); + for (final font in readerFonts) { + // A family named here but not in pubspec renders in the platform's font + // instead, silently: the reader would pick it and see no change. + expect(declared, contains(font.family), + reason: '${font.family} is offered but not declared in pubspec.yaml'); + expect(declared[font.family], hasLength(4), + reason: '${font.family} needs regular, italic, bold and bold italic'); + for (final asset in declared[font.family]!) { + expect(File(asset).existsSync(), isTrue, reason: '$asset is missing'); + } + } + }); + + test('a fallback only names a typeface that is bundled', () { + final declared = _declaredFonts(); + for (final font in readerFonts) { + for (final family in font.fallback) { + // A fallback naming a family the build does not carry is silently + // skipped, so the glyph would come from the platform after all. + expect(declared, contains(family), + reason: '${font.family} falls back to an absent $family'); + expect(family, isNot(font.family), reason: 'falls back to itself'); + for (final asset in declared[family]!) { + expect(File(asset).existsSync(), isTrue, reason: '$asset is missing'); + } + } + } + expect(readerFonts.map((f) => f.family), contains(defaultFontFamily)); + expect(fallbackFor('Comic Sans MS'), isEmpty); + }); + + test('every typeface can reach Hebrew without the platform', () { + // Hebrew ships from the start, so it must never depend on what the device + // happens to have installed. + for (final font in readerFonts) { + expect(font.fallback, anyElement(anyOf(hebrewSans, hebrewSerif)), + reason: '${font.family} has no Hebrew to fall back on'); + } + }); + + test('every typeface has a label of its own', () { + final ftl = File('locales/en-US/main.ftl').readAsStringSync(); + final ids = {}; + for (final font in readerFonts) { + expect(ftl, contains('${font.labelId} ='), + reason: '${font.family} has no label'); + expect(ids.add(font.labelId), isTrue, reason: 'duplicate label id'); + } + }); + + test('a typeface this build does not ship is refused', () async { + final db = MewsDatabase.test(NativeDatabase.memory()); + addTearDown(db.close); + final settings = ReaderSettingsController(db); + + expect(await settings.setFont('Literata'), isTrue); + expect(settings.font.value, 'Literata'); + + // Comic Sans is not bundled; accepting it would render in the platform + // font while the settings page claimed otherwise. + expect(await settings.setFont('Comic Sans MS'), isFalse); + expect(settings.font.value, 'Literata'); + expect(await settings.setFont(''), isFalse); + expect(settings.font.value, 'Literata'); + }); +}