1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
#!/usr/bin/env python3
# :Author: Günter Milde <milde@users.sf.net>
# :Revision: $Revision: 10196 $
# :Date: $Date: 2025-08-07 08:35:37 +0200 (Do, 07. Aug 2025) $
# :Copyright: © 2010 Günter Milde.
# :License: Released under the terms of the `2-Clause BSD license`_, in short:
#
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.
# This file is offered as-is, without any warranty.
#
# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause
"""
XeLaTeX document tree Writer.
A variant of Docutils' standard 'latex2e' writer producing LaTeX output
suited for processing with the Unicode-aware TeX engines
LuaTeX and XeTeX.
"""
from __future__ import annotations
__docformat__ = 'reStructuredText'
from docutils import frontend
from docutils.writers import latex2e
from docutils.writers.latex2e import PreambleCmds
class Writer(latex2e.Writer):
"""A writer for Unicode-aware LaTeX variants (XeTeX, LuaTeX)"""
supported = ('latex', 'tex', 'xetex', 'xelatex', 'luatex', 'lualatex')
"""Formats this writer supports."""
default_template = 'xelatex.tex'
default_preamble = """\
% Linux Libertine (free, wide coverage, not only for Linux)
\\setmainfont{Linux Libertine O}
\\setsansfont{Linux Biolinum O}
\\setmonofont[HyphenChar=None,Scale=MatchLowercase]{DejaVu Sans Mono}"""
config_section = 'xetex writer'
config_section_dependencies = ('writers', 'latex writers')
# use a copy of the parent spec with some modifications:
settings_spec = frontend.filter_settings_spec(
latex2e.Writer.settings_spec,
# removed settings
'font_encoding',
# changed settings:
template=('Template file. Default: "%s".' % default_template,
['--template'],
{'default': default_template, 'metavar': '<file>'}),
latex_preamble=('Customization by LaTeX code in the preamble. '
'Default: select "Linux Libertine" fonts.',
['--latex-preamble'],
{'metavar': '<preamble>',
'default': default_preamble}),
)
def __init__(self) -> None:
latex2e.Writer.__init__(self)
self.settings_defaults.update({'fontencoding': ''}) # use default (TU)
self.translator_class = XeLaTeXTranslator
class Babel(latex2e.Babel):
"""Language specifics for XeTeX.
Use `polyglossia` instead of `babel` and adapt settings.
"""
language_codes = latex2e.Babel.language_codes.copy()
# Additionally supported or differently named languages:
language_codes.update({
# code Polyglossia-name comment
'cop': 'coptic',
'de': 'german', # new spelling (de_1996)
'de-1901': 'ogerman', # old spelling
'dv': 'divehi', # Maldivian
'dsb': 'lsorbian',
'el-polyton': 'polygreek',
'fa': 'farsi',
'grc': 'ancientgreek',
'ko': 'korean',
'hsb': 'usorbian',
'sh-Cyrl': 'serbian', # Serbo-Croatian, Cyrillic script
'sh-Latn': 'croatian', # Serbo-Croatian, Latin script
'sq': 'albanian',
'sr': 'serbian', # Cyrillic script (sr-Cyrl)
'th': 'thai',
'vi': 'vietnamese',
# zh-Latn: ??? # Chinese Pinyin
})
# normalize (downcase) keys
language_codes = {k.lower(): v for k, v in language_codes.items()}
# Languages without Polyglossia support:
for key in ('af', # 'afrikaans',
'de-AT', # 'naustrian',
'de-AT-1901', # 'austrian',
# TODO: use variant=... for English variants
'en-CA', # 'canadian',
'en-GB', # 'british',
'en-NZ', # 'newzealand',
'en-US', # 'american',
'fr-CA', # 'canadien',
'grc-ibycus', # 'ibycus', (Greek Ibycus encoding)
'sr-Latn', # 'serbian script=latin'
):
del language_codes[key.lower()]
warn_msg = 'Language "%s" not supported by LaTeX (polyglossia)'
def __init__(self, language_code, reporter) -> None:
self.language_code = language_code
self.reporter = reporter
self.language = self.language_name(language_code)
self.otherlanguages = {}
self.warn_msg = 'Language "%s" not supported by Polyglossia.'
self.quote_index = 0
self.quotes = ('"', '"')
# language dependent configuration:
# double quotes are "active" in some languages (e.g. German).
self.literal_double_quote = '"' # TODO: use \textquotedbl ?
def __call__(self):
setup = [r'\usepackage{polyglossia}',
r'\setdefaultlanguage{%s}' % self.language]
if self.otherlanguages:
setup.append(r'\setotherlanguages{%s}' %
','.join(sorted(self.otherlanguages.keys())))
return '\n'.join(setup)
class XeLaTeXTranslator(latex2e.LaTeXTranslator):
"""
Generate code for LaTeX using Unicode fonts (XeLaTex or LuaLaTeX).
See the docstring of docutils.writers._html_base.HTMLTranslator for
notes on and examples of safe subclassing.
"""
def __init__(self, document) -> None:
self.is_xetex = True # typeset with XeTeX or LuaTeX engine
latex2e.LaTeXTranslator.__init__(self, document, Babel)
if self.latex_encoding == 'utf8':
self.requirements.pop('_inputenc', None)
else:
self.requirements['_inputenc'] = (r'\XeTeXinputencoding %s '
% self.latex_encoding)
def to_latex_length(self, length_str: str, node=None) -> str:
"""Convert "measure" `length_str` to LaTeX length specification.
XeTeX does not know the length unit px.
Use ``\\pdfpxdimen``, the macro holding the value of 1 px in pdfTeX.
This way, configuring works the same for pdftex and xetex.
"""
length_str = super().to_latex_length(length_str, node)
if length_str.endswith('px'):
if not self.fallback_stylesheet:
self.fallbacks['_providelength'] = PreambleCmds.providelength
self.fallbacks['px'] = '\n\\DUprovidelength{\\pdfpxdimen}{1bp}'
return length_str.replace('px', '\\pdfpxdimen')
return length_str
|