@@ -649,6 +649,71 @@ def do_filetile(out, suffix: str | None = None, state: str = None):
649
649
f" <img class=\" resource-thumb\" src=\" { src_kludge ('/_static/filetiles/' + name )} \" />\n \n " ))
650
650
651
651
652
+ # pending: a fix for Pillow / Sphinx interactions?
653
+ def read_image_size (path : Path | str ) -> tuple [int , int ]:
654
+ """Get the size of a raster image and close the file.
655
+
656
+ This function ensures Sphinx does not break ``with``
657
+ blocks using :py:func:`PIL.Image.open`:
658
+
659
+ Pillow makes assumptions about streams which Sphinx
660
+ may interfere with:
661
+
662
+ #. Pillow assumes things about stream read / write
663
+ #. Sphinx sometimes changes stream read / write global
664
+ #. This makes :py:func:`PIL.Image.open` fail to close files
665
+ #. Python 3.11+ reports unclosed files with warning
666
+
667
+ This is where the problem begins:
668
+
669
+ * When nitpicky mode is off, the logs are filled with noise
670
+ * When it is on, build can break
671
+
672
+ The fix below is good-enough to get build running. To dive
673
+ deper, start with these:
674
+
675
+ #. Pillow dislikes things which alter stream read/write
676
+ (See https://github.com/python-pillow/Pillow/issues/2760)
677
+ #. Sphinx overrides logging stream handling
678
+ (See https://www.sphinx-doc.org/en/master/extdev/logging.html#sphinx.util.logging.getLogger)
679
+
680
+ Args:
681
+ path: A path to an image file to read the size of.
682
+
683
+ Returns:
684
+ A ``(width, height)`` tuple of the image size.
685
+ """
686
+ # Isolating this in a function prevents Sphinx and other
687
+ # "magic" stream things from breaking the context manager.
688
+ # If you care to investigate, see the docstring's links.
689
+ with PIL .Image .open (path ) as im :
690
+ return im .size
691
+
692
+
693
+ def read_size_info (path : Path ) -> str :
694
+ """Cleanliness wrapper for reading image sizes.
695
+
696
+ #. SVGs say they are SVGs
697
+ #. Raster graphics report pixel size
698
+ #. All else says it couldn't get size info.
699
+
700
+ Args:
701
+ path: A path to an image file.
702
+
703
+ Returns:
704
+ The formatted size info as either dimensions or
705
+ another status string.
706
+ """
707
+ if path .suffix == ".svg" :
708
+ return "Scalable Vector Graphic"
709
+
710
+ elif (pair := read_image_size (path )):
711
+ width , height = pair
712
+ return f"{ width } px x { height } px"
713
+
714
+ return "Could not read size info"
715
+
716
+
652
717
def process_resource_files (
653
718
out ,
654
719
file_list : List [Path ],
@@ -707,18 +772,12 @@ def start():
707
772
#out.write(indent(" ", tile_rst_code))
708
773
709
774
size_info = None
710
- if suffix == ".svg" :
711
- size_info = "Scalable Vector Graphic"
712
- else :
713
- try :
714
- im = PIL .Image .open (path )
715
- im_width , im_height = im .size
716
- size_info = f"{ im_width } px x { im_height } px"
717
- except Exception as e :
718
- log .warning (f"FAILED to read size info for { path } :\n { e } " )
719
-
720
- if size_info is None :
721
- size_info = "Could not read size info"
775
+ try :
776
+ size_info = read_size_info (path )
777
+ except Exception as e :
778
+ log .warning (f"FAILED to read size info for { path } :\n { e } " )
779
+
780
+
722
781
parts .append (f"*({ size_info } )*\n " )
723
782
out .write (indent (" " , '\n ' .join (parts )))
724
783
out .write ("\n \n " )
0 commit comments