You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
808 lines
24 KiB
808 lines
24 KiB
#!/usr/bin/env tclsh |
|
|
|
package require Tk |
|
package require Img |
|
package require fileutil |
|
package require platform |
|
|
|
#---------------------------------------------------------------------- |
|
# |
|
# Copyright (C) 2019, Maxim Lihachev, <envrm@yandex.ru> |
|
# |
|
# 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, version 3. |
|
# |
|
# 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. |
|
# |
|
# You should have received a copy of the GNU General Public License |
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
# |
|
#---------------------------------------------------------------------- |
|
|
|
# |
|
# REQUIREMENTS: |
|
# ghostscript |
|
# imagemagick |
|
# cwebp |
|
# exiftool |
|
# resmushit-cli |
|
# |
|
|
|
# Сочетания клавиш |
|
|
|
# Функции для работы с файлами в формате PDF |
|
namespace eval pdf { |
|
# Список меток, доступных в метаданных PDF |
|
set all_tags {author title creator keywords producer subject} |
|
|
|
# Массив для хранения меток |
|
array set tags {} |
|
|
|
array set tag_labels { |
|
Author {Автор(ы)} |
|
Title {Заголовок} |
|
Creator {Создатель} |
|
Keywords {Ключевые слова} |
|
Producer {Продюсер} |
|
Subject {Тема} |
|
} |
|
|
|
# Значение метки по умолчанию |
|
set default_tag "Гностическая библиотека gnosis.study" |
|
|
|
# Текущая дата |
|
set lastmod [clock format [clock seconds] -format %Y-%m-%d] |
|
|
|
# Просмотрщик файлов в формате PDF |
|
if {[string match "macosx*" [::platform::generic]]} { |
|
set viewer open |
|
} else { |
|
set viewer xdg-open |
|
} |
|
|
|
# Markdown-шаблон |
|
set template { |
|
--- |
|
layout: book |
|
lastmod: $::pdf::lastmod |
|
title: $::pdf::title |
|
authors: $::pdf::authors |
|
pathway: Библиотека |
|
category: Исследования |
|
pages: |
|
year: |
|
language: RUS |
|
filesize: $::pdf::tags(File Size) |
|
pdf: $::pdf::file |
|
tags: |
|
- |
|
|
|
--- |
|
} |
|
|
|
# Открытие файла в просмотрщике документов в формате PDF |
|
# По умолчанию используется zathura |
|
proc view_file {} { |
|
exec nohup $::pdf::viewer $::pdf::file >/dev/null 2>/dev/null & |
|
} |
|
|
|
# Загрузка markdown из файла или создание текста на основе шаблона |
|
proc get_markdown {filename template} { |
|
set markdown_file "${::pdf::file_rootname}.md" |
|
|
|
if {[file exists $markdown_file]} { |
|
set md [::fileutil::cat $markdown_file] |
|
} else { |
|
set md [make_markdown $markdown_file $template] |
|
} |
|
|
|
return $md |
|
} |
|
|
|
# Заполнение markdown-шаблона |
|
proc make_markdown {filename template} { |
|
return [join [lrange [lmap line [split [subst $template] "\n"] { |
|
regsub -all {\s{2,}} $line { } line |
|
string trim $line |
|
}] 1 end] "\n"] |
|
} |
|
|
|
# Инициализация пустых переменных с информацией о PDF-файле |
|
proc erase_info {} { |
|
catch { |
|
unset ::pdf::file |
|
unset ::pdf::file_path |
|
unset ::pdf::file_basename |
|
unset ::pdf::file_rootname |
|
unset ::pdf::title |
|
unset ::pdf::authors |
|
unset ::pdf::canonical_authors |
|
|
|
array unset ::pdf::tags |
|
|
|
foreach tag $::pdf::all_tags { |
|
set ::pdf::tags([string totitle "${tag}"]) {} |
|
} |
|
} |
|
} |
|
|
|
# Получение информации о PDF-файле |
|
proc load {filename} { |
|
set file_basename [file rootname [file tail $filename]] |
|
|
|
::pdf::erase_info |
|
|
|
# Попытка получения автора/авторов и названия по шаблону |
|
# |
|
# Фамилия_1 И.О., Фамилия_2 И.О. - Название работы.pdf |
|
# |
|
if {![regexp {(.*) - (.*)} $file_basename {} authors title]} { |
|
set authors {} |
|
set title $file_basename |
|
} |
|
|
|
set ::pdf::file [file tail $filename] |
|
set ::pdf::file_path [file dirname $filename] |
|
set ::pdf::file_basename $file_basename |
|
set ::pdf::file_rootname [file rootname $filename] |
|
set ::pdf::title $title |
|
set ::pdf::authors [authors_list $authors] |
|
set ::pdf::canonical_authors [canonical_authors $authors] |
|
|
|
read_tags |
|
} |
|
|
|
# Чтение метаданных PDF-файла |
|
proc read_tags {} { |
|
foreach line [split [exec exiftool -all -t "$::pdf::file"] "\n"] { |
|
set data [split $line "\t"] |
|
set k [lindex $data 0] |
|
set v [lindex $data 1] |
|
|
|
set ::pdf::tags($k) $v |
|
} |
|
} |
|
|
|
# Сохранение метаданных в файл |
|
# и запись markdown-описания на диск |
|
proc write_metadata {} { |
|
write_tags |
|
write_markdown |
|
} |
|
|
|
# Запись метаданных |
|
proc write_tags {} { |
|
exec exiftool -Author=$::pdf::tags(Author)\ |
|
-Title=$::pdf::tags(Title)\ |
|
-Creator=$::pdf::tags(Creator)\ |
|
-Keywords=$::pdf::tags(Keywords)\ |
|
-Producer=$::pdf::tags(Producer)\ |
|
-Subject=$::pdf::tags(Subject)\ |
|
"[file join $::pdf::file_path $::pdf::file]" |
|
} |
|
|
|
# Сохранение markdown-файла |
|
proc write_markdown {} { |
|
set file_output "[file join $::pdf::file_path $::pdf::file_basename].md" |
|
|
|
::fileutil::writeFile $file_output [$::gui::txt get 1.0 end] |
|
} |
|
|
|
# Перечисление авторов в виде markdown-списка: |
|
# |
|
# Author 1, Author 2 - Title.pdf |
|
# |
|
# => |
|
# |
|
# - Author 1 |
|
# - Author 2 |
|
proc authors_list {authors} { |
|
return [join [ |
|
lmap a [split $authors ","] { |
|
format "\n- %s" [string trim $a] |
|
} |
|
] {}] |
|
} |
|
|
|
# Список авторов в формате |
|
# |
|
# И.О. Фамилия_1, И.О. Фамилия_2 |
|
# |
|
proc canonical_authors {authors} { |
|
return [join [ |
|
lmap a [split $authors ","] { |
|
lreverse [split [string trim $a]] |
|
} |
|
] ", "] |
|
} |
|
} |
|
|
|
# Функции для работы с изображениями |
|
namespace eval cover { |
|
# Размер текущего изображения |
|
set size {} |
|
|
|
# Размеры изображения титульного листа |
|
set sizes {x600 x700 x800} |
|
|
|
# Размер миниатюры изображения титульного листа |
|
set th_size x250 |
|
|
|
# Изменение размера |
|
proc resize {} { |
|
set new_size [.img.resize.sizes get] |
|
|
|
exec convert -resize $new_size "${::pdf::file_rootname}.jpg" "${::pdf::file_rootname}.jpg" |
|
|
|
set ::cover::size [get_size] |
|
} |
|
|
|
# Получение размера изображения в формате WxH |
|
proc get_size {} { |
|
return [exec magick identify -format {%[fx:w]x%[fx:h]} "${::pdf::file_rootname}.jpg"] |
|
} |
|
|
|
# Создание миниатюры |
|
proc make_thumbnail {} { |
|
exec convert -resize $::cover::th_size "${::pdf::file_rootname}.jpg" "${::pdf::file_rootname}.th.jpg" |
|
} |
|
|
|
# Извлечение первой страницы файла |
|
proc extract {} { |
|
exec convert "${::pdf::file}\[0\]" "${::pdf::file_rootname}.jpg" |
|
} |
|
|
|
# Создание webp-вариантов изображений |
|
proc make_webp {} { |
|
exec cwebp "${::pdf::file_rootname}.jpg" -o "${::pdf::file_rootname}.webp" >&- |
|
exec cwebp "${::pdf::file_rootname}.th.jpg" -o "${::pdf::file_rootname}.th.webp" >&- |
|
} |
|
|
|
# Оптимизация изображений |
|
proc optimize {} { |
|
exec resmushit-cli --preserve-filename "${::pdf::file_rootname}.jpg" |
|
} |
|
|
|
# TODO: Обрезка изображения |
|
proc crop {} { |
|
return "convert -resize size image image" |
|
} |
|
} |
|
|
|
########################## |
|
# GUI |
|
########################## |
|
|
|
# Графический интерфейс пользователя |
|
namespace eval gui { |
|
namespace path {::tcl::mathop ::tcl::mathfunc} |
|
|
|
# Название приложения |
|
set main_title "Press'n'Post" |
|
|
|
# |
|
# Использованы значки: https://dribbble.com/shots/2888226-1800-Free-Minimal-Icon-Pack-20x20 |
|
# |
|
namespace eval icon { |
|
set view [image create photo -data { |
|
iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAPxJREFUOBHdVEsO |
|
AUEQbSKuYOEQNlZWVk7ABSQu4BKcw91Y2FhZSAT16Nd506qnRaxU0lOf95kagxBCuH951qZz41vDm7kt |
|
PUcaepg3Ix/5ameek0jI56WefOaLEWdKJqCztpp8zWcTTCgiwL6Wyc/zyYSjjl0AIFB/EuR73OOvDUPP |
|
u01lVnqS5+bdivhPYX39B3vGrZ1+fFbF9jbbFDDluX8OMEUokTVMEezznIChkVaRiE0RJKNexN7DgKdQ |
|
0dim6Esi5WqdzPR7iM9oEJFdYtQLGDeCd0LGZt5LgWBqhxz0qku1blj6BVCMjMi3f9PR/UVvXokht22f |
|
VA+yBpIvVGF67wAAAABJRU5ErkJggg== |
|
}] |
|
|
|
set save [image create photo -data { |
|
iVBORw0KGgoAAAANSUhEUgAAABUAAAAUCAYAAABiS3YzAAAAAXNSR0IArs4c6QAAAMhJREFUOBFjYMAO |
|
nIDCX4D4P5kYw1QfoMh3Mg2DOQLF0DAg7xfUwOVAmhlFFpUDshxkyBmosCSU/58JKgCiEoB4GRCzAjEI |
|
LAXio0AM0hgIxEQDmKE5QB3zgBjksr9E68ajsBwoBwuLBiAb5B0QH+Q9fAAkj9X7IE0wA0GGgwDMUJg4 |
|
IRqkHgSwhukiiBzlJAsWI0ywiJEkBIsokjQRUjy0DSU39uGhQnfv+wKtZsSDQfJYAd1ditUVxAjSxKWg |
|
MAPlbaoCmrgUAAgMVcgk92TZAAAAAElFTkSuQmCC |
|
}] |
|
|
|
set add [image create photo -data { |
|
iVBORw0KGgoAAAANSUhEUgAAABUAAAAUCAYAAABiS3YzAAAAAXNSR0IArs4c6QAAAHFJREFUOBFjYGBg |
|
+E8ApwHlkQGIj1cPE7JqarJhtkpSaChIP9gsmrh0+BnaAw0vEE0Q0MT7LEBrGQlaTZyC58SYtRmoCJbc |
|
sNEgeayAJt7HahMWQZIjCua1wZ2jQD4dGi6lSezTxFDkMIWFLTo9OEp+APPUO5cVXVflAAAAAElFTkSuQmCC |
|
}] |
|
|
|
set extract [image create photo -data { |
|
iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAATpJREFUOBGtlLtK |
|
A2EQhTcSsVJUvJFSC/HyOtZWVj6BT6SNlZ19tAgJ4h2tRax8A/U7ycw46LqG7A6cnfPP5fzzM7Ctoig+ |
|
QWM21ZiSCbWTYCvxSejwpY1PWEdwm2cclD1Fo06ymGPr2zDR0AlSdltFbJ3cXsqHTpCUHIfmhao+dIKM |
|
o2I1a/g7sJ96QidISlbRVZIPQH0f4BDIQifIKF4s4c/Bjp2zW+FwD9Tzbl78KPFvZYK6/daSb/gt4LYM |
|
8VwPvghOgA/kPgIdko9WoKeo4BVsAk19AxTrgwUg02JOgYvJx+HZ+BN+F1za+QXvYldwTZZtmsMZcNEg |
|
Cmhz2qBsDlwAL7yGa9IymyHodUEGBH82zBLrAl2khVTZL8H5P6ol+p+YWoeC+mWJyBr5fWXBkWzNb53f |
|
V+nVX3BUZefmrTd2AAAAAElFTkSuQmCC |
|
}] |
|
|
|
set thumbnail [image create photo -data { |
|
iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAATFJREFUOBGtlLFK |
|
Q1EQRI0o2FhY2NvYCvkBP8E6tensA3apxMJS8gNWItjnQwI2lv6BXZAkMy/3LOuNwoW8C5Pd2Zmd3Bd4 |
|
GRzsnvXuqH1y2G5tcw7abE2u7sl6v+GRvnuv30z7v56y9xv6x/ENW245lO9a4Eb1XvBoZP7v3EvA91JM |
|
cHaCRyPlCrWqn+L4VupPEseKHsaRlB/hAUeq7+pZ+ChzOLbgNA6jr0PPpD0Kz8KF4IN3yxJHcH0TCM6h |
|
p2ylyh6j4DTTooxVmTn0UvgS7oR88DALHg2K6kRg/p36HIrOWvBoUEp9SkF4XAllxlrwaFBUj4W5YG0p |
|
3AqLwj1zaL0XPBqZfPw6vgqeO+xG8DkXcmi915n8UQuzMsthmP8KReuq30sH7nN4t7uM3v9tNgRFf0H5 |
|
p3RQAAAAAElFTkSuQmCC |
|
}] |
|
|
|
set resize [image create photo -data { |
|
iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAF9JREFUOBFjZCAM |
|
/hNWwsDARIwiYtQwAhURZSNQHUgtNgDWTzUXsSBZgddGJHVYmVRz0eAzCDmMsPodSRBv7FLNa8S4iFBs |
|
guWp5qJRg5ASAQ4mcqzhTSc49MOFqRbYAFdBByiUR9g+AAAAAElFTkSuQmCC |
|
}] |
|
|
|
set optimize [image create photo -data { |
|
iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAHVJREFUOBHtk1EK |
|
wCAMQ1W8/5U3FQqNRqlYYR/zz5itL0FjGNczSnYl2a02Z17Y4uKMHbVk7oSV4qiz8r0kuUOoO4RJajLr |
|
q2o0mXuH/w9n/dv173eo72Gfi96z3lT24HOPzAjlxRAYkIQM/FcJZSJg7G7cCV+b8wotFKfp7AAAAABJRU5ErkJggg== |
|
}] |
|
|
|
set content [image create photo -data { |
|
iVBORw0KGgoAAAANSUhEUgAAABUAAAAUCAYAAABiS3YzAAAAAXNSR0IArs4c6QAAAG9JREFUOBFjYGBg |
|
YAHiWiC+D8RvgXg5EEsAMUWgCaj7Pxo+DeQzU2LqE6iB6UBaA4i/QPmGQBoEZgIxuqV4+UxgbTQgqO59 |
|
ULgdBuI/QKwExP+AeCMQxwPxZyAeXGA0SVE/PkaT1GgpReVUNTSSFAA/Q2sLKirhAgAAAABJRU5ErkJggg== |
|
}] |
|
|
|
set webp [image create photo -data { |
|
iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAJlJREFUOBFjYMAE |
|
e4BC/8nAz4F6gjGNY2BYS4ZhMAc8xWYgOWISMEcwkqMbhx6QKxmYgNgTiB8DMczZxNIgPSC9GIAcw2CW |
|
gvTCAEwM7jJQOBAL4GGGpAFsICgMQQwQIDU80fWB+aAwpCoYNZDy4KR6GLJQ7ia4CZ9BLGqmQyd0A+FW |
|
kchAyRBUD0OQgU9IdBGycqx6qVp8AQC1Cl8RUqquKQAAAABJRU5ErkJggg== |
|
}] |
|
} |
|
|
|
# Окно с информацией о программе |
|
proc about_window {} { |
|
tk_messageBox -type ok -icon info \ |
|
-message "$::gui::main_title" \ |
|
-detail [join { \ |
|
"Данная программа предназначена для подготовки PDF-файлов к публикации в гностической библиотеке gnosis.study." |
|
"Copyright (c) 2019 Maxim Likhachev (envrm)." |
|
"Лицензия: GPLv3." |
|
} "\n\n"] |
|
} |
|
|
|
# Функции для работы с текстом |
|
namespace eval text { |
|
# Подсветка синтаксиса YAML |
|
proc hl_yaml {widget} { |
|
set offset 1.0 |
|
|
|
while 1 { |
|
set field_position [split [$widget search ":" $offset end] "."] |
|
|
|
if {[llength $field_position] == 0} { |
|
break |
|
} |
|
|
|
set row [lindex $field_position 0] |
|
set col [lindex $field_position 1] |
|
|
|
set offset "$row.[expr {$col + 1}]" |
|
|
|
$widget tag add field $row.0 $offset |
|
} |
|
} |
|
|
|
# Создание текстового поля |
|
proc create {parent} { |
|
set t [text $parent.t -setgrid true -width 50 -height 20 -wrap none -font {Sans 16}] |
|
|
|
$t tag configure field -foreground "blue" |
|
$t tag configure regular |
|
|
|
::gui::text::load $t [::pdf::get_markdown $::pdf::file $::pdf::template] |
|
|
|
hl_yaml $t |
|
|
|
return $t |
|
} |
|
|
|
# Загрузка текста в виджет |
|
proc load {txt content} { |
|
$txt delete 1.0 end |
|
$txt insert 1.0 $content |
|
|
|
$txt tag add regular 1.0 end |
|
|
|
hl_yaml $txt |
|
} |
|
} |
|
|
|
# Функции |
|
namespace eval image { |
|
# Масштабирование изображения под размер окна (tcl-версия) |
|
proc img_scale_x_tcl {im mx_ht} { |
|
set h [image height $im] |
|
set w [image width $im] |
|
|
|
set ::cover::size "${w}x${h}" |
|
|
|
puts "IMG: ${h}x${w}" |
|
puts "IMG: ${mx_ht}x___" |
|
|
|
set mx_wt [expr {$mx_ht * $w / $h}] |
|
|
|
if {$mx_wt >= $w} { |
|
set mode -subsample |
|
|
|
set xfactor [expr {$mx_ht / $h}] |
|
set yfactor [expr {$mx_wt / $w}] |
|
} else { |
|
set mode -subsample |
|
|
|
set xfactor [expr {$h / $mx_ht}] |
|
set yfactor [expr {$w / $mx_wt}] |
|
} |
|
|
|
puts "IMG: ${mx_ht}x${mx_wt}" |
|
puts "IMG: ${xfactor}x${yfactor}" |
|
|
|
set new [image create photo -width $mx_wt -height $mx_ht] |
|
$new copy $im -shrink -subsample $xfactor $yfactor |
|
|
|
image delete $im |
|
|
|
return $new |
|
} |
|
|
|
# Масштабирование изображения под размер окна (imagemagick-версия) |
|
proc img_scale_x {im mx_ht} { |
|
set h [image height $im] |
|
set w [image width $im] |
|
|
|
set ::cover::size "${h}x${w}" |
|
|
|
exec convert -resize x${mx_ht} "${::pdf::file_rootname}.jpg" "/tmp/${::pdf::file_basename}.publish_tmp.jpg" |
|
set new [image create photo -file "/tmp/${::pdf::file_basename}.publish_tmp.jpg"] |
|
|
|
image delete $im |
|
|
|
return $new |
|
} |
|
|
|
# Отображение изображения |
|
proc load {w} { |
|
set cover_file "${::pdf::file_rootname}.jpg" |
|
|
|
if {[file exists $cover_file]} { |
|
set cover [img_scale_x [image create photo image_file -file "${::pdf::file_rootname}.jpg"] 600] |
|
} else { |
|
set cover [image create photo -height 400 -width 400] |
|
} |
|
|
|
$w configure -image $cover |
|
} |
|
} |
|
|
|
# Главное меню |
|
# |
|
# | Файл | Правка | Изображение | Помощь | |
|
# |
|
namespace eval menu { |
|
proc make {} { |
|
menu .menu |
|
. configure -menu .menu |
|
|
|
::gui::menu::item .menu.file "Файл" { |
|
"Открыть новый файл..." ::main::open_file |
|
"Просмотр текущего файла" ::pdf::view_file |
|
"Выйти из программы" ::main::quit |
|
} |
|
|
|
::gui::menu::item .menu.edit "Правка" { |
|
"Сгенерировать markdown" { |
|
::gui::text::load $::gui::txt [::pdf::make_markdown $::pdf::file $::pdf::template] |
|
} |
|
|
|
"Сохранить метаданные" ::pdf::write_metadata |
|
} |
|
|
|
::gui::menu::item .menu.image "Изображение" { |
|
"Извлечь обложку" { |
|
::cover::extract |
|
::gui::image::load $::gui::cover_image |
|
} |
|
|
|
"Изменить размер" ::cover::resize |
|
"Оптимизировать" ::cover::optimize |
|
"Создать миниатюру" ::cover::make_thumbnail |
|
"Создать webp" ::cover::make_webp |
|
} |
|
|
|
::gui::menu::item .menu.about "Помощь" { |
|
"О программе" ::gui::about_window |
|
} |
|
} |
|
|
|
# Создание выпадающего меню |
|
proc item {path label commands} { |
|
set parent ".[lrange [split $path .] 1 end-1]" |
|
|
|
menu $path -tearoff 0 |
|
|
|
$parent add cascade -menu $path -label $label -underline 0 |
|
|
|
foreach {submenu command} $commands { |
|
$path add command -label $submenu -command $command |
|
} |
|
} |
|
} |
|
|
|
# Установка заголовка главного окна |
|
proc set_title {title} { |
|
wm title . $title |
|
} |
|
|
|
# Кнопка с изображением |
|
proc image_button {path label command {width 15}} { |
|
set id [lindex [split $path .] end] |
|
|
|
ttk::button $path -text $label \ |
|
-image [ set ::gui::icon::$id] -compound left \ |
|
-width $width -style My.TButton \ |
|
-command $command |
|
} |
|
|
|
# ---------------------------------------------------------------------- |
|
# |
|
# GUI |
|
# |
|
# +--------------------------------------------------------+ |
|
# | .menu [x]| |
|
# | +------------------------+ +------------------------+ | |
|
# | | .img | | .metadata | | |
|
# | | +--------------------+ | | +--------------------+ | | |
|
# | | | .img.cover | | | | .metadata.txt | | | |
|
# | | | | | | | | | | |
|
# | | | | | | | | | | |
|
# | | | | | | | | | | |
|
# | | | | | | | | | | |
|
# | | | | | | | | | | |
|
# | | | | | | +--------------------+ | | |
|
# | | | | | | +--------------------+ | | |
|
# | | | | | | | .metadata.tags | | | |
|
# | | +--------------------+ | | | [...] | | | |
|
# | | +--------------------+ | | | [...] | | | |
|
# | | | .img.resize [...] | | | | [...] | | | |
|
# | | +--------------------+ | | +--------------------+ | | |
|
# | | +--------------------+ | | +--------------------+ | | |
|
# | | | .img.buttons [...] | | | | .metadata.buttons | | | |
|
# | | +--------------------+ | | +--------------------+ | | |
|
# | +------------------------+ +------------------------+ | |
|
# +--------------------------------------------------------+ |
|
# |
|
proc show {} { |
|
if {![winfo exists .img]} { |
|
::gui::make |
|
} else { |
|
::gui::load_data |
|
} |
|
|
|
set_title "$::gui::main_title - $::pdf::file" |
|
} |
|
|
|
proc make {} { |
|
# [IMAGE] ------------------------------------------------------ |
|
|
|
frame .img |
|
set ::gui::cover_image [label .img.cover -relief groove] |
|
|
|
::gui::image::load $::gui::cover_image |
|
|
|
frame .img.resize |
|
|
|
label .img.resize.size_label -text "Размер изображения:" |
|
entry .img.resize.size_entry -textvariable ::cover::size -width 10 |
|
|
|
label .img.resize.resize_label -text "Новый размер:" |
|
ttk::combobox .img.resize.sizes -values $::cover::sizes -width 10 |
|
|
|
.img.resize.sizes set [lindex $::cover::sizes 0] |
|
|
|
grid .img.resize.size_label -row 0 -column 0 -sticky w |
|
grid .img.resize.size_entry -row 0 -column 1 -sticky w |
|
grid .img.resize.resize_label -row 0 -column 2 -sticky w |
|
grid .img.resize.sizes -row 0 -column 3 -sticky w |
|
|
|
frame .img.buttons |
|
|
|
image_button .img.buttons.view "Посмотреть файл" ::pdf::view_file |
|
image_button .img.buttons.extract "Извлечь обложку" { |
|
::cover::extract |
|
::gui::image::load $::gui::cover_image |
|
} |
|
|
|
image_button .img.buttons.resize "Изменить размер" ::cover::resize |
|
image_button .img.buttons.optimize "Оптимизировать" ::cover::optimize |
|
image_button .img.buttons.thumbnail "Создать миниатюру" ::cover::make_thumbnail |
|
image_button .img.buttons.webp "Создать webp" ::cover::make_webp |
|
|
|
grid .img.buttons.view -row 0 -column 0 -padx 1 -pady 1 -sticky w |
|
grid .img.buttons.extract -row 0 -column 1 -padx 1 -pady 1 -sticky w |
|
grid .img.buttons.resize -row 0 -column 2 -padx 1 -pady 1 -sticky w |
|
|
|
grid .img.buttons.optimize -row 1 -column 0 -padx 1 -pady 1 -sticky w |
|
grid .img.buttons.thumbnail -row 1 -column 1 -padx 1 -pady 1 -sticky w |
|
grid .img.buttons.webp -row 1 -column 2 -padx 1 -pady 1 -sticky w |
|
|
|
pack .img.cover -fill both -expand true |
|
pack .img.resize -pady 5 |
|
pack .img.buttons |
|
|
|
# [METADATA] --------------------------------------------------- |
|
|
|
set metadata [frame .metadata] |
|
frame .metadata.tags |
|
|
|
set ::gui::txt [::gui::text::create $metadata] |
|
|
|
set row 0 |
|
foreach tag $::pdf::all_tags { |
|
set tag_name [string totitle "${tag}"] |
|
|
|
label .metadata.tags.${tag}_label -text "$::pdf::tag_labels(${tag_name}):" |
|
entry .metadata.tags.${tag}_entry -width 40 -textvariable "::pdf::tags($tag_name)" |
|
|
|
button .metadata.tags.${tag}_button -image $::gui::icon::add -command "::gui::fill_tag $tag_name" |
|
|
|
grid .metadata.tags.${tag}_label -row $row -column 0 -sticky w |
|
grid .metadata.tags.${tag}_entry -row $row -column 1 -sticky w |
|
grid .metadata.tags.${tag}_button -row $row -column 2 -sticky w -ipadx 1 -ipady 1 |
|
|
|
incr row |
|
} |
|
|
|
frame .metadata.buttons |
|
|
|
image_button .metadata.buttons.save "Сохранить метаданные" ::pdf::write_metadata 20 |
|
image_button .metadata.buttons.content "Сгенерировать markdown" { |
|
::gui::text::load $::gui::txt [::pdf::make_markdown $::pdf::file $::pdf::template] |
|
} 20 |
|
|
|
grid .metadata.buttons.content -row 0 -column 1 -padx 1 -pady 1 -sticky ns |
|
grid .metadata.buttons.save -row 0 -column 2 -padx 1 -pady 1 -sticky ns |
|
|
|
pack $::gui::txt -fill both -expand true |
|
pack .metadata.tags -fill x -anchor w |
|
pack .metadata.buttons -pady 5 -anchor w |
|
|
|
# [TOP LEVEL] -------------------------------------------------- |
|
|
|
ttk::style theme use clam |
|
ttk::style configure My.TButton -anchor w |
|
|
|
set_title $::gui::main_title |
|
|
|
menu::make |
|
|
|
bind_keys |
|
|
|
pack .img -fill both -expand true -padx 10 -pady 10 -side left |
|
pack .metadata -fill both -expand true -padx 10 -pady 10 -side right |
|
|
|
focus $::gui::txt |
|
} |
|
|
|
proc load_data {} { |
|
::gui::image::load $::gui::cover_image |
|
::gui::text::load $::gui::txt [::pdf::get_markdown $::pdf::file $::pdf::template] |
|
|
|
update |
|
} |
|
|
|
# Заполнение метаданных вариантами по умолчанию |
|
proc fill_tag {tag} { |
|
set ::pdf::tags($tag) [switch $tag { |
|
"Author" { |
|
set ::pdf::canonical_authors |
|
} |
|
|
|
"Title" { |
|
set ::pdf::title |
|
} |
|
|
|
default { |
|
set ::pdf::default_tag |
|
} |
|
}] |
|
} |
|
|
|
proc bind_keys {} { |
|
# Подсветка YAML при изменении текста |
|
bind $::gui::txt <Any-KeyRelease> "::gui::text::hl_yaml $::gui::txt" |
|
|
|
bind . <KeyPress-Control_L><KeyPress-o> ::main::open_file |
|
bind . <KeyPress-Control_R><KeyPress-o> ::main::open_file |
|
|
|
bind . <KeyPress-Control_L><KeyPress-y> ::pdf::view_file |
|
bind . <KeyPress-Control_R><KeyPress-y> ::pdf::view_file |
|
|
|
bind . <KeyPress-Control_L><KeyPress-q> ::main::quit |
|
bind . <KeyPress-Control_R><KeyPress-q> ::main::quit |
|
|
|
wm protocol . WM_DELETE_WINDOW ::main::quit |
|
} |
|
} |
|
|
|
# ------------------------------------------------------------------------------ |
|
|
|
# Общие процедуры |
|
namespace eval main { |
|
# Удаление временных данных |
|
proc remove_tmp_data {} { |
|
foreach tmp_file [glob "/tmp/*.publish_tmp.jpg"] { |
|
file delete $tmp_file |
|
} |
|
} |
|
|
|
# Выход из программы |
|
proc quit {} { |
|
set answer [tk_messageBox -message "Выйти из программы?" \ |
|
-icon question -type yesno] |
|
|
|
if {$answer == yes} { |
|
remove_tmp_data |
|
|
|
exit 0 |
|
} |
|
} |
|
|
|
# Завершение работы с сообщением об ошибке |
|
proc die {msg {ui cli} {exit_code 1}} { |
|
switch $ui { |
|
cli { |
|
puts stderr $msg |
|
} |
|
|
|
gui { |
|
tk_messageBox -type ok -icon error -message $msg |
|
} |
|
} |
|
|
|
exit $exit_code |
|
} |
|
|
|
# Загрузка файла и запуск GUI |
|
proc open_file {{filename ""}} { |
|
if {$filename ne ""} { |
|
set pdf_file $filename |
|
} else { |
|
# set pdf_file [::tk::dialog::file:: open -filetypes {{{PDF Files} {.pdf}}} ] |
|
set pdf_file [tk_getOpenFile -parent . -filetypes {{PDF .pdf}}] |
|
} |
|
|
|
if {$pdf_file eq ""} { |
|
::main::die "ERROR: Не указан файл." |
|
} |
|
|
|
if {![file exists $pdf_file]} { |
|
::main::die "ERROR: Файл $filename не существует." |
|
} |
|
|
|
pdf::load $pdf_file |
|
|
|
puts [string repeat "=" 80] |
|
puts "#### FILE: $pdf_file" |
|
puts [string repeat "=" 80] |
|
parray ::pdf::tags |
|
puts [string repeat "-" 80] |
|
|
|
gui::show |
|
} |
|
} |
|
|
|
# ------------------------------------------------------------------------------ |
|
# Запуск приложения |
|
# ------------------------------------------------------------------------------ |
|
|
|
update |
|
|
|
main::open_file [lindex $argv 0] |
|
|
|
|