Compare commits

...
10 Commits
Author SHA1 Message Date
Michael StanglandClaude Opus 5.5 c5598f2304 Doku: Optionen mit Kurz- und Langform
- Tabellen für piccreate, svgconvert, webscan mit -x, --lang <wert>
- neue Optionstabelle für glyphextract, --version in der Übersicht

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:40:26 +02:00
Michael StanglandClaude Opus 5.5 6bdaa62a7d Optionsschalter vereinheitlicht, .sh-Skripte von überall aufrufbar
- gleiche Aufgabe = gleicher Schalter in allen Programmen:
  -c/--config (Alias --cfg), -i, -o/--outdir, --section, --format,
  -f/--force, -j, -t/--test, -y/--dry, -v
- svgconvert: -o statt -s (Alias --svgfolder), --section statt --format
  für die Sektion (Standard png), --format überschreibt das Bildformat
- webscan: --timeout und --browser nur noch lang (kollidierten mit -t/-b)
- glyphextract: -i nimmt Glyph oder Font, -e extrahiert (statt -f/-t)
- setenv.sh ermittelt das Projektverzeichnis selbst, alle .sh laden es
  relativ zum Skript; extract-font/fonttest reichen weitere Optionen durch
- Hilfetexte korrigiert, doc/README.md: Abschnitt "Gemeinsame Optionen"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:37:20 +02:00
Michael StanglandClaude Opus 5.5 28f2bcdb32 svgconvert.sh ruft svgconvert.py auf, generationlib entfernt
- bin/svgconvert.sh ist jetzt ein Wrapper wie svgconvert.bat
  (gleiche Optionen unter Linux und Windows)
- svgconvert.py: Endung .cfg/.csv bei -c/-i optional
- bin/generationlib (nur von svgconvert.sh benutzt) entfernt
- doc/README.md angepasst

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:31:36 +02:00
Michael StanglandClaude Opus 5.5 29c066b45a Windows-Unterstützung: .bat-Skripte, Font-Werkzeuge in Python
- libs/glyphextract.py ersetzt die grep/sed/bc-Logik von extract-glyph.sh,
  extract-font.sh und fonttest.sh (die .sh rufen es nur noch auf);
  schreibt FONTNAME= und <font>.cfg nach $PICCREATE_OUT
- bin/*.bat: setenv, piccreate, svgconvert, webscan, extract-glyph,
  extract-font, fonttest, setup-venv; .gitattributes für CRLF
- piccreate.py: Hintergrund als file:// URL (Windows-Pfade)
- svgconvert.py: magick statt convert, Configs/CSV als UTF-8
- webscan.py: Installationshinweise für Windows
- doc/README.md: Abschnitt Windows, Font-Werkzeuge aktualisiert

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:29:02 +02:00
Michael StanglandClaude Opus 5.5 4897883938 bin/setup-venv.sh: Python-Umgebung .venv einrichten
- legt .venv an (--upgrade-deps) und installiert requirements.txt,
  überspringt das, wenn .venv schon existiert
- Browser für webscan: Arch -> Hinweis auf System-Chromium,
  sonst Playwright-Chromium installieren
- doc/README.md verweist auf das Skript

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:18:39 +02:00
Michael StanglandClaude Opus 5.5 c884f4fd92 webscan: Prüfung auf Playwright und Browser mit Installationshinweisen
- fehlt Playwright oder der Browser, werden die passenden Befehle für
  Ubuntu/Debian bzw. Arch ausgegeben (erkannt über /etc/os-release),
  Abbruch mit Exit-Code 2
- doc/README.md: Einrichtung getrennt nach Ubuntu und Arch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:16:04 +02:00
Michael StanglandClaude Opus 5.5 7bd43e9e00 webscan mit Playwright statt CutyCapt
- libs/webscan.py: Screenshots von Webseiten (Kundennummer;URL) mit
  Playwright/Chromium, parallel, Größe aus [webscan] der Konfiguration;
  der Zuschnitt mit convert entfällt
- bin/webscan.sh ruft webscan.py auf (Python aus .venv, falls vorhanden)
- bin/webscan und libs/CutyCapt (Qt4-WebKit, 2012) entfernt
- requirements.txt, .gitignore, Abschnitt in doc/README.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:13:39 +02:00
Michael StanglandClaude Opus 5.5 31c8d97817 Dokumentation in doc/ mit Beispielbildern
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:00:40 +02:00
Michael StanglandClaude Opus 5.5 75db9b7024 Drei alte Fehler behoben
- create_picture: </svg> und Dateiausgabe erst nach allen Zeilen
  (vorher pro Zeile, kaputtes SVG bei mehreren Zeilen)
- yalign=bottom: word_height wird jetzt berechnet (vorher NameError)
- CompleteEnding: ValueError statt undefiniertem Csv2CouchError

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:55:46 +02:00
Michael StanglandClaude Opus 5.5 d4cb697f14 Auf Python 3 portiert, Django-Abhängigkeit entfernt
- piccreate.py, utils.py: print, configparser, has_key -> in, UTF-8 I/O
- smart_str/smart_unicode entfernt, eigenes slugify() in utils.py
- configparser mit strict=False (doppelte [+] Sektion in den Font-cfgs)
- Shebangs und bin/piccreate auf python3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:53:55 +02:00
40 changed files with 1155 additions and 1764 deletions
+3
View File
@@ -0,0 +1,3 @@
*.bat text eol=crlf
*.sh text eol=lf
*.py text eol=lf
+3
View File
@@ -0,0 +1,3 @@
.venv/
__pycache__/
*.pyc
+10
View File
@@ -0,0 +1,10 @@
@echo off
rem Glyphen und Font-Config eines Fonts nach %PICCREATE_OUT% schreiben
setlocal
call "%~dp0setenv.bat" || exit /b 1
if "%~1"=="" (
echo please give a directory in the in folder with .svg glyphs as input
exit /b 1
)
"%PICCREATE_PYTHON%" "%PICCREATE_LIBS%\glyphextract.py" -i %1 -e %2 %3 %4 %5 %6 %7 %8 %9
exit /b %errorlevel%
+4 -21
View File
@@ -1,26 +1,9 @@
#!/bin/bash #!/bin/bash
. setenv.sh . "$(dirname "${BASH_SOURCE[0]}")/setenv.sh"
if [ -z "$1" ]; then if [ -z "$1" ]; then
echo "please give a directory in the templates folder with .svg glyphs as input" echo "please give a directory in the in folder with .svg glyphs as input"
exit 1 exit 1
fi fi
if [ -d "$PICCREATE_IN/$1" ]; then # Glyphen und <font>.cfg eines Fonts nach $PICCREATE_OUT schreiben
export SOURCE="$PICCREATE_IN/$1" python3 $PICCREATE_LIBS/glyphextract.py -i "$1" -e "${@:2}"
elif [ ! -d "$1" ]; then
export SOURCE="$1"
else
echo "directory does not exist"
echo "please give a directory with .svg files as input"
exit 1
fi
export TARGET=$PICCREATE_OUT
for i in `ls $SOURCE/*.svg`
do
echo `basename $i .svg`
$PICCREATE_BIN/extract-glyph.sh -i $i -e
done
CONFIG=`basename $SOURCE`
( cat $TARGET/*.cfg >> $CONFIG.cfg_ ; rm $TARGET/*.cfg; mv $CONFIG.cfg_ $CONFIG.cfg)
+6
View File
@@ -0,0 +1,6 @@
@echo off
rem Einen Buchstaben extrahieren (-i datei.svg [-e])
setlocal
call "%~dp0setenv.bat" || exit /b 1
"%PICCREATE_PYTHON%" "%PICCREATE_LIBS%\glyphextract.py" %*
exit /b %errorlevel%
+5 -147
View File
@@ -1,149 +1,7 @@
#!/bin/bash #!/bin/bash
. setenv.sh . "$(dirname "${BASH_SOURCE[0]}")/setenv.sh"
if [ -z "$PICCREATE" ]; then
echo "please source the environment"
exit 1
fi
O_ARGS=0
E_OPTERROR=85
USAGE="create a jpg file with the purl inside
Usage: `basename $0` [-hie]
-i id
-e extract
Examples:
to test the glyph later with inkscape:
`basename $0` -i ../in/buchstabe_a.svg
to extract the points and text from the glyph:
`basename $0` -i ../in/buchstabe_a.svg -e "
while getopts "i:eu:" Option
do
case $Option in
i) ID=$OPTARG;;
e) EXTRACT=1;;
*) echo "$USAGE" >&2; exit 1; # Default.
esac
done
shift $(($OPTIND - 1))
if [ -z $ID ] ; then
echo "-> no id/filename given"
echo "$USAGE" >&2
exit 1
fi
if [ -z "$ID" ]; then
echo "please give a .svg file of a glyph as input"
echo "$USAGE" >&2
exit 1
fi
if [ -s "$ID" ]; then
export BASE=`basename $ID .svg`
export GLYPH=$PICCREATE_OUT/$BASE.svg
export CONFIG=$PICCREATE_OUT/$BASE.cfg
else
echo "input file does not exist"
echo "$USAGE" >&2
exit 1
fi
# extrahiere die wichtigen Zeilen aus dem Font
cat $ID | egrep '<\<line\>|x1|x2|y1|y2|/>|style' | egrep -v '\<text\>|id=|http:' > line-$BASE.svg
cat $ID | egrep 'circle|translate|\<r\>' > circles-$BASE.svg
cat $ID | egrep '<\<text|text\>>|\<x\>=|\<y\>=|tspan|style|preserve' | egrep -v '\<transform-center-x\>|\<window-|\<id\>=' > text-$BASE.svg
# reduziere einige Zeilen um bestimmte Muster
cat line-$BASE.svg | sed -e 's/inkscape:transform-center-x=".*"//g' > $$; mv $$ line-$BASE.svg
cat line-$BASE.svg | sed -e 's/inkscape:transform-center-y=".*"//g' > $$; mv $$ line-$BASE.svg
cat text-$BASE.svg | sed -e 's/inkscape:transform-center-x=".*"//g' > $$; mv $$ text-$BASE.svg
cat text-$BASE.svg | sed -e 's/inkscape:transform-center-y=".*"//g' > $$; mv $$ text-$BASE.svg
cat circles-$BASE.svg | sed -e 's/inkscape:transform-center-x=".*"//g' > $$; mv $$ circles-$BASE.svg
cat circles-$BASE.svg | sed -e 's/inkscape:transform-center-y=".*"//g' > $$; mv $$ circles-$BASE.svg
# den eventuell alten Buchstaben vorher wegschmeissen
rm -f $GLYPH
if [ -z "$EXTRACT" ];
then
# schreib einen kompletten neuen Buchstaben zum testen
echo '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">' >> $GLYPH
cat text-$BASE.svg >> $GLYPH
cat circles-$BASE.svg >> $GLYPH
cat line-$BASE.svg >> $GLYPH
echo '</svg>' >> $GLYPH
else
# schreib nur das extract zur Verwertung als Font in die Ausgabe
#echo '<g transform="translate(${posx},${posy})">' >> $GLYPH
echo '<g transform="translate(${posx},${posy})">' >> $GLYPH
cat text-$BASE.svg >> $GLYPH
cat circles-$BASE.svg >> $GLYPH
cat line-$BASE.svg >> $GLYPH
echo '</g>' >> $GLYPH
fi
rm line-$BASE.svg
rm circles-$BASE.svg
rm text-$BASE.svg
## alle Zusatzinfos zum Buchstaben rausholen
XCOORDS=./xcoords
# hole das Minium und Maxium der X-Koordinaten um die Schrittweite des Buchstabens zu bestimmen
# zuerst aus den Texten
cat $GLYPH | egrep "\<x\>=" | sed "s/^\s\+//g;s/x=//g;s/\"//g" > $XCOORDS
# dann aus Kreisen
cat $GLYPH | egrep -v '<g' | egrep "transform=" | sed 's/^\s\+//g;s/translate//g;s/transform=//g;s/["|(|)]//g;s/,.*//g' >> $XCOORDS
# dann aus den Linien
cat $GLYPH | grep x1= | sed "s/^\s\+//g;s/x1=//g;s/\"//g" >> $XCOORDS
cat $GLYPH | grep x2= | sed "s/^\s\+//g;s/x2=//g;s/\"//g" >> $XCOORDS
# sortiere die Koordinaten numerisch
cat $XCOORDS | sort -g | uniq > $$ ; mv $$ $XCOORDS;
XMAX=`tail -n 1 $XCOORDS`
XMIN=`head -n 1 $XCOORDS`
rm $XCOORDS
YCOORDS=./ycoords
# hole das Minium und Maxium der Y-Koordinaten um die Schrittweite des Buchstabens zu bestimmen
# zuerst aus den Texten
cat $GLYPH | grep y= | sed "s/^\s\+//g;s/y=//g;s/\"//g" > $YCOORDS
# dann aus Kreisen
cat $GLYPH | egrep -v '<g' | egrep "transform=" | sed 's/^\s\+//g;s/translate//g;s/transform=//g;s/["|(|)]//g;s/.*,//g' >> $YCOORDS
# dann aus den Linien
cat $GLYPH | grep y1= | sed "s/^\s\+//g;s/y1=//g;s/\"//g" >> $YCOORDS
cat $GLYPH | grep y2= | sed "s/^\s\+//g;s/y2=//g;s/\"//g" >> $YCOORDS
# sortiere die Koordinaten numerisch
cat $YCOORDS | sort -g | uniq > $$ ; mv $$ $YCOORDS;
YMAX=`tail -n 1 $YCOORDS`
YMIN=`head -n 1 $YCOORDS`
rm $YCOORDS
XSTEP=`echo "$XMAX-$XMIN" | bc`
YSTEP=`echo "$YMAX-$YMIN" | bc`
# hole raus wieviele x1-n Muster zu ersetzen sind
NPATTERN=`cat $GLYPH | egrep ">x[0-9]+<" | sed "s/.*\(x[0-9]\+\).*/\1/g" | wc -l`
# ersetze die Muster von x1-xn durch Templatezeichen
cat $GLYPH | sed "s/>\(x[0-9]\+\)</>\${\1}</g" > $$ ; mv $$ $GLYPH;
## und in die Buchstabenconfig schreiben
rm -f $CONFIG
echo [$BASE] >> $CONFIG
echo "XSTEP=$XSTEP" >> $CONFIG
echo "XMIN=$XMIN" >> $CONFIG
echo "XMAX=$XMAX" >> $CONFIG
echo "YSTEP=$YSTEP" >> $CONFIG
echo "YMIN=$YMIN" >> $CONFIG
echo "YMAX=$YMAX" >> $CONFIG
echo "NPATTERN=$NPATTERN" >> $CONFIG
echo "FILENAME=$BASE.svg" >> $CONFIG
echo "#" >> $CONFIG
# Einen Buchstaben extrahieren
# extract-glyph.sh -i ../in/ausgangsschrift/a.svg Testbild fuer Inkscape
# extract-glyph.sh -i ../in/ausgangsschrift/a.svg -e Glyph fuer den Font
python3 $PICCREATE_LIBS/glyphextract.py "$@"
+10
View File
@@ -0,0 +1,10 @@
@echo off
rem Testbilder aller Glyphen eines Fonts nach %PICCREATE_OUT% schreiben
setlocal
call "%~dp0setenv.bat" || exit /b 1
if "%~1"=="" (
echo please give a directory in the in folder with .svg glyphs as input
exit /b 1
)
"%PICCREATE_PYTHON%" "%PICCREATE_LIBS%\glyphextract.py" -i %1 %2 %3 %4 %5 %6 %7 %8 %9
exit /b %errorlevel%
+4 -19
View File
@@ -1,24 +1,9 @@
#!/bin/bash #!/bin/bash
. setenv.sh . "$(dirname "${BASH_SOURCE[0]}")/setenv.sh"
if [ -z "$1" ]; then if [ -z "$1" ]; then
echo "please give a directory in the templates folder with .svg glyphs as input" echo "please give a directory in the in folder with .svg glyphs as input"
exit 1 exit 1
fi fi
if [ -d "$PICCREATE_IN/$1" ]; then # Testbilder aller Glyphen eines Fonts nach $PICCREATE_OUT schreiben
export SOURCE="$PICCREATE_IN/$1" python3 $PICCREATE_LIBS/glyphextract.py -i "$1" "${@:2}"
elif [ ! -d "$1" ]; then
export SOURCE="$1"
else
echo "directory does not exist"
echo "please give a directory with .svg files as input"
exit 1
fi
export TARGET=$PICCREATE_OUT
for i in `ls $SOURCE/*.svg`
do
echo `basename $i .svg`
$PICCREATE_BIN/extract-glyph.sh -i $i
done
-151
View File
@@ -1,151 +0,0 @@
#!/bin/bash
NUM=0
QUEUE=""
MAX_NPROC=2 # default
REPLACE_CMD=0 # no replacement by default
USAGE="A simple wrapper for generating jpg pictures out of svg-files in parallel.
Usage: `basename $0` [-h] [-i] [-j nb_jobs] command arg_list
-h Shows this help
-c give the path to the cfg file
-i give the path of the svg files without the last /
-k check if the jpg already exist
-b set the background to transparent
-s set the sleeptime while there are too many simultanious jobs
-j Set number of simultanious jobs. Default is 2
-o directory where the files should be moved to after creation.
Default ist PICCREATE_OUT
Examples:
`basename $0` -i file -c config
`basename $0` -j 3 -i file -c config"
function cfg_parser {
INI_FILE=$1
SECTION=$2
eval `sed -n -e "/^\[$SECTION\]/,/^\s*\[/{/^[^;].*\=.*/p;}" < $INI_FILE`
}
function get_filepath {
# probiere alle möglichen Kombinationen wo das file stehen könnte je
# nachdem ob es mit oder ohne Endung, mit oder ohne den Komplettpfad
# gegeben wurde
FILENAME=$1
ENDING=$2
DEFAULT_DIR=$3
if [ -s $FILENAME ] ; then
echo $FILENAME
elif [ -s $FILENAME.$ENDING ] ; then
echo "$FILENAME.$ENDING"
elif [ -s $DEFAULT_DIR/$FILENAME ] ; then
echo "$DEFAULT_DIR/$FILENAME"
elif [ -s $DEFAULT_DIR/$FILENAME.$ENDING ] ; then
echo "$DEFAULT_DIR/$FILENAME.$ENDING"
fi
return
}
# Funktion die auf globalen Parametern beruht.
# überprüft ob alle notwendigen Angaben vorhanden sind
function check_params {
if [ -z "$INPUT_FILENAME" ]; then
echo "please give input file with -i."
exit 1
fi
if [ -z "$CFG_FILENAME" ]; then
echo "please give config file with -c."
exit 1
fi
INPUT_FILE=`get_filepath $INPUT_FILENAME 'csv' $PICCREATE_WORK`
if [ ! -s $INPUT_FILE ] ; then
echo "-> input filename does not exist"
print "given was: -$INPUT_FILENAME-"
exit 1
fi
CFG_PATH=`get_filepath $CFG_FILENAME 'cfg' $PICCREATE_CFG`
if [ ! -s $CFG_PATH ]; then
echo "-> config file with this name does not exist."
print "given was: -$CFG_FILENAME-"
exit 1
fi
cfg_parser $CFG_PATH "$SECTION_NAME"
echo 'using width '$width
echo 'using height '$height
if [ -z "$width" ]; then
echo "please give width=xxx in section [$SECTION_NAME] in config $CFG_FILENAME."
exit 1
fi
if [ -z "$height" ]; then
echo "please give height=xxx in section [$SECTION_NAME] in config $CFG_FILENAME."
exit 1
fi
}
# parse command line
if [ $# -eq 0 ]; then # must be at least one arg
echo "$USAGE" >&2
exit 1
fi
CHECKFILE=0
SLEEPTIME=0.5
BACKGROUND=''
FILE_FORMAT='png'
while getopts "j:i:kc:s:o:hc:f:bc" OPT; do # "j:" waits for an argument "h" doesnt
case $OPT in
h) echo "$USAGE"
exit 0 ;;
j) MAX_NPROC=$OPTARG ;;
k) CHECKFILE=1 ;;
c) CFG_FILENAME=$OPTARG ;;
o) OUTDIR=$OPTARG ;;
s) SLEEPTIME=$OPTARG ;;
b) BACKGROUND='-background transparent ';;
i) INPUT_FILENAME=$OPTARG ;;
f) FILE_FORMAT=$OPTARG ;;
*) echo "$USAGE" >&2; exit 1; # Default.
esac
done
# Main program
echo Using $MAX_NPROC parallel threads
check_params
# Starte die Generierung
echo $INPUT_FILE
for i in `ls $INPUT_FILE| grep .svg `
do
ID=`echo "$i" | sed -e 's/;.*//g'`
PIC_NAME=`echo "$i" | sed -e 's/.svg//g'`
FILE_PATH=$INPUT_FILE/$PIC_NAME.$FILE_FORMAT
if [ -s $FILE_PATH ] && [ $CHECKFILE -eq 1 ]
then
echo $PIC_NAME exists
else
RESIZE="${width}x${height}"
SWITCHES="-density $density"'x'"$density $BACKGROUND$INPUT_FILE/$ID -resize $RESIZE $INPUT_FILE/$PIC_NAME.$format"
CMDLINE="$CMD $SWITCHES"
echo $CMDLINE
(echo $ID; $CMDLINE) &
while [ `ps -clx|grep -w 'convert' |wc -l` -ge $MAX_NPROC ]; do
sleep $SLEEPTIME
done
fi
done
wait # wait for all processes to finish before exit
if [ ! -z $OUTDIR ]; then
echo -n moving to $OUTDIR
if [ ! -d $OUTDIR ]; then
echo creating directory $OUTDIR
mkdir $OUTDIR
fi
mv $PICCREATE_OUT/*.jpg $OUTDIR
echo done
fi
# vi:set wrapmargin=0 ai showmatch ts=4 syntax=sh:
+2 -2
View File
@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
. setenv.sh . "$(dirname "${BASH_SOURCE[0]}")/setenv.sh"
# Startet das Programm # Startet das Programm
python $PICCREATE_LIBS/piccreate.py "$@" python3 $PICCREATE_LIBS/piccreate.py "$@"
+6
View File
@@ -0,0 +1,6 @@
@echo off
rem Startet das Programm
setlocal
call "%~dp0setenv.bat" || exit /b 1
"%PICCREATE_PYTHON%" "%PICCREATE_LIBS%\piccreate.py" %*
exit /b %errorlevel%
+38
View File
@@ -0,0 +1,38 @@
@echo off
rem ------------------
rem Umgebung fuer piccreate unter Windows setzen
rem (Gegenstueck zu setenv.sh, wird von den anderen .bat Dateien aufgerufen)
rem ------------------
rem Wo ist das Programm hinkopiert: das Verzeichnis ueber bin\
if not defined PICCREATE for %%I in ("%~dp0..") do set "PICCREATE=%%~fI"
echo setting environment to %PICCREATE%
set "PICCREATE_BIN=%PICCREATE%\bin"
set "PICCREATE_CFG=%PICCREATE%\cfg"
set "PICCREATE_LIBS=%PICCREATE%\libs"
set "PICCREATE_FONTS=%PICCREATE%\fonts"
set "PICCREATE_IN=%PICCREATE%\in"
set "PICCREATE_OUT=%PICCREATE%\out"
set "PICCREATE_BACKGROUNDS=%PICCREATE%\backgrounds"
set "PICCREATE_LOG=%PICCREATE%\log"
rem Wo sind die Pythonskripte ?
set "PYTHONPATH=%PICCREATE_LIBS%;%PYTHONPATH%"
rem Dateien und Ausgaben immer als UTF-8
set "PYTHONUTF8=1"
rem Welches Python: zuerst das aus .venv, dann der Python Launcher "py",
rem dann "python" aus dem PATH
if defined PICCREATE_PYTHON exit /b 0
if exist "%PICCREATE%\.venv\Scripts\python.exe" (
set "PICCREATE_PYTHON=%PICCREATE%\.venv\Scripts\python.exe"
exit /b 0
)
for /f "delims=" %%P in ('py -3 -c "import sys; print(sys.executable)" 2^>nul') do set "PICCREATE_PYTHON=%%P"
if defined PICCREATE_PYTHON exit /b 0
for /f "delims=" %%P in ('python -c "import sys; print(sys.executable)" 2^>nul') do set "PICCREATE_PYTHON=%%P"
if defined PICCREATE_PYTHON exit /b 0
echo Python 3 not found. Please install it from https://www.python.org
echo or with: winget install Python.Python.3.13
exit /b 1
+2 -1
View File
@@ -4,7 +4,8 @@
if [ ! -z $PICCREATE ];then if [ ! -z $PICCREATE ];then
return 0 return 0
fi fi
export PICCREATE=~/develop/python/piccreate # das Verzeichnis ueber bin/
export PICCREATE=$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
echo setting environment to $PICCREATE echo setting environment to $PICCREATE
# ------------------ # ------------------
+47
View File
@@ -0,0 +1,47 @@
@echo off
rem ================================================================
rem PICCREATE - Python Virtual Environment einrichten (Windows)
rem ================================================================
setlocal
rem nicht das Python aus einer alten .venv nehmen
set "PICCREATE_PYTHON="
if exist "%~dp0..\.venv\Scripts\python.exe" goto :installed
rem Python fuer das Anlegen der .venv suchen
for /f "delims=" %%P in ('py -3 -c "import sys; print(sys.executable)" 2^>nul') do set "PICCREATE_PYTHON=%%P"
if not defined PICCREATE_PYTHON for /f "delims=" %%P in ('python -c "import sys; print(sys.executable)" 2^>nul') do set "PICCREATE_PYTHON=%%P"
if not defined PICCREATE_PYTHON (
echo Python 3 nicht gefunden. Bitte installieren von https://www.python.org
echo oder mit: winget install Python.Python.3.13
exit /b 1
)
call "%~dp0setenv.bat" || exit /b 1
echo Initialisiere Python virtual environment...
"%PICCREATE_PYTHON%" -m venv "%PICCREATE%\.venv" --upgrade-deps
if errorlevel 1 (
rmdir /s /q "%PICCREATE%\.venv" 2>nul
echo Fehlgeschlagen.
exit /b 1
)
echo Erfolgreich.
echo Installiere erforderliche Python Packages...
"%PICCREATE%\.venv\Scripts\pip" install -r "%PICCREATE%\requirements.txt" -q || exit /b 1
echo Erfolgreich.
goto :browser
:installed
call "%~dp0setenv.bat" || exit /b 1
echo Erforderliche Python Packages bereits installiert!
:browser
rem Browser fuer webscan
if exist "%LOCALAPPDATA%\ms-playwright\chromium*" (
echo Playwright Browser bereits installiert!
exit /b 0
)
echo Installiere Playwright Browser...
"%PICCREATE%\.venv\Scripts\playwright" install chromium || exit /b 1
echo Erfolgreich.
exit /b 0
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# ================================================================
# PICCREATE - Python Virtual Environment einrichten
# ================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/setenv.sh"
if [ ! -d "$PICCREATE/.venv" ]; then
echo "Initialisiere Python virtual environment..."
python3 -m venv "$PICCREATE/.venv" --upgrade-deps || {
rm -rf "$PICCREATE/.venv"
echo "Fehlgeschlagen. Unter Ubuntu/Debian: sudo apt install python3-venv"
exit 1
}
echo "Erfolgreich."
source "$PICCREATE/.venv/bin/activate"
echo "Installiere erforderliche Python Packages..."
pip install -r "$PICCREATE/requirements.txt" -q || exit 1
echo "Erfolgreich."
deactivate
else
echo "Erforderliche Python Packages bereits installiert!"
fi
# Browser für webscan
# Arch: Playwright unterstützt Arch nicht offiziell -> Chromium der Distribution
if grep -qs '^ID=arch\|^ID_LIKE=.*arch' /etc/os-release; then
if [ -x /usr/bin/chromium ]; then
echo "Browser: /usr/bin/chromium"
echo "Für webscan setzen (z.B. in ~/.bashrc):"
echo " export PICCREATE_BROWSER=/usr/bin/chromium"
else
echo "Chromium fehlt. Bitte installieren:"
echo " sudo pacman -S chromium"
echo " export PICCREATE_BROWSER=/usr/bin/chromium"
fi
elif [ -n "$(ls -d ~/.cache/ms-playwright/chromium* 2>/dev/null)" ]; then
echo "Playwright Browser bereits installiert!"
else
echo "Installiere Playwright Browser..."
"$PICCREATE/.venv/bin/playwright" install chromium || exit 1
echo "Erfolgreich."
echo "Falls der Browser nicht startet (Ubuntu/Debian), Systembibliotheken installieren:"
echo " sudo $PICCREATE/.venv/bin/playwright install-deps chromium"
fi
+6
View File
@@ -0,0 +1,6 @@
@echo off
rem SVG Bilder mit ImageMagick in JPG/PNG umwandeln
setlocal
call "%~dp0setenv.bat" || exit /b 1
"%PICCREATE_PYTHON%" "%PICCREATE_LIBS%\svgconvert.py" %*
exit /b %errorlevel%
+4 -9
View File
@@ -1,11 +1,6 @@
#!/bin/bash #!/bin/bash
. setenv.sh . "$(dirname "${BASH_SOURCE[0]}")/setenv.sh"
# Wo ist das Programm hinkopiert
if [ -z $PICCREATE ];then
echo please check environment
exit 0
fi
CMD=convert
SECTION_NAME=png
. $PICCREATE_BIN/generationlib # SVG Bilder mit ImageMagick in JPG/PNG umwandeln
# svgconvert.sh -c punkteraetsel -p
python3 $PICCREATE_LIBS/svgconvert.py "$@"
-191
View File
@@ -1,191 +0,0 @@
#!/bin/bash
NUM=0
QUEUE=""
MAX_NPROC=2 # default
REPLACE_CMD=0 # no replacement by default
USAGE="A simple wrapper for generating jpg pictures out of svg-files in parallel.
Usage: `basename $0` [-h] [-i] [-j nb_jobs] command arg_list
-h Shows this help
-c give the path to the cfg file
-i give the file where the web-adresses are
-k chek if the output file already exist
-s set the sleeptime while there are too many simultanious jobs
-j Set number of simultanious jobs. Default is 2
-o directory where the files should be moved to after creation.
Default ist PICCREATE_OUT
Examples:
`basename $0` -i file -c config
`basename $0` -j 3 -i file -c config"
function cfg_parser {
INI_FILE=$1
SECTION=$2
eval `sed -n -e "/^\[$SECTION\]/,/^\s*\[/{/^[^;].*\=.*/p;}" < $INI_FILE`
}
function get_filepath {
# probiere alle möglichen Kombinationen wo das file stehen könnte je
# nachdem ob es mit oder ohne Endung, mit oder ohne den Komplettpfad
# gegeben wurde
FILENAME=$1
ENDING=$2
DEFAULT_DIR=$3
if [ -s $FILENAME ] ; then
echo $FILENAME
elif [ -s $FILENAME.$ENDING ] ; then
echo "$FILENAME.$ENDING"
elif [ -s $DEFAULT_DIR/$FILENAME ] ; then
echo "$DEFAULT_DIR/$FILENAME"
elif [ -s $DEFAULT_DIR/$FILENAME.$ENDING ] ; then
echo "$DEFAULT_DIR/$FILENAME.$ENDING"
fi
return
}
# Funktion die auf globalen Parametern beruht.
# überprüft ob alle notwendigen Angaben vorhanden sind
function check_params {
if [ -z "$INPUT_FILENAME" ]; then
echo "please give input file with -i."
exit 1
fi
if [ -z "$CFG_FILENAME" ]; then
echo "please give config file with -c."
exit 1
fi
INPUT_FILE=`get_filepath $INPUT_FILENAME 'csv' $PICCREATE_WORK`
if [ ! -s $INPUT_FILE ] ; then
echo "-> input filename does not exist"
print "given was: -$INPUT_FILENAME-"
exit 1
fi
CFG_PATH=`get_filepath $CFG_FILENAME 'cfg' $PICCREATE_CFG`
if [ ! -s $CFG_PATH ]; then
echo "-> config file with this name does not exist."
print "given was: -$CFG_FILENAME-"
exit 1
fi
echo $INPUT_FILENAME
cfg_parser $CFG_PATH "$FILE_FORMAT"
echo 'using width '$width
echo 'using height '$height
if [ -z "$width" ]; then
echo "please give width=xxx in section [$FILE_FORMAT] in config $CFG_FILENAME."
exit 1
fi
if [ -z "$height" ]; then
echo "please give height=xxx in section [$FILE_FORMAT] in config $CFG_FILENAME."
exit 1
fi
FORMAT="$format"
}
# parse command line
if [ $# -eq 0 ]; then # must be at least one arg
echo "$USAGE" >&2
exit 1
fi
CHECKFILE=1
SLEEPTIME=0.5
BACKGROUND=''
FILE_FORMAT='webscan'
OUTDIR=$PICCREATE_OUT
while getopts "j:i:kc:s:o:hc:f" OPT; do # "j:" waits for an argument "h" doesnt
case $OPT in
h) echo "$USAGE"
exit 0 ;;
j) MAX_NPROC=$OPTARG ;;
k) CHECKFILE=0 ;;
c) CFG_FILENAME=$OPTARG ;;
o) OUTDIR=$OPTARG ;;
s) SLEEPTIME=$OPTARG ;;
i) INPUT_FILENAME=$OPTARG ;;
f) FORMAT=$OPTARG ;;
*) echo "$USAGE" >&2; exit 1; # Default.
esac
done
# Main program
echo Using $MAX_NPROC parallel threads
check_params
# Starte die Abspeichern der ganzen Webseite als ganzes
echo $INPUT_FILE
for i in `cat $INPUT_FILENAME`
do
CUSTOMER_ID=`echo "$i" | sed -e 's/;.*//g'`
CUSTOMER_URL=`echo "$i" |sed -e 's/.*;//g'`
FILE_PATH=$OUTDIR/$CUSTOMER_ID.$FORMAT
echo $FILE_PATH
wait
if [ -z $CUSTOMER_URL ]
then
echo 'i need a url!!'
echo $CUSTOMER_ID 'did not have an url, ist was set to www.syntops.de instead'>> logfile.txt
CUSTOMER_URL='www.syntops.de'
sleep
fi
if [ -s $FILE_PATH ] && [ $CHECKFILE -eq 1 ]
then
echo $FILE_PATH exists
else
CMDLINE="$CUTYCAPT/./CutyCapt --url=http://$CUSTOMER_URL --max-wait=20000 --min-width=800 --min-height=600 --out=$FILE_PATH"
$CMDLINE
wait
fi
done
wait # wait for all processes to finish before exit
echo "...all Webpages have been saved!"
echo ""
echo "Converting the pictures..."
echo " "
# Zuschnitt der seite auf das benötigte Format
# Das Bild wir hierfür erst gleichmäßig auf die forgegebene Breite Skaliert
# und anschließend die unten herüberstehende Höhe abgeschnitten
echo $outdir
for i in `ls $OUTDIR| grep .$FORMAT `
do
FILE_PATH_IN=$OUTDIR/$i
FILE_PATH_OUT=$OUTDIR/'beschnitten'/$i
if [ -s $FILE_PATH_OUT ] && [ $CHECKFILE -eq 1 ]
then
echo $PIC_NAME exists
else
RESIZE="${width}"
SLICE="${width}x${height}"
CMDLINE="convert $FILE_PATH_IN -resize $RESIZE -crop $SLICE $FILE_PATH_OUT"
echo $CMDLINE
(echo $ID; $CMDLINE) &
while [ `ps clx|grep -w 'convert' |wc -l` -ge $MAX_NPROC ]; do
sleep $SLEEPTIME
done
fi
# auskommentiert da bereits runtergeladene seiten behalten werden sollen
#rm $FILE_PATH
done
wait # wait for all processes to finish before exit
rm $OUTDIR/'beschnitten'/*-[1-9].$FORMAT
for i in `ls $OUTDIR/'beschnitten'/`
do
first=`basename $i -0.jpg`
second=`basename $first .jpg`
mv $OUTDIR/'beschnitten'/$i $OUTDIR/'beschnitten'/$second.jpg
done
# vi:set wrapmargin=0 ai showmatch ts=4 syntax=sh:
+6
View File
@@ -0,0 +1,6 @@
@echo off
rem Vorschaubilder von Webseiten erzeugen (Playwright)
setlocal
call "%~dp0setenv.bat" || exit /b 1
"%PICCREATE_PYTHON%" "%PICCREATE_LIBS%\webscan.py" %*
exit /b %errorlevel%
+8 -3
View File
@@ -1,4 +1,9 @@
#!/bin/bash #!/bin/bash
. setenv.sh . "$(dirname "${BASH_SOURCE[0]}")/setenv.sh"
export CUTYCAPT=$PICCREATE_LIBS/CutyCapt
$PICCREATE_BIN/webscan "$@" # Vorschaubilder von Webseiten erzeugen (Playwright, Python-venv im Projekt)
PYTHON=python3
if [ -x $PICCREATE/.venv/bin/python ]; then
PYTHON=$PICCREATE/.venv/bin/python
fi
$PYTHON $PICCREATE_LIBS/webscan.py "$@"
+328
View File
@@ -0,0 +1,328 @@
# piccreate
`piccreate` erzeugt aus einer CSV-Datei mit Namen für jede Zeile ein eigenes
SVG-Bild. Die Buchstaben stammen aus einem eigenen SVG-„Font“, dessen Glyphen
nummerierte Punkte enthalten. So entstehen z. B. personalisierte
**Punkt-zu-Punkt-Rätsel** und die passenden **Lösungsbilder**.
Beispiel für die CSV-Zeile `001;Silke`:
Rätsel (`cfg/punkteraetsel.cfg`, Font `ausgangsschrift`, Hintergrund `Karo.png`):
![Punkterätsel für "Silke"](images/punkteraetsel.png)
Lösung (`cfg/punkteloesung.cfg`, Font `ausgangsschrift_verbunden`, ohne Hintergrund):
![Lösung für "Silke"](images/punkteloesung.png)
Die Punkte werden über das ganze Wort fortlaufend nummeriert (1–32).
## Voraussetzungen
- Python 3 (für `piccreate.py`, `svgconvert.py` und `glyphextract.py` keine
weiteren Pakete nötig)
- ImageMagick (`magick`, ältere Versionen `convert`) für die Umwandlung
SVG → JPG/PNG mit `svgconvert.py`
- nur für Webseiten-Vorschauen (`webscan`): Playwright, siehe unten
Alles läuft unter Linux und Windows, siehe [Windows](#windows).
## Verzeichnisse
| Verzeichnis | Inhalt |
|----------------|-----------------------------------------------------------|
| `bin/` | Startskripte: `.sh` für Linux, `.bat` für Windows |
| `libs/` | Python-Programme `piccreate.py`, `svgconvert.py`, `webscan.py`, `glyphextract.py` |
| `cfg/` | Auftrags-Konfigurationen (`*.cfg`) |
| `in/` | Eingabedateien (`*.csv`) und Rohglyphen für neue Fonts |
| `out/` | Ausgabe der erzeugten Bilder |
| `fonts/` | Fonts: ein Verzeichnis pro Font mit Glyph-SVGs + `.cfg` |
| `backgrounds/` | Hintergrundbilder |
## Umgebung
Alle Pfade werden über Umgebungsvariablen gefunden, die `bin/setenv.sh` setzt
(`PICCREATE`, `PICCREATE_CFG`, `PICCREATE_IN`, `PICCREATE_OUT`,
`PICCREATE_FONTS`, `PICCREATE_BACKGROUNDS`, …). Das Projektverzeichnis ermittelt
`setenv.sh` selbst (das Verzeichnis über `bin/`), die Skripte in `bin/` lassen
sich deshalb von überall aufrufen. Nur wer die Variablen in der eigenen Shell
braucht, lädt `setenv.sh` selbst (bash oder zsh):
```sh
cd bin
. ./setenv.sh
```
## Gemeinsame Optionen
Alle Programme benutzen für dieselbe Aufgabe denselben Schalter:
| Option | Bedeutung | piccreate | svgconvert | webscan | glyphextract |
|---------------------|----------------------------------------------------|:-:|:-:|:-:|:-:|
| `-c`, `--config` | Konfiguration aus `cfg/` (Endung `.cfg` optional) | ✓ | ✓ | ✓ | |
| `-i`, `--infile` | Eingabe (CSV, URL-Liste, Glyph oder Font) | ✓ | ✓ | ✓ | ✓ |
| `-o`, `--outdir` | Ordner für die erzeugten Bilder (Standard `$PICCREATE_OUT`) | ✓ | ✓ | ✓ | ✓ |
| `--section` | Sektion der Konfiguration mit den Bildmaßen | | ✓ | ✓ | |
| `--format` | Bildformat `jpg` oder `png` (überschreibt die Konfiguration) | | ✓ | ✓ | |
| `-f`, `--force` | vorhandene Bilder überschreiben | | ✓ | ✓ | |
| `-j`, `--jobs` | Anzahl paralleler Jobs | | ✓ | ✓ | |
| `-t`, `--test` | Testbilder erzeugen | | ✓ | | |
| `-y`, `--dry` | Probelauf, keine Dateien schreiben | ✓ | ✓ | | |
| `-v`, `--verbose` | ausführliche Ausgabe | | ✓ | ✓ | |
| `-h`, `--help` | Hilfe | ✓ | ✓ | ✓ | ✓ |
| `--version` | Versionsnummer | ✓ | ✓ | ✓ | ✓ |
Ältere Namen funktionieren weiter: `--cfg` für `--config` und bei svgconvert
`--svgfolder` für `--outdir`.
## Bilder erzeugen
```sh
./piccreate -c punkteraetsel -i test
```
| Option | Bedeutung |
|-----------------------------|-----------|
| `-c`, `--config <datei>` | Konfiguration aus `cfg/` (Endung `.cfg` optional) |
| `-i`, `--infile <datei>` | CSV-Datei aus `in/` (Endung `.csv` optional) |
| `-o`, `--outdir <ordner>` | Ausgabeverzeichnis (Standard `$PICCREATE_OUT`) |
| `-s`, `--startfrom` | Nummerierung bei jedem Wort / Großbuchstaben neu beginnen |
| `-y`, `--dry` | SVG nur auf der Konsole ausgeben, keine Dateien schreiben |
Für jede CSV-Zeile entsteht `<customerid>.svg` im Ausgabeverzeichnis.
### Eingabedatei (CSV)
UTF-8, Trennzeichen `;`, `,` oder Tab (wird automatisch erkannt), erste Zeile
enthält die Spaltenüberschriften:
```
Kennummer;Name
001;Silke
002;Rau
```
## Konfiguration (`cfg/*.cfg`)
```ini
[globals]
# Bildgröße in Pixel
width=2776
height=898
# optional, Datei aus backgrounds/
background=Karo.png
# Textzeilen im Bild, durch Komma getrennt
lines=PunkteName
# interner Name = Spaltenüberschrift der CSV
[translation]
# Pflicht: ergibt den Dateinamen
customerid=Kennummer
name=Name
# eine Sektion je Eintrag in "lines"
[PunkteName]
# Text, Platzhalter aus [translation]
content=${name}
fontname=ausgangsschrift
# Startpunkt
x=-10
y=0
# Fläche für die Zeile, optional (sonst aus [globals])
width=2640
height=850
# Abstand zwischen den Buchstaben
glyph_space=20
# Punkte fortlaufend nummerieren
counter_replace=True
# left | center | right
align=center
# top | center | bottom
yalign=center
# Wort auf Bildgröße skalieren
autoscale=True
```
Statt `autoscale` kann auch fest mit `scale` bzw. `xscale`/`yscale` skaliert
werden. Fehlen in einer Zeilen-Sektion `width`/`height`, gelten die Werte aus
`[globals]`.
Die Sektionen `[jpgs]`, `[png]` usw. beschreiben die Rastergrafiken für
`svgconvert.py` (`width`, `height`, `format`, `density`).
## SVG in JPG/PNG umwandeln
```sh
./svgconvert.sh -c punkteraetsel -p
```
Wandelt alle SVGs in `$PICCREATE_OUT` (oder `-o <ordner>`) parallel mit
ImageMagick um; die Bilder landen im selben Ordner. Breite, Höhe, Format und
Auflösung kommen aus der Sektion `--section` der Konfiguration (Standard
`[png]`, für große JPGs z. B. `--section jpgs`).
| Option | Bedeutung |
|--------------------------------|-----------|
| `-c`, `--config <datei>` | Konfiguration aus `cfg/` (Endung `.cfg` optional) |
| `-o`, `--outdir <ordner>` | Ordner mit den SVGs, auch für die Bilder (Standard `$PICCREATE_OUT`) |
| `--section <name>` | Sektion mit den Bildmaßen (Standard `png`) |
| `--format <jpg\|png>` | Bildformat, überschreibt die Konfiguration |
| `-i`, `--infile <datei>` | CSV aus `in/` prüfen: fehlende Bilder melden, nur diese umwandeln |
| `-j`, `--jobs <n>` | Anzahl paralleler Jobs (Standard 8) |
| `-b`, `--background <farbe>` | Hintergrundfarbe, `none` für transparent |
| `-f`, `--force` | vorhandene Bilder überschreiben |
| `-p`, `--progress` | Fortschrittsbalken anzeigen |
| `-v`, `--verbose` | ausführliche Ausgabe |
| `-t`, `--test <n>` | `n` Test-SVGs erzeugen und umwandeln |
| `-y`, `--dry` | Probelauf ohne Umwandlung |
## Vorschaubilder von Webseiten (webscan)
`libs/webscan.py` lädt für jede Zeile einer Eingabedatei eine Webseite in
einem Browser ohne Fenster und speichert den sichtbaren Bereich als Bild
`<Kundennummer>.jpg`. Die Größe kommt aus der Sektion `[webscan]` der
Konfiguration (in `cfg/webscan.cfg`: 800×500).
Einmalig einrichten mit `bin/setup-venv.sh`: legt die Python-Umgebung `.venv`
im Projektverzeichnis an, installiert `requirements.txt` und kümmert sich um
den Browser (Ubuntu: Playwright-Chromium, Arch: Hinweis auf das Chromium der
Distribution). Ist `.venv` schon vorhanden, wird nichts neu installiert.
```sh
./setup-venv.sh
```
Von Hand geht es so:
```sh
python3 -m venv .venv # Ubuntu: vorher sudo apt install python3-venv
.venv/bin/pip install -r requirements.txt
```
Browser unter Ubuntu/Debian:
```sh
sudo .venv/bin/playwright install-deps chromium
.venv/bin/playwright install chromium
```
Browser unter Arch Linux (Playwright unterstützt Arch nicht offiziell, daher
besser das Chromium der Distribution):
```sh
sudo pacman -S chromium
export PICCREATE_BROWSER=/usr/bin/chromium # oder Option --browser /usr/bin/chromium
```
Fehlen Playwright oder der Browser, gibt `webscan.py` die passenden Befehle
für die erkannte Distribution aus und bricht mit Exit-Code 2 ab.
Eingabedatei, z. B. `in/kunden.csv` (Kopfzeile optional, `http://` wird bei
Bedarf ergänzt):
```
Kennummer;URL
1001;www.example.org
1002;https://www.example.com/start
```
Aufruf:
```sh
./webscan.sh -c webscan -i kunden
```
| Option | Bedeutung |
|-----------------------------|-----------|
| `-c`, `--config <datei>` | Konfiguration aus `cfg/` (Standard `webscan`) |
| `-i`, `--infile <datei>` | Eingabedatei aus `in/` oder mit Pfad |
| `-o`, `--outdir <ordner>` | Ausgabeverzeichnis (Standard `$PICCREATE_OUT`) |
| `--section <name>` | Sektion mit den Bildmaßen (Standard `webscan`) |
| `--format <jpg\|png>` | Bildformat, überschreibt die Konfiguration |
| `-j`, `--jobs <n>` | Anzahl gleichzeitig geladener Seiten (Standard 4) |
| `--timeout <sekunden>` | Wartezeit pro Seite (Standard 20) |
| `-f`, `--force` | vorhandene Bilder überschreiben |
| `--browser <pfad>` | eigenes Chromium statt des Playwright-Browsers (Standard `$PICCREATE_BROWSER`) |
| `-v`, `--verbose` | ausführliche Ausgabe |
Seiten ohne URL oder mit Ladefehler werden übersprungen und am Ende
aufgelistet; der Exit-Code ist dann 1.
## Fonts
Ein Font liegt in `fonts/<name>/` und besteht aus
- einer SVG-Vorlage pro Zeichen (z. B. `a.svg`), eingeschlossen in
`<g transform="translate(${posx},${posy})">…</g>`; die Punktnummern sind
Platzhalter `${x1}`, `${x2}`, …
- der Datei `<name>.cfg` mit einer Sektion pro Zeichen:
```ini
[a]
# Breite des Zeichens
XSTEP=50.071972
# Höhe des Zeichens
YMAX=139.04482
# Anzahl der Platzhalter x1..xn
NPATTERN=11
# Vorlagendatei
FONTNAME=a.svg
```
Neue Fonts entstehen aus in Inkscape gezeichneten Glyphen (Punkte als Kreise,
Nummern als Text `x1`, `x2`, …):
```sh
./extract-font.sh ausgangsschrift # Glyphen + ausgangsschrift.cfg erzeugen
./fonttest.sh ausgangsschrift # Testbilder der einzelnen Glyphen erzeugen
./extract-glyph.sh -i ../in/ausgangsschrift/a.svg -e # nur ein Buchstabe
```
Alle drei rufen `libs/glyphextract.py` auf. Dort ist `-i` ein einzelner
Buchstabe oder ein Font (Ordner in `in/` oder Pfad); mit `-e` werden die
Glyphen für den Font extrahiert (bei einem Font zusätzlich `<name>.cfg`),
ohne `-e` entstehen Testbilder zum Prüfen in Inkscape:
```sh
python3 glyphextract.py -i ausgangsschrift -e # = extract-font.sh
python3 glyphextract.py -i ausgangsschrift # = fonttest.sh
python3 glyphextract.py -i ../in/ausgangsschrift/a.svg -e
```
| Option | Bedeutung |
|----------------------------------|-----------|
| `-i`, `--infile <datei\|font>` | ein Buchstabe (`.svg`) oder ein Font (Ordner in `in/` oder Pfad) |
| `-e`, `--extract` | Glyphen für den Font extrahieren, bei einem Font zusätzlich `<name>.cfg`; ohne `-e` Testbilder |
| `-o`, `--outdir <ordner>` | Ausgabeverzeichnis (Standard `$PICCREATE_OUT`) |
Alle schreiben nach `$PICCREATE_OUT` (oder `-o <ordner>`). Das Ergebnis
(Glyph-SVGs und `<name>.cfg`) wird anschließend nach `fonts/<name>/`
übernommen. Werte wie `XSTEP` kann man danach in der `.cfg` noch von Hand
anpassen.
## Windows
Für jedes Shell-Skript in `bin/` gibt es eine `.bat`-Datei mit denselben
Optionen: `piccreate.bat`, `svgconvert.bat`, `webscan.bat`,
`extract-glyph.bat`, `extract-font.bat`, `fonttest.bat`, `setup-venv.bat`.
`setenv.bat` setzt die Umgebung; das Projektverzeichnis ermittelt es selbst
(das Verzeichnis über `bin\`), ein fester Pfad ist nicht nötig.
Einrichten:
1. Python 3 installieren (https://www.python.org oder
`winget install Python.Python.3.13`)
2. ImageMagick installieren (https://imagemagick.org), nur für `svgconvert`
3. nur für `webscan`: `bin\setup-venv.bat` ausführen (legt `.venv` an und
installiert Playwright mit Chromium)
Beispiel:
```bat
cd bin
piccreate.bat -c punkteraetsel -i test
svgconvert.bat -c punkteraetsel -p
```
Die `.bat`-Dateien nehmen das Python aus `.venv`, falls vorhanden, sonst den
Python Launcher `py` oder `python` aus dem `PATH`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.
-662
View File
@@ -1,662 +0,0 @@
////////////////////////////////////////////////////////////////////
//
// CutyCapt - A Qt WebKit Web Page Rendering Capture Utility
//
// Copyright (C) 2003-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// $Id$
//
////////////////////////////////////////////////////////////////////
#include <QApplication>
#include <QtWebKit>
#include <QtGui>
#include <QSvgGenerator>
#include <QPrinter>
#include <QTimer>
#include <QByteArray>
#include <QNetworkRequest>
#include "CutyCapt.hpp"
#if QT_VERSION >= 0x040600 && 0
#define CUTYCAPT_SCRIPT 1
#endif
#ifdef STATIC_PLUGINS
Q_IMPORT_PLUGIN(qjpeg)
Q_IMPORT_PLUGIN(qgif)
Q_IMPORT_PLUGIN(qtiff)
Q_IMPORT_PLUGIN(qsvg)
Q_IMPORT_PLUGIN(qmng)
Q_IMPORT_PLUGIN(qico)
#endif
static struct _CutyExtMap {
CutyCapt::OutputFormat id;
const char* extension;
const char* identifier;
} const CutyExtMap[] = {
{ CutyCapt::SvgFormat, ".svg", "svg" },
{ CutyCapt::PdfFormat, ".pdf", "pdf" },
{ CutyCapt::PsFormat, ".ps", "ps" },
{ CutyCapt::InnerTextFormat, ".txt", "itext" },
{ CutyCapt::HtmlFormat, ".html", "html" },
{ CutyCapt::RenderTreeFormat, ".rtree", "rtree" },
{ CutyCapt::JpegFormat, ".jpeg", "jpeg" },
{ CutyCapt::PngFormat, ".png", "png" },
{ CutyCapt::MngFormat, ".mng", "mng" },
{ CutyCapt::TiffFormat, ".tiff", "tiff" },
{ CutyCapt::GifFormat, ".gif", "gif" },
{ CutyCapt::BmpFormat, ".bmp", "bmp" },
{ CutyCapt::PpmFormat, ".ppm", "ppm" },
{ CutyCapt::XbmFormat, ".xbm", "xbm" },
{ CutyCapt::XpmFormat, ".xpm", "xpm" },
{ CutyCapt::OtherFormat, "", "" }
};
QString
CutyPage::chooseFile(QWebFrame* /*frame*/, const QString& /*suggestedFile*/) {
return QString::null;
}
bool
CutyPage::javaScriptConfirm(QWebFrame* /*frame*/, const QString& /*msg*/) {
return true;
}
bool
CutyPage::javaScriptPrompt(QWebFrame* /*frame*/,
const QString& /*msg*/,
const QString& /*defaultValue*/,
QString* /*result*/) {
return true;
}
void
CutyPage::javaScriptConsoleMessage(const QString& /*message*/,
int /*lineNumber*/,
const QString& /*sourceID*/) {
// noop
}
void
CutyPage::javaScriptAlert(QWebFrame* /*frame*/, const QString& msg) {
if (mPrintAlerts)
qDebug() << "[alert]" << msg;
if (mAlertString == msg) {
QTimer::singleShot(10, mCutyCapt, SLOT(Delayed()));
}
}
QString
CutyPage::userAgentForUrl(const QUrl& url) const {
if (!mUserAgent.isNull())
return mUserAgent;
return QWebPage::userAgentForUrl(url);
}
void
CutyPage::setUserAgent(const QString& userAgent) {
mUserAgent = userAgent;
}
void
CutyPage::setAlertString(const QString& alertString) {
mAlertString = alertString;
}
QString
CutyPage::getAlertString() {
return mAlertString;
}
void
CutyPage::setCutyCapt(CutyCapt* cutyCapt) {
mCutyCapt = cutyCapt;
}
void
CutyPage::setPrintAlerts(bool printAlerts) {
mPrintAlerts = printAlerts;
}
void
CutyPage::setAttribute(QWebSettings::WebAttribute option,
const QString& value) {
if (value == "on")
settings()->setAttribute(option, true);
else if (value == "off")
settings()->setAttribute(option, false);
else
(void)0; // TODO: ...
}
// TODO: Consider merging some of main() and CutyCap
CutyCapt::CutyCapt(CutyPage* page, const QString& output, int delay, OutputFormat format,
const QString& scriptProp, const QString& scriptCode) {
mPage = page;
mOutput = output;
mDelay = delay;
mSawInitialLayout = false;
mSawDocumentComplete = false;
mFormat = format;
mScriptProp = scriptProp;
mScriptCode = scriptCode;
mScriptObj = new QObject();
// This is not really nice, but some restructuring work is
// needed anyway, so this should not be that bad for now.
mPage->setCutyCapt(this);
}
void
CutyCapt::InitialLayoutCompleted() {
mSawInitialLayout = true;
if (mSawInitialLayout && mSawDocumentComplete)
TryDelayedRender();
}
void
CutyCapt::DocumentComplete(bool /*ok*/) {
mSawDocumentComplete = true;
if (mSawInitialLayout && mSawDocumentComplete)
TryDelayedRender();
}
void
CutyCapt::JavaScriptWindowObjectCleared() {
if (!mScriptProp.isEmpty()) {
QVariant var = mPage->mainFrame()->evaluateJavaScript(mScriptProp);
QObject* obj = var.value<QObject*>();
if (obj == mScriptObj)
return;
mPage->mainFrame()->addToJavaScriptWindowObject(mScriptProp, mScriptObj);
}
mPage->mainFrame()->evaluateJavaScript(mScriptCode);
}
void
CutyCapt::TryDelayedRender() {
if (!mPage->getAlertString().isEmpty())
return;
if (mDelay > 0) {
QTimer::singleShot(mDelay, this, SLOT(Delayed()));
return;
}
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::Timeout() {
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::Delayed() {
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::saveSnapshot() {
QWebFrame *mainFrame = mPage->mainFrame();
QPainter painter;
const char* format = NULL;
for (int ix = 0; CutyExtMap[ix].id != OtherFormat; ++ix)
if (CutyExtMap[ix].id == mFormat)
format = CutyExtMap[ix].identifier; //, break;
// TODO: sometimes contents/viewport can have size 0x0
// in which case saving them will fail. This is likely
// the result of the method being called too early. So
// far I've been unable to find a workaround, except
// using --delay with some substantial wait time. I've
// tried to resize multiple time, make a fake render,
// check for other events... This is primarily a problem
// under my Ubuntu virtual machine.
mPage->setViewportSize( mainFrame->contentsSize() );
switch (mFormat) {
case SvgFormat: {
QSvgGenerator svg;
svg.setFileName(mOutput);
svg.setSize(mPage->viewportSize());
painter.begin(&svg);
mainFrame->render(&painter);
painter.end();
break;
}
case PdfFormat:
case PsFormat: {
QPrinter printer;
printer.setPageSize(QPrinter::A4);
printer.setOutputFileName(mOutput);
// TODO: change quality here?
mainFrame->print(&printer);
break;
}
case RenderTreeFormat:
case InnerTextFormat:
case HtmlFormat: {
QFile file(mOutput);
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream s(&file);
s.setCodec("utf-8");
s << (mFormat == RenderTreeFormat ? mainFrame->renderTreeDump() :
mFormat == InnerTextFormat ? mainFrame->toPlainText() :
mFormat == HtmlFormat ? mainFrame->toHtml() :
"bug");
break;
}
default: {
QImage image(mPage->viewportSize(), QImage::Format_ARGB32);
painter.begin(&image);
mainFrame->render(&painter);
painter.end();
// TODO: add quality
image.save(mOutput, format);
}
};
}
void
CaptHelp(void) {
printf("%s",
" -----------------------------------------------------------------------------\n"
" Usage: CutyCapt --url=http://www.example.org/ --out=localfile.png \n"
" -----------------------------------------------------------------------------\n"
" --help Print this help page and exit \n"
" --url=<url> The URL to capture (http:...|file:...|...) \n"
" --out=<path> The target file (.png|pdf|ps|svg|jpeg|...) \n"
" --out-format=<f> Like extension in --out, overrides heuristic \n"
// " --out-quality=<int> Output format quality from 1 to 100 \n"
" --min-width=<int> Minimal width for the image (default: 800) \n"
" --min-height=<int> Minimal height for the image (default: 600) \n"
" --max-wait=<ms> Don't wait more than (default: 90000, inf: 0)\n"
" --delay=<ms> After successful load, wait (default: 0) \n"
// " --user-styles=<url> Location of user style sheet (deprecated) \n"
" --user-style-path=<path> Location of user style sheet file, if any \n"
" --user-style-string=<css> User style rules specified as text \n"
" --header=<name>:<value> request header; repeatable; some can't be set\n"
" --method=<get|post|put> Specifies the request method (default: get) \n"
" --body-string=<string> Unencoded request body (default: none) \n"
" --body-base64=<base64> Base64-encoded request body (default: none) \n"
" --app-name=<name> appName used in User-Agent; default is none \n"
" --app-version=<version> appVers used in User-Agent; default is none \n"
" --user-agent=<string> Override the User-Agent header Qt would set \n"
" --javascript=<on|off> JavaScript execution (default: on) \n"
" --java=<on|off> Java execution (default: unknown) \n"
" --plugins=<on|off> Plugin execution (default: unknown) \n"
" --private-browsing=<on|off> Private browsing (default: unknown) \n"
" --auto-load-images=<on|off> Automatic image loading (default: on) \n"
" --js-can-open-windows=<on|off> Script can open windows? (default: unknown) \n"
" --js-can-access-clipboard=<on|off> Script clipboard privs (default: unknown)\n"
#if QT_VERSION >= 0x040500
" --print-backgrounds=<on|off> Backgrounds in PDF/PS output (default: off) \n"
" --zoom-factor=<float> Page zoom factor (default: no zooming) \n"
" --zoom-text-only=<on|off> Whether to zoom only the text (default: off) \n"
" --http-proxy=<url> Address for HTTP proxy server (default: none)\n"
#endif
#if CUTYCAPT_SCRIPT
" --inject-script=<path> JavaScript that will be injected into pages \n"
" --script-object=<string> Property to hold state for injected script \n"
" --expect-alert=<string> Try waiting for alert(string) before capture \n"
" --debug-print-alerts Prints out alert(...) strings for debugging. \n"
#endif
" -----------------------------------------------------------------------------\n"
" <f> is svg,ps,pdf,itext,html,rtree,png,jpeg,mng,tiff,gif,bmp,ppm,xbm,xpm \n"
" -----------------------------------------------------------------------------\n"
#if CUTYCAPT_SCRIPT
" The `inject-script` option can be used to inject script code into loaded web \n"
" pages. The code is called whenever the `javaScriptWindowObjectCleared` signal\n"
" is received. When `script-object` is set, an object under the specified name \n"
" will be available to the script to maintain state across page loads. When the\n"
" `expect-alert` option is specified, the shot will be taken when a script in- \n"
" vokes alert(string) with the string if that happens before `max-wait`. These \n"
" options effectively allow you to remote control the browser and the web page.\n"
" This an experimental and easily abused and misused feature. Use with caution.\n"
" -----------------------------------------------------------------------------\n"
#endif
" http://cutycapt.sf.net - (c) 2003-2010 Bjoern Hoehrmann - bjoern@hoehrmann.de\n"
"");
}
int
main(int argc, char *argv[]) {
int argHelp = 0;
int argDelay = 0;
int argSilent = 0;
int argMinWidth = 800;
int argMinHeight = 600;
int argMaxWait = 90000;
int argVerbosity = 0;
const char* argUrl = NULL;
const char* argUserStyle = NULL;
const char* argUserStylePath = NULL;
const char* argUserStyleString = NULL;
const char* argIconDbPath = NULL;
const char* argInjectScript = NULL;
const char* argScriptObject = NULL;
QString argOut;
CutyCapt::OutputFormat format = CutyCapt::OtherFormat;
QApplication app(argc, argv, true);
CutyPage page;
QNetworkAccessManager::Operation method =
QNetworkAccessManager::GetOperation;
QByteArray body;
QNetworkRequest req;
QNetworkAccessManager manager;
// Parse command line parameters
for (int ax = 1; ax < argc; ++ax) {
size_t nlen;
const char* s = argv[ax];
const char* value;
// boolean options
if (strcmp("--silent", s) == 0) {
argSilent = 1;
continue;
} else if (strcmp("--help", s) == 0) {
argHelp = 1;
break;
} else if (strcmp("--verbose", s) == 0) {
argVerbosity++;
continue;
#if CUTYCAPT_SCRIPT
} else if (strcmp("--debug-print-alerts", s) == 0) {
page.setPrintAlerts(true);
continue;
#endif
}
value = strchr(s, '=');
if (value == NULL) {
// TODO: error
argHelp = 1;
break;
}
nlen = value++ - s;
// --name=value options
if (strncmp("--url", s, nlen) == 0) {
argUrl = value;
} else if (strncmp("--min-width", s, nlen) == 0) {
// TODO: add error checking here?
argMinWidth = (unsigned int)atoi(value);
} else if (strncmp("--min-height", s, nlen) == 0) {
// TODO: add error checking here?
argMinHeight = (unsigned int)atoi(value);
} else if (strncmp("--delay", s, nlen) == 0) {
// TODO: see above
argDelay = (unsigned int)atoi(value);
} else if (strncmp("--max-wait", s, nlen) == 0) {
// TODO: see above
argMaxWait = (unsigned int)atoi(value);
} else if (strncmp("--out", s, nlen) == 0) {
argOut = value;
if (format == CutyCapt::OtherFormat)
for (int ix = 0; CutyExtMap[ix].id != CutyCapt::OtherFormat; ++ix)
if (argOut.endsWith(CutyExtMap[ix].extension))
format = CutyExtMap[ix].id; //, break;
} else if (strncmp("--user-styles", s, nlen) == 0) {
// This option is provided for backwards-compatibility only
argUserStyle = value;
} else if (strncmp("--user-style-path", s, nlen) == 0) {
argUserStylePath = value;
} else if (strncmp("--user-style-string", s, nlen) == 0) {
argUserStyleString = value;
} else if (strncmp("--icon-database-path", s, nlen) == 0) {
argIconDbPath = value;
} else if (strncmp("--auto-load-images", s, nlen) == 0) {
page.setAttribute(QWebSettings::AutoLoadImages, value);
} else if (strncmp("--javascript", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptEnabled, value);
} else if (strncmp("--java", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavaEnabled, value);
} else if (strncmp("--plugins", s, nlen) == 0) {
page.setAttribute(QWebSettings::PluginsEnabled, value);
} else if (strncmp("--private-browsing", s, nlen) == 0) {
page.setAttribute(QWebSettings::PrivateBrowsingEnabled, value);
} else if (strncmp("--js-can-open-windows", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptCanOpenWindows, value);
} else if (strncmp("--js-can-access-clipboard", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptCanAccessClipboard, value);
} else if (strncmp("--developer-extras", s, nlen) == 0) {
page.setAttribute(QWebSettings::DeveloperExtrasEnabled, value);
} else if (strncmp("--links-included-in-focus-chain", s, nlen) == 0) {
page.setAttribute(QWebSettings::LinksIncludedInFocusChain, value);
#if QT_VERSION >= 0x040500
} else if (strncmp("--print-backgrounds", s, nlen) == 0) {
page.setAttribute(QWebSettings::PrintElementBackgrounds, value);
} else if (strncmp("--zoom-factor", s, nlen) == 0) {
page.mainFrame()->setZoomFactor(QString(value).toFloat());
} else if (strncmp("--zoom-text-only", s, nlen) == 0) {
page.setAttribute(QWebSettings::ZoomTextOnly, value);
} else if (strncmp("--http-proxy", s, nlen) == 0) {
QUrl p = QUrl::fromEncoded(value);
QNetworkProxy proxy = QNetworkProxy(QNetworkProxy::HttpProxy,
p.host(), p.port(80), p.userName(), p.password());
manager.setProxy(proxy);
page.setNetworkAccessManager(&manager);
#endif
#if CUTYCAPT_SCRIPT
} else if (strncmp("--inject-script", s, nlen) == 0) {
argInjectScript = value;
} else if (strncmp("--script-object", s, nlen) == 0) {
argScriptObject = value;
} else if (strncmp("--expect-alert", s, nlen) == 0) {
page.setAlertString(value);
#endif
} else if (strncmp("--app-name", s, nlen) == 0) {
app.setApplicationName(value);
} else if (strncmp("--app-version", s, nlen) == 0) {
app.setApplicationVersion(value);
} else if (strncmp("--body-base64", s, nlen) == 0) {
body = QByteArray::fromBase64(value);
} else if (strncmp("--body-string", s, nlen) == 0) {
body = QByteArray(value);
} else if (strncmp("--user-agent", s, nlen) == 0) {
page.setUserAgent(value);
} else if (strncmp("--out-format", s, nlen) == 0) {
for (int ix = 0; CutyExtMap[ix].id != CutyCapt::OtherFormat; ++ix)
if (strcmp(value, CutyExtMap[ix].identifier) == 0)
format = CutyExtMap[ix].id; //, break;
if (format == CutyCapt::OtherFormat) {
// TODO: error
argHelp = 1;
break;
}
} else if (strncmp("--header", s, nlen) == 0) {
const char* hv = strchr(value, ':');
if (hv == NULL) {
// TODO: error
argHelp = 1;
break;
}
req.setRawHeader(QByteArray(value, hv - value), hv + 1);
} else if (strncmp("--method", s, nlen) == 0) {
if (strcmp("value", "get") == 0)
method = QNetworkAccessManager::GetOperation;
else if (strcmp("value", "put") == 0)
method = QNetworkAccessManager::PutOperation;
else if (strcmp("value", "post") == 0)
method = QNetworkAccessManager::PostOperation;
else if (strcmp("value", "head") == 0)
method = QNetworkAccessManager::HeadOperation;
else
(void)0; // TODO: ...
} else {
// TODO: error
argHelp = 1;
}
}
if (argUrl == NULL || argOut == NULL || argHelp) {
CaptHelp();
return EXIT_FAILURE;
}
// This used to use QUrl(argUrl) but that escapes %hh sequences
// even though it should not, as URLs can assumed to be escaped.
req.setUrl( QUrl::fromEncoded(argUrl) );
QString scriptProp(argScriptObject);
QString scriptCode;
if (argInjectScript) {
QFile file(argInjectScript);
if (file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream.setAutoDetectUnicode(true);
scriptCode = stream.readAll();
file.close();
}
}
CutyCapt main(&page, argOut, argDelay, format, scriptProp, scriptCode);
app.connect(&page,
SIGNAL(loadFinished(bool)),
&main,
SLOT(DocumentComplete(bool)));
app.connect(page.mainFrame(),
SIGNAL(initialLayoutCompleted()),
&main,
SLOT(InitialLayoutCompleted()));
if (argMaxWait > 0) {
// TODO: Should this also register one for the application?
QTimer::singleShot(argMaxWait, &main, SLOT(Timeout()));
}
if (argUserStyle != NULL)
// TODO: does this need any syntax checking?
page.settings()->setUserStyleSheetUrl( QUrl::fromEncoded(argUserStyle) );
if (argUserStylePath != NULL) {
page.settings()->setUserStyleSheetUrl( QUrl::fromLocalFile(argUserStylePath) );
}
if (argUserStyleString != NULL) {
QUrl data("data:text/css;charset=utf-8;base64," +
QByteArray(argUserStyleString).toBase64());
page.settings()->setUserStyleSheetUrl( data );
}
if (argIconDbPath != NULL)
// TODO: does this need any syntax checking?
page.settings()->setIconDatabasePath(argUserStyle);
// The documentation does not say, but it seems the mainFrame
// will never change, so we can set this here. Otherwise we'd
// have to set this in snapshot and trigger an update, which
// is not currently possible (Qt 4.4.0) as far as I can tell.
page.mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
page.mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
page.setViewportSize( QSize(argMinWidth, argMinHeight) );
#if CUTYCAPT_SCRIPT
// javaScriptWindowObjectCleared does not get called on the
// initial load unless some JavaScript has been executed.
page.mainFrame()->evaluateJavaScript(QString(""));
app.connect(page.mainFrame(),
SIGNAL(javaScriptWindowObjectCleared()),
&main,
SLOT(JavaScriptWindowObjectCleared()));
#endif
if (!body.isNull())
page.mainFrame()->load(req, method, body);
else
page.mainFrame()->load(req, method);
return app.exec();
}
-66
View File
@@ -1,66 +0,0 @@
#include <QtWebKit>
class CutyCapt;
class CutyPage : public QWebPage {
Q_OBJECT
public:
void setAttribute(QWebSettings::WebAttribute option, const QString& value);
void setUserAgent(const QString& userAgent);
void setAlertString(const QString& alertString);
void setPrintAlerts(bool printAlerts);
void setCutyCapt(CutyCapt* cutyCapt);
QString getAlertString();
protected:
QString chooseFile(QWebFrame *frame, const QString& suggestedFile);
void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID);
bool javaScriptPrompt(QWebFrame* frame, const QString& msg, const QString& defaultValue, QString* result);
void javaScriptAlert(QWebFrame* frame, const QString& msg);
bool javaScriptConfirm(QWebFrame* frame, const QString& msg);
QString userAgentForUrl(const QUrl& url) const;
QString mUserAgent;
QString mAlertString;
bool mPrintAlerts;
CutyCapt* mCutyCapt;
};
class CutyCapt : public QObject {
Q_OBJECT
public:
// TODO: This should really be elsewhere and be named differently
enum OutputFormat { SvgFormat, PdfFormat, PsFormat, InnerTextFormat, HtmlFormat,
RenderTreeFormat, PngFormat, JpegFormat, MngFormat, TiffFormat, GifFormat,
BmpFormat, PpmFormat, XbmFormat, XpmFormat, OtherFormat };
CutyCapt(CutyPage* page,
const QString& output,
int delay,
OutputFormat format,
const QString& scriptProp,
const QString& scriptCode);
private slots:
void DocumentComplete(bool ok);
void InitialLayoutCompleted();
void JavaScriptWindowObjectCleared();
void Timeout();
void Delayed();
private:
void TryDelayedRender();
void saveSnapshot();
bool mSawInitialLayout;
bool mSawDocumentComplete;
protected:
QString mOutput;
int mDelay;
CutyPage* mPage;
OutputFormat mFormat;
QObject* mScriptObj;
QString mScriptProp;
QString mScriptCode;
};
Binary file not shown.
-10
View File
@@ -1,10 +0,0 @@
QT += webkit svg network
SOURCES = CutyCapt.cpp
HEADERS = CutyCapt.hpp
CONFIG += qt console
contains(CONFIG, static): {
QTPLUGIN += qjpeg qgif qsvg qmng qico qtiff
DEFINES += STATIC_PLUGINS
}
-228
View File
@@ -1,228 +0,0 @@
#############################################################################
# Makefile for building: CutyCapt
# Generated by qmake (2.01a) (Qt 4.8.1) on: Thu Aug 30 14:02:45 2012
# Project: CutyCapt.pro
# Template: app
# Command: /usr/bin/qmake -o Makefile CutyCapt.pro
#############################################################################
####### Compiler, tools and options
CC = gcc
CXX = g++
DEFINES = -DQT_WEBKIT -DQT_NO_DEBUG -DQT_WEBKIT_LIB -DQT_SVG_LIB -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_SHARED
CFLAGS = -pipe -O2 -D_REENTRANT -Wall -W $(DEFINES)
CXXFLAGS = -pipe -O2 -D_REENTRANT -Wall -W $(DEFINES)
INCPATH = -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtNetwork -I/usr/include/qt4/QtGui -I/usr/include/qt4/QtSvg -I/usr/include/qt4/QtWebKit -I/usr/include/qt4 -I.
LINK = g++
LFLAGS = -Wl,-O1
LIBS = $(SUBLIBS) -L/usr/lib/i386-linux-gnu -lQtWebKit -lQtSvg -lQtGui -lQtNetwork -lQtCore -lpthread
AR = ar cqs
RANLIB =
QMAKE = /usr/bin/qmake
TAR = tar -cf
COMPRESS = gzip -9f
COPY = cp -f
SED = sed
COPY_FILE = $(COPY)
COPY_DIR = $(COPY) -r
STRIP = strip
INSTALL_FILE = install -m 644 -p
INSTALL_DIR = $(COPY_DIR)
INSTALL_PROGRAM = install -m 755 -p
DEL_FILE = rm -f
SYMLINK = ln -f -s
DEL_DIR = rmdir
MOVE = mv -f
CHK_DIR_EXISTS= test -d
MKDIR = mkdir -p
####### Output directory
OBJECTS_DIR = ./
####### Files
SOURCES = CutyCapt.cpp moc_CutyCapt.cpp
OBJECTS = CutyCapt.o \
moc_CutyCapt.o
DIST = /usr/share/qt4/mkspecs/common/unix.conf \
/usr/share/qt4/mkspecs/common/linux.conf \
/usr/share/qt4/mkspecs/common/gcc-base.conf \
/usr/share/qt4/mkspecs/common/gcc-base-unix.conf \
/usr/share/qt4/mkspecs/common/g++-base.conf \
/usr/share/qt4/mkspecs/common/g++-unix.conf \
/usr/share/qt4/mkspecs/qconfig.pri \
/usr/share/qt4/mkspecs/modules/qt_webkit_version.pri \
/usr/share/qt4/mkspecs/features/qt_functions.prf \
/usr/share/qt4/mkspecs/features/qt_config.prf \
/usr/share/qt4/mkspecs/features/exclusive_builds.prf \
/usr/share/qt4/mkspecs/features/default_pre.prf \
/usr/share/qt4/mkspecs/features/release.prf \
/usr/share/qt4/mkspecs/features/default_post.prf \
/usr/share/qt4/mkspecs/features/qt.prf \
/usr/share/qt4/mkspecs/features/unix/thread.prf \
/usr/share/qt4/mkspecs/features/moc.prf \
/usr/share/qt4/mkspecs/features/unix/gdb_dwarf_index.prf \
/usr/share/qt4/mkspecs/features/warn_on.prf \
/usr/share/qt4/mkspecs/features/resources.prf \
/usr/share/qt4/mkspecs/features/uic.prf \
/usr/share/qt4/mkspecs/features/yacc.prf \
/usr/share/qt4/mkspecs/features/lex.prf \
/usr/share/qt4/mkspecs/features/include_source_dir.prf \
CutyCapt.pro
QMAKE_TARGET = CutyCapt
DESTDIR =
TARGET = CutyCapt
first: all
####### Implicit rules
.SUFFIXES: .o .c .cpp .cc .cxx .C
.cpp.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.cc.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.cxx.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.C.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<"
.c.o:
$(CC) -c $(CFLAGS) $(INCPATH) -o "$@" "$<"
####### Build rules
all: Makefile $(TARGET)
$(TARGET): $(OBJECTS)
$(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJCOMP) $(LIBS)
Makefile: CutyCapt.pro /usr/share/qt4/mkspecs/linux-g++/qmake.conf /usr/share/qt4/mkspecs/common/unix.conf \
/usr/share/qt4/mkspecs/common/linux.conf \
/usr/share/qt4/mkspecs/common/gcc-base.conf \
/usr/share/qt4/mkspecs/common/gcc-base-unix.conf \
/usr/share/qt4/mkspecs/common/g++-base.conf \
/usr/share/qt4/mkspecs/common/g++-unix.conf \
/usr/share/qt4/mkspecs/qconfig.pri \
/usr/share/qt4/mkspecs/modules/qt_webkit_version.pri \
/usr/share/qt4/mkspecs/features/qt_functions.prf \
/usr/share/qt4/mkspecs/features/qt_config.prf \
/usr/share/qt4/mkspecs/features/exclusive_builds.prf \
/usr/share/qt4/mkspecs/features/default_pre.prf \
/usr/share/qt4/mkspecs/features/release.prf \
/usr/share/qt4/mkspecs/features/default_post.prf \
/usr/share/qt4/mkspecs/features/qt.prf \
/usr/share/qt4/mkspecs/features/unix/thread.prf \
/usr/share/qt4/mkspecs/features/moc.prf \
/usr/share/qt4/mkspecs/features/unix/gdb_dwarf_index.prf \
/usr/share/qt4/mkspecs/features/warn_on.prf \
/usr/share/qt4/mkspecs/features/resources.prf \
/usr/share/qt4/mkspecs/features/uic.prf \
/usr/share/qt4/mkspecs/features/yacc.prf \
/usr/share/qt4/mkspecs/features/lex.prf \
/usr/share/qt4/mkspecs/features/include_source_dir.prf \
/usr/lib/i386-linux-gnu/libQtWebKit.prl \
/usr/lib/i386-linux-gnu/libQtSvg.prl \
/usr/lib/i386-linux-gnu/libQtGui.prl \
/usr/lib/i386-linux-gnu/libQtNetwork.prl \
/usr/lib/i386-linux-gnu/libQtCore.prl
$(QMAKE) -o Makefile CutyCapt.pro
/usr/share/qt4/mkspecs/common/unix.conf:
/usr/share/qt4/mkspecs/common/linux.conf:
/usr/share/qt4/mkspecs/common/gcc-base.conf:
/usr/share/qt4/mkspecs/common/gcc-base-unix.conf:
/usr/share/qt4/mkspecs/common/g++-base.conf:
/usr/share/qt4/mkspecs/common/g++-unix.conf:
/usr/share/qt4/mkspecs/qconfig.pri:
/usr/share/qt4/mkspecs/modules/qt_webkit_version.pri:
/usr/share/qt4/mkspecs/features/qt_functions.prf:
/usr/share/qt4/mkspecs/features/qt_config.prf:
/usr/share/qt4/mkspecs/features/exclusive_builds.prf:
/usr/share/qt4/mkspecs/features/default_pre.prf:
/usr/share/qt4/mkspecs/features/release.prf:
/usr/share/qt4/mkspecs/features/default_post.prf:
/usr/share/qt4/mkspecs/features/qt.prf:
/usr/share/qt4/mkspecs/features/unix/thread.prf:
/usr/share/qt4/mkspecs/features/moc.prf:
/usr/share/qt4/mkspecs/features/unix/gdb_dwarf_index.prf:
/usr/share/qt4/mkspecs/features/warn_on.prf:
/usr/share/qt4/mkspecs/features/resources.prf:
/usr/share/qt4/mkspecs/features/uic.prf:
/usr/share/qt4/mkspecs/features/yacc.prf:
/usr/share/qt4/mkspecs/features/lex.prf:
/usr/share/qt4/mkspecs/features/include_source_dir.prf:
/usr/lib/i386-linux-gnu/libQtWebKit.prl:
/usr/lib/i386-linux-gnu/libQtSvg.prl:
/usr/lib/i386-linux-gnu/libQtGui.prl:
/usr/lib/i386-linux-gnu/libQtNetwork.prl:
/usr/lib/i386-linux-gnu/libQtCore.prl:
qmake: FORCE
@$(QMAKE) -o Makefile CutyCapt.pro
dist:
@$(CHK_DIR_EXISTS) .tmp/CutyCapt1.0.0 || $(MKDIR) .tmp/CutyCapt1.0.0
$(COPY_FILE) --parents $(SOURCES) $(DIST) .tmp/CutyCapt1.0.0/ && $(COPY_FILE) --parents CutyCapt.hpp .tmp/CutyCapt1.0.0/ && $(COPY_FILE) --parents CutyCapt.cpp .tmp/CutyCapt1.0.0/ && (cd `dirname .tmp/CutyCapt1.0.0` && $(TAR) CutyCapt1.0.0.tar CutyCapt1.0.0 && $(COMPRESS) CutyCapt1.0.0.tar) && $(MOVE) `dirname .tmp/CutyCapt1.0.0`/CutyCapt1.0.0.tar.gz . && $(DEL_FILE) -r .tmp/CutyCapt1.0.0
clean:compiler_clean
-$(DEL_FILE) $(OBJECTS)
-$(DEL_FILE) *~ core *.core
####### Sub-libraries
distclean: clean
-$(DEL_FILE) $(TARGET)
-$(DEL_FILE) Makefile
check: first
mocclean: compiler_moc_header_clean compiler_moc_source_clean
mocables: compiler_moc_header_make_all compiler_moc_source_make_all
compiler_moc_header_make_all: moc_CutyCapt.cpp
compiler_moc_header_clean:
-$(DEL_FILE) moc_CutyCapt.cpp
moc_CutyCapt.cpp: CutyCapt.hpp
/usr/bin/moc-qt4 $(DEFINES) $(INCPATH) CutyCapt.hpp -o moc_CutyCapt.cpp
compiler_rcc_make_all:
compiler_rcc_clean:
compiler_image_collection_make_all: qmake_image_collection.cpp
compiler_image_collection_clean:
-$(DEL_FILE) qmake_image_collection.cpp
compiler_moc_source_make_all:
compiler_moc_source_clean:
compiler_uic_make_all:
compiler_uic_clean:
compiler_yacc_decl_make_all:
compiler_yacc_decl_clean:
compiler_yacc_impl_make_all:
compiler_yacc_impl_clean:
compiler_lex_make_all:
compiler_lex_clean:
compiler_clean: compiler_moc_header_clean
####### Compile
CutyCapt.o: CutyCapt.cpp CutyCapt.hpp
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o CutyCapt.o CutyCapt.cpp
moc_CutyCapt.o: moc_CutyCapt.cpp
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o moc_CutyCapt.o moc_CutyCapt.cpp
####### Install
install: FORCE
uninstall: FORCE
FORCE:
View File
-165
View File
@@ -1,165 +0,0 @@
/****************************************************************************
** Meta object code from reading C++ file 'CutyCapt.hpp'
**
** Created: Thu Aug 30 14:03:03 2012
** by: The Qt Meta Object Compiler version 63 (Qt 4.8.1)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
#include "CutyCapt.hpp"
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'CutyCapt.hpp' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 63
#error "This file was generated using the moc from 4.8.1. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif
QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_CutyPage[] = {
// content:
6, // revision
0, // classname
0, 0, // classinfo
0, 0, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
0, // signalCount
0 // eod
};
static const char qt_meta_stringdata_CutyPage[] = {
"CutyPage\0"
};
void CutyPage::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
Q_UNUSED(_o);
Q_UNUSED(_id);
Q_UNUSED(_c);
Q_UNUSED(_a);
}
const QMetaObjectExtraData CutyPage::staticMetaObjectExtraData = {
0, qt_static_metacall
};
const QMetaObject CutyPage::staticMetaObject = {
{ &QWebPage::staticMetaObject, qt_meta_stringdata_CutyPage,
qt_meta_data_CutyPage, &staticMetaObjectExtraData }
};
#ifdef Q_NO_DATA_RELOCATION
const QMetaObject &CutyPage::getStaticMetaObject() { return staticMetaObject; }
#endif //Q_NO_DATA_RELOCATION
const QMetaObject *CutyPage::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
}
void *CutyPage::qt_metacast(const char *_clname)
{
if (!_clname) return 0;
if (!strcmp(_clname, qt_meta_stringdata_CutyPage))
return static_cast<void*>(const_cast< CutyPage*>(this));
return QWebPage::qt_metacast(_clname);
}
int CutyPage::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QWebPage::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
return _id;
}
static const uint qt_meta_data_CutyCapt[] = {
// content:
6, // revision
0, // classname
0, 0, // classinfo
5, 14, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
0, // signalCount
// slots: signature, parameters, type, tag, flags
13, 10, 9, 9, 0x08,
36, 9, 9, 9, 0x08,
61, 9, 9, 9, 0x08,
93, 9, 9, 9, 0x08,
103, 9, 9, 9, 0x08,
0 // eod
};
static const char qt_meta_stringdata_CutyCapt[] = {
"CutyCapt\0\0ok\0DocumentComplete(bool)\0"
"InitialLayoutCompleted()\0"
"JavaScriptWindowObjectCleared()\0"
"Timeout()\0Delayed()\0"
};
void CutyCapt::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Q_ASSERT(staticMetaObject.cast(_o));
CutyCapt *_t = static_cast<CutyCapt *>(_o);
switch (_id) {
case 0: _t->DocumentComplete((*reinterpret_cast< bool(*)>(_a[1]))); break;
case 1: _t->InitialLayoutCompleted(); break;
case 2: _t->JavaScriptWindowObjectCleared(); break;
case 3: _t->Timeout(); break;
case 4: _t->Delayed(); break;
default: ;
}
}
}
const QMetaObjectExtraData CutyCapt::staticMetaObjectExtraData = {
0, qt_static_metacall
};
const QMetaObject CutyCapt::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_CutyCapt,
qt_meta_data_CutyCapt, &staticMetaObjectExtraData }
};
#ifdef Q_NO_DATA_RELOCATION
const QMetaObject &CutyCapt::getStaticMetaObject() { return staticMetaObject; }
#endif //Q_NO_DATA_RELOCATION
const QMetaObject *CutyCapt::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
}
void *CutyCapt::qt_metacast(const char *_clname)
{
if (!_clname) return 0;
if (!strcmp(_clname, qt_meta_stringdata_CutyCapt))
return static_cast<void*>(const_cast< CutyCapt*>(this));
return QObject::qt_metacast(_clname);
}
int CutyCapt::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
if (_id < 5)
qt_static_metacall(this, _c, _id, _a);
_id -= 5;
}
return _id;
}
QT_END_MOC_NAMESPACE
Binary file not shown.
-12
View File
@@ -1,12 +0,0 @@
HowTo Install:
% sudo apt-get install subversion libqt4-webkit libqt4-dev g++
% svn co https://cutycapt.svn.sourceforge.net/svnroot/cutycapt
% cd cutycapt/CutyCapt
% qmake
% make
HowTo Use:
% ./CutyCapt --url=http://www.example.org --out=example.png
+243
View File
@@ -0,0 +1,243 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" erzeugt aus in Inkscape gezeichneten Buchstaben (SVG) die Glyphen und die
Fontconfig für piccreate (Ersatz für extract-glyph.sh, extract-font.sh und
fonttest.sh, läuft auch unter Windows).
Die Auswahl der Zeilen folgt bewusst den früheren grep/sed Aufrufen, damit
die erzeugten Glyphen gleich bleiben.
"""
__version__ = '0.1.0'
__license__ = 'BSD'
import glob
import locale
import optparse
import os
import re
import sys
from decimal import Decimal, InvalidOperation
# Wortgrenzen wie \< und \> bei grep
WB = r'(?<!\w)(?=\w)'
WE = r'(?<=\w)(?!\w)'
LINE_IN = re.compile('<' + WB + 'line' + WE + '|x1|x2|y1|y2|/>|style')
LINE_OUT = re.compile(WB + 'text' + WE + '|id=|http:')
CIRCLE_IN = re.compile('circle|translate|' + WB + 'r' + WE)
TEXT_IN = re.compile('<' + WB + 'text|text' + WE + '>|' + WB + 'x' + WE + '=|'
+ WB + 'y' + WE + '=|tspan|style|preserve')
TEXT_OUT = re.compile(WB + 'transform-center-x' + WE + '|' + WB + 'window-|'
+ WB + 'id' + WE + '=')
CENTER_X = re.compile(r'inkscape:transform-center-x=".*"')
CENTER_Y = re.compile(r'inkscape:transform-center-y=".*"')
PATTERN = re.compile(r'>(x[0-9]+)<')
NUMBER = re.compile(r'\s*[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?')
TEST_BEGIN = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">'
TEST_END = '</svg>'
GLYPH_BEGIN = '<g transform="translate(${posx},${posy})">'
GLYPH_END = '</g>'
def split_lines(text):
''' zerlegt einen Text in Zeilen (wie grep, ohne Zeilenende) '''
lines = text.split('\n')
if lines and lines[-1] == '':
lines.pop()
return lines
def strip_center(line):
line = CENTER_X.sub('', line)
return CENTER_Y.sub('', line)
def number_key(value):
''' Sortierschlüssel wie bei "sort -g": Zeilen ohne Zahl zuerst '''
m = NUMBER.match(value)
if not m:
return (0, 0.0, value)
return (1, float(m.group(0)), value)
def coordinates(glyph_lines, axis):
''' sammelt alle x- bzw. y-Koordinaten aus Texten, Kreisen und Linien '''
values = []
if axis == 'x':
att = re.compile(WB + 'x' + WE + '=')
else:
att = re.compile('y=')
for line in glyph_lines:
if att.search(line):
values.append(re.sub(r'^\s+', '', line).replace(axis + '=', '').replace('"', ''))
# Kreise: transform="translate(x,y)"
for line in glyph_lines:
if '<g' not in line and 'transform=' in line:
value = re.sub(r'^\s+', '', line).replace('translate', '').replace('transform=', '')
value = re.sub(r'["|()]', '', value)
if axis == 'x':
value = re.sub(r',.*', '', value)
else:
value = re.sub(r'.*,', '', value)
values.append(value)
# Linien: x1/x2 bzw. y1/y2
for name in (axis + '1=', axis + '2='):
for line in glyph_lines:
if name in line:
values.append(re.sub(r'^\s+', '', line).replace(name, '').replace('"', ''))
values = sorted(set(values), key=number_key)
if not values:
return '', ''
return values[0], values[-1]
def difference(maximum, minimum):
''' maximum - minimum, formatiert wie bc '''
try:
a, b = Decimal(maximum), Decimal(minimum)
except InvalidOperation:
return ''
scale = max(-a.as_tuple().exponent, -b.as_tuple().exponent, 0)
result = (a - b).quantize(Decimal(1).scaleb(-scale))
if result == 0:
return '0'
text = format(result, 'f')
return text.replace('-0.', '-.') if text.startswith('-0.') else re.sub(r'^0\.', '.', text)
def extract_glyph(svg_path, extract=True):
''' liefert (Glyph-SVG, Fontconfig-Sektion) für einen Buchstaben '''
with open(svg_path, encoding='utf-8', newline='') as fh:
source = split_lines(fh.read())
lines = [strip_center(l) for l in source if LINE_IN.search(l) and not LINE_OUT.search(l)]
circles = [strip_center(l) for l in source if CIRCLE_IN.search(l)]
texts = [strip_center(l) for l in source if TEXT_IN.search(l) and not TEXT_OUT.search(l)]
if extract:
glyph = [GLYPH_BEGIN] + texts + circles + lines + [GLYPH_END]
else:
glyph = [TEST_BEGIN] + texts + circles + lines + [TEST_END]
xmin, xmax = coordinates(glyph, 'x')
ymin, ymax = coordinates(glyph, 'y')
# Anzahl der Platzhalter x1..xn, dann durch Templatezeichen ${x1} ersetzen
npattern = sum(1 for l in glyph if re.search(r'>x[0-9]+<', l))
glyph = [PATTERN.sub(r'>${\1}<', l) for l in glyph]
base = os.path.splitext(os.path.basename(svg_path))[0]
config = [
'[' + base + ']',
'XSTEP=' + difference(xmax, xmin),
'XMIN=' + xmin,
'XMAX=' + xmax,
'YSTEP=' + difference(ymax, ymin),
'YMIN=' + ymin,
'YMAX=' + ymax,
'NPATTERN=' + str(npattern),
'FONTNAME=' + base + '.svg',
'#',
]
return '\n'.join(glyph) + '\n', '\n'.join(config) + '\n'
def write_glyph(svg_path, out_dir, extract=True):
''' schreibt Glyph und Fontconfig-Sektion ins Ausgabeverzeichnis '''
glyph, config = extract_glyph(svg_path, extract)
base = os.path.splitext(os.path.basename(svg_path))[0]
with open(os.path.join(out_dir, base + '.svg'), 'w', encoding='utf-8', newline='\n') as fh:
fh.write(glyph)
return base, config
def font_source(name):
''' sucht das Verzeichnis mit den Buchstaben eines Fonts '''
in_dir = os.path.join(os.environ.get('PICCREATE_IN', ''), name)
if os.path.isdir(in_dir):
return in_dir
if os.path.isdir(name):
return name
raise SystemExit('"' + name + '" is no directory. Please give a directory with .svg glyphs as input')
def sorted_glyphs(source):
''' alle .svg Dateien, sortiert wie die Shell (a A ä b B ...) '''
try:
locale.setlocale(locale.LC_COLLATE, '')
except locale.Error:
pass
return sorted(glob.glob(os.path.join(source, '*.svg')), key=locale.strxfrm)
def main():
out_dir = options.outdir
if not os.path.isdir(out_dir):
print('"' + out_dir + '" does not exist. Please give the directory for the output!')
return 1
if os.path.isfile(options.infile):
# ein Buchstabe (extract-glyph.sh)
if not os.path.getsize(options.infile):
print('input file is empty')
return 1
base, config = write_glyph(options.infile, out_dir, options.extract)
with open(os.path.join(out_dir, base + '.cfg'), 'w', encoding='utf-8', newline='\n') as fh:
fh.write(config)
return 0
# alle Buchstaben eines Fonts
source = font_source(options.infile)
configs = []
for svg_path in sorted_glyphs(source):
base, config = write_glyph(svg_path, out_dir, options.extract)
print(base)
configs.append((base, config))
if options.extract:
# Font (extract-font.sh): alle Sektionen in <font>.cfg
cfg_name = os.path.basename(os.path.normpath(source)) + '.cfg'
with open(os.path.join(out_dir, cfg_name), 'w', encoding='utf-8', newline='\n') as fh:
fh.write(''.join(config for _, config in configs))
print('->', os.path.join(out_dir, cfg_name))
else:
# Testbilder (fonttest.sh): je Buchstabe eine .cfg daneben
for base, config in configs:
with open(os.path.join(out_dir, base + '.cfg'), 'w', encoding='utf-8', newline='\n') as fh:
fh.write(config)
return 0
if __name__ == '__main__':
parser = optparse.OptionParser(usage="""%prog -i <glyph.svg | font> [-e] [options]
Example usage
-------------
one glyph, test picture to check it with inkscape:
$ %prog -i ../in/ausgangsschrift/a.svg
one glyph, extract the points and text for the font:
$ %prog -i ../in/ausgangsschrift/a.svg -e
all glyphs of a font (directory in in/ or a path) including the font config:
$ %prog -i ausgangsschrift -e
test pictures of all glyphs of a font:
$ %prog -i ausgangsschrift
""", version='%prog ' + __version__)
parser.add_option("-i", "--infile", dest="infile",
help="svg file of one glyph or directory with the glyphs of a font")
parser.add_option("-e", "--extract", dest="extract", action='store_true', default=False,
help="extract the glyphs for the font (and write <font>.cfg) "
"instead of test pictures")
parser.add_option("-o", "--outdir", dest="outdir",
default=os.environ.get('PICCREATE_OUT', '.'),
help="directory where the files will be written. default is $PICCREATE_OUT")
(options, args) = parser.parse_args()
if not options.infile:
parser.error('please give a glyph or a font with -i')
sys.exit(main())
# vim: set tw=80 ft=python ts=4 sw=4:
+41 -39
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__version__ = '0.0.1' __version__ = '0.0.1'
@@ -9,9 +9,9 @@ __license__ = 'BSD'
import optparse import optparse
import os import os
import re import re
import ConfigParser import configparser
import pathlib
from utils import CSVImport, CompleteEnding, get_translation from utils import CSVImport, CompleteEnding, get_translation
from django.utils.encoding import smart_str, smart_unicode
from string import Template from string import Template
class PicError(Exception): class PicError(Exception):
@@ -40,14 +40,15 @@ def create_picture_frame( config ):
try: try:
params['background'] = config.get('globals', 'background') params['background'] = config.get('globals', 'background')
imagetpl = ''' <image width="${width}" height="${height}" imagetpl = ''' <image width="${width}" height="${height}"
xlink:href="file://${background}"> xlink:href="${background}">
</image> </image>
''' '''
background = config.get('globals', 'background') background = config.get('globals', 'background')
font_path = os.path.expandvars(os.path.join('$PICCREATE_BACKGROUNDS', background )) font_path = os.path.expandvars(os.path.join('$PICCREATE_BACKGROUNDS', background ))
params['background'] = os.path.normpath(font_path) # als file:// URL, damit es auch mit Windows-Pfaden (C:\...) klappt
params['background'] = pathlib.Path(os.path.abspath(font_path)).as_uri()
svgbegin = svgbegin + Template( imagetpl ).substitute(params) svgbegin = svgbegin + Template( imagetpl ).substitute(params)
except ConfigParser.NoOptionError: except configparser.NoOptionError:
# dann eben kein Hintergrundbild # dann eben kein Hintergrundbild
pass pass
svgend = '</svg>' svgend = '</svg>'
@@ -60,18 +61,18 @@ def write_font_line( content, font, params ):
wordsvg = w.GetSVG() wordsvg = w.GetSVG()
# falls das Wort nun skaliert werden soll # falls das Wort nun skaliert werden soll
if params.has_key('scale'): if 'scale' in params:
params['xscale'] = params['scale'] params['xscale'] = params['scale']
params['yscale'] = params['scale'] params['yscale'] = params['scale']
if not params.has_key('xscale'): if 'xscale' not in params:
params['xscale'] = 1.0 params['xscale'] = 1.0
if not params.has_key('yscale'): if 'yscale' not in params:
params['yscale'] = 1.0 params['yscale'] = 1.0
pic_width = float(params['width']) pic_width = float(params['width'])
pic_height = float(params['height']) pic_height = float(params['height'])
if params.has_key('autoscale'): if 'autoscale' in params:
wordlength = w.calc_length() wordlength = w.calc_length()
wordheight = w.calc_height() wordheight = w.calc_height()
#print wordlength, wordheight #print wordlength, wordheight
@@ -87,7 +88,7 @@ def write_font_line( content, font, params ):
scale_faktor = 1.0 scale_faktor = 1.0
# berechne immer den richtigen Startpunkt, je nach Bündigkeit # berechne immer den richtigen Startpunkt, je nach Bündigkeit
if params.has_key('align'): if 'align' in params:
xstart = float(params['x']) xstart = float(params['x'])
if params['align'] == 'left': if params['align'] == 'left':
x = xstart x = xstart
@@ -101,7 +102,7 @@ def write_font_line( content, font, params ):
else: else:
params['xstart'] = params['x'] params['xstart'] = params['x']
if params.has_key('yalign'): if 'yalign' in params:
ystart = float(params['y']) ystart = float(params['y'])
if params['yalign'] == 'top': if params['yalign'] == 'top':
y = 0.0 y = 0.0
@@ -109,6 +110,7 @@ def write_font_line( content, font, params ):
word_height = w.calc_height() word_height = w.calc_height()
y = ystart * scale_faktor + (pic_height - word_height * scale_faktor)/2.0 y = ystart * scale_faktor + (pic_height - word_height * scale_faktor)/2.0
elif params['yalign'] == 'bottom': elif params['yalign'] == 'bottom':
word_height = w.calc_height()
y = ystart * scale_faktor + pic_height - word_height * scale_faktor y = ystart * scale_faktor + pic_height - word_height * scale_faktor
else: else:
y = ystart y = ystart
@@ -204,9 +206,9 @@ class Font:
self.name = fontname self.name = fontname
self.path = font_path self.path = font_path
self.glyphs = dict() self.glyphs = dict()
fontconfig = ConfigParser.ConfigParser() fontconfig = configparser.ConfigParser(interpolation=None, strict=False)
config_path = os.path.join(font_path, fontname+'.cfg') config_path = os.path.join(font_path, fontname+'.cfg')
fontconfig.read(config_path) fontconfig.read(config_path, encoding='utf-8')
fontconfig.optionxform = str fontconfig.optionxform = str
# lies alle Zeichen des Fonts ein # lies alle Zeichen des Fonts ein
@@ -214,8 +216,8 @@ class Font:
# hol die Fontattribute wie Ausdehnung usw. # hol die Fontattribute wie Ausdehnung usw.
content = dict(fontconfig.items(letter)) content = dict(fontconfig.items(letter))
# lade das svg template dazu ein # lade das svg template dazu ein
glyph_path = os.path.join( font_path, smart_unicode(content['fontname'])) glyph_path = os.path.join( font_path, content['fontname'])
f = open(glyph_path, 'r') f = open(glyph_path, 'r', encoding='utf-8')
svgstr = f.read() svgstr = f.read()
f.close() f.close()
# und bau das Zeichen auf # und bau das Zeichen auf
@@ -224,7 +226,7 @@ class Font:
def __repr__(self): def __repr__(self):
return repr(self.glyphs) return repr(self.glyphs)
def GlyphGet(self, glyphname): def GlyphGet(self, glyphname):
return self.glyphs[smart_str(glyphname)] return self.glyphs[glyphname]
def init_fonts( config ): def init_fonts( config ):
''' lädt alle benötigten Fonts für den Auftrag in den Speicher ''' lädt alle benötigten Fonts für den Auftrag in den Speicher
@@ -287,13 +289,13 @@ def create_picture(config, fontinfos, data, pic_path):
lineparams = dict(config.items(linename)) lineparams = dict(config.items(linename))
# falls nix angegeben, dann aus hol defaults aus globalen daten # falls nix angegeben, dann aus hol defaults aus globalen daten
if not lineparams.has_key('width'): if 'width' not in lineparams:
lineparams['width'] = config.get('globals', 'width') lineparams['width'] = config.get('globals', 'width')
if not lineparams.has_key('height'): if 'height' not in lineparams:
lineparams['height'] = config.get('globals', 'height') lineparams['height'] = config.get('globals', 'height')
if not lineparams.has_key('x'): if 'x' not in lineparams:
lineparams['x'] = 0.0 lineparams['x'] = 0.0
if not lineparams.has_key('y'): if 'y' not in lineparams:
lineparams['y'] = 0.0 lineparams['y'] = 0.0
picstr = write_font_line( content, font, lineparams ) picstr = write_font_line( content, font, lineparams )
@@ -302,14 +304,14 @@ def create_picture(config, fontinfos, data, pic_path):
randstr = write_random_font_line( content, font, lineparams) randstr = write_random_font_line( content, font, lineparams)
if randstr: svgstr = svgstr + randstr if randstr: svgstr = svgstr + randstr
svgstr = svgstr + picend svgstr = svgstr + picend
if options.dry: if options.dry:
print svgstr print(svgstr)
else: else:
f = open(pic_path, 'w') f = open(pic_path, 'w', encoding='utf-8')
f.write( svgstr ) f.write( svgstr )
f.close() f.close()
def main( config ): def main( config ):
@@ -326,17 +328,17 @@ def main( config ):
config_path = os.path.expandvars(os.path.join('$PICCREATE_CFG', CompleteEnding( config, 'cfg'))) config_path = os.path.expandvars(os.path.join('$PICCREATE_CFG', CompleteEnding( config, 'cfg')))
in_path = os.path.expandvars(os.path.join('$PICCREATE_IN', CompleteEnding( infile, 'csv'))) in_path = os.path.expandvars(os.path.join('$PICCREATE_IN', CompleteEnding( infile, 'csv')))
if not os.path.exists(config_path): if not os.path.exists(config_path):
print '\"'+ config_path +'\" does not exist. Please give the name of a config file!' print('\"'+ config_path +'\" does not exist. Please give the name of a config file!')
return None return None
if not os.path.exists(in_path): if not os.path.exists(in_path):
print '\"'+ in_path +'\" does not exist. Please give the name to a input file!' print('\"'+ in_path +'\" does not exist. Please give the name to a input file!')
return None return None
if not os.path.exists(out_path): if not os.path.exists(out_path):
print '\"'+ out_path +'\" does not exist. Please give the path to the directory for the pictures!' print('\"'+ out_path +'\" does not exist. Please give the path to the directory for the pictures!')
return None return None
config = ConfigParser.RawConfigParser() config = configparser.RawConfigParser(strict=False)
config.read(config_path) config.read(config_path, encoding='utf-8')
Trans = get_translation(config_path, 'translation') Trans = get_translation(config_path, 'translation')
Rows = CSVImport(in_path, translate=Trans) Rows = CSVImport(in_path, translate=Trans)
@@ -344,7 +346,7 @@ def main( config ):
fontinfos = init_fonts( config ) fontinfos = init_fonts( config )
for row in Rows: for row in Rows:
filename = row.customerid filename = row.customerid
print filename print(filename)
content = row.__dict__ content = row.__dict__
del content['firstrow'] del content['firstrow']
del content['row'] del content['row']
@@ -368,20 +370,20 @@ Read Confg, create wakeup and scale this kind of jiffy box
""", version='%prog ' + __version__) """, version='%prog ' + __version__)
parser.add_option("-c", "--cfg", dest="config", default="test", parser.add_option("-c", "--config", "--cfg", dest="config", default="test",
help="name of the config file with the Name of the rows to be used.", help="name of the config file in cfg/ with the lines to be written",
metavar="CONFIG_FILE") metavar="CONFIG_FILE")
parser.add_option("-i", "--infile", dest="infile", parser.add_option("-i", "--infile", dest="infile",
help="csv file with input of names/content", help="csv file with input of names/content",
action='store') action='store')
parser.add_option("-s", "--startfrom", dest="startfrom", parser.add_option("-s", "--startfrom", dest="startfrom",
help="csv file with input of names/content", help="start the numbering again at every word / capital letter",
action='store_true') action='store_true')
parser.add_option("-o", "--outdir", dest="outdir", default=os.path.expandvars('$PICCREATE_OUT'), parser.add_option("-o", "--outdir", dest="outdir", default=os.path.expandvars('$PICCREATE_OUT'),
help="path to the directory where the maps will be generated", help="directory where the pictures will be written. default is $PICCREATE_OUT",
action='store') action='store')
parser.add_option("-y", "--dry", dest="dry", parser.add_option("-y", "--dry", dest="dry",
help="dont generate images. Just ask the coordinates and fill database.", action='store_true' ) help="dont write files, print the svg instead", action='store_true' )
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# A Python Library to create a Progress Bar. # A Python Library to create a Progress Bar.
# Copyright (C) 2008 BJ Dierkes <wdierkes@5dollarwhitebox.org> # Copyright (C) 2008 BJ Dierkes <wdierkes@5dollarwhitebox.org>
@@ -96,7 +96,7 @@ def bufcount(filename):
if not os.path.exists(filename): if not os.path.exists(filename):
raise Exception raise Exception
try: try:
fh = open(filename) fh = open(filename, encoding='utf-8', errors='replace')
lines = 0 lines = 0
buf_size = 1024 * 1024 buf_size = 1024 * 1024
read_f = fh.read # loop optimization read_f = fh.read # loop optimization
+43 -25
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python3.2 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__version__ = "0.0.1" __version__ = "0.0.1"
@@ -11,17 +11,23 @@ import cProfile
import optparse import optparse
import configparser import configparser
import re import re
import shutil
import subprocess import subprocess
import csv import csv
from string import Template from string import Template
from concurrent import futures from concurrent import futures
from progressbar import EasyProgressBar from progressbar import EasyProgressBar
from utils import CompleteEnding
# ImageMagick 7 heisst "magick"; unter Windows ist "convert" ein Systemprogramm
IMAGEMAGICK = shutil.which('magick') or shutil.which('convert')
def get_translation(cfg_path, sec_name): def get_translation(cfg_path, sec_name):
""" lädt die Zuordnung zwischen einem .csv Eingabefile und den Attributen""" """ lädt die Zuordnung zwischen einem .csv Eingabefile und den Attributen"""
config = configparser.RawConfigParser() config = configparser.RawConfigParser()
config.read(cfg_path) config.read(cfg_path, encoding='utf-8')
trans = dict(config.items(sec_name)) trans = dict(config.items(sec_name))
return dict([[v,k] for k,v in trans.items()]) return dict([[v,k] for k,v in trans.items()])
@@ -42,12 +48,14 @@ def convertSVG(in_path,filename,pic_config):
if options.verbose: if options.verbose:
print (filename, 'already exist!') print (filename, 'already exist!')
else: else:
background = " -background " + pic_config['background'] + " " cmdline = [IMAGEMAGICK,
resize = " -resize " + pic_config['width'] + "x" + pic_config['height'] + " " '-density', pic_config['density'],
density = " -density " + pic_config['density'] '-background', pic_config['background'],
cmd = "convert" inpath,
'-resize', pic_config['width'] + "x" + pic_config['height'],
jpgpath]
if not options.dry: if not options.dry:
subprocess.check_call(cmd + density + background + inpath + resize + jpgpath, shell = True) subprocess.check_call(cmdline)
if options.verbose: if options.verbose:
print ('start converting', filename) print ('start converting', filename)
Epb.slide_progbar() Epb.slide_progbar()
@@ -111,17 +119,18 @@ font-weight:normal;font-stretch:normal;font-family:Ubuntu;\
def main(): def main():
"""get all information from the switches an start the parallel conversion""" """get all information from the switches an start the parallel conversion"""
if not IMAGEMAGICK and not options.dry:
print('ImageMagick not found. Please install it (https://imagemagick.org).')
return None
#set the workpath and the default config #set the workpath and the default config
work_path = os.path.expandvars('$PICCREATE_OUT') work_path = os.path.expandvars(options.outdir)
if options.svgfolder:
work_path = os.path.expandvars(options.svgfolder)
if not os.path.exists(work_path): if not os.path.exists(work_path):
print('\"'+ work_path +'\" does not exist. Please give the name of the folder with the svg-files!') print('\"'+ work_path +'\" does not exist. Please give the name of the folder with the svg-files!')
return None return None
pic_config = { pic_config = {
'width' : '600', 'width' : '600',
'height' : '200', 'height' : '200',
'format' : options.format, 'format' : options.format or 'jpg',
'density' : '150', 'density' : '150',
'background' : 'white', 'background' : 'white',
'folder' : work_path, 'folder' : work_path,
@@ -144,14 +153,19 @@ def main():
filelist = getfilelist(work_path) filelist = getfilelist(work_path)
#load and override the default config if we receive an config file #load and override the default config if we receive an config file
if options.config: if options.config:
config_path = os.path.expandvars(os.path.join('$PICCREATE_CFG', options.config)) config_path = os.path.expandvars(os.path.join('$PICCREATE_CFG', CompleteEnding(options.config, 'cfg')))
if not os.path.exists(config_path): if not os.path.exists(config_path):
print('\"'+ config_path +'\" does not exist. Please give the name of a config file!') print('\"'+ config_path +'\" does not exist. Please give the name of a config file!')
return None return None
config = configparser.RawConfigParser() config = configparser.RawConfigParser()
config.read(config_path) config.read(config_path, encoding='utf-8')
# fill the config # fill the config
pic_config = dict(config.items(options.format)) if not config.has_section(options.section):
print('section [' + options.section + '] is missing in ' + config_path)
return None
pic_config = dict(config.items(options.section))
if options.format:
pic_config['format'] = options.format
if options.background: if options.background:
pic_config['background'] = options.background pic_config['background'] = options.background
elif 'background' in pic_config: elif 'background' in pic_config:
@@ -165,7 +179,7 @@ def main():
pic_config['customerid'] = origin pic_config['customerid'] = origin
#chek if all files from the csv were successfully created and in the workfolder #chek if all files from the csv were successfully created and in the workfolder
if options.infile: if options.infile:
csv_path = os.path.expandvars(os.path.join('$PICCREATE_IN', options.infile)) csv_path = os.path.expandvars(os.path.join('$PICCREATE_IN', CompleteEnding(options.infile, 'csv')))
if not os.path.exists(csv_path): if not os.path.exists(csv_path):
print('\"'+ csv_path +'\" does not exist. Please give the correct name of a csv file!') print('\"'+ csv_path +'\" does not exist. Please give the correct name of a csv file!')
return None return None
@@ -173,7 +187,7 @@ def main():
print ("you must give a config file for checking the svg files") print ("you must give a config file for checking the svg files")
return None return None
svglist = [] svglist = []
with open(csv_path, newline='') as csvfile: with open(csv_path, newline='', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile,delimiter = ';') reader = csv.DictReader(csvfile,delimiter = ';')
for row in reader: for row in reader:
if row[pic_config['customerid']] not in filelist: if row[pic_config['customerid']] not in filelist:
@@ -210,14 +224,17 @@ Read Confg, create wakeup and scale this kind of jiffy box
parser.add_option("-b", "--background", dest="background", parser.add_option("-b", "--background", dest="background",
help="set the backgroundcolor. 'none' for transparent", help="set the backgroundcolor. 'none' for transparent",
action='store') action='store')
parser.add_option("-c", "--config", dest="config", parser.add_option("-c", "--config", "--cfg", dest="config",
help="config file with all data for jobs", help="config file in cfg/ with the picture sizes",
action='store') action='store')
parser.add_option("-f", "--force", dest="force", default=False, parser.add_option("-f", "--force", dest="force", default=False,
help="create the pictures, no matter if they alreads exist", help="create the pictures, no matter if they already exist",
action='store_true') action='store_true')
parser.add_option("--format", dest="format", default='jpg', parser.add_option("--section", dest="section", default='png',
help="format of the picture", help="section in the config file. default is png",
action='store')
parser.add_option("--format", dest="format",
help="jpg or png, overrides the config",
action='store') action='store')
parser.add_option("-i", "--infile", dest="infile", parser.add_option("-i", "--infile", dest="infile",
help="csv file with input of names/content", help="csv file with input of names/content",
@@ -228,8 +245,10 @@ Read Confg, create wakeup and scale this kind of jiffy box
parser.add_option("-p", "--progress", dest="progress", parser.add_option("-p", "--progress", dest="progress",
help="print a progression bar", help="print a progression bar",
action='store_true') action='store_true')
parser.add_option("-s", "--svgfolder", dest="svgfolder", parser.add_option("-o", "--outdir", "--svgfolder", dest="outdir",
help="path to the folder with the svg-files", default=os.path.expandvars('$PICCREATE_OUT'),
help="folder with the svg files, the pictures are written there too. "
"default is $PICCREATE_OUT",
action='store') action='store')
parser.add_option("-t", "--test", dest="test", parser.add_option("-t", "--test", dest="test",
help="number of default svg-files that will be created and tested", help="number of default svg-files that will be created and tested",
@@ -238,8 +257,7 @@ Read Confg, create wakeup and scale this kind of jiffy box
help="print the status of the conversion", help="print the status of the conversion",
action='store_true') action='store_true')
parser.add_option("-y", "--dry", dest="dry", parser.add_option("-y", "--dry", dest="dry",
help="dont generate images. \ help="dont generate images, just try if the conversion would succeed",
just try if the conversion would success",
action='store_true') action='store_true')
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
+19 -11
View File
@@ -1,15 +1,23 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.utils.encoding import smart_unicode import configparser
from django.template.defaultfilters import slugify
import csv import csv
import ConfigParser import os.path
import os.path
import re import re
import unicodedata
def slugify(value):
''' wandelt einen String in einen ASCII Bezeichner um (wie Djangos slugify)
'''
value = unicodedata.normalize('NFKD', str(value))
value = value.encode('ascii', 'ignore').decode('ascii')
value = re.sub(r'[^\w\s-]', '', value).strip().lower()
return re.sub(r'[-\s]+', '-', value)
def csv_open(csvfile, dialect=None): def csv_open(csvfile, dialect=None):
fh = open(csvfile, 'rb') fh = open(csvfile, 'r', encoding='utf-8', newline='')
if dialect is None: if dialect is None:
sniffer = csv.Sniffer() sniffer = csv.Sniffer()
dialect = sniffer.sniff(fh.read(1024), delimiters=';,\t') dialect = sniffer.sniff(fh.read(1024), delimiters=';,\t')
@@ -24,7 +32,7 @@ class CSVRow(object):
self.firstrow = field_refinition self.firstrow = field_refinition
for i, field in enumerate(field_refinition): for i, field in enumerate(field_refinition):
try: try:
setattr(self, field, smart_unicode(row[i])) setattr(self, field, row[i])
except IndexError: except IndexError:
setattr(self, field, None) setattr(self, field, None)
@@ -50,7 +58,7 @@ class CSVImport(object):
field = 'column_%d' % i field = 'column_%d' % i
if translate: if translate:
if field in translate: if field in translate:
print field + ' -> '+ translate[field] print(field + ' -> ' + translate[field])
field = translate[field] field = translate[field]
field = slugify(field.strip()).replace('-', '_') field = slugify(field.strip()).replace('-', '_')
self.field_definition.append(field) self.field_definition.append(field)
@@ -71,8 +79,8 @@ class CSVImport(object):
def get_translation(cfg_path, sec_name): def get_translation(cfg_path, sec_name):
""" lädt die Zuordnung zwischen einem .csv Eingabefile und den """ lädt die Zuordnung zwischen einem .csv Eingabefile und den
Attributen """ Attributen """
config = ConfigParser.RawConfigParser() config = configparser.RawConfigParser(strict=False)
config.read(cfg_path) config.read(cfg_path, encoding='utf-8')
trans = dict(config.items(sec_name)) trans = dict(config.items(sec_name))
return dict([[v,k] for k,v in trans.items()]) return dict([[v,k] for k,v in trans.items()])
@@ -80,7 +88,7 @@ def get_translation(cfg_path, sec_name):
def CompleteEnding(path, newext): def CompleteEnding(path, newext):
''' Fügt die gegebene File Extension an den Filename/Pfad ''' Fügt die gegebene File Extension an den Filename/Pfad
''' '''
if not path: raise Csv2CouchError('no path given') if not path: raise ValueError('no path given')
path, ext = os.path.splitext(path) path, ext = os.path.splitext(path)
if not re.match(newext, ext): if not re.match(newext, ext):
path = path +'.'+newext path = path +'.'+newext
+267
View File
@@ -0,0 +1,267 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" erzeugt Vorschaubilder von Webseiten (Ersatz für CutyCapt + convert)
Eingabe ist eine Datei mit Zeilen der Form Kundennummer;URL
Für jede Zeile wird die Seite in einem Browser ohne Fenster geladen und der
sichtbare Bereich (Breite x Höhe aus der Sektion [webscan] der Konfiguration)
als <Kundennummer>.<format> gespeichert.
"""
__version__ = '0.1.0'
__license__ = 'BSD'
import asyncio
import configparser
import csv
import optparse
import os
import sys
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
VENV = os.path.join(PROJECT_DIR, '.venv')
INSTALL_HINTS = {
'ubuntu': '''Ubuntu / Debian:
sudo apt install python3-venv
python3 -m venv {venv}
{venv}/bin/pip install -r {project}/requirements.txt
sudo {venv}/bin/playwright install-deps chromium
{venv}/bin/playwright install chromium''',
'arch': '''Arch Linux:
python -m venv {venv}
{venv}/bin/pip install -r {project}/requirements.txt
then either the browser from Arch (recommended, Playwright does not
officially support Arch):
sudo pacman -S chromium
export PICCREATE_BROWSER=/usr/bin/chromium
or the Playwright browser:
{venv}/bin/playwright install chromium''',
'windows': r'''Windows (or simply run bin\setup-venv.bat):
py -m venv {venv}
{venv}\Scripts\pip install -r {project}\requirements.txt
{venv}\Scripts\playwright install chromium''',
}
BROWSER_HINTS = {
'ubuntu': '''Ubuntu / Debian:
sudo {venv}/bin/playwright install-deps chromium
{venv}/bin/playwright install chromium''',
'arch': '''Arch Linux:
sudo pacman -S chromium
export PICCREATE_BROWSER=/usr/bin/chromium
or the Playwright browser:
{venv}/bin/playwright install chromium''',
'windows': r'''Windows:
{venv}\Scripts\playwright install chromium''',
}
def platform_name():
''' liefert 'windows', 'ubuntu' oder 'arch' (aus /etc/os-release),
sonst None '''
if sys.platform == 'win32':
return 'windows'
try:
with open('/etc/os-release', encoding='utf-8') as fh:
info = dict(line.rstrip('\n').split('=', 1) for line in fh if '=' in line)
except OSError:
return None
ids = (info.get('ID', '') + ' ' + info.get('ID_LIKE', '')).replace('"', '').split()
if 'arch' in ids:
return 'arch'
if 'ubuntu' in ids or 'debian' in ids:
return 'ubuntu'
return None
def print_hint(hints):
''' gibt die Installationshinweise für dieses System aus
(bei unbekanntem Linux die für alle Linux-Distributionen) '''
distro = platform_name()
if distro in hints:
keys = [distro]
else:
keys = sorted(k for k in hints if k != 'windows')
for key in keys:
print(hints[key].format(venv=VENV, project=PROJECT_DIR), file=sys.stderr)
try:
from playwright.async_api import async_playwright, Error as PlaywrightError
except ImportError:
print('Playwright is not installed for', sys.executable, file=sys.stderr)
print('Install it into the project environment:', file=sys.stderr)
print_hint(INSTALL_HINTS)
sys.exit(2)
from utils import CompleteEnding
def read_config(cfg_name, section):
''' liest Breite, Höhe und Bildformat aus der Konfiguration '''
cfg_path = os.path.expandvars(os.path.join('$PICCREATE_CFG',
CompleteEnding(cfg_name, 'cfg')))
if not os.path.exists(cfg_path):
raise SystemExit('"' + cfg_path + '" does not exist. Please give the name of a config file!')
config = configparser.RawConfigParser(strict=False)
config.read(cfg_path, encoding='utf-8')
if not config.has_section(section):
raise SystemExit('section [' + section + '] is missing in ' + cfg_path)
return {
'width': config.getint(section, 'width'),
'height': config.getint(section, 'height'),
'format': config.get(section, 'format', fallback='jpg'),
}
def read_urls(infile):
''' liest die Paare (Kundennummer, URL) aus der Eingabedatei.
Eine Kopfzeile wird übersprungen, wenn ihre zweite Spalte keine URL ist.
'''
in_path = infile
if not os.path.exists(in_path):
in_path = os.path.expandvars(os.path.join('$PICCREATE_IN',
CompleteEnding(infile, 'csv')))
if not os.path.exists(in_path):
raise SystemExit('"' + in_path + '" does not exist. Please give the name to a input file!')
jobs = []
with open(in_path, encoding='utf-8', newline='') as fh:
for n, row in enumerate(csv.reader(fh, delimiter=';')):
row = [field.strip() for field in row]
if not row or not row[0] or row[0].startswith('#'):
continue
url = row[-1] if len(row) > 1 else ''
if n == 0 and '.' not in url:
continue
if url and '://' not in url:
url = 'http://' + url
jobs.append((row[0], url))
return jobs
async def capture(browser, customer_id, url, out_path, pic_config, timeout):
''' lädt eine Seite und speichert den sichtbaren Bereich als Bild '''
page = await browser.new_page(viewport={'width': pic_config['width'],
'height': pic_config['height']})
try:
await page.goto(url, timeout=timeout, wait_until='load')
kwargs = {'path': out_path}
if pic_config['format'] in ('jpg', 'jpeg'):
kwargs['type'] = 'jpeg'
kwargs['quality'] = 90
else:
kwargs['type'] = 'png'
await page.screenshot(**kwargs)
finally:
await page.close()
async def run(jobs, pic_config, options):
out_dir = options.outdir
timeout = int(options.timeout) * 1000
semaphore = asyncio.Semaphore(int(options.jobs))
failed = []
async with async_playwright() as p:
launch_args = {}
if options.browser:
if not os.path.isfile(options.browser):
print('browser', options.browser, 'does not exist.', file=sys.stderr)
print_hint(BROWSER_HINTS)
return None
launch_args['executable_path'] = options.browser
try:
browser = await p.chromium.launch(**launch_args)
except PlaywrightError as e:
print('could not start the browser:', str(e).splitlines()[0], file=sys.stderr)
print_hint(BROWSER_HINTS)
return None
async def worker(customer_id, url):
out_path = os.path.join(out_dir, customer_id + '.' + pic_config['format'])
if not url:
print(customer_id, 'has no url - skipped', file=sys.stderr)
failed.append(customer_id)
return
if os.path.exists(out_path) and not options.force:
if options.verbose:
print(out_path, 'exists')
return
async with semaphore:
if options.verbose:
print('loading', customer_id, url)
try:
await capture(browser, customer_id, url, out_path, pic_config, timeout)
print(out_path)
except PlaywrightError as e:
first_line = str(e).splitlines()[0]
print(customer_id, url, 'failed:', first_line, file=sys.stderr)
failed.append(customer_id)
await asyncio.gather(*(worker(cid, url) for cid, url in jobs))
await browser.close()
return failed
def main():
if not os.path.isdir(options.outdir):
print('"' + options.outdir + '" does not exist. Please give the path to the directory for the pictures!')
return 1
pic_config = read_config(options.config, options.section)
if options.format:
pic_config['format'] = options.format
jobs = read_urls(options.infile)
if not jobs:
print('no urls found in', options.infile)
return 1
failed = asyncio.run(run(jobs, pic_config, options))
if failed is None:
return 2
if failed:
print(len(failed), 'of', len(jobs), 'pages failed:', ', '.join(failed), file=sys.stderr)
return 1
return 0
if __name__ == '__main__':
parser = optparse.OptionParser(usage="""%prog [options]
Example usage
-------------
Screenshots of all urls in in/kunden.csv (lines: customerid;url):
$ %prog -c webscan -i kunden
""", version='%prog ' + __version__)
parser.add_option("-c", "--config", "--cfg", dest="config", default="webscan",
help="config file with a [webscan] section (width, height, format)",
metavar="CONFIG_FILE")
parser.add_option("--section", dest="section", default="webscan",
help="section in the config file. default is webscan")
parser.add_option("-i", "--infile", dest="infile",
help="file with lines customerid;url")
parser.add_option("-o", "--outdir", dest="outdir", default=os.path.expandvars('$PICCREATE_OUT'),
help="directory where the pictures will be saved")
parser.add_option("--format", dest="format",
help="jpg or png, overrides the config")
parser.add_option("-j", "--jobs", dest="jobs", default=4,
help="number of pages loaded in parallel. default is 4")
parser.add_option("--timeout", dest="timeout", default=20,
help="seconds to wait for a page. default is 20")
parser.add_option("-f", "--force", dest="force", action='store_true',
help="create the pictures, no matter if they already exist")
parser.add_option("--browser", dest="browser",
default=os.environ.get('PICCREATE_BROWSER'),
help="path to a chromium executable instead of the playwright one "
"(default: $PICCREATE_BROWSER)")
parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
help="print more information")
(options, args) = parser.parse_args()
if not options.infile:
parser.error('please give an input file with -i')
sys.exit(main())
# vim: set tw=80 ft=python ts=4 sw=4:
+1
View File
@@ -0,0 +1 @@
playwright