#!/usr/bin/env tclsh package require Tk package require Img package require fileutil package require textutil package require platform #---------------------------------------------------------------------- # # Copyright (C) 2019, Maxim Lihachev, # # 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 . # #---------------------------------------------------------------------- # # REQUIREMENTS: # imagemagick + ghostscript # 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 zathura } # Markdown-шаблон set template { --- layout: book lastmod: $::pdf::lastmod title: $::pdf::title authors: $::pdf::authors pathway: Библиотека category: Исследования pages: year: language: $::pdf::language pdf_size: $::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}"]) {} } } } # Определение языка по названию proc guess_language {title} { # Базовый набор символов set eng {[a-z[:space:][:punct:]]} set rus_eng {[a-zа-я[:space:][:punct:]]} switch -nocase -regexp $title "^${eng}*\[äöüß]+${eng}*" { set lang DEU } "^${eng}*\[àâæçéèêëîïôœùûüÿ]+${eng}*" { set lang FRA } "^${rus_eng}*\[ћџђљњčćžđš]+${rus_eng}*" { set lang SRB } "^${eng}*\[а-я]+${eng}*" { set lang RUS } "^${eng}+" { set lang ENG } default { set lang RUS } } # Получение информации о PDF-файле proc load {filename} { set file_basename [file rootname [file tail $filename]] ::pdf::erase_info # Попытка получения автора/авторов и названия по шаблону # # Фамилия_1 И.О., Фамилия_2 И.О. - Название работы.pdf # set authors_title [::textutil::splitx $file_basename " - "] if {[llength $authors_title] >= 2} { set authors [lindex $authors_title 0] set title [join [lrange $authors_title 1 end] " - "] } else { 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::language [guess_language $file_basename] 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 {} { clear_tags write_tags write_markdown } # Удаление всех метаданных proc clear_tags {} { exec exiftool -all:all= "[file join $::pdf::file_path $::pdf::file]" 2>/dev/null } # Запись метаданных 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 page 1 # Рамка обложки по умолчанию set border 0 # Качество извлекаемого изображения set density 300 set quality 100 # Размер текущего изображения 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 \ -density $::cover::density \ -quality $::cover::quality \ "${::pdf::file}\[[expr {$::cover::page - 1}]\]" \ "${::pdf::file_rootname}.jpg" 2>/dev/null } # Добавление рамки proc add_border {{width 1}} { exec convert "${::pdf::file_rootname}.jpg" \ -bordercolor black \ -border $width \ "${::pdf::file_rootname}.jpg" } # Добавление рамки proc del_border {{width 1}} { exec convert "${::pdf::file_rootname}.jpg" \ -bordercolor black \ -border $width \ -fuzz 4% \ -trim "${::pdf::file_rootname}.jpg" } # Переключение рамки proc toggle_border {{width 1}} { if {$::cover::border == 1} { add_border $width } else { del_border $width } } # Создание webp-вариантов изображений proc make_webp {} { catch {exec cwebp "${::pdf::file_rootname}.jpg" -o "${::pdf::file_rootname}.webp" >/dev/null} if {[file exists ${::pdf::file_rootname}.th.jpg]} { catch {exec cwebp "${::pdf::file_rootname}.th.jpg" -o "${::pdf::file_rootname}.th.webp" >/dev/null} } } # Оптимизация изображений proc optimize {} { exec resmushit-cli --preserve-filename "${::pdf::file_rootname}.jpg" >/dev/null if {[file exists ${::pdf::file_rootname}.th.jpg]} { exec resmushit-cli --preserve-filename "${::pdf::file_rootname}.th.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 "${w}x${h}" 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.page_label -text "Страница:" spinbox .img.resize.page_entry -textvariable ::cover::page -width 2 -from 0 -to 10 -command { ::cover::extract ::gui::image::load $::gui::cover_image set ::cover::border 0 } checkbutton .img.resize.border -text "Рамка" -variable ::cover::border -command { ::cover::toggle_border ::gui::image::load $::gui::cover_image } 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.page_label -row 0 -column 0 -sticky w grid .img.resize.page_entry -row 0 -column 1 -sticky w grid .img.resize.border -row 0 -column 2 -sticky w grid .img.resize.size_label -row 0 -column 3 -sticky w grid .img.resize.size_entry -row 0 -column 4 -sticky w grid .img.resize.resize_label -row 0 -column 5 -sticky w grid .img.resize.sizes -row 0 -column 6 -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 "::gui::text::hl_yaml $::gui::txt" bind . ::main::open_file bind . ::main::open_file bind . ::pdf::view_file bind . ::pdf::view_file bind . ::main::quit bind . ::main::quit wm protocol . WM_DELETE_WINDOW ::main::quit } } # ------------------------------------------------------------------------------ # Общие процедуры namespace eval main { # Удаление временных данных proc remove_tmp_data {} { foreach tmp_file [glob -nocomplain "/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]