SF.net SVN: docutils:[9920] trunk/docutils
milde--- via Docutils-checkins <[email protected]>
| Newsgroups | gmane.text.docutils.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 9920
http://sourceforge.net/p/docutils/code/9920
Author: milde
Date: 2024-09-04 19:33:30 +0000 (Wed, 04 Sep 2024)
Log Message:
-----------
Refactor `HTMLTranslator.image_size()`.
Use `nodes.parse_measure()` instead of ad-hoc parsing
when scaling size values.
Split "spaghetti code" into auxiliary method `read_size_with_PIL()`.
Modified Paths:
--------------
trunk/docutils/docutils/writers/_html_base.py
trunk/docutils/test/test_writers/test_html5_polyglot.py
trunk/docutils/test/test_writers/test_html5_polyglot_misc.py
Modified: trunk/docutils/docutils/writers/_html_base.py
===================================================================
--- trunk/docutils/docutils/writers/_html_base.py 2024-09-04 19:33:12 UTC (rev 9919)
+++ trunk/docutils/docutils/writers/_html_base.py 2024-09-04 19:33:30 UTC (rev 9920)
@@ -14,18 +14,23 @@
#
# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause
-"""common definitions for Docutils HTML writers"""
+"""Common definitions for Docutils HTML writers."""
+from __future__ import annotations
+
+__docformat__ = 'reStructuredText'
+
import base64
import mimetypes
import os
import os.path
-from pathlib import Path
import re
import urllib.parse
import urllib.request
import warnings
import xml.etree.ElementTree as ET # TODO: lazy import in prepare_svg()?
+from pathlib import Path
+from typing import TYPE_CHECKING
import docutils
from docutils import frontend, languages, nodes, utils, writers
@@ -36,6 +41,10 @@
unichar2tex, wrap_math_code, MathError)
+if TYPE_CHECKING:
+ from numbers import Real
+
+
class Writer(writers.Writer):
supported = ('html', 'xhtml') # update in subclass
@@ -404,58 +413,67 @@
text = str(text)
return text.translate(self.special_characters)
- def image_size(self, node):
- # Determine the image size from the node arguments or the image file.
- # Return a size declaration suitable as "style" argument value,
- # e.g., ``'width: 4px; height: 2em;'``.
- # TODO: consider feature-request #102?
- size = [node.get('width', None), node.get('height', None)]
- if 'scale' in node:
- if 'width' not in node or 'height' not in node:
- # try reading size from image file
- reading_problems = []
- uri = node['uri']
- if not PIL:
- reading_problems.append('Requires Python Imaging Library.')
- if mimetypes.guess_type(uri)[0] in self.videotypes:
- reading_problems.append('PIL cannot read video images.')
- if not self.settings.file_insertion_enabled:
- reading_problems.append('Reading external files disabled.')
- if not reading_problems:
- try:
- imagepath = self.uri2imagepath(uri)
- with PIL.Image.open(imagepath) as img:
- imgsize = img.size
- except (ValueError, OSError, UnicodeEncodeError) as err:
- reading_problems.append(str(err))
- else:
- self.settings.record_dependencies.add(
- imagepath.replace('\\', '/'))
- if reading_problems:
- msg = ['Cannot scale image!',
- f'Could not get size from "{uri}":',
- *reading_problems]
- self.messages.append(self.document.reporter.warning(
- '\n '.join(msg), base_node=node))
- else:
- for i in range(2):
- size[i] = size[i] or '%dpx' % imgsize[i]
- # scale provided/determined size values:
- factor = float(node['scale']) / 100
- for i in range(2):
- if size[i]:
- match = re.match(r'([0-9.]+)(\S*)$', size[i])
- size[i] = '%s%s' % (factor * float(match.group(1)),
- match.group(2))
- size_declarations = []
- for i, dimension in enumerate(('width', 'height')):
- if size[i]:
- # Interpret unitless values as pixels:
- if re.match(r'^[0-9.]+$', size[i]):
- size[i] += 'px'
- size_declarations.append(f'{dimension}: {size[i]};')
- return ' '.join(size_declarations)
+ def image_size(self, node: nodes.image) -> str:
+ """Determine the image size from node arguments or the image file.
+ Auxiliary method called from `self.visit_image()`.
+
+ Provisional.
+ """
+ # TODO: Use "width" and "hight" for unitless integers?
+ # [feature-requests:#102]
+
+ # List with optional width and height measures ((value, unit)-tuples)
+ measures: list[tuple[Real, str] | None] = [None, None]
+ dimensions = ('width', 'height')
+ for i, dimension in enumerate(dimensions):
+ if dimension in node:
+ measures[i] = nodes.parse_measure(node[dimension])
+ if None in measures and 'scale' in node:
+ # supplement with (unitless) values read from image file
+ imgsize = self.read_size_with_PIL(node)
+ if imgsize:
+ measures = [measure or (imgvalue, '')
+ for measure, imgvalue in zip(measures, imgsize)]
+ # scale values
+ factor = node.get('scale', 100) / 100 # scaling factor
+ if factor != 1:
+ measures = [(measure[0] * factor, measure[1])
+ for measure in measures if measure]
+ # format as CSS declarations and return
+ return ' '.join(f'{dimension}: {measure[0]:g}{measure[1] or "px"};'
+ for dimension, measure in zip(dimensions, measures)
+ if measure)
+
+ def read_size_with_PIL(self, node) -> tuple[int, int] | None:
+ # Try reading size from image file.
+ # Internal auxiliary method called from `self.image_size()`.
+ reading_problems = []
+ uri = node['uri']
+ if not PIL:
+ reading_problems.append('Requires Python Imaging Library.')
+ if mimetypes.guess_type(uri)[0] in self.videotypes:
+ reading_problems.append('PIL cannot read video images.')
+ if not self.settings.file_insertion_enabled:
+ reading_problems.append('Reading external files disabled.')
+ if not reading_problems:
+ try:
+ imagepath = self.uri2imagepath(uri)
+ with PIL.Image.open(imagepath) as img:
+ imgsize = img.size
+ except (ValueError, OSError, UnicodeEncodeError) as err:
+ reading_problems.append(str(err))
+ else:
+ self.settings.record_dependencies.add(imagepath)
+ if reading_problems:
+ msg = ['Cannot scale image!',
+ f'Could not get size from "{uri}":',
+ *reading_problems]
+ self.messages.append(self.document.reporter.warning(
+ '\n '.join(msg), base_node=node))
+ return None
+ return imgsize
+
def prepare_svg(self, node, imagedata, size_declaration):
# Edit `imagedata` for embedding as SVG image.
# Use ElementTree to add node attributes.
@@ -607,7 +625,7 @@
child['classes'].append(class_)
def uri2imagepath(self, uri):
- """Get filesystem path corresponding to an URI.
+ """Get POSIX filesystem path corresponding to an URI.
The image directive expects an image URI__. Some writers require the
corresponding image path to read the image size from the file or to
Modified: trunk/docutils/test/test_writers/test_html5_polyglot.py
===================================================================
--- trunk/docutils/test/test_writers/test_html5_polyglot.py 2024-09-04 19:33:12 UTC (rev 9919)
+++ trunk/docutils/test/test_writers/test_html5_polyglot.py 2024-09-04 19:33:30 UTC (rev 9920)
@@ -46,7 +46,7 @@
if (tuple(int(i) for i in PIL.__version__.split('.')) >= (10, 3)):
DUMMY_PNG_NOT_FOUND = ("[Errno 2] No such file or directory: '%s'"
% Path('dummy.png').resolve())
- SCALING_OUTPUT = 'style="width: 32.0px; height: 32.0px;" '
+ SCALING_OUTPUT = 'style="width: 32px; height: 32px;" '
NO_PIL_SYSTEM_MESSAGE = ''
else:
REQUIRES_PIL = '\n Requires Python Imaging Library.'
Modified: trunk/docutils/test/test_writers/test_html5_polyglot_misc.py
===================================================================
--- trunk/docutils/test/test_writers/test_html5_polyglot_misc.py 2024-09-04 19:33:12 UTC (rev 9919)
+++ trunk/docutils/test/test_writers/test_html5_polyglot_misc.py 2024-09-04 19:33:30 UTC (rev 9920)
@@ -18,8 +18,8 @@
# so we import the local `docutils` package.
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
-from docutils import core
-from docutils.writers import html5_polyglot
+from docutils import core, frontend, nodes, utils
+from docutils.writers import html5_polyglot, _html_base
# TEST_ROOT is ./test/ from the docutils root
TEST_ROOT = Path(__file__).parents[1]
@@ -241,5 +241,21 @@
self.assertNotIn('MathJax', head)
+class ImagesTestCase(unittest.TestCase):
+ """Test image handling routines."""
+
+ settings = frontend.get_default_settings(_html_base.Writer)
+ document = utils.new_document('test data', settings)
+ translator = _html_base.HTMLTranslator(document)
+
+ def test_image_size(self):
+ image = nodes.image(height='3', width='4em')
+ self.assertEqual(self.translator.image_size(image),
+ 'width: 4em; height: 3px;')
+ image = nodes.image(height='3', width='4em', scale=50)
+ self.assertEqual(self.translator.image_size(image),
+ 'width: 2em; height: 1.5px;')
+
+
if __name__ == '__main__':
unittest.main()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.