Благодаря помощи meuh я смог написать короткую программу на Python, которая преобразует PDF-файл, созданный с помощью моей существующей программы, в файл PostScript с помощью команд, setpagedevice
специфичных для устройства -.
Эта программа весьма специфична для моего использования, но я думаю, что она может быть полезна в качестве отправной точки для других. Вот он:
#! /usr/bin/env python3
import argparse
import re
import subprocess
import sys
import PyPDF2
def make_postscript(f):
"""Convert a PDF file to PostScript, with pdf2ps, and yield it line by line."""
with subprocess.Popen(['pdf2ps', '/dev/stdin', '/dev/stdout'], stdin=f, stdout=subprocess.PIPE, stderr=None) as proc:
for line in proc.stdout:
yield line
def add_device_control(postscript, separator_pages):
"""Add device control commands to a PostScript file with DSC comments."""
DSC_page_re = re.compile(b'%%Page: (?P<page_name>.+) (?P<page_number>[1-9][0-9]*)$')
DSC_begin_page_setup_re = re.compile(b'%%BeginPageSetup$')
page_number = None
for line in postscript:
m = DSC_page_re.match(line)
if m:
assert page_number is None
page_number = int(m.group('page_number').decode('ASCII'))-1
yield line
continue
m = DSC_begin_page_setup_re.match(line)
if m:
assert page_number is not None
yield line
if page_number in separator_pages:
yield b'mark { << /PageSize [1191 842] /ImagingBBox null /MediaType (Plain) /MediaPosition null >> setpagedevice } stopped cleartomark\n'
elif page_number not in separator_pages:
yield b'mark { << /PageSize [595 842] /ImagingBBox null /MediaType (Labels) /MediaPosition 0 >> setpagedevice } stopped cleartomark\n'
page_number = None
continue
yield line
assert page_number is None
def walk_outline(outline, depth=0):
"""Walk through the outline of a PDF file in a depth-first search way, and yield each element with its zero-based depth."""
for obj in outline:
if isinstance(obj, PyPDF2.pdf.Destination):
yield depth, obj
elif isinstance(obj, list):
for result in walk_outline(obj, depth+1):
yield result
else:
assert False
def find_separator_pages(f):
"""Find the page number of the separator pages in a PDF file"""
separator_pages = set()
reader = PyPDF2.PdfFileReader(f)
for depth, obj in walk_outline(reader.outlines):
page_num = reader._getPageNumberByIndirect(obj.page)
if depth == 0:
assert page_num >= 0
separator_pages.add(page_num)
return separator_pages
def main():
parser = argparse.ArgumentParser()
parser.add_argument("input_file", metavar="input.pdf", type=argparse.FileType('rb'))
parser.add_argument("output_file", metavar="output.ps", nargs="?", type=argparse.FileType('wb'), default=sys.stdout.buffer)
args = parser.parse_args()
separator_pages = find_separator_pages(args.input_file)
args.input_file.seek(0)
postscript = make_postscript(args.input_file)
postscript = add_device_control(postscript, separator_pages)
for line in postscript:
args.output_file.write(line)
if __name__ == "__main__":
main()
Еще раз огромное спасибо meuh за помощь в комментариях.
Вот альтернативная реализация вашей up
функции (, включая ее аналог программируемого завершения ), который пытается:
IFS
переменной, что не является тривиальным для правильного восстановления ; .
, ..
; обрабатывается здесь произвольным образом, вы можете адаптировать код в соответствии со своими предпочтениями ); Функция up
:
up () {
local path=$PWD
case $1 in
(''|'..')
cd..; return ;;
(.)
cd.; return ;;
(/)
cd /; return ;;
(*/*)
printf '"%s" %s\n' "$1" 'contains forward slashes, up will not work' >&2
return 1 ;;
esac
if [[ $path != /* ]]; then
printf '%s\n' 'PWD is not an absolute path, up will not work' >&2
return 1
fi
until [[ $path = / ]]; do
path=${path%/*}
path=${path:-/}
if [[ ${path%"$1"} != "$path" ]] # You may add && [[ -d ${path%"$1"} ]] to
# make "up oo" fail when you are in `/foo/bar`,
# while "up foo" would succeed anyway
then
cd -- "$path"
return
fi
done
printf '%s "%s" %s\n' 'Directory' "$1" 'not found' >&2
return 1
}
И ее _up
функция завершения и спецификация (команды complete
):
_up () {
local cur last path
declare -A ancestors
cur=${COMP_WORDS[$COMP_CWORD]}
path=$PWD
if [[ $path != /* ]]; then
return 1
fi
until [[ -z $path ]]; do
path=${path%/*}
last=${path##*/}
last=${last:-/}
if [[ $last == "${cur}"* ]]; then
ancestors+=( ["$last"]= )
fi
done
COMPREPLY=( "${!ancestors[@]}" )
return
}
complete -o nospace -o filenames -F _up up
Оба этих фрагмента кода предназначены для размещения в вашем .bashrc
. Если вы используете bash -завершение , вы можете предпочесть хранить функции завершения и спецификации в $XDG_DATA_HOME/bash-completion
, ~/.local/share/bash-completion
или ~/.bash_completion
(. Обратитесь к FAQ на подписанной странице, чтобы узнать больше об этом )..
Аргументы, содержащие косую черту, исключаются намеренно, потому что:в противном случае (на мой взгляд ), помимо foo/bar
мы должны позаботиться, например, оfoo///bar/./../baz
(т.е. выполнить некоторую форму канонизации ).
Для использования ассоциативных массивов(declare -A
)требуется Bash >= 4.0.
Проблема в том, что оценка переменных LS
и LSARRAY
происходит только один раз, поэтому команда up
всегда будет выполняться с родительскими папками PWD
, которые у вас были, когда вы определили завершение.
Для динамического извлечения родительских папок вам необходимо создать функцию завершения. Таким образом, переменные LS
и LSARRAY
будут оцениваться каждый раз, когда вы пытаетесь завершить свою базу команд up на вашем фактическом текущем PWD
.
_complete_up()
{
local cur=${COMP_WORDS[COMP_CWORD]}
local LS=$(echo $PWD)
local LSARRAY=${LS//\// }
COMPREPLY=( $(compgen -W "${LSARRAY}" -- $cur) )
return 0
}
complete -F _complete_up up
Вы можете сделать это:
_up () {
local oldIFS="$IFS" LS="${PWD%/*}"
IFS='/
'
if [ -n "$2" ]; then
COMPREPLY=($(compgen -W "${LS#/}" "$2"))
else
COMPREPLY=(${LS#/})
fi
IFS="$oldIFS"
}
complete -F _up up
up () {
local oldIFS="$IFS" i=1 LSARRAY LS="$PWD" FIND ARR
IFS='/
'
LSARRAY=(${PWD#/})
if [[ $LS == */$1/* ]]; then
FIND=$1
while [ "${LSARRAY[${#LSARRAY[@]}-$i]}" != "$FIND" ] && [ $i != ${#LSARRAY[@]} ]; do
cd..
i=$(($i+1))
done
else
echo "Did not find directory '$1'"
fi
IFS="$oldIFS"
}
Основная проблема заключалась (, как упоминалось в предыдущем ответе ), в том, что ваши возможности завершения не генерировались динамически.
Кроме того, ваш код не работает с пробелами в имени каталога. Мой не работает с новыми строками в имени каталога.
Я также удалил текущий каталог из списка выбора, потому что это не имеет смысла.
Я не понимаю, что ваш if [[ $1 == */* ]]
должен делать (в любом случае должен быть if [[ $FIND == */* ]]
), так как имена каталогов никогда не могут содержать /
, поэтому я удалил его.