pdfmd-cli 3.29.36__tar.gz → 3.30.32__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/CHANGELOG.md +764 -28
- {pdfmd_cli-3.29.36/pdfmd_cli.egg-info → pdfmd_cli-3.30.32}/PKG-INFO +379 -61
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/README.md +378 -60
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/docs/agents.md +2 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/docs/recipes.md +109 -7
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd.py +1916 -272
- pdfmd_cli-3.30.32/pdfmd_ansi/__init__.py +11 -0
- pdfmd_cli-3.30.32/pdfmd_ansi/highlight.py +249 -0
- pdfmd_cli-3.30.32/pdfmd_ansi/mathbox.py +325 -0
- pdfmd_cli-3.30.32/pdfmd_ansi/native.py +242 -0
- pdfmd_cli-3.30.32/pdfmd_ansi/render.py +578 -0
- pdfmd_cli-3.30.32/pdfmd_ansi/text.py +199 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_check/lint.py +101 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_chem/batch.py +1 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_chem/siunitx.py +8 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32/pdfmd_cli.egg-info}/PKG-INFO +379 -61
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_cli.egg-info/SOURCES.txt +26 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_cli.egg-info/top_level.txt +1 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_direct/defaults.py +1 -1
- pdfmd_cli-3.30.32/pdfmd_docs/__init__.py +11 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_edit/__init__.py +0 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_flat/asciify.py +1 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_flat/keep.py +9 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_help/__init__.py +8 -5
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_history/hst.py +3 -3
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_history/versions.py +2 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_images/svg.py +2 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/ascii.lua +3 -1
- pdfmd_cli-3.30.32/pdfmd_lua/calc.lua +1600 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/chem.lua +16 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/csv_table.lua +29 -9
- pdfmd_cli-3.30.32/pdfmd_lua/indent.lua +180 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/docx.py +7 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/fragments.py +1 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/properties.py +2 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_setup/inventory.py +15 -8
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_setup/registry.py +64 -7
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_tables/convert.py +1 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_tables/scan.py +5 -3
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_tables/sheets.py +1 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/fonts.py +1 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/tessdata.py +0 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pyproject.toml +2 -2
- pdfmd_cli-3.30.32/tests/test_ansi.py +648 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_ascii.py +19 -3
- pdfmd_cli-3.30.32/tests/test_atomic.py +176 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_attach.py +9 -6
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_auto_states.py +1 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_autolog.py +16 -1
- pdfmd_cli-3.30.32/tests/test_backup_summary.py +81 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_build_record.py +30 -0
- pdfmd_cli-3.30.32/tests/test_calc.py +1314 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_check.py +139 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_chem.py +37 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_code_blocks.py +1 -1
- pdfmd_cli-3.30.32/tests/test_command_words.py +34 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_compat.py +2 -0
- pdfmd_cli-3.30.32/tests/test_csv_switch.py +211 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_csv_table.py +105 -8
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_direct.py +4 -4
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_doc.py +16 -3
- pdfmd_cli-3.30.32/tests/test_docs_consistency.py +118 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_embedding.py +3 -3
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_engine_chain.py +9 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_engine_filters.py +5 -5
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_header_includes.py +3 -3
- pdfmd_cli-3.30.32/tests/test_indent.py +261 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_install_calibre.py +37 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_install_diagram.py +0 -2
- pdfmd_cli-3.30.32/tests/test_interrupt.py +35 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_jobs.py +1 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_keep_source.py +29 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_labels.py +5 -5
- pdfmd_cli-3.30.32/tests/test_lua_missing.py +99 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markup_center.py +4 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markup_highlight.py +4 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markup_keep.py +4 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markup_landscape.py +5 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markup_toc.py +1 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_mathjax.py +3 -4
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_native.py +48 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_office.py +10 -12
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_office_latex.py +5 -5
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_package_helper.py +3 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_packaging.py +21 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_pagebreak.py +1 -1
- pdfmd_cli-3.30.32/tests/test_partial_history.py +136 -0
- pdfmd_cli-3.30.32/tests/test_partial_provenance.py +110 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_picture_cache_protocol.py +4 -2
- pdfmd_cli-3.30.32/tests/test_probes.py +173 -0
- pdfmd_cli-3.30.32/tests/test_readonly_home.py +211 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_sections.py +30 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_setup_folder.py +2 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_show_comments.py +4 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_soffice_parts.py +2 -2
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_spreadsheets.py +0 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_standalone_source.py +1 -4
- pdfmd_cli-3.30.32/tests/test_symlinked_parts.py +118 -0
- pdfmd_cli-3.30.32/tests/test_table_keep.py +47 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_tables.py +62 -0
- pdfmd_cli-3.30.32/tests/test_tex_bin.py +75 -0
- pdfmd_cli-3.30.32/tests/test_tex_cause.py +57 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_texpackages.py +1 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_unicode.py +0 -1
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_versions.py +0 -1
- pdfmd_cli-3.29.36/pdfmd_docs/__init__.py +0 -4
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/LICENSE +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/MANIFEST.in +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_check/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_chem/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_chem/__main__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_chem/render.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_cli.egg-info/dependency_links.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_cli.egg-info/entry_points.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_cli.egg-info/requires.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_css/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_direct/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_direct/html.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_direct/typst.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_flat/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_history/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_history/git.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_images/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_images/remote.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_images/scan.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/LICENSE +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/VENDORED.md +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/_kerning_data.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/assets/fonts/DejaVuSans-LICENSE.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/assets/fonts/DejaVuSans.ttf +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/ast.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/cidfont.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/embedded.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/embedded_metrics.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/emoji.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/emoji_font.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/fonts.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/html_filter.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/image_loader.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/layout.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/parser.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/pdf.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/py.typed +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/render.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/truetype.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_inkmd/url_filter.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_labels/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_labels/replay.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_labels/scan.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/chem_client.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/code.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/code_wrap.tex +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/cover.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/diagram.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/ebook.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/ebook_input.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/flat.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/graphicx.tex +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/graphviz.cjs +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/images.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/katex.cjs +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/markup.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/mathjax.cjs +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/pagebreak.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/pdf_images.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/raw.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/select.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/table_width.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/title_meta.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/typst.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_lua/weasyprint.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_mhchem/LICENSE +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_mhchem/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_mhchem/parser.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/docx_post.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/fonts.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/labels.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/odt.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/office.lua +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/spec.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_office/units.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_raw/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_setup/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_setup/fancy.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_setup/plain.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_tables/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_tables/verify.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/article/__name__.md +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/article/description.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/article/next.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/article/refs.bib +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/book/__name__.md +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/book/description.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/book/next.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/book/parts/10-introduction.md +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/book/parts/20-methods.md +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/book/parts/30-results.md +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/notes/__name__.md +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/notes/description.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/notes/next.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/report/__name__.md +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/report/data.csv +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/report/description.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/report/next.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/slides/__name__.md +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/slides/description.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_templates/data/slides/next.txt +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/__init__.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/_catalog.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/_scripts.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/_tessdata_catalog.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/colorfont.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/emoji.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/install.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/plan.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/scripts.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/pdfmd_unicode/translit.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/setup.cfg +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/setup.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_api.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_apple_emoji.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_attach_format.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_bopen.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_browser_engine.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_budget.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_cache_location.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_calibre.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_calibre_markdown.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_calibre_targets.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_check_source.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_completion.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_config.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_cover.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_css.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_css_v2.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_diagrams.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_display_path.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_doctor.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_docx_tables.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_ebook_input.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_edit.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_edit_pdf.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_flat.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_folder_settings.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_help.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_history.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_images.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_install_core_tex.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_install_ocr.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_install_python.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_katex_scale.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_latex_errors.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_lookup.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_lua_trust.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markdown_features.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markup_appendix.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markup_callouts.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markup_space.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_markup_word.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_mathfont.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_mhchem.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_old_python_syntax.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_pan_tec.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_pdf_input.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_plot_cache.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_polish.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_presets.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_raw.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_rawcode.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_reader.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_remote_csl.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_setup.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_table_widths.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_templates.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_tessdata.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_text_input.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_translit.py +0 -0
- {pdfmd_cli-3.29.36 → pdfmd_cli-3.30.32}/tests/test_typst_input.py +0 -0
|
@@ -2,42 +2,778 @@
|
|
|
2
2
|
|
|
3
3
|
Newest first. Versions follow semver loosely: a **major** bump changes
|
|
4
4
|
behavior in a way that could surprise an existing user (a default flips, a
|
|
5
|
-
flag's meaning changes)
|
|
6
|
-
|
|
5
|
+
flag's meaning changes). One **minor** bump (`3.N.0`) opens a feature line,
|
|
6
|
+
however large it is; every change after it in that line is a **patch**
|
|
7
|
+
(`3.N.x`), whatever it adds, so the version count stays small.
|
|
7
8
|
|
|
8
|
-
The version string lives in one place, `pdfmd.py
|
|
9
|
-
docstring
|
|
9
|
+
The version string lives in one place, the `PDFMD_VERSION` line of `pdfmd.py`
|
|
10
|
+
(a little below its module docstring; `pyproject.toml` and the publish workflow
|
|
11
|
+
read it from there):
|
|
10
12
|
|
|
11
13
|
```python
|
|
12
14
|
PDFMD_VERSION = "2.0.0"
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
`pdfmd --version` prints it. Bump it there and add an entry here in the
|
|
16
|
-
same
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Cowork
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
**Update, 2026-10-05:** an approximate outline of the earlier history has
|
|
34
|
-
since been reconstructed, at the very end of this file (see "Before 2.0.0 —
|
|
18
|
+
same commit as the change.
|
|
19
|
+
|
|
20
|
+
**Why this starts at 2.0.0, not 1.0.0.** `pdfmd.py` began as a one-line
|
|
21
|
+
`pandoc file.md -o file.pdf` wrapper and was revised many times since — across
|
|
22
|
+
ChatGPT, ChatGPT Codex, Claude Chat, Cowork, and Claude Code sessions — with no
|
|
23
|
+
version marker or changelog kept at any point. `backups/pdfmd.py.bak-*` has
|
|
24
|
+
snapshots from a handful of points in that history, but not from the
|
|
25
|
+
ChatGPT/Codex/Cowork era at all, so reconstructing an honest "v1.x" changelog
|
|
26
|
+
from what's actually on disk isn't possible — only a partial,
|
|
27
|
+
misleadingly-precise-looking one. Decided 2026-09-16 (the author): start real
|
|
28
|
+
changelog tracking now, at 2.0.0, rather than fabricate the gap. If the full
|
|
29
|
+
history ever matters, it would need pulling the actual chat transcripts from
|
|
30
|
+
each of those tools, not guessing from diffs between inconsistent backup
|
|
31
|
+
snapshots.
|
|
32
|
+
|
|
33
|
+
**Update, 2026-10-05:** an approximate outline of the earlier history has since
|
|
34
|
+
been reconstructed, at the very end of this file (see "Before 2.0.0 —
|
|
35
35
|
reconstructed history"), from the saved Codex transcripts and the surviving
|
|
36
|
-
backup snapshots. It is explicitly NOT reliable: its 1.x numbers are
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
backup snapshots. It is explicitly NOT reliable: its 1.x numbers are invented,
|
|
37
|
+
snapshot-based entries cover windows of days rather than single changes, and
|
|
38
|
+
anything done in unsaved sessions (other ChatGPT chats, claude.ai, Cowork) is
|
|
39
|
+
missing. Real tracking still begins at 2.0.0.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## v3.30.32 — 2026-10-26
|
|
44
|
+
|
|
45
|
+
Pre-edit state: commit `0f6d680` (v3.30.31). Issue 15.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- A TeX Live or MiKTeX that is installed but not on `PATH` was reported as "no TeX engine installed": on Windows `--doctor` listed only Typst, the default run fell back to Typst without a word (a document with `\ce` or `\SI` built without LaTeX) and `-e tex` stopped with an error. When no TeX engine is on `PATH`, pdfmd now looks in the usual places (TeX Live: `C:\texlive\<year>\bin\*`, `%ProgramFiles%\texlive`, `~/texlive`, `/usr/local/texlive/<year>/bin/*`, `/Library/TeX/texbin`; MiKTeX: `%LOCALAPPDATA%\Programs\MiKTeX`, `%ProgramFiles%\MiKTeX*`), newest year first, and puts the folder it found behind `PATH` for its own runs. `PDFMD_TEX_BIN` names a folder for an install in an unusual place. `--doctor` prints `NOTE TeX found at ..., which is not on PATH`. Nothing changes when a TeX engine is on `PATH`.
|
|
50
|
+
- The message of `-e tex` with nothing found now says the engines are not installed "on PATH".
|
|
51
|
+
- `-e lualatex.exe` (and `-e typst.exe,lualatex.EXE`): a trailing `.exe` is dropped from an engine word, so the name Windows shows for the engine works. A word with a path in it is left as written.
|
|
52
|
+
|
|
53
|
+
### Tests
|
|
54
|
+
|
|
55
|
+
- `tests/test_tex_bin.py`. `tests.test_calc.PartBuiltAlone` shows Pandoc's own error when its first run fails (it hid it behind `check=True`).
|
|
56
|
+
- Windows with MiKTeX (the non-gating `windows-latex` job, found by a short debug run): the job puts poppler's folder in front of Git Bash's, whose `pdftotext` is xpdf's (no `-bbox`, Latin-1 output); the poppler calls in the tests decode UTF-8 with `errors="replace"`; `test_calc` writes Pandoc's discarded output to a file (Pandoc cannot open `nul`); `test_check` compares paths with `/`; `test_embedding` passes `-e lualatex`, not `lualatex.EXE` (which pdfmd now also accepts). Test code and CI only.
|
|
57
|
+
|
|
58
|
+
## v3.30.31 — 2026-10-26
|
|
59
|
+
|
|
60
|
+
Pre-edit state: commit `16fad47` (v3.30.30 + the CLAUDE.md rule).
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- A failed TeX run names its cause when the log has a known signature: a second line under the `WARN ... failed` line, `cause: the LaTeX package soul.sty is missing; install it (tlmgr install soul; Debian/Ubuntu: apt install texlive-plain-generic)`. Known: a missing `.sty` (the package that has it for lmodern, soul, selnolig, lua-ul, luacolor; `tlmgr install NAME` for any other), a missing luaotfload, a corrupt luaotfload font database, an unwritable TeX cache. The signatures come from real logs of an outside reviewer's six cases (2026-10-14). Once per document, not once per engine tried.
|
|
65
|
+
- Not done: the unwritable cache and the corrupt database reach pdfmd as Pandoc's bare `Fatal error occurred, no output PDF file produced!`, the cause is only in the engine's own console output, so those two are named only when the log text is at hand (not through Pandoc). Naming them needs a probe run (a one-page `lualatex` smoke document after such a failure, as `--doctor --deep` already has); queued.
|
|
66
|
+
|
|
67
|
+
### Tests
|
|
68
|
+
|
|
69
|
+
- `tests/test_tex_cause.py`.
|
|
70
|
+
- `.gitignore` lists `pdfmd-reviews/` (an outside reviewer's raw logs, kept in the working folder for the classifier; not part of the package: `MANIFEST.in` names the files it ships).
|
|
71
|
+
- Windows `native` CI (first run of the combined tree): `test_doc` reads `--help` output with `errors="replace"`, `test_install_calibre.Detach` and `test_sections.PartRanges` compare paths the way the platform spells them (`Path(...)`, `as_posix()`). Test code only.
|
|
72
|
+
|
|
73
|
+
## v3.30.30 — 2026-10-26
|
|
74
|
+
|
|
75
|
+
Pre-edit state: commit `69dc4a6` (v3.30.29). Issue 13.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- The PDF made by `-e calibre` lost its bookmarks with Calibre 9.16 (`tests.test_calibre.Builds.test_a_document` failed on every machine with it since 2026-10-14). The bookmarks were there in what `ebook-convert` wrote: pdfmd's own stamp of the finished PDF (`PdfmdVersions`, `Creator`) copied it with pypdf's `append()`, which drops the whole outline of a PDF whose bookmarks are typed `/Type /Outlines`, as Calibre 9.16 writes them. The stamp clones the document instead.
|
|
80
|
+
- Every `subprocess` call of pdfmd that reads text names its encoding (`utf-8`, undecodable bytes replaced): `text=True` alone decodes with the ANSI code page on Windows, and Pandoc, TeX, `fc-list` and `kpsewhich` write UTF-8, so a byte the page could not decode made the reader thread die and `.stdout` come back as `None` (issue 13: every build stopped in `installed_font_families()` with TeX Live's `fc-list` on the PATH; that call itself was repaired by `ec8cbfe`, unreleased until now). 46 calls in `pdfmd.py` and five helper modules.
|
|
81
|
+
|
|
82
|
+
### Tests
|
|
83
|
+
|
|
84
|
+
- `tests.test_partial_provenance.OutlineSurvives`, `tests.test_probes.SubprocessDecoding` (no `run`/`Popen`/`check_output` call with `text=` and no `encoding=`).
|
|
85
|
+
- `tests.test_chem.WhatItCannotConvert` (v3.30.25) runs pdfmd on a Typst route that needs Pandoc, so it is skipped without Pandoc, like its neighbours; the first CI run of the combined tree failed the `native` jobs on it.
|
|
86
|
+
|
|
87
|
+
## v3.30.29 — 2026-10-26
|
|
88
|
+
|
|
89
|
+
Pre-edit state: commit `470de88` (v3.30.28). Merged from PR 9 (`cloud/calc-checks`), squashed.
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
|
|
93
|
+
- `--check` reports the calculator's undefined names (`calc-undefined`, an error): a span, a `::: calc` line or an `{ans}` that uses a name nothing defines, with its line and its part, in document order. A document without a calculator never starts Pandoc for it.
|
|
94
|
+
- `--check` notes a name defined more than once (`calc-redefined`, severity `note`, a third severity that is counted and never fails `--check`, not even with `--strict`). A normal build says nothing about it.
|
|
95
|
+
- `calc: {export: FILE}`, `--calc-export FILE`, `PDFMD_CALC_EXPORT` (for `-j` workers): after a build, `name,value,digits` with one row per name, sorted, `%.17g`, written atomically, readable back as a `constants` file. Not written without names, with `calc` off, after a failed build, or for a part built alone without the seed. `--attach-source` and `--embed-metadata files` leave it out unless it is also read. A `--setup` row (`options.calc.export`), the inventory entry, the help topic, README and recipes.
|
|
96
|
+
|
|
97
|
+
## v3.30.28 — 2026-10-26
|
|
98
|
+
|
|
99
|
+
Pre-edit state: commit `b065576` (v3.30.27). Merged from PR 11 (`cloud/partial-provenance`), squashed; it met 3.30.23's partial history in `pdfmd.py`, resolved by keeping both.
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- A partial parts build (`report#intro`, `@60`) keeps the provenance of its PDF (Creator suffix, `PdfmdVersions`, `PdfmdBuildDate`, and a new `PdfmdPartial` naming the parts) and never stamps the `.md`. Word and OpenDocument files never carried these properties. With `stamp: {partial: true}` the "partial" entry of the history (3.30.23) is still written, after the PDF's provenance, by one function (`stamp_build`) that replaces `stamp_unless_partial`; the cache route's intermediate `.tex` stage writes neither.
|
|
104
|
+
- The snapshots of one build are one `BACKUP` line (`report.md + 6 parts -> .backups/, parts/.backups/ (...)`); the per-file lines follow only with `--verbose`.
|
|
105
|
+
|
|
106
|
+
### Tests
|
|
107
|
+
|
|
108
|
+
- `tests.test_partial_provenance`, `tests.test_backup_summary`.
|
|
109
|
+
|
|
110
|
+
## v3.30.27 — 2026-10-26
|
|
111
|
+
|
|
112
|
+
Pre-edit state: commit `b575748` (v3.30.26). Merged from PR 10 (`cloud/csv-notes`), squashed.
|
|
113
|
+
|
|
114
|
+
### Fixed
|
|
115
|
+
|
|
116
|
+
- The note under a cut `.csv` table states the fact only, only for the dimension that was cut (it said "2 of 2 columns" when no column was cut), without code spans. The advice moved to a once-per-table `WARN` on stderr.
|
|
117
|
+
- `--extract-inline-csv`, `--expand-tables` and `--check` know the bare `::: csv`.
|
|
118
|
+
- The stray `unittest.main()` in the middle of `tests/test_csv_table.py` is at the end.
|
|
119
|
+
|
|
120
|
+
### Added
|
|
121
|
+
|
|
122
|
+
- `note=short|off|full` (default `short`): a div's `note=`, `csv: {note}`, `--csv-note`, `PDFMD_CSV_NOTE`, a `--setup` row, the inventory entry, the help topic, README and recipes; in the Lua filter and in the built-in renderer. `full` is the only mode that keeps code spans (other writers get them as Pandoc writes them).
|
|
123
|
+
|
|
124
|
+
## v3.30.26 — 2026-10-26
|
|
125
|
+
|
|
126
|
+
Pre-edit state: commit `9b054bb` (v3.30.25). Merged from PR 8 (`cloud/portability`), squashed.
|
|
127
|
+
|
|
128
|
+
### Fixed (Windows)
|
|
129
|
+
|
|
130
|
+
- `report#section/sub` kept its slash on Windows (`Path()` turned it into a backslash, so the section was never found).
|
|
131
|
+
- A pager or editor typed with a Windows path (`C:\Program Files\...`) keeps its backslashes (`shlex` in POSIX mode ate them): `split_command()`.
|
|
132
|
+
- The calculator's seed run wrote Pandoc's output to the null device; Pandoc's binary writers cannot open `NUL` on Windows. It writes to a file in the scratch folder.
|
|
133
|
+
- A file with a kept source (`--keep-source`) whose line ends had become CRLF (git's `autocrlf`, an editor) "kept no pdfmd source", `--restore` could not give it back, and it was reported as edited. The trailer is read as LF text and the hash ignores the difference.
|
|
134
|
+
- The ASCII filter's helper (`--to ascii`) never started on Windows: its command and paths went through Pandoc's metadata with their backslashes. The paths have forward slashes, and the `WARN` says why the helper did not answer.
|
|
135
|
+
- TeX engines were started with a temporary folder under its Windows short name (`C:\Users\RUNNER~1\...`); the engine reads its command line as TeX, where `~` is active, and MiKTeX's LuaLaTeX opened no file. The label draft pass, the pictures drawn for Word files and the cache build failed there. `tex_argument()` gives the long name.
|
|
136
|
+
- A picture's folder was never taken out of a Word description on Windows.
|
|
137
|
+
- `--verbose` says how the label draft pass ended when it left no `.aux`; `provide_writable_places` creates the temporary `texmf-var`/`texmf-config` folders it points at.
|
|
138
|
+
- A busy disk image (macOS, `Calibre` install) is detached again before it is forced: four tries a second apart, then `-force`.
|
|
139
|
+
|
|
140
|
+
### Added
|
|
141
|
+
|
|
142
|
+
- The built-in parser (no Pandoc) draws an HTML `<table>`'s `colspan` and `rowspan` (`pdfmd_ansi/native.py`). Golden tests.
|
|
143
|
+
|
|
144
|
+
### Changed (CI, tests)
|
|
145
|
+
|
|
146
|
+
- The lint job runs `ruff check --select F` on the code and the tests (two unused locals, seven unused imports, a duplicated dictionary key; no behaviour changes). The Windows job (`windows-latex`) installs poppler, the fonts the tests name, and runs with Python's UTF-8 mode.
|
|
147
|
+
- The tests: the OS separator in what a run prints, Lua paths in forward slashes, `TEXINPUTS` with `os.pathsep`, a file to discard instead of `NUL`, line ends as Pandoc wrote them, a closed `.docx` before the folder is removed. Two `test_readonly_home.Builds` tests are skipped on Windows (MiKTeX's LuaLaTeX stops in luaotfload when LOCALAPPDATA is redirected).
|
|
148
|
+
|
|
149
|
+
## v3.30.25 — 2026-10-26
|
|
150
|
+
|
|
151
|
+
Pre-edit state: commit `abb652a` (v3.30.24). Issue 12.
|
|
152
|
+
|
|
153
|
+
### Fixed
|
|
154
|
+
|
|
155
|
+
- siunitx 2 style `\SI{1.8e-5}{}` and `\qty{1.8e-5}{}` (an empty unit) print the number alone, as `\num{1.8e-5}` does; `\si{}` and `\unit{}`
|
|
156
|
+
print nothing; the same for `\SIlist`, `\SIrange` and their `qty` names. The empty unit made the converter refuse the command, so in
|
|
157
|
+
a Typst, HTML or text output the command was left out of the text without a word, and a formula holding one (even beside a `\ce`) stayed raw
|
|
158
|
+
TeX, with a warning that only blamed the missing `pdftocairo`.
|
|
159
|
+
- A command or a formula the converter refuses is named in one `WARN` with the reason (`[pdfmd] WARN \SI{3}{\bogus} cannot be converted
|
|
160
|
+
(siunitx unit \bogus is not known); it is left out`), once per command, for the outputs the LaTeX filter does not run for.
|
|
161
|
+
|
|
162
|
+
### Tests
|
|
163
|
+
|
|
164
|
+
- `tests.test_chem.Siunitx.test_an_empty_unit_prints_the_number_alone`, `tests.test_chem.WhatItCannotConvert`.
|
|
165
|
+
|
|
166
|
+
## v3.30.24 — 2026-10-25
|
|
167
|
+
|
|
168
|
+
Pre-edit state: commit `de9c29a` (v3.30.23).
|
|
169
|
+
|
|
170
|
+
### Added
|
|
171
|
+
|
|
172
|
+
- `history:` is accepted in `pdfmd-options` (a document, a metadata file, the config) as another name for `stamp:`: the build history is that
|
|
173
|
+
one setting (`enabled`, `store`, `mode`, `scope`, `packages`, `partial`, ...), not a second one. `history: {store: file, partial: true}` is
|
|
174
|
+
`stamp: {store: file, partial: true}`; a field both set is taken from `stamp`. Older pdfmd versions do not know `history:`, so a shared
|
|
175
|
+
metadata file that must work with them keeps `stamp:`.
|
|
176
|
+
|
|
177
|
+
### Tests
|
|
178
|
+
|
|
179
|
+
- `tests.test_partial_history.HistoryAlias`.
|
|
180
|
+
|
|
181
|
+
## v3.30.23 — 2026-10-25
|
|
182
|
+
|
|
183
|
+
Pre-edit state: commit `4e38e85` (v3.30.22).
|
|
184
|
+
|
|
185
|
+
### Added
|
|
186
|
+
|
|
187
|
+
- **Draft builds in the history.** The `.hst` held full builds only: a build of some parts or sections (`pdfmd @60`, `doc#section`) writes no
|
|
188
|
+
BUILD NOTES line, and nothing took its place. With `pdfmd-options: {stamp: {partial: true}}` (or `--stamp-partial`; `--no-stamp-partial`
|
|
189
|
+
overrides a document that set it; a `--setup` row, `options.stamp.partial`) and the history in a file (`store: file` or `both`), such a build
|
|
190
|
+
leaves a new kind of entry, `partial`:
|
|
191
|
+
`... | partial | parts 50-calculations, 60-discussion; changed: 60-discussion; with nulabreport v1.28.20, pdfmd v3.30.23 | parts=... changed=... new=... shas=... out=...`.
|
|
192
|
+
`changed` compares each part's text with the last entry that saw it; a full build in parts mode now records every part's hash (`shas=`) for
|
|
193
|
+
that, and a part no entry has seen is `first seen` (`new=`). `--history` lists the entries as `partial`. Off by default (a history that
|
|
194
|
+
held full builds only gains lines); older pdfmd versions keep a `partial` line as a comment. A `partial` entry has no `sha`, so it is
|
|
195
|
+
never offered by `--history-restore`.
|
|
196
|
+
|
|
197
|
+
### Tests
|
|
198
|
+
|
|
199
|
+
- `tests.test_partial_history` (the entry round trip, the key, parts built and changed against a full build, first seen, off by default,
|
|
200
|
+
the flags, a section of a plain document).
|
|
201
|
+
|
|
202
|
+
## v3.30.22 — 2026-10-25
|
|
203
|
+
|
|
204
|
+
Pre-edit state: commit `85a9283` (v3.30.21).
|
|
205
|
+
|
|
206
|
+
### Added
|
|
207
|
+
|
|
208
|
+
- **A range of parts:** `pdfmd @45-60` (or `--section 45-60`, `doc#45-60`) builds every top-level part numbered from 45 to 60, ends included,
|
|
209
|
+
whether or not a part has that exact number; a reversed range is the same, and it joins other names (`@10,45-60`). A part literally named
|
|
210
|
+
`45-60` still wins, and a range that covers no part is the usual "matches no part" error. Only digits on both sides count, so
|
|
211
|
+
`10-introduction` is a name as before.
|
|
212
|
+
|
|
213
|
+
### Tests
|
|
214
|
+
|
|
215
|
+
- `tests.test_sections.PartRanges`.
|
|
216
|
+
|
|
217
|
+
## v3.30.21 — 2026-10-25
|
|
218
|
+
|
|
219
|
+
Pre-edit state: commit `b20d62b` (v3.30.20).
|
|
220
|
+
|
|
221
|
+
### Fixed
|
|
222
|
+
|
|
223
|
+
- **A build printed `{ans}` lines and bare names instead of numbers (a regression of 3.30.20).** The new `.calc-table` handler was chained
|
|
224
|
+
to the `::: calc` handler with `calc_div(div) or calc_table_div(div)`, and `or` keeps only the first of the two values the first handler
|
|
225
|
+
returns, so the walk went into the div it had just written and the second pass counted its places twice: every span and `{ans}` line after
|
|
226
|
+
the first calc div failed with "the passes disagree about this place" (LR5: 71 lines in the Calculations, the numbers of the Discussion as
|
|
227
|
+
code). The two results are passed on now.
|
|
228
|
+
|
|
229
|
+
### Added
|
|
230
|
+
|
|
231
|
+
- When three or more places of a document could not be evaluated, the calculator ends with one line,
|
|
232
|
+
`WARN calc: 21 places were not evaluated (each is above); the output shows their code or text where the numbers belong`, so a build
|
|
233
|
+
that failed on a whole section is not mistaken for one that printed a single odd span (`--strict` already fails on any WARN).
|
|
234
|
+
- A `.calc-table` caption can be written inside the div: a line `Table: Student's *t*, 95%` (or `: ...` after a blank line); `caption="..."` still works.
|
|
235
|
+
|
|
236
|
+
### Tests
|
|
237
|
+
|
|
238
|
+
- `tests.test_calc.Statistics`: a silent span glued to an `{ans}` line (the four-line example of the report), the summary line, the three
|
|
239
|
+
caption spellings.
|
|
240
|
+
|
|
241
|
+
## v3.30.20 — 2026-10-25
|
|
242
|
+
|
|
243
|
+
Pre-edit state: commit `4f34b63` (v3.30.19).
|
|
244
|
+
|
|
245
|
+
### Added
|
|
246
|
+
|
|
247
|
+
- **Statistics in the calculator:** `sum mean median`, `var sd stdev sem` (sample, n - 1), `rsd` (relative standard deviation in percent),
|
|
248
|
+
`ci(0.95, x1, x2, ...)` (half-width of the confidence interval, t * s / sqrt(n), through the existing `qt`), `gstat(x1, x2, ...)`
|
|
249
|
+
(Grubbs' G) and `gcrit(n, alpha)` (its two-sided critical value; agrees with SciPy to 10 figures). They take names as well as numbers:
|
|
250
|
+
`` ~`R_E4 / mean(R_A, R_B2)`{=0dp} times lower `` works as written. These names stay free for a variable (`mean = 5` and `mean(1, 3)`
|
|
251
|
+
side by side, so no existing document breaks); the older function names are still reserved.
|
|
252
|
+
- **`::: calc-table`, a line `t# tinf`, `:::`** (or `::: {.calc-table names="t# tinf"}`): a real table (Pandoc `Table`, so the document's table styling, widths and numbering apply)
|
|
253
|
+
of calculator names with their values, in the digits each was defined with (`digits="3dp"` for all). The names are the text of the div or `names=`: names and patterns
|
|
254
|
+
(`*` any characters, `?` one, `#` a run of digits, so `t#` leaves out `t_welch` and `tinf`), a pattern in natural order (t2 before t10); `labels="t1=$t_{1}$; V=Volume"` by name or one per row in order (Markdown and math
|
|
255
|
+
work); `head="Symbol; Value"` or `none`; `caption="..."` (Markdown); the identifier, other classes and attributes go to the table, so
|
|
256
|
+
`{#tbl:t}` can be cited. A name counts as defined at the place of the div, as for a span there; in a part built alone the names of the
|
|
257
|
+
parts above it are known (3.30.19). A name that is not defined, a pattern with no match, a value that cannot be computed: one `WARN
|
|
258
|
+
calc-table` naming it; a div without names prints its own code. `::: {.calc-table t*}` is not a div for Pandoc (braces take only
|
|
259
|
+
`.class`, `#id`, `key=value`) and gets one WARN with the right spelling instead of printing as text.
|
|
260
|
+
|
|
261
|
+
### Tests
|
|
262
|
+
|
|
263
|
+
- `tests.test_calc.Statistics` (13): the summary functions against Python's `statistics`, `ci` and `gcrit` against SciPy, the sentence of a
|
|
264
|
+
Discussion, bad calls (one WARN each, the code kept), the new names still free for a variable and the old ones reserved, the table
|
|
265
|
+
(natural order, labels by name, header, caption and identifier on a real `Table`, the place of the div, digits, everything that goes
|
|
266
|
+
wrong, the malformed spelling), and the detection for a document with only a table.
|
|
267
|
+
|
|
268
|
+
## v3.30.19 — 2026-10-24
|
|
269
|
+
|
|
270
|
+
Pre-edit state: commit `65dc1da` (v3.30.18).
|
|
271
|
+
|
|
272
|
+
### Fixed
|
|
273
|
+
|
|
274
|
+
- **A part (or a section) built alone knows the calculator's names from what stands above it.** `pdfmd @60` printed nothing where
|
|
275
|
+
`` `mean_A`{=2dp} `` had been (the name is defined in part 50), and the build was silent about it. Before the real pass pdfmd now has
|
|
276
|
+
Pandoc read the scaffold and the parts left out that stand above the last one built, with `calc.lua` only (`-t native -o /dev/null`,
|
|
277
|
+
about a second for 128 names); `calc.lua` writes every name with the value of its last definition (`%.17g`, full precision) and its
|
|
278
|
+
digits (`-M pdfmd-calc-dump=FILE`), and the build reads the file back (`-M pdfmd-calc-seed=FILE`) as definitions above everything in the
|
|
279
|
+
document, after the constants file. A section of an ordinary document (`doc#discussion`) does the same with the sections above it. The
|
|
280
|
+
build says `NOTE calc: 128 names taken from parts 10-50 (evaluated, not typeset)`. Nothing is stored between builds, so nothing is
|
|
281
|
+
stale. HTML, Word, Typst and the other writers that number the whole document and keep the part already evaluated everything.
|
|
282
|
+
- **A span the calculator cannot evaluate no longer vanishes.** `` `name`{=2dp} `` is a raw inline of the format `2dp` for Pandoc; the
|
|
283
|
+
filter put that item back, and the LaTeX and other writers drop a raw inline of a format they do not know, so the sentence had a hole and
|
|
284
|
+
the WARN's "the code is kept" was untrue. The code is kept now (printed as code), with the one WARN, which names the format as written
|
|
285
|
+
(`` `ghost`{=2dp} ``).
|
|
286
|
+
|
|
287
|
+
### Added
|
|
288
|
+
|
|
289
|
+
- `pdfmd-options: {seed-calc: auto|scan|off}` and `--seed-calc MODE` (a `--setup` row under Cache): `off` gives the seeding up. `auto` and
|
|
290
|
+
`scan` are the same.
|
|
291
|
+
|
|
292
|
+
### Tests
|
|
293
|
+
|
|
294
|
+
- `tests.test_calc.PartBuiltAlone`: the dump and seed through the filter alone (the last value of a redefined name at full precision, its
|
|
295
|
+
digits, a name with no digits); a split report's part 60 built alone against the full build (same text, the NOTE); an unknown name printed
|
|
296
|
+
as code with exactly one WARN in both builds; the setting in the document and on the command line; a part that redefines a name is not
|
|
297
|
+
overridden; a part with no calculator and a document without one are built as before; sections above a section (`doc#Discussion`,
|
|
298
|
+
`doc#Notes`).
|
|
299
|
+
|
|
300
|
+
## v3.30.18 — 2026-10-24
|
|
301
|
+
|
|
302
|
+
Pre-edit state: commit `ad8efcd` (v3.30.17).
|
|
303
|
+
|
|
304
|
+
### Fixed
|
|
305
|
+
|
|
306
|
+
- **Every file pdfmd rewrites is written atomically**: to a temporary file in the same folder, then renamed over the old one (`os.replace`),
|
|
307
|
+
so a crash, a full disk or ^C leaves the old file whole. The mode of the replaced file is kept and a symlink is written through. Covers the
|
|
308
|
+
BUILD NOTES stamp, outputs, `--history-restore`, the backup options, `--extract-tables`, restored attachments, the config and metadata
|
|
309
|
+
files of `--setup` and `--init-backups --global`, `--init-config`, the places that rewrite a finished PDF, and the built-in renderers. A
|
|
310
|
+
process killed between write and rename leaves a `.NAME....part` file.
|
|
311
|
+
- **A read-only home, cache or document folder no longer stops a build.** LuaLaTeX failed with `luaotfload: no writeable cache path`, `--cache`
|
|
312
|
+
crashed on a `PermissionError`, and the import itself died when the home could not be looked into. What cannot be written goes to a
|
|
313
|
+
temporary folder of the run (the cache folder, `TEXMFVAR`, `TEXMFCONFIG`, also for child processes), with one `NOTE`; Pandoc runs in a
|
|
314
|
+
temporary folder when the document's folder is read-only, the document's folder staying on `--resource-path` and `TEXINPUTS`.
|
|
315
|
+
- **Probes have a timeout.** A version or capability probe (`pandoc --version`/`--help`/`--list-extensions`, `kpsewhich`, `lualatex`/`typst
|
|
316
|
+
--version`, the checks after `--install typst|node|d2|tectonic`, the Pandoc reference-document probe) is cut after 10 seconds
|
|
317
|
+
(`PROBE_TIMEOUT`); one that does not answer counts as "not available" (an `AUTO PROBE` line under `--verbose`). Build commands never get a
|
|
318
|
+
timeout.
|
|
319
|
+
- **A missing `pdfmd_lua` is loud.** A lone copy of `pdfmd.py` gave silence while the Lua filters did nothing; it is one `WARN lua:` per run
|
|
320
|
+
now. A document that needs no filter says nothing.
|
|
321
|
+
- **Windows: a font list the console could not decode** came back as `None` and stopped the whole build. `installed_font_families()` and the
|
|
322
|
+
fontconfig and `luaotfload-tool` calls decode the child's output themselves, replacing what cannot be read.
|
|
323
|
+
|
|
324
|
+
### Changed
|
|
325
|
+
|
|
326
|
+
- **CI** (`test.yml`, which `publish.yml` calls; `publish.yml` itself is untouched): a `linux-pandoc` job (Pandoc 3.1.3 and latest, LuaLaTeX
|
|
327
|
+
and XeLaTeX, the whole suite) and a `golden` job (`scripts/vendor_inkmd.py --check` and the golden test) now gate a release. A `lint` job
|
|
328
|
+
(`ruff --select F`, 39 findings, none silenced) and a `windows-latex` job (MiKTeX, the whole suite) run but do not gate.
|
|
329
|
+
|
|
330
|
+
### Tests
|
|
331
|
+
|
|
332
|
+
- New `tests.test_atomic`, `test_readonly_home`, `test_probes`, `test_lua_missing`; `test_auto_states` no longer
|
|
333
|
+
flakes when a scratch folder name ends in `a`.
|
|
334
|
+
- From the cloud session (branch `cloud/robustness-ci`, PR 7). The `windows-latex` job fails on 55 tests and 14 errors of 1652 (Windows paths
|
|
335
|
+
in test Lua files, a `\` in `--section`, MiKTeX refusing to run as an administrator, missing fonts); it was the first whole-suite run on
|
|
336
|
+
Windows and the job does not gate.
|
|
337
|
+
|
|
338
|
+
## v3.30.17 — 2026-10-24
|
|
339
|
+
|
|
340
|
+
Pre-edit state: commit `fbe5640` (v3.30.16).
|
|
341
|
+
|
|
342
|
+
### Added
|
|
343
|
+
|
|
344
|
+
- **`--to ansi` / `--view` draw spanning table cells.** A cell with a colspan or rowspan is drawn across the merged columns and rows, its text
|
|
345
|
+
wraps to the merged width, the borders inside it are left out and each crossing uses the box character its neighbours ask for. A table
|
|
346
|
+
without spans is drawn exactly as before.
|
|
347
|
+
- **Coloured code.** Fenced code in python, bash/sh/shell/zsh, json, yaml, c, cpp, java, javascript/typescript, rust and go (and the usual
|
|
348
|
+
aliases) gets keywords, strings, comments and numbers in four 256-colour codes, with no dependency. With colour off (`NO_COLOR`,
|
|
349
|
+
`--color never`, a pipe) or an unknown language the block is exactly as before.
|
|
350
|
+
- **Stacked display math.** A `$$..$$` that is the whole paragraph is drawn on several rows where it has `\frac`/`\dfrac`/`\tfrac`, `\sqrt`
|
|
351
|
+
(with an overbar, and `\sqrt[n]{x}`), big operators with limits (`\sum \prod \coprod \int \iint \iiint \oint`) and `\lim`/`\max`/`\min`/
|
|
352
|
+
`\sup`/`\inf` with the limit below. A formula with anything that cannot be stacked, or wider than the line, prints its linear form whole,
|
|
353
|
+
never half stacked. Inline math stays linear.
|
|
354
|
+
- **`options.pager_command`** (`pdfmd-options: {pager_command: "bat --paging=always"}`, also spelled `pager-command`; a word list works too),
|
|
355
|
+
the pager of `--view`. Order: `$PDFMD_PAGER`, `$PAGER`, the setting, `less -FRX`. `--setup` row `options.pager_command`, also offered per folder.
|
|
356
|
+
|
|
357
|
+
### Tests
|
|
358
|
+
|
|
359
|
+
- `tests.test_ansi` extended with golden tests (a header spanning two columns, a rowspan of 2, both at once; the code colours; stacked math).
|
|
360
|
+
A fuzz run of about 9000 random span tables had no misaligned line.
|
|
361
|
+
- From the cloud session (branch `cloud/ansi-limits`, PR 6). Not verified: a real terminal and pager (checked through a pipe). The no-Pandoc
|
|
362
|
+
tier (`pdfmd_ansi/native.py`) builds tables without spans.
|
|
363
|
+
|
|
364
|
+
## v3.30.16 — 2026-10-24
|
|
365
|
+
|
|
366
|
+
Pre-edit state: commit `05c6e73` (v3.30.15).
|
|
367
|
+
|
|
368
|
+
### Fixed
|
|
369
|
+
|
|
370
|
+
- The built-in renderers (inkmd, md2pdf) set `b5`, `a6`, `b4`, `b6`, `executive`, `folio`, `a0` to `a2`, `c4` and `c5` as A4 in silence;
|
|
371
|
+
they have real sizes now and honour `landscape` (`papersize: a4,landscape`, `classoption`, `geometry`). A paper they do not know is a
|
|
372
|
+
`NOTE` and A4.
|
|
373
|
+
- `pdfmd --doc changelog 1` returned a block hidden in an HTML comment: `--doc` skips HTML comments now, and `--doc search` skips the
|
|
374
|
+
README's contents list.
|
|
375
|
+
- `docs/agents.md` counted its commands wrongly and called attaching the source the default (it is off unless asked). The README's
|
|
376
|
+
`pdfmd --doc readme tectonic` example and its sample search output were invented; they are real output now. The README no longer calls
|
|
377
|
+
macOS's Python 3.9 too old.
|
|
378
|
+
- The zipapp carries the documents, so `pdfmd --doc` works in a `.pyz`. `pyproject.toml` requires `setuptools>=77` (PEP 639 license fields).
|
|
379
|
+
|
|
380
|
+
### Changed
|
|
381
|
+
|
|
382
|
+
- README: a contents list at the top and a shorter "Why".
|
|
383
|
+
- `pdfmd_docs.version()` derives the version the docs name from `PDFMD_VERSION`, and `tests.test_docs_consistency` fails on drift.
|
|
384
|
+
- `CHANGELOG.md` header: the patch rule and the `PDFMD_VERSION` location are described as CLAUDE.md states them, and the "why this starts
|
|
385
|
+
at 2.0.0" paragraph is visible text instead of a comment.
|
|
386
|
+
|
|
387
|
+
### Removed
|
|
388
|
+
|
|
389
|
+
- Four unused functions: `pandoc_missing_message`, `kpsewhich_path_text`, `normalize_stamp_value`, `normalize_backup_value`.
|
|
390
|
+
|
|
391
|
+
### Tests
|
|
392
|
+
|
|
393
|
+
- New `tests.test_docs_consistency`; `test_native`, `test_doc` and `test_packaging` extended.
|
|
394
|
+
- One reviewer finding was skipped as wrong: the stamp is written before the backup on purpose (a backup per compile would flood the folder).
|
|
395
|
+
- From the cloud session (branch `cloud/review-fixes`, PR 5). Not verified: md2pdf with `executive` and `folio`.
|
|
396
|
+
|
|
397
|
+
## v3.30.15 — 2026-10-24
|
|
398
|
+
|
|
399
|
+
Pre-edit state: commit `c43923e` (v3.30.14).
|
|
400
|
+
|
|
401
|
+
### Added
|
|
402
|
+
|
|
403
|
+
- **An indent div takes its value in the class word.** `::: .indent=3in`, `::: indent=3in`, `::: indent-3in`, `::: {.indent-3in}` and
|
|
404
|
+
`::: {.indent=3in}` (and `::: .indent`, `::: .noindent`) all mean `{indent=3in}`, through the same length parser (false, true, a number,
|
|
405
|
+
units). Pandoc reads the unbraced forms as a single class word, so only the braced `{indent=...}` carried a value before.
|
|
406
|
+
- **A near miss warns.** A class that looks like an indent div but is none of the accepted spellings (`.indent3in`, `.noindent=2em`) gets one
|
|
407
|
+
`WARN indent: '<class>' is not an indent div; write ::: {indent=3in} (or ::: noindent)` per run and is left as written. A project's own
|
|
408
|
+
`.indented`, `.indentation` and `.indent-block` are not looked at.
|
|
409
|
+
- **A bare `::: csv` builds a table** like `::: {.csv}`, in the Pandoc route and in the built-in renderer, when it holds inline data.
|
|
410
|
+
`file=` and `align=` still need the braces; `::: csvfile` and `::: csv-x` do not match.
|
|
411
|
+
- **`csv: off`** (`pdfmd-options: {csv: off}`, short for `csv: {enabled: false}`), `--csv` / `--no-csv` and `PDFMD_CSV` (for `-j` workers)
|
|
412
|
+
switch the csv-in-a-div feature off: the filter is not added and the div stays a plain div with its text, with no WARN, also in the
|
|
413
|
+
no-Pandoc renderer. A mapping such as `csv: {rows: 30}` still works and leaves tables on. The command line wins over the options.
|
|
414
|
+
`--setup` row `options.csv.enabled`.
|
|
415
|
+
|
|
416
|
+
### Tests
|
|
417
|
+
|
|
418
|
+
- New `tests.test_csv_switch` (17 tests); `tests.test_indent` extended (every accepted spelling, a measured x position in a real PDF, near
|
|
419
|
+
misses that warn, `.indented` that does not). One assertion fixed: it now accepts Pandoc 3.1's `\hypertarget` as well as `\label`.
|
|
420
|
+
- From the cloud session (branch `cloud/small-patches`, PR 4).
|
|
421
|
+
|
|
422
|
+
## v3.30.14 — 2026-10-24
|
|
423
|
+
|
|
424
|
+
Pre-edit state: commit `2c0b9a3` (v3.30.13).
|
|
425
|
+
|
|
426
|
+
### Added
|
|
427
|
+
|
|
428
|
+
- **Room around a `::: calc` div, and optional centring.** The block ran straight into the paragraphs above and below it. It has `0.75em`
|
|
429
|
+
of room on each side now (`calc: {space: 0.75em}`, `--calc-space LENGTH|off`, `PDFMD_CALC_SPACE`; pt, mm, cm, in or em; a `--setup` row):
|
|
430
|
+
`\par\addvspace{..}` for LaTeX and Beamer (it merges with the paragraph skip next to it instead of adding to it), `#v(.., weak: true)`
|
|
431
|
+
for Typst, vertical padding on a wrapping `<div>` for HTML and the other HTML writers. `calc: {align: center}` / `--calc-align center`
|
|
432
|
+
(left is the default; a `--setup` row) centres the lines: a `\begingroup\centering` group, `#align(center)[..]`, `text-align: center`.
|
|
433
|
+
Markdown, Word and the plain writers get the block as before. A hidden `::: {.calc .hide}` div has no room, as it prints nothing.
|
|
434
|
+
- **Ctrl-C is one line.** An interrupted run printed a page of traceback. It prints `Interrupted (Ctrl-C).` and exits with status 130 now
|
|
435
|
+
(`main()` wraps `run_main()`); Pandoc and the engines get the signal from the terminal, and the temporary folders are removed.
|
|
436
|
+
|
|
437
|
+
### Tests
|
|
438
|
+
|
|
439
|
+
- `tests.test_calc.Switches.test_a_calc_div_has_room_around_it_and_can_be_centred`, `test_a_bad_space_or_align_is_an_error_and_the_other_writers_get_nothing`;
|
|
440
|
+
the exact argument lists now carry `pdfmd-calc-space=0.75em`; `tests.test_interrupt`.
|
|
441
|
+
- Measured in a LuaLaTeX PDF: the gap between the paragraphs around a calc line grew by the room set (2 cm: +113 pt), and `align: center`
|
|
442
|
+
moved the block's left edge from the margin to the middle of the line.
|
|
443
|
+
|
|
444
|
+
## v3.30.13 — 2026-10-24
|
|
445
|
+
|
|
446
|
+
Pre-edit state: commit `67e53d2` (v3.30.12).
|
|
447
|
+
|
|
448
|
+
### Added
|
|
449
|
+
|
|
450
|
+
- **A trailing comment in a statement block.** In a ```` ```{=calc} ```` block, a `::: {.calc .hide}` div and the constants file a `#` starts
|
|
451
|
+
a comment to the end of the line, so `M_Mg <- 24.305 # g/mol` defines `M_Mg`; a whole-line `#` was skipped already. Before, the `#` was read
|
|
452
|
+
as arithmetic: the WARN "'#' is not arithmetic" and an undefined name for every later use. `--` is not a comment (it can be a double minus).
|
|
453
|
+
|
|
454
|
+
### Tests
|
|
455
|
+
|
|
456
|
+
- `tests.test_calc.Blocks.test_a_hash_starts_a_comment_at_the_end_of_a_line_too`.
|
|
457
|
+
|
|
458
|
+
## v3.30.12 — 2026-10-24
|
|
459
|
+
|
|
460
|
+
Pre-edit state: commit `52f3d0f` (v3.30.11).
|
|
461
|
+
|
|
462
|
+
The calculator's constants file is part of the source that `--attach-source`, `--keep-source` and `--embed-metadata files` carry.
|
|
463
|
+
|
|
464
|
+
### Fixed
|
|
465
|
+
|
|
466
|
+
- **A constants file named in `metadata.yaml` was left out.** `calc: {constants: tables/constants.csv}` was found only when the document's own
|
|
467
|
+
text mentioned the path (the scan for referenced files), so a file named in a metadata file, the usual place for a report, was not attached
|
|
468
|
+
and `pdfmd --restore` gave back a source that failed with "no file". It travels now like the `.hst` history file (`add_calc_constants`),
|
|
469
|
+
and comes back under the same relative path. A constants file outside the document's folder gets the usual "outside" warning.
|
|
470
|
+
|
|
471
|
+
### Tests
|
|
472
|
+
|
|
473
|
+
- `tests.test_calc.Blocks.test_the_constants_file_named_in_a_metadata_file_travels_with_the_source`.
|
|
474
|
+
|
|
475
|
+
## v3.30.11 — 2026-10-24
|
|
476
|
+
|
|
477
|
+
Pre-edit state: commit `2e35666` (v3.30.10).
|
|
478
|
+
|
|
479
|
+
Definitions in bulk for the calculator: a report that needs 14 constants (t values, a stock concentration, molar masses) kept them as a run of
|
|
480
|
+
hidden `` `name = value`{=} `` spans on one line (issue file 2, B).
|
|
481
|
+
|
|
482
|
+
### Added
|
|
483
|
+
|
|
484
|
+
- **A block of statements.** ```` ```{=calc} ```` (Pandoc's native raw block, lines intact), one statement per line, each as the text of a
|
|
485
|
+
`{=}` span: `name = value` or `name <- value` stores and prints nothing, a line with `{ans}` prints (one paragraph per line), blank lines and
|
|
486
|
+
`#` lines are skipped, a failing line is kept as code with one WARN and the rest goes on.
|
|
487
|
+
- **```` ```{=} ```` works as ```` ```{=calc} ````.** Pandoc does not read an empty raw format as a block: it is a paragraph with one code
|
|
488
|
+
span whose line breaks are gone. `calc.lua` finds those paragraphs and reads the lines again from the input files Pandoc was given
|
|
489
|
+
(`PANDOC_STATE.input_files`), matching them by their text in document order; a fence shown inside a longer fence is not touched. From
|
|
490
|
+
standard input there is no file: one WARN says to write `{=calc}`.
|
|
491
|
+
- **`::: {.calc .hide}`** (also `.hidden`): the same statements, nothing printed. Its lines are read as Markdown.
|
|
492
|
+
- **`` `code`{=calc} ``** is `` `code`{=} `` (Pandoc's own raw attribute). A ```` ```{.calc} ```` code block stays a code block, never calculated.
|
|
493
|
+
- **`calc: {constants: FILE}`**, `--calc-constants FILE`, `PDFMD_CALC_CONSTANTS`, a `--setup` row: a CSV (comma, semicolon or tab; columns
|
|
494
|
+
`name,value[,digits[,note]]`; header row optional; `#` lines skipped) read before the document, found beside the document, beside its
|
|
495
|
+
metadata files, then in the current folder. The `digits` column is the format that name remembers for `{subs}` and a bare `` `t18`{=} ``.
|
|
496
|
+
pdfmd turns the file into statements and passes them as `-M pdfmd-calc-constants`; the document defining a name again gets the usual WARN.
|
|
497
|
+
- The filter is also added for a document that has only such a block.
|
|
498
|
+
|
|
499
|
+
Tests: `tests.test_calc.Blocks` (12: the block, the empty-format fence from a file and from standard input, a fence inside a longer one,
|
|
500
|
+
fourteen constants, the hidden div, failing lines, detection, the constants file by option, flag and variable, a semicolon file, the errors).
|
|
501
|
+
README "Calculations inside the text" 6, `docs/recipes.md` 1.11.
|
|
502
|
+
|
|
503
|
+
## v3.30.10 — 2026-10-24
|
|
504
|
+
|
|
505
|
+
Pre-edit state: commit `735569e` (v3.30.9).
|
|
506
|
+
|
|
507
|
+
The calculator gets the normal and Student's t distributions, for a lab report that needs a 95 % two-tailed t at 13, 16, 18, 93 or
|
|
508
|
+
13.947 degrees of freedom, none of which a printed table lists.
|
|
509
|
+
|
|
510
|
+
### Added
|
|
511
|
+
|
|
512
|
+
- **`qt(p, df)`, `pt(t, df)`, `qnorm(p)`, `pnorm(z)`** in `pdfmd_lua/calc.lua`, lower tail as in R, no library. `qnorm` is Wichura's AS 241;
|
|
513
|
+
`qt` inverts the incomplete beta function (continued fraction, bisection on the tail so a small `p` keeps its figures), exact for 1 and 2
|
|
514
|
+
degrees of freedom, and a Cornish-Fisher series above 1000. `df` may be a fraction or `Inf` (`qt(0.975, Inf)` is `qnorm(0.975)`), a name
|
|
515
|
+
works (`qt(0.975, df_w)`). `p` outside (0, 1) or `df <= 0` keeps the text with one WARN. The constant `Inf` is new.
|
|
516
|
+
- Accuracy against R 4.6.1 and SciPy: `qnorm` and `pnorm` about 1e-15; `qt` about 1e-12 (3e-11 for `p` near 1e-12); `pt` about 1e-12 up to
|
|
517
|
+
1000 degrees of freedom and 1e-9 above. `qt(0.975, df)` equals R's to the 12 figures it prints for df 1, 2, 3, 6, 8, 9, 10, 13, 16, 18, 93,
|
|
518
|
+
96, 13.947016149 and Inf.
|
|
519
|
+
- **Changed:** `qt`, `pt`, `qnorm`, `pnorm` and `Inf` are now built-in names, so a variable called `pt` is refused like `sin` (it was free
|
|
520
|
+
before; the calculator is unreleased).
|
|
521
|
+
|
|
522
|
+
Tests: `tests.test_calc.Distributions` (R's table, 105 SciPy values, `Inf`, the symmetry, a name as df, the domain errors). README, the
|
|
523
|
+
docstring.
|
|
524
|
+
|
|
525
|
+
## v3.30.9 — 2026-10-24
|
|
526
|
+
|
|
527
|
+
Pre-edit state: commit `cbcd7a5` (v3.30.8).
|
|
528
|
+
|
|
529
|
+
A `::: calc` div has no first-line indent. The author wrote `::: noindent` around every calc block of a report whose document indents
|
|
530
|
+
its paragraphs; the div now does it itself.
|
|
531
|
+
|
|
532
|
+
### Added
|
|
533
|
+
|
|
534
|
+
- **`calc: {noindent: true}`, on by default.** `pdfmd-options: {calc: {noindent: false}}` gives the div the document's own indent again;
|
|
535
|
+
`--calc-noindent` / `--no-calc-noindent` (the command line wins), `PDFMD_CALC_NOINDENT` for `-j` workers, a `pdfmd --setup` row.
|
|
536
|
+
`calc.lua` puts `indent="false"` on the finished div and `indent.lua` (now also added whenever a calc div is in the text and the
|
|
537
|
+
calculator and this option are on) does the rest per writer: LaTeX and Beamer `\begingroup\setlength{\parindent}{0pt}` ... `\endgroup`,
|
|
538
|
+
Typst a `#set par(first-line-indent: 0pt)` block, HTML a `text-indent` style. Markdown, Word and the plain-text writers get no attribute
|
|
539
|
+
and no NOTE (Word follows with the indent item queued for docx/odt). An `indent=` or `.indent` / `.noindent` on the same div wins, and
|
|
540
|
+
an outer `::: noindent` works as before. The default is on because the `::: calc` div was never published (3.30.6 onward).
|
|
541
|
+
- A div that carries both classes is written `::: {.calc .noindent}`; `::: calc noindent` is not a Pandoc div (one bare word only) and is
|
|
542
|
+
left as text.
|
|
543
|
+
|
|
544
|
+
Tests: `tests/test_calc.py` (the default, an `indent=` and `.indent` that win, the option, the flags, the variable, other writers) and a
|
|
545
|
+
real LuaLaTeX PDF: body text starts at 86.9 pt, the calc lines at 72.0 pt. README "Calculations inside the text", `docs/recipes.md` 1.11.
|
|
546
|
+
|
|
547
|
+
## v3.30.8 — 2026-10-24
|
|
548
|
+
|
|
549
|
+
Pre-edit state: commit `56fe65a` (v3.30.7).
|
|
550
|
+
|
|
551
|
+
Two fixes and three additions to the calculator, found converting a whole Calculations part of a real lab report (about 130 `{ans}` lines)
|
|
552
|
+
to `{=}`, `{ans}` and `::: calc`.
|
|
553
|
+
|
|
554
|
+
### Fixed
|
|
555
|
+
|
|
556
|
+
- **A `` `...`{=} `` span inside a `::: calc` div sees the definition above it, not the last one.** The div's lines were registered before
|
|
557
|
+
the spans in them, so in `a = 1 + 1 = {ans:1dp}, so `a * 10`{=}` followed by the same line with `3 + 3`, both spans took the last `a`
|
|
558
|
+
(60 twice, no WARN). Lines and spans are now read together, line by line, in the order of the text, and the div no longer hands its
|
|
559
|
+
paragraphs to the document-wide pass a second time.
|
|
560
|
+
- **A `::: calc` line may carry anything after its last `{ans}`.** `y = 2 + 2 = {ans:1dp} ppm \ce{Ca^2+}` (raw TeX), a superscript, bold,
|
|
561
|
+
a link or `$..$` after the value used to leave the whole line as written, `{ans}` and all. Only the text up to the last marker has to be
|
|
562
|
+
plain; the rest is copied as it is. Formatting before the last `{ans}` is still refused with the WARN.
|
|
563
|
+
|
|
564
|
+
### Added
|
|
565
|
+
|
|
566
|
+
- **More than one `{ans}` in a span or a line.** After text that does not end in `=` it repeats the value just shown in its own format
|
|
567
|
+
(`= {ans:1dp}, so {ans:0dp}`); after text that ends in `=` it is a new calculation whose expression is the longest tail of that text
|
|
568
|
+
that reads as one (`so round(n) = {ans:0dp}`; `so k = round(n) = {ans:0dp}` also stores `k`).
|
|
569
|
+
- **`{subs}`: the expression with the names replaced by their values.** `ppm1 = C_dil * V / 100.0 * 40.078 * 1000 = {subs} = {ans:2dp} ppm`
|
|
570
|
+
prints `... = 0.002039 * 34.41 / 100.0 * 40.078 * 1000 = 28.12 ppm`. Each name shows the digits it was defined with (a hidden span's use
|
|
571
|
+
`calc: digits`); the computation always uses the full values. Literals, operators, functions, `pi` and `deg` stay as typed; a negative value
|
|
572
|
+
gets parentheses (`(-0.5)^2`) unless nothing before it could be read as a sign. `calc: {subs: true}` (also `--calc-subs` /
|
|
573
|
+
`--no-calc-subs`, `PDFMD_CALC_SUBS`, a `pdfmd --setup` row) puts the copy before every `{ans}` that follows an expression with names; a
|
|
574
|
+
lone name, an expression without names and a repeated `{ans}` get none. `{subs}` forces it on a line, `{nosubs}` or `{subs:off}` stops it.
|
|
575
|
+
- **`<-` assigns like `=`**: `` `t18 <- 2.10092204`{=} ``, `ppm1 <- C_dil * V = {ans:2dp}`.
|
|
576
|
+
|
|
577
|
+
The Unicode operators (`×`, `·`, `÷`, `−`) are now read by the tokenizer itself (the parser needs byte positions for `{subs}`). Queued, not
|
|
578
|
+
done: `qt(p, df)` and `qnorm(p)`.
|
|
579
|
+
|
|
580
|
+
## v3.30.7 — 2026-10-24
|
|
581
|
+
|
|
582
|
+
Pre-edit state: commit `230ff0b` (v3.30.6).
|
|
583
|
+
|
|
584
|
+
### Fixed
|
|
585
|
+
|
|
586
|
+
- **A partial build lists its metadata again.** `pdfmd @10,40` (or `report#section`) printed `FOUND nulabreport.lua, 2 parts` and
|
|
587
|
+
`DEFAULTS packages mhchem`, without the metadata file, the preamble, the global metadata or the balanced tables a full build lists.
|
|
588
|
+
The files were used all along (the Pandoc command had them); only the summary was wrong. The hidden pass over the whole document, which
|
|
589
|
+
gives the left-out parts their numbers, printed its own summary to nowhere and emptied the items already recorded. The summary is now saved
|
|
590
|
+
before that pass and put back after it (`AutoLog.snapshot` / `restore`). Test: `tests/test_autolog.py` (`Builds`).
|
|
591
|
+
|
|
592
|
+
## v3.30.6 — 2026-10-24
|
|
593
|
+
|
|
594
|
+
Pre-edit state: commit `1725405` (v3.30.5).
|
|
595
|
+
|
|
596
|
+
### Added
|
|
597
|
+
|
|
598
|
+
- **`{ans}`: the calculator prints the text it was given.** Inside a `` `...`{=} `` span, `` `V = 20.60 - 0.00 = {ans:2dp} mL`{=} `` becomes
|
|
599
|
+
"V = 20.60 - 0.00 = 20.60 mL" (the text as typed, `{ans}` replaced); the expression is what stands between the last `=` before `{ans}` and that
|
|
600
|
+
`= {ans}`, and what stands before it is a label. `` `2-4={ans}`{=} `` works unspaced and prints as typed. Several `{ans}` may share a span.
|
|
601
|
+
- **Names.** A plain identifier on the left (`C_EDTA = ... = {ans}`, or the word after a `:` `;` `,` of a label: `Run 1: V = ...`) is stored with the
|
|
602
|
+
FULL value, not the displayed one, so later calculations cannot drift from what the page shows. `` `A = 4 - 2`{=} `` only stores (prints nothing);
|
|
603
|
+
`` `A`{=} `` and `` `A * 2`{=} `` print values. A name is found anywhere in the document, parts included (Results may cite Calculations): the
|
|
604
|
+
nearest definition above its use, else the first below; a name may be defined again; a cycle, an unknown name or a name that is a function or
|
|
605
|
+
constant (`pi`, `sqrt`) is a WARN and the text is kept. A stored name remembers the digits it was given.
|
|
606
|
+
- **Number formats.** `{ans:4sf}` four significant figures (trailing zeros kept: 20.60; the digits before the point are never dropped, 1049 at 3sf is
|
|
607
|
+
1049; scientific only below 1e-6 or from 1e15), `{ans:2dp}` two decimal places, and after a span `` `1/3`{=3sf} `` (Pandoc reads `{=3sf}` as a
|
|
608
|
+
raw attribute: the filter takes `NNsf` / `NNdp` raw inlines and leaves every other raw format alone).
|
|
609
|
+
- **`::: calc`** (also `::: {.calc}`, `::: {#id .calc}`; the class alone, as in Pandoc) calculates every line that holds `{ans}`, in paragraphs, list
|
|
610
|
+
items and quotes, without backticks; other lines are plain text; a line with bold, a link or math is left as written with a WARN. The detection
|
|
611
|
+
adds the filter for the div as well. It is the opt-in form of calculating prose: nothing outside it is read.
|
|
612
|
+
- `--calc-digits 4sf|2dp` (and `PDFMD_CALC_DIGITS` for `-j` workers) and `pdfmd-options: {calc: {digits: 4sf}}` set the default format of a document
|
|
613
|
+
(unset: as R prints, up to 7 figures); a span's own format wins. `--setup` rows. README "Calculations inside the text" (for anyone writing with it),
|
|
614
|
+
docs/recipes.md 1.11.
|
|
615
|
+
|
|
616
|
+
### Changed
|
|
617
|
+
|
|
618
|
+
- **The calculator's options live under `calc:`** (like `csv:` and `backup:`): `pdfmd-options: {calc: {enabled, digits, round}}`, `--setup` rows
|
|
619
|
+
`options.calc.enabled|digits|round`. `calc: off` / `calc: false` stays as the short form of `enabled: false`. The flat `calc-round` key of 3.30.4
|
|
620
|
+
is gone (3.30.4 was not released); `--calc`, `--no-calc`, `--calc-round` are unchanged. A wrong `digits` or `round` is an error naming the key.
|
|
621
|
+
- `tests/test_calc.py`: 33 tests (the new ones: 16 statement forms, 20 formats, the div in its spellings, two placeholders in a line, the warnings,
|
|
622
|
+
the options, flags and variables, and a PDF in which Results cites a name that Calculations defines in a `::: calc` div).
|
|
623
|
+
|
|
624
|
+
### Not done (queued)
|
|
625
|
+
|
|
626
|
+
- Spacing the operators for you (`C=n/V={ans}` printed as `C = n / V = 0.0203`), calculating plain prose without `{=}` or a div, `$..$` math, statistics
|
|
627
|
+
(mean, stdev, rsd, a t quantile, Grubbs), a check mode for a number already typed, significant-figure tracking, `.qmd` spellings.
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## v3.30.5 — 2026-10-24
|
|
632
|
+
|
|
633
|
+
Pre-edit state: commit `2d56ccf` (v3.30.4).
|
|
634
|
+
|
|
635
|
+
### Fixed
|
|
636
|
+
|
|
637
|
+
- **A symlink in `parts/` crashed the build** (`ValueError: '...' is not in the subpath of '.../parts'`). `collect_parts()` resolved every part to its real
|
|
638
|
+
path, so a `.md` file linked into the parts folder, or one inside a linked folder, no longer lay under it and `part_key()` / `select_parts()`
|
|
639
|
+
could not name it. A part now keeps the path it has in the parts folder (`os.path.abspath`, not `resolve()`), so a linked file or folder is built
|
|
640
|
+
as a part, in its place in the order and under its link's name. (A folder in `parts/` is always walked: to keep a reference folder beside the parts
|
|
641
|
+
without building it, name it with a leading `_` or `.`.)
|
|
642
|
+
|
|
643
|
+
### Added
|
|
644
|
+
|
|
645
|
+
- **`@` for `#` in the section shorthand**: `pdfmd report@methods`, `pdfmd doc@@yield` (a level-2 heading), `pdfmd @yield` (the folder's only Markdown
|
|
646
|
+
file), `doc@a+b`. An interactive bash (and some other shells) takes a `#` for the start of a comment and never passes it on, so `pdfmd #10` ran
|
|
647
|
+
without its argument. A real file whose name has an `@` still wins, as with `#`; `--section` is unchanged. `tests/test_symlinked_parts.py`.
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
## v3.30.4 — 2026-10-24
|
|
652
|
+
|
|
653
|
+
Pre-edit state: commit `b55a83b` (v3.30.3).
|
|
654
|
+
|
|
655
|
+
### Added
|
|
656
|
+
|
|
657
|
+
- **An inline calculator: `` `2026 - 2006`{=} `` becomes `20`.** Inline code followed at once by a bare `{=}` is replaced by its value, in every
|
|
658
|
+
output format a Pandoc build writes. `pdfmd_lua/calc.lua` has its own small parser and runs nothing (no `load`, no R, no Python), so a document from
|
|
659
|
+
anybody is safe and nothing asks for trust. Operators: `+ - * /`, `^` and `**` (right to left; `-2^2` is -4), `%` and `%%` (remainder, floored:
|
|
660
|
+
`-7 % 3` is 2), `//` and `%/%` (floor division: `7 %/% 2` is 3), parentheses, unary minus. `|a - b|` is the absolute value. A `%` that no operand follows is a percent sign, a label and not an operator (`0.5 / 4 * 100%` is 12.5, as a lab report
|
|
661
|
+
writes it); `×`, `·`, `÷` and `−` are `*`, `*`, `/` and `-`. Numbers: `12`, `.5`, `6.022e23`. Constants `pi` and `deg`
|
|
662
|
+
(= pi/180, so `sin(30*deg)` is 0.5). Functions (angles in radians): `sin cos tan cot sec csc`, `asin acos atan acot` (`atan(y, x)` is the two-argument
|
|
663
|
+
arctangent), `sinh cosh tanh asinh acosh atanh`, `sind cosd tand` (degrees in) and `asind acosd atand` (degrees out; `asin(x) / deg` is the same), `exp ln log` (`log(x, base)`), `log10 log2 sqrt cbrt abs sign`,
|
|
664
|
+
`floor ceil trunc`, `round(x)` and `round(x, digits)` (an exact half goes to the even digit, as in R and Python: `round(2.5)` is 2, `round(3.5)` is 4; `calc-round: away` makes it 3), `min max` (any number of
|
|
665
|
+
arguments), `hypot factorial`. A trig result closer to 0 than 1e-15 is 0 (`sin(pi)`). The value prints as R does: up to 7 significant digits, `20`
|
|
666
|
+
and not `20.0`, `0.3` for `0.1 + 0.2`, `1.2044e+24` for a very large one.
|
|
667
|
+
- What the parser cannot evaluate (a name it does not know, a division by zero, `asin(2)`, an unfinished expression, an infinite or undefined
|
|
668
|
+
result) is kept as the code it was, without the `{=}`, with one `WARN calc: ...` naming the span and the reason. Inline code without `{=}`, code
|
|
669
|
+
blocks and a `{=}` anywhere else are never touched.
|
|
670
|
+
- `--calc` / `--no-calc` (and `PDFMD_CALC=on|off`, which is how a `-j` worker gets it) and `pdfmd-options: {calc: off}` (the document, a metadata file or the
|
|
671
|
+
config file, in that order of precedence after the flag) turn the calculator on or off, with a `--setup` row;
|
|
672
|
+
`--calc-round even|away`, `pdfmd-options: {calc-round: away}` and a `--setup` row choose how `round()` settles an exact half (default even, like R and Python); the filter is
|
|
673
|
+
attached only to a document that has a `` `...`{=} `` span. Without Pandoc the built-in renderer shows the span as written. docs/recipes.md 1.11.
|
|
674
|
+
- `tests/test_calc.py`: 88 expressions with their text, 23 failures with their warning, the neighbouring text, code that must stay, the HTML, LaTeX
|
|
675
|
+
and Typst writers, the detection, the switch (document and metadata file), and real PDFs (a part included) read back with `pdftotext`.
|
|
676
|
+
|
|
677
|
+
### Not done (queued)
|
|
678
|
+
|
|
679
|
+
- spellings for `.qmd` (`` `{r} 2 + 2` ``, `{python}`) and an `all` mode that evaluates every
|
|
680
|
+
arithmetic-only code span.
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## v3.30.3 — 2026-10-24
|
|
685
|
+
|
|
686
|
+
Pre-edit state: commit `ab380d1` (v3.30.2).
|
|
687
|
+
|
|
688
|
+
### Added
|
|
689
|
+
|
|
690
|
+
- **`--to ansi`: a document as styled text for a terminal.** Bold, italic, inline code, strikethrough, links, headings, ordered and bullet lists, block
|
|
691
|
+
quotes, code blocks, rules, footnotes (listed at the end), a title block from the front matter, and every table as a box-drawn grid: a rule between
|
|
692
|
+
rows, a bold header, each column as wide as its widest cell, the alignment the table gives, cells wrapped when the window is narrower than the table,
|
|
693
|
+
wide (East Asian) characters counted as two cells. The text is wrapped to `--width N`, else the terminal's width, else 120. It is a build like the others:
|
|
694
|
+
Pandoc reads the document through the usual steps (metadata files, filters, `pdfmd-options`, citations, `\ce`, the CSV tables) and writes its JSON AST,
|
|
695
|
+
which the new package `pdfmd_ansi/` (a pure function, no other pdfmd module imported) draws; the file is `NAME.ansi`, and `-o x.ansi` selects the format.
|
|
696
|
+
Without Pandoc the built-in parser (`pdfmd_inkmd`) reads the Markdown instead, with a NOTE: no filters, citations, footnotes or metadata files.
|
|
697
|
+
- **Math as text, level (a).** `MathText` (the converter the built-in renderer uses) gets an ANSI back-end: Greek letters, operators, Unicode sub- and
|
|
698
|
+
superscripts, `(a+b)/(c+d)`, `√(...)`, variables in italic, a function name after a variable set off by a space (`RT ln K`); a formula alone on its line is
|
|
699
|
+
centred; one the converter cannot read is shown as its TeX.
|
|
700
|
+
- **`--view`: read a document in the terminal.** = `--to ansi -o -` + a pager. It takes files (a folder: its Markdown files), `-` or a pipe on standard
|
|
701
|
+
input (written to a hidden temporary `.md` beside the cwd so relative pictures resolve, and removed), else the Markdown files of the current folder, else
|
|
702
|
+
what is typed or pasted into the terminal (ended with Ctrl-D; a hint says so on standard error). Several files are one stream in the order given, with a
|
|
703
|
+
dim rule naming each. Each file is built on its own by a child pdfmd into a scratch folder, so all discovery and notes apply; the notes (`OK`, `DEFAULTS`)
|
|
704
|
+
stay off the screen unless a build fails or `--verbose` is given, every other message goes to standard error. The styling stays when the output is piped
|
|
705
|
+
(`pdfmd --view x.md | less -R`). `--view -o FILE` saves the file and shows nothing; `--view` with another `--to` is an error.
|
|
706
|
+
- **The pager** (on by default, `--pager` / `--no-pager`, `pdfmd-options: {pager: false}`, a `--setup` row, also in the folder screen): only when
|
|
707
|
+
standard output is a terminal, through `$PDFMD_PAGER`, else `$PAGER`, else `less -FRX` (`-F` quits at once when the text fits one screen, `-R` keeps the
|
|
708
|
+
colours; a `less` given without `-R` gets it).
|
|
709
|
+
- **`-o -` is standard output**, for `--view` and for any format built from a file or a pipe: the build goes to a scratch folder and its bytes are copied to
|
|
710
|
+
stdout; several documents are joined only for text formats (a binary format, `pdf` or `docx`, with several inputs is refused). `--color auto|always|never`:
|
|
711
|
+
a file and `--view` are styled, `-o -` into a pipe is plain, `NO_COLOR` turns `auto` off, `always` wins over it.
|
|
712
|
+
- Links are `words (address)`, or OSC 8 hyperlinks in a terminal known to draw them (iTerm2, WezTerm, Ghostty, VS Code) when the text is not paged. Text
|
|
713
|
+
from a document is cleaned before it is shown: control characters (an ESC would start a terminal sequence) and bidi overrides are dropped.
|
|
714
|
+
- `--help view` lists `--view --pager --color --width`; `--view` is on the short help page. The flags are in `pdfmd_setup/inventory.py` (`--pager` is the row
|
|
715
|
+
`options.pager`; `--view` a command; `--color` and `--width` per run); `pdfmd_ansi` is in `pyproject.toml` and `scripts/build_zipapp.py`.
|
|
716
|
+
|
|
717
|
+
### Known limits
|
|
718
|
+
|
|
719
|
+
- A cell that spans rows or columns is drawn in its first cell only. A formula is linear (stacked fractions, roots and sums with limits, and pictures of
|
|
720
|
+
display math in terminals that show them, are later patches). Raw LaTeX, HTML and Word pieces are left out (`--verbose` counts them). Code is not highlighted.
|
|
721
|
+
- The vendored built-in parser reads a `>` quote that follows a list, after a blank line, as a paragraph plus a one-line quote; that is in `pdfmd_inkmd`
|
|
722
|
+
(to be fixed upstream in inkmd, the vendored copy is not edited by hand), so it shows only without Pandoc.
|
|
723
|
+
|
|
724
|
+
Tests: `tests/test_ansi.py` — widths and wrapping, escape-code cleaning, inline styles with and without colour, links, grid widths at several window
|
|
725
|
+
widths (wide characters, shrinking, wrapping), lists/quotes/code, math through the callback; and through the full pipeline: `--to ansi` to a file, `-o x.ansi`,
|
|
726
|
+
`--view` into a pipe, `NO_COLOR` / `--color`, `-o -`, several files, a pipe and `-`, `--to docx` refused, `-o` replacing `--view`, no Pandoc.
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## v3.30.2 — 2026-10-22
|
|
731
|
+
|
|
732
|
+
Pre-edit state: commit `814acf5` (v3.30.1).
|
|
733
|
+
|
|
734
|
+
### Added
|
|
735
|
+
|
|
736
|
+
- **`--table-keep N` and `pdfmd-options: {table-keep: N}`.** pdfmd passes `-M tablekeep=N` to Pandoc, for a Lua filter that keeps a short Markdown
|
|
737
|
+
table on one page (nulabreport.lua, v1.28.16: a table of up to `N` body rows moves to the next page whole instead of splitting; its built-in 10 was
|
|
738
|
+
hard-coded before). `0` lets every table break. Order: the command line (it reaches `-j` workers as `PDFMD_TABLE_KEEP`), then `pdfmd-options.table-keep`
|
|
739
|
+
(document, metadata files, config), then nothing: pdfmd passes no `-M`, and the metadata's own `tablekeep:` or the filter's default stands. Pandoc's `-M`
|
|
740
|
+
wins over a metadata file, so the option also overrides a `tablekeep:` there. A value that is not a whole number stops the run with a message naming the
|
|
741
|
+
file. It is set at all three places the command is built (PDF and the other outputs, the Office route). A row in `pdfmd --setup` (Tables), in the
|
|
742
|
+
folder screen too. A filter that does not read `tablekeep` ignores it. Tested through the full pipeline: the flag, the option, the metadata key and
|
|
743
|
+
their order, and a bad value.
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
## v3.30.1 — 2026-10-21
|
|
748
|
+
|
|
749
|
+
Pre-edit state: commit `5617970` (v3.30.0).
|
|
750
|
+
|
|
751
|
+
### Fixed
|
|
752
|
+
|
|
753
|
+
- **The closing line names the PDF, not the `.tex` the cache made on the way.** A build through the cache folder (a part alone, `report#second`, with `cache:` on,
|
|
754
|
+
as the lab reports have it) printed `OK report.md > ~/.cache/pdfmd/b888f4388b-report/report.tex (lualatex)` although the PDF was written beside the source:
|
|
755
|
+
the nested Markdown-to-`.tex` stage of `convert_via_cache()` recorded its own file as the build's output, and nothing put the PDF back. It is restored after the
|
|
756
|
+
stage, so the line is `OK report.md > report.second.pdf (lualatex)`; `--to latex` still names the `.tex`, and `--verbose` still shows the cache folder (the
|
|
757
|
+
`CMD` and `TEXDIRECT` lines). Tested with the cache route on, which fails without the fix (`tests/test_build_record.py`).
|
|
758
|
+
|
|
759
|
+
---
|
|
760
|
+
|
|
761
|
+
## v3.30.0 — 2026-10-21
|
|
762
|
+
|
|
763
|
+
Pre-edit state: commit `a030424` (v3.29.36).
|
|
764
|
+
|
|
765
|
+
### Added
|
|
766
|
+
|
|
767
|
+
- **A paragraph indent for a stretch of text: `::: {indent=false}`.** A fenced div with `indent=` (`false`, `no`, `off`, `0`; `true`, which is 1.5em; or a
|
|
768
|
+
length: `2em`, `12pt`, `5mm`, a bare number is em), or the classes `.noindent` / `.indent`, sets the first-line indent of the paragraphs inside it and puts the
|
|
769
|
+
document's own back at the closing `:::`, so there is no "turn it back on" line to forget. Divs nest (the inner one wins, the outer comes back after it), and
|
|
770
|
+
the div's `#id` and other classes stay. The new `pdfmd_lua/indent.lua` does it per writer: LaTeX and Beamer set `\parindent` in a `\begingroup` ...
|
|
771
|
+
`\endgroup`, Typst a `first-line-indent` rule in a content block (not in a `#block`, where it did not reach a paragraph that followed another), HTML and
|
|
772
|
+
EPUB a `text-indent` style on the div. Pandoc Markdown, GFM and CommonMark keep the div as written; Word, OpenDocument and the rest have no per-block indent,
|
|
773
|
+
so the text stays and a NOTE says the setting was left out. A value that is not an indent is a WARN and changes nothing. The filter is added only for a
|
|
774
|
+
document (or one of its parts) that has such a div (`indent_filter_args()`, `INDENT_DIV_RE`); there is no switch, since the div is the opt-in. Found with the
|
|
775
|
+
calculations of a lab report, where the lines of arithmetic should not be indented but the prose between them should. New `tests/test_indent.py`: the
|
|
776
|
+
detection, every writer, and real PDFs measured with `pdftotext -bbox` (a plain document and a report in parts). `docs/recipes.md` 1.9.
|
|
41
777
|
|
|
42
778
|
---
|
|
43
779
|
|