diff --git a/vasl_templates/webapp/data/default-template-pack/atmm.j2 b/vasl_templates/webapp/data/default-template-pack/atmm.j2
index a800135..7cc3295 100644
--- a/vasl_templates/webapp/data/default-template-pack/atmm.j2
+++ b/vasl_templates/webapp/data/default-template-pack/atmm.j2
@@ -16,7 +16,7 @@ td { margin: 0 ; padding: 0 ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}Anti-Tank Magnetic Mines
+ {%if PLAYER_FLAG%} {%endif%}Anti-Tank Magnetic Mines
diff --git a/vasl_templates/webapp/data/default-template-pack/baz.j2 b/vasl_templates/webapp/data/default-template-pack/baz.j2
index 1b11cc1..5192a36 100644
--- a/vasl_templates/webapp/data/default-template-pack/baz.j2
+++ b/vasl_templates/webapp/data/default-template-pack/baz.j2
@@ -18,7 +18,7 @@ td.r { text-align: right ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}Bazooka {%if BAZ_TYPE%} ('{{BAZ_TYPE}}) {%endif%}
+ {%if PLAYER_FLAG%} {%endif%}Bazooka {%if BAZ_TYPE%} ('{{BAZ_TYPE}}) {%endif%}
|
diff --git a/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2 b/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2
index c22878d..5069b54 100644
--- a/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2
+++ b/vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2
@@ -14,7 +14,7 @@ td { margin: 0 ; padding: 0 ; }
- {%if PLAYER_FLAGS["german"]%} {%endif%}Grenade Bundles
+ {%if PLAYER_FLAGS["german"]%} {%endif%}Grenade Bundles
|
diff --git a/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2 b/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2
index 1835c33..7817327 100644
--- a/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2
+++ b/vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2
@@ -15,7 +15,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; }
|
- {%if PLAYER_FLAGS["german"]%} {%endif%}Molotov Cocktails
+ {%if PLAYER_FLAGS["german"]%} {%endif%}Molotov Cocktails
|
diff --git a/vasl_templates/webapp/data/default-template-pack/mol-p.j2 b/vasl_templates/webapp/data/default-template-pack/mol-p.j2
index b6ba16e..053f103 100644
--- a/vasl_templates/webapp/data/default-template-pack/mol-p.j2
+++ b/vasl_templates/webapp/data/default-template-pack/mol-p.j2
@@ -19,7 +19,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}MOL Projector
+ {%if PLAYER_FLAG%} {%endif%}MOL Projector
|
diff --git a/vasl_templates/webapp/data/default-template-pack/mol.j2 b/vasl_templates/webapp/data/default-template-pack/mol.j2
index 09cbb18..be5bdee 100644
--- a/vasl_templates/webapp/data/default-template-pack/mol.j2
+++ b/vasl_templates/webapp/data/default-template-pack/mol.j2
@@ -17,7 +17,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}Molotov Cocktail
+ {%if PLAYER_FLAG%} {%endif%}Molotov Cocktail
diff --git a/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2 b/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2
index 52052a8..7418a2f 100644
--- a/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2
+++ b/vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2
@@ -20,7 +20,7 @@ sup { font-size: 75% ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Ordnance
+ {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Ordnance
{%for ord in OB_ORDNANCE%}
|
diff --git a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2 b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2
index 2024f61..77f675b 100644
--- a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2
+++ b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2
@@ -21,7 +21,7 @@ sup { font-size: 75% ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Ordnance Notes
+ {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Ordnance Notes
{%if OB_ORDNANCE_MA_NOTES%}
diff --git a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2 b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2
index f87db92..28a2bd8 100644
--- a/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2
+++ b/vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2
@@ -13,7 +13,7 @@
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}{{ORDNANCE_NAME}}
+ {%if PLAYER_FLAG%} {%endif%}{{ORDNANCE_NAME}}
|
diff --git a/vasl_templates/webapp/data/default-template-pack/ob_setup.j2 b/vasl_templates/webapp/data/default-template-pack/ob_setup.j2
index 6cb9242..976291f 100644
--- a/vasl_templates/webapp/data/default-template-pack/ob_setup.j2
+++ b/vasl_templates/webapp/data/default-template-pack/ob_setup.j2
@@ -14,7 +14,7 @@
font-weight: bold ;
{%if OB_SETUP_WIDTH%} width: {{OB_SETUP_WIDTH}} ; {%endif%}
">
- {%if PLAYER_FLAG%} {%endif%}{{OB_SETUP}}
+ {%if PLAYER_FLAG%} {%endif%}{{OB_SETUP}}
diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2
index 85eb718..26eeed3 100644
--- a/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2
+++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2
@@ -13,7 +13,7 @@
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}{{VEHICLE_NAME}}
+ {%if PLAYER_FLAG%} {%endif%}{{VEHICLE_NAME}}
|
diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2
index 88f32a3..f53d4df 100644
--- a/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2
+++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2
@@ -20,7 +20,7 @@ sup { font-size: 75% ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Vehicles
+ {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Vehicles
{%for veh in OB_VEHICLES%}
|
diff --git a/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2 b/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2
index 724dfca..d993fb6 100644
--- a/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2
+++ b/vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2
@@ -21,7 +21,7 @@ sup { font-size: 75% ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Vehicle Notes
+ {%if PLAYER_FLAG%} {%endif%}{{PLAYER_NAME}} Vehicle Notes
{%if OB_VEHICLES_MA_NOTES%}
diff --git a/vasl_templates/webapp/data/default-template-pack/pf.j2 b/vasl_templates/webapp/data/default-template-pack/pf.j2
index d59f446..f0c2262 100644
--- a/vasl_templates/webapp/data/default-template-pack/pf.j2
+++ b/vasl_templates/webapp/data/default-template-pack/pf.j2
@@ -18,7 +18,7 @@ td.r { text-align: right ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}Panzerfaust
+ {%if PLAYER_FLAG%} {%endif%}Panzerfaust
|
diff --git a/vasl_templates/webapp/data/default-template-pack/piat.j2 b/vasl_templates/webapp/data/default-template-pack/piat.j2
index 5a12515..02b9aa0 100644
--- a/vasl_templates/webapp/data/default-template-pack/piat.j2
+++ b/vasl_templates/webapp/data/default-template-pack/piat.j2
@@ -18,7 +18,7 @@ td.r { text-align: right ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}PIAT
+ {%if PLAYER_FLAG%} {%endif%}PIAT
diff --git a/vasl_templates/webapp/data/default-template-pack/players.j2 b/vasl_templates/webapp/data/default-template-pack/players.j2
index 879dab5..1539db5 100644
--- a/vasl_templates/webapp/data/default-template-pack/players.j2
+++ b/vasl_templates/webapp/data/default-template-pack/players.j2
@@ -10,8 +10,8 @@
">
- {%if PLAYER_FLAG_1%} {%endif%}{{PLAYER_1_NAME}}:
- {%if PLAYER_FLAG_2%} {%endif%}{{PLAYER_2_NAME}}:
+ {%if PLAYER_FLAG_1%} {%endif%}{{PLAYER_1_NAME}}:
+ {%if PLAYER_FLAG_2%} {%endif%}{{PLAYER_2_NAME}}:
|
ELR: {{PLAYER_1_ELR}}
ELR: {{PLAYER_2_ELR}}
diff --git a/vasl_templates/webapp/data/default-template-pack/psk.j2 b/vasl_templates/webapp/data/default-template-pack/psk.j2
index 398eb50..a2fc7f8 100644
--- a/vasl_templates/webapp/data/default-template-pack/psk.j2
+++ b/vasl_templates/webapp/data/default-template-pack/psk.j2
@@ -18,7 +18,7 @@ td.r { text-align: right ; }
padding: 2px 5px ;
font-weight: bold ;
">
- {%if PLAYER_FLAG%} {%endif%}Panzerschrek
+ {%if PLAYER_FLAG%} {%endif%}Panzerschrek
|
diff --git a/vasl_templates/webapp/files.py b/vasl_templates/webapp/files.py
index 07e2a76..4bff877 100644
--- a/vasl_templates/webapp/files.py
+++ b/vasl_templates/webapp/files.py
@@ -10,6 +10,7 @@ from flask import send_file, send_from_directory, jsonify, redirect, url_for, ab
from vasl_templates.webapp import app
from vasl_templates.webapp.file_server.vasl_mod import get_vasl_mod
+from vasl_templates.webapp.utils import resize_image_response
# ---------------------------------------------------------------------
@@ -24,6 +25,9 @@ class FileServer:
def serve_file( self, path ):
"""Serve a file."""
+ # NOTE: We return a Flask Response object, instead of the file data, so that (1) we can use
+ # send_from_directory() and (2) if we have to download a file from a URL, we can include
+ # the MIME type in what we return.
if FileServer.is_remote_path( self.base_dir ):
url = "{}/{}".format( self.base_dir, path )
# NOTE: We download the target file and serve it ourself (instead of just redirecting)
@@ -55,7 +59,8 @@ def get_user_file( path ):
dname = app.config.get( "USER_FILES_DIR" )
if not dname:
abort( 404 )
- return FileServer( dname ).serve_file( path )
+ resp = FileServer( dname ).serve_file( path )
+ return resize_image_response( resp )
# ---------------------------------------------------------------------
diff --git a/vasl_templates/webapp/snippets.py b/vasl_templates/webapp/snippets.py
index e98bdfb..bde5a5b 100644
--- a/vasl_templates/webapp/snippets.py
+++ b/vasl_templates/webapp/snippets.py
@@ -6,6 +6,7 @@ import re
import zipfile
import io
import base64
+import threading
from flask import request, jsonify, send_file, abort
from PIL import Image
@@ -137,6 +138,9 @@ def make_snippet_image():
# ---------------------------------------------------------------------
+_flag_image_cache = {}
+_flag_image_cache_lock = threading.Lock()
+
@app.route( "/flags/" )
def get_flag( nat ):
"""Get a flag image."""
@@ -145,25 +149,36 @@ def get_flag( nat ):
if not re.search( "^[-a-z]+$", nat ):
abort( 404 )
- fname = "static/images/flags/{}.png".format( nat )
- with app.open_resource( fname, "rb" ) as fp:
-
- # load the image
- img = Image.open( fp )
-
- # check if we should resize the image
- # NOTE: Resizing images in the HTML snippets looks dreadful (presumably
- # because VASSAL's HTML engine is so ancient), so we do it ourself :-/
- height = request.args.get( "height" )
- if height:
- height = int( height )
- if height > 0:
- width = img.size[0] / ( float(img.size[1]) / height )
- width = int( width + 0.5 )
- img = img.resize( (width,height), Image.ANTIALIAS )
-
- # return the image
- buf = io.BytesIO()
- img.save( buf, format="PNG" )
- buf.seek( 0 )
- return send_file( buf, mimetype="image/png" )
+ # check how we should resize the image
+ # NOTE: Resizing images in the HTML snippets looks dreadful (presumably
+ # because VASSAL's HTML engine is so ancient), so we do it ourself :-/
+ default_height = app.config.get( "DEFAULT_FLAG_HEIGHT", 11 )
+ height = int( request.args.get( "height", default_height ) )
+ if height <= 0:
+ abort( 400 )
+
+ with _flag_image_cache_lock:
+
+ # check if we have the image in the cache
+ cache_key = ( nat, height )
+ if cache_key not in _flag_image_cache:
+
+ # nope - load it
+ fname = "static/images/flags/{}.png".format( nat )
+ with app.open_resource( fname, "rb" ) as fp:
+ img = Image.open( fp )
+ # resize the image
+ height = int( height )
+ if height > 0:
+ width = img.size[0] / ( float(img.size[1]) / height )
+ width = int( width + 0.5 )
+ img = img.resize( (width,height), Image.ANTIALIAS )
+ # add the image to the cache
+ buf = io.BytesIO()
+ img.save( buf, format="PNG" )
+ buf.seek( 0 )
+ _flag_image_cache[ cache_key ] = buf.read()
+
+ # return the flag image
+ img_data =_flag_image_cache[ cache_key ]
+ return send_file( io.BytesIO(img_data), mimetype="image/png" )
diff --git a/vasl_templates/webapp/utils.py b/vasl_templates/webapp/utils.py
index 2af3e7f..6887631 100644
--- a/vasl_templates/webapp/utils.py
+++ b/vasl_templates/webapp/utils.py
@@ -1,10 +1,14 @@
""" Miscellaneous utilities. """
import os
+import io
import tempfile
import pathlib
from collections import defaultdict
+from flask import request, Response, send_file
+from PIL import Image
+
# ---------------------------------------------------------------------
class MsgStore:
@@ -75,6 +79,60 @@ class TempFile:
# ---------------------------------------------------------------------
+def resize_image_response( resp, default_width=None, default_height=None, default_scaling=None ):
+ """Resize an image that will be returned as a Flask response."""
+
+ assert isinstance( resp, Response )
+
+ def get_image():
+ """Get the the image from the Flask response that was passed in."""
+ resp.direct_passthrough = False
+ buf = io.BytesIO()
+ buf.write( resp.get_data() )
+ buf.seek( 0 )
+ return Image.open( buf )
+
+ # check if the caller specified a width and/or height
+ width = request.args.get( "width", default_width )
+ height = request.args.get( "height", default_height )
+ if width and height:
+ # width and height were specified, just use them as-is
+ img = get_image()
+ width = int( width )
+ height = int( height )
+ elif width and not height:
+ # width only, calculate the height
+ img = get_image()
+ aspect_ratio = float(img.size[0]) / float(img.size[1])
+ height = int(width) / aspect_ratio
+ elif not width and height:
+ # height only, calculate the width
+ img = get_image()
+ aspect_ratio = float(img.size[0]) / float(img.size[1])
+ width = int(height) * aspect_ratio
+ elif not width and not height:
+ # check if the caller specified a scaling factor
+ scaling = request.args.get( "scaling", default_scaling )
+ if scaling and scaling != 100:
+ img = get_image()
+ width = img.size[0] * float(scaling)/100
+ height = img.size[1] * float(scaling)/100
+
+ # check if we need to resize the image
+ if width or height:
+ assert width and height
+ # yup - make it so
+ img = img.resize( (int(width),int(height)), Image.ANTIALIAS )
+ buf = io.BytesIO()
+ img.save( buf, format="PNG" )
+ buf.seek( 0 )
+ return send_file( buf, mimetype="image/png" )
+ else:
+ # nope - return the image as-is
+ return resp
+
+# ---------------------------------------------------------------------
+
def change_extn( fname, extn ):
"""Change a filename's extension."""
return pathlib.Path( fname ).with_suffix( extn )
diff --git a/vasl_templates/webapp/vo_notes.py b/vasl_templates/webapp/vo_notes.py
index d300fbc..4b4a894 100644
--- a/vasl_templates/webapp/vo_notes.py
+++ b/vasl_templates/webapp/vo_notes.py
@@ -3,16 +3,14 @@
import os
import threading
-import io
import logging
from collections import defaultdict
-from flask import request, render_template, send_file, jsonify, abort
-from PIL import Image
+from flask import render_template, jsonify, abort
from vasl_templates.webapp import app
from vasl_templates.webapp.files import FileServer
-from vasl_templates.webapp.utils import is_image_file
+from vasl_templates.webapp.utils import is_image_file, resize_image_response
_vo_notes_lock = threading.RLock() # nb: this controls the cached V/O notes and the FileServer
_cached_vo_notes = None
@@ -127,27 +125,8 @@ def get_vo_note( vo_type, nat, key ):
fname = vo_notes.get( nat, {} ).get( key )
resp = _vo_notes_file_server.serve_file( fname )
- # check if we should resize the file
- scaling = request.args.get( "scaling" ) # nb: allow individual notes to set their scaling
- if not scaling:
- scaling = app.config.get( "CHAPTER_H_NOTE_SCALING", 100 )
- if scaling == 100:
- # nope - just return the file as it is
- return resp
- else:
- # yup - make it so
- buf = io.BytesIO()
- resp.direct_passthrough = False
- buf.write( resp.get_data() )
- buf.seek( 0 )
- img = Image.open( buf )
- width = int( img.size[0] * float(scaling) / 100 )
- height = int( img.size[1] * float(scaling) / 100 )
- img = img.resize( (width,height), Image.ANTIALIAS )
- buf = io.BytesIO()
- img.save( buf, format="PNG" )
- buf.seek( 0 )
- return send_file( buf, mimetype="image/png" )
+ default_scaling = app.config.get( "CHAPTER_H_NOTE_SCALING", 100 )
+ return resize_image_response( resp, default_scaling=default_scaling )
# ---------------------------------------------------------------------