-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Support block-specific style overrides #13611
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
base: master
Are you sure you want to change the base?
Conversation
* Code highlight directives accept two new options, style-light and style-dark; these override the default Pygments style with a block-specific one, but only for the block in question * Classes amended were CodeBlock, LiteralInclude, and Code; this means directives code-block, sourcecode, literalinclude, and code * A theme may support light and/or dark styles; as already done, light and dark styles must be tracked separately. For this overhaul, a data structure is created inside the HtmlBuilder; this tracks the relevant styles, their associated PygmentsBridge highlighter, and the code blocks they're associated with. * Association with code blocks is handled by using the docutil's node hash; this is used as a CSS selector. * As the Html5Translator visits elements of the docutils tree, it discovers their light and dark styles, if any. It populates the HtmlBuilder's data structure. * To finalize, the CSS sheets are created. Since the Pygments style is now a "generated" file rather than static (it depends on what style overrides document contains), the function call is moved down: the HtmlBuilder's create_pygments_style_file got moved from copy_static_files() into a task under the finish() method. * CSS selectors are grouped to supply different code blocks with the same override style, if the user so chooses (Pygments handles this internally if presented the selectors). This minimizes the length of the CSS files. They're also annotated with a comment for their override style.
* There is a CSS media feature that is set by the user agent: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme ; we add those specifiers to the HTML builder's light and dark Pygments style sheet writers
* Use media queries and scopes to include light and dark styles in a single CSS file * `app.registry.css_files` apparently only tracked "extra" files, but not the default `pygments.css` file, so the `test_theming.test_dark_style` block removed that check * Added a `stylename` attribute to `PygmentsBridge` objects, so they can track the "pretty" style name used throughout Pygments, rather than relying on the classname of the associated style, which is not necessarily what is declared in the Pygments style entry-point installation
This reverts commit b5c34d2.
* `python_docs_theme` uses separate CSS files for light and dark modes, with a simple javascript function to toggle what is shown. It doesn't decorate either CSS file with a `@media` query. This seems advantageous, allowing the user to select their preferred view, and aligns with the philosophy that "the theme is not part of the document, but just a view of it" (paraphrased from MDN) * bugfix: specialized dark iterator used wrong value for CSS sheet building
* As for the HTML builder, the building of the style sheet is moved from the `prepare_writing` method to the `finish` one; overriding styles are discovered on-the-fly, so the `.sty` file can only be finalized after the document's nodes have been visited
The issue with the This is valid use of said function html.py#L516, so I am unsure how to tell |
All the tests are passing, the documentation is updated, and there's no visible issues left, so I don't have any more work planned on this. If there's something missing, or there's concerns, I'm happy to work / address them; any feedback would be welcome. As I mention in the Discussion linked in the first post, I have a project that requires this functionality |
* Whether a character is in the correct category for the definition of a LaTeX macro can, in the general sense, only be determined at compile time. As a fall-back, "valid" macro names generally use the a-zA-Z range; so we use this to replace any character not in those ranges with an uppercase Z using regular expressions. * Since the user-facing names come from the SetupTools installation entry-points, they need not match the `name` attribute in the associated classes (sigh). Added to this, something needs to be printed to the LaTeX file. So, on the Python side and any user-facing usage retain the user-given name (this also helps avoid collisions, as Z-replacement loses information); any LaTeX-printed file will use the "sanitized" version in the macro names where likely-wrong-category characters got replaced with a Z. A header / comment in the .sty file specifies the user-given name for that portion of the file * The sphinx re-definitions of "problematic" LaTeX characters (e.g. \PYGam{\&} ) gets overhauled; the string now contains an `override` key, for use with Python's `str.format(key=value)` syntax. When given an empty string, the string prior to this commit is returned; otherwise, appropriate overrides are generated for the various style-specific special characters. However, the usage in `sphinx/texinputs/sphinxlatexliterals.sty`, lines 561-650, seem to have the `PYG` prefix baked-in...
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
@hmedina I have implemented my remarks to achieve full LaTeX support at https://github.com/jfbu/sphinx/tree/multistyle. I think at some places your docstrings are a bit long and should be hardwrapped for shorter line lengths. I am not too happy about using twice and not only once md5, but it works. I checked the wrapping of long codelines inclusive of Anyway, do you mind if I push that commit to this branch? |
@jfbu hey there! I do not mind at all if you push your work to this branch; I'm very happy the full feature set is kept. Once you merge into this PR, I'll take a look at the docstrings; I kept them at the Ruff setting (95 characters?), but if a shorter length is preferable I'll shorten them, just let me know to what length. As for the MD5 hashing, it might be appropriate to replace the two calls and create a utility function just for providing "sanitized" LaTeX macro names; there's some functions in For the PDF breaking, what kind of break are you seeing? I just tried with the ~270 lines of Edit: a couple |
This is done. I will need to investigate why the RTD build test fails. (Maybe this will be a bit later as I have tasks ; the same commit on my clone passed all tests).
I personnally have a preference for 80 or even 78 charactes for line lengths in docstrings, but maybe we don't enforce it here. Maybe wait for a more global review of this PR. Things may need some attention like referring to builders from the LaTeX writer.
Yes another approach which I had considered avoids hashing altogether. It simply enumerates all the encountered specialized block styles, and output them as a string acceptable to LaTeX in command names i.e. only In my Python virtual environment I have about 70 Pygments styles and I checked the md5 truncated to 6 hexadecimals were unique. Using md5 solves our problem and keeps the produced sanitized tex names to a controlled length.
I see
with HEAD at 7fab81e and my test
Can you test with the new HEAD at 4b09baa? |
Testing failed apparentely because CI could not fetch HEAD after my push. No idea why and can't investigate for now. |
@hmedina I have merged master with was without conflicts (done by Ort strategy I did not look too closely if merge was sane). I was eager to see if CI testing would succeed and it seems to turn out good (attow only CI/Windows has not yet completed). Some glitch happened earlier for unknown reason at the commit I contributed prior. |
Now that we let Pygments use only Anyhow what matters is that we can use So a possibility avoiding the The difference though with this is that we have to pass the information to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the fact that this works fully for LaTeX/PDF output, LGTM! Internal aspects may need reviewing though.
Sorry there, my 4b09baa had a botched revert of changes in \def\PYGZob{{\{{}}
\def\PYGZcb{{\}}}} were very wrong with the result that I have corrected this oversight at the third commit I pushed here (27b5239). I know those commits might disappear as we will need at one point to rebase on master for the merge to be feasible. |
Hey there! Seems to be working mostly, however I noticed the background colors, for LaTeX builds, only appear for the main/default style; none of the local overrides have it (try with a default style of The HTML builder does not suffer from this, apparently because everything got a class selector with either Left: HTML build. Right: LaTeX build. As for using I'm going to try a couple work-around for the background color issue, and try to do some code cleanup. Anything else while I'm at it? |
The issue about background colors is a Pygments issue. Their output makes zero provision for setting a background color. The explanation is probably than until very recently the target LaTeX package
renders to PDF (using (note that above code-block for example the docstring is modified from original to test specific things; besides it was missing The background color you see in non especially styled blocks is not the one from the VerbatimColor
About
one has to reuse it after the code-block to reset another color. Of course, if we implement this at Sphinx level, we would prefer locate the change inside the LaTeX environment. Which however is not trivial due to actual structure of
Sorry for late reply but I recommend you don't devote energy to this as part of this PR, you can do it in a separate PR if you have the motivation. I don't see anything else regarding LaTeX/PDF until other maintainers check the structure of how information is exchanged between builder and writer. |
copied pasted from earlier now hidden review
It is needed to set the default color for text adequately, as the above choices do, so that un-highlighted tokens show on dark background. The issue for us is only to get from the Pygments style the info about what it wants to set as background color (and ideally default text color) for the framed block.
|
The As for the default text color, assuming there's one, that would inherit from the Not sure how to translate the values into viable latex, but for example, modifying the final part of if self.specialized_highlighters:
specialized_styles = []
for style_name, pyg_bridge in self.specialized_highlighters.items():
specialized_style = '\n% Stylesheet for style {}'.format(style_name)
specialized_style += pyg_bridge.get_stylesheet(style_name)
print('For style {}:'.format(style_name))
if pyg_bridge.get_style(style_name).background_color is not None:
bc = pyg_bridge.get_style(style_name).background_color.lstrip('#')
bc_rgb = '{:.2f},{:.2f},{:.2f}'.format(*[int(bc[i:i+2], 16)/255 for i in (0, 2, 4)])
print('\tgeneral background color was: {} -> {}'.format(bc, bc_rgb))
from pygments.token import Token
base_style = pyg_bridge.get_style(style_name).styles[Token]
if base_style: # could look like 'italic #000 bg:#ffffff'
match = re.match(r'#([0-9a-fA-F]{3,6})(?:\s+bg:#([0-9a-fA-F]{3,6}))?', base_style)
text_color_rgb = '{:.2f},{:.2f},{:.2f}'.format(*[int(match.group(1)[i:i+2], 16)/255 for i in (0, 2, 4)])
print('\tdefault text color was: {} -> {}'.format(match.group(1), text_color_rgb))
if match.group(2):
text_back_rgb = '{:.2f},{:.2f},{:.2f}'.format(*[int(match.group(2)[i:i+2], 16)/255 for i in (0, 2, 4)])
print('\tdefault text background color was: {} -> {}'.format(match.group(2), text_back_rgb))
specialized_styles.append(specialized_style)
f.write('\n'.join(specialized_styles)) My local testing gets this in output:
What would be the right way of translating this into background colors? I tried something with the NB: I guess both text-color and background-color could be optional, so the |
(I will be offline for some time so quick reply here)
What would be the right way of translating this into background colors? I tried something with the ***@***@***.*** line's use of \colorbox but didn't get much...
You can't set it from any of the mark-up set in the stylesheet. The way to proceed is for the Sphinx latex.py writer to inject a suitable `\sphinxsetup` setting `VerbatimColor`. Check my recent comments. A second `\sphinxsetup` will need to reset the original. `VerbatimColor`, I can take care of that later on as this may need some small add-on to `sphinx.sty`.
By the way using RGB not rgb you can use integer values from 0 to 255 for each component and this is better than rgb with decimals. And you can simply use {HTML}{6 hexadecimal digits}, which may be the simplest.
|
* `LaTeXBuilder.get_bridge_for_style()` was only used to get a recently-created `PygmentsBridge` object, but added a None-check. Refactoring the method that added the created object to return it (i.e. `LaTeXBuilder.add_block_style()`) avoids the extra method and the requirement for a None check. If the method fails, it would fail at creation of the PygmentsBridge object, which is better handled by its own reporting * Rename `LaTeXBuilder.add_block_style()` to `update_override_styles()`
* `StandaloneHTMLBuilder.get_bridge_for_style()` was only used to get a recently-created `PygmentsBridge` object, but added a None-check. Refactoring the methods that added the created object to return it (i.e. `add_block_dark_style()` & `add_block_light_style()`) avoids the extra method and the requirement for a None check. If the methods fail, they would fail at creation of the PygmentsBridge object(s), which is better handled by its own reporting * Rename `add_block_dark_style()` to `update_override_styles_dark()` * Rename `add_block_light_style()` to `update_override_styles_light()`
Some styles set the background_color to #ffffff, in such cases we ignore that because the default Sphinx PDF light gray is nicer. This also handles a "default" text color, but some testing shoud be done.
I have implemented following on your comment LaTeX/PDF support for |
Purpose
Allow local overrides of the Pygments style, on a per-code-block basis.
This work extends the directives for
code-block
,sourcecode
,literalinclude
andcode
, by introducing two new options,style-light
andstyle-dark
. As the app parses the document and discovers these, the builders collects the information. This allows Pygments to produce CSS or STY files (for HTML & LaTeX) for the relevant classes. These are moved from the initializing phase of the builder, to the finalizing phase.Special attention was paid to light & dark modes. For most builders (e.g. LaTeX), this distinction is ignored, with only the
light
style having any effect; this mirrors how currently, the configuration valuepygments_dark_style
has no effect on such builders. For the HTML family builders, if the theme supports a dark style, then thepygments_dark.css
file gains the appropriate selectors, independently of what happens to thepygments.css
file for light styles. Since in the general sense, a user need not track the overrides for light style in the same fashion as for the dark style, the selectors are generated by hashing the docutils node. In a CPython implementation, this returns a pointer; as the docutils hierarchy is a tree, a pointer to the code-block appears sufficient for these purposes, as collisions should not happen; this said, I'm open to suggestions. The single-style builders, like LaTeX, use a simpler tracking for their command prefixes (used similarly as CSS selectors in the STY files).The files have been linted with Ruff.
The new options are tested in the test suite. I'm unsure how to incorporate more advanced testing for other parts, so I welcome feedback (should I add tests to the builders?).
References