File: thumbspage/examples/__prior-version-items/__prior-versions-code/thumbspage-1.5-preImageScalingJS.py

#!/usr/bin/python3
"""
===========================================================================
thumbspage.py, version 1.5, Aug-12-2018
Synopsis: Make an HTML thumbnail-links page plus image-viewer pages 
          for the images in a source folder.

Requires: Any Python 3.X, plus the Pillow (PIL) image library 
          available at https://pypi.python.org/pypi/Pillow.
Launch:   Run thumbspage.py, input run parameters in console.
          See also option setting variables at start of code.
Examples: See learning-python.com/trnpix, and /examples here.
License:  Provided freely but with no warranties of any kind.
Author:   Copyright M. Lutz (learning-python.com), 2016-2018.

===========================================================================
OVERVIEW

Given a folder of image files, this script generates a basic HTML
index page with thumbnail links for each image file in the folder
that open either a generated viewer page or the image directly.

The script skips non-image files, uses optional header and footer 
HTML inserts, makes an automatic bullet list for any subfolders 
in the images folder (not named with a leading "_"), and creates 
the output index page in the images folder itself, along with a
"thumbs" subfolder for thumbnail images and viewer pages' HTML.  

As of 1.3, non-ASCII Unicode filenames and content are supported. 
As of 1.4, all output pages are more mobile-friendly.  As of 1.5,
formatted image-viewer pages with next/previous links can also be
generated; when omitted, index links open images per browsers.

To view results, open the output "index.html" index page created in 
your images folder.  To publish the page, upload the entire images 
folder, including its generated "thumbs" subfolder and "index.html" 
file, to the folder representing your page on your site's web-server 
host; zip first for convenience.  The names of the generated index
page and thumbs folder can both be changed in settings below.

For more custom behavior, add custom HTML code to the top and bottom
of the output page by placing it in files in the images folder named
"HEADER.html" and "FOOTER.html" respectively (both are optional). 
If these files are not present, generic HTML and text is generated 
in the output page around the thumbs table.  For details on how to 
code these files, see the examples in examples/trnpix.  In brief:

- HEADER.html should be a full HTML preamble (with doctype, mobile 
  viewport, content type/encoding, and any styles) and begin <body> 
- FOOTER.html should add any post-table content and close the <body>

===========================================================================
USAGE NOTE

Because image scaling is weak in CSS (and JavaScript is both overkill
and nonstarter for this program), the image-viewer pages added in 
version 1.5 should be considered an optional feature.

These pages can be disabled via the last console reply (see the next
section) if the former browser-native view is preferred.  If these 
pages are used, your desktop users may wish to resize windows for 
optimal view when needed, and click "Raw" for browser-native view 
when desired.  Mobile users need not resize, but can tap "Raw" to 
go native.  Despite viewer-pages' scaling limits, their "Prev"/"Next"
navigation links are still a compelling feature in most use cases.

===========================================================================
USAGE EXAMPLE

  /.../thumbspage/_private/test15$ python3 ../../thumbspage.py 
  Clean thumbs folder [y or n] (enter=n)? y
  Thumbs per row [int] (enter=4)? 
  Thumb max size [x, y] (enter=(100, 100))? 
  Images folder path [. or dir] (enter=.)? trnpix
  Use image-viewer pages [y or n] (enter=n)? y
  Running
  Cleaning: trnpix/thumbs/1996-first-pybook.png
  Cleaning: trnpix/thumbs/1996-first-pybook.png.html
  ...
  Skipping: .DS_Store
  Making thumbnail: trnpix/thumbs/1996-first-pybook.png
  Making thumbnail: trnpix/thumbs/1998-puertorico-1.jpg
  ...
  Skipping: _cut
  Skipping: _HOW.txt
  ...
  Generating thumbnails index page
  Generating view page for: 1996-first-pybook.png
  Generating view page for: 1998-puertorico-1.jpg
  ...
  Finished: see results in images folder.

===========================================================================
GUI MODE

You can also view the thumbs and images in GUI mode (i.e., without a 
web browser) by using the PP4E book example included and used here
on Windows or Unix (Mac OS and Linux):

  c:\...\thumbspage> py -3.3 viewer_thumbs.py examples\test\images
  /.../thumbspage$ python3 viewer_thumbs.py examples/test/images

This is a very basic GUI view, however; the book's later PyPhoto 
example adds scroll bars to both the thumbs index and open images.

  Update: a much-upgraded PyPhoto is now available standalone, at
  http://learning-python.com/pygadgets.html (it's a bundled gadget).

===========================================================================
VERSION HISTORY

1.5, Aug-12-18: formatted image-viewer pages.

     Generate a styled viewer page for each image in the folder,
     instead of relying on each browser's native image display.

     These new viewer pages are an addition to the former index 
     page.  They are opened on index-page thumbnail clicks, and
     have filename, the image scaled per CSS, view-native and 
     goto-index links, and previous/next-image links that cycle
     through all images without having to return to the index page.  

     Viewer pages are generated in the thumbs folder, along with 
     the prior version's thumbnail-index image files.  They can 
     also be suppressed via console input prompts; when omitted, 
     images open in browsers directly and natively, as before.
     An example client - http://learning-python.com/trnpix/

     Caveat: because image scaling is weak in CSS (and JavaScript
     is overkill here), image-viewer pages should be considered 
     an optional feature.  See "USAGE NOTE" above for deployment
     suggestions, and code notes below for more details.

     1.5 also:
     - Formalizes index/navigation ordering (it's now case sensitive
       by default everywhere, but can be changed in settings below).

     - Formalizes URL-escapes Unicode encoding (it's now always 
       UTF-8 regardless of the encoding used for whole HTML pages,
       because this seems to be required by standards and browsers).

     - Sets body font to Arial (sans serif) for default-header index 
       pages (which looks nicer and matches new viewer pages' font,
       but is not inlined so it can differ in a custom HEADER.html).

     - Works around a desktop Chrome <hr> bug (see "CAVEATS" #6 
       ahead), by restyling the thumbs table with table borders 
       instead of <hr>s.  As a consequence, the thumbs table now
       always stretches to 100% window width, to extend the border
       lines (this was formerly a configuration, off by default).

     - Sets thumbs-table background color to light grey as part of 
       the <hr> restyling, and allows it to be configured in settings 
       below.  Viewer page color can be similarly tailored below.

     - Refactors its code to functions (it's now large enough 
       that top-level code is difficult to navigate), and cleans
       up its page output (HTML/CSS is tough to read as it is).

     - Still uses all-inline styles for index pages, not <style> 
       blocks, so that custom HEADER.html files need not include 
       or link to styles for the generated table.  Viewer pages 
       use a <style> block, as they are not customizable (yet?).


1.4, Mar-4-18: restyle for mobile and improved table display. 

     This script's output page now better supports browsers on 
     smaller screens, and looks nicer in general.  Its new CSS:

     - Autoscrolls the thumbs table to appease mobile browsers;
     - Adds padding to thumb cells to reduce run-together; 
     - Center-aligns thumbs images for a more even look; this 
       helps overall, but especially for narrow/portrait images; 
     - Uses nowrap paragraphs for image labels; the former <br>+wrap
       scheme looked jumbled, and made Chrome (only!) arrange thumb 
       table columns unevenly in small windows (left was narrower).

     This version also adds a mobile-friendly viewport <meta> tag 
     to default headers if "useViewPort."  This may impact other 
     page components; use a custom HEADER.html for more options.  

     Tip: because filenames used on labels are now not wrapped, 
     their width largely determines column spacing in the index
     page; use shorter filenames for less space between columns.

1.3, Aug-8-16: support non-ASCII Unicode filenames and content:

     a) HTML-escape all added text - image, folder, subfolder names

     b) URL-escape all added links - to thumbs, images, subfolders

     c) Output the index file in UTF-8 Unicode encoding by default,
        with a content-type <meta> tag; ASCII content is unchanged,
        as it is a subset of UTF-8; other encodings may be used for
        the output file via setting outputEncoding in the code 

     d) Load any header and footer inserts per UTF-8 Unicode encoding
        by default, as it is general and supports ASCII directly;
        other encodings, including the platform's default, may be
        used for inserts via setting insertEncoding in the code

     e) Assume any inserts are both HTML-safe and UTF-8-compatible

     See examples\test\escapes\images for a Unicode test case.
     
1.2, Aug-1-16: add table styling options, per settings in code:

     a) Uniform-width columns, not per content (_on_ by default)
     b) Stretch thumbs table to fill whole window (_off_ by default)
     c) Scrollbar if window too small? (skipped in 1.2, added in 1.4)

1.1, Jul-27-16: add auto subfolder-links list, per setting in code.

1.0, Jul-24-16: initial release.

===========================================================================
DEVELOPER NOTES

1) (all) SCOPE: Apart from header/footer inserts, this script makes
fairly simplistic image and subfolder links only; edit either
the output HTML page or the code here that creates it as desired.
(Version 1.5 has grown more sophisticated, with image-viewer pages.)

2) (1.1) SUBFOLDERS: Makes a bullet list for any subfolders in the
images folder (else they cannot be navigated to), but skips all
other non-image content; put any doctype, readme, font, CSS code,
etc., in header or footer.  Run on each image tree subfolder manually;
automating this and other content seems too clever and complex.

3) (1.2) STYLING: Stretching the thumbs table to fill the whole window
is off by default, as it can appear too spread out for short names in
large windows.  Enable this via the code setting below if desired.

4) (1.2) STYLING: A table overflow scrollbar was skipped, as it
appears only at table bottom, which can be very far away on large
pages.  Instead, use the browser's automatic general window scroll.

   ==> changed in 1.4: now uses CSS autoscroll for thumbs table,
       which is more mobile-friendly, and shows scroll iff needed.

5) (1.3) UNICODE: The script now fully supports images with non-ASCII
names, but getting them onto your web server may take special steps.
On my Linux server, an "unzip" of the WinZip zip package failed on the
Unicode test images, but GoDaddy's browser-based file upload worked.
See examples/test/escapes/readme.txt for more on uploading files.
See also learning-python.com/ziptools for a Python zip alternative.

6) (1.3) UNICODE: the insertEncoding setting now defaults to UTF-8,
as this is a general scheme which also works for ASCII files.  To use
your platform's default encoding instead, set this variable to None,
or save your header/footer inserts in UTF-8 format.

7) (1.3) UNICODE: If (but only if) file or folder names are non-ASCII,
custom HEADER.html files must include a <meta> content-type tag with
a charset matching the outputEncoding (which is UTF-8 by default).
Default headers include this tag automatically (see the code).

8) (all) Note that index-page styling must be all inline, to avoid
asking custom HEADER.html files to code or link to this CSS code.

9) (all) RELATED: See also pixindex, a related tool that adds images
to a zip file and FTPs them to a sever: learning-python.com/pixindex.

===========================================================================
CAVEATS AND TBDS

--CLOSED--

1) Python 3.X assumes local platform's Unicode encoding default
for header/footer inserts and the output page: change as needed.

   ==> Addressed and resolved in 1.3, per version notes above.

2) This script would probably run on Python 2.X too if the
viewer_thumbs module didn't import "tkinter" (a book legacy).

   ==> But no longer as is, as of 1.3's Unicode enhancements.

3) Styling of the table is open ended.  For example, a <table>
style "border-spacing: 4px;" may help avoid names running into
each other, though the default (2px?) seems adequate.

   ==> Addressed in version 1.4 with extra CSS styling

4) The reused viewer_thumbs module skips non-image files by simply
catching errors; this could use Python's mimetypes module instead.

   ==> Addressed in version 1.5: incorporated the PyPhoto mods.

   ==> See PyPhoto's newer version of this module, which uses 
       mimetypes as described; alas, it's a fork.  Here's the 
       newer code's online link:
          http://learning-python.com/
          pygadgets-products/unzipped/_PyPhoto/PIL/viewer_thumbs.py

5) Relying on browser-specific display of full images isn't ideal,
because such displays vary in scaling, background color, etc.

   ==> Addressed by version 1.5's image-viewer pages (see above).

6) Desktop Chrome botches <hr>s and <img> borders at some zoom 
levels: the former may be lighter at some page locations than others, 
and the latter may drop some sides altogether.  

This was first seen when odd Unicode characters were on-page, but 
can happen anywhere - at user-zoom levels at or below 90%, all bets 
are off on <hr> and <img>-border rendering.  It also happens on both
index pages and 1.5 viewer pages; the latter's CSS <img> borders may
drop one or more sides at <= 90% zoom.

This has been observed on desktop Chrome only, and in versions 66 
and 68 on Mac OS and Windows.  The only fix seems to be to not use
<hr> or <img> borders - an extreme workaround.  Styling <hr>s (e.g., 
style="height: 0.5px; background-color: black;") doesn't fix the 
issue at all zoom levels.  This is clearly a (temporary?) browser 
bug, but unfortunate in this site's most widely-used browser.

    ==> Addressed by 1.5's restyle: table borders replace <hr>s.
        Chrome borders around <img>s still vanish at zoom <= 90%,
        but they are nice enough to keep, even if only 3 sided.

--OPEN--

7) A ".." parent link is not generated in automatic subfolder
lists; should it be (the parent may or may not have images)?

8) Creating the index file in the image folder might preclude
use of some page-generation tools (see trnpix template copies).

9) Assuming UTF-8 for all insert files' Unicode encoding is simple
(and also works for ASCII, as it's a UTF-8 subset), but requires
either a minor script change or file conversions for differing
platform defaults; is this a significant usage factor?

10) This could generate a doctype and meta content-type tag in all
cases - not just for default headers - but that would limit doctype.

11) Even after refactoring to functions in 1.5, there are still 
a lot of globals in this code; clean up more in next release?

12) Viewer-page image scaling in CSS is lousy - there's no way to 
adjust to changes in window aspect ratio well.  For more details, 
see the "USAGE NOTE" above, and code notes below.  JavaScript may 
help, but using Python to generate HTML that embeds both CSS and
JavaScript seems a bit much for a simple Python image viewer...

===========================================================================
"""

#
# Library tools
#

import os, sys, glob, re                    # [1.5] re for input tests
import html, cgi, urllib.parse              # [1.3] text escapes

if sys.version[0] == 2: input = raw_input   # 2.X compatibility (but unused!)

from viewer_thumbs import makeThumbs        # courtesy of the book PP4E
from viewer_thumbs import isImageFileName   # courtesy of standalone PyPhoto
from viewer_thumbs import imageWideHigh     # courtesy of Pillow/PIL


def html_escape(text, **options):
    """
    -----------------------------------------------------------------------
    HTML escapes - for text inserted in HTML code
 
    [1.5] Both the html and cgi escaping functions take an additional
    'quote' argument which defaults to True for html.escape, but False 
    for cgi.escape; a True is needed iff the result is embedded in a 
    quoted HTML attribute (e.g., <tag attr="%s">, but not <tag>%s</tag>).

    [1.3] cgi.escape is subsumed by html.escape which was new in 3.2.
    Both escape HTML-syntax text: 'good>&day' => 'good&gt;&amp;day'.
    -----------------------------------------------------------------------
    """
    escaper = html.escape if hasattr(html, 'escape') else cgi.escape
    return escaper(text, **options)


def url_escape(link):
    """
    -----------------------------------------------------------------------
    URL escapes - for the text of inserted links
 
    [1.5] Always use UTF-8 here, not outputEncoding, per the following.
 
    [1.3]: The 'encoding' here is used only to preencode to bytes before 
    applying escape replacements.  The returned URL is an ASCII str string 
    with '%xx' escapes; it's a URL-escaped format of Unicode-encoded text. 

    How the resulting URL link is interpreted depends on the agent that 
    unescapes it later, but general UTF-8 handles encoding of arbitrary 
    content, and its unescaped (but still encoded) bytes are recognized 
    everywhere that this script's results have been tested. 

    Subtly, the encoding used for the while enclosing HTML page's content 
    and declared in its <meta> tag (this script's 'outputEncoding') has 
    nothing to do with the encoding used for embedded and escaped URL links
    (e.g., the HTML/URL encodings pair UTF-16/UTF-16 fails in browsers 
    tested, but UTF-16/UTF-8 works correctly).

    In fact, UTF-8 appears to be required for URLs per standards docs,
    which makes urllib's alternative encoding option seem a bit dubious:
        https://tools.ietf.org/html/rfc3986#section-2.5 (older)
        https://tools.ietf.org/html/rfc3987#section-6.4 (newer)

    Tool examples:
    >>> ord('☞'), hex(ord('☞'))                   # code points
    (9758, '0x261e')
    >>> '☞'.encode('utf8'), '☞'.encode('utf16')   # encoded bytes
    (b'\xe2\x98\x9e', b'\xff\xfe\x1e&')

    >>> from urllib.parse import quote            
    >>> quote('http://a+b&c☞', encoding='utf8')   # encode + escape
    'http%3A//a%2Bb%26c%E2%98%9E'
    >>> quote('http://a+b&c☞', encoding='utf16')
    '%FF%FEh%00t%00t%00p%00%3A%00/%00/%00a%00%2B%00b%00%26%00c%00%1E%26'

    Other ideas: it's possible to skip urllib's Unicode encoding step by 
    calling its quote_from_bytes(), but this just passes the buck - it 
    requires a manually encoded bytes.  URLs might also be embedded in 
    HTML pages using the whole-page Unicode encoding, with only HTML 
    escapes, or with no escapes (e.g., url_escape = lambda link: link,
    or url_escape = lambda link: html_escape(link, quote=True)); this 
    almost works for UTF-16, but some pathological filename links fail.
    -----------------------------------------------------------------------
    """
    return urllib.parse.quote(link, encoding='UTF-8')


#==========================================================================
# CONFIGURE, PART 1: manual settings for rarely-changed options
#==========================================================================

# output folder/file names
THUMBS = 'thumbs'                           # created subfolder (changeable)
INDEX  = 'index'                            # created page (or 'default'/'home')

# code generation options
listSubfolders = True                       # auto folder list? (or via header)
uniformColumns = True                       # same-width columns? (else content)

# [1.5] now always on, for table borders
# spanFullWindow = False                    # stretch table to window's width?

# recent additions
useViewPort   = True                        # add mobile-friendly viewport? [1.4]
caseSensOrder = True                        # index/nav order case sensitive? [1.5]
thumbsBgColor = '#f5f5f5'                   # thumbs index table (was 'white') [1.5]
viewerBgColor = 'black'                     # viewer pages background color [1.5]

#
# 1.3: use explicit Unicode encodings (not 'locale' platform defaults)
#
# insertEncoding 
#     is used for header/footer file loads;
#     None=default platform encoding, 'utf8' also handles ascii;
#
# outputEncoding 
#     is used for generated pages' content and <meta> tags (only);
#     use a real encoding name (not None), 'UTF-8' works for all;
#
insertEncoding = 'UTF-8'                    # or None, 'latin1', 'utf16',...
outputEncoding = 'UTF-8'                    # changeable, but utf8 is general


#==========================================================================
# CONFIGURE, PART 2: console inputs for per-run options, enter=default
#==========================================================================

# Tools [1.5]

def ask(prompt, hint, default):
    return input('%s [%s] (enter=%s)? ' % (prompt, hint, default))

def askbool(prompt):
    return ask(prompt, 'y or n', 'n').lower() in ['y', 'yes']

def askint(prompt, hint, default):
    reply = ask(prompt, hint, default)
    return int(reply) if reply else default

def askeval(prompt, hint, default, require=None):
    reply = ask(prompt, hint, default)
    if reply and require: 
        # else eval() mildly dangerous [1.5]
        assert re.match(require, reply), 'Invalid input "%s"' % reply
    return eval(reply) if reply else default


# Inputs

# y or n => remove any exiting thumb files?
cleanFirst = askbool('Clean thumbs folder')

# int => fixed row size, irrespective of window
thumbsPerRow = askint('Thumbs per row', 'int', 4)   # 5->4 so less scrolling [1.4]

# (int, int) => max (x, y) pixels limit, preserving original aspect ratio
require = '\(?[0-9]+\s*,\s*[0-9]+\)?'   # 2-tuple, parens optional
thumbMaxSize = askeval('Thumb max size', 'x, y', (100, 100), require)

# str => images folder path: images, header/footer, output
imageDir = ask('Images folder path', '. or dir', '.')   # default = cwd

# y or n => create image viewer pages? [1.5]
useViewerPages = askbool('Use image-viewer pages') 


# Calcs

# don't make a thumbs folder if input dir bad [1.5]
assert os.path.isdir(imageDir), 'Invalid image folder'

# the output page created in images folder
indexPath = os.path.join(imageDir, INDEX + '.html')

# optional inserts in images folder, else generic text
headerPath = os.path.join(imageDir, 'HEADER.html')    
footerPath = os.path.join(imageDir, 'FOOTER.html')


#==========================================================================
# MAKE THUMBNAILS in image folder subdir (via viewer_thumbs)
#==========================================================================

def makeThumbnails(imageDir):
    """
    -----------------------------------------------------------
    Reuse the (now modified) thumbnail generator from PP4E.
    Its [(imgname, thumbobj)] return value is unused here: 
    os.listdir() is run later to collect thumb names, and
    thumb objects are not used (building pages, not GUIs).
    -----------------------------------------------------------
    """
    if cleanFirst:
        # this cleans viewer pages too [1.5]
        for thumbpath in glob.glob(os.path.join(imageDir, THUMBS, '*')):
            print('Cleaning: %s' % thumbpath)
            os.remove(thumbpath)

    makeThumbs(imageDir, size=thumbMaxSize, subdir=THUMBS)


#==========================================================================
# GENERATE full index web page in images folder
#==========================================================================


def orderedListing(dirpath, casesensitive=caseSensOrder):
    """
    ----------------------------------------------------------------
    [1.5] A platform- and filesystem-neutral directory listing,
    which is case sensitive by default.  Called out here because 
    order must agree between index and view (navigation) pages.
    Uppercase matters by default: assumed to be more important.

    The os.listdir() order matters only on the build machine, 
    not server (pages built here are static), but varies widely:
    on Mac OS, HFS is case-insensitive, but APFS is nearly random.
    The difference, on APFS ("ls" yields the second of these):

    >>> os.listdir('.')
    ['LA2.png', 'NYC.png', 'la1.png', '2018-x.png', 'nyc-more.png']
    >>> sorted(os.listdir('.'))
    ['2018-x.png', 'LA2.png', 'NYC.png', 'la1.png', 'nyc-more.png']
    >>> sorted(os.listdir('.'), key=str.lower)
    ['2018-x.png', 'la1.png', 'LA2.png', 'nyc-more.png', 'NYC.png']
    ----------------------------------------------------------------
    """
    if casesensitive:
        return sorted(os.listdir(dirpath))
    else:
        return sorted(os.listdir(dirpath), key=str.lower)


def formatImageLinks(imageDir, styleImgThumbs):
    """
    ----------------------------------------------------------------
    Format index-page links text for each thumb.  When a thumb is
    clicked, open either the raw image or a 1.5 view/navigate page.
    ----------------------------------------------------------------
    """
    imglinks = []
    for thumbname in orderedListing(os.path.join(imageDir, THUMBS)):
        if not isImageFileName(thumbname):
            # skip prior viewer-page files if not cleaned [1.5]
            continue    

        if not useViewerPages:
            # click opens raw image in . (1.4)
            target = url_escape(thumbname)
        else:
            # click opens viewer page in thumbs/ [1.5]
            viewpage = thumbname + '.html'
            target = url_escape(THUMBS + '/' + viewpage)

        # index page uses image in thumbs/
        source = url_escape(THUMBS + '/' + thumbname)

        link = ('<A href="%s">\n\t<img src="%s" style="%s"></A>' %
                      (target, source, styleImgThumbs)) 
                  
        imglinks.append((html_escape(thumbname), link))   # use Unix / for web!
    return imglinks


def formatSubfolderLinks(imageDir):
    """
    ----------------------------------------------------------------
    Format index-page links text for any and all subfolders in the 
    images folder.  On link click, open folder or its index.html.
    ----------------------------------------------------------------
    """
    sublinks = []
    for item in orderedListing(imageDir):                 # uniform ordering [1.5]
        if item != THUMBS and not item.startswith('_'):   # skip thumbnails and '_*'
 
            # valid name
            itempath = os.path.join(imageDir, item)       # uses local path here
            if os.path.isdir(itempath):

                # folder: make link
                escsub = html_escape(item)
                target = url_escape(item)
                sublinks.append('<A href="%s">%s</A>' % (target, escsub))
    return sublinks


#
# Content defs: HTML/CSS constants
#

# [1.5] workaround vanishing <hr> bug on desktop Chrome at zoom <= 90% 
styleTableThumbs = """                        /* not used: table-layout:fixed; */
    background-color: %s;                     /* for fun (former 'white') */
    width: 100%%;                             /* expand table+borderlines */
    margin-top: 5px; margin-bottom: 5px;      /* above/below borderlines */
    padding-top: 25px; padding-bottom: 5px;   /* under/over borderlines  */
    border-top: 1px solid black;              /* manual lines, _not_ <hr>s: */ 
    border-bottom: 1px solid black;           /* chrome botches <hr> at zoom <= 90%% */
""" % thumbsBgColor                            # configurable, default=light grey

# [1.5] Chrome botches these at zoom <= 90% too, but keep: still nice 
styleImgThumbs = 'border: 1px solid black;'    # formerly: html border=1

# generate UTF-8 content tag for non-ASCII use cases
doctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'

contype = '<meta http-equiv="content-type" content="text/html; charset=%s">'
contype = contype % outputEncoding

# [1.4]: mobile friendly
viewmeta = '<meta name="viewport" content="width=device-width, initial-scale=1.0">'

# [1.5]: default-header font for index pages (same in viewer pages)
indexfont = '<style>body {font-family: Arial, Helvetica, sans-serif;}</style>'


def generateIndexPage(imageDir):
    """
    ----------------------------------------------------------------
    Build and output the HTML for the thumbnails-index page in the
    images folder, referencing already-built thumbnail images.
    This uses all-inline CSS styling, because custom HEADER.html
    files are not expected to code or link to anything made here.
    This also uses individual prints, because most content varies.
    ----------------------------------------------------------------
    """
    print('Generating thumbnails index page')

    # collect href lists 
    imglinks = formatImageLinks(imageDir, styleImgThumbs)
    sublinks = formatSubfolderLinks(imageDir)

    # don't assume Unicode default (in locale module)
    save_stdout = sys.stdout
    sys.stdout = open(indexPath, 'w', encoding=outputEncoding)

    # header section
    if os.path.exists(headerPath):
        # assume HTML-safe (pre-escaped), explicit Unicode
        insert = open(headerPath, 'r', encoding=insertEncoding)
        print(insert.read())
        print('<!-- end custom header -->'.upper())
    else:
        foldername = os.path.basename(imageDir)
        escfoldername = html_escape(foldername)
        print(doctype)
        print('<html><head>')
        print(contype)
        if useViewPort:
            print(viewmeta)
        print(indexfont) 
        print('<title>Index of %s</title>'
              '\n</head>\n<body>\n'
              '<h1>Index of image folder "%s"</h1>' % ((escfoldername,) * 2))
        print('<!-- end default header -->'.upper())

    # subfolders bullet list (skip other content)
    if sublinks and listSubfolders:
        print('\n<!-- start subfolder links -->'.upper())
        print('<p><b>Subfolders here:</b><div style="margin-bottom: 30px;"><p><ul>')
        for link in sublinks:
              linkstyle = 'style="margin-bottom: 6px;"'    # add space for mobile [1.4]
              print('<li %s>%s' % (linkstyle, link))       # add space below list [1.4]
        print('</div></ul></p>')
          
    # thumb links table 
    print('\n<!-- start thumbs table -->'.upper())
    print('<p>')   # drop <hr>
    print('<div style="overflow-x: auto;">')   # table autoscroll on small screens [1.4]
                                               # whole-window scroll breaks mobile widgets
    # [1.5] styled top/bottom borders, not <hr> 
    print('<table style="%s">' %  styleTableThumbs)

    # thumb links cells
    while imglinks:
        row, imglinks = imglinks[:thumbsPerRow], imglinks[thumbsPerRow:]
        print('<tr>')
        for (escname, link) in row:
              colstyle  = 'style="'
              colstyle += 'padding: 3px; '       # avoid running together [1.4]
              colstyle += 'text-align: center;'  # center img in its cell [1.4]
              if  uniformColumns:
                  colstyle += ' width: %d%%;' % (100 / thumbsPerRow)
              colstyle += '"'
              labstyle = 'style="white-space: nowrap; margin-top: 0px;"'
              print('<td %s>\n\t%s\n\t<p %s>%s</p></td>' % (colstyle, link, labstyle, escname))
        print('</tr>')
        #print('<tr><tr>')   # drop in [1.4]: use css as needed

    print('</table></div></p>')   # drop <hr>

    # footer section
    if os.path.exists(footerPath):
        # assume HTML-safe (pre-escaped), explicit Unicode
        print('\n<!-- start custom footer -->'.upper())
        insert = open(footerPath, 'r', encoding=insertEncoding)
        print(insert.read())
    else:
        print('\n<!-- start default footer -->'.upper())
        print('<p><i>This page was generated by '
              '<A HREF="http://learning-python.com/thumbspage.html">thumbspage.py'
              '</A></i></p>')
        print('</body></html>')

    sys.stdout.close()         # this used to be script exit
    sys.stdout = save_stdout   # there's now more to the show...


#==========================================================================
# GENERATE image-viewer/navigation web pages in thumbs subfolder [1.5]
#==========================================================================

#
# Template: HTML/CSS, uppercase for % dict-key replacement targets, %% = %
#

viewerTemplate = """<!DOCTYPE HTML>
<html><head>
<!-- generated by thumbspage: learning-python.com/thumbspage.html -->

<!-- unicode and mobile friendly -->
<meta http-equiv="content-type" content="text/html; charset=%(ENCODING)s">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<style>     /* css in html expanded and generated by python (!) */
* {
    font-family: Arial, Helvetica, sans-serif; 
    background-color: %(BGCOLOR)s;
    color: white;
}

img {
    display: block;                   /* kill bottom space */
    /*vertical-align: text-bottom;    /* but not quite this... */
}

.navdiv {
    width: 100%%; 
    position: fixed;
    bottom: 0; 
    overflow-x: auto;
}

.navtable {
    width: 100%%;
    border-collapse: collapse;
    table-layout: fixed;
}

.navlink {
    display: block;            /* nicer links through css */
    text-decoration: none;
    text-align: center;
}
.navlink:hover {
    text-decoration: underline; 
    font-style: italic;
}
.navlink:active {   
    font-style: italic;
}

/* add horizontal padding for large fonts in Android Firefox */
.navtable td {
    padding-right: 10px;      /* for all <td> nested in a class=navtable */
}

/* smaller mobile devices: scale up toolbar links and no hover */
@media screen and (max-device-width: 640px) {
.navtable { 
    height: 1.25em;           /* for 0..640 pixel screens only */
}
.navlink {
    font-size: 1.25em;        /* go large, for touch */
}
.navlink:hover {
    text-decoration: none;    /* else underline/italic may get stuck on */
    font-style: normal;
}
}

/* don't upscale text in landscape mode on iOS Safari */
@media screen and (max-device-width: 640px) { 
html {
    -webkit-text-size-adjust: 100%%;     /* webkit browsers (old chrome too) */
}
}
</style>

<title>%(IMAGENAME)s</title>

</head><body>

<!-- filename: possibly long, <h1> too big -->

<div style="overflow-x: auto;">
<P style="text-align: center; margin-top: 4px; margin-bottom: 10px;">
%(IMAGENAME)s
</p></div>

<!-- image: scaled to fit window/viewport (not href: too busy) -->

<div style="border: 1px solid white;">
<p align=center style="margin: 0px;">
<img src="%(IMAGEPATH)s" 
     style="width: %(IMAGEWIDE)s; height: %(IMAGEHIGH)s;">
</p></div>

<!-- buttons: probably too small to overflow viewport, but... -->

<div class=navdiv>
<p>
<table class=navtable>
<tr>
<td><a class=navlink href="%(PREVPAGE)s">Prev</a></td>
<td><a class=navlink href="%(NEXTPAGE)s">Next</a></td>
<td><a class=navlink href="../index.html">Index</a></td>
<td><a class=navlink href="%(IMAGEPATH)s">Raw</a></td>
</tr></table>
</p>             <!-- the /p adds space below links bar (oddly) -->
</div>

</body></html>
"""


def generateViewerPages(imageDir):
    """
    -----------------------------------------------------------------------
    Build and output the HTML for one view/navigate page for each image
    and thumbnail, opened on thumbnail clicks.  Navigation matches the
    filesystem-neutral and case-specific image order on index page.

    Not run if not useViewerPages, in which case there may be some pages 
    from prior uncleaned runs, but we aren't linking to them, or making 
    any now.  If run, always makes new viewer pages (unlike thumbs). 

    This was an afterthought, might be improved with JavaScript for scaling,
    and probably should be customizable in general, but its Prev/Next links 
    already beat browser-native displays.  Assumes that thumbnail filenames
    are the same as fullsize-image filenames in .., and uses a template 
    string (not many prints) because most page content is fixed.

    The template could be read from a file instead, but modifying a string 
    here is easy.  The CSS could also be generated in a separate file, but 
    it's easy to mod this code.  The CSS need not be inlined here, because 
    no parts can be custom files.  Caveat: this script may need PyPhoto's 
    pickle-file scheme if many-photo use cases yield too many thumb files.

    -----------------------------------------------------------------------
    About image scaling: because scaling as implemented here is less than 
    ideal, viewer pages are optional - see "USAGE NOTE" above for tips.
    
    CSS is poor at this: it cannot emulate better browser-native display.
    As is, users may need to resize their desktop windows for optimal 
    viewing, because images may exceed page containers on window resizes.
    This probably requires JavaScript + onresize event callbacks to do 
    better: CSS has no way to adjust as window size and aspect changes.
    
    Dev notes on the assorted scaling attempts sketched below:
    - 'auto' seems busted for width; fractional % usage may vary
    - [max-height: 100%%; max-width: 100%%;] doesn't constrain high
    - [overflow-x/y: auto;] doesn't limit image size, on div or img
    - [object-fit: contain;] attempt didn't work on chrome (why?)
    - the math scales side1 to N%, and side2 to N% of its ratio to side1

    Failed attempt: portrait not scaled down, landscape no diff
    if imgwide >= imghigh:
        IMAGEWIDE, IMAGEHIGH = '100%', 'auto'
    else:
        IMAGEHIGH, IMAGEWIDE = '100%', 'auto'
 
    Failed attempt: portrait too tall, landscape no diff
    ratio = min(imgwide / imghigh, imghigh / imgwide)
    if imgwide >= imghigh:
        IMAGEWIDE, IMAGEHIGH = '100%', '%f%%' % (100 * (imghigh / imgwide))
    else:
        IMAGEHIGH, IMAGEWIDE = '100%', '%f%%' % (100 * (imgwide / imghigh))
    -----------------------------------------------------------------------
    """

    allthumbs = orderedListing(os.path.join(imageDir, THUMBS))
    allthumbs = [thumb for thumb in allthumbs if isImageFileName(thumb)]
    thumb1, thumbN = 0, len(allthumbs) - 1

    for ix in range(len(allthumbs)):
        thumbname = allthumbs[ix]
        imgwide, imghigh = imageWideHigh(imageDir, thumbname)

        # these work well on mobile, and on desktop if window sized
        if imgwide > imghigh:
            IMAGEWIDE, IMAGEHIGH = '100%', 'auto'    # landscape
        else:
            IMAGEHIGH, IMAGEWIDE = '80%',  '%f%%' % (80 * (imgwide / imghigh))
    
        vals = dict(
            ENCODING = outputEncoding,
            BGCOLOR  = viewerBgColor,

            # relative to thumbs/, always unix "/" on web
            IMAGENAME = html_escape(thumbname),
            IMAGEPATH = url_escape('../' + thumbname),  

            # nav links: pages in '.', wrap around at end/start 
            PREVPAGE = url_escape('%s.html' % allthumbs[(ix-1) if ix > thumb1 else -1]),
            NEXTPAGE = url_escape('%s.html' % allthumbs[(ix+1) if ix < thumbN else  0]),

            # scale larger dimension to viewport (see calcs and notes above)
            IMAGEWIDE = IMAGEWIDE,
            IMAGEHIGH = IMAGEHIGH
            )

        print('Generating view page for: %s' % thumbname)
        viewerpath = os.path.join(imageDir, THUMBS, thumbname + '.html')
        viewerfile = open(viewerpath, mode='w', encoding=outputEncoding)
        viewerfile.write(viewerTemplate % vals)
        viewerfile.close()


#==========================================================================
# MAIN LOGIC: kick off the functions above (no longer top-level code [1.5])
#==========================================================================

if __name__ == '__main__':
    print('Running')

    makeThumbnails(imageDir)
    generateIndexPage(imageDir)
    if useViewerPages:
        generateViewerPages(imageDir)

    print('Finished: see results in images folder.')
    # and view index.html in images folder, zip/upload images folder to site



[Home page] Books Code Blog Python Author Train Find ©M.Lutz