Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build PDF files using latexmk #5437

Merged
merged 6 commits into from Mar 18, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/guides/feature-flags.rst
Expand Up @@ -12,6 +12,8 @@ or disable one or more of these featured flags for a particular project.
Available Flags
---------------

``USE_PDF_LATEXMK``: :featureflags:`USE_PDF_LATEXMK`

``USE_SPHINX_LATEST``: :featureflags:`USE_SPHINX_LATEST`

``USE_SETUPTOOLS_LATEST``: :featureflags:`USE_SETUPTOOLS_LATEST`
Expand Down
66 changes: 66 additions & 0 deletions readthedocs/doc_builder/backends/sphinx.py
Expand Up @@ -6,12 +6,14 @@
.. _Sphinx: http://www.sphinx-doc.org/
"""
import codecs
import itertools
import logging
import os
import shutil
import sys
import zipfile
from glob import glob
from pathlib import Path

from django.conf import settings
from django.template import loader as template_loader
Expand Down Expand Up @@ -151,6 +153,9 @@ def get_config_params(self):
'dont_overwrite_sphinx_context': self.project.has_feature(
Feature.DONT_OVERWRITE_SPHINX_CONTEXT,
),
'use_pdf_latexmk': self.project.has_feature(
Feature.USE_PDF_LATEXMK,
),
}

finalize_sphinx_context_data.send(
Expand Down Expand Up @@ -395,6 +400,67 @@ def build(self):
raise BuildEnvironmentError('No TeX files were found')

# Run LaTeX -> PDF conversions
if self.project.has_feature(Feature.USE_PDF_LATEXMK):
return self._build_latexmk(cwd, latex_cwd)

return self._build_pdflatex(tex_files, latex_cwd)

def _build_latexmk(self, cwd, latex_cwd):
# These steps are copied from the Makefile generated by Sphinx >= 1.6
# https://github.com/sphinx-doc/sphinx/blob/master/sphinx/texinputs/Makefile_t
latex_path = Path(latex_cwd)
images = []
for extension in ('png', 'gif', 'jpg', 'jpeg'):
images.extend(latex_path.glob(f'*.{extension}'))

# FIXME: instead of checking by language here, what we want to check if
# ``latex_engine`` is ``platex``
pdfs = []
if self.project.language == 'ja':
# Japanese language is the only one that requires this extra
# step. I don't know exactly why but most of the documentation that
# I read differentiate this language from the others. I suppose
# it's because it mix kanji (Chinese) with its own symbols.
pdfs = latex_path.glob('*.pdf')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this only required by ja lang? Don't we already run this whole function under a feature flag?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Japanese language is the only one that requires this extra step. I don't know exactly why but most of the documentation that I read differentiate this language from the others. I suppose it's because it mix kanji (Chinese) with its own symbols.

I took this step from the Makefile generated by Sphinx when the language is Japanese.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above github comment should be a code comment.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Japanese language uses latex+dvipdfmx path. Until TeXLive 2015 release, extractbb step was needed. But it is not needed with newer dvipdfmx binary. Here is a quote of an exchange I had in 2017 on TeXLive mailing list:

Can someone confirm that since TL2014 there is no need
on Unix-like systems to prepare beforehand .xbb
files for graphics inclusion of .png, .jpeg, ...,
when doing latex+dvipdfmx or platex+dvipdfmx ?
(and that it was either needed before, or at least
shell-escape had to be enabled) ?

I remember xbb files were necessary for TL2014 or earlier.
Japanese people has been heavily using dvipdfmx, so they added
extractbb to their local texmf.cnf by hand

...

Thanks for the information ! This is for support
of some other software, now I know they can avoid
the explicit extractbb calls but under assumption
of a TL2015 or more recent install,

Thus Sphinx may soon drop the extractbb and make the Japanese set-up in Makefile less singular: indexe 2.0 release requires "LaTeX builder now depends on TeX Live 2015 or above." but we forgot to actually remove the extractbb dependency as we don't want either to break too easily user projects with older TeX installations.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have prepared sphinx-doc/sphinx#6189 for Sphinx to remove the extra handling of image files for Japanese language by extractbb as it is superfluous for TL2015 or later based TeX distribution. Not sure it will be merged during 2.x series.


for image in itertools.chain(images, pdfs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image and pdfs are not related I guess, but not sure about this step

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

images may be provided in pdf format: in fact this is the preferred format for image inclusion

From sphinx doc here is the list of file extensions for images in order of preference for LaTeX:

supported_image_types = ['application/pdf', 'image/png', 'image/jpeg']

self.run(
'extractbb',
image.name,
cwd=latex_cwd,
record=False,
)

rcfile = 'latexmkrc'
if self.project.language == 'ja':
rcfile = 'latexmkjarc'

self.run(
'cat',
rcfile,
cwd=latex_cwd,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this to output to the user, or some other reason?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This is only to show the content of the latexmkrc file to the user.

I want to show this because that file is built by Sphinx depending on some configurations. Also, this is the file that will say what command execute to build the PDF in the end. So, without this output will be very hard to debug a problem.


cmd = self.run(
'latexmk',
'-r',
rcfile,

# FIXME: check for platex here as well
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused about platex and latexmk

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

latexmk uses internally pdflatex or xelatex or lualatex or platex (defined by latex_engine

'-pdfdvi' if self.project.language == 'ja' else '-pdf',

'-dvi-',
'-ps-',
f'-jobname={self.project.slug}',
warn_only=True,
cwd=latex_cwd,
)

self.pdf_file_name = f'{self.project.slug}.pdf'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did this come from? From the -jobname option?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


return cmd.successful

def _build_pdflatex(self, tex_files, latex_cwd):
pdflatex_cmds = [
['pdflatex', '-interaction=nonstopmode', tex_file]
for tex_file in tex_files
Expand Down
35 changes: 35 additions & 0 deletions readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl
Expand Up @@ -140,3 +140,38 @@ if 'extensions' in globals():
extensions.insert(0, "readthedocs_ext.readthedocs")
else:
extensions = ["readthedocs_ext.readthedocs"]

{% if use_pdf_latexmk %}
project_language = '{{ project.language }}'

# User's Sphinx configurations
language_user = globals().get('language', None)
latex_engine_user = globals().get('latex_engine', None)
latex_elements_user = globals().get('latex_elements', None)

chinese = any([
language_user in ('zh_CN', 'zh_TW'),
project_language in ('zh_CN', 'zh_TW'),
])

japanase = any([
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the spelling japanase intended here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It's a typo!

language_user == 'ja',
project_language == 'ja',
])

if chinese:
latex_engine = latex_engine_user or 'xelatex'

# Remove this once xindy gets installed in Docker image and XINDYOPS
# env variable is supported
# https://github.com/rtfd/readthedocs-docker-images/pull/98
latex_use_xindy = False

latex_elements_rtd = {
'preamble': '\\usepackage[UTF8]{ctex}\n',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sadly, I am not at all competent in Chinese. I don't know how Chinese users of LaTeX go about producing indices... Xindy does not seem to have any special support for CJK languages, but at least it is Unicode aware contrarily to makeindex. There is zhmakeindex but its usage has not been incorporated to Sphinx, I am not aware of PRs about this.

The Japanese language is handled separately by Sphinx because it uses a specific LaTeX binary, platex and a specific indexing binary mendex. But they are not Unicode aware, so mixing Japanese with European languages is not easy/feasible apart from English as it only needs ascii range. In future Sphinx will switch presumably to uplatex+upmendex for Japanese support (see e.g. sphinx-doc/sphinx#4187 (comment)).

}
latex_elements = latex_elements_user or latex_elements_rtd
elif japanase:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cf my prior comment about spelling

latex_engine = latex_engine_user or 'platex'
latex_use_xindy = False
{% endif %}
2 changes: 2 additions & 0 deletions readthedocs/projects/models.py
Expand Up @@ -1314,10 +1314,12 @@ def add_features(sender, **kwargs):
DONT_SHALLOW_CLONE = 'dont_shallow_clone'
USE_TESTING_BUILD_IMAGE = 'use_testing_build_image'
SHARE_SPHINX_DOCTREE = 'share_sphinx_doctree'
USE_PDF_LATEXMK = 'use_pdf_latexmk'

FEATURES = (
(USE_SPHINX_LATEST, _('Use latest version of Sphinx')),
(USE_SETUPTOOLS_LATEST, _('Use latest version of setuptools')),
(USE_PDF_LATEXMK, _('Use latexmk to build the PDF')),
(ALLOW_DEPRECATED_WEBHOOKS, _('Allow deprecated webhook views')),
(PIP_ALWAYS_UPGRADE, _('Always run pip install --upgrade')),
(SKIP_SUBMODULES, _('Skip git submodule checkout')),
Expand Down