summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/pagelayout/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/pagelayout/scripts')
-rwxr-xr-xmacros/latex/contrib/pagelayout/scripts/pagelayoutapi371
-rw-r--r--macros/latex/contrib/pagelayout/scripts/pagelayoutapi.1125
-rw-r--r--macros/latex/contrib/pagelayout/scripts/pagelayoutapi.1.md87
-rwxr-xr-xmacros/latex/contrib/pagelayout/scripts/textestvis506
-rw-r--r--macros/latex/contrib/pagelayout/scripts/textestvis.173
-rw-r--r--macros/latex/contrib/pagelayout/scripts/textestvis.1.md55
6 files changed, 1217 insertions, 0 deletions
diff --git a/macros/latex/contrib/pagelayout/scripts/pagelayoutapi b/macros/latex/contrib/pagelayout/scripts/pagelayoutapi
new file mode 100755
index 0000000000..295beef2f4
--- /dev/null
+++ b/macros/latex/contrib/pagelayout/scripts/pagelayoutapi
@@ -0,0 +1,371 @@
+#!/bin/sh
+#
+# Copyright (c) 2022-2023 by Friedemann Bartels
+#
+# This file may be distributed and/or modified under the
+# conditions of the LaTeX Project Public License, either
+# version 1.3c of this license or (at your option) any later
+# version. The latest version of this license is in:
+#
+# http://www.latex-project.org/lppl.txt
+#
+# and version 1.3c or later is part of all distributions of
+# LaTeX version 2008/05/04 or later.
+#
+
+version=1.0.3
+
+_convert() {
+ turbo=$1
+ filename=$2
+ filenamecache=$3
+ fileextcache=$4
+ originalwidthsp=$5
+ originalheightsp=$6
+ originalcropleftsp=$7
+ originalcroprightsp=$8
+ originalcroptopsp=$9
+ originalcropbottomsp=${10}
+ density=${11}
+ displaywidth=${12}
+ displayheight=${13}
+ resizethreshold=${14}
+ unsharp=${15}
+ quality="${16}"
+
+ mkdir -p cache/"$filenamecache"
+ originalwidth=$( magick identify -ping -format %w "$filename" )
+ originalheight=$( magick identify -ping -format %h "$filename" )
+ width=$(( (10 * originalwidth * (originalwidthsp - originalcropleftsp - originalcroprightsp) / originalwidthsp + 5) / 10 ))
+ height=$(( (10 * originalheight * (originalheightsp - originalcroptopsp - originalcropbottomsp) / originalheightsp + 5) / 10 ))
+ cropleft=$(( (10 * originalwidth * originalcropleftsp / originalwidthsp + 5) / 10 ))
+ croptop=$(( (10 * originalheight * originalcroptopsp / originalheightsp + 5) / 10 ))
+ resizewidth=$(( (10 * displaywidth * 100 * density / 473628672 + 5) / 10 ))
+ if [ $(( resizewidth * resizethreshold / 100 )) -gt $width ]; then
+ resizewidth=$width
+ resizeheight=$height
+ else
+ resizeheight=$(( resizewidth * height / width + 1 ))
+ fi
+
+ x="x"
+ if [ $turbo = 1 ]; then
+ export MAGICK_THREAD_LIMIT=1
+ fi
+ if [ "$quality" != "" ]; then
+ quality="-quality $quality"
+ fi
+ if [ "$unsharp" != "" ]; then
+ unsharp="-unsharp $unsharp"
+ fi
+
+ magick "$filename" -crop $width$x$height+$cropleft+$croptop -resize $resizewidth$x$resizeheight $unsharp $quality "cache/$filenamecache/.g$fileextcache"
+ mv "cache/$filenamecache/.g$fileextcache" "cache/$filenamecache/g$fileextcache"
+}
+
+_startbatchprocess() {
+ success=0
+ for file in cache/*; do
+ if [ -f "$file" ]; then
+ success=1
+ name=$( basename "${file}" )
+ mv "$file" "cache/.$name" 2> /dev/null
+ if [ $? -eq 0 ]; then
+ line=$(head -n 1 "cache/.$name")
+
+ IFS="%"
+ set -- $line
+ IFS=" "
+ _convert 1 "$@"
+
+ rm "cache/.$name"
+ break
+ fi
+ fi
+ done
+
+ if [ $success -eq 1 ]; then
+ _startbatchprocess
+ fi
+}
+
+getwidth() {
+ if command -v magick >/dev/null 2>&1; then
+ filename=$1
+
+ originalwidth=$( magick identify -ping -format %w "$filename" )
+ echo $originalwidth
+ else
+ echo errormagicknotinstalled
+ fi
+}
+
+optimize() {
+ if command -v magick >/dev/null 2>&1; then
+ filenamecache=$2
+ fileextcache=$3
+
+ if [ -f cache/"$filenamecache" ]; then
+ mv cache/"$filenamecache" cache/."$filenamecache" 2> /dev/null
+ if [ $? -eq 0 ]; then
+ line=$(head -n 1 "cache/.$filenamecache")
+
+ IFS="%"
+ set -- $line
+ IFS=" "
+ _convert 1 "$@"
+
+ rm "cache/.$filenamecache"
+ else
+ while [ ! -f cache/"$filenamecache"/g"$fileextcache" ]; do
+ sleep 0.05
+ done
+ fi
+ elif [ -d cache/"$filenamecache" ]; then
+ while [ ! -f cache/"$filenamecache"/g"$fileextcache" ]; do
+ sleep 0.05
+ done
+ else
+ _convert 0 "$@"
+ fi
+
+ echo ok
+ else
+ echo errormagicknotinstalled
+ fi
+}
+
+makeshadow() {
+ if command -v magick >/dev/null 2>&1; then
+ filename=$1
+ stdDeviation=$2
+ opacity=$3
+ fill=$4
+ width=$5
+ height=$6
+ framewidth=$7
+ frameheight=$8
+ frameborder=$9
+ frameradius=${10}
+
+ [ ! -d cache ] && mkdir cache
+ mkdir -p cache/$filename
+ echo "<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+ <svg
+ width='$width'
+ height='$height'
+ version='1.1'
+ xmlns='http://www.w3.org/2000/svg'
+ xmlns:svg='http://www.w3.org/2000/svg'>
+ <defs>
+ <filter
+ style='color-interpolation-filters:sRGB'
+ id='blur'
+ x='-4'
+ y='-4'
+ width='8'
+ height='8'>
+ <feGaussianBlur
+ stdDeviation='$stdDeviation' />
+ </filter>
+ </defs>
+ <rect
+ style='filter:url(#blur);opacity:$opacity;fill:$fill'
+ width='$framewidth'
+ height='$frameheight'
+ x='$frameborder'
+ y='$frameborder'
+ rx='$frameradius'
+ ry='$frameradius' />
+ </svg>
+ " > cache/$filename/s.svg
+ inkscape cache/$filename/s.svg --export-dpi=600 --export-filename cache/$filename/s.pdf
+ rm cache/$filename/s.svg
+
+ echo ok
+ else
+ echo errorinkscapenotinstalled
+ fi
+}
+
+import() {
+ if [ -d import ]; then
+ importdir=import
+ elif [ "$PAGELAYOUT_IMPORT_DIRECTORY" != "" ]; then
+ if [ -d "$PAGELAYOUT_IMPORT_DIRECTORY" ]; then
+ importdir="$PAGELAYOUT_IMPORT_DIRECTORY"
+ else
+ echo $PAGELAYOUT_IMPORT_DIRECTORY
+ exit
+ fi
+ else
+ echo errornoimportdir
+ exit
+ fi
+
+ defaultdirectory=$( echo $1 | sed -e 's/^{//g' | sed -e 's/}{.*//g' )
+ directorylist=$( echo $1 | sed -e 's/^{//g' | sed -e 's/}$//g' | sed -e 's/}{/$/g' )
+
+ for entry in "$importdir"/*.*
+ do
+ if [ -f "$entry" ]; then
+ name=$( basename "${entry}" )
+ if [ "$directorylist" != "" ]; then
+ echo "$directorylist" | tr '$' '\n' | while read directory; do
+ if [ -f "$directory$name" ]; then
+ mv "$entry" "$directory"
+ fi
+ done
+ fi
+ if [ -f "$entry" ]; then
+ if [ -d "$defaultdirectory" ]; then
+ mv "$entry" "$defaultdirectory"
+ else
+ mv "$entry" .
+ fi
+ fi
+ cachename=$( echo "$name" | sed -e 's/\.[a-zA-Z]*$//g' )
+ rm -rf cache/"$cachename"*
+ fi
+ done
+}
+
+startturbo() {
+ file="$(echo $1 | sed -e 's/\.pdflatex$//g')"
+ if [ -f $file.tex ]; then
+ tmpdir=$( mktemp -d 2>/dev/null )/pagelayoutturbo$( date "+%Y%m%d%H%M%S" )$RANDOM
+ mkdir $tmpdir
+
+ cp $file.tex $tmpdir/pagelayoutturbobatchoptimizexyz.tex
+
+ command=$(echo $(ps -p $PPID -o command ))
+ engine=$(echo $command | sed -e 's/^[A-Z ]*//g' | sed -e 's/ .*//g' | tr -d '\n')
+
+ case "$command" in
+ *-shell-escape*)
+ shellescape="--shell-escape"
+ ;;
+ esac
+
+ if [ "$engine" = "xelatex" ]; then
+ nopdf="-no-pdf"
+ fi
+
+ $engine $shellescape -interaction=batchmode $nopdf --output-directory $tmpdir $tmpdir/pagelayoutturbobatchoptimizexyz.tex > /dev/null
+
+ rm -rf $tmpdir
+ fi
+}
+
+batchoptimize() {
+ [ ! -d cache ] && mkdir cache
+
+ length=$(( $# / 15 ))
+ cores=$( nproc )
+ [ $cores -gt $length ] && cores=$length
+
+ while [ $length -gt 0 ]; do
+ filenamecache="$2"
+
+ if [ ! -e cache/"$filenamecache" ]; then
+ index=0
+ while [ $index -lt 15 ]; do
+ if [ $index -eq 0 ]; then
+ item="$1"
+ else
+ item="$item%$1"
+ fi
+
+ shift
+ index=$(( index + 1 ))
+ done
+
+ echo $item > cache/"$filenamecache"
+ else
+ shift 15
+ fi
+
+ length=$(( length - 1 ))
+ done
+
+ cores=$(( cores - 1 ))
+ while [ $cores -gt 0 ]; do
+ _startbatchprocess &
+ sleep 0.01
+ cores=$(( cores - 1 ))
+ done
+}
+
+usage() {
+ cat <<HELP_USAGE
+usage: pagelayoutapi getwidth % FILE
+ pagelayoutapi optimize % FILE % FILENAME % EXTENSION % ORIGINALWIDTH
+ % ORIGINALHEIGHT % CROPLEFT % CROPRIGHT % CROPTOP % CROPBOTTOM
+ % DENSITY % WIDTH % HEIGHT % DOWNSAMPLETHRESHOLD % UNSHARP % QUALITY
+ pagelayoutapi makeshadow % FILENAME % STANDARDDEVIATION % OPACITY % COLOR
+ % WIDTH % HEIGHT % FRAMEWIDTH % FRAMEHEIGHT % MARGIN % BORDERRADIUS
+ pagelayoutapi start [% import % [GRAPHICSPATH]] [% turbo % JOBNAME]
+ pagelayoutapi batchoptimize % BATCHLIST
+
+ --help Prints a help message.
+ --version Prints version information.
+HELP_USAGE
+}
+
+version() {
+ echo pagelayoutapi $version
+}
+
+input=$( echo "$@" | sed -e 's/\\//g' | sed -e 's/ %/%/g' | sed -e 's/% /%/g' )
+IFS="%"
+set -- $input
+IFS=" "
+
+case "$1" in
+getwidth)
+ shift
+
+ getwidth "$1"
+ ;;
+optimize)
+ shift
+
+ optimize "$@"
+ ;;
+makeshadow)
+ shift
+
+ makeshadow "$@"
+ ;;
+start)
+ shift
+
+ if [ "$1" = "import" ]; then
+ shift
+
+ import "$1"
+
+ shift
+ fi
+
+ if [ "$1" = "turbo" ]; then
+ shift
+
+ startturbo "$1" &
+ fi
+
+ echo ok
+ ;;
+batchoptimize)
+ shift
+
+ batchoptimize "$@" &
+
+ echo ok
+ ;;
+--version)
+ version
+ ;;
+--help)
+ usage
+esac
diff --git a/macros/latex/contrib/pagelayout/scripts/pagelayoutapi.1 b/macros/latex/contrib/pagelayout/scripts/pagelayoutapi.1
new file mode 100644
index 0000000000..368e411ef7
--- /dev/null
+++ b/macros/latex/contrib/pagelayout/scripts/pagelayoutapi.1
@@ -0,0 +1,125 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "PAGELAYOUTAPI" "1" "January 2023" "" ""
+.
+.SH "NAME"
+\fBpagelayoutapi\fR \- API for the pagelayout LaTeX class
+.
+.SH "SYNOPSIS"
+\fBpagelayoutapi getwidth\fR % FILE
+.
+.P
+\fBpagelayoutapi optimize\fR % FILE % FILENAME % EXTENSION % ORIGINALWIDTH % ORIGINALHEIGHT % CROPLEFT % CROPRIGHT % CROPTOP % CROPBOTTOM % DENSITY % WIDTH % HEIGHT % DOWNSAMPLETHRESHOLD % UNSHARP % QUALITY
+.
+.P
+\fBpagelayoutapi makeshadow\fR % FILENAME % STANDARDDEVIATION % OPACITY % COLOR % WIDTH % HEIGHT % FRAMEWIDTH % FRAMEHEIGHT % MARGIN % BORDERRADIUS
+.
+.P
+\fBpagelayoutapi start\fR [% import % [GRAPHICSPATH]] [% turbo % JOBNAME]
+.
+.P
+\fBpagelayoutapi batchoptimize\fR % BATCHLIST
+.
+.SH "DESCRIPTION"
+This script provides an API for the pagelayout LaTeX class\.
+.
+.TP
+\fBpagelayoutapi getwidth\fR
+Returns the width in pixels for the given image file\.
+.
+.TP
+\fBpagelayoutapi optimize\fR
+Creates a cropped, rezised, sharpend and compressed image and stores it in the cache directory\.
+.
+.TP
+\fBpagelayoutapi makeshadow\fR
+Creates a shadow image and stores it in the cache directory\.
+.
+.TP
+\fBpagelayoutapi start\fR
+The import parameter triggers the image import (see pagelayout\-manual\.pdf chapter "Image Optimization")\. Running the command with the parameter turbo, where the jobname is the filename of the document, processes the document in a special batch mode, that creates a batch list and calls the command \fBpagelayoutapi batchoptimize\fR\.
+.
+.TP
+\fBpagelayoutapi batchoptimize\fR
+Optimizes multiple images in parallel\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-help\fR
+Prints a help message\.
+.
+.TP
+\fB\-\-version\fR
+Prints version information\.
+.
+.SH "PARAMETERS"
+.
+.TP
+FILE
+filename with extension (eg\. IMG1234\.JPEG)
+.
+.TP
+FILENAME
+filename without extension (eg\. IMG1234)
+.
+.TP
+EXTENSION
+optimized file extension (\.jpg|\.png)
+.
+.TP
+DENSITY
+density in ppi (eg\. 72)
+.
+.TP
+DOWNSAMPLETHRESHOLD
+downsample threshold (integer >= 100)
+.
+.TP
+UNSHARP
+unsharp filter (eg\. 2x1)
+.
+.TP
+QUALITY
+quality (integer > 0, <= 100)
+.
+.TP
+STANDARDDEVIATION
+standard deviation (decimal > 0\.0)
+.
+.TP
+OPACITY
+opacity (decimal >= 0\.0, <= 1\.0)
+.
+.TP
+COLOR
+color string (eg\. pink)
+.
+.TP
+GRAPHICSPATH
+list of directories (eg\. {images/}{tmp/})
+.
+.TP
+JOBNAME
+LaTeX filename without extension (eg\. my\-document)
+.
+.TP
+BATCHLIST
+a flat list of batch items where each batch item is a flat list of the 15 parameters required by the command `pagelayoutapi optimize`
+.
+.P
+All other parameters are length dimensions\. The command \fBpagelayoutapi optimize\fR expects integer values in the LaTeX unit sp (eg\. 65536)\. The command \fBpagelayoutapi makeshadow\fR expects decimal values in a SVG compatible unit (eg\. 420\.0pt)\.
+.
+.SH "ENVIRONMENT"
+\fBpagelayoutapi\fR requires ImageMagick 7\.0 or later and Inkscape 1\.0 or later\.
+.
+.TP
+\fBPAGELAYOUT_IMPORT_DIRECTORY\fR
+Defines a system wide import directory\.
+.
+.SH "AUTHOR"
+\fBpagelayoutapi\fR was written by Friedemann Bartels\. \fIhttps://github\.com/friedemannbartels\fR
+.
+.SH "LICENSE"
+Copyright (c) 2022-2023 Friedemann Bartels\. Free use of this software is granted under the terms of the LaTeX Project Public License version 1\.3c or later\.
diff --git a/macros/latex/contrib/pagelayout/scripts/pagelayoutapi.1.md b/macros/latex/contrib/pagelayout/scripts/pagelayoutapi.1.md
new file mode 100644
index 0000000000..ab04ddd0b8
--- /dev/null
+++ b/macros/latex/contrib/pagelayout/scripts/pagelayoutapi.1.md
@@ -0,0 +1,87 @@
+pagelayoutapi(1) -- API for the pagelayout LaTeX class
+====
+
+## SYNOPSIS
+
+`pagelayoutapi getwidth` % FILE
+
+`pagelayoutapi optimize` % FILE % FILENAME % EXTENSION % ORIGINALWIDTH % ORIGINALHEIGHT % CROPLEFT % CROPRIGHT % CROPTOP % CROPBOTTOM % DENSITY % WIDTH % HEIGHT % DOWNSAMPLETHRESHOLD % UNSHARP % QUALITY
+
+`pagelayoutapi makeshadow` % FILENAME % STANDARDDEVIATION % OPACITY % COLOR % WIDTH % HEIGHT % FRAMEWIDTH % FRAMEHEIGHT % MARGIN % BORDERRADIUS
+
+`pagelayoutapi start` [% import % [GRAPHICSPATH]] [% turbo % JOBNAME]
+
+`pagelayoutapi batchoptimize` % BATCHLIST
+
+## DESCRIPTION
+
+This script provides an API for the pagelayout LaTeX class.
+
+* `pagelayoutapi getwidth`:
+ Returns the width in pixels for the given image file.
+
+* `pagelayoutapi optimize`:
+ Creates a cropped, rezised, sharpend and compressed image and stores it in the cache directory.
+
+* `pagelayoutapi makeshadow`:
+ Creates a shadow image and stores it in the cache directory.
+
+* `pagelayoutapi start`:
+ The import parameter triggers the image import (see pagelayout-manual.pdf chapter "Image Optimization").
+ Running the command with the parameter turbo, where the jobname is the filename of the document, processes the document in a special batch mode, that creates a batch list and calls the command `pagelayoutapi batchoptimize`.
+
+* `pagelayoutapi batchoptimize`:
+ Optimizes multiple images in parallel.
+
+## OPTIONS
+
+* `--help`:
+ Prints a help message.
+* `--version`:
+ Prints version information.
+
+## PARAMETERS
+
+* FILE:
+ filename with extension (eg. IMG1234.JPEG)
+* FILENAME:
+ filename without extension (eg. IMG1234)
+* EXTENSION:
+ optimized file extension (.jpg|.png)
+* DENSITY:
+ density in ppi (eg. 72)
+* DOWNSAMPLETHRESHOLD:
+ downsample threshold (integer >= 100)
+* UNSHARP:
+ unsharp filter (eg. 2x1)
+* QUALITY:
+ quality (integer > 0, <= 100)
+* STANDARDDEVIATION:
+ standard deviation (decimal > 0.0)
+* OPACITY:
+ opacity (decimal >= 0.0, <= 1.0)
+* COLOR:
+ color string (eg. pink)
+* GRAPHICSPATH:
+ list of directories (eg. {images/}{tmp/})
+* JOBNAME:
+ LaTeX filename without extension (eg. my-document)
+* BATCHLIST:
+ a flat list of batch items where each batch item is a flat list of the 15 parameters required by the command \`pagelayoutapi optimize\`
+
+All other parameters are length dimensions. The command `pagelayoutapi optimize` expects integer values in the LaTeX unit sp (eg. 65536). The command `pagelayoutapi makeshadow` expects decimal values in a SVG compatible unit (eg. 420.0pt).
+
+## ENVIRONMENT
+
+`pagelayoutapi` requires ImageMagick 7.0 or later and Inkscape 1.0 or later.
+
+* `PAGELAYOUT_IMPORT_DIRECTORY`:
+ Defines a system wide import directory.
+
+## AUTHOR
+
+`pagelayoutapi` was written by Friedemann Bartels. <https://github.com/friedemannbartels>
+
+## LICENSE
+
+Copyright (c) 2022-2023 Friedemann Bartels. Free use of this software is granted under the terms of the LaTeX Project Public License version 1.3c or later.
diff --git a/macros/latex/contrib/pagelayout/scripts/textestvis b/macros/latex/contrib/pagelayout/scripts/textestvis
new file mode 100755
index 0000000000..36b3ff6cfb
--- /dev/null
+++ b/macros/latex/contrib/pagelayout/scripts/textestvis
@@ -0,0 +1,506 @@
+#!/bin/sh
+#
+# Copyright (c) 2022-2023 by Friedemann Bartels
+#
+# This file may be distributed and/or modified under the
+# conditions of the LaTeX Project Public License, either
+# version 1.3c of this license or (at your option) any later
+# version. The latest version of this license is in:
+#
+# http://www.latex-project.org/lppl.txt
+#
+# and version 1.3c or later is part of all distributions of
+# LaTeX version 2008/05/04 or later.
+#
+
+version=1.0.3
+
+OK="$( tput setaf 2 )•$( tput sgr0 )"
+FAILED="$( tput setaf 1 )$( tput bold )x$( tput sgr0 )"
+NEW="$( tput setaf 3 )$( tput bold )+$( tput sgr0 )"
+REMOVED="$( tput setaf 5 )$( tput bold )-$( tput sgr0 )"
+ERROR="$( tput setaf 1 )$( tput bold )!$( tput sgr0 )"
+
+_playok() {
+ if [ -f /System/Library/Sounds/Glass.aiff ]; then
+ afplay /System/Library/Sounds/Glass.aiff >/dev/null 2>&1
+ fi
+}
+
+_playhm() {
+ if [ -f /System/Library/Sounds/Basso.aiff ]; then
+ afplay /System/Library/Sounds/Basso.aiff >/dev/null 2>&1
+ fi
+}
+
+_playerror() {
+ if [ -f /System/Library/Sounds/Sosumi.aiff ]; then
+ afplay /System/Library/Sounds/Sosumi.aiff >/dev/null 2>&1
+ fi
+}
+
+_gettime() {
+ if command -v gdate &> /dev/null; then
+ echo $( gdate +%s%3N )
+ else
+ if command -v date &> /dev/null; then
+ milliseconds=$( date +%3N )
+ if [ $milliseconds = 3N ]; then
+ echo $(( SECONDS * 1000 ))
+ else
+ echo $( date +%s%3N )
+ fi
+ else
+ echo $(( SECONDS * 1000 ))
+ fi
+ fi
+}
+
+_starttimer() {
+ starttime=$( _gettime )
+}
+
+_stoptimer() {
+ endtime=$( _gettime )
+
+ elapsedtime=$(( endtime - starttime ))
+ elapsedseconds=$(( elapsedtime / 1000 ))
+ elapsedmilliseconds=$(( elapsedtime % 1000 ))
+ elapsed=$elapsedseconds.$( printf "%03d" "$elapsedmilliseconds" )
+}
+
+test() {
+ _starttimer
+ tmpdir=$( mktemp -d 2>/dev/null )/textest$( date "+%Y%m%d%H%M%S" )$RANDOM
+ mkdir $tmpdir
+
+ if [ $3 = true ] && [ -d cache ]; then
+ rm -rf cache
+ fi
+
+ okcount=0
+ failedcount=0
+ newcount=0
+ removedcount=0
+ fileokcount=0
+ filefailedcount=0
+ defaultfiller=23
+
+ echo ""
+ for entry in *$5*.tex
+ do
+ if [ $entry != "*$5*.tex" ]; then
+ name=$( echo $entry | sed -e 's/\.tex$//g' )
+ if [ ${name:0:1} != _ ]; then
+ printf "$( tput sgr0 )$name"
+
+ runtwice=0
+ if [ "${name:$(( ${#name} - 2 )):2}" = "@2" ]; then
+ runtwice=1
+ fi
+
+ if [ "$4" != "xelatex" ]; then
+ name="$name.$4"
+ fi
+
+ passed=1
+ rm -f $name.failed.*
+
+ cp $entry $tmpdir/$name.tex
+ shell=""
+ if [ $1 = true ]; then
+ shell="--shell-escape"
+ fi
+ $4 -interaction=batchmode $shell --output-directory $tmpdir $tmpdir/$name.tex > /dev/null
+ if [ $? -eq 1 ]; then
+ passed=0
+ fi
+ if [ $runtwice -eq 1 ]; then
+ $4 -interaction=batchmode $shell --output-directory $tmpdir $tmpdir/$name.tex > /dev/null
+ if [ $? -eq 1 ]; then
+ passed=0
+ fi
+ fi
+
+ magick convert -density $2 -quiet $tmpdir/$name.pdf $tmpdir/$name.png
+ if [ -f "$tmpdir/$name.png" ]; then
+ mv $tmpdir/$name.png $tmpdir/$name-0.png
+ fi
+
+ if [ -f "$name.approved.pdf" ]; then
+ magick convert -density $2 -quiet $name.approved.pdf $tmpdir/$name.approved.png
+ if [ -f "$tmpdir/$name.approved.png" ]; then
+ mv $tmpdir/$name.approved.png $tmpdir/$name.approved-0.png
+ fi
+ fi
+
+ if [ $passed -eq 0 ]; then
+ filler=$(( defaultfiller - ${#name} - 2 ))
+ else
+ filler=$(( defaultfiller - ${#name} ))
+ fi
+ while [ 0 -le $filler ]; do
+ printf " "
+ filler=$(( filler - 1 ))
+ done
+ if [ $passed -eq 0 ]; then
+ printf " $ERROR"
+ fi
+
+ index=0
+ while [ -f "$tmpdir/$name-$index.png" ]; do
+ if [ -f "$tmpdir/$name.approved-$index.png" ]; then
+ changedpixels=$( magick compare -quiet -metric AE $tmpdir/$name-$index.png $tmpdir/$name.approved-$index.png null: 2>&1 )
+ if [ $changedpixels -eq 0 ]; then
+ okcount=$(( okcount + 1 ))
+ printf " $OK"
+ else
+ failedcount=$(( failedcount + 1 ))
+ passed=0
+ printf " $FAILED"
+ magick compare -quiet $tmpdir/$name-$index.png $tmpdir/$name.approved-$index.png $name.failed.$(( index + 1 )).png
+ if [ -f "$tmpdir/$name.pdf" ]; then
+ mv $tmpdir/$name.pdf $name.failed.pdf
+ fi
+ fi
+ else
+ newcount=$(( newcount + 1 ))
+ passed=0
+ printf " $NEW"
+ mv $tmpdir/$name-$index.png $name.failed.$(( index + 1 )).png
+ if [ -f "$tmpdir/$name.pdf" ]; then
+ mv $tmpdir/$name.pdf $name.failed.pdf
+ fi
+ fi
+ index=$(( index + 1 ))
+ if [ `expr $index % 20` -eq 0 ] && [ -f "$tmpdir/$name-$index.png" ]; then
+ echo ""
+ filler=$defaultfiller
+ while [ 0 -le $filler ]; do
+ printf " "
+ filler=$(( filler - 1 ))
+ done
+ fi
+ done
+ while [ -f "$tmpdir/$name.approved-$index.png" ]; do
+ removedcount=$(( removedcount + 1 ))
+ passed=0
+ printf " $REMOVED"
+ mv $tmpdir/$name.approved-$index.png $name.failed.$(( index + 1 )).png
+ if [ -f "$tmpdir/$name.pdf" ]; then
+ mv $tmpdir/$name.pdf $name.failed.pdf
+ fi
+ index=$(( index + 1 ))
+ if [ `expr $index % 20` -eq 0 ] && [ -f "$tmpdir/$name.approved-$index.png" ]; then
+ echo ""
+ filler=$defaultfiller
+ while [ 0 -le $filler ]; do
+ printf " "
+ filler=$(( filler - 1 ))
+ done
+ fi
+ done
+
+ if [ $passed -eq 1 ]; then
+ fileokcount=$(( fileokcount + 1 ))
+ else
+ filefailedcount=$(( filefailedcount + 1 ))
+ fi
+
+ echo ""
+ fi
+ fi
+ done
+
+ rm -rf $tmpdir
+ _stoptimer
+ seconds=$( printf "%.1f" "$elapsed" )
+
+ filecount=$(( fileokcount + filefailedcount ))
+
+ if [ $filecount -gt 0 ]; then
+ echo ""
+ fi
+ if [ $filecount -eq $fileokcount ]; then
+ if [ $filecount -eq 0 ]; then
+ _playhm &
+ echo "$( tput sgr0 )Tested 0 files 🧐."
+ else
+ _playok &
+ echo "$( tput sgr0 )Successfully tested $filecount files in $seconds seconds 🎉."
+ fi
+ echo ""
+ else
+ _playerror &
+ echo "$( tput sgr0 )Tested $filecount files in $seconds seconds."
+ echo ""
+
+ if [ $fileokcount -gt 0 ]; then
+ filler=$(( 6 + ${#filecount} - ${#fileokcount} ))
+ while [ 0 -le $filler ]; do
+ printf " "
+ filler=$(( filler - 1 ))
+ done
+ printf "$( tput setaf 2 )$fileokcount OK$( tput sgr0 )"
+ echo ""
+ fi
+ if [ $filefailedcount -gt 0 ]; then
+ filler=$(( 6 + ${#filecount} - ${#filefailedcount} ))
+ while [ 0 -le $filler ]; do
+ printf " "
+ filler=$(( filler - 1 ))
+ done
+ printf "$( tput setaf 1 )$filefailedcount Failed$( tput sgr0 )"
+ echo ""
+ fi
+ echo ""
+ exit 1
+ fi
+}
+
+approve() {
+ filecount=0
+
+ suffix=""
+ if [ "$1" != "xelatex" ]; then
+ suffix=".$1"
+ fi
+
+ echo ""
+ for entry in *$2*$suffix.failed.pdf
+ do
+ if [ $entry != "*$2*$suffix.failed.pdf" ]; then
+ filecount=$(( filecount + 1 ))
+ name=$( echo $entry | sed -e "s/$suffix.failed.pdf//" )
+ echo $name
+ rm -f $name$suffix.approved.pdf
+ if [ -f "$name.aux" ]; then
+ cp $name$suffix.failed.pdf $name$suffix.approved.pdf
+ else
+ mv $name$suffix.failed.pdf $name$suffix.approved.pdf
+ fi
+
+ rm -f $name$suffix.failed.*
+ fi
+ done
+
+ if [ $filecount -gt 0 ]; then
+ echo ""
+ fi
+ if [ $filecount -eq 1 ]; then
+ echo "Approved 1 file."
+ else
+ echo "Approved $filecount files."
+ fi
+ echo ""
+}
+
+compare() {
+ filea=$2
+ fileb=$3
+ tmpdir=$( mktemp -d 2>/dev/null)/textest$( date "+%Y%m%d%H%M%S" )$RANDOM
+ mkdir $tmpdir
+
+ namea=$( echo $filea | sed -e 's/\.\///g' | sed -e 's/\.pdf$//g' )
+ nameb=$( echo $fileb | sed -e 's/\.\///g' | sed -e 's/\.pdf$//g' )
+ basenamea=$(basename $namea)
+ basenameb=$(basename $nameb)
+
+ rm -f $namea.diff.*.png
+
+ spacer=""
+ if [ -f "$filea" ]; then
+ if [ -f "$fileb" ]; then
+ magick convert -density $1 -quiet $filea $tmpdir/$basenamea.png
+ if [ -f "$tmpdir/$basenamea.png" ]; then
+ mv $tmpdir/$basenamea.png $tmpdir/$basenamea-0.png
+ fi
+ magick convert -density $1 -quiet $fileb $tmpdir/$basenameb.png
+ if [ -f "$tmpdir/$basenameb.png" ]; then
+ mv $tmpdir/$basenameb.png $tmpdir/$basenameb-0.png
+ fi
+
+ index=0
+ while [ -f "$tmpdir/$basenamea-$index.png" ]; do
+ if [ -f "$tmpdir/$basenameb-$index.png" ]; then
+ changedpixels=$( magick compare -quiet -metric AE $tmpdir/$basenamea-$index.png $tmpdir/$basenameb-$index.png null: 2>&1 )
+ if [ $changedpixels -eq 0 ]; then
+ printf "$spacer$OK"
+ spacer=" "
+ else
+ printf "$spacer$FAILED"
+ spacer=" "
+ magick compare -quiet $tmpdir/$basenamea-$index.png $tmpdir/$basenameb-$index.png $namea.diff.$(( index + 1 )).png
+ fi
+ else
+ printf "$spacer$NEW"
+ spacer=" "
+ fi
+ index=$(( index + 1 ))
+ done
+ while [ -f "$tmpdir/$basenameb-$index.png" ]; do
+ printf "$spacer$REMOVED"
+ spacer=" "
+ index=$(( index + 1 ))
+ done
+ echo ""
+ else
+ echo "File $fileb not found."
+ fi
+ else
+ echo "File $filea not found."
+ fi
+
+ rm -rf $tmpdir
+}
+
+perf() {
+ if [ -f $4 ]; then
+ tmpdir=$( mktemp -d 2>/dev/null )/textestperf$( date "+%Y%m%d%H%M%S" )$RANDOM
+ mkdir $tmpdir
+ basename=$(basename $4)
+
+ if [ $1 = true ] && [ -d cache ]; then
+ rm -rf cache
+ fi
+
+ cp $4 $tmpdir
+ shell=""
+ if [ $2 = true ]; then
+ shell="--shell-escape"
+ fi
+ _starttimer
+ $3 -interaction=batchmode $shell --output-directory $tmpdir $tmpdir/$basename > /dev/null
+ _stoptimer
+ echo "$elapsed"
+
+ rm -rf $tmpdir
+ else
+ echo "File $4 not found."
+ fi
+}
+
+usage() {
+ cat <<HELP_USAGE
+usage: textest [-cs] [-e ENGINE] [-d DENSITY] [PATTERN]
+ textest approve [-e ENGINE] [PATTERN]
+ textest compare [-d DENSITY] FILE FILE
+ textest perf [-cs] [-e ENGINE] FILE
+
+ -c Clears cache.
+ -s Enables shell escape.
+ -d DENSITY Sets density in ppi (default 72).
+ -e ENGINE Sets latex engine (default xelatex).
+ --help Prints a help message.
+ --version Prints version information.
+HELP_USAGE
+}
+
+version() {
+ echo textest $version
+}
+
+case "$1" in
+approve)
+ shift
+ engine=xelatex
+
+ while getopts ":e:" arg; do
+ case "${arg}" in
+ e)
+ engine="${OPTARG}"
+ ;;
+ ?)
+ echo "Invalid option: -${OPTARG}."
+ ;;
+ esac
+ done
+
+ shift "$(( OPTIND - 1 ))"
+
+ approve $engine $1
+ ;;
+compare)
+ shift
+
+ density=72
+
+ while getopts ":d:" arg; do
+ case "${arg}" in
+ d)
+ density="${OPTARG}"
+ ;;
+ ?)
+ echo "Invalid option: -${OPTARG}."
+ ;;
+ esac
+ done
+
+ shift "$(( OPTIND - 1 ))"
+
+ compare $density $1 $2
+ ;;
+perf)
+ shift
+
+ shell=false
+ clear=false
+ engine=xelatex
+
+ while getopts ":e:sc" arg; do
+ case "${arg}" in
+ c)
+ clear=true
+ ;;
+ e)
+ engine="${OPTARG}"
+ ;;
+ s)
+ shell=true
+ ;;
+ ?)
+ echo "Invalid option: -${OPTARG}."
+ ;;
+ esac
+ done
+
+ shift "$(( OPTIND - 1 ))"
+
+ perf $clear $shell $engine $1
+ ;;
+--version)
+ version
+ ;;
+--help)
+ usage
+ ;;
+*)
+ density=72
+ shell=false
+ clear=false
+ engine=xelatex
+
+ while getopts ":e:d:sc" arg; do
+ case "${arg}" in
+ c)
+ clear=true
+ ;;
+ d)
+ density="${OPTARG}"
+ ;;
+ e)
+ engine="${OPTARG}"
+ ;;
+ s)
+ shell=true
+ ;;
+ ?)
+ echo "Invalid option: -${OPTARG}."
+ ;;
+ esac
+ done
+
+ shift "$(( OPTIND - 1 ))"
+
+ test $shell $density $clear $engine $1
+esac
diff --git a/macros/latex/contrib/pagelayout/scripts/textestvis.1 b/macros/latex/contrib/pagelayout/scripts/textestvis.1
new file mode 100644
index 0000000000..7f7b7df740
--- /dev/null
+++ b/macros/latex/contrib/pagelayout/scripts/textestvis.1
@@ -0,0 +1,73 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "TEXTESTVIS" "1" "January 2023" "" ""
+.
+.SH "NAME"
+\fBtextestvis\fR \- visual regression tests for LaTeX
+.
+.SH "SYNOPSIS"
+\fBtextestvis\fR [\-cs] [\-e ENGINE] [\-d DENSITY] [PATTERN]
+.
+.P
+\fBtextestvis approve\fR [\-e ENGINE] [PATTERN]
+.
+.P
+\fBtextestvis compare\fR [\-d DENSITY] FILE FILE
+.
+.P
+\fBtextestvis perf\fR [\-cs] [\-e ENGINE] FILE
+.
+.SH "DESCRIPTION"
+Perform visual regression tests for LaTeX documents\.
+.
+.TP
+\fBtextestvis\fR
+Creates PDF files for all tex files matching the pattern and performs a visual regression test\. If no pattern is specified, all files in the current directory are tested except for files whose names begin with an underscore\.
+.
+.TP
+\fBtextestvis approve\fR
+Replaces the reference files with the test files\.
+.
+.TP
+\fBtextestvis compare\fR
+Creates a visual diff for two pdf files\.
+.
+.TP
+\fBtextestvis perf\fR
+Measures the time for creating a pdf file from the given tex file\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-c\fR
+Clears cache\.
+.
+.TP
+\fB\-s\fR
+Enables shell escape\.
+.
+.TP
+\fB\-d\fR \fIDENSITY\fR
+Sets density in ppi (default 72)\.
+.
+.TP
+\fB\-e\fR \fIENGINE\fR
+Sets latex engine (default xelatex)\.
+.
+.TP
+\fB\-\-help\fR
+Prints a help message\.
+.
+.TP
+\fB\-\-version\fR
+Prints version information\.
+.
+.SH "ENVIRONMENT"
+\fBtextestvis\fR requires ImageMagick 7\.0 or later\.
+.
+.SH "AUTHOR"
+\fBtextestvis\fR was written by Friedemann Bartels\. \fIhttps://github\.com/friedemannbartels\fR
+.
+.SH "LICENSE"
+Copyright (c) 2022-2023 Friedemann Bartels\. Free use of this software is granted under the terms of the LaTeX Project Public License version 1\.3c or later\.
diff --git a/macros/latex/contrib/pagelayout/scripts/textestvis.1.md b/macros/latex/contrib/pagelayout/scripts/textestvis.1.md
new file mode 100644
index 0000000000..00b475d935
--- /dev/null
+++ b/macros/latex/contrib/pagelayout/scripts/textestvis.1.md
@@ -0,0 +1,55 @@
+textestvis(1) -- visual regression tests for LaTeX
+====
+
+## SYNOPSIS
+
+`textestvis` [-cs] [-e ENGINE] [-d DENSITY] [PATTERN]
+
+`textestvis approve` [-e ENGINE] [PATTERN]
+
+`textestvis compare` [-d DENSITY] FILE FILE
+
+`textestvis perf` [-cs] [-e ENGINE] FILE
+
+## DESCRIPTION
+
+Perform visual regression tests for LaTeX documents.
+
+* `textestvis`:
+ Creates PDF files for all tex files matching the pattern and performs a visual regression test. If no pattern is specified, all files in the current directory are tested except for files whose names begin with an underscore.
+
+* `textestvis approve`:
+ Replaces the reference files with the test files.
+
+* `textestvis compare`:
+ Creates a visual diff for two pdf files.
+
+* `textestvis perf`:
+ Measures the time for creating a pdf file from the given tex file.
+
+## OPTIONS
+
+* `-c`:
+ Clears cache.
+* `-s`:
+ Enables shell escape.
+* `-d` <DENSITY>:
+ Sets density in ppi (default 72).
+* `-e` <ENGINE>:
+ Sets latex engine (default xelatex).
+* `--help`:
+ Prints a help message.
+* `--version`:
+ Prints version information.
+
+## ENVIRONMENT
+
+`textestvis` requires ImageMagick 7.0 or later.
+
+## AUTHOR
+
+`textestvis` was written by Friedemann Bartels. <https://github.com/friedemannbartels>
+
+## LICENSE
+
+Copyright (c) 2022-2023 Friedemann Bartels. Free use of this software is granted under the terms of the LaTeX Project Public License version 1.3c or later.