#!/bin/mksh # -*- mode: sh -*- #- # Copyright © 2007, 2008, 2011, 2012, 2013, 2014, 2018, 2019, # 2020, 2021, 2022 # mirabilos # # Provided that these terms and disclaimer and all copyright notices # are retained or reproduced in an accompanying document, permission # is granted to deal in this work without restriction, including un‐ # limited rights to use, publicly perform, distribute, sell, modify, # merge, give away, or sublicence. # # This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to # the utmost extent permitted by applicable law, neither express nor # implied; without malicious intent or gross negligence. In no event # may a licensor, author or contributor be held liable for indirect, # direct, other damage, loss, or other issues arising in any way out # of dealing in the work, even if advised of the possibility of such # damage or existence of a defect, except proven that it results out # of said person’s immediate fault when using the work as intended. #- # cf. $(dirname "$0")/#tbd unset LANGUAGE export LC_ALL=C.UTF-8 export TZ=UTC unset UNZIP me=$(realpath "$0") if [[ -z $DISPLAY ]]; then print -ru2 -- "E: re-run with DISPLAY set, need X11" exit 1 fi usage() { print -r -- "E: Usage: $0 [±23 cmd] [-f | -c file ...]" print -r -- "N: -c convert only given files; -f do only fonts" exit ${1:-1} } function saveFilename { REPLY=${1//+([ - ]|…| | | | | | | | | | | | | |
|
| | | )/ } REPLY=${REPLY%%+( )} REPLY=${REPLY##+( )} REPLY=${REPLY// /_} REPLY=${REPLY//ä/ae} REPLY=${REPLY//ö/oe} REPLY=${REPLY//ü/ue} REPLY=${REPLY//ß/ss} REPLY=${REPLY//Ä/Ae} REPLY=${REPLY//Ö/Oe} REPLY=${REPLY//Ü/Ue} REPLY=${REPLY//♭/b} REPLY=${REPLY//♯/#} REPLY=${REPLY//[\\/:*?\"<>|]/_} } function dumparr { nameref arr=$1 local el for el in "${arr[@]}"; do REPLY+=" ${el@Q}" done REPLY="${1@Q}=(${REPLY:1})" } function try_mscore { local have=0 cmd vchk=$1 exe chroot dv=$2 nameref arr=$dv shift 2 while [[ $1 != . ]]; do exe=$1; chroot=$2 shift 2 set -A cmd -- schroot -prc "$chroot" -- "$exe" [[ -n $chroot ]] || set -A cmd -- "$exe" [[ $("${cmd[@]}" --version 2>/dev/null) = \ MuseScore*" $vchk"* ]] || continue if [[ $( if [[ -n $chroot ]]; then schroot -prc "$chroot" -- mksh -c \ 'file $(realpath $(which '"${exe@Q}))" else file $(realpath $(which "$exe")) fi ) = *32-bit*x86-64* ]]; then # x32 binary, prefer others (often slightly buggy) (( have )) || set -A arr -- "${cmd[@]}" have=1 else set -A arr -- "${cmd[@]}" break fi done } try_mscore 2.3.2 mscore2 \ musescore '' \ musescore bullseye \ musescore buster \ musescore stretch \ . try_mscore 3. mscore3 \ musescore3 '' \ musescore3 bullseye \ musescore3 buster \ musescore-snapshot '' \ . onlyfont=0 onlyconvert=0 while getopts "2:3:cfh" ch; do case $ch { (2|3) nameref cmd=mscore$ch set -A cmd -- "$OPTARG" ;; (+2|+3) set -o noglob nameref cmd=mscore${ch#'+'} set -A cmd -- $OPTARG set +o noglob ;; (c) onlyconvert=1 ;; (f) onlyfont=1 ;; (+f) onlyfont=0 ;; (h) usage 0 ;; (*) usage ;; } done shift $((OPTIND - 1)) theremote=${1:-origin} print -ru2 -- "D: ${|dumparr mscore2;} -" print -ru2 -- "D: ${|dumparr mscore3;} -" sleep 1 set -x set -e set -o inherit-xtrace set -o pipefail . "${me%/*}"/push.inc owd=$PWD cd "$(realpath "$0/../../..")" wd=$(realpath .) mkdir -p .git/t-totex.sh T=$(realpath .git/t-totex.sh) rm -rf "$T/home" mkdir "$T/home" "$T/home/.config" "$T/home/cfg" "$T/home/cfg/MuseScore" ohome=$(realpath ~) cat >"$T/home/.config/QtProject.conf" <"$T/home/cfg/MuseScore/MuseScore2.ini" <<\EOF [General] alsaDevice=default alsaFragments=3 alsaPeriodSize=1024 alsaSampleRate=48000 checkExtensionsUpdateStartup=false checkUpdateStartup=false enableMidiInput=false exportAudioSampleRate=48000 exportMp3BitRate=128 exportPdfDpi=360 iconHeight=22 iconWidth=22 language=system mixerVisible=false nativeDialogs=true pngResolution=360 pngTransparent=true sessionStart=empty showNavigator=false showPlayPanel=false showSplashScreen=false showStartcenter1=false showStatusBar=true synthControlVisible=false twosided=true useAlsaAudio=true useJackAudio=false useJackMidi=false useJackTransport=false useMidiRemote=false useOsc=false usePortaudioAudio=false usePulseAudio=false workspace=Advanced [MainWindow] showInspector=false showPanel=false showPianoKeyboard=false showSelectionWindow=false EOF cat >"$T/home/cfg/MuseScore/MuseScore3.ini" <<\EOF [General] mixerVisible=false synthControlVisible=false [MainWindow] showInspector=false showPanel=false showPianoKeyboard=false showSelectionWindow=false [application] keyboardLayout=US - International playback\playRepeats=true startup\firstStart=false startup\sessionStart=@Variant(\0\0\0\x7f\0\0\0\x11Ms::SessionStart\0\0\0\0\0) workspace=Advanced [export] audio\normalize=true audio\sampleRate=48000 mp3\bitRate=128 pdf\dpi=360 png\resolution=360 png\useTransparency=true [io] alsa\device=default alsa\fragments=3 alsa\periodSize=1024 alsa\sampleRate=48000 alsa\useAlsaAudio=true jack\useJackAudio=false jack\useJackMIDI=false jack\useJackTransport=false midi\enableInput=false osc\useRemoteControl=false portAudio\usePortAudio=false pulseAudio\usePulseAudio=false [ui] application\language=system application\showStatusBar=true application\startup\checkUpdate=false application\startup\showNavigator=false application\startup\showPlayPanel=false application\startup\showSplashScreen=false application\startup\showStartCenter=false application\startup\showTours=false application\useNativeDialogs=true theme\fontFamily=Fira Sans Medium theme\fontSize=10 theme\iconHeight=16 theme\iconWidth=16 EOF HOME=$T/home : "${XAUTHORITY:=$ohome/.Xauthority}" export XAUTHORITY # RFC 2396 and some optional characters _plus_ apostrophe # -> escapes all shell meta-characters as well # minus / function p_uri_escape { print -nr -- "$@" | sed --posix -e ' s.%.%25.g s.;.%3B.g s.?.%3F.g s.:.%3A.g s.@.%40.g s.&.%26.g s.=.%3D.g s.+.%2B.g s.\$.%24.g s.,.%2C.g s. .%09.g s. .%20.g s.<.%3C.g s.>.%3E.g s.#.%23.g s.".%22.g s.{.%7B.g s.}.%7D.g s.|.%7C.g s.\\.%5C.g s.\^.%5E.g s.\[.%5B.g s.\].%5D.g s.`.%60.g s.'\''.%27.g ' } # escape XHTML characters (three mandatory XML ones plus double quotes, # the latter in an XML safe fashion numerically though) function xhtml_escape { if (( $# )); then print -nr -- "$@" else cat fi | sed --posix \ -e 's&\&g' \ -e 's<\<g' \ -e 's>\>g' \ -e 's"\"g' } # same as valsub avoiding fork function xhtml_fesc { REPLY=${1//'&'/'&'} REPLY=${REPLY//'<'/'<'} REPLY=${REPLY//'>'/'>'} REPLY=${REPLY//'"'/'"'} } function tex_escape { sed --posix \ -e 's!\\!\\textbackslash!g' \ -e 's!\^!\\textasciicircum!g' \ -e 's!~!\\textasciitilde!g' \ -e 's![{}#&_%$]!\\&!g' } function utf16hex_escape { # note: UTF-16 needs surrogates for astral planes codepoints! print -nr -- "$1" | iconv -t utf-16be | hexdump -ve '1/1 "%02X"' } if (( !onlyconvert )); then rm -rf .git/t-fonts mkdir -p .git/t-fonts/{conf,fnts,cache,x} sed --posix \ -e "s@fontdir@$(realpath .git/t-fonts/fnts | xhtml_escape)" \ -e "s@cachedir@$(realpath .git/t-fonts/cache | xhtml_escape)" \ .git/t-fonts/conf/fonts.conf for archive in music/resources/Gentium*.zip \ music/resources/GentiumPlus-* \ music/resources/UnifrakturMaguntia*.zip \ ; do archive=$(realpath "$archive") ( cd .git/t-fonts/x case $archive { (*.zip) unzip -- "$archive" ;; (*.t[gx]z|*.tar.[gx]z) tar -xaf "$archive" ;; (*) print -ru2 -- "E: unknown type: $archive"; exit 1 ;; } ) done fntdir=$(realpath .git/t-fonts/fnts) (cd /usr/share/fonts/truetype/freefont; pax -rw -l \ Free{Sans,Serif{,Bold}{,Italic}}.ttf "$fntdir/") (cd .git/t-fonts/x/Gentium-*/; pax -rw -l Gentium-*ttf "$fntdir/") for x in .git/t-fonts/x/GentiumPlus-*; do (cd "$x"; pax -rw -l GentiumPlus*.ttf "$fntdir/") done (cd .git/t-fonts/x/UnifrakturMaguntia*/; pax -rw -l UnifrakturMaguntia.ttf "$fntdir/") rm -r .git/t-fonts/x (cd music/resources/; pax -rw -l *.?tf "$fntdir/") # not suitable for embedding rm -f .git/t-fonts/fnts/Inconsolatazi4varl_qu-Bold.otf fi fntdir=$(realpath .git/t-fonts/fnts) export FC_CONFIG_FILE=$(realpath .git/t-fonts/conf/fonts.conf) export FC_CONFIG_DIR=$(realpath .git/t-fonts/conf) export FONTCONFIG_FILE=$FC_CONFIG_FILE FONTCONFIG_PATH=$FC_CONFIG_DIR fc-list | sort (( !onlyfont )) || exit 0 set -A cfgdir -- -c "$T/home/cfg" -a alsa #-b 160 if ! command -v qpdf >/dev/null 2>&1; then print -ru2 -- "E: install qpdf" exit 1 fi # remove target dir unless we’re converting individual files only (( onlyconvert )) || rm -rf .git/t-totex set -A renamef set -A renamet nrename=0 function cvtf2meta { set +x local V=$1 name=$2 f=$3 pdflist x local bn=${name%.*} parts part partf nameref tpdf=to_pdf$nmischk set -A tpdf -- "$bn.pdf" local xparts xid=${bn##*/} xurl xa xt xx xa1 xt1 set -A xparts local xtex xpart xpartid mainpartname=$(xmlstarlet <"$f" sel -T -t \ --if 'boolean(//museScore/Score/name)' \ -v //museScore/Score/name -b \ -n) if [[ -n $mainpartname ]]; then part=$mainpartname partx=${|xhtml_fesc "$part";} renamef[nrename]="$bn.pdf" renamet[nrename]="$bn [$part].pdf" let ++nrename tpdf[0]="$bn [$part].pdf" fi # xmlstarlet errors out silently if its output is empty; # write a newline (-n) in the “no parts” case to prevent # that (the $() removes trailing newlines) parts=$(xmlstarlet <"$f" sel -T -t \ --if 'boolean(//museScore/Score/Score)' \ -m //museScore/Score/Score/name -v . -o '' -b \ --else -n -b) while [[ -n $parts ]]; do part=${parts%%*} parts=${parts#*} partf=${|saveFilename "$part";} partx=${|xhtml_fesc "$part";} if [[ $partf != "$part" ]]; then renamef[nrename]="$bn [$partf].pdf" renamet[nrename]="$bn [$part].pdf" let ++nrename fi tpdf+=("$bn [$part].pdf") xparts+=("$part") done local xbn=$(print -r -- "$xid" | tex_escape) xurl=$(xmlstarlet <"$f" sel -T -t \ -c '/museScore/Score/metaTag[@name="pathweb"]' | tex_escape) xa=$(xmlstarlet <"$f" sel -T -t \ -c '/museScore/Score/metaTag[@name="tex:author"]' | tex_escape) xt=$(xmlstarlet <"$f" sel -T -t \ -c '/museScore/Score/metaTag[@name="tex:title"]' | tex_escape) xx=$(xmlstarlet <"$f" sel -T -t \ -c '/museScore/Score/metaTag[@name="tex:xtra"]' | tex_escape) [[ -n $xa ]] || if [[ $xbn = *' -- '* ]]; then xa=${xbn%% -- *} else xa=unbekannt fi [[ -n $xt ]] || xt=${xbn#* -- } xa=${xa// -- / \\protect\\dash } xt=${xt// -- / \\protect\\dash } xx=${xx// -- / \\protect\\dash } xa1=${xa%%$'\n'*} xa1=${xa1/'|'/ } xt1=${xt%%$'\n'*} xtex=$( print -r -- "\\mypdfinc{${xbn}}{}{}{%" print -r -- "$xa" | while IFS= read -r line; do [[ -n $line ]] || continue print -r -- " \\addcontentsline{SBv}{figure}{${line/'|'/ } \\protect\\dash ${xt1} }%" [[ $line != *'|'* ]] || line=${line#*'|'}', '${line%'|'*} print -r -- " \\addcontentsline{SBn}{figure}{${line} \\protect\\dash ${xt1} }%" done print -r -- "$xt" | while IFS= read -r line; do [[ -n $line ]] || continue print -r -- " \\addcontentsline{SBt}{figure}{${line}  \\protect\\HandPencilLeft ${xa1}}%" done print -r -- "$xx" | while IFS= read -r line; do [[ -n $line ]] || continue print -r -- " \\addcontentsline{SBx}{myxtra}{\textit{${line}} \newline\hspace*{0.8em} ${xbn// -- / \\protect\\dash } }%" done print -r -- "}{${xurl}}%" ) { print -r -- 'set -ex' print -r -- 'set -o pipefail' x=$(realpath .git/t-totex) print -r -- "cd ${x@Q}" print -r -- "bn=${bn@Q}" partf='set -A partf --' xpart='set -A partx --' xpartid='set -A partid --' for part in '' "${xparts[@]}"; do if [[ -z $part ]]; then partf+=" ''" xpart+=" ''" xpartid+=" ''" else x=" [$part]" partf+=" ${x@Q}" x=\~$(print -r -- "[$part]" | tex_escape) xpart+=" ${x@Q}" x=$(utf16hex_escape " [$part]") xpartid+=" ${x@Q}" fi done print -r -- "$partf" print -r -- "$xpart" print -r -- "$xpartid" print -r -- "xtex=${xtex@Q}" cat <<\EOF i=${#partx[*]} while (( i-- > 0 )); do fn=$bn${partf[i]} pr=$(qpdf --show-npages "right/$fn.pdf") if [[ $pr != [1-9]*([0-9]) ]]; then print -ru2 "E: non-numeric #pages for $fn.pdf: ${pr@Q}" exit 1 fi pl=$(qpdf --show-npages "left/$fn.pdf") if [[ $pl != "$pr" ]]; then print -ru2 "E: mismatching #pages for $fn.pdf: $pr vs. ${pl@Q}" exit 1 fi x=${xtex///"${partx[i]}"} x=${x///"${partid[i]}"} x=${x///$pl} x=${x///"${partx[i]/#\~/ }"} print -r -- "$x" >"inf/$fn.tex" done EOF } >".git/t-totex/inf/$bn.sh" } set -A to_mischk nmischk=0 function cvtf { local name=$1 fv side # analyse file local f=$name V fv=$(xmlstarlet <"$f" sel -T -t -m /museScore -v @version -n) if [[ $fv = 2.* ]]; then V=2 elif [[ $fv = 3.* ]]; then V=3 else print -ru2 -- "E: unknown score version in $name" print -ru2 -- "N: version: ${fv@Q}" print -ru2 -- "N: line:" $(fgrep '".git/t-totex/right/$name" <<\EOP BEGIN { $skip = 0; } if (m!<(evenFooterL|oddFooterR)>!) { print "<$1>\n"; $skip = 1; } if ($skip == 1) { $skip = 0 if m!!; next; } s!(.*\.msc)x()!$1z$2!; print $_; EOP perl -n - ".git/t-totex/right/$name" >".git/t-totex/left/$name" <<\EOP BEGIN { my %map = ( 'evenHeaderL' => 'oddHeaderL', 'evenHeaderC' => 'oddHeaderC', 'evenHeaderR' => 'oddHeaderR', 'evenFooterL' => 'oddFooterL', 'evenFooterC' => 'oddFooterC', 'evenFooterR' => 'oddFooterR', 'page-margins type="even"' => 'page-margins type="odd"', 'pageEvenLeftMargin' => 'pageOddLeftMargin', 'pageEvenTopMargin' => 'pageOddTopMargin', 'pageEvenBottomMargin' => 'pageOddBottomMargin', ); %hmap = (); while (($a, $b) = each(%map)) { $hmap{$a} = $b; $hmap{$b} = $a; } $re = "("; } s/$re/$1$hmap{$2}>/go; print; EOP } function mischk { local basename name rv=0 idx=-1 pdflist rm -f "$T/tmp-analyse" runalltrans while (( ++idx < nmischk )); do basename=${to_mischk[idx]} mksh ".git/t-totex/inf/$basename.sh" nameref pdflist=to_pdf$idx for name in "${pdflist[@]}"; do for name in .git/t-totex/{right,left}/"$name"; do if [[ ! -s $name ]]; then print -ru2 -- "E: ${name@Q} missing!" rv=1 continue fi if gs -q -dSAFER -sDEVICE=txtwrite -o - "$name" | \ grep -Paq '\x00'; then print -ru2 -- "W: missing glyphs in $name" misglyph+=("$name") rv=1 continue fi done done done (( rv )) || return 0 set +x (( ${#misglyph[*]} )) || exit 1 print -ru2 -- "E: missing glyphs found; check them with:" for name in "${misglyph[@]}"; do print -ru2 -- "N: mutool draw -o - -F txt ${PWD@Q}/${name@Q} | less \$'+/\xEF\xBF\xBD'" done print -ru2 -- 'N: or: gs -q -dSAFER -sDEVICE=txtwrite -o - "$name" | jupp -mold dosrch,quote,\"@\",rtn,rtn -' exit 1 } mkdir -p .git/t-totex [[ -d .git/t-totex/. ]] set -A misglyph rm -f "$T"/t*.{jsn,xml} # escape string into JSON string (with surrounding quotes) function json_escape { set +x # requires mksh R51 set -U local o=\" s if (( $# )); then read -raN-1 s <<<"$*" unset s[${#s[*]}-1] else read -raN-1 s fi local -i i=0 n=${#s[*]} wc local -Uui16 -Z7 x local -i1 ch while (( i < n )); do (( ch = x = wc = s[i++] )) case $wc { (8) o+=\\b ;; (9) o+=\\t ;; (10) o+=\\n ;; (12) o+=\\f ;; (13) o+=\\r ;; (34) o+=\\\" ;; (92) o+=\\\\ ;; (*) if (( wc < 0x20 || wc > 0xFFFD || \ (wc >= 0xD800 && wc <= 0xDFFF) || \ (wc > 0x7E && wc < 0xA0) )); then o+=\\u${x#16#} else o+=${ch#1#} fi ;; } done REPLY="$o\"" } function addtransA { local v=$1 from=$2 toJSN=$3 nameref tlast=tlast$v local f=$T/t$v.jsn print -r -- "${tlast:-[}{\"in\":${|json_escape "$from";},\"out\":$toJSN}" \ >>"$f" tlast=, } function runalltrans { local f for f in $T/t*.jsn; do [[ -s $f ]] || continue runtrans "${f##*/t}" done cd "$wd/.git/t-totex" while (( nrename-- )); do mv "right/${renamef[nrename]}" "right/${renamet[nrename]}" mv "left/${renamef[nrename]}" "left/${renamet[nrename]}" done cd "$wd" } function runtrans { local V=${1%.jsn} local v=${V::1} nameref cmd=mscore$v local f=$T/t$V.jsn local p= [[ $V = *p* ]] && p=-P print ']' >>"$f" "${cmd[@]}" "${cfgdir[@]}" $p -j "$f" rm -f "$f" } if (( onlyconvert )); then rv=0 for name in "$@"; do name=$(cd "$owd" && realpath "$name") if [[ -s "$name" ]]; then cvtf "${name#"$wd/"}" else print -ru2 -- "E: file \"$name\" does not exist" rv=1 fi done mischk exit $rv fi git find -name \*.mscx -print0 |& while IFS= read -r -p -d '' name; do checkskip totex "$name" || continue cvtf "$name" done mischk rv=0 cd "$wd/.git/t-totex" for x in \ 'music/free/Monk -- Eventide (Abide with me) [open score].pdf' \ ; do test -s "right/$x" && test -s "left/$x" && \ test -s "inf/${x%.*}.tex" && continue print -ru2 -- "E: missing: $x" rv=1 done cd "$wd" exit $rv