Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ Instructions: Add a subsection under `[Unreleased]` for additions, fixes, change

## [Unreleased]

### Added

- Reader options menu for HTML output, in place of light/dark toggle.
- Additional accessibility improvements.
- Support for testing experimental PDF-FO method for generating PDFs. This is an experimental method that uses Apache FOP to generate PDFs from XSL-FO. It is not yet fully supported, but can be enabled by setting `pdf-method="pdf-fo"` on a target in the manifest.

## [2.42.0] - 2026-06-18

Includes updates to core through commit: [afb99f7](https://github.com/PreTeXtBook/pretext/commit/afb99f78226d4631e3fe87f5a0d4d41ff753e26f)

# Added
### Added

- You can now specify the debug level *after* the command (e.g., `pretext -v debug build` can now also be entered as `pretext build -v debug`).
- Temporary directories created by core pretext will now be cleaned up at all debug levels. To save them for debugging, use the `--save-tmp-dirs` flag, as in `pretext --save-tmp-dirs build`.
Expand Down
440 changes: 0 additions & 440 deletions docs/superpowers/plans/2026-06-17-pretext-validate-command.md

This file was deleted.

126 changes: 0 additions & 126 deletions docs/superpowers/specs/2026-06-17-pretext-validate-command-design.md

This file was deleted.

2 changes: 1 addition & 1 deletion pretext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
VERSION = get_version("pretext", Path(__file__).parent.parent)


CORE_COMMIT = "afb99f78226d4631e3fe87f5a0d4d41ff753e26f"
CORE_COMMIT = "73e61ec2c3f91468a80a12ce249fa69a734e6d26"


def activate() -> None:
Expand Down
62 changes: 44 additions & 18 deletions pretext/project/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)
import pydantic_xml as pxml
from pydantic_xml.element.element import SearchMode
from .xml import Executables, LegacyProject, LatexEngine
from .xml import Executables, LegacyProject, LatexEngine, PdfMethod
from . import generate
from .. import constants
from .. import core
Expand Down Expand Up @@ -143,9 +143,11 @@ class Target(pxml.BaseXmlModel, tag="target", search_mode=SearchMode.UNORDERED):
_source_element_with_ids: t.Optional[ET._Element] = None
# A path to the publication file for this target, relative to the project's `publication` path. This is mostly validated by `post_validate`.
publication: Path = pxml.attr(default=None)
latex_engine: LatexEngine = pxml.attr(
name="latex-engine", default=LatexEngine.XELATEX
)
# `pdf_method` is the canonical PDF engine setting.
# `latex_engine` is retained only for backwards compatibility and will be
# mapped into `pdf_method` when `pdf_method` is not explicitly provided.
pdf_method: PdfMethod = pxml.attr(name="pdf-method", default=None)
latex_engine: t.Optional[LatexEngine] = pxml.attr(name="latex-engine", default=None)
# Flag to indicate whether to include LaTeX source files in the output directory when building a PDF target.
latex_source: t.Optional[str] = pxml.attr(name="latex-source", default=None)

Expand All @@ -156,6 +158,19 @@ def latex_source_validator(cls, v: t.Optional[str]) -> bool:
return False
return v.lower() != "no"

@model_validator(mode="after")
def pdf_method_validator(self) -> "Target":
if self.pdf_method is None:
if self.latex_engine is not None:
self.pdf_method = PdfMethod(self.latex_engine.value)
else:
self.pdf_method = PdfMethod.XELATEX
if self.format == Format.LATEX and self.pdf_method == PdfMethod.PDF_FO:
raise ValueError(
"The LaTeX format does not support pdf-method='pdf-fo'. Please use a standard LaTeX PDF method instead."
)
return self

braille_mode: BrailleMode = pxml.attr(
name="braille-mode", default=BrailleMode.EMBOSS
)
Expand Down Expand Up @@ -832,19 +847,30 @@ def build(
)
log.debug(e, exc_info=True)
elif self.format == Format.PDF:
# Include latex source files if requested.
if self.latex_source:
latex = True
core.pdf(
xml=self.source_abspath(),
pub_file=self.publication_abspath().as_posix(),
stringparams=stringparams_copy,
extra_xsl=custom_xsl,
out_file=out_file,
dest_dir=self.output_dir_abspath().as_posix(),
method=self.latex_engine,
outputs="all" if latex else "pdf-only",
)
if self.pdf_method == PdfMethod.PDF_FO:
# Experimental support for the new PDF-FO method.
core.pdf_fo(
xml=self.source_abspath(),
pub_file=self.publication_abspath().as_posix(),
stringparams=stringparams_copy,
out_file=out_file,
dest_dir=self.output_dir_abspath().as_posix(),
)
else:
# Otherwise we use the standard latex methods.
# Include latex source files if requested.
if self.latex_source:
latex = True
core.pdf(
xml=self.source_abspath(),
pub_file=self.publication_abspath().as_posix(),
stringparams=stringparams_copy,
extra_xsl=custom_xsl,
out_file=out_file,
dest_dir=self.output_dir_abspath().as_posix(),
method=self.pdf_method,
outputs="all" if latex else "pdf-only",
)
elif self.format == Format.LATEX:
core.pdf(
xml=self.source_abspath(),
Expand All @@ -853,7 +879,7 @@ def build(
extra_xsl=custom_xsl,
out_file=out_file,
dest_dir=self.output_dir_abspath().as_posix(),
method=self.latex_engine,
method=self.pdf_method,
outputs="prebuild",
)
elif self.format == Format.EPUB:
Expand Down
9 changes: 9 additions & 0 deletions pretext/project/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Executables(pxml.BaseXmlModel, tag="executables"):
node: str = pxml.attr(default="node")
liblouis: str = pxml.attr(default="file2brl")
perl: str = pxml.attr(default="perl")
fop: str = pxml.attr(default="fop")


class LegacyFormat(str, Enum):
Expand All @@ -46,6 +47,14 @@ class LatexEngine(str, Enum):
PDFLATEX = "pdflatex"


class PdfMethod(str, Enum):
XELATEX = "xelatex"
LATEX = "latex"
PDFLATEX = "pdflatex"
LUALATEX = "lualatex"
PDF_FO = "pdf-fo"


class LegacyStringParam(pxml.BaseXmlModel):
model_config = ConfigDict()
key: str = pxml.attr()
Expand Down
15 changes: 11 additions & 4 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import requests
import pretext
from lxml import etree as ET # noqa: N812
from pretext import constants
from pretext import utils
from pretext import constants, server, utils
from typing import cast, Generator
import pytest
from pytest_console_scripts import ScriptRunner
Expand Down Expand Up @@ -460,9 +459,17 @@ def test_view(tmp_path: Path, script_runner: ScriptRunner) -> None:
assert script_runner.run([PTX_CMD, "-v", "debug", "new", "-d", "1"]).success
os.chdir(Path("1"))
assert script_runner.run([PTX_CMD, "-v", "debug", "build"]).success
port = random.randint(10_000, 65_536)
port = random.randint(10_000, 65_535)
with pretext_view("-p", f"{port}"):
r = requests.get(f"http://localhost:{port}")
project_hash = utils.hash_path(Path.cwd().resolve())
running_server = None
for _ in range(50):
running_server = server.active_server_for_path_hash(project_hash)
if running_server is not None and running_server.is_active_server():
break
time.sleep(0.1)
assert running_server is not None
r = requests.get(f"http://localhost:{running_server.port}")
assert r.status_code == 200
assert script_runner.run([PTX_CMD, "-v", "debug", "view", "-s"]).success

Expand Down
14 changes: 7 additions & 7 deletions tests/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_defaults(tmp_path: Path) -> None:
assert target.output_dir == Path(name)
assert target.deploy_dir is None
assert target.xsl is None
assert target.latex_engine == pr.LatexEngine.XELATEX
assert target.pdf_method == pr.PdfMethod.XELATEX
assert target.stringparams == {}
# Default asy_method should be "local"
assert project.asy_method == pr.AsyMethod.LOCAL
Expand Down Expand Up @@ -225,22 +225,22 @@ def test_manifest_legacy() -> None:
"publication", "publication.ptx"
)
assert t_html.output_dir_abspath() == project.abspath() / Path("output", "html")
assert t_html.latex_engine == "xelatex"
assert t_html.pdf_method == "xelatex"
assert t_html.stringparams == {"one": "uno", "two": "dos"}

t_latex = project.get_target("latex")
assert t_latex.format == "latex"
assert t_latex.source == Path("source", "main.ptx")
assert t_latex.publication == Path("publication", "publication.ptx")
assert t_latex.output_dir == Path("output", "latex")
assert t_latex.latex_engine == "xelatex"
assert t_latex.pdf_method == "xelatex"

t_pdf = project.get_target("pdf")
assert t_pdf.format == "pdf"
assert t_pdf.source == Path("source", "main.ptx")
assert t_pdf.publication == Path("publication", "publication.ptx")
assert t_pdf.output_dir == Path("output", "pdf")
assert t_pdf.latex_engine == "pdflatex"
assert t_pdf.pdf_method == "pdflatex"

assert not project.has_target("foo")

Expand All @@ -265,22 +265,22 @@ def test_manifest_legacy_wrong() -> None:
"publication", "publication.ptx"
)
assert t_html.output_dir_abspath() == project.abspath() / Path("output", "html")
assert t_html.latex_engine == "xelatex"
assert t_html.pdf_method == "xelatex"
assert t_html.stringparams == {"one": "uno", "two": "dos"}

t_latex = project.get_target("latex")
assert t_latex.format == "latex"
assert t_latex.source == Path("source", "main.ptx")
assert t_latex.publication == Path("publication", "publication.ptx")
assert t_latex.output_dir == Path("output", "latex")
assert t_latex.latex_engine == "xelatex"
assert t_latex.pdf_method == "xelatex"

t_pdf = project.get_target("pdf")
assert t_pdf.format == "pdf"
assert t_pdf.source == Path("source", "main.ptx")
assert t_pdf.publication == Path("publication", "publication.ptx")
assert t_pdf.output_dir == Path("output", "pdf")
assert t_pdf.latex_engine == "pdflatex"
assert t_pdf.pdf_method == "pdflatex"

assert not project.has_target("foo")

Expand Down
Loading