From f066f2c1d0b70d150271b582281d67185302db70 Mon Sep 17 00:00:00 2001 From: iGor milhit Date: Tue, 12 Nov 2024 14:41:32 +0100 Subject: [PATCH] =?UTF-8?q?support:=20mets=20=C3=A0=20jour=20pour=20la=20s?= =?UTF-8?q?ession=20d'automne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mentionne la licence dans le `README.md`. Fixes #1. - Remplace le fichier d'introduction par la notion de document, puiqu'il n'y a plus, pour l'instant, plusieurs sessions. - Fragmente la source en sous-fichiers pour améliorer la gestion des références (à évaluer). - Mets à jour les fichiers du template (`v0.2.0`). - Documente comment installer les fichiers du template. - Mets à jour le fichier du style de citation et des références, désormais au format CSL JSON. - Affiche toutes les références dans la bibliographie. - Crée un Makefile pour faciliter les conversions et les commandes de suivi des modification (avec watchexec). - Reformule et ajoute des liens. Co-Authored-by: iGor milhit --- .gitignore | 5 +- 0-frontmatter.md | 17 + 1-objectifs.md | 9 + 1.1-licence.md | 4 + 100-references.md | 32 + 2-definitions.md | 110 + 3-syntaxe.md | 26 + 4-editeurs.md | 87 + 5-advanced.md | 90 + 6-bibliographie.md | 1 + Makefile | 40 + README.md | 77 +- intro/intro-déroulé.md => déroulé.md | 37 +- heg-iso-690.csl | 692 + intro/intro.md | 371 - intro/references.bib | 370 - paged.polyfill.js | 33251 +++++++++++++++++++++ references.json | 15 + reload-in-place.js | 117 + static/references.bib | 370 - style.css | 57 +- template.html | 10 +- 22 files changed, 34622 insertions(+), 1166 deletions(-) create mode 100644 0-frontmatter.md create mode 100644 1-objectifs.md create mode 100644 1.1-licence.md create mode 100644 100-references.md create mode 100644 2-definitions.md create mode 100644 3-syntaxe.md create mode 100644 4-editeurs.md create mode 100644 5-advanced.md create mode 100644 6-bibliographie.md create mode 100644 Makefile rename intro/intro-déroulé.md => déroulé.md (80%) create mode 100644 heg-iso-690.csl delete mode 100644 intro/intro.md delete mode 100644 intro/references.bib create mode 100644 paged.polyfill.js create mode 100644 references.json create mode 100644 reload-in-place.js delete mode 100644 static/references.bib diff --git a/.gitignore b/.gitignore index f1ed8e3..15b3e4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -intro.html -intro.pdf +document.html +document.pdf +*.zip diff --git a/0-frontmatter.md b/0-frontmatter.md new file mode 100644 index 0000000..cf0cdec --- /dev/null +++ b/0-frontmatter.md @@ -0,0 +1,17 @@ +--- +title: Introduction à la prise de note avec markdown +author: +- Igor Milhit^[Uni CMU] +date: Printemps 2024 +creation_date: 2023-04-19T11:35:14+02:00 +id: 20230419113601 +tags: [note, markdown] +logo: +- static/logo-md.svg +lang: fr +bibliography: references.json +citation-style: heg-iso-690.csl +nocite: '@*' +link-citations: true +toc-title: Table des matières +--- diff --git a/1-objectifs.md b/1-objectifs.md new file mode 100644 index 0000000..6fd3799 --- /dev/null +++ b/1-objectifs.md @@ -0,0 +1,9 @@ +## Objectifs + +- Avantages et inconvénients. +- Aperçu de la syntaxe de base. +- Les fonctionnalités minimales d’un éditeur. +- Bref aperçu des possibilités avancées : + - Ensemble de notes. + - Conversion dans d’autres formats. + - Insertion de références bibliographiques. diff --git a/1.1-licence.md b/1.1-licence.md new file mode 100644 index 0000000..173a91c --- /dev/null +++ b/1.1-licence.md @@ -0,0 +1,4 @@ +:::{#licence} +![Logo de la licence CC BY-SA 4.0][1] \ +[CC BY-SA 4.0][2] --- [Sources][3] +::: diff --git a/100-references.md b/100-references.md new file mode 100644 index 0000000..d930a46 --- /dev/null +++ b/100-references.md @@ -0,0 +1,32 @@ + + +[1]: ./static/by-sa.svg +[2]: https://creativecommons.org/licenses/by-sa/4.0/deed.fr "Texte de la licence en français" +[3]: https://git.milhit.ch/igor/rdv-info-markdown +[4]: https://www.arthurperret.fr/cours/format-texte.html +[5]: https://hedgedoc.org/ +[6]: https://hackmd.io/ +[7]: https://fr.wikipedia.org/wiki/Git "Article Wikipedia en français" +[8]: #dedies +[9]: #generalistes +[10]: ./static/ghostwriter.png +[11]: ./static/zettlr.png +[12]: ./static/hackmd.png +[13]: ./static/vscode.png +[14]: ./static/lien-entre-fichiers.png +[15]: ./static/recherche-vscode.png +[16]: https://pandoc.org/MANUAL.html#citations +[17]: https://zotero.org +[18]: https://joplinapp.org/ +[19]: https://www.notion.so/ +[20]: https://obsidian.md/ + +[^0]: Cette section repose beaucoup sur la page [Format texte][4] d'Arthur + Perret [@perretFormatTexte2022]. +[^1]: Markdown n'est pas le seul langage de balisage léger existant, bien que + ce soit le plus répandu. D'autres sont encore plus robustes ou puissants, + comme *RestructuredText* ou *AsciiDoc*. +[^2]: Il semble possible de l'installer sur MacOS, mais n'est pas recommandé. +[^3]: Les sources et la méthode de conversion est disponible sur + . +[^4]: Au moins si on compare la syntaxe Markdown au HTML ou au XML. diff --git a/2-definitions.md b/2-definitions.md new file mode 100644 index 0000000..a09747d --- /dev/null +++ b/2-definitions.md @@ -0,0 +1,110 @@ +## Définition, avantages et inconvénients[^0] {#first-section} + +:::{.exercise} +Le plus important est de trouver des méthodes et des outils adaptés à *ses +besoins*. Ci-dessous sont listés quelques arguments en faveur de l'usage de +fichiers au format texte et de la syntaxe markdown, mais aucune méthode ou +outil n'est *la* solution miracle pertinente dans *tous* les contextes. +::: + +### Définition + +L'information numérique est utile si elle est *structurée*. C'est ce qui +permet, par exemple, que la table des matières d'un document numérique soit +constituée de liens vers les sections. Cette structure peut s'obtenir par +l'usage des styles et des niveaux de titre dans un traitement de texte +(Microsoft Word, Libreoffice Writer, etc.), ou grâce à un langage de balisage +comme HTML ou XML. Markdown est une autre méthode, très efficace.[^1] + +Markdown est *un langage de balisage léger*. Il s'agit d'une syntaxe qui +permet, *uniquement avec des éléments textuels*, d'indiquer la structure +(sections, titres) et la fonction d'éléments textuels (emphase, liste, liens). +Cette structure et ces fonctions sont lisibles, compréhensibles par des êtres +humains[^4] ou par des programmes informatiques. Aussi, les documents rédigés à +l'aide de markdown peuvent soit être lus tels quels, soit être traités par un +programme pour afficher un « rendu » sous forme de page web, de document PDF ou +DOCX. + +La syntaxe markdown est relativement simple à apprendre et mémoriser. Aussi, la +rédaction au format markdown repose sur très peu de fonctionnalités de +l'éditeur de texte. Le bloc note suffit, même si un peu plus de confort est +souhaitable. + +Cette syntaxe est utilisée par de nombreux logiciels ou services en lignes pour +la rédaction. C'est le cas des notes dans [Zotero][17], de solution de prise de +note comme [Joplin][18], [Notion][19] ou [Obsidian][20], ou encore d'éditeurs +en ligne adaptés au travail collaboratif ([HedgeDoc][5], [HackMD][6], etc.). + +### Avantages + +Les fichiers utilisés ne sont composés que de caractères textuels. Aussi, ils +sont : + +- Simples et légers : + - Ne dépendent pas d'un logiciel spécifique, ni d'un système d'exploitation. + - Pourront être ouverts et édités dans n'importe quel contexte, et + probablement dans un avenir éloigné (pérennité). + - Rapides à ouvrir. Avec n'importe quel éditeur de texte (bloc note Windows, + Notepad++, TextEdit, Gedit, VIM, EMACS, etc.) + - Faciles à sauvegarder, copier, synchroniser, partager. + +Toujours grâce à leur nature textuelle, il est facile d'avoir des outils pour +rechercher (et remplacer) rapidement du texte au sein non seulement du fichier +ouvert, mais d'un ensemble de fichier dans un répertoire. \ +Il est même possible d'écrire des programmes pour modifier ou utiliser +automatiquement ces fichiers. Et il en existe justement beaucoup ! + +Grâce à ces propriétés, il est possible : + +- De prendre des notes au clavier rapidement, de manière structurée, et avec + une recherche rapide. +- De rédiger des documents simples (une note) ou complexes (une thèse). +- De regrouper un ensemble de notes dans un « bloc note » et de naviguer dans + cet ensemble, de rechercher de l'information . +- De manière similaire, de regrouper un ensemble de fichiers pour constituer un + document complexe, comme un livre ou un article scientifique. +- Enfin de convertir à partir des mêmes fichiers le document dans plusieurs + formats pour une diffusion sur le web, la lecture dans un PDF ou un ePub + (compatible avec une liseuse électronique). + +Enfin, les fichiers textuels sont particulièrement adaptés au suivi de +l'historique des modification (*versioning*), par exemple avec le logiciel +[Git][7]. + +### Inconvénients + +Pour un usage simple, l'apprentissage n'est pas très difficile. La syntaxe de +base s'acquiert rapidement. Pour parvenir à rédiger des documents relativement +complexes (notes, citations et bibliographies, etc.), cet apprentissage est un +peu plus long. Afin de maîtriser la conversion dans différents formats, tout en +personnalisant la mise en forme des documents obtenus, il devient vite +nécessaire d'apprendre à utiliser d'autres outils, à modifier des modèles dans +les formats désirés (HTML, DOCX, peut-être LaTeX, etc.). + +Certains éditeurs intègrent la fonction de conversion, mais elle repose sur +l'installation de logiciels annexes, principalement `pandoc`, mais aussi une +distribution LaTeX pour obtenir une conversion en PDF. Ces outils ne sont pas +si difficiles à installer. Surtout, ils sont libres, gratuits et disponibles +pour Windows, Mac OS ou Linux. + +Pour utiliser `pandoc`, qui est l'outil de conversion le plus complet, il est +nécessaire de le faire « en ligne de commande » plutôt que dans une interface +graphique, avec la souris. C'est un élément en plus à apprendre, mais qui ouvre +beaucoup de possibilités. + +Le travail collaboratif à plusieurs est toujours possible, mais selon les cas +avec une certaine friction. La situation la plus simple est d'utiliser un +éditeur en ligne qui offre des fonctionnalités d'édition à plusieurs. Souvent +cela est au prix de ne pouvoir disposer de toute la puissance de `pandoc`, du +moins directement. + +Toujours dans le cas où toutes les personnes qui collaborent sont capables de +travailler avec les mêmes outils, elles peuvent toujours travailler ensemble en +se mettant d'accord sur un « workflow », par exemple grâce à `git` mentionné +plus haut. Mais c'est un outil de plus à apprendre. Et de nouvelles +possibilités à découvrir. + +Enfin, si les personnes avec qui on collabore ne veulent ou ne peuvent pas +utiliser les mêmes méthodes, alors il faudra mettre en place un processus +d'export et d'import un peu complexe, mais en partie automatisable +[@perretMarkdownTraitementTexte2022]. diff --git a/3-syntaxe.md b/3-syntaxe.md new file mode 100644 index 0000000..c7cfff8 --- /dev/null +++ b/3-syntaxe.md @@ -0,0 +1,26 @@ +## La syntaxe markdown + +La syntaxe markdown est très bien documentée sur le web : + +- La documentation « officielle » est disponible sur le site personnel d'un des + concepteurs de la syntaxe : + [@gruberMarkdown2004]. +- Le site *flus* propose Une documentation en français et très claire : + [@fressinaudGuideMarkdown2022]. +- Il y a plusieurs variantes (ou extensions) de la syntaxe et un site essaie de + proposer un standard. Il met à disposition un tutoriel interactif : + [@commonmarkCommonMark]. +- Arthur Perret a traduit en français ce même tutoriel, disponible sur sa page + expliquant ce qu'est markdown : + [@perretMarkdown2022]. + +Pour les tableaux, il est fortement conseillé : + +- D'utiliser un générateur, par exemple + . +- Ou de disposer dans son éditeur d'un générateur de tableaux. + +Le plus souvent les fichiers contenant du markdown ont pour extension `.md`, +mais c'est une convention. Rien n'interdit d'utiliser d'autres extensions comme +`.mkdn` ou `.markdown`, pour autant que l'éditeur que vous utilisez les +reconnaissent comme du markdown. diff --git a/4-editeurs.md b/4-editeurs.md new file mode 100644 index 0000000..d66d99f --- /dev/null +++ b/4-editeurs.md @@ -0,0 +1,87 @@ +## Éditeurs {.newpage} + +Il existe beaucoup d'éditeurs pour rédiger en markdown. On peut les regrouper +en deux grandes catégories : + +1. Les éditeurs [dédiés][8]. +1. Les éditeurs [généralistes][9]. + +Dans les deux cas, des fonctionnalités de base qui sont presque +incontournables : + +- Une prévisualisation, le plus souvent sous la forme d'un *rendu* HTML. Cette + fonctionnalité a l'avantage de permettre de se faire une idée du résultat + sans devoir générer le fichier final. +- La possibilité de naviguer dans une arborescence de fichiers, en général dans + une barre sur le côté de l'interface. +- Une autocomplétion, pour faciliter l'entrée de syntaxe avec des `[]` ou des + `()`. + +D'autres fonctionnalités ne sont pas indispensables, mais deviennent rapidement +utiles : + +- La correction orthographique pour les langues dans lesquelles vous rédigez. +- Des raccourcis pour la syntaxe (taper `img` et obtenir la syntaxe pour + l'insertion d'une image). +- Un formatage automatique des tableaux. +- La possibilité de suivre les liens internes, au sein du même fichier ou entre + les fichiers d'un même répertoire. + +### Éditeurs dédiés {#dedies} + +Ce sont des logiciels conçu spécifiquement pour éditer des fichiers avec la +syntaxe markdown. Ils peuvent être sous la forme d'un logiciel à installer sur +votre appareil (ordinateur, tablette, téléphone) ou d'un service web. + +**Typora** est éditeur qui met l'accent sur son interface épurée. Il est disponible +pour tous les systèmes d'exploitation, payant. + +![Interface de GhostWriter][10] + +**GhostWriter** est libre et gratuit, pour Windows et Linux +principalement.[^2] + +**Abricotine** est libre et gratuit, pour toutes les plateformes. + + +**iA Writer** est payant, pour MacOS et Windows, mais est très apprécié pour la +rédaction. Il permet de lier des notes entre elles et offre ainsi une +navigation dans un ensemble de fichiers. + +**Obsidian** est un logiciel propriétaire, pour toutes les plateformes. Il ne +se limite pas à l'édition de fichiers markdown, mais permet de constituer une +base de connaissance basée sur des notes, et cela de manière très complète. + + +![Interface de Zettlr][11] + +**Zettlr** est libre et disponible pour tous les systèmes d'exploitation. Il +est destiné à un usage académique, que ce soit pour construire une base de +connaissance à partir de notes ou pour rédiger un travail de mémoire ou un +article scientifique. Il intègre Zotero pour la gestion des références. + +En ligne, on peut mentionner à nouveau le service [HackMD][2] ou le logiciel +[HedgeDoc][1] qui permet non seulement d'éditer des fichiers markdown avec une +prévisualisation, mais également de le faire à plusieurs, de disposer d'un +suivi des modifications et de commenter. + +![Interface de l'éditeur HackMD][12] + +### Éditeurs généralistes {#generalistes} + +*Généralistes* car ces éditeurs ne sont pas limités à l'édition de fichier +markdown. Le plus souvent, ce sont des *éditeurs de texte* utilisés +par des développeurs pour écrire du code informatique. Le plus souvent, ils +disposent d'un écosystème d'extensions afin de s'adapter à des besoins +spécifiques. De ce fait, ils sont très puissants. + +Ainsi, il est possible d'adapter à l'édition du markdown les éditeurs de texte +suivant, notamment : + +- Notepad++, libre et gratuit, pour Windows. +- Visual Studio Code, gratuit, pour Windows, MacOS, Linux. Un des logiciels les + plus utilisés actuellement. +- VIM ou neovim, libre et gratuit, pour tous les systèmes d'exploitation. +- EMACS, libre et gratuit. + +![Interface de Visual Studio Code][13] diff --git a/5-advanced.md b/5-advanced.md new file mode 100644 index 0000000..2bc2352 --- /dev/null +++ b/5-advanced.md @@ -0,0 +1,90 @@ +## Aperçu des possibilités avancées + +### Ensemble de notes + +Afin d'organiser ses notes, il est possible de réunir un ensemble de fichiers +dans le même répertoires (y compris avec des sous répertoires) et de faire des +liens entre les fichiers (et même au sein d'un seul fichier). + +Certains éditeurs, ou certaines extensions d'éditeurs, proposent des facilités +pour générer ces liens, au prix d'une syntaxe *ad hoc* (« wiki links »). Mais +il est parfaitement possible de le faire avec la syntaxe « normale » du lien. + +![Lien depuis le fichier `README.md` vers le fichier `points-a-ameliorer.md`][14] + +La figure 5 montre sur la gauche une arborescence de fichiers, avec +des sous-répertoires, et dans le fichier affiché (`README.md`), un lien vers le +fichier `points-a-ameliorer.md`. Dans cet éditeur, en plaçant le curseur sur le +lien et en tapant la touche « entrée », on ouvre le fichier cible. + +La figure 6 montre la recherche du terme `rdv-info` lancée sur la même +arborescence de fichiers que la figure 5, au moyen du logiciel Visual Studio +Code. Il y a 13 occurrences du terme dans 7 fichiers. Ce type de recherche est +facilité par le fait que ce sont des fichiers au format texte et se retrouve +dans la plupart des éditeurs généralistes de qualité. + +![Recherche dans Visual Studio Code][15] + +### Export vers d'autres formats + +La conversion d'un document markdown en un autre format, plus simple à partager +devient assez vite utile. Pour les conversions, `pandoc` est l'outil le plus +souvent utilisé. Il est soit utilisé par votre éditeur pour faire les exports +(par exemple avec Zettlr), soit par vous même en ligne de commande. C'est un +logiciel libre et gratuit, développé très activement. + +Il est capable de lire et d'écrire dans un grand nombre de formats différents, +parfois à l'aide de logiciels spécifiques. Par exemple, pour produire un +fichier PDF, il peut utiliser plusieurs méthodes, dont LaTeX, ce qui suppose +d'en avoir une version installée. + +La commande pour une conversion simple est de la forme suivante : + +```bash +pandoc --output fichier.docx fichier.md +``` + +Le format obtenu correspond à celui qui est indiqué en extension du nom de +fichier de sortie `--output`, ici du DOCX. Mais il est possible de +l'expliciter : + +```bash +pandoc --to docx --output fichier.docx fichier.md +``` + +Le support de cours de cette introduction à markdown a été rédigée en markdown +et est exportée en PDF grâce à `pandoc` et à *Paged.js*.[^3] + +### Citations et Bibliographie + +Pour insérer des citations et générer une bibliographie, il est nécessaire +d'avoir au moins une collection de références bibliographiques, par exemple +sous la forme d'un fichier `.bib`. Pour obtenir ce fichier, il est possible de +passer par Zotero et d'exporter une collection dans le format BibTex (ou Better +BibTex avec l'extension Zotero du même nom.) + +Dans ce fichier (et dans Zotero), on trouve pour chaque référence une « clé de +citation » qui permet de l'identifier de manière unique. C'est cette clé que +l'on va insérer dans notre fichier markdown pour indiquer une citation. Elle +est insérée avec la syntaxe suivante : `[@clé-de-citation]`. + +On peut encore ajouter à la fin de notre fichier un titre de deuxième niveau +avec le texte bibliographie, pour que `pandoc` puisse y insérer la +bibliographie. + +Pour être en mesure de traiter ces informations, il faut utiliser l'option de +`--citeproc` intégrée dans `pandoc` : + +```bash +pandoc --citeproc \ + --to=pdf --pdf-engine=xelatex \ + --bibliography=references.bib \ + --output fichier.pdf fichier.md +``` + +Dans l'exemple ci-dessus, nous voyons l'option `--citeproc` et l'option +`--bibliography=` qui indique où se trouve le fichier des références +bibliographiques. Par défaut le style est Chicago, de type auteur-date. + +Pour aller plus loin, le mieux est de consulter la [documentation +officielle][16] de `pandoc.` diff --git a/6-bibliographie.md b/6-bibliographie.md new file mode 100644 index 0000000..8aeaf36 --- /dev/null +++ b/6-bibliographie.md @@ -0,0 +1 @@ +## Bibliographie {#bibliography .newpage} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f03c4e5 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +input_files := \ +0-frontmatter.md \ +1.1-licence.md \ +2-definitions.md \ +3-syntaxe.md \ +4-editeurs.md \ +5-advanced.md \ +6-bibliographie.md \ +100-references.md \ + +pandoc := \ + pandoc -s --citeproc \ + --css=style.css --template=template.html \ + --toc --toc-depth=2 \ + +pandoc_html := \ + $(pandoc) \ + --to=html --output=document.html \ + $(input_files) + +pandoc_pdf := \ + $(pandoc) \ + -V noscript=true \ + --embed-resource --to=pdf \ + --pdf-engine=pagedjs-cli \ + --output=document.pdf \ + $(input_files) + +html: $(input_files) + $(pandoc_html) + +watch_html: $(input_files) + watchexec -r -w . -i document.html \ + " $(pandoc_html)" + +pdf: $(input_files) + $(pandoc_pdf) + +serve: + watchexec -r -w . "python -m http.server" diff --git a/README.md b/README.md index a6ccf79..ef6b959 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ --- title: Rendez-vous de l'info scientifique sur markdown -date: 2023-04-19T10:13:18+02:00 +date: 2023-04-19T10:13:18+0200 id: 20230419101340 tags: [rdv-info, documentation, markdown] --- @@ -11,26 +11,47 @@ Ce répertoire contient le matériel utilisé pour le [rendez-vous de l'info scientifique sur Markdown][2] proposés par la bibliothèque de l'Université de Genève. -Les sources du support de cours sont dans le fichier [`intro/intro.html`][4]. +Les sources du support de cours sont dans le fichier [`document.html`][4]. La dernière version du support de cours au format PDF se trouve dans la dernière [publication de version][3]. +## Licence et réutilisation + +La [licence][6] du projet est la licence *Creative Common by, share alike, +4.0 International*. Elle permet la diffusion et la modification du projet pour +autant que la responsabilité soit mentionnée et que le partage se fait avec la +même licence. + ## Pré-requis - `pandoc`. - `pagedjs-cli` (`npm install -g puppeteer pagedjs pagedjs-cli`). +- Le modèle `pagedjs` pour la BUNIGE. + +Les fichiers de ce modèle peuvent être téléchargés depuis la dernière +publication de version du projet : +. L'archive +ZIP `vX.X.X.zip` contient tout ce qui est nécessaire. Plus précisément, ce sont +les fichiers suivants : + +- `./template.html`, le modèle HTML utilisé par `pandoc` ou `pagedjs-cli`. +- `./style.css` avec les règles de style pour la mise en forme. +- `./static/bibliotheque-logo.svg` et `./static/by-sa.svg`, deux images + utilisées sur la page d'accueil. +- `./paged.polyfill.js` pour le support des fonctionnalité `pagedjs`. +- `./interface.css` pour afficher le ficher HTML dans un navigateur avec + l'interface web de `pagedjs`. +- `./reload-in-place.js`, qui n'est pas strictement nécessaire, mais replace le + fichier HTML là où on le consultait. ## Génération du PDF -Le support de cours est rédigé au format markdown, en utilisant des -fonctionnalités propres au `pandoc-markdown`. La génération du PDF s'appuie sur -`pagedjs`, un modèle HTML (`template.html`) et des règles de styles -(`styles.css`). Le fichier des règles de style est commenté, ce qui devrait -permettre de comprendre son fonctionnement. - -Lancer les commandes depuis le répertoire contenant le fichier source au format -Markdown, à savoir `./intro`. +Le support de cours (`./document.md`) est rédigé au format markdown, en +utilisant des fonctionnalités propres à la version [Pandoc de markdown][5]. La +génération du PDF s'appuie sur `pagedjs`, un modèle HTML (`template.html`) et +des règles de styles (`styles.css`). Le fichier des règles de style est +commenté, ce qui devrait permettre de comprendre son fonctionnement. Pour générer le fichier HTML à servir par un serveur web pour le visualiser dans le navigateur, il faut utiliser `pandoc` avec les paramètres suivants (à @@ -38,19 +59,19 @@ adapter au fichier à générer) : ```bash pandoc --to=html \ - --template=../template.html --css=../style.css \ + --template=template.html --css=style.css \ --toc --toc-depth=2 --citeproc \ - --output=intro.html intro.md + --output=document.html document.md ``` - `--to=html` pour convertir vers du HTML. -- `--template=` et `--css=` indiquent quels modèle et feuille de style +- `--template=` et `--css=` indiquent quel modèle et feuille de style utiliser. - `--toc` et `--toc-depth=2` génèrent la table des matière en n'allant pas plus loin que les titres de niveau 2. - `--citeproc` indique qu'il faut tenir compte des citations et générer la bibliographie. -- `--output=` précise où et à quel nom générer le fichier de sortie. +- `--output=` précise l'endroit et le nom du fichier de sortie. Une fois le fichier HTML généré, il faut lancer un serveur web depuis la racine du projet, par exemple avec `python` : @@ -59,10 +80,11 @@ du projet, par exemple avec `python` : python -m http.server ``` -Puis, ouvrir le navigateur Chromium (ou Chrome) à l'URL -. En effet, Firefox supporte un peu moins bien -`pagedjs`, notamment pour l'impression en PDF. Pour éviter la coloration des -liens déjà visités, la fonction « navigation privée » est bien utile. +Puis, ouvrir un navigateur web Chromium (ou Chrome) à l'URL +. Pour l'impression, il est conseillé d'utiliser +Chromium (ou Chrome). En effet, Firefox supporte un peu moins bien `pagedjs`, +notamment pour l'impression en PDF. Pour éviter la coloration des liens déjà +visités, la fonction « navigation privée » est bien utile. Cet affichage permet d'inspecter les règles CSS et de les modifier à la volée pour comprendre comment améliorer le rendu, et d'imprimer la page web au format @@ -74,8 +96,9 @@ résultats différents. Soit en utilisant `pagedjs` directement avec `pandoc`  ```bash pandoc --citeproc \ --to=pdf --pdf-engine=pagedjs-cli \ - --css=../style.css --toc --toc-depth=2 \ - --output=intro.pdf intro.md + --template=template.html --css=style.css \ + --toc --toc-depth=2 \ + --output=document.pdf document.md ``` Soit en utilisant `pagedjs` pour convertir le fichier HTML obtenu par pandoc @@ -83,17 +106,19 @@ plus haut : ```bash pandoc --to=html \ - --template=../template.html --css=../style.css \ + --template=template.html --css=style.css \ --toc --toc-depth=2 --citeproc \ - --output=intro.html intro.md \ - && pagedjs-cli intro.html \ + --output=document.html document.md \ + && pagedjs-cli document.html \ --css=../style.css \ - -o intro.pdf + -o document.pdf ``` - + [1]: ./ [2]: https://www.unige.ch/biblio/index.php?cID=4127 [3]: https://git.milhit.ch/igor/rdv-info-markdown/releases/latest -[4]: ./intro/intro.md +[4]: ./document.md +[5]: https://pandoc.org/MANUAL.html#pandocs-markdown +[6]: ./LICENSE diff --git a/intro/intro-déroulé.md b/déroulé.md similarity index 80% rename from intro/intro-déroulé.md rename to déroulé.md index 325ae9c..c246b20 100644 --- a/intro/intro-déroulé.md +++ b/déroulé.md @@ -8,18 +8,18 @@ tags: [rdv-info, déroulé, markdown] ## Checklist - [ ] Polycopié généré et prêt au partage et ouvert pour s'y référer. -- [ ] URL du formulaire de feedback prête à être copiée-collée. -- [ ] Page HackMD ouverte avec un exemple de fichier markdown. +- [ ] URL du formulaire de feedback prête à être copiée-collée. \ + +- [ ] Éditeur ouvert avec un exemple de fichier markdown. - [ ] Navigateur de fichier ouvert sur l'ensemble de notes et dans le dossier - `intro`. -- [ ] VScode ouvert avec l'ensemble de notes. -- [ ] Un terminal ouvert dans le dossier `intro` (pour la démo de pandoc). + `~/tmp/intro-md/`. +- [ ] Un terminal ouvert dans le dossier `~/tmp/intro-md/` (pour la démo de + pandoc). ## Présentation (2 min.) -- Présentation. -- Partage du PDF. -- Mention des questions à la fin. +- Présentation personnelle. +- Partage du PDF du support de cours. ## Objectifs (1 min.) @@ -33,7 +33,7 @@ tags: [rdv-info, déroulé, markdown] ## Présentation -- Ouvrir HackMD. +- Ouvrir l'éditeur avec le fichier d'exemple. - Montrer que ce n'est que du texte = format texte. - Montrer le rendu et la structure. - Avantages du format texte : @@ -44,19 +44,19 @@ tags: [rdv-info, déroulé, markdown] - Sauvegarde, synchronisation, partage. - Recherche textuelle facilitée. - Favorise un riche écosystème pour utiliser ou manipuler les fichiers. + - Facilite le suivi de l'historique des versions (`git`). - Inconvénients : - Apprentissage simple au début, peut devenir plus ardu selon les besoins. - - La conversion nécessite l'installation de logiciels supplémentaires. - - L'usage de `pandoc` pour la conversion se fait dans en « ligne de - commande ». - - Le travail collaboratif est possible, mais selon les situations, elle peut - devenir plus complexe. + - La conversion nécessite l'installation de logiciels supplémentaire. + - L'usage de `pandoc` pour la conversion se fait en « ligne de commande ». + - Le travail collaboratif est possible, mais selon les situations, il peut + s'avérer complexe. - Conclusion : - Rapidité et légèreté adaptée à la prise de note. - Notes structurées, recherche textuelle efficace. - Adapté pour documents simples (notes) ET pour documents complexes (thèse). - Constitution d'une base de connaissance. -- Détail de la syntaxe depuis HackMD. +- Détail de la syntaxe depuis l'éditeur. - Documentation en lien dans le support de cours (page 4). - Générateur de tableau en ligne. - Intérêt d'apprendre la syntaxe et de l'utiliser régulièrement. @@ -65,14 +65,15 @@ tags: [rdv-info, déroulé, markdown] - Italiques et gras. - Liens. Sans et avec référence. - Image. -- Fonctionnalités nécessaires : - - Prévisualisation (HackMD). +- Fonctionnalités utiles : + - Prévisualisation. - Autocomplétion de la syntaxe. - - Navigateur de fichiers (VScode). + - Navigateur de fichiers. - Un plus : - Correcteur orthographique. - Formatage automatique des tableaux. - Suivi des liens internes, entre fichiers. + - Raccourcis pour accéder à la syntaxe. - Usages avancés : - Démo avec Visual Code. Ouvrir le dossier `~/bc/`. - Ouvrir README, suivre un lien. diff --git a/heg-iso-690.csl b/heg-iso-690.csl new file mode 100644 index 0000000..12090fa --- /dev/null +++ b/heg-iso-690.csl @@ -0,0 +1,692 @@ + + diff --git a/intro/intro.md b/intro/intro.md deleted file mode 100644 index 4f00ae8..0000000 --- a/intro/intro.md +++ /dev/null @@ -1,371 +0,0 @@ ---- -title: Introduction à la prise de note avec markdown -author: -- Igor Milhit^[Uni CMU] -date: Printemps 2024 -creation_date: 2023-04-19T11:35:14+02:00 -id: 20230419113601 -tags: [note, markdown] -logo: -- ../static/logo-md.svg -lang: fr -bibliography: ../static/references.bib -citation-style: ../static/heg-iso-690.csl -link-citations: true -toc-title: Table des matières ---- - -## Objectifs - -- Avantages et inconvénients. -- Aperçu de la syntaxe de base. -- Les fonctionnalités minimales d’un éditeur. -- Bref aperçu des possibilités avancées : - - Ensemble de notes. - - Conversion dans d’autres formats. - - Insertion de références bibliographiques. - -:::{#licence} -![Logo de la licence CC BY-SA 4.0](../static/by-sa.svg) \ -[CC BY-SA 4.0][cc-by-sa] --- [Sources][sources] -::: - -[cc-by-sa]: https://creativecommons.org/licenses/by-sa/4.0/deed.fr "Texte de la licence en français" -[sources]: https://git.milhit.ch/igor/rdv-info-markdown - -## Définition, avantages et inconvénients[^0] {#first-section} - -Le plus important est de trouver des méthodes et des outils adaptés à *ses -besoins*. Ci-dessous sont listés quelques arguments en faveur de l'usage de -fichiers au format texte et de la syntaxe markdown, mais aucune méthode ou -outil n'est *la* solution miracle pertinente dans *tous* les contextes. - -### Définition - -L'information numérique est utile si elle est **structurée**. La table des -matières d'un document numérique devrait être constituée de liens vers les -sections. Cette structure peut s'obtenir par l'usage des styles et des niveaux -de titre dans un traitement de texte (Microsoft Word, Libreoffice Writer, -etc.). La syntaxe markdown est une autre méthode, très efficace.[^1] - -Markdown est *un langage de balisage léger*. Concrètement, il s'agit d'une -syntaxe qui permet, **uniquement avec des éléments textuels**, de structurer -(niveau de titres) et de mettre en forme du texte. Cette structure et cette -mise en forme sont compréhensibles par des êtres humains ou par des programmes -informatiques. Aussi, les documents rédigés à l'aide de markdown peuvent soit -être lus tels quels, soit être traités par un programme pour afficher un -« rendu » sous forme de page web, de document PDF ou DOCX. - -La syntaxe markdown est relativement simple à apprendre et mémoriser. Aussi, la -rédaction au format markdown repose sur très peu de fonctionnalités du -logiciel. Le bloc note suffit, même si un peu plus de confort est souhaitable. - -Cette syntaxe est utilisée par de nombreux logiciels ou services en lignes pour -la rédaction. C'est le cas des notes dans Zotero ou d'éditeurs en ligne adaptés -au travail collaboratif ([HedgeDoc][1], [HackMD][2], etc.). - -### Avantages - -Les fichiers utilisés ne sont composés que de caractères textuels. Aussi, ils -sont : - -- Simples et légers : - - Ne dépendent pas d'un logiciel spécifique, ni d'un système d'exploitation. - - Pourront être ouverts et édités dans n'importe quel contexte, et - probablement dans un avenir éloigné (pérennité). - - Rapides à ouvrir. Avec n'importe quel éditeur de texte (bloc note Windows, - Notepad++, TextEdit, Gedit, VIM, EMACS, etc.) - - Faciles à sauvegarder, copier, synchroniser, partager. - -Toujours grâce à leur nature textuelle, il est facile d'avoir des outils pour -rechercher (et remplacer) rapidement du texte au sein non seulement du fichier -ouvert, mais d'un ensemble de fichier dans un répertoire. \ -Il est même possible d'écrire des programmes pour modifier ou utiliser -automatiquement ces fichiers. Et il en existe justement beaucoup ! - -Grâce à ces propriétés, il est possible : - -- De prendre des notes au clavier rapidement, de manière structurée, et avec - une recherche interne efficace. -- De rédiger des documents simples (une note) ou complexes (une thèse). -- De regrouper un ensemble de notes dans un « bloc note » numérique et de - naviguer dans cet ensemble, de rechercher de l'information . -- De manière similaire, de regrouper un ensemble de fichiers pour constituer un - document complexe, comme un livre ou un article scientifique. -- Enfin de convertir à partir des mêmes fichiers le document dans plusieurs - formats pour une diffusion sur le web, la lecture dans un PDF ou un ePub - (compatible avec une liseuse électronique). - -Enfin, les fichiers textuels sont particulièrement adaptés au suivi de -l'historique des modification (*versioning*), par exemple avec le logiciel -[Git][3]. - -### Inconvénients - -Pour un usage simple, l'apprentissage n'est pas très difficile. La syntaxe de -base s'acquiert rapidement. Pour parvenir à rédiger des documents relativement -complexes (notes, citations et bibliographies, etc.), cet apprentissage est un -peu plus long. Afin de maîtriser la conversion dans différents formats, tout en -personnalisant la mise en forme des documents obtenus, il devient vite -nécessaire d'apprendre à utiliser d'autres outils, à modifier des modèles dans -les formats désirés (HTML, DOCX, peut-être LaTeX, etc.). - -Certains éditeurs intègrent la fonction de conversion, mais elle repose sur -l'installation de logiciels annexes, principalement `pandoc`, mais aussi une -distribution LaTeX pour obtenir une conversion en PDF. Ces outils ne sont pas -si difficiles à installer. Surtout, ils sont libres, gratuits et disponibles -pour Windows, Mac OS ou Linux. - -Pour utiliser `pandoc`, qui est l'outil de conversion le plus complet, il est -nécessaire de le faire « en ligne de commande » plutôt que dans une interface -graphique, avec la souris. C'est un élément en plus à apprendre, mais qui ouvre -beaucoup de possibilités. - -Le travail collaboratif à plusieurs est toujours possible, mais selon les cas -avec une certaine friction. La situation la plus simple est d'utiliser un -éditeur en ligne qui offre des fonctionnalités d'édition à plusieurs. Souvent -cela est au prix de ne pouvoir disposer de toute la puissance de `pandoc`, du -moins directement. - -Toujours dans le cas où toutes les personnes qui collaborent sont capables de -travailler avec les mêmes outils, elles peuvent toujours travailler ensemble en -se mettant d'accord sur un « workflow », par exemple grâce à `git` mentionné -plus haut. Mais c'est un outil de plus à apprendre. Et de nouvelles -possibilités à découvrir. - -Enfin, si les personnes avec qui on collabore ne veulent ou ne peuvent pas -utiliser les mêmes méthodes, alors il faudra mettre en place un processus -d'export et d'import un peu complexe, mais en partie automatisable -[@perretMarkdownTraitementTexte2022]. - -## La syntaxe markdown - -La syntaxe markdown est très bien documentée sur le web : - -- La documentation « officielle » est disponible sur le site personnel d'un des - concepteurs de la syntaxe : - [@gruberMarkdown2004]. -- Le site *flus* propose Une documentation en français et très claire : - [@fressinaudGuideMarkdown2022]. -- Il y a plusieurs variantes (ou extensions) de la syntaxe et un site essaie de - proposer un standard. Il met à disposition un tutoriel interactif : - [@commonmarkCommonMark]. -- Arthur Perret a traduit en français ce même tutoriel, disponible sur sa page - expliquant ce qu'est markdown : - [@perretMarkdown2022]. - -Pour les tableaux, il est fortement conseillé : - -- D'utiliser un générateur, par exemple - . -- Ou de disposer dans son éditeur d'un générateur de tableaux. - -Le plus souvent les fichiers contenant du markdown ont pour extension `.md`, -mais c'est une convention. Rien n'interdit d'utiliser d'autres extensions comme -`.mkdn` ou `.markdown`, pour autant que l'éditeur que vous utilisez les -reconnaissent comme du markdown. - -## Éditeurs - -Il existe beaucoup d'éditeurs pour rédiger en markdown. On peut les regrouper -en deux grandes catégories : - -1. Les éditeurs dédiés. -1. Les éditeurs généralistes. - -Dans les deux cas, des fonctionnalités de base qui sont presque -incontournables : - -- Une prévisualisation, le plus souvent sous la forme d'un *rendu* HTML. Cette - fonctionnalité a l'avantage de permettre de se faire une idée du résultat - sans devoir générer le fichier final. -- La possibilité de naviguer dans une arborescence de fichiers, en général dans - une barre sur le côté de l'interface. -- Une autocomplétion, pour faciliter l'entrée de syntaxe avec des `[]` ou des - `()`. - -D'autres fonctionnalités ne sont pas indispensables, mais deviennent rapidement -utiles : - -- La correction orthographique pour les langues dans lesquelles vous rédigez. -- Un formatage automatique des tableaux. -- La possibilité de suivre les liens internes, au sein du même fichier ou entre - les fichiers d'un même répertoire. - -### Éditeurs dédiés - -Ce sont des logiciels conçu spécifiquement pour éditer des fichiers avec la -syntaxe markdown. Ils peuvent être sous la forme d'un logiciel à installer sur -votre appareil (ordinateur, tablette, téléphone) ou d'un service web. - -**Typora** est éditeur qui met l'accent sur son interface épurée. Il est disponible -pour tous les systèmes d'exploitation, payant. - -![Interface de GhostWriter][6] - -**GhostWriter** est libre et gratuit, pour Windows et Linux -principalement.[^2] - -**Abricotine** est libre et gratuit, pour toutes les plateformes. - - -**iA Writer** est payant, pour MacOS et Windows, mais est très apprécié pour la -rédaction. Il permet de lier des notes entre elles et offre ainsi une -navigation dans un ensemble de fichiers. - -**Obsidian** est un logiciel propriétaire, pour toutes les plateformes. Il ne -se limite pas à l'édition de fichiers markdown, mais permet de constituer une -base de connaissance basée sur des notes, et cela de manière très complète. - - -![Interface de Zettlr][5] - -**Zettlr** est libre et disponible pour tous les systèmes d'exploitation. Il -est destiné à un usage académique, que ce soit pour construire une base de -connaissance à partir de notes ou pour rédiger un travail de mémoire ou un -article scientifique. Il intègre Zotero pour la gestion des références. - -En ligne, on peut mentionner à nouveau le service [HackMD][2] ou le logiciel -[HedgeDoc][1] qui permet non seulement d'éditer des fichiers markdown avec une -prévisualisation, mais également de le faire à plusieurs, de disposer d'un -suivi des modifications et de commenter. - -![Interface de l'éditeur HackMD][4] - - -### Éditeurs généralistes - -*Généralistes* car ces éditeurs ne sont pas limités à l'édition de fichier -markdown. Le plus souvent, ce sont des *éditeurs de texte* utilisés -par des développeurs pour écrire du code informatique. Le plus souvent, ils -disposent d'un écosystème d'extensions afin de s'adapter à des besoins -spécifiques. De ce fait, ils sont très puissants. - -Ainsi, il est possible d'adapter à l'édition du markdown les éditeurs de texte -suivant, notamment : - -- Notepad++, libre et gratuit, pour Windows. -- Visual Studio Code, gratuit, pour Windows, MacOS, Linux. Un des logiciels les - plus utilisés actuellement. -- VIM ou neovim, libre et gratuit, pour tous les systèmes d'exploitation. -- EMACS, libre et gratuit. - -![Interface de Visual Studio Code][7] - -## Aperçu des possibilités avancées - -### Ensemble de notes - -Afin d'organiser ses notes, il est possible de réunir un ensemble de fichiers -dans le même répertoires (y compris avec des sous répertoires) et de faire des -liens entre les fichiers (et même au sein d'un seul fichier). - -Certains éditeurs, ou certaines extensions d'éditeurs, proposent des facilités -pour générer ces liens, au prix d'une syntaxe *ad hoc* (« wiki links »). Mais -il est parfaitement possible de le faire avec la syntaxe « normale » du lien. - -![Lien depuis le fichier `README.md` vers le fichier `points-a-ameliorer.md`][8] - -La figure 5 montre sur la gauche une arborescence de fichiers, avec -des sous-répertoires, et dans le fichier affiché (`README.md`), un lien vers le -fichier `points-a-ameliorer.md`. Dans cet éditeur, en plaçant le curseur sur le -lien et en tapant la touche « entrée », on ouvre le fichier cible. - -La figure 6 montre la recherche du terme `rdv-info` lancée sur la même -arborescence de fichiers que la figure 5, au moyen du logiciel Visual Studio -Code. Il y a 13 occurrences du terme dans 7 fichiers. Ce type de recherche est -facilité par le fait que ce sont des fichiers au format texte et se retrouve -dans la plupart des éditeurs généralistes de qualité. - -![Recherche dans Visual Studio Code][9] - -### Export vers d'autres formats - -La conversion d'un document markdown en un autre format, plus simple à partager -devient assez vite utile. Pour les conversions, `pandoc` est l'outil le plus -souvent utilisé. Il est soit utilisé par votre éditeur pour faire les exports -(par exemple avec Zettlr), soit par vous même en ligne de commande. C'est un -logiciel libre et gratuit, développé très activement. - -Il est capable de lire et d'écrire dans un grand nombre de formats différents, -parfois à l'aide de logiciels spécifiques. Par exemple, pour produire un -fichier PDF, il peut utiliser plusieurs méthodes, dont LaTeX, ce qui suppose -d'en avoir une version installée. - -La commande pour une conversion simple est de la forme suivante : - -```bash -pandoc --output fichier.docx fichier.md -``` - -Le format obtenu correspond à celui qui est indiqué en extension du nom de -fichier de sortie `--output`, ici du DOCX. Mais il est possible de -l'expliciter : - -```bash -pandoc --to docx --output fichier.docx fichier.md -``` - -Le support de cours de cette introduction à markdown a été rédigée en markdown -et est exportée en PDF grâce à `pandoc` et à *Paged.js*.[^3] - -### Citations et Bibliographie - -Pour insérer des citations et générer une bibliographie, il est nécessaire -d'avoir au moins une collection de références bibliographiques, par exemple -sous la forme d'un fichier `.bib`. Pour obtenir ce fichier, il est possible de -passer par Zotero et d'exporter une collection dans le format BibTex (ou Better -BibTex avec l'extension Zotero du même nom.) - -Dans ce fichier (et dans Zotero), on trouve pour chaque référence une « clé de -citation » qui permet de l'identifier de manière unique. C'est cette clé que -l'on va insérer dans notre fichier markdown pour indiquer une citation. Elle -est insérée avec la syntaxe suivante : `[@clé-de-citation]`. - -On peut encore ajouter à la fin de notre fichier un titre de deuxième niveau -avec le texte bibliographie, pour que `pandoc` puisse y insérer la -bibliographie. - -Pour être en mesure de traiter ces informations, il faut utiliser l'option de -`--citeproc` intégrée dans `pandoc` : - -```bash -pandoc --citeproc \ - --to=pdf --pdf-engine=xelatex \ - --bibliography=references.bib \ - --output fichier.pdf fichier.md -``` - -Dans l'exemple ci-dessus, nous voyons l'option `--citeproc` et l'option -`--bibliography=` qui indique où se trouve le fichier des références -bibliographiques. Par défaut le style est Chicago, de type auteur-date. - -Pour aller plus loin, le mieux est de consulter la [documentation -officielle][10] de `pandoc.` - -## Bibliographie {#bibliography} - - - -[0]: https://www.arthurperret.fr/cours/format-texte.html -[1]: https://hedgedoc.org/ -[2]: https://hackmd.io/ -[3]: https://fr.wikipedia.org/wiki/Git "Article Wikipedia en français" -[4]: ../static/hackmd.png -[5]: ../static/zettlr.png -[6]: ../static/ghostwriter.png -[7]: ../static/vscode.png -[8]: ../static/lien-entre-fichiers.png -[9]: ../static/recherche-vscode.png -[10]: https://pandoc.org/MANUAL.html#citations - - - -[^0]: Cette section repose beaucoup sur la page [Format texte][0] d'Arthur -Perret [@perretFormatTexte2022]. -[^1]: Markdown n'est pas le seul langage de balisage léger existant, bien que -ce soit le plus répandu. D'autres sont encore plus robustes ou puissants, comme -*RestructuredText* ou *AsciiDoc*. -[^2]: Il semble possible de l'installer sur MacOS, mais n'est pas recommandé. -[^3]: Les sources et la méthode de conversion est disponible sur -. diff --git a/intro/references.bib b/intro/references.bib deleted file mode 100644 index 9f0f64b..0000000 --- a/intro/references.bib +++ /dev/null @@ -1,370 +0,0 @@ -@article{bunchIndirectReferenceIntervals2022, - title = {Indirect Reference Intervals Using an {{R}} Pipeline.}, - author = {Bunch, Dustin R.}, - date = {2022-04}, - journaltitle = {J Mass Spectrom Adv Clin Lab}, - volume = {24}, - eprint = {35252947}, - eprinttype = {pmid}, - pages = {22--30}, - location = {Netherlands}, - issn = {2667-145X 2667-1468}, - doi = {10.1016/j.jmsacl.2022.02.004}, - abstract = {BACKGROUND: Indirect reference intervals require robust statistical approaches to separate the pathological and healthy values. This can be achieved with a data pipeline created in R, a freely available statistical programming language. METHODS: A data pipeline was created to ingest, partition, normalize, remove outliers, and identify reference intervals for testosterone (Testo; n ~=~7,207) and aspartate aminotransferase (AST; n ~=~5,882) using data sets from NHANES. RESULTS: The estimates for AST and Testo determined by this pipeline approximated current RIs. Care should be taken when using this pipeline as there are limitations that depend on the pathology of the analyte and the data set being used for RI estimation. CONCLUSIONS: R can be used to create a robust statistical reference interval pipeline.}, - langid = {english}, - pmcid = {PMC8889237}, - keywords = {ANOVA Analysis of variance,AST aspartate aminotransferase,CLSI Clinical Laboratory Standards Institute,cmu,EHR electronic health record,IFCC International Federation of Clinical Chemistry and Laboratory Medicine,LC-MS/MS Liquid chromatography tandem mass spectrometry,LIS Laboratory informatics system,markdown,Mixtools,non lu,pubmed,R markdown,R markdown tutorial,Reference interval,RI reference interval,SDI Standard deviation index,SDR Standard deviation ratio,Testo Testosterone,TukeyHSD Tukey multiple pairwise-comparisons,z5 Critical z-score}, - file = {/home/igor/Zotero/storage/YYP9JQTY/Bunch_2022_Indirect reference intervals using an R pipeline.pdf} -} - -@article{chapmanExpectedPosterioriScoring2022, - title = {Expected a Posteriori Scoring in {{PROMIS}}(®).}, - author = {Chapman, Robert}, - date = {2022-06-03}, - journaltitle = {J Patient Rep Outcomes}, - volume = {6}, - number = {1}, - eprint = {35657454}, - eprinttype = {pmid}, - pages = {59}, - location = {Germany}, - issn = {2509-8020}, - doi = {10.1186/s41687-022-00464-9}, - abstract = {BACKGROUND: The Patient-Reported Outcome Measurement Information System(®) (PROMIS(®)) was developed to reliably measure health-related quality of life using the patient's voice. To achieve these aims, PROMIS utilized Item Response Theory methods in its development, validation and implementation. PROMIS measures are typically scored using a specific method to calculate scores, called Expected A Posteriori estimation. BODY: Expected A Posteriori scoring methods are flexible, produce accurate scores and can be efficiently calculated by statistical software. This work seeks to make Expected A Posteriori scoring methods transparent and accessible to a larger audience through description, graphical demonstration and examples. Further applications and practical considerations of Expected A Posteriori scoring are presented and discussed. All materials used in this paper are made available through the R Markdown reproducibility framework and are intended to be reviewed and reused. Commented statistical code for the calculation of Expected A Posteriori scores is included. CONCLUSION: This work seeks to provide the reader with a summary and visualization of the operation of Expected A Posteriori scoring, as implemented in PROMIS. As PROMIS is increasingly adopted and implemented, this work will provide a basis for making psychometric methods more accessible to the PROMIS user base.}, - langid = {english}, - pmcid = {PMC9166925}, - keywords = {cmu,markdown,non lu,pubmed,R markdown}, - file = {/home/igor/Zotero/storage/7R2KLGBS/Chapman_2022_Expected a posteriori scoring in PROMIS(®).pdf} -} - -@online{commonmarkCommonMark, - title = {{{CommonMark}}}, - author = {CommonMark}, - url = {https://commonmark.org/}, - urldate = {2023-04-25}, - langid = {english}, - organization = {CommonMark}, - keywords = {cmu,non lu}, - file = {/home/igor/Zotero/storage/ESNTHT9H/commonmark.org.html} -} - -@article{considineToolEncourageMinimum2019, - title = {A {{Tool}} to {{Encourage Minimum Reporting Guideline Uptake}} for {{Data Analysis}} in {{Metabolomics}}.}, - author = {Considine, Elizabeth C. and Salek, Reza M.}, - date = {2019-03-05}, - journaltitle = {Metabolites}, - volume = {9}, - number = {3}, - eprint = {30841575}, - eprinttype = {pmid}, - location = {Switzerland}, - issn = {2218-1989}, - doi = {10.3390/metabo9030043}, - abstract = {Despite the proposal of minimum reporting guidelines for metabolomics over a decade ago, reporting on the data analysis step in metabolomics studies has been shown to be unclear and incomplete. Major omissions and a lack of logical flow render the data analysis' sections in metabolomics studies impossible to follow, and therefore replicate or even imitate. Here, we propose possible reasons why the original reporting guidelines have had poor adherence and present an approach to improve their uptake. We present in this paper an R markdown reporting template file that guides the production of text and generates workflow diagrams based on user input. This R Markdown template contains, as an example in this instance, a set of minimum information requirements specifically for the data pre-treatment and data analysis section of biomarker discovery metabolomics studies, (gleaned directly from the original proposed guidelines by Goodacre at al). These minimum requirements are presented in the format of a questionnaire checklist in an R markdown template file. The R Markdown reporting template proposed here can be presented as a starting point to encourage the data analysis section of a metabolomics manuscript to have a more logical presentation and to contain enough information to be understandable and reusable. The idea is that these guidelines would be open to user feedback, modification and updating by the metabolomics community via GitHub.}, - langid = {english}, - pmcid = {PMC6468746}, - keywords = {cmu,data analysis,markdown,minimum guidelines,non lu,pubmed,R markdown,reporting,reproducibility}, - file = {/home/igor/Zotero/storage/RSRUDDYS/Considine_Salek_2019_A Tool to Encourage Minimum Reporting Guideline Uptake for Data Analysis in.pdf} -} - -@article{daasDynamicPublicationMedia2022, - title = {Dynamic Publication Media with the {{COPASI R Connector}} ({{CoRC}}).}, - author = {Daas, Johanna C. J. and Förster, Jonas D. and Pahle, Jürgen}, - date = {2022-06}, - journaltitle = {Math Biosci}, - volume = {348}, - eprint = {35452633}, - eprinttype = {pmid}, - pages = {108822}, - location = {United States}, - issn = {1879-3134 0025-5564}, - doi = {10.1016/j.mbs.2022.108822}, - abstract = {In this article we show how dynamic publication media and the COPASI R Connector (CoRC) can be combined in a natural and synergistic way to communicate (biochemical) models. Dynamic publication media are becoming a popular tool for authors to effectively compose and publish their work. They are built from templates and the final documents are created dynamically. In addition, they can also be interactive. Working with dynamic publication media is made easy with the programming environment R via its integration with tools such as R Markdown, Jupyter and Shiny. Additionally, the COmplex PAthway SImulator COPASI (http://www.copasi.org), a widely used biochemical modelling toolkit, is available in R through the use of the COPASI R Connector (CoRC, https://jpahle.github.io/CoRC). Models are a common tool in the mathematical biosciences, in particular kinetic models of biochemical networks in (computational) systems biology. We focus on three application areas of dynamic publication media and CoRC: Documentation (reproducible workflows), Teaching (creating self-paced lessons) and Science Communication (immersive and engaging presentation). To illustrate these, we created six dynamic document examples in the form of R Markdown and Jupyter notebooks, hosted on the platforms GitHub, shinyapps.io, Google Colaboratory. Having code and output in one place, creating documents in template-form and the option of interactivity make the combination of dynamic documents and CoRC a versatile tool. All our example documents are freely available at https://jpahle.github.io/DynamiCoRC under the Creative Commons BY 4.0 licence.}, - langid = {english}, - keywords = {*Software,*Systems Biology,cmu,COPASI,CoRC,Dynamic publication media,Jupyter,Kinetics,markdown,non lu,pubmed,R markdown,Systems biology}, - file = {/home/igor/Zotero/storage/WJ5FCFZY/Daas et al_2022_Dynamic publication media with the COPASI R Connector (CoRC).pdf} -} - -@unpublished{deletrazModeTexteMarkdown2022, - type = {Atelier}, - title = {En mode texte\,: Markdown, Stylo, Pandoc, Notebook…}, - author = {Deletraz, Gaëlle and Rabaud, Julien}, - date = {2022}, - url = {https://markdown-somate2022.netlify.app}, - urldate = {2023-04-17}, - eventtitle = {So-Mate 2022}, - langid = {fre}, - venue = {Pau}, - keywords = {cmu,markdown,non lu,pandoc,Publication scientifique} -} - -@online{eyssetteUtiliserMarkdownPour2023, - type = {Mastodon post}, - title = {Utiliser le Markdown pour tout faire.Le diaporama (fait en markdown bien sûr !) qui m'a servi de support lors de mon atelier pour la Journée…}, - author = {Eyssette, Cédric (@eyssette@scholar.social)}, - date = {2023-04-07}, - url = {https://scholar.social/@eyssette/110158455766516456}, - urldate = {2023-04-17}, - abstract = {Utiliser le Markdown pour tout faire.Le diaporama (fait en markdown bien sûr !) qui m'a servi de support lors de mon atelier pour la Journée du Libre Éducatif 2023.https://eyssette.forge.aeif.fr/marp-slides/slides/2022-2023/utiliser-le-markdown-pour-tout-faire\#Markdown\#JDLE\#JDLE2023\#TeamEduc\#MastoProf}, - langid = {french}, - organization = {Mastodon}, - keywords = {cmu,fediverse,markdown,mastodon,non lu}, - file = {/home/igor/Zotero/storage/7FYFNED6/110158455766516456.html} -} - -@unpublished{eyssetteUtiliserMarkdownPour2023a, - title = {Utiliser le markdown pour tout faire}, - author = {Eyssette, Cédric}, - date = {2023}, - url = {https://eyssette.forge.aeif.fr/marp-slides/slides/2022-2023/utiliser-le-markdown-pour-tout-faire}, - urldate = {2023-04-17}, - langid = {fre}, - keywords = {cmu,non lu}, - file = {/home/igor/Zotero/storage/I7CTE3X5/utiliser-le-markdown-pour-tout-faire.html} -} - -@online{fressinaudGuideMarkdown2022, - type = {Site d'une application web}, - title = {Guide Markdown}, - author = {Fressinaud, Marien}, - date = {2022-07-11}, - url = {https://flus.fr/carnet/markdown.html}, - urldate = {2023-04-17}, - abstract = {Un guide simple pour apprendre Markdown.}, - langid = {fre}, - organization = {flus}, - keywords = {cmu,documentation,markdown,non lu}, - file = {/home/igor/Zotero/storage/96VGWVYV/markdown.html} -} - -@article{graysonMarkdownDynamicInterface2022, - title = {R {{Markdown}} as a Dynamic Interface for Teaching: {{Modules}} from Math and Biology Classrooms.}, - author = {Grayson, Kristine L. and Hilliker, Angela K. and Wares, Joanna R.}, - date = {2022-07}, - journaltitle = {Math Biosci}, - volume = {349}, - eprint = {35623397}, - eprinttype = {pmid}, - pages = {108844}, - location = {United States}, - issn = {1879-3134 0025-5564}, - doi = {10.1016/j.mbs.2022.108844}, - abstract = {Advancing technologies, including interactive tools, are changing classroom pedagogy across academia. Here, we discuss the R Markdown interface, which allows for the creation of partial or complete interactive classroom modules for courses using the R programming language. R Markdown files mix sections of R code with formatted text, including LaTeX, which are rendered together to form complete reports and documents. These features allow instructors to create classroom modules that guide students through concepts, while providing areas for coding and text response by students. Students can also learn to create their own reports for more independent assignments. After presenting the features and uses of R Markdown to enhance teaching and learning, we present examples of materials from two courses. In a Computational Modeling course for math students, we used R Markdown to guide students through exploring mathematical models to understand the principle of herd immunity. In a Data Visualization and Communication course for biology students, we used R Markdown for teaching the fundamentals of R programming and graphing, and for students to learn to create reproducible data investigations. Through these examples, we demonstrate the benefits of R Markdown as a dynamic teaching and learning tool.}, - langid = {english}, - pmcid = {PMC9487201}, - keywords = {*Learning,*Students,Biology/education,cmu,Data visualization,Herd immunity,Humans,markdown,non lu,Pedagogy,pubmed,R markdown,Teaching programming}, - file = {/home/igor/Zotero/storage/JQGHBHB5/Grayson et al_2022_R Markdown as a dynamic interface for teaching.pdf} -} - -@online{gruberMarkdown2004, - type = {Site personnel}, - title = {Markdown}, - author = {Gruber, John}, - date = {2004-12-17}, - url = {https://daringfireball.net/projects/markdown/}, - urldate = {2023-04-17}, - langid = {english}, - organization = {Daring Fireball}, - keywords = {cmu,documentation,markdown,non lu}, - file = {/home/igor/Zotero/storage/J7WP5B3D/markdown.html} -} - -@article{hershbergJBrowseRInterfaceJBrowse2021, - title = {{{JBrowseR}}: An {{R}} Interface to the {{JBrowse}} 2 Genome Browser.}, - author = {Hershberg, Elliot A. and Stevens, Garrett and Diesh, Colin and Xie, Peter and De Jesus Martinez, Teresa and Buels, Robert and Stein, Lincoln and Holmes, Ian}, - date = {2021-11-05}, - journaltitle = {Bioinformatics}, - volume = {37}, - number = {21}, - eprint = {34196689}, - eprinttype = {pmid}, - pages = {3914--3915}, - location = {England}, - issn = {1367-4811 1367-4803}, - doi = {10.1093/bioinformatics/btab459}, - abstract = {MOTIVATION: Genome browsers are an essential tool in genome analysis. Modern genome browsers enable complex and interactive visualization of a wide variety of genomic data modalities. While such browsers are very powerful, they can be challenging to configure and program for bioinformaticians lacking expertise in web development. RESULTS: We have developed an R package that provides an interface to the JBrowse 2 genome browser. The package can be used to configure and customize the browser entirely with R code. The browser can be deployed from the R console, or embedded in Shiny applications or R Markdown documents. AVAILABILITY AND IMPLEMENTATION: JBrowseR is available for download from CRAN, and the source code is openly available from the Github repository at https://github.com/GMOD/JBrowseR/.}, - langid = {english}, - pmcid = {PMC8570803}, - keywords = {*Genome,*Genomics,cmu,markdown,non lu,pubmed,R markdown,Software}, - file = {/home/igor/Zotero/storage/LYEIF748/Hershberg et al_2021_JBrowseR.pdf} -} - -@article{jaglaSCHNAPPsSingleCell2021, - title = {{{SCHNAPPs}} - {{Single Cell sHiNy APPlication}}(s).}, - author = {Jagla, Bernd and Libri, Valentina and Chica, Claudia and Rouilly, Vincent and Mella, Sebastien and Puceat, Michel and Hasan, Milena}, - date = {2021-12}, - journaltitle = {J Immunol Methods}, - volume = {499}, - eprint = {34742775}, - eprinttype = {pmid}, - pages = {113176}, - location = {Netherlands}, - issn = {1872-7905 0022-1759}, - doi = {10.1016/j.jim.2021.113176}, - abstract = {Single-cell RNA-sequencing (scRNAseq) experiments are becoming a standard tool for bench-scientists to explore the cellular diversity present in all tissues. Data produced by scRNAseq is technically complex and requires analytical workflows that are an active field of bioinformatics research, whereas a wealth of biological background knowledge is needed to guide the investigation. Thus, there is an increasing need to develop applications geared towards bench-scientists to help them abstract the technical challenges of the analysis so that they can focus on the science at play. It is also expected that such applications should support closer collaboration between bioinformaticians and bench-scientists by providing reproducible science tools. We present SCHNAPPs, a Graphical User Interface (GUI), designed to enable bench-scientists to autonomously explore and interpret scRNAseq data and associated annotations. The R/Shiny-based application allows following different steps of scRNAseq analysis workflows from Seurat or Scran packages: performing quality control on cells and genes, normalizing the expression matrix, integrating different samples, dimension reduction, clustering, and differential gene expression analysis. Visualization tools for exploring each step of the process include violin plots, 2D projections, Box-plots, alluvial plots, and histograms. An R-markdown report can be generated that tracks modifications and selected visualizations. The modular design of the tool allows it to easily integrate new visualizations and analyses by bioinformaticians. We illustrate the main features of the tool by applying it to the characterization of T cells in a scRNAseq and Cellular Indexing of Transcriptomes and Epitopes by Sequencing (CITE-Seq) experiment of two healthy individuals.}, - langid = {english}, - keywords = {*Sequence Analysis RNA,*Single-Cell Analysis,*Software,CITE-Seq,cmu,Humans,Leukocytes Mononuclear/*cytology/immunology,markdown,multi-omics data analysis,non lu,pubmed,R markdown,scRNA-seq,Shiny application}, - file = {/home/igor/Zotero/storage/PEARJ76P/Jagla et al_2021_SCHNAPPs - Single Cell sHiNy APPlication(s).pdf} -} - -@article{kariyawasamDashboardstyleInteractivePlots2021, - title = {Dashboard-Style Interactive Plots for {{RNA-seq}} Analysis Are {{R Markdown}} Ready with {{Glimma}} 2.0.}, - author = {Kariyawasam, Hasaru and Su, Shian and Voogd, Oliver and Ritchie, Matthew E. and Law, Charity W.}, - date = {2021-12}, - journaltitle = {NAR Genom Bioinform}, - volume = {3}, - number = {4}, - eprint = {34988439}, - eprinttype = {pmid}, - pages = {lqab116}, - location = {England}, - issn = {2631-9268}, - doi = {10.1093/nargab/lqab116}, - abstract = {Glimma 1.0 introduced intuitive, point-and-click interactive graphics for differential gene expression analysis. Here, we present a major update to Glimma that brings improved interactivity and reproducibility using high-level visualization frameworks for R and JavaScript. Glimma 2.0 plots are now readily embeddable in R Markdown, thus allowing users to create reproducible reports containing interactive graphics. The revamped multidimensional scaling plot features dashboard-style controls allowing the user to dynamically change the colour, shape and size of sample points according to different experimental conditions. Interactivity was enhanced in the MA-style plot for comparing differences to average expression, which now supports selecting multiple genes, export options to PNG, SVG or CSV formats and includes a new volcano plot function. Feature-rich and user-friendly, Glimma makes exploring data for gene expression analysis more accessible and intuitive and is available on Bioconductor and GitHub.}, - langid = {english}, - pmcid = {PMC8693569}, - keywords = {cmu,markdown,non lu,pubmed,R markdown}, - file = {/home/igor/Zotero/storage/ZQD2WXNN/Kariyawasam et al_2021_Dashboard-style interactive plots for RNA-seq analysis are R Markdown ready.pdf} -} - -@online{masuttiRedigerManipulerDocuments2019, - title = {Rédiger et manipuler des documents avec Markdown}, - author = {Masutti, Christophe}, - date = {2019-05-08}, - url = {https://golb.statium.link/post/20190528rediger-et-manipuler-des-documents-markdown/}, - urldate = {2024-03-19}, - langid = {fre}, - organization = {Statium : blog}, - keywords = {cmu,markdown,non lu,pandoc,publication numérique,Publication scientifique} -} - -@article{ovadiaMarkdownLibrariansAcademics2014a, - title = {Markdown for {{Librarians}} and {{Academics}}}, - author = {Ovadia, Steven}, - date = {2014-04-03}, - journaltitle = {Behavioral \& Social Sciences Librarian}, - volume = {33}, - number = {2}, - pages = {120--124}, - issn = {0163-9269, 1544-4546}, - doi = {10.1080/01639269.2014.904696}, - url = {http://www.tandfonline.com/doi/abs/10.1080/01639269.2014.904696}, - urldate = {2023-05-02}, - langid = {english}, - keywords = {cmu,markdown,non lu,Publication scientifique}, - file = {/home/igor/Zotero/storage/25MP4P3N/Ovadia_2014_Markdown for Librarians and Academics.pdf} -} - -@online{paged.jsPagedJs, - title = {About {{Paged}}.Js?}, - author = {Paged.js}, - url = {https://pagedjs.org/about/}, - urldate = {2024-03-08}, - langid = {english}, - organization = {Paged.js}, - keywords = {édition numérique,non lu,publication numérique} -} - -@online{pandocWritingThesisThinking2023, - type = {Mastodon post}, - title = {Writing a \#thesis? {{Thinking}} about Doing It in \#{{Markdown}}? {{No}} Problem, {{Tom Pollard}} Has You Covered.{{https://github.com/tompollard/phd\_thesis\_ma…}}}, - shorttitle = {Writing a \#thesis?}, - author = {{pandoc}, (@pandoc@fosstodon.org)}, - date = {2023-04-12}, - url = {https://fosstodon.org/@pandoc/110184589069297715}, - urldate = {2023-04-17}, - abstract = {Writing a \#thesis? Thinking about doing it in \#Markdown? No problem, Tom Pollard has you covered.https://github.com/tompollard/phd\_thesis\_markdown}, - langid = {english}, - organization = {Mastodon}, - keywords = {cmu,fediverse,markdown,mastodon,non lu,pandoc,Publication scientifique}, - file = {/home/igor/Zotero/storage/T354LYSW/110184589069297715.html} -} - -@article{perretFormatTexte2022, - title = {Format texte}, - author = {Perret, Arthur}, - date = {2022-12-11}, - journaltitle = {Arthur Perret}, - url = {https://www.arthurperret.fr/cours/format-texte.html}, - urldate = {2023-04-18}, - abstract = {Cette page explique ce qu’est le format texte et donne des arguments en faveur de son utilisation. Elle ouvre vers d’autres ressources pour explorer l’écosystème du format texte.}, - langid = {fre}, - keywords = {cmu,markdown,non lu,plain text}, - file = {/home/igor/Zotero/storage/3IM8K4DU/format-texte.html} -} - -@article{perretMarkdown2022, - title = {Markdown}, - author = {Perret, Arthur}, - date = {2022-12-11}, - journaltitle = {Arthur Perret}, - publisher = {arthurperret.fr/}, - url = {https://www.arthurperret.fr/cours/markdown.html}, - urldate = {2023-04-17}, - abstract = {Cette page explique ce qu’est Markdown, le plus populaire des langages de balisage léger, et renvoie vers un tutoriel interactif en français.}, - langid = {french}, - keywords = {cmu,documentation,markdown,non lu}, - file = {/home/igor/Zotero/storage/HZNSDQ7S/markdown.html} -} - -@online{perretMarkdownTraitementTexte2022, - title = {Markdown et traitement de texte~: une méthode pour collaborer}, - shorttitle = {Markdown et traitement de texte}, - author = {Perret, Arthur}, - date = {2022-10-25}, - url = {https://www.arthurperret.fr/blog/2022-10-25-markdown-traitement-de-texte-methode-pour-collaborer.html}, - urldate = {2024-04-23}, - langid = {fre}, - organization = {arthurperret.fr}, - keywords = {non lu}, - file = {/home/igor/Zotero/storage/TQEHEIHA/2022-10-25-markdown-traitement-de-texte-methode-pour-collaborer.html} -} - -@software{pollardTemplateWritingPhD2023, - title = {Template for Writing a {{PhD}} Thesis in {{Markdown}}}, - author = {Pollard, Tom}, - date = {2023-04-17T04:40:26Z}, - origdate = {2015-02-10T10:32:25Z}, - url = {https://github.com/tompollard/phd_thesis_markdown}, - urldate = {2023-04-17}, - abstract = {Template for writing a PhD thesis in Markdown}, - keywords = {cmu,markdown,non lu,pandoc,Publication scientifique,thesis-template} -} - -@article{schneiderFacilitatingOpenScience2022, - title = {Facilitating Open Science Practices for Research Syntheses: {{PreregRS}} Guides Preregistration.}, - author = {Schneider, Jürgen and Backfisch, Iris and Lachner, Andreas}, - date = {2022-03}, - journaltitle = {Res Synth Methods}, - volume = {13}, - number = {2}, - eprint = {34921744}, - eprinttype = {pmid}, - pages = {284--289}, - location = {England}, - issn = {1759-2887 1759-2879}, - doi = {10.1002/jrsm.1540}, - abstract = {Researchers increasingly engage in adopting open science practices in the field of research syntheses, such as preregistration. Preregistration is a central open science practice in empirical research to enhance transparency in the research process and it gains steady adoption in the context of conducting research synthesis. From an interdisciplinary perspective, frameworks and particularly templates are lacking which support researchers preparing a preregistration. To this end, we introduce preregRS, a template to guide researchers across disciplines through the process of preregistering research syntheses. We utilized an R Markdown template file to provide a framework that structures the process of preparing a preregistration. Researchers can write up the preregistration using the template file similar to filling out a form, with the template providing additional hints and further information for the decisions along the framework. We integrated the R Markdown template in an R package for easy installation and use, but also provide a browser-based option for users granting low-barrier access. PreregRS constitutes a first step to facilitate and support preregistration with research syntheses for all disciplines. It further adds to establishing open science practices in conducting research syntheses.}, - langid = {english}, - keywords = {*Surveys and Questionnaires,cmu,Empirical Research,markdown,non lu,open science,preregistration,pubmed,R markdown,R package} -} - -@article{yuVeridicalDataScience2020, - title = {Veridical Data Science.}, - author = {Yu, Bin and Kumbier, Karl}, - date = {2020-02-25}, - journaltitle = {Proc Natl Acad Sci U S A}, - volume = {117}, - number = {8}, - eprint = {32054788}, - eprinttype = {pmid}, - pages = {3920--3929}, - location = {United States}, - issn = {1091-6490 0027-8424}, - doi = {10.1073/pnas.1901326117}, - abstract = {Building and expanding on principles of statistics, machine learning, and scientific inquiry, we propose the predictability, computability, and stability (PCS) framework for veridical data science. Our framework, composed of both a workflow and documentation, aims to provide responsible, reliable, reproducible, and transparent results across the data science life cycle. The PCS workflow uses predictability as a reality check and considers the importance of computation in data collection/storage and algorithm design. It augments predictability and computability with an overarching stability principle. Stability expands on statistical uncertainty considerations to assess how human judgment calls impact data results through data and model/algorithm perturbations. As part of the PCS workflow, we develop PCS inference procedures, namely PCS perturbation intervals and PCS hypothesis testing, to investigate the stability of data results relative to problem formulation, data cleaning, modeling decisions, and interpretations. We illustrate PCS inference through neuroscience and genomics projects of our own and others. Moreover, we demonstrate its favorable performance over existing methods in terms of receiver operating characteristic (ROC) curves in high-dimensional, sparse linear model simulations, including a wide range of misspecified models. Finally, we propose PCS documentation based on R Markdown or Jupyter Notebook, with publicly available, reproducible codes and narratives to back up human choices made throughout an analysis. The PCS workflow and documentation are demonstrated in a genomics case study available on Zenodo.}, - langid = {english}, - pmcid = {PMC7049126}, - keywords = {cmu,computation,data science,markdown,non lu,prediction,pubmed,R markdown,stability}, - file = {/home/igor/Zotero/storage/GWVCYDA6/Yu_Kumbier_2020_Veridical data science.pdf} -} diff --git a/paged.polyfill.js b/paged.polyfill.js new file mode 100644 index 0000000..3100799 --- /dev/null +++ b/paged.polyfill.js @@ -0,0 +1,33251 @@ +/** + * @license Paged.js v0.4.3 | MIT | https://gitlab.coko.foundation/pagedjs/pagedjs + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.PagedPolyfill = factory()); +})(this, (function () { 'use strict'; + + function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; + } + + var eventEmitter = {exports: {}}; + + var d$2 = {exports: {}}; + + var isImplemented$6 = function () { + var assign = Object.assign, obj; + if (typeof assign !== "function") return false; + obj = { foo: "raz" }; + assign(obj, { bar: "dwa" }, { trzy: "trzy" }); + return (obj.foo + obj.bar + obj.trzy) === "razdwatrzy"; + }; + + var isImplemented$5; + var hasRequiredIsImplemented$1; + + function requireIsImplemented$1 () { + if (hasRequiredIsImplemented$1) return isImplemented$5; + hasRequiredIsImplemented$1 = 1; + + isImplemented$5 = function () { + try { + Object.keys("primitive"); + return true; + } catch (e) { + return false; + } + }; + return isImplemented$5; + } + + // eslint-disable-next-line no-empty-function + var noop$4 = function () {}; + + var _undefined = noop$4(); // Support ES3 engines + + var isValue$3 = function (val) { + return (val !== _undefined) && (val !== null); + }; + + var shim$5; + var hasRequiredShim$5; + + function requireShim$5 () { + if (hasRequiredShim$5) return shim$5; + hasRequiredShim$5 = 1; + + var isValue = isValue$3; + + var keys = Object.keys; + + shim$5 = function (object) { + return keys(isValue(object) ? Object(object) : object); + }; + return shim$5; + } + + var keys; + var hasRequiredKeys; + + function requireKeys () { + if (hasRequiredKeys) return keys; + hasRequiredKeys = 1; + + keys = requireIsImplemented$1()() + ? Object.keys + : requireShim$5(); + return keys; + } + + var isValue$2 = isValue$3; + + var validValue = function (value) { + if (!isValue$2(value)) throw new TypeError("Cannot use null or undefined"); + return value; + }; + + var shim$4; + var hasRequiredShim$4; + + function requireShim$4 () { + if (hasRequiredShim$4) return shim$4; + hasRequiredShim$4 = 1; + + var keys = requireKeys() + , value = validValue + , max = Math.max; + + shim$4 = function (dest, src /*, …srcn*/) { + var error, i, length = max(arguments.length, 2), assign; + dest = Object(value(dest)); + assign = function (key) { + try { + dest[key] = src[key]; + } catch (e) { + if (!error) error = e; + } + }; + for (i = 1; i < length; ++i) { + src = arguments[i]; + keys(src).forEach(assign); + } + if (error !== undefined) throw error; + return dest; + }; + return shim$4; + } + + var assign$2 = isImplemented$6() + ? Object.assign + : requireShim$4(); + + var isValue$1 = isValue$3; + + var forEach$1 = Array.prototype.forEach, create$5 = Object.create; + + var process = function (src, obj) { + var key; + for (key in src) obj[key] = src[key]; + }; + + // eslint-disable-next-line no-unused-vars + var normalizeOptions = function (opts1 /*, …options*/) { + var result = create$5(null); + forEach$1.call(arguments, function (options) { + if (!isValue$1(options)) return; + process(Object(options), result); + }); + return result; + }; + + var isCallable$1 = function (obj) { + return typeof obj === "function"; + }; + + var str = "razdwatrzy"; + + var isImplemented$4 = function () { + if (typeof str.contains !== "function") return false; + return (str.contains("dwa") === true) && (str.contains("foo") === false); + }; + + var shim$3; + var hasRequiredShim$3; + + function requireShim$3 () { + if (hasRequiredShim$3) return shim$3; + hasRequiredShim$3 = 1; + + var indexOf = String.prototype.indexOf; + + shim$3 = function (searchString/*, position*/) { + return indexOf.call(this, searchString, arguments[1]) > -1; + }; + return shim$3; + } + + var contains$1 = isImplemented$4() + ? String.prototype.contains + : requireShim$3(); + + var assign$1 = assign$2 + , normalizeOpts = normalizeOptions + , isCallable = isCallable$1 + , contains = contains$1 + + , d$1; + + d$1 = d$2.exports = function (dscr, value/*, options*/) { + var c, e, w, options, desc; + if ((arguments.length < 2) || (typeof dscr !== 'string')) { + options = value; + value = dscr; + dscr = null; + } else { + options = arguments[2]; + } + if (dscr == null) { + c = w = true; + e = false; + } else { + c = contains.call(dscr, 'c'); + e = contains.call(dscr, 'e'); + w = contains.call(dscr, 'w'); + } + + desc = { value: value, configurable: c, enumerable: e, writable: w }; + return !options ? desc : assign$1(normalizeOpts(options), desc); + }; + + d$1.gs = function (dscr, get, set/*, options*/) { + var c, e, options, desc; + if (typeof dscr !== 'string') { + options = set; + set = get; + get = dscr; + dscr = null; + } else { + options = arguments[3]; + } + if (get == null) { + get = undefined; + } else if (!isCallable(get)) { + options = get; + get = set = undefined; + } else if (set == null) { + set = undefined; + } else if (!isCallable(set)) { + options = set; + set = undefined; + } + if (dscr == null) { + c = true; + e = false; + } else { + c = contains.call(dscr, 'c'); + e = contains.call(dscr, 'e'); + } + + desc = { get: get, set: set, configurable: c, enumerable: e }; + return !options ? desc : assign$1(normalizeOpts(options), desc); + }; + + var dExports = d$2.exports; + + var validCallable = function (fn) { + if (typeof fn !== "function") throw new TypeError(fn + " is not a function"); + return fn; + }; + + (function (module, exports) { + + var d = dExports + , callable = validCallable + + , apply = Function.prototype.apply, call = Function.prototype.call + , create = Object.create, defineProperty = Object.defineProperty + , defineProperties = Object.defineProperties + , hasOwnProperty = Object.prototype.hasOwnProperty + , descriptor = { configurable: true, enumerable: false, writable: true } + + , on, once, off, emit, methods, descriptors, base; + + on = function (type, listener) { + var data; + + callable(listener); + + if (!hasOwnProperty.call(this, '__ee__')) { + data = descriptor.value = create(null); + defineProperty(this, '__ee__', descriptor); + descriptor.value = null; + } else { + data = this.__ee__; + } + if (!data[type]) data[type] = listener; + else if (typeof data[type] === 'object') data[type].push(listener); + else data[type] = [data[type], listener]; + + return this; + }; + + once = function (type, listener) { + var once, self; + + callable(listener); + self = this; + on.call(this, type, once = function () { + off.call(self, type, once); + apply.call(listener, this, arguments); + }); + + once.__eeOnceListener__ = listener; + return this; + }; + + off = function (type, listener) { + var data, listeners, candidate, i; + + callable(listener); + + if (!hasOwnProperty.call(this, '__ee__')) return this; + data = this.__ee__; + if (!data[type]) return this; + listeners = data[type]; + + if (typeof listeners === 'object') { + for (i = 0; (candidate = listeners[i]); ++i) { + if ((candidate === listener) || + (candidate.__eeOnceListener__ === listener)) { + if (listeners.length === 2) data[type] = listeners[i ? 0 : 1]; + else listeners.splice(i, 1); + } + } + } else { + if ((listeners === listener) || + (listeners.__eeOnceListener__ === listener)) { + delete data[type]; + } + } + + return this; + }; + + emit = function (type) { + var i, l, listener, listeners, args; + + if (!hasOwnProperty.call(this, '__ee__')) return; + listeners = this.__ee__[type]; + if (!listeners) return; + + if (typeof listeners === 'object') { + l = arguments.length; + args = new Array(l - 1); + for (i = 1; i < l; ++i) args[i - 1] = arguments[i]; + + listeners = listeners.slice(); + for (i = 0; (listener = listeners[i]); ++i) { + apply.call(listener, this, args); + } + } else { + switch (arguments.length) { + case 1: + call.call(listeners, this); + break; + case 2: + call.call(listeners, this, arguments[1]); + break; + case 3: + call.call(listeners, this, arguments[1], arguments[2]); + break; + default: + l = arguments.length; + args = new Array(l - 1); + for (i = 1; i < l; ++i) { + args[i - 1] = arguments[i]; + } + apply.call(listeners, this, args); + } + } + }; + + methods = { + on: on, + once: once, + off: off, + emit: emit + }; + + descriptors = { + on: d(on), + once: d(once), + off: d(off), + emit: d(emit) + }; + + base = defineProperties({}, descriptors); + + module.exports = exports = function (o) { + return (o == null) ? create(base) : defineProperties(Object(o), descriptors); + }; + exports.methods = methods; + } (eventEmitter, eventEmitter.exports)); + + var eventEmitterExports = eventEmitter.exports; + var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventEmitterExports); + + /** + * Hooks allow for injecting functions that must all complete in order before finishing + * They will execute in parallel but all must finish before continuing + * Functions may return a promise if they are asycn. + * From epubjs/src/utils/hooks + * @param {any} context scope of this + * @example this.content = new Hook(this); + */ + class Hook { + constructor(context){ + this.context = context || this; + this.hooks = []; + } + + /** + * Adds a function to be run before a hook completes + * @example this.content.register(function(){...}); + * @return {undefined} void + */ + register(){ + for(var i = 0; i < arguments.length; ++i) { + if (typeof arguments[i] === "function") { + this.hooks.push(arguments[i]); + } else { + // unpack array + for(var j = 0; j < arguments[i].length; ++j) { + this.hooks.push(arguments[i][j]); + } + } + } + } + + /** + * Triggers a hook to run all functions + * @example this.content.trigger(args).then(function(){...}); + * @return {Promise} results + */ + trigger(){ + var args = arguments; + var context = this.context; + var promises = []; + + this.hooks.forEach(function(task) { + var executing = task.apply(context, args); + + if(executing && typeof executing["then"] === "function") { + // Task is a function that returns a promise + promises.push(executing); + } else { + // Otherwise Task resolves immediately, add resolved promise with result + promises.push(new Promise((resolve, reject) => { + resolve(executing); + })); + } + }); + + + return Promise.all(promises); + } + + /** + * Triggers a hook to run all functions synchronously + * @example this.content.trigger(args).then(function(){...}); + * @return {Array} results + */ + triggerSync(){ + var args = arguments; + var context = this.context; + var results = []; + + this.hooks.forEach(function(task) { + var executing = task.apply(context, args); + + results.push(executing); + }); + + + return results; + } + + // Adds a function to be run before a hook completes + list(){ + return this.hooks; + } + + clear(){ + return this.hooks = []; + } + } + + function getBoundingClientRect(element) { + if (!element) { + return; + } + let rect; + if (typeof element.getBoundingClientRect !== "undefined") { + rect = element.getBoundingClientRect(); + } else { + let range = document.createRange(); + range.selectNode(element); + rect = range.getBoundingClientRect(); + } + return rect; + } + + function getClientRects(element) { + if (!element) { + return; + } + let rect; + if (typeof element.getClientRects !== "undefined") { + rect = element.getClientRects(); + } else { + let range = document.createRange(); + range.selectNode(element); + rect = range.getClientRects(); + } + return rect; + } + + /** + * Generates a UUID + * based on: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript + * @returns {string} uuid + */ + function UUID() { + var d = new Date().getTime(); + if (typeof performance !== "undefined" && typeof performance.now === "function") { + d += performance.now(); //use high-precision timer if available + } + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16); + }); + } + + function attr(element, attributes) { + for (var i = 0; i < attributes.length; i++) { + if (element.hasAttribute(attributes[i])) { + return element.getAttribute(attributes[i]); + } + } + } + + /* Based on by https://mths.be/cssescape v1.5.1 by @mathias | MIT license + * Allows # and . + */ + function querySelectorEscape(value) { + if (arguments.length == 0) { + throw new TypeError("`CSS.escape` requires an argument."); + } + var string = String(value); + + var length = string.length; + var index = -1; + var codeUnit; + var result = ""; + var firstCodeUnit = string.charCodeAt(0); + while (++index < length) { + codeUnit = string.charCodeAt(index); + + + + // Note: there’s no need to special-case astral symbols, surrogate + // pairs, or lone surrogates. + + // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER + // (U+FFFD). + if (codeUnit == 0x0000) { + result += "\uFFFD"; + continue; + } + + if ( + // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is + // U+007F, […] + (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || + // If the character is the first character and is in the range [0-9] + // (U+0030 to U+0039), […] + (index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || + // If the character is the second character and is in the range [0-9] + // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] + ( + index == 1 && + codeUnit >= 0x0030 && codeUnit <= 0x0039 && + firstCodeUnit == 0x002D + ) + ) { + // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point + result += "\\" + codeUnit.toString(16) + " "; + continue; + } + + if ( + // If the character is the first character and is a `-` (U+002D), and + // there is no second character, […] + index == 0 && + length == 1 && + codeUnit == 0x002D + ) { + result += "\\" + string.charAt(index); + continue; + } + + // support for period character in id + if (codeUnit == 0x002E) { + if (string.charAt(0) == "#") { + result += "\\."; + continue; + } + } + + + // If the character is not handled by one of the above rules and is + // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or + // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to + // U+005A), or [a-z] (U+0061 to U+007A), […] + if ( + codeUnit >= 0x0080 || + codeUnit == 0x002D || + codeUnit == 0x005F || + codeUnit == 35 || // Allow # + codeUnit == 46 || // Allow . + codeUnit >= 0x0030 && codeUnit <= 0x0039 || + codeUnit >= 0x0041 && codeUnit <= 0x005A || + codeUnit >= 0x0061 && codeUnit <= 0x007A + ) { + // the character itself + result += string.charAt(index); + continue; + } + + // Otherwise, the escaped character. + // https://drafts.csswg.org/cssom/#escape-a-character + result += "\\" + string.charAt(index); + + } + return result; + } + + /** + * Creates a new pending promise and provides methods to resolve or reject it. + * From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible + * @returns {object} defered + */ + function defer() { + this.resolve = null; + + this.reject = null; + + this.id = UUID(); + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + Object.freeze(this); + } + + const requestIdleCallback = typeof window !== "undefined" && ("requestIdleCallback" in window ? window.requestIdleCallback : window.requestAnimationFrame); + + function CSSValueToString(obj) { + return obj.value + (obj.unit || ""); + } + + function isElement(node) { + return node && node.nodeType === 1; + } + + function isText(node) { + return node && node.nodeType === 3; + } + + function* walk$2(start, limiter) { + let node = start; + + while (node) { + + yield node; + + if (node.childNodes.length) { + node = node.firstChild; + } else if (node.nextSibling) { + if (limiter && node === limiter) { + node = undefined; + break; + } + node = node.nextSibling; + } else { + while (node) { + node = node.parentNode; + if (limiter && node === limiter) { + node = undefined; + break; + } + if (node && node.nextSibling) { + node = node.nextSibling; + break; + } + + } + } + } + } + + function nodeAfter(node, limiter) { + if (limiter && node === limiter) { + return; + } + let significantNode = nextSignificantNode(node); + if (significantNode) { + return significantNode; + } + if (node.parentNode) { + while ((node = node.parentNode)) { + if (limiter && node === limiter) { + return; + } + significantNode = nextSignificantNode(node); + if (significantNode) { + return significantNode; + } + } + } + } + + function nodeBefore(node, limiter) { + if (limiter && node === limiter) { + return; + } + let significantNode = previousSignificantNode(node); + if (significantNode) { + return significantNode; + } + if (node.parentNode) { + while ((node = node.parentNode)) { + if (limiter && node === limiter) { + return; + } + significantNode = previousSignificantNode(node); + if (significantNode) { + return significantNode; + } + } + } + } + + function elementAfter(node, limiter) { + let after = nodeAfter(node, limiter); + + while (after && after.nodeType !== 1) { + after = nodeAfter(after, limiter); + } + + return after; + } + + function elementBefore(node, limiter) { + let before = nodeBefore(node, limiter); + + while (before && before.nodeType !== 1) { + before = nodeBefore(before, limiter); + } + + return before; + } + + function displayedElementAfter(node, limiter) { + let after = elementAfter(node, limiter); + + while (after && after.dataset.undisplayed) { + after = elementAfter(after, limiter); + } + + return after; + } + + function displayedElementBefore(node, limiter) { + let before = elementBefore(node, limiter); + + while (before && before.dataset.undisplayed) { + before = elementBefore(before, limiter); + } + + return before; + } + + function rebuildAncestors(node) { + let parent, ancestor; + let ancestors = []; + let added = []; + + let fragment = document.createDocumentFragment(); + + // Handle rowspan on table + if (node.nodeName === "TR") { + let previousRow = node.previousElementSibling; + let previousRowDistance = 1; + while (previousRow) { + // previous row has more columns, might indicate a rowspan. + if (previousRow.childElementCount > node.childElementCount) { + const initialColumns = Array.from(node.children); + while (node.firstChild) { + node.firstChild.remove(); + } + let k = 0; + for (let j = 0; j < previousRow.children.length; j++) { + let column = previousRow.children[j]; + if (column.rowSpan && column.rowSpan > previousRowDistance) { + const duplicatedColumn = column.cloneNode(true); + // Adjust rowspan value + duplicatedColumn.rowSpan = column.rowSpan - previousRowDistance; + // Add the column to the row + node.appendChild(duplicatedColumn); + } else { + // Fill the gap with the initial columns (if exists) + const initialColumn = initialColumns[k++]; + // The initial column can be undefined if the newly created table has less columns than the original table + if (initialColumn) { + node.appendChild(initialColumn); + } + } + } + } + previousRow = previousRow.previousElementSibling; + previousRowDistance++; + } + } + + // Gather all ancestors + let element = node; + while(element.parentNode && element.parentNode.nodeType === 1) { + ancestors.unshift(element.parentNode); + element = element.parentNode; + } + + for (var i = 0; i < ancestors.length; i++) { + ancestor = ancestors[i]; + parent = ancestor.cloneNode(false); + + parent.setAttribute("data-split-from", parent.getAttribute("data-ref")); + // ancestor.setAttribute("data-split-to", parent.getAttribute("data-ref")); + + if (parent.hasAttribute("id")) { + let dataID = parent.getAttribute("id"); + parent.setAttribute("data-id", dataID); + parent.removeAttribute("id"); + } + + // This is handled by css :not, but also tidied up here + if (parent.hasAttribute("data-break-before")) { + parent.removeAttribute("data-break-before"); + } + + if (parent.hasAttribute("data-previous-break-after")) { + parent.removeAttribute("data-previous-break-after"); + } + + if (added.length) { + let container = added[added.length-1]; + container.appendChild(parent); + } else { + fragment.appendChild(parent); + } + added.push(parent); + + // rebuild table rows + if (parent.nodeName === "TD" && ancestor.parentElement.contains(ancestor)) { + let td = ancestor; + let prev = parent; + while ((td = td.previousElementSibling)) { + let sib = td.cloneNode(false); + parent.parentElement.insertBefore(sib, prev); + prev = sib; + } + + } + } + + added = undefined; + return fragment; + } + /* + export function split(bound, cutElement, breakAfter) { + let needsRemoval = []; + let index = indexOf(cutElement); + + if (!breakAfter && index === 0) { + return; + } + + if (breakAfter && index === (cutElement.parentNode.children.length - 1)) { + return; + } + + // Create a fragment with rebuilt ancestors + let fragment = rebuildAncestors(cutElement); + + // Clone cut + if (!breakAfter) { + let clone = cutElement.cloneNode(true); + let ref = cutElement.parentNode.getAttribute('data-ref'); + let parent = fragment.querySelector("[data-ref='" + ref + "']"); + parent.appendChild(clone); + needsRemoval.push(cutElement); + } + + // Remove all after cut + let next = nodeAfter(cutElement, bound); + while (next) { + let clone = next.cloneNode(true); + let ref = next.parentNode.getAttribute('data-ref'); + let parent = fragment.querySelector("[data-ref='" + ref + "']"); + parent.appendChild(clone); + needsRemoval.push(next); + next = nodeAfter(next, bound); + } + + // Remove originals + needsRemoval.forEach((node) => { + if (node) { + node.remove(); + } + }); + + // Insert after bounds + bound.parentNode.insertBefore(fragment, bound.nextSibling); + return [bound, bound.nextSibling]; + } + */ + + function needsBreakBefore(node) { + if( typeof node !== "undefined" && + typeof node.dataset !== "undefined" && + typeof node.dataset.breakBefore !== "undefined" && + (node.dataset.breakBefore === "always" || + node.dataset.breakBefore === "page" || + node.dataset.breakBefore === "left" || + node.dataset.breakBefore === "right" || + node.dataset.breakBefore === "recto" || + node.dataset.breakBefore === "verso") + ) { + return true; + } + + return false; + } + + function needsPreviousBreakAfter(node) { + if( typeof node !== "undefined" && + typeof node.dataset !== "undefined" && + typeof node.dataset.previousBreakAfter !== "undefined" && + (node.dataset.previousBreakAfter === "always" || + node.dataset.previousBreakAfter === "page" || + node.dataset.previousBreakAfter === "left" || + node.dataset.previousBreakAfter === "right" || + node.dataset.previousBreakAfter === "recto" || + node.dataset.previousBreakAfter === "verso") + ) { + return true; + } + + return false; + } + + function needsPageBreak(node, previousSignificantNode) { + if (typeof node === "undefined" || !previousSignificantNode || isIgnorable(node)) { + return false; + } + if (node.dataset && node.dataset.undisplayed) { + return false; + } + let previousSignificantNodePage = previousSignificantNode.dataset ? previousSignificantNode.dataset.page : undefined; + if (typeof previousSignificantNodePage === "undefined") { + const nodeWithNamedPage = getNodeWithNamedPage(previousSignificantNode); + if (nodeWithNamedPage) { + previousSignificantNodePage = nodeWithNamedPage.dataset.page; + } + } + let currentNodePage = node.dataset ? node.dataset.page : undefined; + if (typeof currentNodePage === "undefined") { + const nodeWithNamedPage = getNodeWithNamedPage(node, previousSignificantNode); + if (nodeWithNamedPage) { + currentNodePage = nodeWithNamedPage.dataset.page; + } + } + return currentNodePage !== previousSignificantNodePage; + } + + function *words(node) { + let currentText = node.nodeValue; + let max = currentText.length; + let currentOffset = 0; + let currentLetter; + + let range; + const significantWhitespaces = node.parentElement && node.parentElement.nodeName === "PRE"; + + while (currentOffset < max) { + currentLetter = currentText[currentOffset]; + if (/^[\S\u202F\u00A0]$/.test(currentLetter) || significantWhitespaces) { + if (!range) { + range = document.createRange(); + range.setStart(node, currentOffset); + } + } else { + if (range) { + range.setEnd(node, currentOffset); + yield range; + range = undefined; + } + } + + currentOffset += 1; + } + + if (range) { + range.setEnd(node, currentOffset); + yield range; + } + } + + function *letters(wordRange) { + let currentText = wordRange.startContainer; + let max = currentText.length; + let currentOffset = wordRange.startOffset; + // let currentLetter; + + let range; + + while(currentOffset < max) { + // currentLetter = currentText[currentOffset]; + range = document.createRange(); + range.setStart(currentText, currentOffset); + range.setEnd(currentText, currentOffset+1); + + yield range; + + currentOffset += 1; + } + } + + function isContainer(node) { + let container; + + if (typeof node.tagName === "undefined") { + return true; + } + + if (node.style && node.style.display === "none") { + return false; + } + + switch (node.tagName) { + // Inline + case "A": + case "ABBR": + case "ACRONYM": + case "B": + case "BDO": + case "BIG": + case "BR": + case "BUTTON": + case "CITE": + case "CODE": + case "DFN": + case "EM": + case "I": + case "IMG": + case "INPUT": + case "KBD": + case "LABEL": + case "MAP": + case "OBJECT": + case "Q": + case "SAMP": + case "SCRIPT": + case "SELECT": + case "SMALL": + case "SPAN": + case "STRONG": + case "SUB": + case "SUP": + case "TEXTAREA": + case "TIME": + case "TT": + case "VAR": + case "P": + case "H1": + case "H2": + case "H3": + case "H4": + case "H5": + case "H6": + case "FIGCAPTION": + case "BLOCKQUOTE": + case "PRE": + case "LI": + case "TD": + case "DT": + case "DD": + case "VIDEO": + case "CANVAS": + container = false; + break; + default: + container = true; + } + + return container; + } + + function cloneNode(n, deep=false) { + return n.cloneNode(deep); + } + + function findElement(node, doc, forceQuery) { + const ref = node.getAttribute("data-ref"); + return findRef(ref, doc, forceQuery); + } + + function findRef(ref, doc, forceQuery) { + if (!forceQuery && doc.indexOfRefs && doc.indexOfRefs[ref]) { + return doc.indexOfRefs[ref]; + } else { + return doc.querySelector(`[data-ref='${ref}']`); + } + } + + function validNode(node) { + if (isText(node)) { + return true; + } + + if (isElement(node) && node.dataset.ref) { + return true; + } + + return false; + } + + function prevValidNode(node) { + while (!validNode(node)) { + if (node.previousSibling) { + node = node.previousSibling; + } else { + node = node.parentNode; + } + + if (!node) { + break; + } + } + + return node; + } + + + function indexOf$2(node) { + let parent = node.parentNode; + if (!parent) { + return 0; + } + return Array.prototype.indexOf.call(parent.childNodes, node); + } + + function child(node, index) { + return node.childNodes[index]; + } + + function hasContent(node) { + if (isElement(node)) { + return true; + } else if (isText(node) && + node.textContent.trim().length) { + return true; + } + return false; + } + + function indexOfTextNode(node, parent) { + if (!isText(node)) { + return -1; + } + let nodeTextContent = node.textContent; + let child; + let index = -1; + for (var i = 0; i < parent.childNodes.length; i++) { + child = parent.childNodes[i]; + if (child.nodeType === 3) { + let text = parent.childNodes[i].textContent; + if (text.includes(nodeTextContent)) { + index = i; + break; + } + } + } + + return index; + } + + + /** + * Throughout, whitespace is defined as one of the characters + * "\t" TAB \u0009 + * "\n" LF \u000A + * "\r" CR \u000D + * " " SPC \u0020 + * + * This does not use Javascript's "\s" because that includes non-breaking + * spaces (and also some other characters). + */ + + /** + * Determine if a node should be ignored by the iterator functions. + * taken from https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace#Whitespace_helper_functions + * + * @param {Node} node An object implementing the DOM1 |Node| interface. + * @return {boolean} true if the node is: + * 1) A |Text| node that is all whitespace + * 2) A |Comment| node + * and otherwise false. + */ + function isIgnorable(node) { + return (node.nodeType === 8) || // A comment node + ((node.nodeType === 3) && isAllWhitespace(node)); // a text node, all whitespace + } + + /** + * Determine whether a node's text content is entirely whitespace. + * + * @param {Node} node A node implementing the |CharacterData| interface (i.e., a |Text|, |Comment|, or |CDATASection| node + * @return {boolean} true if all of the text content of |nod| is whitespace, otherwise false. + */ + function isAllWhitespace(node) { + return !(/[^\t\n\r ]/.test(node.textContent)); + } + + /** + * Version of |previousSibling| that skips nodes that are entirely + * whitespace or comments. (Normally |previousSibling| is a property + * of all DOM nodes that gives the sibling node, the node that is + * a child of the same parent, that occurs immediately before the + * reference node.) + * + * @param {ChildNode} sib The reference node. + * @return {Node|null} Either: + * 1) The closest previous sibling to |sib| that is not ignorable according to |is_ignorable|, or + * 2) null if no such node exists. + */ + function previousSignificantNode(sib) { + while ((sib = sib.previousSibling)) { + if (!isIgnorable(sib)) return sib; + } + return null; + } + + function getNodeWithNamedPage(node, limiter) { + if (node && node.dataset && node.dataset.page) { + return node; + } + if (node.parentNode) { + while ((node = node.parentNode)) { + if (limiter && node === limiter) { + return; + } + if (node.dataset && node.dataset.page) { + return node; + } + } + } + return null; + } + + function breakInsideAvoidParentNode(node) { + while ((node = node.parentNode)) { + if (node && node.dataset && node.dataset.breakInside === "avoid") { + return node; + } + } + return null; + } + + /** + * Find a parent with a given node name. + * @param {Node} node - initial Node + * @param {string} nodeName - node name (eg. "TD", "TABLE", "STRONG"...) + * @param {Node} limiter - go up to the parent until there's no more parent or the current node is equals to the limiter + * @returns {Node|undefined} - Either: + * 1) The closest parent for a the given node name, or + * 2) undefined if no such node exists. + */ + function parentOf(node, nodeName, limiter) { + if (limiter && node === limiter) { + return; + } + if (node.parentNode) { + while ((node = node.parentNode)) { + if (limiter && node === limiter) { + return; + } + if (node.nodeName === nodeName) { + return node; + } + } + } + } + + /** + * Version of |nextSibling| that skips nodes that are entirely + * whitespace or comments. + * + * @param {ChildNode} sib The reference node. + * @return {Node|null} Either: + * 1) The closest next sibling to |sib| that is not ignorable according to |is_ignorable|, or + * 2) null if no such node exists. + */ + function nextSignificantNode(sib) { + while ((sib = sib.nextSibling)) { + if (!isIgnorable(sib)) return sib; + } + return null; + } + + function filterTree(content, func, what) { + const treeWalker = document.createTreeWalker( + content || this.dom, + what || NodeFilter.SHOW_ALL, + func ? { acceptNode: func } : null, + false + ); + + let node; + let current; + node = treeWalker.nextNode(); + while(node) { + current = node; + node = treeWalker.nextNode(); + current.parentNode.removeChild(current); + } + } + + /** + * BreakToken + * @class + */ + class BreakToken { + + constructor(node, offset) { + this.node = node; + this.offset = offset; + } + + equals(otherBreakToken) { + if (!otherBreakToken) { + return false; + } + if (this["node"] && otherBreakToken["node"] && + this["node"] !== otherBreakToken["node"]) { + return false; + } + if (this["offset"] && otherBreakToken["offset"] && + this["offset"] !== otherBreakToken["offset"]) { + return false; + } + return true; + } + + toJSON(hash) { + let node; + let index = 0; + if (!this.node) { + return {}; + } + if (isElement(this.node) && this.node.dataset.ref) { + node = this.node.dataset.ref; + } else if (hash) { + node = this.node.parentElement.dataset.ref; + } + + if (this.node.parentElement) { + const children = Array.from(this.node.parentElement.childNodes); + index = children.indexOf(this.node); + } + + return JSON.stringify({ + "node": node, + "index" : index, + "offset": this.offset + }); + } + + } + + /** + * Render result. + * @class + */ + class RenderResult { + + constructor(breakToken, error) { + this.breakToken = breakToken; + this.error = error; + } + } + + class OverflowContentError extends Error { + constructor(message, items) { + super(message); + this.items = items; + } + } + + const MAX_CHARS_PER_BREAK = 1500; + + /** + * Layout + * @class + */ + class Layout { + + constructor(element, hooks, options) { + this.element = element; + + this.bounds = this.element.getBoundingClientRect(); + this.parentBounds = this.element.offsetParent.getBoundingClientRect(); + let gap = parseFloat(window.getComputedStyle(this.element).columnGap); + + if (gap) { + let leftMargin = this.bounds.left - this.parentBounds.left; + this.gap = gap - leftMargin; + } else { + this.gap = 0; + } + + if (hooks) { + this.hooks = hooks; + } else { + this.hooks = {}; + this.hooks.onPageLayout = new Hook(); + this.hooks.layout = new Hook(); + this.hooks.renderNode = new Hook(); + this.hooks.layoutNode = new Hook(); + this.hooks.beforeOverflow = new Hook(); + this.hooks.onOverflow = new Hook(); + this.hooks.afterOverflowRemoved = new Hook(); + this.hooks.onBreakToken = new Hook(); + this.hooks.beforeRenderResult = new Hook(); + } + + this.settings = options || {}; + + this.maxChars = this.settings.maxChars || MAX_CHARS_PER_BREAK; + this.forceRenderBreak = false; + } + + async renderTo(wrapper, source, breakToken, bounds = this.bounds) { + let start = this.getStart(source, breakToken); + let walker = walk$2(start, source); + + let node; + let prevNode; + let done; + let next; + + let hasRenderedContent = false; + let newBreakToken; + + let length = 0; + + let prevBreakToken = breakToken || new BreakToken(start); + + this.hooks && this.hooks.onPageLayout.trigger(wrapper, prevBreakToken, this); + + while (!done && !newBreakToken) { + next = walker.next(); + prevNode = node; + node = next.value; + done = next.done; + + if (!node) { + this.hooks && this.hooks.layout.trigger(wrapper, this); + + let imgs = wrapper.querySelectorAll("img"); + if (imgs.length) { + await this.waitForImages(imgs); + } + + newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); + + if (newBreakToken && newBreakToken.equals(prevBreakToken)) { + console.warn("Unable to layout item: ", prevNode); + this.hooks && this.hooks.beforeRenderResult.trigger(undefined, wrapper, this); + return new RenderResult(undefined, new OverflowContentError("Unable to layout item", [prevNode])); + } + + this.rebuildTableFromBreakToken(newBreakToken, wrapper); + + this.hooks && this.hooks.beforeRenderResult.trigger(newBreakToken, wrapper, this); + return new RenderResult(newBreakToken); + } + + this.hooks && this.hooks.layoutNode.trigger(node); + + // Check if the rendered element has a break set + if (hasRenderedContent && this.shouldBreak(node, start)) { + this.hooks && this.hooks.layout.trigger(wrapper, this); + + let imgs = wrapper.querySelectorAll("img"); + if (imgs.length) { + await this.waitForImages(imgs); + } + + newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); + + if (!newBreakToken) { + newBreakToken = this.breakAt(node); + } else { + this.rebuildTableFromBreakToken(newBreakToken, wrapper); + } + + if (newBreakToken && newBreakToken.equals(prevBreakToken)) { + console.warn("Unable to layout item: ", node); + let after = newBreakToken.node && nodeAfter(newBreakToken.node); + if (after) { + newBreakToken = new BreakToken(after); + } else { + return new RenderResult(undefined, new OverflowContentError("Unable to layout item", [node])); + } + } + + length = 0; + + break; + } + + if (node.dataset && node.dataset.page) { + let named = node.dataset.page; + let page = this.element.closest(".pagedjs_page"); + page.classList.add("pagedjs_named_page"); + page.classList.add("pagedjs_" + named + "_page"); + + if (!node.dataset.splitFrom) { + page.classList.add("pagedjs_" + named + "_first_page"); + } + } + + // Should the Node be a shallow or deep clone + let shallow = isContainer(node); + + let rendered = this.append(node, wrapper, breakToken, shallow); + + length += rendered.textContent.length; + + // Check if layout has content yet + if (!hasRenderedContent) { + hasRenderedContent = hasContent(node); + } + + // Skip to the next node if a deep clone was rendered + if (!shallow) { + walker = walk$2(nodeAfter(node, source), source); + } + + if (this.forceRenderBreak) { + this.hooks && this.hooks.layout.trigger(wrapper, this); + + newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); + + if (!newBreakToken) { + newBreakToken = this.breakAt(node); + } else { + this.rebuildTableFromBreakToken(newBreakToken, wrapper); + } + + length = 0; + this.forceRenderBreak = false; + + break; + } + + // Only check x characters + if (length >= this.maxChars) { + + this.hooks && this.hooks.layout.trigger(wrapper, this); + + let imgs = wrapper.querySelectorAll("img"); + if (imgs.length) { + await this.waitForImages(imgs); + } + + newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); + + if (newBreakToken) { + length = 0; + this.rebuildTableFromBreakToken(newBreakToken, wrapper); + } + + if (newBreakToken && newBreakToken.equals(prevBreakToken)) { + console.warn("Unable to layout item: ", node); + let after = newBreakToken.node && nodeAfter(newBreakToken.node); + if (after) { + newBreakToken = new BreakToken(after); + } else { + this.hooks && this.hooks.beforeRenderResult.trigger(undefined, wrapper, this); + return new RenderResult(undefined, new OverflowContentError("Unable to layout item", [node])); + } + } + } + + } + + this.hooks && this.hooks.beforeRenderResult.trigger(newBreakToken, wrapper, this); + return new RenderResult(newBreakToken); + } + + breakAt(node, offset = 0) { + let newBreakToken = new BreakToken( + node, + offset + ); + let breakHooks = this.hooks.onBreakToken.triggerSync(newBreakToken, undefined, node, this); + breakHooks.forEach((newToken) => { + if (typeof newToken != "undefined") { + newBreakToken = newToken; + } + }); + + return newBreakToken; + } + + shouldBreak(node, limiter) { + let previousNode = nodeBefore(node, limiter); + let parentNode = node.parentNode; + let parentBreakBefore = needsBreakBefore(node) && parentNode && !previousNode && needsBreakBefore(parentNode); + let doubleBreakBefore; + + if (parentBreakBefore) { + doubleBreakBefore = node.dataset.breakBefore === parentNode.dataset.breakBefore; + } + + return !doubleBreakBefore && needsBreakBefore(node) || needsPreviousBreakAfter(node) || needsPageBreak(node, previousNode); + } + + forceBreak() { + this.forceRenderBreak = true; + } + + getStart(source, breakToken) { + let start; + let node = breakToken && breakToken.node; + + if (node) { + start = node; + } else { + start = source.firstChild; + } + + return start; + } + + append(node, dest, breakToken, shallow = true, rebuild = true) { + + let clone = cloneNode(node, !shallow); + + if (node.parentNode && isElement(node.parentNode)) { + let parent = findElement(node.parentNode, dest); + // Rebuild chain + if (parent) { + parent.appendChild(clone); + } else if (rebuild) { + let fragment = rebuildAncestors(node); + parent = findElement(node.parentNode, fragment); + if (!parent) { + dest.appendChild(clone); + } else if (breakToken && isText(breakToken.node) && breakToken.offset > 0) { + clone.textContent = clone.textContent.substring(breakToken.offset); + parent.appendChild(clone); + } else { + parent.appendChild(clone); + } + + dest.appendChild(fragment); + } else { + dest.appendChild(clone); + } + + + } else { + dest.appendChild(clone); + } + + if (clone.dataset && clone.dataset.ref) { + if (!dest.indexOfRefs) { + dest.indexOfRefs = {}; + } + dest.indexOfRefs[clone.dataset.ref] = clone; + } + + let nodeHooks = this.hooks.renderNode.triggerSync(clone, node, this); + nodeHooks.forEach((newNode) => { + if (typeof newNode != "undefined") { + clone = newNode; + } + }); + + return clone; + } + + rebuildTableFromBreakToken(breakToken, dest) { + if (!breakToken || !breakToken.node) { + return; + } + let node = breakToken.node; + let td = isElement(node) ? node.closest("td") : node.parentElement.closest("td"); + if (td) { + let rendered = findElement(td, dest, true); + if (!rendered) { + return; + } + while ((td = td.nextElementSibling)) { + this.append(td, dest, null, true); + } + } + } + + async waitForImages(imgs) { + let results = Array.from(imgs).map(async (img) => { + return this.awaitImageLoaded(img); + }); + await Promise.all(results); + } + + async awaitImageLoaded(image) { + return new Promise(resolve => { + if (image.complete !== true) { + image.onload = function () { + let {width, height} = window.getComputedStyle(image); + resolve(width, height); + }; + image.onerror = function (e) { + let {width, height} = window.getComputedStyle(image); + resolve(width, height, e); + }; + } else { + let {width, height} = window.getComputedStyle(image); + resolve(width, height); + } + }); + } + + avoidBreakInside(node, limiter) { + let breakNode; + + if (node === limiter) { + return; + } + + while (node.parentNode) { + node = node.parentNode; + + if (node === limiter) { + break; + } + + if (window.getComputedStyle(node)["break-inside"] === "avoid") { + breakNode = node; + break; + } + + } + return breakNode; + } + + createBreakToken(overflow, rendered, source) { + let container = overflow.startContainer; + let offset = overflow.startOffset; + let node, renderedNode, parent, index, temp; + + if (isElement(container)) { + temp = child(container, offset); + + if (isElement(temp)) { + renderedNode = findElement(temp, rendered); + + if (!renderedNode) { + // Find closest element with data-ref + let prevNode = prevValidNode(temp); + if (!isElement(prevNode)) { + prevNode = prevNode.parentElement; + } + renderedNode = findElement(prevNode, rendered); + // Check if temp is the last rendered node at its level. + if (!temp.nextSibling) { + // We need to ensure that the previous sibling of temp is fully rendered. + const renderedNodeFromSource = findElement(renderedNode, source); + const walker = document.createTreeWalker(renderedNodeFromSource, NodeFilter.SHOW_ELEMENT); + const lastChildOfRenderedNodeFromSource = walker.lastChild(); + const lastChildOfRenderedNodeMatchingFromRendered = findElement(lastChildOfRenderedNodeFromSource, rendered); + // Check if we found that the last child in source + if (!lastChildOfRenderedNodeMatchingFromRendered) { + // Pending content to be rendered before virtual break token + return; + } + // Otherwise we will return a break token as per below + } + // renderedNode is actually the last unbroken box that does not overflow. + // Break Token is therefore the next sibling of renderedNode within source node. + node = findElement(renderedNode, source).nextSibling; + offset = 0; + } else { + node = findElement(renderedNode, source); + offset = 0; + } + } else { + renderedNode = findElement(container, rendered); + + if (!renderedNode) { + renderedNode = findElement(prevValidNode(container), rendered); + } + + parent = findElement(renderedNode, source); + index = indexOfTextNode(temp, parent); + // No seperatation for the first textNode of an element + if(index === 0) { + node = parent; + offset = 0; + } else { + node = child(parent, index); + offset = 0; + } + } + } else { + renderedNode = findElement(container.parentNode, rendered); + + if (!renderedNode) { + renderedNode = findElement(prevValidNode(container.parentNode), rendered); + } + + parent = findElement(renderedNode, source); + index = indexOfTextNode(container, parent); + + if (index === -1) { + return; + } + + node = child(parent, index); + + offset += node.textContent.indexOf(container.textContent); + } + + if (!node) { + return; + } + + return new BreakToken( + node, + offset + ); + + } + + findBreakToken(rendered, source, bounds = this.bounds, prevBreakToken, extract = true) { + let overflow = this.findOverflow(rendered, bounds); + let breakToken, breakLetter; + + let overflowHooks = this.hooks.onOverflow.triggerSync(overflow, rendered, bounds, this); + overflowHooks.forEach((newOverflow) => { + if (typeof newOverflow != "undefined") { + overflow = newOverflow; + } + }); + + if (overflow) { + breakToken = this.createBreakToken(overflow, rendered, source); + // breakToken is nullable + let breakHooks = this.hooks.onBreakToken.triggerSync(breakToken, overflow, rendered, this); + breakHooks.forEach((newToken) => { + if (typeof newToken != "undefined") { + breakToken = newToken; + } + }); + + // Stop removal if we are in a loop + if (breakToken && breakToken.equals(prevBreakToken)) { + return breakToken; + } + + if (breakToken && breakToken["node"] && breakToken["offset"] && breakToken["node"].textContent) { + breakLetter = breakToken["node"].textContent.charAt(breakToken["offset"]); + } else { + breakLetter = undefined; + } + + if (breakToken && breakToken.node && extract) { + let removed = this.removeOverflow(overflow, breakLetter); + this.hooks && this.hooks.afterOverflowRemoved.trigger(removed, rendered, this); + } + + } + return breakToken; + } + + hasOverflow(element, bounds = this.bounds) { + let constrainingElement = element && element.parentNode; // this gets the element, instead of the wrapper for the width workaround + let {width, height} = element.getBoundingClientRect(); + let scrollWidth = constrainingElement ? constrainingElement.scrollWidth : 0; + let scrollHeight = constrainingElement ? constrainingElement.scrollHeight : 0; + return Math.max(Math.floor(width), scrollWidth) > Math.round(bounds.width) || + Math.max(Math.floor(height), scrollHeight) > Math.round(bounds.height); + } + + findOverflow(rendered, bounds = this.bounds, gap = this.gap) { + if (!this.hasOverflow(rendered, bounds)) return; + + let start = Math.floor(bounds.left); + let end = Math.round(bounds.right + gap); + let vStart = Math.round(bounds.top); + let vEnd = Math.round(bounds.bottom); + let range; + + let walker = walk$2(rendered.firstChild, rendered); + + // Find Start + let next, done, node, offset, skip, breakAvoid, prev, br; + while (!done) { + next = walker.next(); + done = next.done; + node = next.value; + skip = false; + breakAvoid = false; + prev = undefined; + br = undefined; + + if (node) { + let pos = getBoundingClientRect(node); + let left = Math.round(pos.left); + let right = Math.floor(pos.right); + let top = Math.round(pos.top); + let bottom = Math.floor(pos.bottom); + + if (!range && (left >= end || top >= vEnd)) { + // Check if it is a float + let isFloat = false; + + // Check if the node is inside a break-inside: avoid table cell + const insideTableCell = parentOf(node, "TD", rendered); + if (insideTableCell && window.getComputedStyle(insideTableCell)["break-inside"] === "avoid") { + // breaking inside a table cell produces unexpected result, as a workaround, we forcibly avoid break inside in a cell. + // But we take the whole row, not just the cell that is causing the break. + prev = insideTableCell.parentElement; + } else if (isElement(node)) { + let styles = window.getComputedStyle(node); + isFloat = styles.getPropertyValue("float") !== "none"; + skip = styles.getPropertyValue("break-inside") === "avoid"; + breakAvoid = node.dataset.breakBefore === "avoid" || node.dataset.previousBreakAfter === "avoid"; + prev = breakAvoid && nodeBefore(node, rendered); + br = node.tagName === "BR" || node.tagName === "WBR"; + } + + let tableRow; + if (node.nodeName === "TR") { + tableRow = node; + } else { + tableRow = parentOf(node, "TR", rendered); + } + if (tableRow) { + // honor break-inside="avoid" in parent tbody/thead + let container = tableRow.parentElement; + if (["TBODY", "THEAD"].includes(container.nodeName)) { + let styles = window.getComputedStyle(container); + if (styles.getPropertyValue("break-inside") === "avoid") prev = container; + } + + // Check if the node is inside a row with a rowspan + const table = parentOf(tableRow, "TABLE", rendered); + const rowspan = table.querySelector("[colspan]"); + if (table && rowspan) { + let columnCount = 0; + for (const cell of Array.from(table.rows[0].cells)) { + columnCount += parseInt(cell.getAttribute("colspan") || "1"); + } + if (tableRow.cells.length !== columnCount) { + let previousRow = tableRow.previousElementSibling; + let previousRowColumnCount; + while (previousRow !== null) { + previousRowColumnCount = 0; + for (const cell of Array.from(previousRow.cells)) { + previousRowColumnCount += parseInt(cell.getAttribute("colspan") || "1"); + } + if (previousRowColumnCount === columnCount) { + break; + } + previousRow = previousRow.previousElementSibling; + } + if (previousRowColumnCount === columnCount) { + prev = previousRow; + } + } + } + } + + if (prev) { + range = document.createRange(); + range.selectNode(prev); + break; + } + + if (!br && !isFloat && isElement(node)) { + range = document.createRange(); + range.selectNode(node); + break; + } + + if (isText(node) && node.textContent.trim().length) { + range = document.createRange(); + range.selectNode(node); + break; + } + + } + + if (!range && isText(node) && + node.textContent.trim().length && + !breakInsideAvoidParentNode(node.parentNode)) { + + let rects = getClientRects(node); + let rect; + left = 0; + top = 0; + for (var i = 0; i != rects.length; i++) { + rect = rects[i]; + if (rect.width > 0 && (!left || rect.left > left)) { + left = rect.left; + } + if (rect.height > 0 && (!top || rect.top > top)) { + top = rect.top; + } + } + + if (left >= end || top >= vEnd) { + range = document.createRange(); + offset = this.textBreak(node, start, end, vStart, vEnd); + if (!offset) { + range = undefined; + } else { + range.setStart(node, offset); + } + break; + } + } + + // Skip children + if (skip || (right <= end && bottom <= vEnd)) { + next = nodeAfter(node, rendered); + if (next) { + walker = walk$2(next, rendered); + } + + } + + } + } + + // Find End + if (range) { + range.setEndAfter(rendered.lastChild); + return range; + } + + } + + findEndToken(rendered, source) { + if (rendered.childNodes.length === 0) { + return; + } + + let lastChild = rendered.lastChild; + + let lastNodeIndex; + while (lastChild && lastChild.lastChild) { + if (!validNode(lastChild)) { + // Only get elements with refs + lastChild = lastChild.previousSibling; + } else if (!validNode(lastChild.lastChild)) { + // Deal with invalid dom items + lastChild = prevValidNode(lastChild.lastChild); + break; + } else { + lastChild = lastChild.lastChild; + } + } + + if (isText(lastChild)) { + + if (lastChild.parentNode.dataset.ref) { + lastNodeIndex = indexOf$2(lastChild); + lastChild = lastChild.parentNode; + } else { + lastChild = lastChild.previousSibling; + } + } + + let original = findElement(lastChild, source); + + if (lastNodeIndex) { + original = original.childNodes[lastNodeIndex]; + } + + let after = nodeAfter(original); + + return this.breakAt(after); + } + + textBreak(node, start, end, vStart, vEnd) { + let wordwalker = words(node); + let left = 0; + let right = 0; + let top = 0; + let bottom = 0; + let word, next, done, pos; + let offset; + while (!done) { + next = wordwalker.next(); + word = next.value; + done = next.done; + + if (!word) { + break; + } + + pos = getBoundingClientRect(word); + + left = Math.floor(pos.left); + right = Math.floor(pos.right); + top = Math.floor(pos.top); + bottom = Math.floor(pos.bottom); + + if (left >= end || top >= vEnd) { + offset = word.startOffset; + break; + } + + if (right > end || bottom > vEnd) { + let letterwalker = letters(word); + let letter, nextLetter, doneLetter; + + while (!doneLetter) { + nextLetter = letterwalker.next(); + letter = nextLetter.value; + doneLetter = nextLetter.done; + + if (!letter) { + break; + } + + pos = getBoundingClientRect(letter); + left = Math.floor(pos.left); + top = Math.floor(pos.top); + + if (left >= end || top >= vEnd) { + offset = letter.startOffset; + done = true; + + break; + } + } + } + + } + + return offset; + } + + removeOverflow(overflow, breakLetter) { + let {startContainer} = overflow; + let extracted = overflow.extractContents(); + + this.hyphenateAtBreak(startContainer, breakLetter); + + return extracted; + } + + hyphenateAtBreak(startContainer, breakLetter) { + if (isText(startContainer)) { + let startText = startContainer.textContent; + let prevLetter = startText[startText.length - 1]; + + // Add a hyphen if previous character is a letter or soft hyphen + if ( + (breakLetter && /^\w|\u00AD$/.test(prevLetter) && /^\w|\u00AD$/.test(breakLetter)) || + (!breakLetter && /^\w|\u00AD$/.test(prevLetter)) + ) { + startContainer.parentNode.classList.add("pagedjs_hyphen"); + startContainer.textContent += this.settings.hyphenGlyph || "\u2011"; + } + } + } + + equalTokens(a, b) { + if (!a || !b) { + return false; + } + if (a["node"] && b["node"] && a["node"] !== b["node"]) { + return false; + } + if (a["offset"] && b["offset"] && a["offset"] !== b["offset"]) { + return false; + } + return true; + } + } + + EventEmitter(Layout.prototype); + + /** + * Render a page + * @class + */ + class Page { + constructor(pagesArea, pageTemplate, blank, hooks, options) { + this.pagesArea = pagesArea; + this.pageTemplate = pageTemplate; + this.blank = blank; + + this.width = undefined; + this.height = undefined; + + this.hooks = hooks; + + this.settings = options || {}; + + // this.element = this.create(this.pageTemplate); + } + + create(template, after) { + //let documentFragment = document.createRange().createContextualFragment( TEMPLATE ); + //let page = documentFragment.children[0]; + let clone = document.importNode(this.pageTemplate.content, true); + + let page, index; + if (after) { + this.pagesArea.insertBefore(clone, after.nextElementSibling); + index = Array.prototype.indexOf.call(this.pagesArea.children, after.nextElementSibling); + page = this.pagesArea.children[index]; + } else { + this.pagesArea.appendChild(clone); + page = this.pagesArea.lastChild; + } + + let pagebox = page.querySelector(".pagedjs_pagebox"); + let area = page.querySelector(".pagedjs_page_content"); + let footnotesArea = page.querySelector(".pagedjs_footnote_area"); + + + let size = area.getBoundingClientRect(); + + + area.style.columnWidth = Math.round(size.width) + "px"; + area.style.columnGap = "calc(var(--pagedjs-margin-right) + var(--pagedjs-margin-left) + var(--pagedjs-bleed-right) + var(--pagedjs-bleed-left) + var(--pagedjs-column-gap-offset))"; + // area.style.overflow = "scroll"; + + this.width = Math.round(size.width); + this.height = Math.round(size.height); + + this.element = page; + this.pagebox = pagebox; + this.area = area; + this.footnotesArea = footnotesArea; + + return page; + } + + createWrapper() { + let wrapper = document.createElement("div"); + + this.area.appendChild(wrapper); + + this.wrapper = wrapper; + + return wrapper; + } + + index(pgnum) { + this.position = pgnum; + + let page = this.element; + // let pagebox = this.pagebox; + + let index = pgnum + 1; + + let id = `page-${index}`; + + this.id = id; + + // page.dataset.pageNumber = index; + + page.dataset.pageNumber = index; + page.setAttribute("id", id); + + if (this.name) { + page.classList.add("pagedjs_" + this.name + "_page"); + } + + if (this.blank) { + page.classList.add("pagedjs_blank_page"); + } + + if (pgnum === 0) { + page.classList.add("pagedjs_first_page"); + } + + if (pgnum % 2 !== 1) { + page.classList.remove("pagedjs_left_page"); + page.classList.add("pagedjs_right_page"); + } else { + page.classList.remove("pagedjs_right_page"); + page.classList.add("pagedjs_left_page"); + } + } + + /* + size(width, height) { + if (width === this.width && height === this.height) { + return; + } + this.width = width; + this.height = height; + + this.element.style.width = Math.round(width) + "px"; + this.element.style.height = Math.round(height) + "px"; + this.element.style.columnWidth = Math.round(width) + "px"; + } + */ + + async layout(contents, breakToken, maxChars) { + + this.clear(); + + this.startToken = breakToken; + + let settings = this.settings; + if (!settings.maxChars && maxChars) { + settings.maxChars = maxChars; + } + + this.layoutMethod = new Layout(this.area, this.hooks, settings); + + let renderResult = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken); + let newBreakToken = renderResult.breakToken; + + this.addListeners(contents); + + this.endToken = newBreakToken; + + return newBreakToken; + } + + async append(contents, breakToken) { + + if (!this.layoutMethod) { + return this.layout(contents, breakToken); + } + + let renderResult = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken); + let newBreakToken = renderResult.breakToken; + + this.endToken = newBreakToken; + + return newBreakToken; + } + + getByParent(ref, entries) { + let e; + for (var i = 0; i < entries.length; i++) { + e = entries[i]; + if (e.dataset.ref === ref) { + return e; + } + } + } + + onOverflow(func) { + this._onOverflow = func; + } + + onUnderflow(func) { + this._onUnderflow = func; + } + + clear() { + this.removeListeners(); + this.wrapper && this.wrapper.remove(); + this.createWrapper(); + } + + addListeners(contents) { + if (typeof ResizeObserver !== "undefined") { + this.addResizeObserver(contents); + } else { + this._checkOverflowAfterResize = this.checkOverflowAfterResize.bind(this, contents); + this.element.addEventListener("overflow", this._checkOverflowAfterResize, false); + this.element.addEventListener("underflow", this._checkOverflowAfterResize, false); + } + // TODO: fall back to mutation observer? + + this._onScroll = function () { + if (this.listening) { + this.element.scrollLeft = 0; + } + }.bind(this); + + // Keep scroll left from changing + this.element.addEventListener("scroll", this._onScroll); + + this.listening = true; + + return true; + } + + removeListeners() { + this.listening = false; + + if (typeof ResizeObserver !== "undefined" && this.ro) { + this.ro.disconnect(); + } else if (this.element) { + this.element.removeEventListener("overflow", this._checkOverflowAfterResize, false); + this.element.removeEventListener("underflow", this._checkOverflowAfterResize, false); + } + + this.element && this.element.removeEventListener("scroll", this._onScroll); + + } + + addResizeObserver(contents) { + let wrapper = this.wrapper; + let prevHeight = wrapper.getBoundingClientRect().height; + this.ro = new ResizeObserver(entries => { + + if (!this.listening) { + return; + } + requestAnimationFrame(() => { + for (let entry of entries) { + const cr = entry.contentRect; + + if (cr.height > prevHeight) { + this.checkOverflowAfterResize(contents); + prevHeight = wrapper.getBoundingClientRect().height; + } else if (cr.height < prevHeight) { // TODO: calc line height && (prevHeight - cr.height) >= 22 + this.checkUnderflowAfterResize(contents); + prevHeight = cr.height; + } + } + }); + }); + + this.ro.observe(wrapper); + } + + checkOverflowAfterResize(contents) { + if (!this.listening || !this.layoutMethod) { + return; + } + + let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents, this.startToken); + + if (newBreakToken) { + this.endToken = newBreakToken; + this._onOverflow && this._onOverflow(newBreakToken); + } + } + + checkUnderflowAfterResize(contents) { + if (!this.listening || !this.layoutMethod) { + return; + } + + let endToken = this.layoutMethod.findEndToken(this.wrapper, contents); + + if (endToken) { + this._onUnderflow && this._onUnderflow(endToken); + } + } + + + destroy() { + this.removeListeners(); + + this.element.remove(); + + this.element = undefined; + this.wrapper = undefined; + } + } + + EventEmitter(Page.prototype); + + /** + * Render a flow of text offscreen + * @class + */ + class ContentParser { + + constructor(content, cb) { + if (content && content.nodeType) { + // handle dom + this.dom = this.add(content); + } else if (typeof content === "string") { + this.dom = this.parse(content); + } + + return this.dom; + } + + parse(markup, mime) { + let range = document.createRange(); + let fragment = range.createContextualFragment(markup); + + this.addRefs(fragment); + + return fragment; + } + + add(contents) { + // let fragment = document.createDocumentFragment(); + // + // let children = [...contents.childNodes]; + // for (let child of children) { + // let clone = child.cloneNode(true); + // fragment.appendChild(clone); + // } + + this.addRefs(contents); + + return contents; + } + + addRefs(content) { + var treeWalker = document.createTreeWalker( + content, + NodeFilter.SHOW_ELEMENT, + null, + false + ); + + let node = treeWalker.nextNode(); + while(node) { + + if (!node.hasAttribute("data-ref")) { + let uuid = UUID(); + node.setAttribute("data-ref", uuid); + } + + if (node.id) { + node.setAttribute("data-id", node.id); + } + + // node.setAttribute("data-children", node.childNodes.length); + + // node.setAttribute("data-text", node.textContent.trim().length); + node = treeWalker.nextNode(); + } + } + + find(ref) { + return this.refs[ref]; + } + + destroy() { + this.refs = undefined; + this.dom = undefined; + } + } + + /** + * Queue for handling tasks one at a time + * @class + * @param {scope} context what this will resolve to in the tasks + */ + class Queue { + constructor(context){ + this._q = []; + this.context = context; + this.tick = requestAnimationFrame; + this.running = false; + this.paused = false; + } + + /** + * Add an item to the queue + * @return {Promise} enqueued + */ + enqueue() { + var deferred, promise; + var queued; + var task = [].shift.call(arguments); + var args = arguments; + + // Handle single args without context + // if(args && !Array.isArray(args)) { + // args = [args]; + // } + if(!task) { + throw new Error("No Task Provided"); + } + + if(typeof task === "function"){ + + deferred = new defer(); + promise = deferred.promise; + + queued = { + "task" : task, + "args" : args, + //"context" : context, + "deferred" : deferred, + "promise" : promise + }; + + } else { + // Task is a promise + queued = { + "promise" : task + }; + + } + + this._q.push(queued); + + // Wait to start queue flush + if (this.paused == false && !this.running) { + this.run(); + } + + return queued.promise; + } + + /** + * Run one item + * @return {Promise} dequeued + */ + dequeue(){ + var inwait, task, result; + + if(this._q.length && !this.paused) { + inwait = this._q.shift(); + task = inwait.task; + if(task){ + // console.log(task) + + result = task.apply(this.context, inwait.args); + + if(result && typeof result["then"] === "function") { + // Task is a function that returns a promise + return result.then(function(){ + inwait.deferred.resolve.apply(this.context, arguments); + }.bind(this), function() { + inwait.deferred.reject.apply(this.context, arguments); + }.bind(this)); + } else { + // Task resolves immediately + inwait.deferred.resolve.apply(this.context, result); + return inwait.promise; + } + + + + } else if(inwait.promise) { + // Task is a promise + return inwait.promise; + } + + } else { + inwait = new defer(); + inwait.deferred.resolve(); + return inwait.promise; + } + + } + + // Run All Immediately + dump(){ + while(this._q.length) { + this.dequeue(); + } + } + + /** + * Run all tasks sequentially, at convince + * @return {Promise} all run + */ + run(){ + + if(!this.running){ + this.running = true; + this.defered = new defer(); + } + + this.tick.call(window, () => { + + if(this._q.length) { + + this.dequeue() + .then(function(){ + this.run(); + }.bind(this)); + + } else { + this.defered.resolve(); + this.running = undefined; + } + + }); + + // Unpause + if(this.paused == true) { + this.paused = false; + } + + return this.defered.promise; + } + + /** + * Flush all, as quickly as possible + * @return {Promise} ran + */ + flush(){ + + if(this.running){ + return this.running; + } + + if(this._q.length) { + this.running = this.dequeue() + .then(function(){ + this.running = undefined; + return this.flush(); + }.bind(this)); + + return this.running; + } + + } + + /** + * Clear all items in wait + * @return {void} + */ + clear(){ + this._q = []; + } + + /** + * Get the number of tasks in the queue + * @return {number} tasks + */ + length(){ + return this._q.length; + } + + /** + * Pause a running queue + * @return {void} + */ + pause(){ + this.paused = true; + } + + /** + * End the queue + * @return {void} + */ + stop(){ + this._q = []; + this.running = false; + this.paused = true; + } + } + + const TEMPLATE = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`; + + /** + * Chop up text into flows + * @class + */ + class Chunker { + constructor(content, renderTo, options) { + // this.preview = preview; + + this.settings = options || {}; + + this.hooks = {}; + this.hooks.beforeParsed = new Hook(this); + this.hooks.filter = new Hook(this); + this.hooks.afterParsed = new Hook(this); + this.hooks.beforePageLayout = new Hook(this); + this.hooks.onPageLayout = new Hook(this); + this.hooks.layout = new Hook(this); + this.hooks.renderNode = new Hook(this); + this.hooks.layoutNode = new Hook(this); + this.hooks.onOverflow = new Hook(this); + this.hooks.afterOverflowRemoved = new Hook(this); + this.hooks.onBreakToken = new Hook(); + this.hooks.beforeRenderResult = new Hook(this); + this.hooks.afterPageLayout = new Hook(this); + this.hooks.finalizePage = new Hook(this); + this.hooks.afterRendered = new Hook(this); + + this.pages = []; + this.total = 0; + + this.q = new Queue(this); + this.stopped = false; + this.rendered = false; + + this.content = content; + + this.charsPerBreak = []; + this.maxChars; + + if (content) { + this.flow(content, renderTo); + } + } + + setup(renderTo) { + this.pagesArea = document.createElement("div"); + this.pagesArea.classList.add("pagedjs_pages"); + + if (renderTo) { + renderTo.appendChild(this.pagesArea); + } else { + document.querySelector("body").appendChild(this.pagesArea); + } + + this.pageTemplate = document.createElement("template"); + this.pageTemplate.innerHTML = TEMPLATE; + + } + + async flow(content, renderTo) { + let parsed; + + await this.hooks.beforeParsed.trigger(content, this); + + parsed = new ContentParser(content); + + this.hooks.filter.triggerSync(parsed); + + this.source = parsed; + this.breakToken = undefined; + + if (this.pagesArea && this.pageTemplate) { + this.q.clear(); + this.removePages(); + } else { + this.setup(renderTo); + } + + this.emit("rendering", parsed); + + await this.hooks.afterParsed.trigger(parsed, this); + + await this.loadFonts(); + + let rendered = await this.render(parsed, this.breakToken); + while (rendered.canceled) { + this.start(); + rendered = await this.render(parsed, this.breakToken); + } + + this.rendered = true; + this.pagesArea.style.setProperty("--pagedjs-page-count", this.total); + + await this.hooks.afterRendered.trigger(this.pages, this); + + this.emit("rendered", this.pages); + + + + return this; + } + + // oversetPages() { + // let overset = []; + // for (let i = 0; i < this.pages.length; i++) { + // let page = this.pages[i]; + // if (page.overset) { + // overset.push(page); + // // page.overset = false; + // } + // } + // return overset; + // } + // + // async handleOverset(parsed) { + // let overset = this.oversetPages(); + // if (overset.length) { + // console.log("overset", overset); + // let index = this.pages.indexOf(overset[0]) + 1; + // console.log("INDEX", index); + // + // // Remove pages + // // this.removePages(index); + // + // // await this.render(parsed, overset[0].overset); + // + // // return this.handleOverset(parsed); + // } + // } + + async render(parsed, startAt) { + let renderer = this.layout(parsed, startAt); + + let done = false; + let result; + while (!done) { + result = await this.q.enqueue(() => { return this.renderAsync(renderer); }); + done = result.done; + } + + return result; + } + + start() { + this.rendered = false; + this.stopped = false; + } + + stop() { + this.stopped = true; + // this.q.clear(); + } + + renderOnIdle(renderer) { + return new Promise(resolve => { + requestIdleCallback(async () => { + if (this.stopped) { + return resolve({ done: true, canceled: true }); + } + let result = await renderer.next(); + if (this.stopped) { + resolve({ done: true, canceled: true }); + } else { + resolve(result); + } + }); + }); + } + + async renderAsync(renderer) { + if (this.stopped) { + return { done: true, canceled: true }; + } + let result = await renderer.next(); + if (this.stopped) { + return { done: true, canceled: true }; + } else { + return result; + } + } + + async handleBreaks(node, force) { + let currentPage = this.total + 1; + let currentPosition = currentPage % 2 === 0 ? "left" : "right"; + // TODO: Recto and Verso should reverse for rtl languages + let currentSide = currentPage % 2 === 0 ? "verso" : "recto"; + let previousBreakAfter; + let breakBefore; + let page; + + if (currentPage === 1) { + return; + } + + if (node && + typeof node.dataset !== "undefined" && + typeof node.dataset.previousBreakAfter !== "undefined") { + previousBreakAfter = node.dataset.previousBreakAfter; + } + + if (node && + typeof node.dataset !== "undefined" && + typeof node.dataset.breakBefore !== "undefined") { + breakBefore = node.dataset.breakBefore; + } + + if (force) { + page = this.addPage(true); + } else if( previousBreakAfter && + (previousBreakAfter === "left" || previousBreakAfter === "right") && + previousBreakAfter !== currentPosition) { + page = this.addPage(true); + } else if( previousBreakAfter && + (previousBreakAfter === "verso" || previousBreakAfter === "recto") && + previousBreakAfter !== currentSide) { + page = this.addPage(true); + } else if( breakBefore && + (breakBefore === "left" || breakBefore === "right") && + breakBefore !== currentPosition) { + page = this.addPage(true); + } else if( breakBefore && + (breakBefore === "verso" || breakBefore === "recto") && + breakBefore !== currentSide) { + page = this.addPage(true); + } + + if (page) { + await this.hooks.beforePageLayout.trigger(page, undefined, undefined, this); + this.emit("page", page); + // await this.hooks.layout.trigger(page.element, page, undefined, this); + await this.hooks.afterPageLayout.trigger(page.element, page, undefined, this); + await this.hooks.finalizePage.trigger(page.element, page, undefined, this); + this.emit("renderedPage", page); + } + } + + async *layout(content, startAt) { + let breakToken = startAt || false; + let tokens = []; + + while (breakToken !== undefined && (true)) { + + if (breakToken && breakToken.node) { + await this.handleBreaks(breakToken.node); + } else { + await this.handleBreaks(content.firstChild); + } + + let page = this.addPage(); + + await this.hooks.beforePageLayout.trigger(page, content, breakToken, this); + this.emit("page", page); + + // Layout content in the page, starting from the breakToken + breakToken = await page.layout(content, breakToken, this.maxChars); + + if (breakToken) { + let newToken = breakToken.toJSON(true); + if (tokens.lastIndexOf(newToken) > -1) { + // loop + let err = new OverflowContentError("Layout repeated", [breakToken.node]); + console.error("Layout repeated at: ", breakToken.node); + return err; + } else { + tokens.push(newToken); + } + } + + await this.hooks.afterPageLayout.trigger(page.element, page, breakToken, this); + await this.hooks.finalizePage.trigger(page.element, page, undefined, this); + this.emit("renderedPage", page); + + this.recoredCharLength(page.wrapper.textContent.length); + + yield breakToken; + + // Stop if we get undefined, showing we have reached the end of the content + } + + + } + + recoredCharLength(length) { + if (length === 0) { + return; + } + + this.charsPerBreak.push(length); + + // Keep the length of the last few breaks + if (this.charsPerBreak.length > 4) { + this.charsPerBreak.shift(); + } + + this.maxChars = this.charsPerBreak.reduce((a, b) => a + b, 0) / (this.charsPerBreak.length); + } + + removePages(fromIndex=0) { + + if (fromIndex >= this.pages.length) { + return; + } + + // Remove pages + for (let i = fromIndex; i < this.pages.length; i++) { + this.pages[i].destroy(); + } + + if (fromIndex > 0) { + this.pages.splice(fromIndex); + } else { + this.pages = []; + } + + this.total = this.pages.length; + } + + addPage(blank) { + let lastPage = this.pages[this.pages.length - 1]; + // Create a new page from the template + let page = new Page(this.pagesArea, this.pageTemplate, blank, this.hooks, this.settings); + + this.pages.push(page); + + // Create the pages + page.create(undefined, lastPage && lastPage.element); + + page.index(this.total); + + if (!blank) { + // Listen for page overflow + page.onOverflow((overflowToken) => { + console.warn("overflow on", page.id, overflowToken); + + // Only reflow while rendering + if (this.rendered) { + return; + } + + let index = this.pages.indexOf(page) + 1; + + // Stop the rendering + this.stop(); + + // Set the breakToken to resume at + this.breakToken = overflowToken; + + // Remove pages + this.removePages(index); + + if (this.rendered === true) { + this.rendered = false; + + this.q.enqueue(async () => { + + this.start(); + + await this.render(this.source, this.breakToken); + + this.rendered = true; + + }); + } + + + }); + + page.onUnderflow((overflowToken) => { + // console.log("underflow on", page.id, overflowToken); + + // page.append(this.source, overflowToken); + + }); + } + + this.total = this.pages.length; + + return page; + } + /* + insertPage(index, blank) { + let lastPage = this.pages[index]; + // Create a new page from the template + let page = new Page(this.pagesArea, this.pageTemplate, blank, this.hooks); + + let total = this.pages.splice(index, 0, page); + + // Create the pages + page.create(undefined, lastPage && lastPage.element); + + page.index(index + 1); + + for (let i = index + 2; i < this.pages.length; i++) { + this.pages[i].index(i); + } + + if (!blank) { + // Listen for page overflow + page.onOverflow((overflowToken) => { + if (total < this.pages.length) { + this.pages[total].layout(this.source, overflowToken); + } else { + let newPage = this.addPage(); + newPage.layout(this.source, overflowToken); + } + }); + + page.onUnderflow(() => { + // console.log("underflow on", page.id); + }); + } + + this.total += 1; + + return page; + } + */ + + async clonePage(originalPage) { + let lastPage = this.pages[this.pages.length - 1]; + + let page = new Page(this.pagesArea, this.pageTemplate, false, this.hooks); + + this.pages.push(page); + + // Create the pages + page.create(undefined, lastPage && lastPage.element); + + page.index(this.total); + + await this.hooks.beforePageLayout.trigger(page, undefined, undefined, this); + this.emit("page", page); + + for (const className of originalPage.element.classList) { + if (className !== "pagedjs_left_page" && className !== "pagedjs_right_page") { + page.element.classList.add(className); + } + } + + await this.hooks.afterPageLayout.trigger(page.element, page, undefined, this); + await this.hooks.finalizePage.trigger(page.element, page, undefined, this); + this.emit("renderedPage", page); + } + + loadFonts() { + let fontPromises = []; + (document.fonts || []).forEach((fontFace) => { + if (fontFace.status !== "loaded") { + let fontLoaded = fontFace.load().then((r) => { + return fontFace.family; + }, (r) => { + console.warn("Failed to preload font-family:", fontFace.family); + return fontFace.family; + }); + fontPromises.push(fontLoaded); + } + }); + return Promise.all(fontPromises).catch((err) => { + console.warn(err); + }); + } + + destroy() { + this.pagesArea.remove(); + this.pageTemplate.remove(); + } + + } + + EventEmitter(Chunker.prototype); + + var syntax = {exports: {}}; + + var create$4 = {}; + + // + // list + // ┌──────┐ + // ┌──────────────┼─head │ + // │ │ tail─┼──────────────┐ + // │ └──────┘ │ + // ▼ ▼ + // item item item item + // ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ + // null ◀──┼─prev │◀───┼─prev │◀───┼─prev │◀───┼─prev │ + // │ next─┼───▶│ next─┼───▶│ next─┼───▶│ next─┼──▶ null + // ├──────┤ ├──────┤ ├──────┤ ├──────┤ + // │ data │ │ data │ │ data │ │ data │ + // └──────┘ └──────┘ └──────┘ └──────┘ + // + + function createItem(data) { + return { + prev: null, + next: null, + data: data + }; + } + + function allocateCursor(node, prev, next) { + var cursor; + + if (cursors !== null) { + cursor = cursors; + cursors = cursors.cursor; + cursor.prev = prev; + cursor.next = next; + cursor.cursor = node.cursor; + } else { + cursor = { + prev: prev, + next: next, + cursor: node.cursor + }; + } + + node.cursor = cursor; + + return cursor; + } + + function releaseCursor(node) { + var cursor = node.cursor; + + node.cursor = cursor.cursor; + cursor.prev = null; + cursor.next = null; + cursor.cursor = cursors; + cursors = cursor; + } + + var cursors = null; + var List$6 = function() { + this.cursor = null; + this.head = null; + this.tail = null; + }; + + List$6.createItem = createItem; + List$6.prototype.createItem = createItem; + + List$6.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) { + var cursor = this.cursor; + + while (cursor !== null) { + if (cursor.prev === prevOld) { + cursor.prev = prevNew; + } + + if (cursor.next === nextOld) { + cursor.next = nextNew; + } + + cursor = cursor.cursor; + } + }; + + List$6.prototype.getSize = function() { + var size = 0; + var cursor = this.head; + + while (cursor) { + size++; + cursor = cursor.next; + } + + return size; + }; + + List$6.prototype.fromArray = function(array) { + var cursor = null; + + this.head = null; + + for (var i = 0; i < array.length; i++) { + var item = createItem(array[i]); + + if (cursor !== null) { + cursor.next = item; + } else { + this.head = item; + } + + item.prev = cursor; + cursor = item; + } + + this.tail = cursor; + + return this; + }; + + List$6.prototype.toArray = function() { + var cursor = this.head; + var result = []; + + while (cursor) { + result.push(cursor.data); + cursor = cursor.next; + } + + return result; + }; + + List$6.prototype.toJSON = List$6.prototype.toArray; + + List$6.prototype.isEmpty = function() { + return this.head === null; + }; + + List$6.prototype.first = function() { + return this.head && this.head.data; + }; + + List$6.prototype.last = function() { + return this.tail && this.tail.data; + }; + + List$6.prototype.each = function(fn, context) { + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, null, this.head); + + while (cursor.next !== null) { + item = cursor.next; + cursor.next = item.next; + + fn.call(context, item.data, item, this); + } + + // pop cursor + releaseCursor(this); + }; + + List$6.prototype.forEach = List$6.prototype.each; + + List$6.prototype.eachRight = function(fn, context) { + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, this.tail, null); + + while (cursor.prev !== null) { + item = cursor.prev; + cursor.prev = item.prev; + + fn.call(context, item.data, item, this); + } + + // pop cursor + releaseCursor(this); + }; + + List$6.prototype.forEachRight = List$6.prototype.eachRight; + + List$6.prototype.reduce = function(fn, initialValue, context) { + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, null, this.head); + var acc = initialValue; + + while (cursor.next !== null) { + item = cursor.next; + cursor.next = item.next; + + acc = fn.call(context, acc, item.data, item, this); + } + + // pop cursor + releaseCursor(this); + + return acc; + }; + + List$6.prototype.reduceRight = function(fn, initialValue, context) { + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, this.tail, null); + var acc = initialValue; + + while (cursor.prev !== null) { + item = cursor.prev; + cursor.prev = item.prev; + + acc = fn.call(context, acc, item.data, item, this); + } + + // pop cursor + releaseCursor(this); + + return acc; + }; + + List$6.prototype.nextUntil = function(start, fn, context) { + if (start === null) { + return; + } + + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, null, start); + + while (cursor.next !== null) { + item = cursor.next; + cursor.next = item.next; + + if (fn.call(context, item.data, item, this)) { + break; + } + } + + // pop cursor + releaseCursor(this); + }; + + List$6.prototype.prevUntil = function(start, fn, context) { + if (start === null) { + return; + } + + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, start, null); + + while (cursor.prev !== null) { + item = cursor.prev; + cursor.prev = item.prev; + + if (fn.call(context, item.data, item, this)) { + break; + } + } + + // pop cursor + releaseCursor(this); + }; + + List$6.prototype.some = function(fn, context) { + var cursor = this.head; + + if (context === undefined) { + context = this; + } + + while (cursor !== null) { + if (fn.call(context, cursor.data, cursor, this)) { + return true; + } + + cursor = cursor.next; + } + + return false; + }; + + List$6.prototype.map = function(fn, context) { + var result = new List$6(); + var cursor = this.head; + + if (context === undefined) { + context = this; + } + + while (cursor !== null) { + result.appendData(fn.call(context, cursor.data, cursor, this)); + cursor = cursor.next; + } + + return result; + }; + + List$6.prototype.filter = function(fn, context) { + var result = new List$6(); + var cursor = this.head; + + if (context === undefined) { + context = this; + } + + while (cursor !== null) { + if (fn.call(context, cursor.data, cursor, this)) { + result.appendData(cursor.data); + } + cursor = cursor.next; + } + + return result; + }; + + List$6.prototype.clear = function() { + this.head = null; + this.tail = null; + }; + + List$6.prototype.copy = function() { + var result = new List$6(); + var cursor = this.head; + + while (cursor !== null) { + result.insert(createItem(cursor.data)); + cursor = cursor.next; + } + + return result; + }; + + List$6.prototype.prepend = function(item) { + // head + // ^ + // item + this.updateCursors(null, item, this.head, item); + + // insert to the beginning of the list + if (this.head !== null) { + // new item <- first item + this.head.prev = item; + + // new item -> first item + item.next = this.head; + } else { + // if list has no head, then it also has no tail + // in this case tail points to the new item + this.tail = item; + } + + // head always points to new item + this.head = item; + + return this; + }; + + List$6.prototype.prependData = function(data) { + return this.prepend(createItem(data)); + }; + + List$6.prototype.append = function(item) { + return this.insert(item); + }; + + List$6.prototype.appendData = function(data) { + return this.insert(createItem(data)); + }; + + List$6.prototype.insert = function(item, before) { + if (before !== undefined && before !== null) { + // prev before + // ^ + // item + this.updateCursors(before.prev, item, before, item); + + if (before.prev === null) { + // insert to the beginning of list + if (this.head !== before) { + throw new Error('before doesn\'t belong to list'); + } + + // since head points to before therefore list doesn't empty + // no need to check tail + this.head = item; + before.prev = item; + item.next = before; + + this.updateCursors(null, item); + } else { + + // insert between two items + before.prev.next = item; + item.prev = before.prev; + + before.prev = item; + item.next = before; + } + } else { + // tail + // ^ + // item + this.updateCursors(this.tail, item, null, item); + + // insert to the ending of the list + if (this.tail !== null) { + // last item -> new item + this.tail.next = item; + + // last item <- new item + item.prev = this.tail; + } else { + // if list has no tail, then it also has no head + // in this case head points to new item + this.head = item; + } + + // tail always points to new item + this.tail = item; + } + + return this; + }; + + List$6.prototype.insertData = function(data, before) { + return this.insert(createItem(data), before); + }; + + List$6.prototype.remove = function(item) { + // item + // ^ + // prev next + this.updateCursors(item, item.prev, item, item.next); + + if (item.prev !== null) { + item.prev.next = item.next; + } else { + if (this.head !== item) { + throw new Error('item doesn\'t belong to list'); + } + + this.head = item.next; + } + + if (item.next !== null) { + item.next.prev = item.prev; + } else { + if (this.tail !== item) { + throw new Error('item doesn\'t belong to list'); + } + + this.tail = item.prev; + } + + item.prev = null; + item.next = null; + + return item; + }; + + List$6.prototype.push = function(data) { + this.insert(createItem(data)); + }; + + List$6.prototype.pop = function() { + if (this.tail !== null) { + return this.remove(this.tail); + } + }; + + List$6.prototype.unshift = function(data) { + this.prepend(createItem(data)); + }; + + List$6.prototype.shift = function() { + if (this.head !== null) { + return this.remove(this.head); + } + }; + + List$6.prototype.prependList = function(list) { + return this.insertList(list, this.head); + }; + + List$6.prototype.appendList = function(list) { + return this.insertList(list); + }; + + List$6.prototype.insertList = function(list, before) { + // ignore empty lists + if (list.head === null) { + return this; + } + + if (before !== undefined && before !== null) { + this.updateCursors(before.prev, list.tail, before, list.head); + + // insert in the middle of dist list + if (before.prev !== null) { + // before.prev <-> list.head + before.prev.next = list.head; + list.head.prev = before.prev; + } else { + this.head = list.head; + } + + before.prev = list.tail; + list.tail.next = before; + } else { + this.updateCursors(this.tail, list.tail, null, list.head); + + // insert to end of the list + if (this.tail !== null) { + // if destination list has a tail, then it also has a head, + // but head doesn't change + + // dest tail -> source head + this.tail.next = list.head; + + // dest tail <- source head + list.head.prev = this.tail; + } else { + // if list has no a tail, then it also has no a head + // in this case points head to new item + this.head = list.head; + } + + // tail always start point to new item + this.tail = list.tail; + } + + list.head = null; + list.tail = null; + + return this; + }; + + List$6.prototype.replace = function(oldItem, newItemOrList) { + if ('head' in newItemOrList) { + this.insertList(newItemOrList, oldItem); + } else { + this.insert(newItemOrList, oldItem); + } + + this.remove(oldItem); + }; + + var List_1 = List$6; + + var createCustomError$3 = function createCustomError(name, message) { + // use Object.create(), because some VMs prevent setting line/column otherwise + // (iOS Safari 10 even throws an exception) + var error = Object.create(SyntaxError.prototype); + var errorStack = new Error(); + + error.name = name; + error.message = message; + + Object.defineProperty(error, 'stack', { + get: function() { + return (errorStack.stack || '').replace(/^(.+\n){1,3}/, name + ': ' + message + '\n'); + } + }); + + return error; + }; + + var createCustomError$2 = createCustomError$3; + var MAX_LINE_LENGTH = 100; + var OFFSET_CORRECTION = 60; + var TAB_REPLACEMENT = ' '; + + function sourceFragment(error, extraLines) { + function processLines(start, end) { + return lines.slice(start, end).map(function(line, idx) { + var num = String(start + idx + 1); + + while (num.length < maxNumLength) { + num = ' ' + num; + } + + return num + ' |' + line; + }).join('\n'); + } + + var lines = error.source.split(/\r\n?|\n|\f/); + var line = error.line; + var column = error.column; + var startLine = Math.max(1, line - extraLines) - 1; + var endLine = Math.min(line + extraLines, lines.length + 1); + var maxNumLength = Math.max(4, String(endLine).length) + 1; + var cutLeft = 0; + + // column correction according to replaced tab before column + column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\t/g) || []).length; + + if (column > MAX_LINE_LENGTH) { + cutLeft = column - OFFSET_CORRECTION + 3; + column = OFFSET_CORRECTION - 2; + } + + for (var i = startLine; i <= endLine; i++) { + if (i >= 0 && i < lines.length) { + lines[i] = lines[i].replace(/\t/g, TAB_REPLACEMENT); + lines[i] = + (cutLeft > 0 && lines[i].length > cutLeft ? '\u2026' : '') + + lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) + + (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? '\u2026' : ''); + } + } + + return [ + processLines(startLine, line), + new Array(column + maxNumLength + 2).join('-') + '^', + processLines(line, endLine) + ].filter(Boolean).join('\n'); + } + + var SyntaxError$4 = function(message, source, offset, line, column) { + var error = createCustomError$2('SyntaxError', message); + + error.source = source; + error.offset = offset; + error.line = line; + error.column = column; + + error.sourceFragment = function(extraLines) { + return sourceFragment(error, isNaN(extraLines) ? 0 : extraLines); + }; + Object.defineProperty(error, 'formattedMessage', { + get: function() { + return ( + 'Parse error: ' + error.message + '\n' + + sourceFragment(error, 2) + ); + } + }); + + // for backward capability + error.parseError = { + offset: offset, + line: line, + column: column + }; + + return error; + }; + + var _SyntaxError$1 = SyntaxError$4; + + // CSS Syntax Module Level 3 + // https://www.w3.org/TR/css-syntax-3/ + var TYPE$H = { + EOF: 0, // + Ident: 1, // + Function: 2, // + AtKeyword: 3, // + Hash: 4, // + String: 5, // + BadString: 6, // + Url: 7, // + BadUrl: 8, // + Delim: 9, // + Number: 10, // + Percentage: 11, // + Dimension: 12, // + WhiteSpace: 13, // + CDO: 14, // + CDC: 15, // + Colon: 16, // : + Semicolon: 17, // ; + Comma: 18, // , + LeftSquareBracket: 19, // <[-token> + RightSquareBracket: 20, // <]-token> + LeftParenthesis: 21, // <(-token> + RightParenthesis: 22, // <)-token> + LeftCurlyBracket: 23, // <{-token> + RightCurlyBracket: 24, // <}-token> + Comment: 25 + }; + + var NAME$3 = Object.keys(TYPE$H).reduce(function(result, key) { + result[TYPE$H[key]] = key; + return result; + }, {}); + + var _const = { + TYPE: TYPE$H, + NAME: NAME$3 + }; + + var EOF$1 = 0; + + // https://drafts.csswg.org/css-syntax-3/ + // § 4.2. Definitions + + // digit + // A code point between U+0030 DIGIT ZERO (0) and U+0039 DIGIT NINE (9). + function isDigit$5(code) { + return code >= 0x0030 && code <= 0x0039; + } + + // hex digit + // A digit, or a code point between U+0041 LATIN CAPITAL LETTER A (A) and U+0046 LATIN CAPITAL LETTER F (F), + // or a code point between U+0061 LATIN SMALL LETTER A (a) and U+0066 LATIN SMALL LETTER F (f). + function isHexDigit$4(code) { + return ( + isDigit$5(code) || // 0 .. 9 + (code >= 0x0041 && code <= 0x0046) || // A .. F + (code >= 0x0061 && code <= 0x0066) // a .. f + ); + } + + // uppercase letter + // A code point between U+0041 LATIN CAPITAL LETTER A (A) and U+005A LATIN CAPITAL LETTER Z (Z). + function isUppercaseLetter$1(code) { + return code >= 0x0041 && code <= 0x005A; + } + + // lowercase letter + // A code point between U+0061 LATIN SMALL LETTER A (a) and U+007A LATIN SMALL LETTER Z (z). + function isLowercaseLetter(code) { + return code >= 0x0061 && code <= 0x007A; + } + + // letter + // An uppercase letter or a lowercase letter. + function isLetter(code) { + return isUppercaseLetter$1(code) || isLowercaseLetter(code); + } + + // non-ASCII code point + // A code point with a value equal to or greater than U+0080 . + function isNonAscii(code) { + return code >= 0x0080; + } + + // name-start code point + // A letter, a non-ASCII code point, or U+005F LOW LINE (_). + function isNameStart(code) { + return isLetter(code) || isNonAscii(code) || code === 0x005F; + } + + // name code point + // A name-start code point, a digit, or U+002D HYPHEN-MINUS (-). + function isName$2(code) { + return isNameStart(code) || isDigit$5(code) || code === 0x002D; + } + + // non-printable code point + // A code point between U+0000 NULL and U+0008 BACKSPACE, or U+000B LINE TABULATION, + // or a code point between U+000E SHIFT OUT and U+001F INFORMATION SEPARATOR ONE, or U+007F DELETE. + function isNonPrintable(code) { + return ( + (code >= 0x0000 && code <= 0x0008) || + (code === 0x000B) || + (code >= 0x000E && code <= 0x001F) || + (code === 0x007F) + ); + } + + // newline + // U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition, + // as they are converted to U+000A LINE FEED during preprocessing. + // TODO: we doesn't do a preprocessing, so check a code point for U+000D CARRIAGE RETURN and U+000C FORM FEED + function isNewline$1(code) { + return code === 0x000A || code === 0x000D || code === 0x000C; + } + + // whitespace + // A newline, U+0009 CHARACTER TABULATION, or U+0020 SPACE. + function isWhiteSpace$2(code) { + return isNewline$1(code) || code === 0x0020 || code === 0x0009; + } + + // § 4.3.8. Check if two code points are a valid escape + function isValidEscape$2(first, second) { + // If the first code point is not U+005C REVERSE SOLIDUS (\), return false. + if (first !== 0x005C) { + return false; + } + + // Otherwise, if the second code point is a newline or EOF, return false. + if (isNewline$1(second) || second === EOF$1) { + return false; + } + + // Otherwise, return true. + return true; + } + + // § 4.3.9. Check if three code points would start an identifier + function isIdentifierStart$2(first, second, third) { + // Look at the first code point: + + // U+002D HYPHEN-MINUS + if (first === 0x002D) { + // If the second code point is a name-start code point or a U+002D HYPHEN-MINUS, + // or the second and third code points are a valid escape, return true. Otherwise, return false. + return ( + isNameStart(second) || + second === 0x002D || + isValidEscape$2(second, third) + ); + } + + // name-start code point + if (isNameStart(first)) { + // Return true. + return true; + } + + // U+005C REVERSE SOLIDUS (\) + if (first === 0x005C) { + // If the first and second code points are a valid escape, return true. Otherwise, return false. + return isValidEscape$2(first, second); + } + + // anything else + // Return false. + return false; + } + + // § 4.3.10. Check if three code points would start a number + function isNumberStart$1(first, second, third) { + // Look at the first code point: + + // U+002B PLUS SIGN (+) + // U+002D HYPHEN-MINUS (-) + if (first === 0x002B || first === 0x002D) { + // If the second code point is a digit, return true. + if (isDigit$5(second)) { + return 2; + } + + // Otherwise, if the second code point is a U+002E FULL STOP (.) + // and the third code point is a digit, return true. + // Otherwise, return false. + return second === 0x002E && isDigit$5(third) ? 3 : 0; + } + + // U+002E FULL STOP (.) + if (first === 0x002E) { + // If the second code point is a digit, return true. Otherwise, return false. + return isDigit$5(second) ? 2 : 0; + } + + // digit + if (isDigit$5(first)) { + // Return true. + return 1; + } + + // anything else + // Return false. + return 0; + } + + // + // Misc + // + + // detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark) + function isBOM$2(code) { + // UTF-16BE + if (code === 0xFEFF) { + return 1; + } + + // UTF-16LE + if (code === 0xFFFE) { + return 1; + } + + return 0; + } + + // Fast code category + // + // https://drafts.csswg.org/css-syntax/#tokenizer-definitions + // > non-ASCII code point + // > A code point with a value equal to or greater than U+0080 + // > name-start code point + // > A letter, a non-ASCII code point, or U+005F LOW LINE (_). + // > name code point + // > A name-start code point, a digit, or U+002D HYPHEN-MINUS (-) + // That means only ASCII code points has a special meaning and we define a maps for 0..127 codes only + var CATEGORY = new Array(0x80); + charCodeCategory$1.Eof = 0x80; + charCodeCategory$1.WhiteSpace = 0x82; + charCodeCategory$1.Digit = 0x83; + charCodeCategory$1.NameStart = 0x84; + charCodeCategory$1.NonPrintable = 0x85; + + for (var i = 0; i < CATEGORY.length; i++) { + switch (true) { + case isWhiteSpace$2(i): + CATEGORY[i] = charCodeCategory$1.WhiteSpace; + break; + + case isDigit$5(i): + CATEGORY[i] = charCodeCategory$1.Digit; + break; + + case isNameStart(i): + CATEGORY[i] = charCodeCategory$1.NameStart; + break; + + case isNonPrintable(i): + CATEGORY[i] = charCodeCategory$1.NonPrintable; + break; + + default: + CATEGORY[i] = i || charCodeCategory$1.Eof; + } + } + + function charCodeCategory$1(code) { + return code < 0x80 ? CATEGORY[code] : charCodeCategory$1.NameStart; + } + var charCodeDefinitions$1 = { + isDigit: isDigit$5, + isHexDigit: isHexDigit$4, + isUppercaseLetter: isUppercaseLetter$1, + isLowercaseLetter: isLowercaseLetter, + isLetter: isLetter, + isNonAscii: isNonAscii, + isNameStart: isNameStart, + isName: isName$2, + isNonPrintable: isNonPrintable, + isNewline: isNewline$1, + isWhiteSpace: isWhiteSpace$2, + isValidEscape: isValidEscape$2, + isIdentifierStart: isIdentifierStart$2, + isNumberStart: isNumberStart$1, + + isBOM: isBOM$2, + charCodeCategory: charCodeCategory$1 + }; + + var charCodeDef = charCodeDefinitions$1; + var isDigit$4 = charCodeDef.isDigit; + var isHexDigit$3 = charCodeDef.isHexDigit; + var isUppercaseLetter = charCodeDef.isUppercaseLetter; + var isName$1 = charCodeDef.isName; + var isWhiteSpace$1 = charCodeDef.isWhiteSpace; + var isValidEscape$1 = charCodeDef.isValidEscape; + + function getCharCode(source, offset) { + return offset < source.length ? source.charCodeAt(offset) : 0; + } + + function getNewlineLength$1(source, offset, code) { + if (code === 13 /* \r */ && getCharCode(source, offset + 1) === 10 /* \n */) { + return 2; + } + + return 1; + } + + function cmpChar$5(testStr, offset, referenceCode) { + var code = testStr.charCodeAt(offset); + + // code.toLowerCase() for A..Z + if (isUppercaseLetter(code)) { + code = code | 32; + } + + return code === referenceCode; + } + + function cmpStr$6(testStr, start, end, referenceStr) { + if (end - start !== referenceStr.length) { + return false; + } + + if (start < 0 || end > testStr.length) { + return false; + } + + for (var i = start; i < end; i++) { + var testCode = testStr.charCodeAt(i); + var referenceCode = referenceStr.charCodeAt(i - start); + + // testCode.toLowerCase() for A..Z + if (isUppercaseLetter(testCode)) { + testCode = testCode | 32; + } + + if (testCode !== referenceCode) { + return false; + } + } + + return true; + } + + function findWhiteSpaceStart$1(source, offset) { + for (; offset >= 0; offset--) { + if (!isWhiteSpace$1(source.charCodeAt(offset))) { + break; + } + } + + return offset + 1; + } + + function findWhiteSpaceEnd$1(source, offset) { + for (; offset < source.length; offset++) { + if (!isWhiteSpace$1(source.charCodeAt(offset))) { + break; + } + } + + return offset; + } + + function findDecimalNumberEnd(source, offset) { + for (; offset < source.length; offset++) { + if (!isDigit$4(source.charCodeAt(offset))) { + break; + } + } + + return offset; + } + + // § 4.3.7. Consume an escaped code point + function consumeEscaped$1(source, offset) { + // It assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed and + // that the next input code point has already been verified to be part of a valid escape. + offset += 2; + + // hex digit + if (isHexDigit$3(getCharCode(source, offset - 1))) { + // Consume as many hex digits as possible, but no more than 5. + // Note that this means 1-6 hex digits have been consumed in total. + for (var maxOffset = Math.min(source.length, offset + 5); offset < maxOffset; offset++) { + if (!isHexDigit$3(getCharCode(source, offset))) { + break; + } + } + + // If the next input code point is whitespace, consume it as well. + var code = getCharCode(source, offset); + if (isWhiteSpace$1(code)) { + offset += getNewlineLength$1(source, offset, code); + } + } + + return offset; + } + + // §4.3.11. Consume a name + // Note: This algorithm does not do the verification of the first few code points that are necessary + // to ensure the returned code points would constitute an . If that is the intended use, + // ensure that the stream starts with an identifier before calling this algorithm. + function consumeName$1(source, offset) { + // Let result initially be an empty string. + // Repeatedly consume the next input code point from the stream: + for (; offset < source.length; offset++) { + var code = source.charCodeAt(offset); + + // name code point + if (isName$1(code)) { + // Append the code point to result. + continue; + } + + // the stream starts with a valid escape + if (isValidEscape$1(code, getCharCode(source, offset + 1))) { + // Consume an escaped code point. Append the returned code point to result. + offset = consumeEscaped$1(source, offset) - 1; + continue; + } + + // anything else + // Reconsume the current input code point. Return result. + break; + } + + return offset; + } + + // §4.3.12. Consume a number + function consumeNumber$5(source, offset) { + var code = source.charCodeAt(offset); + + // 2. If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), + // consume it and append it to repr. + if (code === 0x002B || code === 0x002D) { + code = source.charCodeAt(offset += 1); + } + + // 3. While the next input code point is a digit, consume it and append it to repr. + if (isDigit$4(code)) { + offset = findDecimalNumberEnd(source, offset + 1); + code = source.charCodeAt(offset); + } + + // 4. If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then: + if (code === 0x002E && isDigit$4(source.charCodeAt(offset + 1))) { + // 4.1 Consume them. + // 4.2 Append them to repr. + code = source.charCodeAt(offset += 2); + + // 4.3 Set type to "number". + // TODO + + // 4.4 While the next input code point is a digit, consume it and append it to repr. + + offset = findDecimalNumberEnd(source, offset); + } + + // 5. If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) + // or U+0065 LATIN SMALL LETTER E (e), ... , followed by a digit, then: + if (cmpChar$5(source, offset, 101 /* e */)) { + var sign = 0; + code = source.charCodeAt(offset + 1); + + // ... optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+) ... + if (code === 0x002D || code === 0x002B) { + sign = 1; + code = source.charCodeAt(offset + 2); + } + + // ... followed by a digit + if (isDigit$4(code)) { + // 5.1 Consume them. + // 5.2 Append them to repr. + + // 5.3 Set type to "number". + // TODO + + // 5.4 While the next input code point is a digit, consume it and append it to repr. + offset = findDecimalNumberEnd(source, offset + 1 + sign + 1); + } + } + + return offset; + } + + // § 4.3.14. Consume the remnants of a bad url + // ... its sole use is to consume enough of the input stream to reach a recovery point + // where normal tokenizing can resume. + function consumeBadUrlRemnants$1(source, offset) { + // Repeatedly consume the next input code point from the stream: + for (; offset < source.length; offset++) { + var code = source.charCodeAt(offset); + + // U+0029 RIGHT PARENTHESIS ()) + // EOF + if (code === 0x0029) { + // Return. + offset++; + break; + } + + if (isValidEscape$1(code, getCharCode(source, offset + 1))) { + // Consume an escaped code point. + // Note: This allows an escaped right parenthesis ("\)") to be encountered + // without ending the . This is otherwise identical to + // the "anything else" clause. + offset = consumeEscaped$1(source, offset); + } + } + + return offset; + } + + var utils$2 = { + consumeEscaped: consumeEscaped$1, + consumeName: consumeName$1, + consumeNumber: consumeNumber$5, + consumeBadUrlRemnants: consumeBadUrlRemnants$1, + + cmpChar: cmpChar$5, + cmpStr: cmpStr$6, + + getNewlineLength: getNewlineLength$1, + findWhiteSpaceStart: findWhiteSpaceStart$1, + findWhiteSpaceEnd: findWhiteSpaceEnd$1 + }; + + var constants$2 = _const; + var TYPE$G = constants$2.TYPE; + var NAME$2 = constants$2.NAME; + + var utils$1 = utils$2; + var cmpStr$5 = utils$1.cmpStr; + + var EOF = TYPE$G.EOF; + var WHITESPACE$c = TYPE$G.WhiteSpace; + var COMMENT$a = TYPE$G.Comment; + + var OFFSET_MASK$1 = 0x00FFFFFF; + var TYPE_SHIFT$1 = 24; + + var TokenStream$4 = function() { + this.offsetAndType = null; + this.balance = null; + + this.reset(); + }; + + TokenStream$4.prototype = { + reset: function() { + this.eof = false; + this.tokenIndex = -1; + this.tokenType = 0; + this.tokenStart = this.firstCharOffset; + this.tokenEnd = this.firstCharOffset; + }, + + lookupType: function(offset) { + offset += this.tokenIndex; + + if (offset < this.tokenCount) { + return this.offsetAndType[offset] >> TYPE_SHIFT$1; + } + + return EOF; + }, + lookupOffset: function(offset) { + offset += this.tokenIndex; + + if (offset < this.tokenCount) { + return this.offsetAndType[offset - 1] & OFFSET_MASK$1; + } + + return this.source.length; + }, + lookupValue: function(offset, referenceStr) { + offset += this.tokenIndex; + + if (offset < this.tokenCount) { + return cmpStr$5( + this.source, + this.offsetAndType[offset - 1] & OFFSET_MASK$1, + this.offsetAndType[offset] & OFFSET_MASK$1, + referenceStr + ); + } + + return false; + }, + getTokenStart: function(tokenIndex) { + if (tokenIndex === this.tokenIndex) { + return this.tokenStart; + } + + if (tokenIndex > 0) { + return tokenIndex < this.tokenCount + ? this.offsetAndType[tokenIndex - 1] & OFFSET_MASK$1 + : this.offsetAndType[this.tokenCount] & OFFSET_MASK$1; + } + + return this.firstCharOffset; + }, + + // TODO: -> skipUntilBalanced + getRawLength: function(startToken, mode) { + var cursor = startToken; + var balanceEnd; + var offset = this.offsetAndType[Math.max(cursor - 1, 0)] & OFFSET_MASK$1; + var type; + + loop: + for (; cursor < this.tokenCount; cursor++) { + balanceEnd = this.balance[cursor]; + + // stop scanning on balance edge that points to offset before start token + if (balanceEnd < startToken) { + break loop; + } + + type = this.offsetAndType[cursor] >> TYPE_SHIFT$1; + + // check token is stop type + switch (mode(type, this.source, offset)) { + case 1: + break loop; + + case 2: + cursor++; + break loop; + + default: + // fast forward to the end of balanced block + if (this.balance[balanceEnd] === cursor) { + cursor = balanceEnd; + } + + offset = this.offsetAndType[cursor] & OFFSET_MASK$1; + } + } + + return cursor - this.tokenIndex; + }, + isBalanceEdge: function(pos) { + return this.balance[this.tokenIndex] < pos; + }, + isDelim: function(code, offset) { + if (offset) { + return ( + this.lookupType(offset) === TYPE$G.Delim && + this.source.charCodeAt(this.lookupOffset(offset)) === code + ); + } + + return ( + this.tokenType === TYPE$G.Delim && + this.source.charCodeAt(this.tokenStart) === code + ); + }, + + getTokenValue: function() { + return this.source.substring(this.tokenStart, this.tokenEnd); + }, + getTokenLength: function() { + return this.tokenEnd - this.tokenStart; + }, + substrToCursor: function(start) { + return this.source.substring(start, this.tokenStart); + }, + + skipWS: function() { + for (var i = this.tokenIndex, skipTokenCount = 0; i < this.tokenCount; i++, skipTokenCount++) { + if ((this.offsetAndType[i] >> TYPE_SHIFT$1) !== WHITESPACE$c) { + break; + } + } + + if (skipTokenCount > 0) { + this.skip(skipTokenCount); + } + }, + skipSC: function() { + while (this.tokenType === WHITESPACE$c || this.tokenType === COMMENT$a) { + this.next(); + } + }, + skip: function(tokenCount) { + var next = this.tokenIndex + tokenCount; + + if (next < this.tokenCount) { + this.tokenIndex = next; + this.tokenStart = this.offsetAndType[next - 1] & OFFSET_MASK$1; + next = this.offsetAndType[next]; + this.tokenType = next >> TYPE_SHIFT$1; + this.tokenEnd = next & OFFSET_MASK$1; + } else { + this.tokenIndex = this.tokenCount; + this.next(); + } + }, + next: function() { + var next = this.tokenIndex + 1; + + if (next < this.tokenCount) { + this.tokenIndex = next; + this.tokenStart = this.tokenEnd; + next = this.offsetAndType[next]; + this.tokenType = next >> TYPE_SHIFT$1; + this.tokenEnd = next & OFFSET_MASK$1; + } else { + this.tokenIndex = this.tokenCount; + this.eof = true; + this.tokenType = EOF; + this.tokenStart = this.tokenEnd = this.source.length; + } + }, + + forEachToken(fn) { + for (var i = 0, offset = this.firstCharOffset; i < this.tokenCount; i++) { + var start = offset; + var item = this.offsetAndType[i]; + var end = item & OFFSET_MASK$1; + var type = item >> TYPE_SHIFT$1; + + offset = end; + + fn(type, start, end, i); + } + }, + + dump() { + var tokens = new Array(this.tokenCount); + + this.forEachToken((type, start, end, index) => { + tokens[index] = { + idx: index, + type: NAME$2[type], + chunk: this.source.substring(start, end), + balance: this.balance[index] + }; + }); + + return tokens; + } + }; + + var TokenStream_1 = TokenStream$4; + + function noop$3(value) { + return value; + } + + function generateMultiplier(multiplier) { + if (multiplier.min === 0 && multiplier.max === 0) { + return '*'; + } + + if (multiplier.min === 0 && multiplier.max === 1) { + return '?'; + } + + if (multiplier.min === 1 && multiplier.max === 0) { + return multiplier.comma ? '#' : '+'; + } + + if (multiplier.min === 1 && multiplier.max === 1) { + return ''; + } + + return ( + (multiplier.comma ? '#' : '') + + (multiplier.min === multiplier.max + ? '{' + multiplier.min + '}' + : '{' + multiplier.min + ',' + (multiplier.max !== 0 ? multiplier.max : '') + '}' + ) + ); + } + + function generateTypeOpts(node) { + switch (node.type) { + case 'Range': + return ( + ' [' + + (node.min === null ? '-∞' : node.min) + + ',' + + (node.max === null ? '∞' : node.max) + + ']' + ); + + default: + throw new Error('Unknown node type `' + node.type + '`'); + } + } + + function generateSequence(node, decorate, forceBraces, compact) { + var combinator = node.combinator === ' ' || compact ? node.combinator : ' ' + node.combinator + ' '; + var result = node.terms.map(function(term) { + return generate$2(term, decorate, forceBraces, compact); + }).join(combinator); + + if (node.explicit || forceBraces) { + result = (compact || result[0] === ',' ? '[' : '[ ') + result + (compact ? ']' : ' ]'); + } + + return result; + } + + function generate$2(node, decorate, forceBraces, compact) { + var result; + + switch (node.type) { + case 'Group': + result = + generateSequence(node, decorate, forceBraces, compact) + + (node.disallowEmpty ? '!' : ''); + break; + + case 'Multiplier': + // return since node is a composition + return ( + generate$2(node.term, decorate, forceBraces, compact) + + decorate(generateMultiplier(node), node) + ); + + case 'Type': + result = '<' + node.name + (node.opts ? decorate(generateTypeOpts(node.opts), node.opts) : '') + '>'; + break; + + case 'Property': + result = '<\'' + node.name + '\'>'; + break; + + case 'Keyword': + result = node.name; + break; + + case 'AtKeyword': + result = '@' + node.name; + break; + + case 'Function': + result = node.name + '('; + break; + + case 'String': + case 'Token': + result = node.value; + break; + + case 'Comma': + result = ','; + break; + + default: + throw new Error('Unknown node type `' + node.type + '`'); + } + + return decorate(result, node); + } + + var generate_1 = function(node, options) { + var decorate = noop$3; + var forceBraces = false; + var compact = false; + + if (typeof options === 'function') { + decorate = options; + } else if (options) { + forceBraces = Boolean(options.forceBraces); + compact = Boolean(options.compact); + if (typeof options.decorate === 'function') { + decorate = options.decorate; + } + } + + return generate$2(node, decorate, forceBraces, compact); + }; + + const createCustomError$1 = createCustomError$3; + const generate$1 = generate_1; + const defaultLoc = { offset: 0, line: 1, column: 1 }; + + function locateMismatch(matchResult, node) { + const tokens = matchResult.tokens; + const longestMatch = matchResult.longestMatch; + const mismatchNode = longestMatch < tokens.length ? tokens[longestMatch].node || null : null; + const badNode = mismatchNode !== node ? mismatchNode : null; + let mismatchOffset = 0; + let mismatchLength = 0; + let entries = 0; + let css = ''; + let start; + let end; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i].value; + + if (i === longestMatch) { + mismatchLength = token.length; + mismatchOffset = css.length; + } + + if (badNode !== null && tokens[i].node === badNode) { + if (i <= longestMatch) { + entries++; + } else { + entries = 0; + } + } + + css += token; + } + + if (longestMatch === tokens.length || entries > 1) { // last + start = fromLoc(badNode || node, 'end') || buildLoc(defaultLoc, css); + end = buildLoc(start); + } else { + start = fromLoc(badNode, 'start') || + buildLoc(fromLoc(node, 'start') || defaultLoc, css.slice(0, mismatchOffset)); + end = fromLoc(badNode, 'end') || + buildLoc(start, css.substr(mismatchOffset, mismatchLength)); + } + + return { + css, + mismatchOffset, + mismatchLength, + start, + end + }; + } + + function fromLoc(node, point) { + const value = node && node.loc && node.loc[point]; + + if (value) { + return 'line' in value ? buildLoc(value) : value; + } + + return null; + } + + function buildLoc({ offset, line, column }, extra) { + const loc = { + offset, + line, + column + }; + + if (extra) { + const lines = extra.split(/\n|\r\n?|\f/); + + loc.offset += extra.length; + loc.line += lines.length - 1; + loc.column = lines.length === 1 ? loc.column + extra.length : lines.pop().length + 1; + } + + return loc; + } + + const SyntaxReferenceError$1 = function(type, referenceName) { + const error = createCustomError$1( + 'SyntaxReferenceError', + type + (referenceName ? ' `' + referenceName + '`' : '') + ); + + error.reference = referenceName; + + return error; + }; + + const SyntaxMatchError$1 = function(message, syntax, node, matchResult) { + const error = createCustomError$1('SyntaxMatchError', message); + const { + css, + mismatchOffset, + mismatchLength, + start, + end + } = locateMismatch(matchResult, node); + + error.rawMessage = message; + error.syntax = syntax ? generate$1(syntax) : ''; + error.css = css; + error.mismatchOffset = mismatchOffset; + error.mismatchLength = mismatchLength; + error.message = message + '\n' + + ' syntax: ' + error.syntax + '\n' + + ' value: ' + (css || '') + '\n' + + ' --------' + new Array(error.mismatchOffset + 1).join('-') + '^'; + + Object.assign(error, start); + error.loc = { + source: (node && node.loc && node.loc.source) || '', + start, + end + }; + + return error; + }; + + var error = { + SyntaxReferenceError: SyntaxReferenceError$1, + SyntaxMatchError: SyntaxMatchError$1 + }; + + var hasOwnProperty$7 = Object.prototype.hasOwnProperty; + var keywords$1 = Object.create(null); + var properties$1 = Object.create(null); + var HYPHENMINUS$5 = 45; // '-'.charCodeAt() + + function isCustomProperty$1(str, offset) { + offset = offset || 0; + + return str.length - offset >= 2 && + str.charCodeAt(offset) === HYPHENMINUS$5 && + str.charCodeAt(offset + 1) === HYPHENMINUS$5; + } + + function getVendorPrefix(str, offset) { + offset = offset || 0; + + // verdor prefix should be at least 3 chars length + if (str.length - offset >= 3) { + // vendor prefix starts with hyper minus following non-hyper minus + if (str.charCodeAt(offset) === HYPHENMINUS$5 && + str.charCodeAt(offset + 1) !== HYPHENMINUS$5) { + // vendor prefix should contain a hyper minus at the ending + var secondDashIndex = str.indexOf('-', offset + 2); + + if (secondDashIndex !== -1) { + return str.substring(offset, secondDashIndex + 1); + } + } + } + + return ''; + } + + function getKeywordDescriptor(keyword) { + if (hasOwnProperty$7.call(keywords$1, keyword)) { + return keywords$1[keyword]; + } + + var name = keyword.toLowerCase(); + + if (hasOwnProperty$7.call(keywords$1, name)) { + return keywords$1[keyword] = keywords$1[name]; + } + + var custom = isCustomProperty$1(name, 0); + var vendor = !custom ? getVendorPrefix(name, 0) : ''; + + return keywords$1[keyword] = Object.freeze({ + basename: name.substr(vendor.length), + name: name, + vendor: vendor, + prefix: vendor, + custom: custom + }); + } + + function getPropertyDescriptor(property) { + if (hasOwnProperty$7.call(properties$1, property)) { + return properties$1[property]; + } + + var name = property; + var hack = property[0]; + + if (hack === '/') { + hack = property[1] === '/' ? '//' : '/'; + } else if (hack !== '_' && + hack !== '*' && + hack !== '$' && + hack !== '#' && + hack !== '+' && + hack !== '&') { + hack = ''; + } + + var custom = isCustomProperty$1(name, hack.length); + + // re-use result when possible (the same as for lower case) + if (!custom) { + name = name.toLowerCase(); + if (hasOwnProperty$7.call(properties$1, name)) { + return properties$1[property] = properties$1[name]; + } + } + + var vendor = !custom ? getVendorPrefix(name, hack.length) : ''; + var prefix = name.substr(0, hack.length + vendor.length); + + return properties$1[property] = Object.freeze({ + basename: name.substr(prefix.length), + name: name.substr(hack.length), + hack: hack, + vendor: vendor, + prefix: prefix, + custom: custom + }); + } + + var names$2 = { + keyword: getKeywordDescriptor, + property: getPropertyDescriptor, + isCustomProperty: isCustomProperty$1, + vendorPrefix: getVendorPrefix + }; + + var MIN_SIZE = 16 * 1024; + var SafeUint32Array = typeof Uint32Array !== 'undefined' ? Uint32Array : Array; // fallback on Array when TypedArray is not supported + + var adoptBuffer$2 = function adoptBuffer(buffer, size) { + if (buffer === null || buffer.length < size) { + return new SafeUint32Array(Math.max(size + 1024, MIN_SIZE)); + } + + return buffer; + }; + + var TokenStream$3 = TokenStream_1; + var adoptBuffer$1 = adoptBuffer$2; + + var constants$1 = _const; + var TYPE$F = constants$1.TYPE; + + var charCodeDefinitions = charCodeDefinitions$1; + var isNewline = charCodeDefinitions.isNewline; + var isName = charCodeDefinitions.isName; + var isValidEscape = charCodeDefinitions.isValidEscape; + var isNumberStart = charCodeDefinitions.isNumberStart; + var isIdentifierStart$1 = charCodeDefinitions.isIdentifierStart; + var charCodeCategory = charCodeDefinitions.charCodeCategory; + var isBOM$1 = charCodeDefinitions.isBOM; + + var utils = utils$2; + var cmpStr$4 = utils.cmpStr; + var getNewlineLength = utils.getNewlineLength; + var findWhiteSpaceEnd = utils.findWhiteSpaceEnd; + var consumeEscaped = utils.consumeEscaped; + var consumeName = utils.consumeName; + var consumeNumber$4 = utils.consumeNumber; + var consumeBadUrlRemnants = utils.consumeBadUrlRemnants; + + var OFFSET_MASK = 0x00FFFFFF; + var TYPE_SHIFT = 24; + + function tokenize$3(source, stream) { + function getCharCode(offset) { + return offset < sourceLength ? source.charCodeAt(offset) : 0; + } + + // § 4.3.3. Consume a numeric token + function consumeNumericToken() { + // Consume a number and let number be the result. + offset = consumeNumber$4(source, offset); + + // If the next 3 input code points would start an identifier, then: + if (isIdentifierStart$1(getCharCode(offset), getCharCode(offset + 1), getCharCode(offset + 2))) { + // Create a with the same value and type flag as number, and a unit set initially to the empty string. + // Consume a name. Set the ’s unit to the returned value. + // Return the . + type = TYPE$F.Dimension; + offset = consumeName(source, offset); + return; + } + + // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it. + if (getCharCode(offset) === 0x0025) { + // Create a with the same value as number, and return it. + type = TYPE$F.Percentage; + offset++; + return; + } + + // Otherwise, create a with the same value and type flag as number, and return it. + type = TYPE$F.Number; + } + + // § 4.3.4. Consume an ident-like token + function consumeIdentLikeToken() { + const nameStartOffset = offset; + + // Consume a name, and let string be the result. + offset = consumeName(source, offset); + + // If string’s value is an ASCII case-insensitive match for "url", + // and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. + if (cmpStr$4(source, nameStartOffset, offset, 'url') && getCharCode(offset) === 0x0028) { + // While the next two input code points are whitespace, consume the next input code point. + offset = findWhiteSpaceEnd(source, offset + 1); + + // If the next one or two input code points are U+0022 QUOTATION MARK ("), U+0027 APOSTROPHE ('), + // or whitespace followed by U+0022 QUOTATION MARK (") or U+0027 APOSTROPHE ('), + // then create a with its value set to string and return it. + if (getCharCode(offset) === 0x0022 || + getCharCode(offset) === 0x0027) { + type = TYPE$F.Function; + offset = nameStartOffset + 4; + return; + } + + // Otherwise, consume a url token, and return it. + consumeUrlToken(); + return; + } + + // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. + // Create a with its value set to string and return it. + if (getCharCode(offset) === 0x0028) { + type = TYPE$F.Function; + offset++; + return; + } + + // Otherwise, create an with its value set to string and return it. + type = TYPE$F.Ident; + } + + // § 4.3.5. Consume a string token + function consumeStringToken(endingCodePoint) { + // This algorithm may be called with an ending code point, which denotes the code point + // that ends the string. If an ending code point is not specified, + // the current input code point is used. + if (!endingCodePoint) { + endingCodePoint = getCharCode(offset++); + } + + // Initially create a with its value set to the empty string. + type = TYPE$F.String; + + // Repeatedly consume the next input code point from the stream: + for (; offset < source.length; offset++) { + var code = source.charCodeAt(offset); + + switch (charCodeCategory(code)) { + // ending code point + case endingCodePoint: + // Return the . + offset++; + return; + + // EOF + case charCodeCategory.Eof: + // This is a parse error. Return the . + return; + + // newline + case charCodeCategory.WhiteSpace: + if (isNewline(code)) { + // This is a parse error. Reconsume the current input code point, + // create a , and return it. + offset += getNewlineLength(source, offset, code); + type = TYPE$F.BadString; + return; + } + break; + + // U+005C REVERSE SOLIDUS (\) + case 0x005C: + // If the next input code point is EOF, do nothing. + if (offset === source.length - 1) { + break; + } + + var nextCode = getCharCode(offset + 1); + + // Otherwise, if the next input code point is a newline, consume it. + if (isNewline(nextCode)) { + offset += getNewlineLength(source, offset + 1, nextCode); + } else if (isValidEscape(code, nextCode)) { + // Otherwise, (the stream starts with a valid escape) consume + // an escaped code point and append the returned code point to + // the ’s value. + offset = consumeEscaped(source, offset) - 1; + } + break; + + // anything else + // Append the current input code point to the ’s value. + } + } + } + + // § 4.3.6. Consume a url token + // Note: This algorithm assumes that the initial "url(" has already been consumed. + // This algorithm also assumes that it’s being called to consume an "unquoted" value, like url(foo). + // A quoted value, like url("foo"), is parsed as a . Consume an ident-like token + // automatically handles this distinction; this algorithm shouldn’t be called directly otherwise. + function consumeUrlToken() { + // Initially create a with its value set to the empty string. + type = TYPE$F.Url; + + // Consume as much whitespace as possible. + offset = findWhiteSpaceEnd(source, offset); + + // Repeatedly consume the next input code point from the stream: + for (; offset < source.length; offset++) { + var code = source.charCodeAt(offset); + + switch (charCodeCategory(code)) { + // U+0029 RIGHT PARENTHESIS ()) + case 0x0029: + // Return the . + offset++; + return; + + // EOF + case charCodeCategory.Eof: + // This is a parse error. Return the . + return; + + // whitespace + case charCodeCategory.WhiteSpace: + // Consume as much whitespace as possible. + offset = findWhiteSpaceEnd(source, offset); + + // If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF, + // consume it and return the + // (if EOF was encountered, this is a parse error); + if (getCharCode(offset) === 0x0029 || offset >= source.length) { + if (offset < source.length) { + offset++; + } + return; + } + + // otherwise, consume the remnants of a bad url, create a , + // and return it. + offset = consumeBadUrlRemnants(source, offset); + type = TYPE$F.BadUrl; + return; + + // U+0022 QUOTATION MARK (") + // U+0027 APOSTROPHE (') + // U+0028 LEFT PARENTHESIS (() + // non-printable code point + case 0x0022: + case 0x0027: + case 0x0028: + case charCodeCategory.NonPrintable: + // This is a parse error. Consume the remnants of a bad url, + // create a , and return it. + offset = consumeBadUrlRemnants(source, offset); + type = TYPE$F.BadUrl; + return; + + // U+005C REVERSE SOLIDUS (\) + case 0x005C: + // If the stream starts with a valid escape, consume an escaped code point and + // append the returned code point to the ’s value. + if (isValidEscape(code, getCharCode(offset + 1))) { + offset = consumeEscaped(source, offset) - 1; + break; + } + + // Otherwise, this is a parse error. Consume the remnants of a bad url, + // create a , and return it. + offset = consumeBadUrlRemnants(source, offset); + type = TYPE$F.BadUrl; + return; + + // anything else + // Append the current input code point to the ’s value. + } + } + } + + if (!stream) { + stream = new TokenStream$3(); + } + + // ensure source is a string + source = String(source || ''); + + var sourceLength = source.length; + var offsetAndType = adoptBuffer$1(stream.offsetAndType, sourceLength + 1); // +1 because of eof-token + var balance = adoptBuffer$1(stream.balance, sourceLength + 1); + var tokenCount = 0; + var start = isBOM$1(getCharCode(0)); + var offset = start; + var balanceCloseType = 0; + var balanceStart = 0; + var balancePrev = 0; + + // https://drafts.csswg.org/css-syntax-3/#consume-token + // § 4.3.1. Consume a token + while (offset < sourceLength) { + var code = source.charCodeAt(offset); + var type = 0; + + balance[tokenCount] = sourceLength; + + switch (charCodeCategory(code)) { + // whitespace + case charCodeCategory.WhiteSpace: + // Consume as much whitespace as possible. Return a . + type = TYPE$F.WhiteSpace; + offset = findWhiteSpaceEnd(source, offset + 1); + break; + + // U+0022 QUOTATION MARK (") + case 0x0022: + // Consume a string token and return it. + consumeStringToken(); + break; + + // U+0023 NUMBER SIGN (#) + case 0x0023: + // If the next input code point is a name code point or the next two input code points are a valid escape, then: + if (isName(getCharCode(offset + 1)) || isValidEscape(getCharCode(offset + 1), getCharCode(offset + 2))) { + // Create a . + type = TYPE$F.Hash; + + // If the next 3 input code points would start an identifier, set the ’s type flag to "id". + // if (isIdentifierStart(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) { + // // TODO: set id flag + // } + + // Consume a name, and set the ’s value to the returned string. + offset = consumeName(source, offset + 1); + + // Return the . + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + break; + + // U+0027 APOSTROPHE (') + case 0x0027: + // Consume a string token and return it. + consumeStringToken(); + break; + + // U+0028 LEFT PARENTHESIS (() + case 0x0028: + // Return a <(-token>. + type = TYPE$F.LeftParenthesis; + offset++; + break; + + // U+0029 RIGHT PARENTHESIS ()) + case 0x0029: + // Return a <)-token>. + type = TYPE$F.RightParenthesis; + offset++; + break; + + // U+002B PLUS SIGN (+) + case 0x002B: + // If the input stream starts with a number, ... + if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) { + // ... reconsume the current input code point, consume a numeric token, and return it. + consumeNumericToken(); + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + break; + + // U+002C COMMA (,) + case 0x002C: + // Return a . + type = TYPE$F.Comma; + offset++; + break; + + // U+002D HYPHEN-MINUS (-) + case 0x002D: + // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. + if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) { + consumeNumericToken(); + } else { + // Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a . + if (getCharCode(offset + 1) === 0x002D && + getCharCode(offset + 2) === 0x003E) { + type = TYPE$F.CDC; + offset = offset + 3; + } else { + // Otherwise, if the input stream starts with an identifier, ... + if (isIdentifierStart$1(code, getCharCode(offset + 1), getCharCode(offset + 2))) { + // ... reconsume the current input code point, consume an ident-like token, and return it. + consumeIdentLikeToken(); + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + } + } + break; + + // U+002E FULL STOP (.) + case 0x002E: + // If the input stream starts with a number, ... + if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) { + // ... reconsume the current input code point, consume a numeric token, and return it. + consumeNumericToken(); + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + break; + + // U+002F SOLIDUS (/) + case 0x002F: + // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A ASTERISK (*), + if (getCharCode(offset + 1) === 0x002A) { + // ... consume them and all following code points up to and including the first U+002A ASTERISK (*) + // followed by a U+002F SOLIDUS (/), or up to an EOF code point. + type = TYPE$F.Comment; + offset = source.indexOf('*/', offset + 2) + 2; + if (offset === 1) { + offset = source.length; + } + } else { + type = TYPE$F.Delim; + offset++; + } + break; + + // U+003A COLON (:) + case 0x003A: + // Return a . + type = TYPE$F.Colon; + offset++; + break; + + // U+003B SEMICOLON (;) + case 0x003B: + // Return a . + type = TYPE$F.Semicolon; + offset++; + break; + + // U+003C LESS-THAN SIGN (<) + case 0x003C: + // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), ... + if (getCharCode(offset + 1) === 0x0021 && + getCharCode(offset + 2) === 0x002D && + getCharCode(offset + 3) === 0x002D) { + // ... consume them and return a . + type = TYPE$F.CDO; + offset = offset + 4; + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + break; + + // U+0040 COMMERCIAL AT (@) + case 0x0040: + // If the next 3 input code points would start an identifier, ... + if (isIdentifierStart$1(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) { + // ... consume a name, create an with its value set to the returned value, and return it. + type = TYPE$F.AtKeyword; + offset = consumeName(source, offset + 1); + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + break; + + // U+005B LEFT SQUARE BRACKET ([) + case 0x005B: + // Return a <[-token>. + type = TYPE$F.LeftSquareBracket; + offset++; + break; + + // U+005C REVERSE SOLIDUS (\) + case 0x005C: + // If the input stream starts with a valid escape, ... + if (isValidEscape(code, getCharCode(offset + 1))) { + // ... reconsume the current input code point, consume an ident-like token, and return it. + consumeIdentLikeToken(); + } else { + // Otherwise, this is a parse error. Return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + break; + + // U+005D RIGHT SQUARE BRACKET (]) + case 0x005D: + // Return a <]-token>. + type = TYPE$F.RightSquareBracket; + offset++; + break; + + // U+007B LEFT CURLY BRACKET ({) + case 0x007B: + // Return a <{-token>. + type = TYPE$F.LeftCurlyBracket; + offset++; + break; + + // U+007D RIGHT CURLY BRACKET (}) + case 0x007D: + // Return a <}-token>. + type = TYPE$F.RightCurlyBracket; + offset++; + break; + + // digit + case charCodeCategory.Digit: + // Reconsume the current input code point, consume a numeric token, and return it. + consumeNumericToken(); + break; + + // name-start code point + case charCodeCategory.NameStart: + // Reconsume the current input code point, consume an ident-like token, and return it. + consumeIdentLikeToken(); + break; + + // EOF + case charCodeCategory.Eof: + // Return an . + break; + + // anything else + default: + // Return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + switch (type) { + case balanceCloseType: + balancePrev = balanceStart & OFFSET_MASK; + balanceStart = balance[balancePrev]; + balanceCloseType = balanceStart >> TYPE_SHIFT; + balance[tokenCount] = balancePrev; + balance[balancePrev++] = tokenCount; + for (; balancePrev < tokenCount; balancePrev++) { + if (balance[balancePrev] === sourceLength) { + balance[balancePrev] = tokenCount; + } + } + break; + + case TYPE$F.LeftParenthesis: + case TYPE$F.Function: + balance[tokenCount] = balanceStart; + balanceCloseType = TYPE$F.RightParenthesis; + balanceStart = (balanceCloseType << TYPE_SHIFT) | tokenCount; + break; + + case TYPE$F.LeftSquareBracket: + balance[tokenCount] = balanceStart; + balanceCloseType = TYPE$F.RightSquareBracket; + balanceStart = (balanceCloseType << TYPE_SHIFT) | tokenCount; + break; + + case TYPE$F.LeftCurlyBracket: + balance[tokenCount] = balanceStart; + balanceCloseType = TYPE$F.RightCurlyBracket; + balanceStart = (balanceCloseType << TYPE_SHIFT) | tokenCount; + break; + } + + offsetAndType[tokenCount++] = (type << TYPE_SHIFT) | offset; + } + + // finalize buffers + offsetAndType[tokenCount] = (TYPE$F.EOF << TYPE_SHIFT) | offset; // + balance[tokenCount] = sourceLength; + balance[sourceLength] = sourceLength; // prevents false positive balance match with any token + while (balanceStart !== 0) { + balancePrev = balanceStart & OFFSET_MASK; + balanceStart = balance[balancePrev]; + balance[balancePrev] = sourceLength; + } + + // update stream + stream.source = source; + stream.firstCharOffset = start; + stream.offsetAndType = offsetAndType; + stream.tokenCount = tokenCount; + stream.balance = balance; + stream.reset(); + stream.next(); + + return stream; + } + + // extend tokenizer with constants + Object.keys(constants$1).forEach(function(key) { + tokenize$3[key] = constants$1[key]; + }); + + // extend tokenizer with static methods from utils + Object.keys(charCodeDefinitions).forEach(function(key) { + tokenize$3[key] = charCodeDefinitions[key]; + }); + Object.keys(utils).forEach(function(key) { + tokenize$3[key] = utils[key]; + }); + + var tokenizer$3 = tokenize$3; + + var isDigit$3 = tokenizer$3.isDigit; + var cmpChar$4 = tokenizer$3.cmpChar; + var TYPE$E = tokenizer$3.TYPE; + + var DELIM$6 = TYPE$E.Delim; + var WHITESPACE$b = TYPE$E.WhiteSpace; + var COMMENT$9 = TYPE$E.Comment; + var IDENT$i = TYPE$E.Ident; + var NUMBER$9 = TYPE$E.Number; + var DIMENSION$7 = TYPE$E.Dimension; + var PLUSSIGN$8 = 0x002B; // U+002B PLUS SIGN (+) + var HYPHENMINUS$4 = 0x002D; // U+002D HYPHEN-MINUS (-) + var N$4 = 0x006E; // U+006E LATIN SMALL LETTER N (n) + var DISALLOW_SIGN$1 = true; + var ALLOW_SIGN$1 = false; + + function isDelim$1(token, code) { + return token !== null && token.type === DELIM$6 && token.value.charCodeAt(0) === code; + } + + function skipSC(token, offset, getNextToken) { + while (token !== null && (token.type === WHITESPACE$b || token.type === COMMENT$9)) { + token = getNextToken(++offset); + } + + return offset; + } + + function checkInteger$1(token, valueOffset, disallowSign, offset) { + if (!token) { + return 0; + } + + var code = token.value.charCodeAt(valueOffset); + + if (code === PLUSSIGN$8 || code === HYPHENMINUS$4) { + if (disallowSign) { + // Number sign is not allowed + return 0; + } + valueOffset++; + } + + for (; valueOffset < token.value.length; valueOffset++) { + if (!isDigit$3(token.value.charCodeAt(valueOffset))) { + // Integer is expected + return 0; + } + } + + return offset + 1; + } + + // ... + // ... ['+' | '-'] + function consumeB$1(token, offset_, getNextToken) { + var sign = false; + var offset = skipSC(token, offset_, getNextToken); + + token = getNextToken(offset); + + if (token === null) { + return offset_; + } + + if (token.type !== NUMBER$9) { + if (isDelim$1(token, PLUSSIGN$8) || isDelim$1(token, HYPHENMINUS$4)) { + sign = true; + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + if (token === null && token.type !== NUMBER$9) { + return 0; + } + } else { + return offset_; + } + } + + if (!sign) { + var code = token.value.charCodeAt(0); + if (code !== PLUSSIGN$8 && code !== HYPHENMINUS$4) { + // Number sign is expected + return 0; + } + } + + return checkInteger$1(token, sign ? 0 : 1, sign, offset); + } + + // An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb + var genericAnPlusB = function anPlusB(token, getNextToken) { + /* eslint-disable brace-style*/ + var offset = 0; + + if (!token) { + return 0; + } + + // + if (token.type === NUMBER$9) { + return checkInteger$1(token, 0, ALLOW_SIGN$1, offset); // b + } + + // -n + // -n + // -n ['+' | '-'] + // -n- + // + else if (token.type === IDENT$i && token.value.charCodeAt(0) === HYPHENMINUS$4) { + // expect 1st char is N + if (!cmpChar$4(token.value, 1, N$4)) { + return 0; + } + + switch (token.value.length) { + // -n + // -n + // -n ['+' | '-'] + case 2: + return consumeB$1(getNextToken(++offset), offset, getNextToken); + + // -n- + case 3: + if (token.value.charCodeAt(2) !== HYPHENMINUS$4) { + return 0; + } + + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + return checkInteger$1(token, 0, DISALLOW_SIGN$1, offset); + + // + default: + if (token.value.charCodeAt(2) !== HYPHENMINUS$4) { + return 0; + } + + return checkInteger$1(token, 3, DISALLOW_SIGN$1, offset); + } + } + + // '+'? n + // '+'? n + // '+'? n ['+' | '-'] + // '+'? n- + // '+'? + else if (token.type === IDENT$i || (isDelim$1(token, PLUSSIGN$8) && getNextToken(offset + 1).type === IDENT$i)) { + // just ignore a plus + if (token.type !== IDENT$i) { + token = getNextToken(++offset); + } + + if (token === null || !cmpChar$4(token.value, 0, N$4)) { + return 0; + } + + switch (token.value.length) { + // '+'? n + // '+'? n + // '+'? n ['+' | '-'] + case 1: + return consumeB$1(getNextToken(++offset), offset, getNextToken); + + // '+'? n- + case 2: + if (token.value.charCodeAt(1) !== HYPHENMINUS$4) { + return 0; + } + + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + return checkInteger$1(token, 0, DISALLOW_SIGN$1, offset); + + // '+'? + default: + if (token.value.charCodeAt(1) !== HYPHENMINUS$4) { + return 0; + } + + return checkInteger$1(token, 2, DISALLOW_SIGN$1, offset); + } + } + + // + // + // + // + // ['+' | '-'] + else if (token.type === DIMENSION$7) { + var code = token.value.charCodeAt(0); + var sign = code === PLUSSIGN$8 || code === HYPHENMINUS$4 ? 1 : 0; + + for (var i = sign; i < token.value.length; i++) { + if (!isDigit$3(token.value.charCodeAt(i))) { + break; + } + } + + if (i === sign) { + // Integer is expected + return 0; + } + + if (!cmpChar$4(token.value, i, N$4)) { + return 0; + } + + // + // + // ['+' | '-'] + if (i + 1 === token.value.length) { + return consumeB$1(getNextToken(++offset), offset, getNextToken); + } else { + if (token.value.charCodeAt(i + 1) !== HYPHENMINUS$4) { + return 0; + } + + // + if (i + 2 === token.value.length) { + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + return checkInteger$1(token, 0, DISALLOW_SIGN$1, offset); + } + // + else { + return checkInteger$1(token, i + 2, DISALLOW_SIGN$1, offset); + } + } + } + + return 0; + }; + + var isHexDigit$2 = tokenizer$3.isHexDigit; + var cmpChar$3 = tokenizer$3.cmpChar; + var TYPE$D = tokenizer$3.TYPE; + + var IDENT$h = TYPE$D.Ident; + var DELIM$5 = TYPE$D.Delim; + var NUMBER$8 = TYPE$D.Number; + var DIMENSION$6 = TYPE$D.Dimension; + var PLUSSIGN$7 = 0x002B; // U+002B PLUS SIGN (+) + var HYPHENMINUS$3 = 0x002D; // U+002D HYPHEN-MINUS (-) + var QUESTIONMARK$2 = 0x003F; // U+003F QUESTION MARK (?) + var U$2 = 0x0075; // U+0075 LATIN SMALL LETTER U (u) + + function isDelim(token, code) { + return token !== null && token.type === DELIM$5 && token.value.charCodeAt(0) === code; + } + + function startsWith$1(token, code) { + return token.value.charCodeAt(0) === code; + } + + function hexSequence(token, offset, allowDash) { + for (var pos = offset, hexlen = 0; pos < token.value.length; pos++) { + var code = token.value.charCodeAt(pos); + + if (code === HYPHENMINUS$3 && allowDash && hexlen !== 0) { + if (hexSequence(token, offset + hexlen + 1, false) > 0) { + return 6; // dissallow following question marks + } + + return 0; // dash at the ending of a hex sequence is not allowed + } + + if (!isHexDigit$2(code)) { + return 0; // not a hex digit + } + + if (++hexlen > 6) { + return 0; // too many hex digits + } } + + return hexlen; + } + + function withQuestionMarkSequence(consumed, length, getNextToken) { + if (!consumed) { + return 0; // nothing consumed + } + + while (isDelim(getNextToken(length), QUESTIONMARK$2)) { + if (++consumed > 6) { + return 0; // too many question marks + } + + length++; + } + + return length; + } + + // https://drafts.csswg.org/css-syntax/#urange + // Informally, the production has three forms: + // U+0001 + // Defines a range consisting of a single code point, in this case the code point "1". + // U+0001-00ff + // Defines a range of codepoints between the first and the second value, in this case + // the range between "1" and "ff" (255 in decimal) inclusive. + // U+00?? + // Defines a range of codepoints where the "?" characters range over all hex digits, + // in this case defining the same as the value U+0000-00ff. + // In each form, a maximum of 6 digits is allowed for each hexadecimal number (if you treat "?" as a hexadecimal digit). + // + // = + // u '+' '?'* | + // u '?'* | + // u '?'* | + // u | + // u | + // u '+' '?'+ + var genericUrange = function urange(token, getNextToken) { + var length = 0; + + // should start with `u` or `U` + if (token === null || token.type !== IDENT$h || !cmpChar$3(token.value, 0, U$2)) { + return 0; + } + + token = getNextToken(++length); + if (token === null) { + return 0; + } + + // u '+' '?'* + // u '+' '?'+ + if (isDelim(token, PLUSSIGN$7)) { + token = getNextToken(++length); + if (token === null) { + return 0; + } + + if (token.type === IDENT$h) { + // u '+' '?'* + return withQuestionMarkSequence(hexSequence(token, 0, true), ++length, getNextToken); + } + + if (isDelim(token, QUESTIONMARK$2)) { + // u '+' '?'+ + return withQuestionMarkSequence(1, ++length, getNextToken); + } + + // Hex digit or question mark is expected + return 0; + } + + // u '?'* + // u + // u + if (token.type === NUMBER$8) { + if (!startsWith$1(token, PLUSSIGN$7)) { + return 0; + } + + var consumedHexLength = hexSequence(token, 1, true); + if (consumedHexLength === 0) { + return 0; + } + + token = getNextToken(++length); + if (token === null) { + // u + return length; + } + + if (token.type === DIMENSION$6 || token.type === NUMBER$8) { + // u + // u + if (!startsWith$1(token, HYPHENMINUS$3) || !hexSequence(token, 1, false)) { + return 0; + } + + return length + 1; + } + + // u '?'* + return withQuestionMarkSequence(consumedHexLength, length, getNextToken); + } + + // u '?'* + if (token.type === DIMENSION$6) { + if (!startsWith$1(token, PLUSSIGN$7)) { + return 0; + } + + return withQuestionMarkSequence(hexSequence(token, 1, true), ++length, getNextToken); + } + + return 0; + }; + + var tokenizer$2 = tokenizer$3; + var isIdentifierStart = tokenizer$2.isIdentifierStart; + var isHexDigit$1 = tokenizer$2.isHexDigit; + var isDigit$2 = tokenizer$2.isDigit; + var cmpStr$3 = tokenizer$2.cmpStr; + var consumeNumber$3 = tokenizer$2.consumeNumber; + var TYPE$C = tokenizer$2.TYPE; + var anPlusB = genericAnPlusB; + var urange = genericUrange; + + var cssWideKeywords$1 = ['unset', 'initial', 'inherit']; + var calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc(']; + + // https://www.w3.org/TR/css-values-3/#lengths + var LENGTH = { + // absolute length units + 'px': true, + 'mm': true, + 'cm': true, + 'in': true, + 'pt': true, + 'pc': true, + 'q': true, + + // relative length units + 'em': true, + 'ex': true, + 'ch': true, + 'rem': true, + + // viewport-percentage lengths + 'vh': true, + 'vw': true, + 'vmin': true, + 'vmax': true, + 'vm': true + }; + + var ANGLE = { + 'deg': true, + 'grad': true, + 'rad': true, + 'turn': true + }; + + var TIME = { + 's': true, + 'ms': true + }; + + var FREQUENCY = { + 'hz': true, + 'khz': true + }; + + // https://www.w3.org/TR/css-values-3/#resolution (https://drafts.csswg.org/css-values/#resolution) + var RESOLUTION = { + 'dpi': true, + 'dpcm': true, + 'dppx': true, + 'x': true // https://github.com/w3c/csswg-drafts/issues/461 + }; + + // https://drafts.csswg.org/css-grid/#fr-unit + var FLEX = { + 'fr': true + }; + + // https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume + var DECIBEL = { + 'db': true + }; + + // https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch + var SEMITONES = { + 'st': true + }; + + // safe char code getter + function charCode(str, index) { + return index < str.length ? str.charCodeAt(index) : 0; + } + + function eqStr(actual, expected) { + return cmpStr$3(actual, 0, actual.length, expected); + } + + function eqStrAny(actual, expected) { + for (var i = 0; i < expected.length; i++) { + if (eqStr(actual, expected[i])) { + return true; + } + } + + return false; + } + + // IE postfix hack, i.e. 123\0 or 123px\9 + function isPostfixIeHack(str, offset) { + if (offset !== str.length - 2) { + return false; + } + + return ( + str.charCodeAt(offset) === 0x005C && // U+005C REVERSE SOLIDUS (\) + isDigit$2(str.charCodeAt(offset + 1)) + ); + } + + function outOfRange(opts, value, numEnd) { + if (opts && opts.type === 'Range') { + var num = Number( + numEnd !== undefined && numEnd !== value.length + ? value.substr(0, numEnd) + : value + ); + + if (isNaN(num)) { + return true; + } + + if (opts.min !== null && num < opts.min) { + return true; + } + + if (opts.max !== null && num > opts.max) { + return true; + } + } + + return false; + } + + function consumeFunction(token, getNextToken) { + var startIdx = token.index; + var length = 0; + + // balanced token consuming + do { + length++; + + if (token.balance <= startIdx) { + break; + } + } while (token = getNextToken(length)); + + return length; + } + + // TODO: implement + // can be used wherever , , ,