djvusmooth-0.2.14/0000755000000000000000000000000012111517063013733 5ustar rootroot00000000000000djvusmooth-0.2.14/edit-text0000755000000000000000000000410211570151711015567 0ustar rootroot00000000000000#!/usr/bin/python # encoding=UTF-8 # Copyright © 2008 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. if __name__ != '__main__': raise ImportError('This module is not intended for import') import sys import djvu.decode import djvu.sexpr try: import djvusmooth_importer except ImportError: pass from djvusmooth.text.mangle import import_, export def do_export(djvu_file_name, page_no): context = djvu.decode.Context() document = context.new_document(djvu.decode.FileURI(djvu_file_name)) text = djvu.decode.PageText(document.pages[page_no], details = djvu.decode.TEXT_DETAILS_WORD) text.wait() export(text.sexpr, sys.stdout) def do_import(djvu_file_name, page_no): context = djvu.decode.Context() document = context.new_document(djvu.decode.FileURI(djvu_file_name)) text = djvu.decode.PageText(document.pages[page_no], details = djvu.decode.TEXT_DETAILS_WORD) text.wait() print import_(text.sexpr, sys.stdin) def main(): from optparse import OptionParser oparser = OptionParser(usage = '%prog {--import | --export} --page N') oparser.add_option('-p', '--page', dest='page_no', action='store', type='int') oparser.add_option('-x', '--export', dest='action', action='store_const', const=do_export) oparser.add_option('-i', '--import', dest='action', action='store_const', const=do_import) options, [djvu_file_name] = oparser.parse_args() if options.action is None: oparser.error('No action specified') if options.page_no is None: oparser.error('No page specified') options.action(djvu_file_name, options.page_no - 1) if __name__ == '__main__': main() # vim:ts=4 sw=4 et djvusmooth-0.2.14/locale/0000755000000000000000000000000012111517063015172 5ustar rootroot00000000000000djvusmooth-0.2.14/locale/pl/0000755000000000000000000000000012111517063015605 5ustar rootroot00000000000000djvusmooth-0.2.14/locale/pl/LC_MESSAGES/0000755000000000000000000000000012111517063017372 5ustar rootroot00000000000000djvusmooth-0.2.14/locale/pl/LC_MESSAGES/djvusmooth.mo0000644000000000000000000002244012111224231022123 0ustar rootroot00000000000000           %1 8 CM _j{   #=hL&7!J)l!3!#EW+u $")! ' 1<U h r }  9Ihp u  #=,CH am u-  &0CRp#"#6Z s }  $  5;? H R_ c mw |  S ;HNjsz  )?Qci r|    $3P_`   2 +5L\(n$9+G Z1{+$'6 =G)Pz( ,&C&j'(   % : F X "g % !    !!-!C!L!'S!{!! !!! !!!!"8"H"*^")""1"&"2# F#g#w#####$##$ $$/$3$ O$Y$ ]$ h$s$$$ $$$$$$$$$$$$$% %%%A!KBe *+PjNXG6yh_ C{kI}[:3U]b9^g0iJft~/u(\Z`5@'W$d&"E%qD|-cTapm vY)R8,Vw1oM>zrLxlO2H #n=Q7.< 4?SFs;&About&Background&Bookmark this page&Close&Color&Edit&External editor&File&First page&Flatten&Foreground&Go&Go to page…&Help&Hyperlinks&Image&Last page&Metadata&New hyperlink…&Next page&Non-raster data&None&Open&Outline&Previous page&Properties…&Quit&Refresh&Remove&Remove all&Save&Settings&Stencil&Stretch&Text&View&Zoom(no title)About…Add the current to document outlineAlways visibleAn unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author. ArrowAuthorBackground colorBorderCannot edit text with character zones.Close the documentCommentDecrease the magnificationDisplay everythingDisplay only the background layerDisplay only the document bitonal stencilDisplay only the foreground layerDisplay overprinted annotationsDisplay the text layerDjVu files (*.djvu, *.djv)|*.djvu;*.djv|All files|*Do you want to save your changes?Document metadataDon't display non-raster dataEdit document outline in an external editorEdit metadataEdit page text in an external editorEdit the document or page metadataEnter path to your favourite text editor.ErrorEtched inEtched outExternal edit failed: %sExternal editor…Fit &pageFit &widthFlatten textGo to pageHighlight colorHighlight color and opacityHyperlinksIncrease the magnificationJump to first document pageJump to last document pageJump to next document pageJump to page…Jump to previous document pageLicenseLineLine colorLine widthLine-specific propertiesLink: %sMagnify %d%%Main propertiesMore information about this programNeither display the foreground layer nor the background layerNo text layer to edit.NoneNumber of lines changed.One &to oneOpacityOpen &recentOpen a DjVu documentOutlineOvalOverprinted annotation (hyperlink) propertiesPage %(pageno)d of %(npages)dPage %d metadataPolygonPushpinQuit the applicationRectangleRefresh the windowRemove detailsRemove details from page textRemove whole document outlineSave the documentSaving documentSaving document failed: %sSaving the document, please wait…ScopeSet full resolution magnification.Set magnification to fit pageSet magnification to fit page widthSetup an external editorShadow inShadow outShapeShow &sidebarShow/hide the sidebarSolid colorStretch the image to the window sizeTarget frameTextText colorText-specific propertiesURIUnhandled exception: %sWidthXORZoom &inZoom &out[Text layer]allall pagesbookmarkscharcharacterscolumncolumnscurrent pagekeylinelinespageparaparagraphsregionregionsvaluewordwordsProject-Id-Version: djvusmooth 0.2.14 Report-Msgid-Bugs-To: Jakub Wilk POT-Creation-Date: 2013-02-20 17:57+0100 PO-Revision-Date: 2012-10-09 20:41+0200 Last-Translator: Jakub Wilk Language-Team: none Language: pl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit &O programie&TłoDodaj stronę do z&akładek&Zamknij&Kolor&Edycja&Zewnętrzny edytor&PlikPi&erwsza strona&Spłaszcz strukturę tekstu&Pierwszy plan&IdźI&dź do strony…Pomo&c&HiperłączaO&braz&Ostatnia strona&Metadane&Nowe hiperłącze…&Następna stronaDane &nierastrowe&Brak&Otwórz&Konspekt&Poprzednia strona&Właściwości…Za&kończ&Odśwież&Usuń&Usuń wszystkoZa&pisz&Ustawienia&Szablon&Rozciągnij&Tekst&Widok&Powiększenie(brak tytułu)O programie…Dodaj do konspektu dokumentuZawsze widocznyWystąpił nieoczekiwany błąd. Jeśli ta sytuacja się powtórzy, powiadom autora programu. StrzałkaAutorKolor tłaObramowanieNie można edytować tekstu ze strefami znakowymi.Zamknij dokumentKomentarzZmniejsz powiększeniePokaż wszystkoPokaż tylko tłoPokaż tylko dwubarwny szablon dokumentuPokaż tylko pierwszy planPokazuj obwódki wokół hiperłączPokaż warstwę tekstowąPliki DjVu (*.djvu, *.djv)|*.djvu;*.djv|Wszystkie pliki|*Czy chcesz zapisać zmiany?Metadane dokumentuNie pokazuj danych nierastrowychEdytuj konspekt dokumentu w zewnętrznym edytorzeEdytuj metadaneEdytuj tekst strony w zewnętrznym edytorzeEdytuj metadane dokumentu lub stronyPodaj ścieżkę do edytora tekstowego.BłądWklęsłyWypukłyZewnętrzna edycja nie powiodła się: %sZewnętrzny edytor…D&opasuj do oknaDopasuj &szerokośćSpłaszcz strukturę tekstuIdź do stronyKolor podświetleniaKolor i przezroczystość podświetleniaHiperłączaZwiększ powiększeniePrzejdź do pierwszej strony dokumentuPrzejdź do ostatniej strony dokumentuPrzejdź do następnej strony dokumentuPrzejdź do strony…Przejdź do poprzedniej strony dokumentuLicencjaLiniaKolor liniiSzerokość liniiWłaściwości liniiŁącze: %sPowiększ do %d%%WłaściwościWięcej informacji o tym programieNie pokazuj pierwszego planu ani tłaBrak warstwy tekstowej do edycji.BrakLiczba linii uległa zmianie.&Jeden do jednegoPrzezroczystośćOstatnio otwie&raneOtwórz dokument DjVuKonspektElipsaWłaściwości adnotacji (hiperłącza)Strona %(pageno)d z %(npages)dMetadane strony %dWielokątPinezkaZamknij aplikacjęProstokątOdśwież oknoUsuń szczegółyUsuń szczegóły z tekstuUsuń cały konspekt dokumentuZapisz dokumentZapisywanie dokumentuZapisanie dokumentu nie powiodło się: %sZapisywanie dokumentu, proszę czekać…ZakresDopasuj powiększenie do pełnej rozdzielczości.Dopasuj powiększenie do całej stronyDopasuj powiększenie do całej szerokości stronyUstawienia zewnętrznego edytoraCień wewnątrzCień na zewnątrzKształtPokaż panel &bocznyPokaż/ukryj panel bocznyJednolity kolorRozciągnij obraz do rozmiarów oknaRamka docelowaTekstKolor tekstuWłaściwości tekstuURINieobsłużony wyjątek: %sGrubośćXORPow&iększP&omniejsz[Warstwa tekstowa]wszystkowszystkie stronyzakładkiznakznakiłamłamybieżąca stronakluczlinialiniestronaakapitakapityregionregionywartośćsłowosłowadjvusmooth-0.2.14/locale/es/0000755000000000000000000000000012111517063015601 5ustar rootroot00000000000000djvusmooth-0.2.14/locale/es/LC_MESSAGES/0000755000000000000000000000000012111517063017366 5ustar rootroot00000000000000djvusmooth-0.2.14/locale/es/LC_MESSAGES/djvusmooth.mo0000644000000000000000000002263112111431167022131 0ustar rootroot00000000000000           %1 8 CM _j{   #=hL&7!J)l!3!#EW+u $")! ' 1<U h r }  9Ihp u  #=,CH am u-  &0CRp#"#6Z s }  $  5;? H R_ c mw |  ` HSZv~ /BZc jt     3GwWB4 HS ly-?Zw'/22K~   4C(^)+* ( / B "U x  ) , !!$!,! J!U!^!n!!!,! !! ! "" *"6"J")\"&"""$"("$# ,#,M#*z## # ### # $*%$P$_$e$%u$$$$$$ $$$$ % % %!%)%2%A%G%N%V%^% g%q%y%%%%A!KBe *+PjNXG6yh_ C{kI}[:3U]b9^g0iJft~/u(\Z`5@'W$d&"E%qD|-cTapm vY)R8,Vw1oM>zrLxlO2H #n=Q7.< 4?SFs;&About&Background&Bookmark this page&Close&Color&Edit&External editor&File&First page&Flatten&Foreground&Go&Go to page…&Help&Hyperlinks&Image&Last page&Metadata&New hyperlink…&Next page&Non-raster data&None&Open&Outline&Previous page&Properties…&Quit&Refresh&Remove&Remove all&Save&Settings&Stencil&Stretch&Text&View&Zoom(no title)About…Add the current to document outlineAlways visibleAn unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author. ArrowAuthorBackground colorBorderCannot edit text with character zones.Close the documentCommentDecrease the magnificationDisplay everythingDisplay only the background layerDisplay only the document bitonal stencilDisplay only the foreground layerDisplay overprinted annotationsDisplay the text layerDjVu files (*.djvu, *.djv)|*.djvu;*.djv|All files|*Do you want to save your changes?Document metadataDon't display non-raster dataEdit document outline in an external editorEdit metadataEdit page text in an external editorEdit the document or page metadataEnter path to your favourite text editor.ErrorEtched inEtched outExternal edit failed: %sExternal editor…Fit &pageFit &widthFlatten textGo to pageHighlight colorHighlight color and opacityHyperlinksIncrease the magnificationJump to first document pageJump to last document pageJump to next document pageJump to page…Jump to previous document pageLicenseLineLine colorLine widthLine-specific propertiesLink: %sMagnify %d%%Main propertiesMore information about this programNeither display the foreground layer nor the background layerNo text layer to edit.NoneNumber of lines changed.One &to oneOpacityOpen &recentOpen a DjVu documentOutlineOvalOverprinted annotation (hyperlink) propertiesPage %(pageno)d of %(npages)dPage %d metadataPolygonPushpinQuit the applicationRectangleRefresh the windowRemove detailsRemove details from page textRemove whole document outlineSave the documentSaving documentSaving document failed: %sSaving the document, please wait…ScopeSet full resolution magnification.Set magnification to fit pageSet magnification to fit page widthSetup an external editorShadow inShadow outShapeShow &sidebarShow/hide the sidebarSolid colorStretch the image to the window sizeTarget frameTextText colorText-specific propertiesURIUnhandled exception: %sWidthXORZoom &inZoom &out[Text layer]allall pagesbookmarkscharcharacterscolumncolumnscurrent pagekeylinelinespageparaparagraphsregionregionsvaluewordwordsProject-Id-Version: djvusmooth 0.2.14 Report-Msgid-Bugs-To: Jakub Wilk POT-Creation-Date: 2013-02-20 17:57+0100 PO-Revision-Date: 2012-05-21 00:20-0500 Last-Translator: C. Daniel Sanchez R. Language-Team: Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit &Acerca de&Fondo&Marcadores de esta página&Cerrar&Color&Editar&Editor externo&Archivo&Primer página&Aplanar&Frente&Ir&Ir a página...&Ayuda&Hipervínculos&Imagen&Última página&Metadatos&Nuevo hipervínculo...&Página siguiente&Datos ajenos al bitmap&Ninguno&Abrir&Bosquejo&Página anterior&Propiedades...&Salir&Refrescar&Eliminar&Eliminar todo&Guardar&Opciones&Patrón&Estirar&Texto&Ver&Zoom(sin título)Acerca de...Agregar la página actual al bosquejo del documentoSiempre visibleHa ocurrido una excepción no controlada. Idealmente, esto no debería ocurrir. Por favor, informe el error al autor. FlechaAutorColor de fondoBordeNo se puede editar el texto que esté en una zona para caracteres.Cerrar el documentoComentarioDisminuir la ampliaciónMostrar todoMostrar solo la capa del fondoMostrar solo el patrón bitonal del documentoMostrar solo la capa del frenteMostrar anotaciones encimaMostrar la capa de textoArchivos DjVu (*.djvu, *.djv)|*.djvu;*.djv|Todos los archivos|*¿Desea guardar los cambios?Metadatos del documentoNo mostrar los datos del bitmapEditar el bosquejo en un editor externoEditar metadatosEditar texto de la página en un editor externoEditar los metadatos del documento o de la páginaIngrese la ruta hacia su editor de texto favorito.ErrorGrabado inGrabado outFalló la edición externa: %sEditor externo...Ajustar &páginaAjustar &anchoAplanar el textoIr a la páginaRealzar colorRealzar color y opacidadHipervínculosIncrementar la ampliaciónSaltar a la primer página del documentoSaltar a la última página del documentoSaltar a la siguiente página del documentoSaltar a la página...Saltar a la página anterior del documentoLicenciaLíneaColor de la líneaAncho de la líneaPropiedades de la linea especificaVínculo: %sAumentar %d%%Propiedades principalesMás información acerca de este programaNo mostrar las capas del frente ni del fondoNo hay capa de texto para editar.NingunoNúmero de líneas cambiadas.Uno &a unoOpacidadAbrir &recienteAbrir un documento DjVuBosquejoÓvaloPropiedades de la anotación (hipervínculo)Página %(pageno)d de %(npages)dMetadatos de la página %dPolígonoPush-pinSalir de la aplicaciónRectánguloRefresca la ventanaEliminar detallesEliminar detalles del texto en la páginaEliminar bosquejo de todo el documentoGuardar el documentoGuardando documentoFalló el guardado del documento: %sGuardando documento, espere por favor...AlcanceAjustar a la máxima resoluciónAjustar ampliación al tamaño de la páginaAjustar ampliación al ancho de la páginaConfigurar un editor externoSombra inSombra outSiluetaMostrar &barra lateralMostrar/ocultar la barra lateralColor sólidoEstirar la imagen al tamaño de la ventanaMarco objetivoTextoColor del textoPropiedades para un texto específicoURIExcepción no controlada: %sAnchoXORZoom &inZoom &out[Capa de texto]TodoTodas las páginasMarcadorescaráctercaracterescolumnacolumnasPágina actualclavelínealíneaspáginapárrafopárrafosregiónregionesvalorpalabrapalabrasdjvusmooth-0.2.14/locale/ru/0000755000000000000000000000000012111517063015620 5ustar rootroot00000000000000djvusmooth-0.2.14/locale/ru/LC_MESSAGES/0000755000000000000000000000000012111517063017405 5ustar rootroot00000000000000djvusmooth-0.2.14/locale/ru/LC_MESSAGES/djvusmooth.mo0000644000000000000000000002651612111431215022150 0ustar rootroot00000000000000           %1 8 CM _j{   #=hL&7!J)l!3!#EW+u $")! ' 1<U h r }  9Ihp u  #=,CH am u-  &0CRp#"#6Z s }  $  5;? H R_ c mw |  s[r?z     &,Sn(~ $$4$Y ~&  3G [i }(!  YKk!$A7!:Y,88'3 =[ X % A!=Z!3! !!!>!#:" ^"#"2""""$ # 1#!>#/`#5#5#'#7$$\$ m$x$$>$$$!%70%=h%H% %0%-&G&`&$}&&&,&+&('B'%]'"''''6'A.(#p('(3(>(/)#>)0b)?).)!*!$* F*-Q*9**J*$+ :+E+>[++1+ ++++ ,',.,F, W,d,s,,,, , ,,,, --'- 8- C-A!KBe *+PjNXG6yh_ C{kI}[:3U]b9^g0iJft~/u(\Z`5@'W$d&"E%qD|-cTapm vY)R8,Vw1oM>zrLxlO2H #n=Q7.< 4?SFs;&About&Background&Bookmark this page&Close&Color&Edit&External editor&File&First page&Flatten&Foreground&Go&Go to page…&Help&Hyperlinks&Image&Last page&Metadata&New hyperlink…&Next page&Non-raster data&None&Open&Outline&Previous page&Properties…&Quit&Refresh&Remove&Remove all&Save&Settings&Stencil&Stretch&Text&View&Zoom(no title)About…Add the current to document outlineAlways visibleAn unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author. ArrowAuthorBackground colorBorderCannot edit text with character zones.Close the documentCommentDecrease the magnificationDisplay everythingDisplay only the background layerDisplay only the document bitonal stencilDisplay only the foreground layerDisplay overprinted annotationsDisplay the text layerDjVu files (*.djvu, *.djv)|*.djvu;*.djv|All files|*Do you want to save your changes?Document metadataDon't display non-raster dataEdit document outline in an external editorEdit metadataEdit page text in an external editorEdit the document or page metadataEnter path to your favourite text editor.ErrorEtched inEtched outExternal edit failed: %sExternal editor…Fit &pageFit &widthFlatten textGo to pageHighlight colorHighlight color and opacityHyperlinksIncrease the magnificationJump to first document pageJump to last document pageJump to next document pageJump to page…Jump to previous document pageLicenseLineLine colorLine widthLine-specific propertiesLink: %sMagnify %d%%Main propertiesMore information about this programNeither display the foreground layer nor the background layerNo text layer to edit.NoneNumber of lines changed.One &to oneOpacityOpen &recentOpen a DjVu documentOutlineOvalOverprinted annotation (hyperlink) propertiesPage %(pageno)d of %(npages)dPage %d metadataPolygonPushpinQuit the applicationRectangleRefresh the windowRemove detailsRemove details from page textRemove whole document outlineSave the documentSaving documentSaving document failed: %sSaving the document, please wait…ScopeSet full resolution magnification.Set magnification to fit pageSet magnification to fit page widthSetup an external editorShadow inShadow outShapeShow &sidebarShow/hide the sidebarSolid colorStretch the image to the window sizeTarget frameTextText colorText-specific propertiesURIUnhandled exception: %sWidthXORZoom &inZoom &out[Text layer]allall pagesbookmarkscharcharacterscolumncolumnscurrent pagekeylinelinespageparaparagraphsregionregionsvaluewordwordsProject-Id-Version: djvusmooth 0.2.14 Report-Msgid-Bugs-To: Jakub Wilk POT-Creation-Date: 2013-02-20 17:57+0100 PO-Revision-Date: 2013-02-21 18:33+0300 Last-Translator: Kyrill Detinov Language-Team: openSUSE Translation Team Language: ru MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit &О программе&ФонДобавить &закладку на эту страницу&Закрыть&Цвет&Правка&Внешний редактор&ФайлПерв&ая страница&Упростить структуру&Передний планП&ерейтиПерейти &на страницу…&Справка&Ссылки&ИзображениеПоследн&яя страница&Метаданные&Новая ссылка…&Следующая страница&Нерастровые данные&Ничего&Открыть&Оглавление&Предыдущая страница&Свойства…&Выйти&Обновить&Удалить&Удалить всё&Сохранить&Настройка&Шаблон&Растянуть&Текст&Вид&Масштаб(без названия)О программе…Добавить в оглавлениеВсегда показыватьНепредвиденная ситуация. Так не должно быть. Пожалуйста, сообщите об ошибке автору. СтрелкаАвторЦвет фонаРамкаНевозможно редактировать текст с зоной символовЗакрыть документПримечаниеУменьшить масштабПоказать всёПоказать только фонПоказать только битональный шаблонПоказать только передний планПоказывать комментарии (ссылки)Показать текстовый слойDjVu файлы (*.djvu, *.djv)|*.djvu;*.djv|All files|*Вы хотите сохранить изменения?Метаданные документаНе показывать нерастровые данныеРедактировать оглавление документа в редактореИзменить метаданныеРедактировать во внешнем редактореИзменить документ или метаданныеПуть к текстовому редакторуОшибкаВогнутаяВыпуклаяОшибка внешнего редактирования: %s&Внешний редактор…Страница &целикомПо &ширине страницыУпростить структуру текстаПерейти к страницеПодсветкаЦвет и прозрачностьСсылкиУвеличить масштабПерейти к первой страницеПерейти к последней страницеПерейти к следующей страницеПерейти на страницу…Перейти к предыдущей страницеЛицензияЛинияЦвет линииШирина линииПараметры, специфичные для строкиСсылка: %sУвеличение %d%%Основные свойстваБольше информации о программеНе показывать фон и передний планНет текстового слоя для редактированияНичегоКоличество строк изменено&Один к одномуПрозрачность&Недавние файлыОткрыть DjVu документОглавлениеОвалВид комментария (ссылки)Страница %(pageno)d из %(npages)dМетаданные страницы %dМногоугольникКанцелярская кнопкаВыйти из программыПрямоугольникОбновить окноУдалить деталиУменьшить детализацию текстаУдалить оглавление всего документаСохранить документСохранение документаНе удалось сохранить файл: %sСохранение документа, подождите…ОбластьФактический размерПоказать страницу целикомМасштабировать по ширине страницыВыбрать внешний редакторЗатемнение внутрьЗатемнение наружуФормаПоказать &боковую панельПоказать/скрыть боковую панельСплошная заливкаМасштабировать страницу по размеру окнаЦель фреймаТекстЦвет текстаПараметры, специфичные для текстаURIНепредвиденная ситуация: %sШиринаXORУ&величитьУ&меньшить[Текстовый слой]всевсе страницызакладкисимволсимволыстолбецстолбцытекущая страницаключстрокастрокистраницапараграфпараграфыобластьобластизначениесловословаdjvusmooth-0.2.14/MANIFEST.in0000644000000000000000000000035612111223775015501 0ustar rootroot00000000000000include COPYING include MANIFEST.in include djvusmooth edit-text include djvusmooth.py include doc/*.1 include doc/*.txt include doc/*.xml include doc/changelog include extra/* include po/*.po include private/* recursive-include lib *.py djvusmooth-0.2.14/doc/0000755000000000000000000000000012111517063014500 5ustar rootroot00000000000000djvusmooth-0.2.14/doc/djvusmooth.10000644000000000000000000000545512111517063016775 0ustar rootroot00000000000000.\" [created by setup.py sdist] '\" t .\" Title: djvusmooth .\" Author: Jakub Wilk .\" Generator: DocBook XSL Stylesheets v1.76.1 .\" Date: 02/21/2013 .\" Manual: djvusmooth manual .\" Source: djvusmooth 0.2.14 .\" Language: English .\" .TH "DJVUSMOOTH" "1" 2013-02-21 "djvusmooth 0\&.2\&.14" "djvusmooth manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" djvusmooth \- graphical editor for DjVu .SH "SYNOPSIS" .HP \w'\fBdjvusmooth\fR\ 'u \fBdjvusmooth\fR [\fIdjvu\-file\fR] .SH "DESCRIPTION" .PP djvusmooth is a graphical editor for DjVu files, which allows one to: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} edit document metadata, .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} edit document outline (bookmarks), .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} correct occasional errors in the hidden text layer\&. .RE .sp .SH "ENVIRONMENT" .PP \fIXDG_CONFIG_HOME\fR .RS 4 Base directory for configuration files\&. The default is $HOME/\&.config\&. .sp See \m[blue]\fIXDG Base Directory Specification\fR\m[]\&\s-2\u[1]\d\s+2 for details\&. .RE .SH "FILES" .PP \fI$XDG_CONFIG_HOME\fR/djvusmooth/djvusmooth\&.conf .RS 4 djvusmooth configuration file .RE .SH "BUGS" .SS "Portability issues" .PP djvusmooth allows one to create hyperlinks with shadow borders as thin as 1 pixel, as specified by the \m[blue]\fILizardtech DjVu Reference\fR\m[]\&\s-2\u[2]\d\s+2\&. However, some DjVu browsers do not accept shadow borders thinner than 3 pixels\&. .SS "Reporting bugs" .PP Please report bugs at: \m[blue]\fI\%https://bitbucket.org/jwilk/djvusmooth/issues\fR\m[] .SH "SEE ALSO" .PP \fBdjvu\fR(1) .SH "NOTES" .IP " 1." 4 XDG Base Directory Specification .RS 4 \m[blue]\fI\%http://standards.freedesktop.org/basedir-spec/latest/\fR\m[] .RE .IP " 2." 4 Lizardtech DjVu Reference .RS 4 \m[blue]\fI\%http://djvu.org/docs/DjVu3Spec.djvu\fR\m[] .RE djvusmooth-0.2.14/doc/djvusmooth.xml0000644000000000000000000000617312111177123017432 0ustar rootroot00000000000000 ]> &p; manual &p; Jakub Wilk jwilk@jwilk.net &p; 1 &version; &p; graphical editor for DjVu &p; djvu-file Description &p; is a graphical editor for DjVu files, which allows one to: edit document metadata, edit document outline (bookmarks), correct occasional errors in the hidden text layer. Environment XDG_CONFIG_HOME Base directory for configuration files. The default is $HOME/.config. See XDG Base Directory Specification for details. Files $XDG_CONFIG_HOME/djvusmooth/djvusmooth.conf &p; configuration file Bugs Portability issues &p; allows one to create hyperlinks with shadow borders as thin as 1 pixel, as specified by the Lizardtech DjVu Reference. However, some DjVu browsers do not accept shadow borders thinner than 3 pixels. Reporting bugs Please report bugs at: See also djvu 1 djvusmooth-0.2.14/doc/credits.txt0000644000000000000000000000027311767172511016713 0ustar rootroot00000000000000Since May 2009 djvusmooth development has been supported by the Polish Ministry of Science and Higher Education's grant no. N N519 384036 (2009 - 2012, https://bitbucket.org/jsbien/ndt). djvusmooth-0.2.14/doc/changelog0000644000000000000000000001641612111516474016367 0ustar rootroot00000000000000djvusmooth (0.2.14) unstable; urgency=low [ Jakub Wilk ] * Add “Open recent” submenu. https://bitbucket.org/jwilk/djvusmooth/issue/7 * Do not abort opening a new file if user chose to save the current one. * Check Python version at runtime. * Improve the manual page, as per man-pages(7) recommendations: - Remove the “AUTHOR” section. - Rename the “ENVIRONMENT VARIABLES” section as “ENVIRONMENT”. - Make “PORTABILITY” and “REPORTING BUGS” subsections of the “BUGS” section. * Improve the setup script: + Make “setup.py clean -a” remove compiled manual page (unless it was built by “setup.py sdist”). + Fix compatiblity with Python 2.5 and 2.6 (broken in 0.2.12). * Improve error handilng. [ C. Daniel Sanchez R. ] * Update the Spanish translation. [ Kyrill Detinov ] * Update the Russian translation. -- Jakub Wilk Thu, 21 Feb 2013 23:18:33 +0100 djvusmooth (0.2.13) unstable; urgency=low * Add keyboard shortcuts for “Zoom in” and “Zoom out”. https://bitbucket.org/jwilk/djvusmooth/issue/8 * Fix editing text in external editor: when a zone broader than line without sub-zones existed, djvusmooth incorrectly skipped it when importing text. Thanks to Janusz S. Bień for the bug report. * Fix segmentation fault on shutdown. Thanks to Janusz S. Bień for the bug report. -- Jakub Wilk Tue, 02 Oct 2012 12:02:42 +0200 djvusmooth (0.2.12) unstable; urgency=low [ Jakub Wilk ] * Install desktop file. * Rename menu item “Edit → Outline → Remove” to “… → Remove all”. Thanks to Maxim Leyenson for the bug report. * Use our own minimal XDG Base Directory implementation instead of PyXDG. [ Kyrill Detinov ] * Update the Russian translation. [ C. Daniel Sanchez R. ] * Add Spanish translation. -- Jakub Wilk Wed, 13 Jun 2012 20:53:49 +0200 djvusmooth (0.2.11) unstable; urgency=low * Let the setup.py script build and install binary message catalogs. * Don't throw exceptions when using “Fit width”, “Fit height” and “Stretch” zoom factors and no page is shown. Thanks to Sherwin Singer for the bug report. * Fix a typo in the manual page. -- Jakub Wilk Sun, 11 Dec 2011 22:57:54 +0100 djvusmooth (0.2.10) unstable; urgency=low * Let the setup.py script build and install manual pages. Thanks to Kyrill Detinov and Markus Baertschi for bug reports. * Fix the code that was supposed to disable the GUI while an external editor is running. Thanks to Mike Thiery for the bug report. * Fix the manual page: djvusmooth uses XDG_CONFIG_HOME rather than XDG_DATA_HOME. -- Jakub Wilk Fri, 18 Feb 2011 12:13:43 +0100 djvusmooth (0.2.9) unstable; urgency=low * Improve support for non-POSIX systems: + Windows: guess location of DjVuLibre DLLs and tools (requires python-djvulibre ≥ 0.3.3). + Windows: use the “Application Data” folder to store configuration files (unless the XDG_CONFIG_HOME environment variable is set). + Don't read configuration from ~/.DjVuSmooth (which is deprecated location anyway). + Don't rely on atomic renames when saving configuration files. * Reset the SIGCHLD signal to SIG_IGN on start. Thanks to Heinrich Schwietering for the bug report. http://bugs.debian.org/596232 * Fix support for external editors that use the overwrite-by-rename technique. Thanks to Markus Baertschi for the bug report. -- Jakub Wilk Wed, 19 Jan 2011 21:07:53 +0100 djvusmooth (0.2.8) unstable; urgency=low [ Kyrill Detinov ] * Update the Russian translation. [ Jakub Wilk ] * Handle directories with non-ASCII characters. http://bugs.debian.org/595002 * Fix editing line/arrow annotations. http://bugs.debian.org/595012 -- Jakub Wilk Tue, 31 Aug 2010 15:15:01 +0200 djvusmooth (0.2.7) unstable; urgency=low [ Kyrill Detinov ] * Add Russian translation. [ Jakub Wilk ] * Handle non-ASCII metadata keys. Thanks to Aleš Kapica for the bug report. -- Jakub Wilk Sat, 26 Jun 2010 14:34:19 +0200 djvusmooth (0.2.6) unstable; urgency=low * Add keyboard shortcut Ctrl+G for “Go to page…”. Thanks to Kyrill Detinov for the bug report and the patch. * Reopen document after save, so that it's possible to display non-cached pages. Thanks to Kyrill Detinov for the bug report. -- Jakub Wilk Tue, 15 Jun 2010 21:08:26 +0200 djvusmooth (0.2.5) unstable; urgency=low * Fix setup.py to install all the required packages. Thanks to Kyrill Detinov for the bug report. -- Jakub Wilk Thu, 08 Apr 2010 18:06:50 +0200 djvusmooth (0.2.4) unstable; urgency=low * Fix regression in handling sidebar events with wxWidgets 2.8. Thanks to Janusz S. Bień for the bug report. -- Jakub Wilk Wed, 31 Mar 2010 12:53:04 +0200 djvusmooth (0.2.3) unstable; urgency=low * Fix issues with “Overprinted annotation properties” dialog: + Allow to turn off the “allow visible” property. Thanks to Janusz S. Bień for the bug report. http://bugs.debian.org/574362 + Disable the “allow visible” checkbox if a hyperlink has no border. http://bugs.debian.org/574338 + Display real border width instead of hard-coded 1. + Use the term “width” rather than “thickness” consistently. * Document that using shadow border thinner than 3 pixels is not portable. http://bugs.debian.org/574361 -- Jakub Wilk Wed, 17 Mar 2010 21:59:58 +0100 djvusmooth (0.2.2) unstable; urgency=low * Fix crashes in the ‘Saving document’ and ‘Go to page’ dialogs. * Fix compatibility with wxWidgets 2.8. * Give a more helpful error messages if djvused is not available. -- Jakub Wilk Fri, 22 Jan 2010 19:36:03 +0100 djvusmooth (0.2.1) unstable; urgency=low * Don't crash on non-ASCII file names. Thanks to Jean-Christophe Heger for the bug report. -- Jakub Wilk Mon, 11 Jan 2010 18:27:28 +0100 djvusmooth (0.2.0) unstable; urgency=low [ Mateusz Turcza ] * Remember last visited directory. * Add Polish translation. [ Jakub Wilk ] * Follow the XDG Base Directory Specification. * Fix a typo in variable name that was causing random crashes. * Provide a manual page. -- Jakub Wilk Fri, 20 Nov 2009 22:45:08 +0100 djvusmooth (0.1.4) unstable; urgency=low * Fix some issues with external editing. -- Jakub Wilk Mon, 02 Mar 2009 23:36:21 +0100 djvusmooth (0.1.3) unstable; urgency=low * Don't assume UTF-8 locale and UTF-8-encoded text in DjVu files. -- Jakub Wilk Mon, 07 Jul 2008 01:12:34 +0200 djvusmooth (0.1.2) unstable; urgency=low * Fix bookmarking current page when there are no bookmarks. * Fix flattening text. -- Jakub Wilk Tue, 20 May 2008 12:49:30 +0200 djvusmooth (0.1.1) unstable; urgency=low * Don't require pkg-config nor develoment files of DjVuLibre. -- Jakub Wilk Wed, 14 May 2008 11:22:49 +0200 djvusmooth (0.1) unstable; urgency=low * Initial release. -- Jakub Wilk Tue, 06 May 2008 23:28:58 +0200 djvusmooth-0.2.14/lib/0000755000000000000000000000000012111517063014501 5ustar rootroot00000000000000djvusmooth-0.2.14/lib/varietes.py0000644000000000000000000001026212046305140016674 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import re import functools import warnings import weakref class NotOverriddenWarning(UserWarning): pass def not_overridden(f): r''' >>> warnings.filterwarnings('error', category=NotOverriddenWarning) >>> class B(object): ... @not_overridden ... def f(self, x, y): pass >>> class C(B): ... def f(self, x, y): return x * y >>> B().f(6, 7) Traceback (most recent call last): ... NotOverriddenWarning: `lib.varietes.B.f()` is not overridden >>> C().f(6, 7) 42 ''' @functools.wraps(f) def new_f(self, *args, **kwargs): cls = type(self) warnings.warn( '`%s.%s.%s()` is not overridden' % (cls.__module__, cls.__name__, f.__name__), category = NotOverriddenWarning, stacklevel = 2 ) return f(self, *args, **kwargs) return new_f def wref(o): r''' Return a weak reference to object. This is almost the same as `weakref.ref()`, but accepts `None` too. >>> class O(object): ... pass >>> x = O() >>> xref = wref(x) >>> xref() is x True >>> del x >>> xref() is None True >>> xref = wref(None) >>> xref() is None True ''' if o is None: ref = weakref.ref(set()) assert ref() is None else: ref = weakref.ref(o) return ref def indents_to_tree(lines): r''' >>> lines = [ ... 'bacon', ... ' egg', ... ' eggs', ... 'ham', ... ' sausage', ... ' spam', ... ' bacon', ... ' egg' ... ] >>> indents_to_tree(lines) [None, ['bacon', ['egg', ['eggs']]], ['ham', ['sausage'], ['spam', ['bacon']], ['egg']]] ''' root = [None] memo = [(-1, root)] for line in lines: old_len = len(line) line = line.lstrip() current = [line] indent = old_len - len(line) while memo[-1][0] >= indent: memo.pop() memo[-1][1].append(current) memo += (indent, current), return root URI_SPECIAL_CHARACTERS = \ ( ':/?#[]@' + # RFC 3986, `gen-delims` '!$&()*+,;=' + # RFC 3986, `sub-delims` '%' # RFC 3986, `pct-encoded` ) def fix_uri(s): r''' >>> uri = 'http://eggs.spam/' >>> fix_uri(uri) == uri True >>> uri = fix_uri('http://eggs.spam/eggs and spam/') >>> uri 'http://eggs.spam/eggs%20and%20spam/' >>> fix_uri(uri) == uri True ''' from urllib import quote if isinstance(s, unicode): s = s.encode('UTF-8') return quote(s, safe=URI_SPECIAL_CHARACTERS) replace_control_characters = re.compile('[\0-\x1f]+').sub _is_html_color = re.compile('^[#][0-9a-fA-F]{6}$').match def is_html_color(s): ''' >>> is_html_color('#000000') True >>> is_html_color('#ffffff') True >>> is_html_color('#FFFFFF') True >>> is_html_color('#c0c0f0') True >>> is_html_color('') False >>> is_html_color('#bcdefg') False >>> is_html_color('#ffffff ') False >>> is_html_color(' #ffffff') False ''' return bool(_is_html_color(s)) class idict(object): ''' >>> o = idict(eggs = 'spam', ham = 'eggs') >>> o lib.varietes.idict(eggs='spam', ham='eggs') >>> o.eggs 'spam' >>> o.ham 'eggs' >>> o.spam Traceback (most recent call last): ... AttributeError: 'idict' object has no attribute 'spam' ''' def __init__(self, **kwargs): self.__dict__.update(kwargs) def __repr__(self): return '%s.%s(%s)' % \ ( self.__module__, self.__class__.__name__, ', '.join('%s=%r' % (k, v) for k, v in self.__dict__.iteritems()) ) # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/dependencies.py0000644000000000000000000000461712032367545017523 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2010, 2011, 2012 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. ''' Checks for djvusmooth dependencies. ''' WX_VERSIONS = ('2.8-unicode', '2.6-unicode') DDJVU_API_MIN_VERSION = 26 PYTHON_DJVULIBRE_MIN_VERSION = (0, 1, 4) djvulibre_path = None def _check_signals(): # Protect from scanadf[0] and possibly other brain-dead software that set # SIGCHLD to SIG_IGN. # [0] http://bugs.debian.org/596232 import os import signal if os.name == 'posix': signal.signal(signal.SIGCHLD, signal.SIG_DFL) def _check_djvu(): # On Windows, special measures may be needed to find the DjVuLibre DLL. global djvulibre_path try: from djvu.dllpath import set_dll_search_path except ImportError: pass else: djvulibre_path = set_dll_search_path() try: from djvu.decode import __version__ as djvu_decode_version except ImportError, ex: raise ImportError('%s; perhaps python-djvulibre is not installed' % (ex,)) python_djvu_decode_version, ddjvu_api_version = djvu_decode_version.split('/') if int(ddjvu_api_version) < DDJVU_API_MIN_VERSION: raise ImportError('DjVuLibre with DDJVU API >= %d is required' % DDJVU_API_MIN_VERSION) python_djvu_decode_version = map(int, python_djvu_decode_version.split('.')) if tuple(python_djvu_decode_version) < PYTHON_DJVULIBRE_MIN_VERSION: raise ImportError('python-djvulibre >= %s is required' % ('.'.join(map(str, PYTHON_DJVULIBRE_MIN_VERSION)))) def _check_wx(): try: import wxversion except ImportError, ex: raise ImportError('%s; perhaps wxPython is not installed' % (ex,)) if not wxversion.checkInstalled(WX_VERSIONS): raise ImportError('wxPython 2.6 or 2.8 with Unicode support is required') wxversion.select(WX_VERSIONS) _check_signals() del _check_signals try: _check_djvu() finally: del _check_djvu try: _check_wx() finally: del _check_wx # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/pkgconfig.py0000644000000000000000000000205211573212252017024 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import subprocess class IOError(IOError): pass class Package(object): def __init__(self, name): self._name = name def variable(self, variable_name): pkgconfig = subprocess.Popen( ['pkg-config', '--variable=' + str(variable_name), self._name], stdout = subprocess.PIPE, stderr = subprocess.PIPE ) stdout, stderr = pkgconfig.communicate() if pkgconfig.returncode: raise IOError(stderr.strip()) return stdout.strip() # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/external_editor.py0000644000000000000000000000504712111505471020251 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2011, 2013 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import os.path import subprocess import tempfile class temporary_file(object): def __init__(self, suffix='', prefix='tmp', dir=None, text=False): fd, self.name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir, text=text) self.mode = 'r+' + 'bt'[bool(text)] self.fp = os.fdopen(fd, self.mode) def _reopen(self): if self.fp is None: self.fp = open(self.name, self.mode) def flush(self): if self.fp is None: return self.fp.close() self.fp = None def close(self): if self.name is None: return self.flush() os.remove(self.name) self.name = None def seek(self, offset, whence=0): self._reopen() self.fp.seek(offset, whence) def write(self, s): self._reopen() self.fp.write(s) def read(self, n=-1): self._reopen() return self.fp.read(n) def __iter__(self): self._reopen() return iter(self.fp) def __enter__(self): return self def __exit__(self, exc, value, tb): self.close() class Editor(object): def __call__(self, file_name): raise NotImplementedError class RunMailcapEditor(object): def __call__(self, file_name): file_name = os.path.abspath(file_name) edit = subprocess.Popen( ['edit', 'text/plain:%s' % file_name], stdin = subprocess.PIPE, stdout = subprocess.PIPE, ) edit.stdin.close() edit.stdout.close() edit.wait() class CustomEditor(object): def __init__(self, command, *extra_args): self._command = [command] self._command += extra_args def __call__(self, file_name): file_name = os.path.abspath(file_name) edit = subprocess.Popen( self._command + [file_name], stdin = subprocess.PIPE, stdout = subprocess.PIPE, ) edit.stdin.close() edit.stdout.close() edit.wait() # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/config.py0000644000000000000000000001045412035074101016320 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2009, 2010, 2011, 2012 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import errno import os class xdg(object): ''' tiny replacement for PyXDG's xdg.BaseDirectory ''' xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or '' if os.name == 'nt' and xdg_config_home == '': # On Windows, use the “Application Data” folder if XDG_CONFIG_HOME is # not set. xdg_config_home = os.environ.get('APPDATA') or '' if not os.path.isabs(xdg_config_home): xdg_config_home = os.path.join(os.path.expanduser('~'), '.config') xdg_config_dirs = os.environ.get('XDG_CONFIG_DIRS') or '/etc/xdg' xdg_config_dirs = ( [xdg_config_home] + filter(os.path.abspath, xdg_config_dirs.split(os.path.pathsep)) ) @classmethod def save_config_path(xdg, resource): path = os.path.join(xdg.xdg_config_home, resource) try: os.makedirs(path, 0700) except OSError: if not os.path.isdir(path): raise return path @classmethod def load_config_paths(xdg, resource): for config_dir in xdg.xdg_config_dirs: path = os.path.join(config_dir, resource) if os.path.exists(path): yield path class Config(object): def __init__(self, resource, legacy_path=None): self._dirty = False self._data = {} self._resource = resource for directory in xdg.load_config_paths(resource): self._load(os.path.join(directory, '%s.conf' % resource)) self._legacy_path = legacy_path if legacy_path is not None: self._load(legacy_path) self._dirty = True def read(self, key, default): return self._data.get(key, default) def read_int(self, key, default): return int(self.read(key, default)) def read_bool(self, key, default): return bool(self.read_int(key, default)) def __setitem__(self, key, value): self._data[key] = value self._dirty = True def del_array(self, key): keys_to_delete = frozenset( k for k in self._data if k.startswith(key + '[') ) for k in keys_to_delete: del self._data[k] def _load(self, path): try: file = open(path, 'r') except IOError, ex: if ex.errno == errno.ENOENT: return try: for line in file: line = line.rstrip() try: key, value = line.split('=', 1) except ValueError: pass value = value.decode('UTF-8') self._data[key] = value finally: file.close() def flush(self): if not self._dirty: return directory = xdg.save_config_path(self._resource) path = os.path.join(directory, '%s.conf' % self._resource) tmp_path = path + '.tmp' file = open(tmp_path, 'w') try: for key, value in sorted(self._data.iteritems()): if isinstance(value, unicode): value = value.encode('UTF-8') file.write('%s=%s\n' % (key, value)) file.flush() os.fsync(file.fileno()) finally: file.close() if os.name != 'posix' and os.path.exists(path): # Atomic renames might be not supported. backup_path = path + '.bak' os.rename(path, backup_path) os.rename(tmp_path, path) os.remove(backup_path) else: os.rename(tmp_path, path) if self._legacy_path is not None: try: os.remove(self._legacy_path) except OSError, ex: if ex.errno != errno.ENOENT: raise self._dirty = False # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/text/0000755000000000000000000000000012111517063015465 5ustar rootroot00000000000000djvusmooth-0.2.14/lib/text/mangle.py0000644000000000000000000001110112032367434017303 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2012 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import itertools import djvu.sexpr import djvu.const from djvusmooth.text.levenshtein import distance def mangle(s, t, input): s = s.decode('UTF-8', 'replace') t = t.decode('UTF-8', 'replace') if len(input) == 1 and isinstance(input[0], djvu.sexpr.StringExpression): yield t return input = tuple(map(lambda o: o.value, item) for item in input) j = 0 current_word = '' for item in input: item[5] = item[5].decode('UTF-8', 'replace') for item in input: item[5] = len(item[5]) input_iter = iter(input) input_head = input_iter.next() for i, ot, to in distance(s, t): while i > j: if s[j] == ' ': yield input_head[:5] + [current_word] input_head = input_iter.next() current_word = '' else: current_word += s[j] j += 1 if to == ' ': new_len = len(current_word) old_len = input_head[5] if new_len >= old_len: old_len = new_len * 2 old_width = 0.0 + input_head[3] - input_head[1] spc_width = old_width / old_len new_width = old_width / old_len * new_len yield input_head[:3] + [int(input_head[1] + new_width), input_head[4], current_word] input_head[1] += int(new_width + spc_width) input_head[5] = old_len current_word = '' elif ot == ' ': current_word += to next_head = input_iter.next() input_head[2] = min(next_head[2], input_head[2]) input_head[3] = max(next_head[3], input_head[3]) input_head[4] = next_head[4] input_head[5] = input_head[5] + next_head[5] + len(to) else: current_word += to j = j + len(ot) len_s = len(s) while j < len_s: if s[j] == ' ': yield input_head[:5] + [current_word] input_head = input_iter.next() current_word = '' else: current_word += s[j] j += 1 yield input_head[:5] + [current_word] def linearize_for_export(expr): if expr[0].value == djvu.const.TEXT_ZONE_CHARACTER: raise CharacterZoneFound if len(expr) == 6 and isinstance(expr[5], djvu.sexpr.StringExpression): yield expr[5].value elif expr[0].value == djvu.const.TEXT_ZONE_LINE: yield ' '.join(linearize_for_export(expr[5:])) else: for subexpr in expr: if not isinstance(subexpr, djvu.sexpr.ListExpression): continue for item in linearize_for_export(subexpr): yield item def linearize_for_import(expr): if expr[0].value == djvu.const.TEXT_ZONE_CHARACTER: raise CharacterZoneFound if len(expr) == 6 and isinstance(expr[5], djvu.sexpr.StringExpression): yield expr elif expr[0].value == djvu.const.TEXT_ZONE_LINE: yield expr else: for subexpr in expr: if not isinstance(subexpr, djvu.sexpr.ListExpression): continue for item in linearize_for_import(subexpr): yield item def export(sexpr, stream): for line in linearize_for_export(sexpr): print >>stream, line def import_(sexpr, stdin): exported = tuple(linearize_for_export(sexpr)) inputs = tuple(linearize_for_import(sexpr)) stdin = tuple(line for line in stdin) if len(exported) != len(stdin): raise LengthChanged assert len(exported) == len(inputs) == len(stdin) dirty = False for n, line, xline, input in itertools.izip(itertools.count(1), stdin, exported, inputs): line = line.rstrip('\n') if line != xline: input[5:] = list(mangle(xline, line, input[5:])) dirty = True if not dirty: raise NothingChanged return sexpr class NothingChanged(Exception): pass class LengthChanged(Exception): pass class CharacterZoneFound(Exception): pass __all__ = \ ( 'import_', 'export', 'NothingChanged', 'CharacterZoneFound', 'LengthChanged' ) # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/text/levenshtein.py0000644000000000000000000000425711573212252020376 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. class Operation(object): def __init__(self, cost): self.cost = cost def __cmp__(self, other): return cmp(self.cost, other.cost) def __repr__(self): return '%s(cost=%r)' % (self.__class__.__name__, self.cost) def __add__(self, other): return self.cost + other class Delete(Operation): pass class Insert(Operation): pass class Substitute(Operation): pass class Drop(Operation): pass class Append(Operation): pass def distance(s, t): len_s = len(s) len_t = len(t) d = [[None for j in xrange(len_t + 1)] for i in xrange(len_s + 1)] for i in xrange(len_s + 1): d[i][0] = Drop(i) for j in xrange(len_t + 1): d[0][j] = Append(j) for i in xrange(1, len_s + 1): for j in xrange(1, len_t + 1): subst_cost = int(s[i-1] != t[j-1]) d[i][j] = min( Delete(d[i-1][j] + 1), Insert(d[i][j-1] + 1), Substitute(d[i-1][j-1] + subst_cost) ) i = len_s j = len_t ops = [] while True: op = d[i][j] if isinstance(op, Delete): i -= 1 ops += (i, s[i], ''), elif isinstance(op, Insert): j -= 1 ops += (i, '', t[j]), elif isinstance(op, Substitute): i -= 1 j -= 1 if s[i] != t[j]: ops += (i, s[i], t[j],), elif isinstance(op, Append): ops += ((i, '', t[jj]) for jj in xrange(j-1, -1, -1)) break elif isinstance(op, Drop): ops += ((ii, s[ii], '') for ii in xrange(i-1, -1, -1)) break return reversed(ops) __all__ = 'distance', # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/text/__init__.py0000644000000000000000000000000011570151711017566 0ustar rootroot00000000000000djvusmooth-0.2.14/lib/djvused.py0000644000000000000000000001156111573212252016526 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2010, 2011 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import pkgconfig import os.path import subprocess import threading from djvu.sexpr import Expression, Symbol djvused_path = None if os.name =='nt': from . import dependencies djvused_path = os.path.join(dependencies.djvulibre_path, 'djvused.exe') else: try: djvulibre_bin_path = os.path.join(pkgconfig.Package('ddjvuapi').variable('exec_prefix'), 'bin') except (IOError, OSError): pass else: djvused_path = os.path.join(djvulibre_bin_path, 'djvused') if djvused_path is None or not os.path.isfile(djvused_path): # Let's hope it's within $PATH... djvused_path = 'djvused' DJVUSED_PATH = djvused_path def _djvused_usability_check(): try: djvused = subprocess.Popen([DJVUSED_PATH], stdout = subprocess.PIPE, stderr = subprocess.PIPE) djvused.communicate() if djvused.returncode == 10: return except (IOError, OSError): pass raise IOError('%r does not seem to be usable' % DJVUSED_PATH) _djvused_usability_check() class IOError(IOError): pass class StreamEditor(object): def __init__(self, file_name, autosave = False): self._file_name = file_name self._commands = [] self._autosave = autosave def clone(self): return StreamEditor(self._filename, self._autosave) def _add(self, *commands): for command in commands: if not isinstance(command, str): raise TypeError self._commands += commands def select_all(self): self._add('select') def select(self, page_id): self._add('select %s' % page_id) def select_shared_annotations(self): self._add('select-shared-ant') def create_shared_annotations(self): self._add('create-shared-ant') def set_annotations(self, annotations): self._add('set-ant') for annotation in annotations: self._add(str(annotation)) self._add('.') def remove_annotations(self): self._add('remove-ant') def print_annotations(self): self._add('print-ant') def set_metadata(self, meta): self._add('set-meta') for key, value in meta.iteritems(): value = unicode(value) self._add('%s\t%s' % (Expression(Symbol(key)), Expression(value))) self._add('.') def remove_metadata(self): self._add('remove-meta') def set_text(self, text): if text is None: self.remove_text() else: self._add('set-txt', str(text), '.') def remove_text(self): self._add('remove-txt') def set_outline(self, outline): if outline is None: outline = '' self._add('set-outline', str(outline), '.') def set_thumbnails(self, size): self._add('set-thumbnails %d' % size) def remove_thumbnails(self): self._add('remove-thumbnails') def set_page_title(self, title): self._add('set-page-title %s' % Expression(title)) def save_page(self, file_name, include = False): command = 'save-page' if include: command += '-with' self._add('%s %s' % command, file_name) def save_as_bundled(self, file_name): self._add('save-bundled %s' % file_name) def save_as_indirect(self, file_name): self._add('save-indirect %s' % file_name) def save(self): self._add('save') def _reader_thread(self, fo, result): result[0] = fo.read(), def _execute(self, commands, save = False): args = [DJVUSED_PATH] if save: args += '-s', args += self._file_name, djvused = subprocess.Popen(args, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) result = [None] reader_thread = threading.Thread(target = self._reader_thread, args = (djvused.stdout, result)) reader_thread.setDaemon(True) reader_thread.start() stdin = djvused.stdin for command in commands: stdin.write(command + '\n') stdin.close() djvused.wait() if djvused.returncode: raise IOError(djvused.stderr.readline().lstrip('* ')) reader_thread.join() return result[0] def commit(self): try: return self._execute(self._commands, save = self._autosave) finally: self._commands = [] # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/models/0000755000000000000000000000000012111517063015764 5ustar rootroot00000000000000djvusmooth-0.2.14/lib/models/annotations.py0000644000000000000000000006704511573212252020712 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2010 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. ''' Models for annotations. See Lizardtech DjVu Reference (DjVu 3): - 3.3.1 Annotations. - 8.3.4 Annotation chunk. ''' import weakref import itertools import djvu.const import djvu.sexpr import djvu.decode from djvusmooth.models import MultiPageModel, SHARED_ANNOTATIONS_PAGENO from djvusmooth.varietes import not_overridden, is_html_color class AnnotationSyntaxError(ValueError): pass class MapAreaSyntaxError(AnnotationSyntaxError): pass def parse_color(color, allow_none=False): if allow_none and color is None: return color = str(color).upper() if not is_html_color(color): raise ValueError('%r is not a valid color' % (color,)) return color class PageAnnotationsCallback(object): @not_overridden def notify_node_change(self, node): pass @not_overridden def notify_node_select(self, node): pass @not_overridden def notify_node_deselect(self, node): pass @not_overridden def notify_node_add(self, node): pass @not_overridden def notify_node_delete(self, node): pass @not_overridden def notify_node_replace(self, node, other_node): pass class Border(object): @not_overridden def __init__(self, *args, **kwargs): raise NotImplementedError @not_overridden def _get_sexpr(self): raise NotImplementedError @apply def sexpr(): def get(self): return self._get_sexpr() return property(get) class NoBorder(Border): def __init__(self): pass def _get_sexpr(self): return djvu.sexpr.Expression((djvu.const.MAPAREA_BORDER_NONE,)) class XorBorder(Border): def __init__(self): pass def _get_sexpr(self): return djvu.sexpr.Expression((djvu.const.MAPAREA_BORDER_XOR,)) class SolidBorder(Border): def __init__(self, color): self._color = parse_color(color) @property def color(self): return self._color def _get_sexpr(self): return djvu.sexpr.Expression((djvu.const.MAPAREA_BORDER_SOLID_COLOR, djvu.sexpr.Symbol(self._color))) class BorderShadow(Border): def __init__(self, width): width = int(width) if not djvu.const.MAPAREA_SHADOW_BORDER_MIN_WIDTH <= width <= djvu.const.MAPAREA_SHADOW_BORDER_MAX_WIDTH: raise ValueError self._width = width @property def width(self): return self._width def _get_sexpr(self): return djvu.sexpr.Expression((self.SYMBOL, self._width)) class BorderShadowIn(BorderShadow): SYMBOL = djvu.const.MAPAREA_BORDER_SHADOW_IN class BorderShadowOut(BorderShadow): SYMBOL = djvu.const.MAPAREA_BORDER_SHADOW_OUT class BorderEtchedIn(BorderShadow): SYMBOL = djvu.const.MAPAREA_BORDER_ETCHED_IN class BorderEtchedOut(BorderShadow): SYMBOL = djvu.const.MAPAREA_BORDER_ETCHED_OUT class Annotations(MultiPageModel): def get_page_model_class(self, n): if n == SHARED_ANNOTATIONS_PAGENO: return SharedAnnotations else: return PageAnnotations class Annotation(object): @classmethod def from_sexpr(cls, sexpr, owner): raise NotImplementedError @not_overridden def _get_sexpr(self): raise NotImplementedError @apply def sexpr(): def get(self): return self._get_sexpr() return property(get) class OtherAnnotation(Annotation): def __init__(self, sexpr, owner=None): self._sexpr = sexpr @classmethod def from_sexpr(cls, sexpr, owner): return cls(sexpr, owner) def _get_sexpr(self): return self._sexpr class MapArea(Annotation): DEFAULT_ARGUMENTS = NotImplemented @classmethod def can_have_shadow_border(cls): return False def replace(self, other): if not isinstance(other, MapArea): raise TypeError if self._owner is None: return self._owner.replace_maparea(self, other) other._owner = self._owner def delete(self): former_owner = self._owner if former_owner is None: return self._owner.remove_maparea(self) self._owner = None def insert(self, owner): owner.add_maparea(self) self._owner = owner @classmethod def from_maparea(cls, maparea, owner): if maparea is None: uri = comment = '' target = None else: uri = maparea.uri target = maparea.target comment = maparea.comment self = cls( *cls.DEFAULT_ARGUMENTS, **dict( uri = uri, target = target, comment = comment, owner = owner ) ) if maparea is None: self._border = NoBorder() else: self._border = maparea.border if isinstance(self._border, BorderShadow) and not cls.can_have_shadow_border(): self._border = NoBorder() self._border_always_visible = maparea is not None and maparea.border_always_visible is True return self @classmethod def from_sexpr(cls, sexpr, owner): sexpr = iter(sexpr) try: symbol = sexpr.next().value if symbol is not djvu.const.ANNOTATION_MAPAREA: raise MapAreaSyntaxError uri = sexpr.next().value if isinstance(uri, tuple): symbol, uri, target = uri if symbol is not djvu.const.MAPAREA_URI: raise MapAreaSyntaxError target = target.decode('UTF-8', 'replace') else: target = None uri = uri.decode('UTF-8') comment = sexpr.next().value.decode('UTF-8', 'replace') shape = sexpr.next() shape_iter = iter(shape) cls = MAPAREA_SHAPE_TO_CLASS[shape_iter.next().value] args = [int(item) for item in shape_iter] kwargs = dict(uri = uri, target = target, comment = comment, owner = owner) for item in sexpr: try: key, value = item key = key.value value = value.value except ValueError: key, = item key = key.value value = True kwargs['s_%s' % key] = value except (StopIteration, TypeError), ex: raise MapAreaSyntaxError(ex) return cls(*args, **kwargs) @not_overridden def _get_sexpr_area(self): raise NotImplementedError @not_overridden def _get_sexpr_extra(self): return () def _get_sexpr_border(self): if self._border is None: return return self._border.sexpr def _get_sexpr(self): if self._target is None: uri_part = self._uri else: uri_part = (djvu.const.MAPAREA_URI, self._uri, self._target) border_part = self._get_sexpr_border() if border_part is None: border_part = () else: border_part = (border_part,) if self.border_always_visible is True: border_part += (djvu.const.MAPAREA_BORDER_ALWAYS_VISIBLE,), return djvu.sexpr.Expression( ( djvu.const.ANNOTATION_MAPAREA, uri_part, self._comment, self._get_sexpr_area(), ) + border_part + self._get_sexpr_extra() ) def _parse_border_options(self, options): self._border = None try: del options['s_%s' % djvu.const.MAPAREA_BORDER_NONE] except KeyError: pass else: self._border = NoBorder() try: del options['s_%s' % djvu.const.MAPAREA_BORDER_XOR] except KeyError: pass else: self._border = XorBorder() try: self._border = SolidBorder(parse_color(options.pop('s_%s' % djvu.const.MAPAREA_BORDER_SOLID_COLOR))) except KeyError: pass except (TypeError, ValueError), ex: raise MapAreaSyntaxError(ex) def _parse_shadow_border_options(self, options): for border_style in djvu.const.MAPAREA_SHADOW_BORDERS: try: width = self._parse_width(options.pop('s_%s' % border_style)) except KeyError: continue except (TypeError, ValueError), ex: raise MapAreaSyntaxError(ex) cls = MAPAREA_SHADOW_BORDER_TO_CLASS[border_style] self._border = cls(width) def _parse_border_always_visible(self, options): try: del options['s_%s' % djvu.const.MAPAREA_BORDER_ALWAYS_VISIBLE] except KeyError: self._border_always_visible = False else: self._border_always_visible = True def _check_invalid_options(self, options): for option in options: if option.startswith('s_'): raise MapAreaSyntaxError('%r is invalid option for %r annotations' % (option[2:], self.SYMBOL)) if options: raise ValueError('%r is invalid keyword argument for this function' % (iter(options).next(),)) def _parse_common_options(self, options): self._uri = options.pop('uri') self._target = options.pop('target') self._comment = options.pop('comment') self._owner = options.pop('owner') @apply def uri(): def get(self): return self._uri def set(self, value): self._uri = value self._notify_change() return property(get, set) @apply def target(): def get(self): return self._target def set(self, value): self._target = value self._notify_change() return property(get, set) @apply def comment(): def get(self): return self._comment def set(self, value): self._comment = value self._notify_change() return property(get, set) @not_overridden def _set_rect(self, rect): raise NotImplementedError @not_overridden def _get_rect(self): raise NotImplementedError def _get_orign(self): return self._get_rect()[:2] def _set_origin(self, (x1, y1)): x0, y0, w, h = self._get_rect() self._set_rect((x1, y1, w, h)) @apply def origin(): def get(self): return self._get_origin() def set(self, rect): self._set_origin(rect) self._notify_change() return property(get, set) @apply def rect(): def get(self): return self._get_rect() def set(self, rect): self._set_rect(rect) self._notify_change() return property(get, set) @apply def border_always_visible(): def get(self): return self._border_always_visible def set(self, value): self._border_always_visible = value self._notify_change() return property(get, set) @apply def border(): def get(self): return self._border def set(self, border): if not isinstance(border, Border): raise TypeError if not self.can_have_shadow_border() and isinstance(border, BorderShadow): raise TypeError self._border = border self._notify_change() return property(get, set) def _parse_width(self, w): w = int(w) if w < 0: raise ValueError return w def _notify_change(self): if self._owner is None: return return self._owner.notify_node_change(self) def notify_select(self): if self._owner is None: return self._owner.notify_node_select(self) def notify_deselect(self): if self._owner is None: return self._owner.notify_node_deselect(self) class XywhMapArea(MapArea): DEFAULT_ARGUMENTS = (0, 0, 32, 32) @classmethod def from_maparea(cls, maparea, owner): self = super(XywhMapArea, cls).from_maparea(maparea, owner) if maparea is not None: self._set_rect(maparea.rect) return self def _parse_xywh(self, x, y, w, h): x, y, w, h = map(int, (x, y, w, h)) if w <= 0 or h <= 0: raise ValueError self._x, self._y, self._w, self._h = x, y, w, h def _set_rect(self, (x, y, w, h)): self._parse_xywh(x, y, w, h) def _get_rect(self): return self._x, self._y, self._w, self._h def _get_sexpr_area(self): return (self.SYMBOL, self._x, self._y, self._w, self._h) class RectangleMapArea(XywhMapArea): SYMBOL = djvu.const.MAPAREA_SHAPE_RECTANGLE @classmethod def can_have_shadow_border(cls): return True @classmethod def from_maparea(cls, maparea, owner): self = super(RectangleMapArea, cls).from_maparea(maparea, owner) if isinstance(maparea, RectangleMapArea): self._opacity = maparea.opacity self._highlight_color = maparea.highlight_color return self def __init__(self, x, y, w, h, **options): self._parse_xywh(x, y, w, h) self._parse_border_options(options) self._parse_shadow_border_options(options) self._parse_border_always_visible(options) try: self._highlight_color = parse_color(options.pop('s_%s' % djvu.const.MAPAREA_HIGHLIGHT_COLOR)) except KeyError: self._highlight_color = None except (TypeError, ValueError), ex: raise MapAreaSyntaxError(ex) try: self._opacity = int(options.pop('s_%s' % djvu.const.MAPAREA_OPACITY)) if not (djvu.const.MAPAREA_OPACITY_MIN <= self._opacity <= djvu.const.MAPAREA_OPACITY_MAX): raise MapAreaSyntaxError except KeyError: self._opacity = djvu.const.MAPAREA_OPACITY_DEFAULT except (TypeError, ValueError), ex: raise MapAreaSyntaxError(ex) self._parse_common_options(options) self._check_invalid_options(options) def _get_sexpr_extra(self): result = [] if self._opacity != djvu.const.MAPAREA_OPACITY_DEFAULT: result += (djvu.const.MAPAREA_OPACITY, self._opacity), if self._highlight_color is not None: result += (djvu.const.MAPAREA_HIGHLIGHT_COLOR, djvu.sexpr.Symbol(self._highlight_color)), return tuple(result) @apply def opacity(): def get(self): return self._opacity def set(self, value): value = int(value) if not (djvu.const.MAPAREA_OPACITY_MIN <= self._opacity <= djvu.const.MAPAREA_OPACITY_MAX): raise ValueError self._opacity = value self._notify_change() return property(get, set) @apply def highlight_color(): def get(self): return self._highlight_color def set(self, value): self._highlight_color = parse_color(value, allow_none=True) self._notify_change() return property(get, set) class OvalMapArea(XywhMapArea): SYMBOL = djvu.const.MAPAREA_SHAPE_OVAL def __init__(self, x, y, w, h, **options): self._parse_xywh(x, y, w, h) self._parse_border_options(options) self._parse_border_always_visible(options) self._parse_common_options(options) self._check_invalid_options(options) def _get_sexpr_extra(self): return () class PolygonMapArea(MapArea): SYMBOL = djvu.const.MAPAREA_SHAPE_POLYGON DEFAULT_ARGUMENTS = (0, 0, 32, 0, 32, 32, 0, 32, 32, 32) def _get_sexpr_area(self): return djvu.sexpr.Expression(itertools.chain( (self.SYMBOL,), itertools.chain(*self._coords) )) def _get_sexpr_extra(self): return () def _get_rect(self): x0 = y0 = 1e999 x1 = y1 = -1e999 for (x, y) in self._coords: if x < x0: x0 = x if y < y0: y0 = y if x > x1: x1 = x if y > y1: y1 = y w = x1 - x0 h = y1 - y0 if w <= 0: w = 1 if h <= 0: h = 1 return (x0, y0, w, h) def _set_rect(self, rect): xform = djvu.decode.AffineTransform(self.rect, rect) self._coords = map(xform, self._coords) self._notify_change() @classmethod def from_maparea(cls, maparea, owner): self = super(PolygonMapArea, cls).from_maparea(maparea, owner) if isinstance(maparea, PolygonMapArea): self._coords = maparea.coordinates elif maparea is not None: self._set_rect(maparea.rect) return self @apply def coordinates(): def get(self): return self._coords return property(get) def __init__(self, *coords, **options): n_coords = len(coords) if n_coords & 1: raise ValueError('polygon with %2.f vertices' % (n_coords / 2.0)) if n_coords < 6: raise ValueError('polygon with %d vertices' % (n_coords // 2)) coords = (int(x) for x in coords) self._coords = zip(coords, coords) self._parse_border_options(options) self._parse_border_always_visible(options) self._parse_common_options(options) self._check_invalid_options(options) class LineMapArea(MapArea): SYMBOL = djvu.const.MAPAREA_SHAPE_LINE DEFAULT_ARGUMENTS = (0, 0, 32, 32) def _get_sexpr_area(self): return djvu.sexpr.Expression((self.SYMBOL, self._x0, self._y0, self._x1, self._y1)) def _get_sexpr_extra(self): result = [] if self._line_arrow: result += (djvu.const.MAPAREA_ARROW,), if self._line_width != djvu.const.MAPAREA_LINE_MIN_WIDTH: result += (djvu.const.MAPAREA_LINE_WIDTH, self._line_width), if self._line_color != djvu.const.MAPAREA_LINE_COLOR_DEFAULT: result += (djvu.const.MAPAREA_LINE_COLOR, self._line_color), return tuple(result) def _get_rect(self): x0, y0, x1, y1 = self._x0, self._y0, self._x1, self._y1 return (min(x0, x1), min(y0, y1), abs(x0 - x1), abs(y0 - y1)) def _set_rect(self, rect): xform = djvu.decode.AffineTransform(self.rect, rect) self._x0, self._y0 = xform((self._x0, self._y0)) self._x1, self._y1 = xform((self._x1, self._y1)) @apply def point_from(): def get(self): return self._x0, self._y0 return property(get) @apply def point_to(): def get(self): return self._x1, self._y1 return property(get) @classmethod def from_maparea(cls, maparea, owner): self = super(LineMapArea, cls).from_maparea(maparea, owner) if isinstance(maparea, LineMapArea): (self._x0, self._y0), (self._x1, self._y1) = maparea.point_from, maparea.point_to self._line_arrow = maparea.line_arrow self._line_width = maparea.line_width self._line_color = maparea.line_color elif maparea is not None: self._set_rect(maparea.rect) return self def __init__(self, x1, y1, x2, y2, **options): self._x0, self._y0, self._x1, self._y1 = itertools.imap(int, (x1, y1, x2, y2)) try: del options['s_%s' % djvu.const.MAPAREA_ARROW] except KeyError: self._line_arrow = False else: self._line_arrow = True try: self._line_width = int(options.pop('s_%s' % djvu.const.MAPAREA_LINE_WIDTH)) if self._line_width < djvu.const.MAPAREA_LINE_MIN_WIDTH: raise ValueError except KeyError: self._line_width = djvu.const.MAPAREA_LINE_MIN_WIDTH except (TypeError, ValueError), ex: raise MapAreaSyntaxError(ex) try: self._line_color = parse_color(options.pop('s_%s' % djvu.const.MAPAREA_LINE_COLOR)) except KeyError: self._line_color = djvu.const.MAPAREA_LINE_COLOR_DEFAULT except (TypeError, ValueError), ex: raise MapAreaSyntaxError(ex) self._parse_border_options(options) self._parse_common_options(options) self._check_invalid_options(options) @apply def line_width(): def get(self): return self._line_width def set(self, value): self._line_width = value self._notify_change() return property(get, set) @apply def line_color(): def get(self): return self._line_color def set(self, value): self._line_color = parse_color(value) self._notify_change() return property(get, set) @apply def line_arrow(): def get(self): return self._line_arrow def set(self, value): self._line_arrow = value self._notify_change() return property(get, set) @apply def border_always_visible(): def get(self): return NotImplemented def set(self, value): pass # FIXME? return property(get, set) class TextMapArea(XywhMapArea): SYMBOL = djvu.const.MAPAREA_SHAPE_TEXT @classmethod def from_maparea(cls, maparea, owner): self = super(TextMapArea, cls).from_maparea(maparea, owner) if isinstance(maparea, TextMapArea): self._background_color = maparea.background_color self._text_color = maparea.text_color self._pushpin = maparea.pushpin return self def __init__(self, x, y, w, h, **options): self._parse_xywh(x, y, w, h) self._parse_border_options(options) self._parse_border_always_visible(options) # XXX Reference (8.3.4.2.3.1 Miscellaneous parameters) states that ``(border_avis)`` # is not relevant for text annotations. Nevertheless that option can be found # in the wild, e.g. in the ``lizard2005-antz.djvu`` file. try: self._background_color = parse_color(options.pop('s_%s' % djvu.const.MAPAREA_BACKGROUND_COLOR)) except KeyError: self._background_color = None except (TypeError, ValueError), ex: raise MapAreaSyntaxError(ex) try: self._text_color = parse_color(options.pop('s_%s' % djvu.const.MAPAREA_TEXT_COLOR)) except KeyError: self._text_color = djvu.const.MAPAREA_TEXT_COLOR_DEFAULT except (TypeError, ValueError), ex: raise MapAreaSyntaxError(ex) try: del options['s_%s' % djvu.const.MAPAREA_PUSHPIN] except KeyError: self._pushpin = False else: self._pushpin = True self._parse_common_options(options) self._check_invalid_options(options) def _get_sexpr_extra(self): result = [] if self._background_color is not None: result += (djvu.const.MAPAREA_BACKGROUND_COLOR, djvu.sexpr.Symbol(self._background_color)), if self._text_color != djvu.const.MAPAREA_TEXT_COLOR_DEFAULT: result += (djvu.const.MAPAREA_TEXT_COLOR, djvu.sexpr.Symbol(self._text_color)), if self._pushpin: result += (djvu.const.MAPAREA_PUSHPIN,), return tuple(result) @apply def background_color(): def get(self): return self._background_color def set(self, color): self._background_color = parse_color(color, allow_none=True) self._notify_change() return property(get, set) @apply def text_color(): def get(self): return self._text_color def set(self, color): self._text_color = parse_color(color) self._notify_change() return property(get, set) @apply def pushpin(): def get(self): return self._pushpin def set(self, value): self._pushpin = value self._notify_change() return property(get, set) MAPAREA_SHADOW_BORDER_TO_CLASS = dict( (cls.SYMBOL, cls) for cls in (BorderShadowIn, BorderShadowOut, BorderEtchedIn, BorderEtchedOut) ) MAPAREA_SHAPE_TO_CLASS = dict( (cls.SYMBOL, cls) for cls in (RectangleMapArea, OvalMapArea, PolygonMapArea, LineMapArea, TextMapArea) ) ANNOTATION_TYPE_TO_CLASS = \ { djvu.const.ANNOTATION_MAPAREA: MapArea } class PageAnnotations(object): def __init__(self, n, original_data): self._old_data = original_data self._callbacks = weakref.WeakKeyDictionary() self.revert() self._n = n def register_callback(self, callback): if not isinstance(callback, PageAnnotationsCallback): raise TypeError self._callbacks[callback] = 1 def _classify_data(self, items): result = dict((key, []) for key in ANNOTATION_TYPE_TO_CLASS.itervalues()) result[None] = [] for item in items: cls = ANNOTATION_TYPE_TO_CLASS.get(item[0].value) if cls is not None: item = cls.from_sexpr(item, self) else: item = OtherAnnotation(item) result[cls].append(item) return result def add_maparea(self, node): self._data[MapArea] += node, self.notify_node_add(node) def remove_maparea(self, node): try: self._data[MapArea].remove(node) except ValueError: return self.notify_node_delete(node) def replace_maparea(self, node, other_node): mapareas = self._data[MapArea] try: i = mapareas.index(node) except ValueError: return mapareas[i] = other_node self.notify_node_replace(node, other_node) @property def mapareas(self): return self._data.get(MapArea, ()) def revert(self): self._data = self._classify_data(self._old_data) self._dirty = False def export(self, djvused): if not self._dirty: return self.export_select(djvused) djvused.set_annotations(node.sexpr for nodes in self._data.itervalues() for node in nodes) def export_select(self, djvused): djvused.select(self._n + 1) def notify_node_add(self, node): self._dirty = True for callback in self._callbacks: callback.notify_node_add(node) def notify_node_change(self, node): self._dirty = True for callback in self._callbacks: callback.notify_node_change(node) def notify_node_replace(self, node, other_node): self._dirty = True for callback in self._callbacks: callback.notify_node_replace(node, other_node) def notify_node_delete(self, node): self._dirty = True for callback in self._callbacks: callback.notify_node_delete(node) def notify_node_select(self, node): for callback in self._callbacks: callback.notify_node_select(node) def notify_node_deselect(self, node): for callback in self._callbacks: callback.notify_node_deselect(node) class SharedAnnotations(object): def export_select(self, djvused): djvused.create_shared_annotations() # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/models/text.py0000644000000000000000000002730411573212252017333 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. ''' Models for hidden text. See Lizardtech DjVu Reference (DjVu 3): - 3.3.2 Hidden text. - 8.3.5 Text Chunk. ''' import copy import weakref import itertools import djvu.decode import djvu.sexpr from djvusmooth.varietes import not_overridden, wref from djvusmooth.models import MultiPageModel class Node(object): def __new__(cls, sexpr, owner): if len(sexpr) == 6 and isinstance(sexpr[5], djvu.sexpr.StringExpression): cls = LeafNode else: cls = InnerNode return object.__new__(cls) def __init__(self, sexpr, owner): self._owner = owner self._type = djvu.const.get_text_zone_type(sexpr[0].value) x0, y0, x1, y1 = (sexpr[i].value for i in xrange(1, 5)) self._x = x0 self._y = y0 self._w = x1 - x0 self._h = y1 - y0 self._link_left = self._link_right = self._link_parent = wref(None) @property def sexpr(self): return self._construct_sexpr() def _construct_sexpr(self): raise NotImplementedError @apply def separator(): def get(self): return djvu.const.TEXT_ZONE_SEPARATORS[self._type] return property(get) @apply def x(): def get(self): return self._x def set(self, value): self._x = value self._notify_change() return property(get, set) @apply def y(): def get(self): return self._y def set(self, value): self._y = value self._notify_change() return property(get, set) @apply def w(): def get(self): return self._w def set(self, value): self._w = value self._notify_change() return property(get, set) @apply def h(): def get(self): return self._h def set(self, value): self._h = value self._notify_change() return property(get, set) @apply def rect(): def get(self): return self._x, self._y, self._w, self._h def set(self, value): self._x, self._y, self._w, self._h = value self._notify_change() return property(get, set) @apply def type(): def get(self): return self._type return property(get) @apply def left_sibling(): def get(self): link = self._link_left() if link is None: raise StopIteration return link return property(get) @apply def right_sibling(): def get(self): link = self._link_right() if link is None: raise StopIteration return link return property(get) @apply def parent(): def get(self): link = self._link_parent() if link is None: raise StopIteration return link return property(get) @apply def left_child(): def get(self): raise StopIteration return property(get) def delete(self): try: parent = self.parent except StopIteration: return parent.remove_child(self) def remove_child(self, child): raise NotImplementedError def strip(self, zone_type): raise NotImplementedError def is_leaf(self): return False def is_inner(self): return False def notify_select(self): self._owner.notify_node_select(self) def notify_deselect(self): self._owner.notify_node_deselect(self) def _notify_change(self): return self._owner.notify_node_change(self) def _notify_children_change(self): return self._owner.notify_node_children_change(self) class LeafNode(Node): def is_leaf(self): return True def __init__(self, sexpr, owner): Node.__init__(self, sexpr, owner) self._text = sexpr[5].value.decode('UTF-8', 'replace') def _construct_sexpr(self): x, y, w, h = self.x, self.y, self.w, self.h return djvu.sexpr.Expression((self.type, x, y, x + w, y + h, self.text)) @apply def text(): def get(self): return self._text def set(self, value): self._text = value self._notify_change() return property(get, set) def remove_child(self, child): raise TypeError('%r is not having children' % (self,)) def strip(self, zone_type): if self.type <= zone_type: return self.text, self.separator return self def __getitem__(self, n): raise TypeError def __len__(self): raise TypeError def __iter__(self): raise TypeError class InnerNode(Node): def is_inner(self): return True def __init__(self, sexpr, owner): Node.__init__(self, sexpr, owner) self._set_children(Node(child, self._owner) for child in sexpr[5:]) def _set_children(self, children): self._children = list(children) prev = None for child in self._children: child._link_left = wref(prev) child._link_parent = wref(self) prev = child prev = None for child in reversed(self._children): child._link_right = wref(prev) prev = child def _construct_sexpr(self): x, y, w, h = self.x, self.y, self.w, self.h if self._children: child_sexprs = (child.sexpr for child in self) else: # FIXME: this needs a better solution child_sexprs = (djvu.sexpr.Expression(''),) return djvu.sexpr.Expression( itertools.chain( (self.type, x, y, x + w, y + h), child_sexprs ) ) @apply def text(): return property() @apply def left_child(): def get(self): return iter(self._children).next() return property(get) def remove_child(self, child): child_idx = self._children.index(child) if child_idx - 1 >= 0: self._children[child_idx - 1]._link_right = child._link_right if child_idx + 1 < len(self._children): self._children[child_idx + 1]._link_left = child._link_left child._link_left = child._link_right = child._link_parent = wref(None) del self._children[child_idx] self._notify_children_change() def strip(self, zone_type): stripped_children = [child.strip(zone_type) for child in self._children] if self.type <= zone_type: texts = [text for text, child_separator in stripped_children] return child_separator.join(texts), self.separator else: node_children = [child for child in stripped_children if isinstance(child, Node)] if node_children: self._set_children(node_children) return self else: texts = [text for text, child_separator in stripped_children] text = child_separator.join(texts) return Node( djvu.sexpr.Expression(( self.type, self.x, self.y, self.x + self.w, self.y + self.h, text )), self._owner ) return self def __getitem__(self, n): return self._children[n] def __len__(self): return len(self._children) def __iter__(self): return iter(self._children) class Text(MultiPageModel): def get_page_model_class(self, n): return PageText class PageTextCallback(object): @not_overridden def notify_node_change(self, node): pass @not_overridden def notify_node_children_change(self, node): pass @not_overridden def notify_node_select(self, node): pass @not_overridden def notify_node_deselect(self, node): pass @not_overridden def notify_tree_change(self, node): pass class PageText(object): def __init__(self, n, original_data): self._callbacks = weakref.WeakKeyDictionary() self._original_sexpr = original_data self.revert() self._n = n def register_callback(self, callback): if not isinstance(callback, PageTextCallback): raise TypeError self._callbacks[callback] = 1 @apply def root(): def get(self): return self._root return property(get) @apply def raw_value(): def get(self): if self._root is None: return None return self._root.sexpr def set(self, sexpr): if sexpr: self._root = Node(sexpr, self) else: self._root = None self.notify_tree_change() return property(get, set) def strip(self, zone_type): zone_type = djvu.const.get_text_zone_type(zone_type) # ensure it's not a plain Symbol if self._root is None: return stripped_root = self._root.strip(zone_type) if not isinstance(stripped_root, Node): stripped_root = None self._root = stripped_root self.notify_tree_change() def clone(self): return copy.copy(self) def export(self, djvused): if not self._dirty: return djvused.select(self._n + 1) djvused.set_text(self.raw_value) def revert(self): self.raw_value = self._original_sexpr self._dirty = False def notify_node_change(self, node): self._dirty = True for callback in self._callbacks: callback.notify_node_change(node) def notify_node_children_change(self, node): self._dirty = True for callback in self._callbacks: callback.notify_node_children_change(node) def notify_node_select(self, node): for callback in self._callbacks: callback.notify_node_select(node) def notify_node_deselect(self, node): for callback in self._callbacks: callback.notify_node_deselect(node) def notify_tree_change(self): self._dirty = True for callback in self._callbacks: callback.notify_tree_change(self._root) def get_preorder_nodes(self): if self.root is None: return () return _get_preorder_nodes(self.root) def get_postorder_nodes(self): if self.root is None: return () return _get_postorder_nodes(self.root) def get_leafs(self): if self.root is None: return () return _get_leafs(self.root) def _get_preorder_nodes(node): yield node if isinstance(node, LeafNode): return for child in node: for item in _get_preorder_nodes(child): yield item def _get_postorder_nodes(node): if isinstance(node, InnerNode): for child in node: for item in _get_postorder_nodes(child): yield item yield node def _get_leafs(node): if isinstance(node, LeafNode): yield node else: for child in node: for item in _get_leafs(child): yield item __all__ = 'Text', 'PageText' # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/models/metadata.py0000644000000000000000000000460011573212252020121 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2010 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. ''' Models for metadata. See ``djvuchanges.txt``: - 4. Metadata Annotations. - 5. Document Annotations and Metadata. ''' from djvusmooth.models import MultiPageModel, SHARED_ANNOTATIONS_PAGENO class Metadata(MultiPageModel): def get_page_model_class(self, n): if n == SHARED_ANNOTATIONS_PAGENO: return SharedMetadata else: return PageMetadata class PageMetadata(dict): def __init__(self, n, original_data): self._old_data = None self._dirty = False self._n = n self.load(original_data, overwrite=True) def __setitem__(self, key, value): self._dirty = True dict.__setitem__(self, key, value) def clone(self): from copy import copy return copy(self) def load(self, original_data, overwrite=False): if self._old_data is not None or overwrite: self._old_data = dict(original_data) self.revert() def export_select(self, djvused): djvused.select(self._n + 1) def export(self, djvused): if not self._dirty: return self.export_select(djvused) djvused.set_metadata(self) def revert(self, key = None): if key is None: self.clear() self.update(self._old_data) self._dirty = False else: try: self[key] = self._old_data[key] except KeyError: del self[key] def is_dirty(self, key=None): if key is None: return self._dirty new_value = self[key] try: return self._old_data[key] == new_value except KeyError: return True class SharedMetadata(PageMetadata): def export_select(self, djvused): djvused.create_shared_annotations() __all__ = 'Metadata', 'PageMetadata', 'SharedMetadata' # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/models/outline.py0000644000000000000000000002030712111177622020022 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2012 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. ''' Models for document outline. See Lizardtech DjVu Reference (DjVu 3): - 4 What's new in DjVu File Format. - 8.3.3 Document Outline Chunk. ''' import weakref import itertools import djvu.sexpr import djvu.const from djvusmooth.varietes import not_overridden, wref, fix_uri, indents_to_tree class Node(object): def __init__(self, sexpr, owner): self._owner = owner self._type = None self._link_left = self._link_right = self._link_parent = wref(None) def _set_children(self, children): self._children = list(children) prev = None for child in self._children: child._link_left = wref(prev) child._link_parent = wref(self) prev = child prev = None for child in reversed(self._children): child._link_right = wref(prev) prev = child def add_child(self, node): self._children if self._children: self._children[-1]._link_ref = wref(node) node._link_right = wref(self._children[-1]) node._link_parent = wref(self) self._children += node, self._notify_children_change() def remove_child(self, child): child_idx = self._children.index(child) if child_idx - 1 >= 0: self._children[child_idx - 1]._link_right = child._link_right if child_idx + 1 < len(self._children): self._children[child_idx + 1]._link_left = child._link_left child._link_left = child._link_right = child._link_parent = wref(None) del self._children[child_idx] self._notify_children_change() uri = property() text = property() @property def sexpr(self): return self._construct_sexpr() def _construct_sexpr(self): raise NotImplementedError @property def type(self): return self._type def __getitem__(self, item): return self._children[item] def __len__(self): return len(self._children) def __iter__(self): return iter(self._children) @apply def left_sibling(): def get(self): link = self._link_left() if link is None: raise StopIteration return link return property(get) @apply def right_sibling(): def get(self): link = self._link_right() if link is None: raise StopIteration return link return property(get) @apply def parent(): def get(self): link = self._link_parent() if link is None: raise StopIteration return link return property(get) @apply def left_child(): def get(self): return iter(self).next() return property(get) def delete(self): raise NotImplementedError def notify_select(self): self._owner.notify_node_select(self) def _notify_children_change(self): return self._owner.notify_node_children_change(self) class RootNode(Node): def __init__(self, sexpr, owner): Node.__init__(self, sexpr, owner) sexpr = iter(sexpr) self._type = sexpr.next().value self._set_children(InnerNode(subexpr, owner) for subexpr in sexpr) def _construct_sexpr(self): return djvu.sexpr.Expression(itertools.chain( (self.type,), (child._construct_sexpr() for child in self._children) )) def export_as_plaintext(self, stream): for child in self: child.export_as_plaintext(stream, indent=0) class InnerNode(Node): def __init__(self, sexpr, owner): Node.__init__(self, sexpr, owner) sexpr = iter(sexpr) self._text = sexpr.next().value.decode('UTF-8', 'replace') self._uri = fix_uri(sexpr.next().value) self._set_children(InnerNode(subexpr, owner) for subexpr in sexpr) def _construct_sexpr(self): return djvu.sexpr.Expression(itertools.chain( (self.text, self.uri), (child._construct_sexpr() for child in self._children) )) @apply def uri(): def get(self): return self._uri def set(self, value): self._uri = value self._notify_change() return property(get, set) @apply def text(): def get(self): return self._text def set(self, value): self._text = value self._notify_change() return property(get, set) def _notify_change(self): self._owner.notify_node_change(self) def export_as_plaintext(self, stream, indent): stream.write(' ' * indent) stream.write(self.uri) stream.write(' ') stream.write(self.text.encode('UTF-8')) # TODO: what about control characters etc.? stream.write('\n') for child in self: child.export_as_plaintext(stream, indent = indent + 1) def delete(self): try: parent = self.parent except StopIteration: return parent.remove_child(self) class OutlineCallback(object): @not_overridden def notify_tree_change(self, node): pass @not_overridden def notify_node_change(self, node): pass @not_overridden def notify_node_children_change(self, node): pass @not_overridden def notify_node_select(self, node): pass class Outline(object): def __init__(self): self._callbacks = weakref.WeakKeyDictionary() self._original_sexpr = self.acquire_data() self.revert() def register_callback(self, callback): if not isinstance(callback, OutlineCallback): raise TypeError self._callbacks[callback] = 1 @apply def root(): def get(self): return self._root return property(get) @apply def raw_value(): def get(self): return self._root.sexpr def set(self, sexpr): if not sexpr: sexpr = djvu.const.EMPTY_OUTLINE self._root = RootNode(sexpr, self) self.notify_tree_change() return property(get, set) def remove(self): self.raw_value = djvu.const.EMPTY_OUTLINE def revert(self): self.raw_value = self._original_sexpr self._dirty = False def acquire_data(self): return djvu.const.EMPTY_OUTLINE def notify_tree_change(self): self._dirty = True for callback in self._callbacks: callback.notify_tree_change(self._root) def notify_node_change(self, node): self._dirty = True for callback in self._callbacks: callback.notify_node_change(node) def notify_node_children_change(self, node): self._dirty = True for callback in self._callbacks: callback.notify_node_children_change(node) def notify_node_select(self, node): for callback in self._callbacks: callback.notify_node_select(node) def export(self, djvused): if self.root: value = self.raw_value else: value = None djvused.set_outline(value) def export_as_plaintext(self, stream): return self.root.export_as_plaintext(stream) def import_plaintext(self, lines): def fix_node(node): it = iter(node) it.next() for subnode in it: fix_node(subnode) text = node[0] if text is not None: node[0:1] = (text.split(None, 1) + ['', ''])[1::-1] tree = indents_to_tree(lines) fix_node(tree) tree[0:1] = djvu.const.EMPTY_OUTLINE self.raw_value = djvu.sexpr.Expression(tree) # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/models/__init__.py0000644000000000000000000000230011573212252020073 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. SHARED_ANNOTATIONS_PAGENO = -1 class MultiPageModel(object): def get_page_model_class(self, n): raise NotImplementedError def __init__(self): self._pages = {} def __getitem__(self, n): if n not in self._pages: cls = self.get_page_model_class(n) self._pages[n] = cls(n, self.acquire_data(n)) return self._pages[n] def __setitem__(self, n, model): self._pages[n] = model def acquire_data(self, n): return {} def export(self, djvused): for id in sorted(self._pages): self._pages[id].export(djvused) __all__ = 'MultiPageModel', 'SHARED_ANNOTATIONS_PAGENO' # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/__init__.py0000644000000000000000000000040612111177123016611 0ustar rootroot00000000000000__version__ = '0.2.14' __author__ = 'Jakub Wilk ' import sys if sys.version_info < (2, 5): raise RuntimeError('Python >= 2.5 is required') if sys.version_info >= (3, 0): raise RuntimeError('Python 2.X is required') # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/i18n.py0000644000000000000000000000170311735124342015640 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2009 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import gettext import os import sys for dir in os.path.join(os.path.dirname(sys.argv[0]), 'locale'), None: try: _ = gettext.translation('djvusmooth', dir).ugettext break except IOError: pass else: def _(s): return s del dir # Some dummy translations: if False: _('page') _('column') _('region') _('para') _('line') _('word') _('char') _('bookmarks') # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/0000755000000000000000000000000012111517063015265 5ustar rootroot00000000000000djvusmooth-0.2.14/lib/gui/maparea_browser.py0000644000000000000000000001425712111506274021023 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # Copyright © 2009 Mateusz Turcza # # This package 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 2 dated June, 1991. # # This package 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. import wx import wx.lib.mixins.listctrl import djvusmooth.models.annotations from djvusmooth import models import djvusmooth.gui.maparea_menu from djvusmooth import gui from djvusmooth.i18n import _ class PageAnnotationsCallback(models.annotations.PageAnnotationsCallback): def __init__(self, owner): self._owner = owner def notify_node_change(self, node): wx.CallAfter(lambda: self._owner.on_node_change(node)) def notify_node_select(self, node): wx.CallAfter(lambda: self._owner.on_node_select(node)) def notify_node_deselect(self, node): pass def notify_node_add(self, node): wx.CallAfter(lambda: self._owner.on_node_add(node)) def notify_node_replace(self, node, other_node): wx.CallAfter(lambda: self._owner.on_node_replace(node, other_node)) def notify_node_delete(self, node): wx.CallAfter(lambda: self._owner.on_node_delete(node)) def item_to_id(item): try: return int(item) except TypeError: return item.GetId() class MapAreaBrowser( wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin, wx.lib.mixins.listctrl.TextEditMixin ): def __init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.LC_REPORT): wx.ListCtrl.__init__(self, parent, id, pos, size, style) self.InsertColumn(0, _('URI')) self.InsertColumn(1, _('Comment')) self._have_items = False self._data = {} self._data_map = {} self.page = None wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self) wx.lib.mixins.listctrl.TextEditMixin.__init__(self) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_selection_changed, self) self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.on_item_right_click, self) self.Bind(wx.EVT_CHAR, self.on_char, self) def do_remove_node(self, node): node.delete() _WXK_TO_METHOD = { wx.WXK_DELETE: do_remove_node } def on_char(self, event): key_code = event.GetKeyCode() try: method = self._WXK_TO_METHOD[key_code] except KeyError: return item = self.GetFirstSelected() node = self.GetPyData(item) if node is None: return method(self, node) def on_node_add(self, node): item = self._insert_item(node) self.Focus(item) def on_node_replace(self, node, other_node): item = self._insert_item(other_node, replace_node=node) self.Focus(item) def on_node_change(self, node): self.on_node_replace(node, node) def on_node_delete(self, node): self._remove_item(node) def on_node_select(self, node): try: current_item = self._data_map[node] except KeyError: return selected_item = self.GetFirstSelected() if selected_item != current_item: self.Select(selected_item, False) self.Select(current_item, True) def on_item_right_click(self, event): item = event.m_itemIndex node = self.GetPyData(item) # Yup, we accept the fact that `node` can be `None` self.show_menu(node, event.GetPoint()) def show_menu(self, node, point): gui.maparea_menu.show_menu(self, self.page.annotations, node, point) def on_selection_changed(self, event): event.Skip() item = event.m_itemIndex node = self.GetPyData(item) if node is None: return node.notify_select() @apply def page(): def get(self): return self._page def set(self, value): if value is not True: self._page = value if self._page is not None: self._callback = PageAnnotationsCallback(self) self._page.annotations.register_callback(self._callback) self._model = self.page.annotations self._recreate_items() return property(get, set) def SetStringItem(self, item, col, label, super = False): wx.ListCtrl.SetStringItem(self, item, col, label) if super: return node = self.GetPyData(item) if node is None: return if col == 0: node.uri = label elif col == 1: node.comment = label def GetPyData(self, item): id = item_to_id(item) return self._data.get(id) def SetPyData(self, item, data): id = item_to_id(item) try: del self._data_map[self._data[id]] except KeyError: pass self._data[id] = data self._data_map[data] = id def _remove_all_items(self): wx.ListCtrl.DeleteAllItems(self) self._data.clear() self._data_map.clear() def _remove_item(self, node): item = self._data_map.pop(node) i = item_to_id(item) del self._data[i] self.DeleteItem(i) def _insert_item(self, node, replace_node = None): if replace_node in self._data_map: item = self._data_map[replace_node] i = item_to_id(item) else: i = self.GetItemCount() item = self.InsertStringItem(i, '') for i, s in enumerate((node.uri, node.comment)): self.SetStringItem(item, i, s, super = True) self.SetPyData(item, node) return item def _recreate_items(self): self._remove_all_items() self._nodes = [] if self.page is None: return for node in self._model.mapareas: self._insert_item(node) __all__ = 'MapAreaBrowser', # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/flatten_text.py0000644000000000000000000000472111573212252020347 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # Copyright © 2009 Mateusz Turcza # # This package 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 2 dated June, 1991. # # This package 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. import wx import djvu.sexpr import djvu.const from djvusmooth.i18n import _ ZONES_MAP = \ ( (_('all'), djvu.const.TEXT_ZONE_PAGE), (_('columns'), djvu.const.TEXT_ZONE_COLUMN), (_('regions'), djvu.const.TEXT_ZONE_REGION), (_('paragraphs'), djvu.const.TEXT_ZONE_PARAGRAPH), (_('lines'), djvu.const.TEXT_ZONE_LINE), (_('words'), djvu.const.TEXT_ZONE_WORD), (_('characters'), djvu.const.TEXT_ZONE_CHARACTER) ) class FlattenTextDialog(wx.Dialog): def __init__(self, parent): wx.Dialog.__init__(self, parent, title = _('Flatten text')) sizer = wx.BoxSizer(wx.VERTICAL) self._scope_box = wx.RadioBox(self, label = _('Scope') + ':', choices = (_('current page'), _('all pages')), style = wx.RA_HORIZONTAL ) self._zone_box = wx.RadioBox(self, label = _('Remove details') + ':', choices = [label for label, type in ZONES_MAP], style = wx.RA_SPECIFY_COLS, majorDimension = 2 ) self._zone_box.SetSelection(len(ZONES_MAP) - 1) for box in self._scope_box, self._zone_box: sizer.Add(box, 0, wx.EXPAND | wx.ALL, 5) line = wx.StaticLine(self, -1, style = wx.LI_HORIZONTAL) sizer.Add(line, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5) button_sizer = wx.StdDialogButtonSizer() button = wx.Button(self, wx.ID_OK) button.SetDefault() button_sizer.AddButton(button) button = wx.Button(self, wx.ID_CANCEL) button_sizer.AddButton(button) button_sizer.Realize() sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 5) self.SetSizerAndFit(sizer) def get_scope(self): return self._scope_box.GetSelection() def get_zone(self): label, zone = ZONES_MAP[self._zone_box.GetSelection()] return zone __all__ = 'FlattenTextDialog', # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/history.py0000644000000000000000000000417412035066131017346 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2012 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import itertools import wx class FileHistory(object): def __init__(self, config): self._wx = wx.FileHistory() self._config = config to_add = [] for i in itertools.count(0): key = 'recent[%d]' % i path = config.read(key, None) if path is None: break to_add += [path] for path in reversed(to_add): self._add(path) def _add(self, path): self._wx.AddFileToHistory(path) def add(self, path): self._add(path) config = self._config config.del_array('recent') for n, path in enumerate(self): config['recent[%d]' % n] = path self._enable_menu_item() def set_menu(self, window, menu_item, on_click): menu = menu_item.GetSubMenu() self._wx.UseMenu(menu) self._wx.AddFilesToMenu() self._menu_item = menu_item id1 = wx.ID_FILE1 id2 = id1 + self._get_max_length() def on_click_wrapper(event): n = event.GetId() - wx.ID_FILE1 path = self[n] return on_click(path) window.Bind(wx.EVT_MENU_RANGE, on_click_wrapper, id=id1, id2=id2) self._enable_menu_item() def _enable_menu_item(self): self._menu_item.Enable(enable=bool(self)) def __len__(self): return self._wx.Count def __iter__(self): return ( self[n] for n in xrange(len(self)) ) def __getitem__(self, n): return self._wx.GetHistoryFile(n) def _get_max_length(self): return self._wx.GetMaxFiles() # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/dialogs.py0000644000000000000000000000307411573212252017270 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import wx class ProgressDialog(wx.ProgressDialog): def __init__(self, title, message, maximum = 100, parent = None, style = wx.PD_AUTO_HIDE | wx.PD_APP_MODAL): wx.ProgressDialog.__init__(self, title, message, maximum, parent, style) self.__max = maximum self.__n = 0 try: wx.ProgressDialog.Pulse except AttributeError: def Pulse(self): self.__n = (self.__n + 1) % self.__max self.Update(self.__n) try: NumberEntryDialog = wx.NumberEntryDialog except AttributeError: class NumberEntryDialog(wx.SingleChoiceDialog): def __init__(self, parent, message, prompt, caption, value, min, max, pos = wx.DefaultPosition): wx.SingleChoiceDialog.__init__(self, parent = parent, message = message, caption = caption, choices = map(str, xrange(min, max + 1)), pos = pos) self.SetSelection(value - min) def GetValue(self): return int(wx.SingleChoiceDialog.GetStringSelection(self)) __all__ = 'ProgressDialog', 'NumberEntryDialog' # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/main.py0000644000000000000000000011640212111506560016567 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2010, 2011, 2012, 2013 Jakub Wilk # Copyright © 2009 Mateusz Turcza # # This package 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 2 dated June, 1991. # # This package 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. from __future__ import with_statement APPLICATION_NAME = 'djvusmooth' LICENSE = 'GPL-2' import sys import itertools import functools import locale import os.path import threading from Queue import Queue, Empty as QueueEmpty import djvusmooth.dependencies as __dependencies import wx import wx.lib.ogl import wx.lib.newevent import wx.lib.scrolledpanel import djvu.decode import djvu.const from djvusmooth.djvused import StreamEditor from djvusmooth.gui.page import PageWidget, PercentZoom, OneToOneZoom, StretchZoom, FitWidthZoom, FitPageZoom from djvusmooth.gui.page import RENDER_NONRASTER_TEXT, RENDER_NONRASTER_MAPAREA from djvusmooth.gui.metadata import MetadataDialog from djvusmooth.gui.flatten_text import FlattenTextDialog from djvusmooth.gui.text_browser import TextBrowser from djvusmooth.gui.outline_browser import OutlineBrowser from djvusmooth.gui.maparea_browser import MapAreaBrowser from djvusmooth.gui.history import FileHistory from djvusmooth.gui import dialogs from djvusmooth.text import mangle as text_mangle import djvusmooth.models.metadata import djvusmooth.models.annotations import djvusmooth.models.text from djvusmooth import models from djvusmooth import external_editor from djvusmooth import config from djvusmooth import __version__, __author__ from djvusmooth.i18n import _ MENU_ICON_SIZE = (16, 16) WxDjVuMessage, wx.EVT_DJVU_MESSAGE = wx.lib.newevent.NewEvent() system_encoding = locale.getpreferredencoding() if wx.__version__.startswith('2.8.'): wx.Choicebook = wx.Notebook wx.EVT_CHOICEBOOK_PAGE_CHANGED = wx.EVT_NOTEBOOK_PAGE_CHANGED class OpenDialog(wx.FileDialog): __wildcard = _( 'DjVu files (*.djvu, *.djv)|*.djvu;*.djv|' 'All files|*' ) def __init__(self, parent): wx.FileDialog.__init__(self, parent, style=wx.OPEN, wildcard=self.__wildcard, message=_('Open a DjVu document') ) class TextModel(models.text.Text): def __init__(self, document): models.text.Text.__init__(self) self._document = document def reset_document(self, document): self._document = document def acquire_data(self, n): text = self._document.pages[n].text text.wait() return text.sexpr class OutlineModel(models.outline.Outline): def __init__(self, document): self._document = document models.outline.Outline.__init__(self) def reset_document(self, document): self._document = document def acquire_data(self): outline = self._document.outline outline.wait() return outline.sexpr class PageProxy(object): def __init__(self, page, text_model, annotations_model): self._page = page self._text = text_model self._annotations = annotations_model @property def page_job(self): return self._page.decode(wait = False) @property def text(self): return self._text @property def annotations(self): return self._annotations def register_text_callback(self, callback): self._text.register_callback(callback) def register_annotations_callback(self, callback): self._annotations.register_callback(callback) class DocumentProxy(object): def __init__(self, document, outline): self._document = document self._outline = outline @property def outline(self): return self._outline def register_outline_callback(self, callback): self._outline.register_callback(callback) class AnnotationsModel(models.annotations.Annotations): def __init__(self, document_path): models.annotations.Annotations.__init__(self) self.__djvused = StreamEditor(document_path) def reset_document(self, document): pass # Nothing to do def acquire_data(self, n): djvused = self.__djvused if n == models.SHARED_ANNOTATIONS_PAGENO: djvused.select_shared_annotations() else: djvused.select(n + 1) djvused.print_annotations() s = '(%s)' % djvused.commit() # FIXME: optimize try: return djvu.sexpr.Expression.from_string(s) except djvu.sexpr.ExpressionSyntaxError: raise # FIXME class MetadataModel(models.metadata.Metadata): def __init__(self, document): models.metadata.Metadata.__init__(self) self._document = document def reset_document(self, document): self._document = document def acquire_data(self, n): document_annotations = self._document.annotations document_annotations.wait() document_metadata = document_annotations.metadata if n == models.SHARED_ANNOTATIONS_PAGENO: result = document_metadata else: page_annotations = self._document.pages[n].annotations page_annotations.wait() page_metadata = page_annotations.metadata result = {} for k, v in page_metadata.iteritems(): if k not in document_metadata: pass elif v != document_metadata[k]: pass else: continue result[k] = v return result class PageTextCallback(models.text.PageTextCallback): def __init__(self, owner): self._owner = owner def notify_node_change(self, node): self._owner.dirty = True def notify_node_children_change(self, node): self._owner.dirty = True def notify_node_select(self, node): type = str(node.type) text = _('[Text layer]') + ' ' + _(type) if node.is_leaf(): text += ': %s' % node.text self._owner.SetStatusText(text) def notify_node_deselect(self, node): self._owner.SetStatusText('') def notify_tree_change(self, node): self._owner.dirty = True class OutlineCallback(models.outline.OutlineCallback): def __init__(self, owner): self._owner = owner def notify_tree_change(self, node): self._owner.dirty = True def notify_node_change(self, node): self._owner.dirty = True def notify_node_children_change(self, node): self._owner.dirty = True def notify_node_select(self, node): try: self._owner.SetStatusText(_('Link: %s') % node.uri) except AttributeError: pass class PageAnnotationsCallback(models.annotations.PageAnnotationsCallback): def __init__(self, owner): self._owner = owner def notify_node_change(self, node): self._owner.dirty = True def notify_node_add(self, node): self._owner.dirty = True def notify_node_delete(self, node): self._owner.dirty = True def notify_node_replace(self, node, other_node): self._owner.dirty = True def notify_node_select(self, node): try: self._owner.SetStatusText(_('Link: %s') % node.uri) except AttributeError: pass def notify_node_deselect(self, node): self._owner.SetStatusText('') class ScrolledPanel(wx.lib.scrolledpanel.ScrolledPanel): def OnChildFocus(self, event): # We *don't* want to scroll to the child window which just got the focus. # So just skip the event: event.Skip() def skip_if_being_deleted(method): @functools.wraps(method) def method_wrapper(self, *args, **kwargs): if self.IsBeingDeleted(): # This method could be called via an event while the window is # being deleted. Do *not* fiddle with menu in such a case, as it # would provoke segmentation fault. return return method(self, *args, **kwargs) return method_wrapper class MainWindow(wx.Frame): @apply def default_xywh(): def get(self): return tuple( self._config.read_int('main_window_%s' % key, value) for key, value in (('x', -1), ('y', -1), ('width', 640), ('height', 480)) ) def set(self, (x, y, w, h)): for key, value in dict(x=x, y=y, width=w, height=h).iteritems(): self._config['main_window_%s' % key] = value return property(get, set) @apply def default_splitter_sash(): def get(self): return self._config.read_int('main_window_splitter_sash', 160) def set(self, value): self._config['main_window_splitter_sash'] = value return property(get, set) @apply def default_sidebar_shown(): def get(self): return self._config.read_bool('main_window_sidebar_shown', True) def set(self, value): self._config['main_window_sidebar_shown'] = value return property(get, set) @apply def default_editor_path(): def get(self): return self._config.read('external_editor', '') or None def set(self, value): self._config['external_editor'] = value or '' return property(get, set) @apply def default_open_dir(): def get(self): return self._config.read('open_dir', '') def set(self, value): self._config['open_dir'] = value or '' return property(get, set) def save_defaults(self): self._config.flush() def setup_external_editor(self): editor_path = self.default_editor_path if editor_path is None: self.external_editor = external_editor.RunMailcapEditor() else: self.external_editor = external_editor.CustomEditor(*editor_path.split()) def __init__(self): self._config = wx.GetApp().config # The ._config attribute should not be accessed directly, but only # via .default_* properties. # Use .save_defaults() to save the config file. x, y, w, h = self.default_xywh wx.Frame.__init__(self, None, pos=(x, y), size=(w, h)) self.setup_external_editor() self.Bind(wx.EVT_DJVU_MESSAGE, self.handle_message) self.context = Context(self) self._page_text_callback = PageTextCallback(self) self._page_annotations_callback = PageAnnotationsCallback(self) self._outline_callback = OutlineCallback(self) self.status_bar = self.CreateStatusBar(2, style = wx.ST_SIZEGRIP) self.splitter = wx.SplitterWindow(self, style = wx.SP_LIVE_UPDATE) self.splitter.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.on_splitter_sash_changed) self.sidebar = wx.Choicebook(self.splitter, wx.ID_ANY) self.text_browser = TextBrowser(self.sidebar) self.outline_browser = OutlineBrowser(self.sidebar) self.maparea_browser = MapAreaBrowser(self.sidebar) self.sidebar.AddPage(self.outline_browser, _('Outline')) self.sidebar.AddPage(self.maparea_browser, _('Hyperlinks')) self.sidebar.AddPage(self.text_browser, _('Text')) self.sidebar.Bind( wx.EVT_CHOICEBOOK_PAGE_CHANGED, self._on_sidebar_page_changed( self.on_display_no_nonraster, self.on_display_maparea, self.on_display_text) ) self.scrolled_panel = ScrolledPanel(self.splitter) self.splitter.SetSashGravity(0.1) self.do_show_sidebar() if not self.default_sidebar_shown: self.do_hide_sidebar() sizer = wx.BoxSizer(wx.VERTICAL) self.scrolled_panel.SetSizer(sizer) self.scrolled_panel.SetupScrolling() self.page_widget = PageWidget(self.scrolled_panel) self.page_widget.Bind(wx.EVT_CHAR, self.on_char) self.scrolled_panel.Bind(wx.EVT_SIZE, self.page_widget.on_parent_resize) sizer.Add(self.page_widget, 0, wx.ALL, 0) self.editable_menu_items = [] self.saveable_menu_items = [] self.file_history = FileHistory(self._config) self.create_menus() self.dirty = False self.do_open(None) self.Bind(wx.EVT_CLOSE, self.on_exit) def create_menus(self): menu_bar = wx.MenuBar() menu_bar.Append(self._create_file_menu(), _('&File')) menu_bar.Append(self._create_edit_menu(), _('&Edit')) menu_bar.Append(self._create_view_menu(), _('&View')) menu_bar.Append(self._create_go_menu(), _('&Go')); menu_bar.Append(self._create_settings_menu(), _('&Settings')); menu_bar.Append(self._create_help_menu(), _('&Help')); self.SetMenuBar(menu_bar) def _create_menu_item(self, menu, caption, help, method, style=wx.ITEM_NORMAL, icon=None, id=wx.ID_ANY): item = wx.MenuItem(menu, id, caption, help, style) if icon is not None: bitmap = wx.ArtProvider_GetBitmap(icon, wx.ART_MENU, MENU_ICON_SIZE) item.SetBitmap(bitmap) self.Bind(wx.EVT_MENU, method, item) menu.AppendItem(item) return item def _create_file_menu(self): menu = wx.Menu() menu_item = functools.partial(self._create_menu_item, menu) menu_item(_('&Open') + '\tCtrl+O', _('Open a DjVu document'), self.on_open, icon=wx.ART_FILE_OPEN) recent_menu = wx.Menu() recent_menu_item = menu.AppendMenu(wx.ID_ANY, _('Open &recent'), recent_menu) self.file_history.set_menu(self, recent_menu_item, self.do_open) save_menu_item = menu_item(_('&Save') + '\tCtrl+S', _('Save the document'), self.on_save, icon=wx.ART_FILE_SAVE) close_menu_item = menu_item(_('&Close') + '\tCtrl+W', _('Close the document'), self.on_close, id=wx.ID_CLOSE) self.editable_menu_items += close_menu_item, self.saveable_menu_items += save_menu_item, menu.AppendSeparator() menu_item(_('&Quit') + '\tCtrl+Q', _('Quit the application'), self.on_exit, icon=wx.ART_QUIT) return menu def _create_edit_menu(self): menu = wx.Menu() menu_item = functools.partial(self._create_menu_item, menu) menu_item(_('&Metadata') + '\tCtrl+M', _('Edit the document or page metadata'), self.on_edit_metadata) submenu = wx.Menu() submenu_item = functools.partial(self._create_menu_item, submenu) submenu_item(_('&External editor') + '\tCtrl+T', _('Edit page text in an external editor'), self.on_external_edit_text) submenu_item(_('&Flatten'), _('Remove details from page text'), self.on_flatten_text) menu.AppendMenu(wx.ID_ANY, _('&Text'), submenu) submenu = wx.Menu() submenu_item = functools.partial(self._create_menu_item, submenu) submenu_item(_('&Bookmark this page') + '\tCtrl+B', _('Add the current to document outline'), self.on_bookmark_current_page) submenu_item(_('&External editor'), _('Edit document outline in an external editor'), self.on_external_edit_outline) submenu_item(_('&Remove all'), _('Remove whole document outline'), self.on_remove_outline) menu.AppendMenu(wx.ID_ANY, _('&Outline'), submenu) return menu def _create_view_menu(self): menu = wx.Menu() menu_item = functools.partial(self._create_menu_item, menu) submenu = wx.Menu() submenu_item = functools.partial(self._create_menu_item, submenu) for caption, help, method, id in \ [ (_('Zoom &in') + '\tCtrl++', _('Increase the magnification'), self.on_zoom_in, wx.ID_ZOOM_IN), (_('Zoom &out') + '\tCtrl+-', _('Decrease the magnification'), self.on_zoom_out, wx.ID_ZOOM_OUT), ]: submenu_item(caption, help, method, id=id) submenu.AppendSeparator() for caption, help, zoom, id in \ [ (_('Fit &width'), _('Set magnification to fit page width'), FitWidthZoom(), None), (_('Fit &page'), _('Set magnification to fit page'), FitPageZoom(), wx.ID_ZOOM_FIT), (_('&Stretch'), _('Stretch the image to the window size'), StretchZoom(), None), (_('One &to one'), _('Set full resolution magnification.'), OneToOneZoom(), wx.ID_ZOOM_100), ]: id = id or wx.ID_ANY submenu_item(caption, help, self.on_zoom(zoom), style=wx.ITEM_RADIO, id=id) submenu.AppendSeparator() self.zoom_menu_items = {} for percent in 300, 200, 150, 100, 75, 50, 25: item = submenu_item( '%d%%' % percent, _('Magnify %d%%') % percent, self.on_zoom(PercentZoom(percent)), style=wx.ITEM_RADIO ) if percent == 100: item.Check() self.zoom_menu_items[percent] = item menu.AppendMenu(wx.ID_ANY, _('&Zoom'), submenu) submenu = wx.Menu() submenu_item = functools.partial(self._create_menu_item, submenu) for caption, help, method in \ [ (_('&Color') + '\tAlt+C', _('Display everything'), self.on_display_everything), (_('&Stencil'), _('Display only the document bitonal stencil'), self.on_display_stencil), (_('&Foreground'), _('Display only the foreground layer'), self.on_display_foreground), (_('&Background'), _('Display only the background layer'), self.on_display_background), (_('&None') + '\tAlt+N', _('Neither display the foreground layer nor the background layer'), self.on_display_none) ]: submenu_item(caption, help, method, style=wx.ITEM_RADIO) menu.AppendMenu(wx.ID_ANY, _('&Image'), submenu) submenu = wx.Menu() submenu_item = functools.partial(self._create_menu_item, submenu) _tmp_items = [] for caption, help, method in \ [ (_('&None'), _('Don\'t display non-raster data'), self.on_display_no_nonraster), (_('&Hyperlinks') + '\tAlt+H', _('Display overprinted annotations'), self.on_display_maparea), (_('&Text') + '\tAlt+T', _('Display the text layer'), self.on_display_text), ]: _tmp_items += [submenu_item(caption, help, method, style=wx.ITEM_RADIO)] self.menu_item_display_no_nonraster, self.menu_item_display_maparea, self.menu_item_display_text = _tmp_items del _tmp_items self.menu_item_display_no_nonraster.Check() menu.AppendMenu(wx.ID_ANY, _('&Non-raster data'), submenu) menu_item(_('&Refresh') + '\tCtrl+L', _('Refresh the window'), self.on_refresh) return menu def _create_go_menu(self): menu = wx.Menu() menu_item = functools.partial(self._create_menu_item, menu) for caption, help, method, icon in \ [ (_('&First page') + '\tCtrl-Home', _('Jump to first document page'), self.on_first_page, None), (_('&Previous page') + '\tPgUp', _('Jump to previous document page'), self.on_previous_page, wx.ART_GO_UP), (_('&Next page') + '\tPgDn', _('Jump to next document page'), self.on_next_page, wx.ART_GO_DOWN), (_('&Last page') + '\tCtrl-End', _('Jump to last document page'), self.on_last_page, None), (_(u'&Go to page…') + '\tCtrl-G', _(u'Jump to page…'), self.on_goto_page, None) ]: menu_item(caption, help, method, icon=icon) return menu def _create_settings_menu(self): menu = wx.Menu() menu_item = functools.partial(self._create_menu_item, menu) sidebar_menu_item = menu_item(_('Show &sidebar') + '\tF9', _('Show/hide the sidebar'), self.on_show_sidebar, style=wx.ITEM_CHECK) if self.default_sidebar_shown: sidebar_menu_item.Check() menu_item(_(u'External editor…'), _('Setup an external editor'), self.on_setup_external_editor) return menu def _create_help_menu(self): menu = wx.Menu() menu_item = functools.partial(self._create_menu_item, menu) menu_item(_('&About') + '\tF1', _('More information about this program'), self.on_about, id=wx.ID_ABOUT) return menu def on_setup_external_editor(self, event): dialog = wx.TextEntryDialog(self, caption=_('Setup an external editor'), message=_('Enter path to your favourite text editor.') ) try: dialog.SetValue(self.default_editor_path or '') if dialog.ShowModal() == wx.ID_OK: self.default_editor_path = dialog.GetValue() self.setup_external_editor() finally: dialog.Destroy() def on_splitter_sash_changed(self, event): self.default_splitter_sash = event.GetSashPosition() def _on_sidebar_page_changed(self, *methods): def event_handler(event): methods[event.GetSelection()](event) return event_handler def on_char(self, event): key_code = event.GetKeyCode() if key_code == ord('-'): self.on_zoom_out(event) elif key_code == ord('+'): self.on_zoom_in(event) else: event.Skip() def enable_edit(self, enable=True): for i in 1, 3: self.GetMenuBar().EnableTop(i, enable) for menu_item in self.editable_menu_items: menu_item.Enable(enable) def enable_save(self, enable=True): for menu_item in self.saveable_menu_items: menu_item.Enable(enable) @apply def dirty(): def get(self): return self._dirty def set(self, value): self._dirty = value self.enable_save(value) return property(get, set) def error_box(self, message, caption = _('Error')): wx.MessageBox(message = message, caption = caption, style = wx.OK | wx.ICON_ERROR, parent = self) def on_exit(self, event): if self.do_open(None): x, y = self.GetPosition() w, h = self.GetSize() self.default_xywh = x, y, w, h self.save_defaults() self.Destroy() def on_open(self, event): dialog = OpenDialog(self) dialog.SetDirectory(self.default_open_dir) try: if dialog.ShowModal() == wx.ID_OK: self.do_open(dialog.GetPath()) finally: dialog.Destroy() def on_close(self, event): self.do_open(None) def on_save(self, event): self.do_save() def on_save_failed(self, exception): self.error_box(_('Saving document failed:\n%s') % exception) def do_save(self): if not self.dirty: return True queue = Queue() sed = StreamEditor(self.path, autosave=True) for model in self.models: model.export(sed) def job(): try: sed.commit() self.document = self.context.new_document(djvu.decode.FileURI(self.path)) for model in self.models: model.reset_document(self.document) except Exception, exception: pass else: exception = None queue.put(exception) thread = threading.Thread(target = job) thread.start() dialog = None try: try: exception = queue.get(block = True, timeout = 0.1) if exception is not None: self.on_save_failed(exception) return False except QueueEmpty: dialog = dialogs.ProgressDialog( title = _('Saving document'), message = _(u'Saving the document, please wait…'), parent = self, style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME ) while dialog is not None: try: exception = queue.get(block = True, timeout = 0.1) if exception is not None: self.on_save_failed(exception) return False break except QueueEmpty: dialog.Pulse() finally: thread.join() if dialog is not None: dialog.Destroy() self.dirty = False return True def on_show_sidebar(self, event): if event.IsChecked(): self.do_show_sidebar() else: self.do_hide_sidebar() def do_show_sidebar(self): self.splitter.SplitVertically(self.sidebar, self.scrolled_panel, self.default_splitter_sash) self.default_sidebar_shown = True def do_hide_sidebar(self): self.splitter.Unsplit(self.sidebar) self.default_sidebar_shown = False def on_display_everything(self, event): self.page_widget.render_mode = djvu.decode.RENDER_COLOR def on_display_foreground(self, event): self.page_widget.render_mode = djvu.decode.RENDER_FOREGROUND def on_display_background(self, event): self.page_widget.render_mode = djvu.decode.RENDER_BACKGROUND def on_display_stencil(self, event): self.page_widget.render_mode = djvu.decode.RENDER_BLACK def on_display_none(self, event): self.page_widget.render_mode = None @skip_if_being_deleted def on_display_text(self, event): self.page_widget.render_nonraster = RENDER_NONRASTER_TEXT self.menu_item_display_text.Check() @skip_if_being_deleted def on_display_maparea(self, event): self.page_widget.render_nonraster = RENDER_NONRASTER_MAPAREA self.menu_item_display_maparea.Check() @skip_if_being_deleted def on_display_no_nonraster(self, event): self.page_widget.render_nonraster = None self.menu_item_display_no_nonraster.Check() def on_refresh(self, event): self.Refresh() def get_page_uri(self, page_no = None): if page_no is None: page_no = self.page_no try: id = self.document.pages[page_no].file.id except djvu.decode.NotAvailable: id = str(page_no) return '#' + id @apply def page_no(): def get(self): return self._page_no def set(self, n): if self.document is None: self._page_no = 0 self.status_bar.SetStatusText('', 1) return if n < 0 or n >= len(self.document.pages): return self._page_no = n self.status_bar.SetStatusText(_('Page %(pageno)d of %(npages)d') % {'pageno':(n + 1), 'npages':len(self.document.pages)}, 1) self.update_page_widget(new_page = True) return property(get, set) def on_first_page(self, event): self.page_no = 0 def on_last_page(self, event): self.page_no = len(self.document.pages) - 1 def on_next_page(self, event): self.page_no += 1 def on_previous_page(self, event): self.page_no -= 1 def on_goto_page(self, event): dialog = dialogs.NumberEntryDialog( parent = self, message = _('Go to page') + ':', prompt = '', caption = _('Go to page'), value = self.page_no + 1, min = 1, max = len(self.document.pages) ) try: rc = dialog.ShowModal() if rc == wx.ID_OK: self.page_no = dialog.GetValue() - 1 finally: dialog.Destroy() def on_edit_metadata(self, event): document_metadata_model = self.metadata_model[models.SHARED_ANNOTATIONS_PAGENO].clone() document_metadata_model.title = _('Document metadata') page_metadata_model = self.metadata_model[self.page_no].clone() page_metadata_model.title = _('Page %d metadata') % (self.page_no + 1) dialog = MetadataDialog(self, models=(document_metadata_model, page_metadata_model), known_keys=djvu.const.METADATA_KEYS) try: if dialog.ShowModal() == wx.ID_OK: self.metadata_model[models.SHARED_ANNOTATIONS_PAGENO] = document_metadata_model self.metadata_model[self.page_no] = page_metadata_model self.dirty = True finally: dialog.Destroy() def on_flatten_text(self, event): dialog = FlattenTextDialog(self) zone = None try: if dialog.ShowModal() == wx.ID_OK: scope_all = dialog.get_scope() zone = dialog.get_zone() finally: dialog.Destroy() if zone is None: return if scope_all: page_nos = xrange(len(self.document.pages)) else: page_nos = (self.page_no,) for page_no in page_nos: self.text_model[page_no].strip(zone) def on_bookmark_current_page(self, event): uri = self.get_page_uri() node = models.outline.InnerNode(djvu.sexpr.Expression((_('(no title)'), uri)), self.outline_model) self.outline_model.root.add_child(node) node.notify_select() def on_remove_outline(self, event): self.outline_model.remove() def on_external_edit_outline(self, event): model = self.outline_model def job(disabler): new_repr = None try: with external_editor.temporary_file(suffix='.txt') as tmp_file: model.export_as_plaintext(tmp_file) tmp_file.flush() self.external_editor(tmp_file.name) tmp_file.seek(0) new_repr = map(str.expandtabs, itertools.imap(str.rstrip, tmp_file)) except Exception, exception: pass else: exception = None wx.CallAfter(lambda: self.after_external_edit_outline(new_repr, disabler, exception)) disabler = wx.WindowDisabler() thread = threading.Thread(target=job, args=(disabler,)) thread.start() def on_external_edit_failed(self, exception): self.error_box(_('External edit failed:\n%s') % exception) def after_external_edit_outline(self, new_repr, disabler, exception): if exception is not None: self.on_external_edit_failed(exception) return # TODO: how to check if actually something changed? self.outline_model.import_plaintext(new_repr) def on_external_edit_text(self, event): sexpr = self.text_model[self.page_no].raw_value if not sexpr: self.error_box(_('No text layer to edit.')) return def job(disabler): new_sexpr = None try: with external_editor.temporary_file(suffix='.txt') as tmp_file: text_mangle.export(sexpr, tmp_file) tmp_file.flush() self.external_editor(tmp_file.name) tmp_file.seek(0) try: new_sexpr = text_mangle.import_(sexpr, tmp_file) except text_mangle.NothingChanged: pass except Exception, exception: pass else: exception = None wx.CallAfter(lambda: self.after_external_edit_text(new_sexpr, disabler, exception)) disabler = wx.WindowDisabler() thread = threading.Thread(target=job, args=(disabler,)) thread.start() def after_external_edit_text(self, sexpr, disabler, exception): if exception is not None: try: raise exception except text_mangle.CharacterZoneFound: self.error_box(_('Cannot edit text with character zones.')) return except text_mangle.LengthChanged: self.error_box(_('Number of lines changed.')) return except: self.on_external_edit_failed(exception) return if sexpr is None: # nothing changed return self.text_model[self.page_no].raw_value = sexpr def do_percent_zoom(self, percent): self.page_widget.zoom = PercentZoom(percent) self.zoom_menu_items[percent].Check() def on_zoom_out(self, event): try: percent = self.page_widget.zoom.percent except ValueError: return # FIXME candidates = [k for k in self.zoom_menu_items.iterkeys() if k < percent] if not candidates: return self.do_percent_zoom(max(candidates)) def on_zoom_in(self, event): try: percent = self.page_widget.zoom.percent except ValueError: return # FIXME candidates = [k for k in self.zoom_menu_items.iterkeys() if k > percent] if not candidates: return self.do_percent_zoom(min(candidates)) def on_zoom(self, zoom): def event_handler(event): self.page_widget.zoom = zoom return event_handler def do_open(self, path): if isinstance(path, unicode): path = path.encode(system_encoding) if self.dirty: dialog = wx.MessageDialog(self, _('Do you want to save your changes?'), '', wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION) try: rc = dialog.ShowModal() if rc == wx.ID_YES: if not self.do_save(): return False assert not self.dirty elif rc == wx.ID_NO: pass elif rc == wx.ID_CANCEL: return False finally: dialog.Destroy() self.path = path self.document = None self.page_no = 0 def clear_models(): self.metadata_model = self.text_model = self.outline_model = self.annotations_model = None self.models = () self.enable_edit(False) if path is None: clear_models() else: self.file_history.add(path) self.default_open_dir = os.path.dirname(path) try: self.document = self.context.new_document(djvu.decode.FileURI(path)) self.metadata_model = MetadataModel(self.document) self.text_model = TextModel(self.document) self.outline_model = OutlineModel(self.document) self.annotations_model = AnnotationsModel(path) self.models = self.metadata_model, self.text_model, self.outline_model, self.annotations_model self.enable_edit(True) except djvu.decode.JobFailed: clear_models() self.document = None # Do *not* display error message here. It will be displayed by `handle_message()`. self.page_no = 0 # again, to set status bar text self.update_title() self.update_page_widget(new_document = True, new_page = True) self.dirty = False return True def update_page_widget(self, new_document = False, new_page = False): if self.document is None: self.page_widget.Hide() self.page = self.page_job = self.page_proxy = self.document_proxy = None elif self.page_job is None or new_page: self.page_widget.Show() self.page = self.document.pages[self.page_no] self.page_job = self.page.decode(wait = False) self.page_proxy = PageProxy( page = self.page, text_model = self.text_model[self.page_no], annotations_model = self.annotations_model[self.page_no] ) self.page_proxy.register_text_callback(self._page_text_callback) self.page_proxy.register_annotations_callback(self._page_annotations_callback) if new_document: self.document_proxy = DocumentProxy(document = self.document, outline = self.outline_model) self.document_proxy.register_outline_callback(self._outline_callback) self.page_widget.page = self.page_proxy self.text_browser.page = self.page_proxy self.maparea_browser.page = self.page_proxy if new_document: self.outline_browser.document = self.document_proxy def update_title(self): if self.path is None: title = APPLICATION_NAME else: base_path = os.path.basename(self.path) if isinstance(base_path, str): base_path = base_path.decode(system_encoding, 'replace') title = u'%s — %s' % (APPLICATION_NAME, base_path) self.SetTitle(title) def on_about(self, event): message = ( '%(app)s %(version)s\n' + _('Author') + ': %(author)s\n' + _('License') + ': %(license)s' ) message = message % dict( app=APPLICATION_NAME, version=__version__, author=__author__, license=LICENSE ) wx.MessageBox(message = message, caption = _(u'About…')) def handle_message(self, event): message = event.message if isinstance(message, djvu.decode.ErrorMessage): self.error_box(message = str(message)) elif message.document is not self.document: # Bogus, non-error message are ignored. pass self.update_title() if isinstance(message, (djvu.decode.RedisplayMessage, djvu.decode.RelayoutMessage)): if self.page_job is message.page_job: self.update_page_widget() class Context(djvu.decode.Context): def __new__(cls, window): return djvu.decode.Context.__new__(cls) def __init__(self, window): djvu.decode.Context.__init__(self) self.window = window def handle_message(self, message): wx.PostEvent(self.window, WxDjVuMessage(message=message)) class Application(wx.App): @apply def config(): def get(self): return self._config return property(get) def except_hook(self, *args): sys.__excepthook__(*args) wx.CallAfter(self.except_hook_after, *args) def except_hook_after(self, type_, value, traceback): from traceback import format_exception message = _('An unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author.\n\n') message += ''.join(format_exception(type_, value, traceback)) caption = _('Unhandled exception: %s' % type_.__name__) wx.MessageBox(message=message, caption=caption, style = wx.OK | wx.ICON_ERROR) def OnInit(self): wx.lib.ogl.OGLInitialize() self.SetAppName(APPLICATION_NAME) if os.name == 'posix': legacy_path = os.path.expanduser('~/.DjVuSmooth') else: legacy_path = None self._config = config.Config('djvusmooth', legacy_path) sys.excepthook = self.except_hook return True def start(self, argv): window = MainWindow() window.Show(True) if argv: path = argv[0] path = os.path.abspath(path) window.do_open(path) return self.MainLoop() # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/maparea_properties.py0000644000000000000000000004547112111506274021536 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2010 Jakub Wilk # Copyright © 2009 Mateusz Turcza # # This package 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 2 dated June, 1991. # # This package 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. import wx import wx.lib.colourselect import djvu.const import djvusmooth.models.annotations from djvusmooth import models from djvusmooth.varietes import idict from djvusmooth.i18n import _ # See: # , # # for details. HTML_TARGETS = '_blank _self _parent _top'.split() class Shape(idict): def __init__(self, **kwargs): kwargs.setdefault('enabled', True) idict.__init__(self, **kwargs) SHAPE_TEXT = Shape(label = _('Text'), model_class = models.annotations.TextMapArea) SHAPE_LINE = Shape(label = _('Line'), model_class = models.annotations.LineMapArea, enabled=False) SHAPE_RECTANGLE = Shape(label = _('Rectangle'), model_class = models.annotations.RectangleMapArea) SHAPES = ( SHAPE_RECTANGLE, Shape(label = _('Oval'), model_class = models.annotations.OvalMapArea), Shape(label = _('Polygon'), model_class = models.annotations.PolygonMapArea, enabled=False), SHAPE_LINE, SHAPE_TEXT, ) SHADOW_BORDERS = ( idict(model_class = models.annotations.BorderShadowIn, label = _('Shadow in')), idict(model_class = models.annotations.BorderShadowOut, label = _('Shadow out')), idict(model_class = models.annotations.BorderEtchedIn, label = _('Etched in')), idict(model_class = models.annotations.BorderEtchedOut, label = _('Etched out')) ) def color_as_html(color): return '#%02x%02x%02x' % color.Get() class MapareaPropertiesDialog(wx.Dialog): DEFAULT_TEXT_WIDTH = 200 DEFAULT_SPIN_WIDTH = 50 def _setup_main_properties_box(self): node = self._node box = wx.StaticBox(self, label = _('Main properties')) box_sizer = wx.StaticBoxSizer(box) grid_sizer = wx.FlexGridSizer(0, 2, 5, 5) uri_label = wx.StaticText(self, label = 'URI: ') uri_edit = wx.TextCtrl(self, size = (self.DEFAULT_TEXT_WIDTH, -1)) if node is not None: uri_edit.SetValue(self._node.uri) target_label = wx.StaticText(self, label = _('Target frame') + ': ') target_edit = wx.ComboBox(self, size = (self.DEFAULT_TEXT_WIDTH, -1), style = wx.CB_DROPDOWN, choices = HTML_TARGETS ) if node is not None: target_edit.SetValue(self._node.target or '') comment_label = wx.StaticText(self, label = _('Comment') + ': ') comment_edit = wx.TextCtrl(self, size = (self.DEFAULT_TEXT_WIDTH, -1)) if node is not None: comment_edit.SetValue(self._node.comment) for widget in uri_label, uri_edit, target_label, target_edit, comment_label, comment_edit: grid_sizer.Add(widget) box_sizer.Add(grid_sizer, 0, wx.EXPAND | wx.ALL, 5) self._edit_uri = uri_edit self._edit_target = target_edit self._edit_comment = comment_edit return box_sizer def _setup_shape_box(self): box = wx.RadioBox(self, label = _('Shape'), choices = [shape.label for shape in SHAPES] ) for i, shape in enumerate(SHAPES): box.EnableItem(i, shape.enabled) self.Bind(wx.EVT_RADIOBOX, self.on_select_shape, box) self._edit_shape = box # It's too early to select proper shape. We'll do it later. return box def do_select_shape(self, shape): for _shape, items in self._specific_sizers.iteritems(): for item in items: self._sizer.Show(item, False, recursive=True) try: specific_sizers = self._specific_sizers[shape] except KeyError: pass else: for item in specific_sizers: self._sizer.Show(item, True, recursive=True) can_have_shadow_border = shape.model_class.can_have_shadow_border() for widget in self._edit_border_shadows: widget.Enable(can_have_shadow_border) self.Fit() def on_select_shape(self, event): shape = SHAPES[event.GetInt()] self.do_select_shape(shape) def enable_border_width(self, enable): for widget in self._edit_border_width, self._label_border_thickenss: widget.Enable(enable) def enable_solid_border(self, enable): self._edit_border_solid_color.Enable(enable) def enable_always_visible_border(self, enable): self._edit_border_always_visible.Enable(enable) def on_select_no_border(self, event): self.enable_always_visible_border(False) self.enable_border_width(False) self.enable_solid_border(False) def on_select_solid_border(self, event): self.enable_always_visible_border(True) self.enable_border_width(False) self.enable_solid_border(True) def on_select_nonshadow_border(self, event): self.enable_always_visible_border(True) self.enable_border_width(False) self.enable_solid_border(False) def on_select_shadow_border(self, event): self.enable_always_visible_border(True) self.enable_border_width(True) self.enable_solid_border(False) def _setup_border_box(self): node = self._node try: border = node.border except AttributeError: border = None box = wx.StaticBox(self, label = _('Border')) box_sizer = wx.StaticBoxSizer(box, orient = wx.VERTICAL) box_grid_sizer = wx.FlexGridSizer(0, 3, 5, 10) border_width_sizer = wx.BoxSizer(wx.HORIZONTAL) border_width_label = wx.StaticText(self, label = _('Width') + ': ') border_width_edit = wx.SpinCtrl(self, size = (self.DEFAULT_SPIN_WIDTH, -1)) border_width_edit.SetRange(djvu.const.MAPAREA_SHADOW_BORDER_MIN_WIDTH, djvu.const.MAPAREA_SHADOW_BORDER_MAX_WIDTH) border_width_edit.SetValue(djvu.const.MAPAREA_SHADOW_BORDER_MIN_WIDTH) border_width_sizer.Add(border_width_label, 0, wx.ALIGN_CENTER_VERTICAL) border_width_sizer.Add(border_width_edit, 0, wx.ALIGN_CENTER_VERTICAL) radio_none = wx.RadioButton(self, label = _('None')) radio_xor = wx.RadioButton(self, label = _('XOR')) if isinstance(border, models.annotations.XorBorder): radio_xor.SetValue(True) radio_solid = wx.RadioButton(self, label = _('Solid color') + ': ') solid_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) if isinstance(border, models.annotations.SolidBorder): radio_solid.SetValue(True) solid_color_selector.Enable(True) solid_color_selector.SetColour(border.color) else: solid_color_selector.Enable(False) solid_sizer = wx.BoxSizer(wx.HORIZONTAL) solid_sizer.Add(radio_solid, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) solid_sizer.Add(solid_color_selector, 0, wx.ALIGN_CENTER_VERTICAL) for widget in radio_none, radio_xor, solid_sizer: box_grid_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) shadow_widgets = [] have_shadow_border = False for i, shadow_border in enumerate(SHADOW_BORDERS): if i == 2: box_grid_sizer.Add(border_width_sizer, 0, wx.ALIGN_CENTER_VERTICAL) widget = wx.RadioButton(self, label = shadow_border.label) if isinstance(border, shadow_border.model_class): widget.SetValue(True) have_shadow_border = True widget.model_class = shadow_border.model_class box_grid_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) shadow_widgets += widget, for widget in shadow_widgets: self.Bind(wx.EVT_RADIOBUTTON, self.on_select_shadow_border, widget) self.Bind(wx.EVT_RADIOBUTTON, self.on_select_nonshadow_border, radio_xor) self.Bind(wx.EVT_RADIOBUTTON, self.on_select_no_border, radio_none) self.Bind(wx.EVT_RADIOBUTTON, self.on_select_solid_border, radio_solid) avis_checkbox = wx.CheckBox(self, label = _('Always visible')) # TODO: hide it for irrelevant shapes, i.e. `line` and maybe `text` if border is None or isinstance(border, models.annotations.NoBorder): avis_checkbox.Enable(False) elif node is not None and node.border_always_visible is True: avis_checkbox.SetValue(True) box_sizer.Add(box_grid_sizer, 0, wx.EXPAND | wx.ALL, 5) box_sizer.Add(avis_checkbox, 0, wx.ALL, 5) self._edit_border_none = radio_none self._edit_border_xor = radio_xor self._edit_border_solid = radio_solid self._edit_border_solid_color = solid_color_selector self._edit_border_shadows = shadow_widgets self._edit_border_always_visible = avis_checkbox self._edit_border_width = border_width_edit self._label_border_thickenss = border_width_label self.enable_border_width(have_shadow_border) if have_shadow_border: border_width_edit.SetValue(border.width) return box_sizer def _setup_extra_boxes(self): node = self._node extra_boxes = \ [ wx.StaticBox(self, label = label) for label in (_('Highlight color and opacity'), _('Line-specific properties'), _('Text-specific properties')) ] extra_sizers = map(wx.StaticBoxSizer, extra_boxes) highlight_specific_sizer, line_specific_sizer, text_specific_sizer = extra_sizers self._specific_sizers = { SHAPE_RECTANGLE: [highlight_specific_sizer], SHAPE_LINE: [line_specific_sizer], SHAPE_TEXT: [text_specific_sizer] } extra_grid_sizers = [wx.FlexGridSizer(0, 2, 5, 5) for i in extra_sizers] for extra_sizer, extra_grid_sizer in zip(extra_sizers, extra_grid_sizers): extra_sizer.Add(extra_grid_sizer, 0, wx.EXPAND | wx.ALL, 5) highlight_specific_sizer, line_specific_sizer, text_specific_sizer = extra_grid_sizers highlight_color_label = wx.CheckBox(self, label = _('Highlight color') + ': ') highlight_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) highlight_color_selector.SetColour(wx.BLUE) highlight_color_selector.Enable(False) def on_switch_highlight_color(event): highlight_color_selector.Enable(event.IsChecked()) self.Bind(wx.EVT_CHECKBOX, on_switch_highlight_color, highlight_color_label) opacity_label = wx.StaticText(self, label = _('Opacity') + ': ') opacity_slider = wx.Slider(self, value = djvu.const.MAPAREA_OPACITY_DEFAULT, size = (self.DEFAULT_TEXT_WIDTH, -1), style = wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS ) for widget in highlight_color_label, highlight_color_selector, opacity_label, opacity_slider: highlight_specific_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) if isinstance(node, models.annotations.RectangleMapArea): if node.highlight_color is None: highlight_color_label.SetValue(False) highlight_color_selector.Enable(False) else: highlight_color_label.SetValue(True) highlight_color_selector.Enable(True) highlight_color_selector.SetColour(node.highlight_color) opacity_slider.SetValue(node.opacity) line_width_label = wx.StaticText(self, label = _('Line width') + ': ') line_width_edit = wx.SpinCtrl(self, size = (self.DEFAULT_SPIN_WIDTH, -1)) line_width_edit.SetRange(djvu.const.MAPAREA_LINE_MIN_WIDTH, 99) line_width_edit.SetValue(djvu.const.MAPAREA_LINE_MIN_WIDTH) line_color_label = wx.StaticText(self, label = _('Line color') + ': ') line_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) line_arrow_checkbox = wx.CheckBox(self, label = _('Arrow')) dummy = (0, 0) for widget in line_arrow_checkbox, dummy, line_width_label, line_width_edit, line_color_label, line_color_selector: line_specific_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) if isinstance(node, models.annotations.LineMapArea): line_width_edit.SetValue(node.line_width) line_color_selector.SetColour(node.line_color) line_arrow_checkbox.SetValue(node.line_arrow) else: line_color_selector.SetColour(djvu.const.MAPAREA_LINE_COLOR_DEFAULT) text_background_color_label = wx.CheckBox(self, label = _('Background color') + ': ') text_background_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) text_background_color_selector.SetColour(wx.WHITE) text_background_color_selector.Enable(False) def on_switch_text_background_color(event): text_background_color_selector.Enable(event.IsChecked()) self.Bind(wx.EVT_CHECKBOX, on_switch_text_background_color, text_background_color_label) text_color_label = wx.StaticText(self, label = _('Text color') + ': ') text_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) text_pushpin = wx.CheckBox(self, label = _('Pushpin')) for widget in text_background_color_label, text_background_color_selector, text_color_label, text_color_selector, text_pushpin: text_specific_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) if isinstance(node, models.annotations.TextMapArea): if node.background_color is not None: text_background_color_label.SetValue(True) text_background_color_selector.Enable(True) text_background_color_selector.SetColour(node.background_color) else: text_background_color_label.SetValue(False) text_background_color_selector.Enable(False) text_color_selector.SetColour(node.text_color) text_pushpin.SetValue(node.pushpin) else: text_color_selector.SetColour(djvu.const.MAPAREA_LINE_COLOR_DEFAULT) self._edit_have_highlight = highlight_color_label self._edit_highlight_color = highlight_color_selector self._edit_opacity = opacity_slider self._edit_line_width = line_width_edit self._edit_line_color = line_color_selector self._edit_arrow = line_arrow_checkbox self._edit_background_nontrasparent = text_background_color_label self._edit_background_color = text_background_color_selector self._edit_text_color = text_color_selector self._edit_pushpin = text_pushpin return extra_sizers def __init__(self, parent, node=None, origin=None): wx.Dialog.__init__(self, parent, title = _('Overprinted annotation (hyperlink) properties')) self._node = node if origin is None: self._origin = None else: self._origin = tuple(origin) if len(self._origin) != 2: raise ValueError sizer = wx.BoxSizer(wx.VERTICAL) main_properties_box_sizer = self._setup_main_properties_box() shape_box_sizer = self._setup_shape_box() border_box_sizer = self._setup_border_box() extra_sizers = self._setup_extra_boxes() for box_sizer in [main_properties_box_sizer, shape_box_sizer, border_box_sizer] + extra_sizers: sizer.Add(box_sizer, 0, wx.EXPAND | wx.ALL, 5) line = wx.StaticLine(self, -1, style = wx.LI_HORIZONTAL) sizer.Add(line, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5) button_sizer = wx.StdDialogButtonSizer() button = wx.Button(self, wx.ID_OK) button.SetDefault() button_sizer.AddButton(button) button = wx.Button(self, wx.ID_CANCEL) button_sizer.AddButton(button) button_sizer.Realize() sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 5) self.SetSizer(sizer) self.Fit() self._sizer = sizer if self._node is None: i, shape = 0, SHAPE_RECTANGLE else: for i, shape in enumerate(SHAPES): if isinstance(node, shape.model_class): break else: raise TypeError self._edit_shape.SetSelection(i) self.do_select_shape(shape) def get_node(self): shape = SHAPES[self._edit_shape.GetSelection()] model_class = shape.model_class node = model_class.from_maparea(self._node, owner=None) node.uri = self._edit_uri.GetValue() node.target = self._edit_target.GetValue() or None node.comment = self._edit_comment.GetValue() if self._edit_border_none.GetValue(): node.border = models.annotations.NoBorder() elif self._edit_border_xor.GetValue(): node.border = models.annotations.XorBorder() elif self._edit_border_solid.GetValue(): color = color_as_html(self._edit_border_solid_color.GetColour()) node.border = models.annotations.SolidBorder(color) elif model_class.can_have_shadow_border(): for widget in self._edit_border_shadows: if widget.GetValue(): node.border = widget.model_class(self._edit_border_width.GetValue()) break node.border_always_visible = ( self._edit_border_always_visible.IsEnabled() and self._edit_border_always_visible.GetValue() ) if isinstance(node, models.annotations.RectangleMapArea): if self._edit_have_highlight.GetValue(): node.highlight_color = color_as_html(self._edit_highlight_color.GetColour()) else: node.highlight_color = None node.opacity = self._edit_opacity.GetValue() elif isinstance(node, models.annotations.LineMapArea): node.line_width = self._edit_line_width.GetValue() node.line_color = color_as_html(self._edit_line_color.GetColour()) node.line_arrow = self._edit_arrow.GetValue() elif isinstance(node, models.annotations.TextMapArea): if self._edit_background_nontrasparent.GetValue(): node.background_color = color_as_html(self._edit_background_color.GetColour()) else: node.background_color = None node.text_color = color_as_html(self._edit_text_color.GetColour()) node.pushpin = self._edit_pushpin.GetValue() if self._origin: node.origin = self._origin return node node = property(get_node) __all__ = 'MapareaPropertiesDialog' # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/page.py0000644000000000000000000005460412111506274016566 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2010 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. import wx import wx.lib.ogl from djvu import decode, sexpr import djvu.const import djvusmooth.gui.maparea_menu from djvusmooth import gui import djvusmooth.models.text import djvusmooth.models.annotations from djvusmooth import models from djvusmooth.varietes import not_overridden PIXEL_FORMAT = decode.PixelFormatRgb() PIXEL_FORMAT.rows_top_to_bottom = 1 PIXEL_FORMAT.y_top_to_bottom = 1 RENDER_NONRASTER_TEXT = 0 RENDER_NONRASTER_MAPAREA = 1 RENDER_NONRASTER_VALUES = (RENDER_NONRASTER_TEXT, RENDER_NONRASTER_MAPAREA, None) class Zoom(object): @not_overridden def rezoom_on_resize(self): raise NotImplementedError @not_overridden def get_page_screen_size(self, page_job, viewport_size): raise NotImplementedError def _get_percent(self): raise ValueError @apply def percent(): def get(self): return self._get_percent() return property(get) class PercentZoom(Zoom): def __init__(self, percent = 100): self._percent = float(percent) def rezoom_on_resize(self): return False def get_page_screen_size(self, page_job, viewport_size): dpi = float(page_job.dpi) real_page_size = (page_job.width, page_job.height) screen_page_size = tuple(int(t * self._percent / dpi) for t in (real_page_size)) return screen_page_size def _get_percent(self): return self._percent class OneToOneZoom(Zoom): def rezoom_on_resize(self): return False def get_page_screen_size(self, page_job, viewport_size): real_page_size = (page_job.width, page_job.height) return real_page_size class StretchZoom(Zoom): def rezoom_on_resize(self): return True def get_page_screen_size(self, page_job, viewport_size): return viewport_size class FitWidthZoom(Zoom): def rezoom_on_resize(self): return True def get_page_screen_size(self, page_job, (viewport_width, viewport_height)): real_width, real_height = (page_job.width, page_job.height) ratio = 1.0 * real_height / real_width return (viewport_width, int(viewport_width * ratio)) class FitPageZoom(Zoom): def rezoom_on_resize(self): return True def get_page_screen_size(self, page_job, (viewport_width, viewport_height)): real_width, real_height = (page_job.width, page_job.height) ratio = 1.0 * real_height / real_width screen_height = int(viewport_width * ratio) if screen_height <= viewport_height: screen_width = viewport_width else: screen_width = int(viewport_height / ratio) screen_height = viewport_height return (screen_width, screen_height) class PageImage(wx.lib.ogl.RectangleShape): def __init__(self, widget, page_job, real_page_size, viewport_size, screen_page_size, xform_real_to_screen, render_mode, zoom): self._widget = widget self._render_mode = render_mode self._zoom = zoom self._screen_page_size = screen_page_size self._xform_real_to_screen = xform_real_to_screen self._page_job = page_job wx.lib.ogl.RectangleShape.__init__(self, *screen_page_size) self.SetX(self._width // 2) self.SetY(self._height // 2) def OnLeftClick(self, x, y, keys=0, attachment=0): shape = self.GetShape() canvas = shape.GetCanvas() dc = wx.ClientDC(canvas) canvas.PrepareDC(dc) to_deselect = list(shape for shape in canvas.GetDiagram().GetShapeList() if shape.Selected()) for shape in to_deselect: shape.Select(False, dc) if to_deselect: canvas.Redraw(dc) shape.node.notify_deselect() def OnRightClick(self, x, y, keys=0, attachment=0): wx.CallAfter(lambda: self._widget.on_right_click((x, y), None)) def OnDraw(self, dc): x, y, w, h = self.GetCanvas().GetUpdateRegion().GetBox() if w < 0 or h < 0 or x == y == w == h == 0: # This is not a regular refresh. # So just see what might have been overwritten. x, y, w, h = dc.GetBoundingBox() dc.BeginDrawing() page_job = self._page_job xform_real_to_screen = self._xform_real_to_screen try: if page_job is None: raise decode.NotAvailable page_width, page_height = self._screen_page_size if x < 0: w += x x = 0 if x >= page_width or w <= 0: raise decode.NotAvailable if x + w > page_width: w = page_width - x if y < 0: h += y y = 0 if y >= page_height or h <= 0: raise decode.NotAvailable if y + h > page_height: h = page_height - y render_mode = self._render_mode if render_mode is None: raise decode.NotAvailable else: data = page_job.render( render_mode, (0, 0, page_width, page_height), (x, y, w, h), PIXEL_FORMAT, 1 ) image = wx.EmptyImage(w, h) image.SetData(data) dc.DrawBitmap(image.ConvertToBitmap(), x, y) except decode.NotAvailable: dc.SetBrush(wx.WHITE_BRUSH) dc.SetPen(wx.TRANSPARENT_PEN) dc.DrawRectangle(x, y, w, h) dc.EndDrawing() class NodeShape(wx.lib.ogl.RectangleShape): def _get_frame_color(self): raise NotImplementedError def _get_text(self): return None def __init__(self, node, has_text, xform_real_to_screen): wx.lib.ogl.RectangleShape.__init__(self, 100, 100) self._xform_real_to_screen = xform_real_to_screen self._node = node self._update_size() self._text_color = self._get_frame_color() self._text_pen = wx.Pen(self._text_color, 1) self.SetBrush(wx.TRANSPARENT_BRUSH) self._shows_text = has_text self._text = None if self._update_text() is not None: self._textMarginX = 1 self._textMarginY = 1 self.SetBrush(wx.WHITE_BRUSH) self.SetPen(self._text_pen) self.SetCentreResize(False) @property def node(self): return self._node def _update_size(self): x, y, w, h = self._xform_real_to_screen(self._node.rect) if h <= 13: font_size = 8 elif h <= 15: font_size = 9 else: font_size = 10 self.SetFont(wx.Font(font_size, wx.SWISS, wx.NORMAL, wx.NORMAL)) self.SetSize(w, h) x, y = x + w // 2, y + h // 2 self.SetX(x) self.SetY(y) def _update_text(self): self.ClearText() node = self._node if not self._shows_text: return text = self._text = self._get_text() if text is None: return self.AddText(text) return text def update(self): self._update_size() self._update_text() canvas = self.GetCanvas() canvas.Refresh() # FIXME: something lighter here? def _update_node_size(self): x, y, w, h = self.GetX(), self.GetY(), self.GetWidth(), self.GetHeight() screen_rect = x - w//2, y - h//2, w, h self._node.rect = self._xform_real_to_screen.inverse(screen_rect) def OnMovePost(self, dc, x, y, old_x, old_y, display): wx.lib.ogl.RectangleShape.OnMovePost(self, dc, x, y, old_x, old_y, display) self._update_node_size() def get_cdc(self): canvas = self.GetCanvas() dc = wx.ClientDC(canvas) canvas.PrepareDC(dc) return canvas, dc def deselect(self, notify = True, cdc = None): if not self.Selected(): return try: canvas, dc = cdc except TypeError: canvas, dc = self.get_cdc() self.Select(False, dc) canvas.Redraw(dc) if notify: self.node.notify_deselect() def select(self, notify = True, cdc = None): if self.Selected(): return try: canvas, dc = cdc except TypeError: canvas, dc = self.get_cdc() to_deselect = list(shape for shape in canvas.GetDiagram().GetShapeList() if shape.Selected()) self.Select(True, dc) for shape in to_deselect: shape.Select(False, dc) if to_deselect: canvas.Redraw(dc) if notify: self.node.notify_select() class PageTextCallback(models.text.PageTextCallback): def __init__(self, widget): self._widget = widget def notify_node_change(self, node): shape = self._widget._nonraster_shapes_map.get(node) if shape is not None: shape.update() def notify_node_select(self, node): self._widget.on_node_selected(node) def notify_node_deselect(self, node): self._widget.on_node_deselected(node) def notify_node_children_change(self, node): shape = self._widget._nonraster_shapes_map.get(node) if shape is not None: shape.deselect() self._widget.page = True # FIXME: consider something lighter here def notify_tree_change(self, node): self._widget.page = True class TextShape(NodeShape): _FRAME_COLORS = \ { djvu.const.TEXT_ZONE_COLUMN: (0x80, 0x80, 0x00), djvu.const.TEXT_ZONE_REGION: (0x80, 0x80, 0x80), djvu.const.TEXT_ZONE_PARAGRAPH: (0x80, 0x00, 0x00), djvu.const.TEXT_ZONE_LINE: (0x80, 0x00, 0x80), djvu.const.TEXT_ZONE_WORD: (0x00, 0x00, 0x80), djvu.const.TEXT_ZONE_CHARACTER: (0x00, 0x80, 0x00), } def _get_frame_color(self): return wx.Color(*self._FRAME_COLORS[self._node.type]) def _get_text(self): if self._node.is_inner(): return return self._node.text class MapareaShape(NodeShape): def _get_frame_color(self): try: return self._node.border.color except AttributeError: return wx.BLUE def _get_text(self): return self._node.uri class MapareaCallback(models.annotations.PageAnnotationsCallback): def __init__(self, widget): self._widget = widget def notify_node_change(self, node): shape = self._widget._nonraster_shapes_map.get(node) if shape is not None: shape.update() def notify_node_select(self, node): self._widget.on_node_selected(node) def notify_node_deselect(self, node): self._widget.on_node_deselected(node) def notify_node_delete(self, node): self._widget.page = True # FIXME: consider something lighter here def notify_node_add(self, node): self._widget.on_maparea_add(node) def notify_node_replace(self, node, other_node): self._widget.on_maparea_replace(node, other_node) class ShapeEventHandler(wx.lib.ogl.ShapeEvtHandler): def __init__(self, widget): self._widget = widget wx.lib.ogl.ShapeEvtHandler.__init__(self) def OnLeftClick(self, x, y, keys=0, attachment=0): shape = self.GetShape() canvas = shape.GetCanvas() dc = wx.ClientDC(canvas) canvas.PrepareDC(dc) if shape.Selected(): shape.deselect(notify = True) else: shape.select(notify = True) def OnRightClick(self, x, y, keys=0, attachment=0): shape = self.GetShape() wx.CallAfter(lambda: self._widget.on_right_click((x, y), shape.node)) class PageWidget(wx.lib.ogl.ShapeCanvas): _WXK_TO_LINK_GETTER = \ { wx.WXK_LEFT: lambda node: node.left_sibling, wx.WXK_RIGHT: lambda node: node.right_sibling, wx.WXK_UP: lambda node: node.parent, wx.WXK_DOWN: lambda node: node.left_child } def __init__(self, parent): wx.lib.ogl.ShapeCanvas.__init__(self, parent) self._initial_size = self.GetSize() self.SetBackgroundColour(wx.WHITE) self._diagram = wx.lib.ogl.Diagram() self._diagram.SetSnapToGrid(False) self.SetDiagram(self._diagram) self._diagram.SetCanvas(self) self._image = None dc = wx.ClientDC(self) self.PrepareDC(dc) self._render_mode = decode.RENDER_COLOR self._render_nonraster = None self.setup_nonraster_shapes() self._zoom = PercentZoom() self.page = None self._current_shape = None self.Bind(wx.EVT_CHAR, self.on_char) def OnMouseEvent(self, event): if event.GetEventType() == wx.wxEVT_MOUSEWHEEL: event.Skip() return wx.lib.ogl.ShapeCanvas.OnMouseEvent(self, event) def on_char(self, event): skip = True try: shape = self._current_shape if shape is None: return key_code = event.GetKeyCode() try: link_getter = self._WXK_TO_LINK_GETTER[key_code] next_node = link_getter(shape.node) next_shape = self._nonraster_shapes_map[next_node] except StopIteration: return except KeyError: if key_code == wx.WXK_DELETE: wx.CallAfter(shape.node.delete) skip = False return skip = False finally: if skip: event.Skip() def reselect(): shape.deselect() next_shape.select() wx.CallAfter(reselect) def on_right_click(self, point, node): if self.render_nonraster == RENDER_NONRASTER_MAPAREA: self.show_maparea_menu(node, point) def show_maparea_menu(self, node, point, extra={}): origin = self._xform_real_to_screen.inverse(point) gui.maparea_menu.show_menu(self, self._page_annotations, node, point, origin) def on_node_selected(self, node): try: shape = self._nonraster_shapes_map[node] except KeyError: return return self._on_shape_selected(shape) def on_node_deselected(self, node): try: shape = self._nonraster_shapes_map[node] except KeyError: return return self._on_shape_deselected(shape) def on_maparea_add(self, node): self.page = True # TODO: something lighter def on_maparea_replace(self, node, other_node): if node not in self._nonraster_shapes_map: return self.page = True # TODO: something lighter def _on_shape_selected(self, shape): shape.select(notify = False) # in case it was selected otherwhere self._current_shape = shape def _on_shape_deselected(self, shape): shape.deselect(notify = False) # in case it was selected otherwhere self._current_shape = None def on_parent_resize(self, event): if self._zoom.rezoom_on_resize(): self.zoom = self._zoom event.Skip() @apply def render_mode(): def get(self): return self._render_mode def set(self, value): self._render_mode = value self.page = True return property(get, set) @apply def render_nonraster(): def get(self): return self._render_nonraster def set(self, value): if value not in RENDER_NONRASTER_VALUES: raise ValueError self._render_nonraster = value self.setup_nonraster_shapes() self.recreate_shapes() return property(get, set) @apply def zoom(): def get(self): return self._zoom def set(self, value): self._zoom = value self.page = True return property(get, set) @apply def page(): def set(self, page): try: if page is None: raise decode.NotAvailable elif page is True: page_job = self._page_job if page_job is None: raise decode.NotAvailable page_text = self._page_text page_annotations = self._page_annotations callbacks = self._callbacks else: page_job = page.page_job text_callback = PageTextCallback(self) maparea_callback = MapareaCallback(self) callbacks = text_callback, maparea_callback page_text = page.text page_text.register_callback(text_callback) page_annotations = page.annotations page_annotations.register_callback(maparea_callback) real_page_size = (page_job.width, page_job.height) viewport_size = tuple(self.GetParent().GetSize()) screen_page_size = self._zoom.get_page_screen_size(page_job, viewport_size) screen_page_rect = (0, 0) + screen_page_size rotation = page_job.initial_rotation real_page_rect = (0, 0) + real_page_size xform_real_to_screen = decode.AffineTransform(real_page_rect, screen_page_rect) xform_real_to_screen.mirror_y() xform_rotate = decode.AffineTransform((0, 0, 1, 1), (0, 0, 1, 1)) xform_rotate.rotate(rotation) text_page_rect = (0, 0) + xform_rotate(real_page_rect)[2:] xform_text_to_screen = decode.AffineTransform(text_page_rect, screen_page_rect) xform_text_to_screen.mirror_y() xform_text_to_screen.rotate(rotation) self.set_size(screen_page_size) except decode.NotAvailable: screen_page_size = -1, -1 xform_real_to_screen = xform_text_to_screen = decode.AffineTransform((0, 0, 1, 1), (0, 0, 1, 1)) page_job = None page_text = None page_annotations = None need_recreate_text = True callbacks = () self._screen_page_size = screen_page_size self._xform_real_to_screen = xform_real_to_screen self._xform_text_to_screen = xform_text_to_screen self._page_job = page_job self._page_text = page_text self._page_annotations = page_annotations self._callbacks = callbacks if page is None: self.set_size(self._initial_size) if self._image is not None: self._image.Delete() self._image = None if page_job is not None: image = PageImage(self, page_job = page_job, real_page_size = real_page_size, viewport_size = viewport_size, screen_page_size = screen_page_size, xform_real_to_screen = xform_real_to_screen, render_mode = self.render_mode, zoom = self.zoom) image.SetDraggable(False, False) self._image = image self.setup_nonraster_shapes() self.recreate_shapes() return property(fset = set) def recreate_shapes(self): self.remove_all_shapes() image = self._image if image is not None: self.add_shape(image) if self.render_nonraster is not None: for shape in self._nonraster_shapes: self.add_shape(shape) event_handler = ShapeEventHandler(self) event_handler.SetShape(shape) event_handler.SetPreviousHandler(shape.GetEventHandler()) shape.SetEventHandler(event_handler) self.Refresh() def add_shape(self, shape): shape.SetCanvas(self) shape.Show(True) self._diagram.AddShape(shape) def remove_all_shapes(self): self._diagram.RemoveAllShapes() def set_size(self, size): if self.GetSize() == size: return self.SetSize(size) self.SetBestFittingSize(size) self.GetParent().Layout() self.GetParent().SetupScrolling() def setup_nonraster_shapes(self): self.clear_nonraster_shapes() have_text = self.render_mode is None if self.render_nonraster == RENDER_NONRASTER_TEXT and self._page_text is not None: self.setup_text_shapes(have_text) if self.render_nonraster == RENDER_NONRASTER_MAPAREA and self._page_annotations is not None: self.setup_maparea_shapes(have_text) def clear_nonraster_shapes(self): self._nonraster_shapes = () self._nonraster_shapes_map = {} def setup_maparea_shapes(self, have_text = False): xform_real_to_screen = self._xform_real_to_screen try: items = \ [ (node, MapareaShape(node, have_text, xform_real_to_screen)) for node in self._page_annotations.mapareas ] self._nonraster_shapes = tuple(shape for node, shape in items) self._nonraster_shapes_map = dict(items) except decode.NotAvailable: pass def setup_text_shapes(self, have_text = False): xform_text_to_screen = self._xform_text_to_screen try: page_type = sexpr.Symbol('page') items = \ [ (node, TextShape(node, have_text, xform_text_to_screen)) for node in self._page_text.get_preorder_nodes() if node is not None and node.type < djvu.const.TEXT_ZONE_PAGE ] self._nonraster_shapes = tuple(shape for node, shape in items) self._nonraster_shapes_map = dict(items) except decode.NotAvailable: pass __all__ = ( 'Zoom', 'PercentZoom', 'OneToOneZoom', 'StretchZoom', 'FitWidthZoom', 'FitPageZoom', 'PageWidget', 'RENDER_NONRASTER_TEXT', 'RENDER_NONRASTER_MAPAREA' ) # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/metadata.py0000644000000000000000000001132211573212252017421 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # Copyright © 2009 Mateusz Turcza # # This package 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 2 dated June, 1991. # # This package 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. import wx import wx.grid import wx.lib.mixins.grid import djvu.sexpr from djvusmooth.i18n import _ LABELS = [_('key'), _('value')] class MetadataTable(wx.grid.PyGridTableBase): def __init__(self, model, known_keys): wx.grid.PyGridTableBase.__init__(self) self._model = model self._keys = sorted(model) self._keys.append(None) attr_normal = wx.grid.GridCellAttr() attr_known = wx.grid.GridCellAttr() font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetWeight(wx.FONTWEIGHT_BOLD) attr_known.SetFont(font) self._attrs = attr_normal, attr_known self._known_keys = known_keys def GetAttr(self, y, x, kind): key = self._keys[y] attr = self._attrs[x == 0 and key in self._known_keys] attr.IncRef() return attr def GetColLabelValue(self, n): return LABELS[n] def GetNumberRows(self): return len(self._keys) def GetNumberCols(self): return 2 def GetValue(self, y, x): try: key = self._keys[y] if key is None: value = '' elif x: value = self._model[key] else: value = key except IndexError: value = '' return value def set_value(self, key, value): self._model[key] = value def set_new_key(self, y, new_key, value): assert isinstance(new_key, djvu.sexpr.Symbol) del self._model[self._keys[y]] self._model[new_key] = value self._keys[y] = new_key def add_new_key(self, new_key): assert isinstance(new_key, djvu.sexpr.Symbol) self._model[new_key] = '' y = len(self._keys) - 1 self._keys[y:] = new_key, None self.GetView().ProcessTableMessage(wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, 1)) def delete_key(self, y): key = self._keys[y] assert isinstance(key, djvu.sexpr.Symbol) del self._model[key] del self._keys[y] self.GetView().ProcessTableMessage(wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, y, 1)) def SetValue(self, y, x, value): key = self._keys[y] if x == 0: if value == key: pass elif not value: self.delete_key(y) # Delete a row elif value in self._model: pass # TODO: raise an exception else: value = djvu.sexpr.Symbol(value.encode('UTF-8')) if key is None: # Add a row self.add_new_key(value) else: self.set_new_key(y, value, self._model[key]) elif x == 1: self.set_value(key, value) class MetadataGrid(wx.grid.Grid, wx.lib.mixins.grid.GridAutoEditMixin): def __init__(self, parent, model, known_keys): wx.grid.Grid.__init__(self, parent) table = MetadataTable(model, known_keys) self.SetTable(table) self.SetRowLabelSize(0) self.SetDefaultEditor(wx.grid.GridCellAutoWrapStringEditor()) self.AutoSize() class MetadataDialog(wx.Dialog): def __init__(self, parent, models, known_keys): wx.Dialog.__init__(self, parent, title=_('Edit metadata'), style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) sizer = wx.BoxSizer(wx.VERTICAL) tabs = wx.Notebook(self, -1) for model in models: grid = MetadataGrid(tabs, model, known_keys) tabs.AddPage(grid, model.title) sizer.Add(tabs, 1, wx.EXPAND | wx.ALL, 5) line = wx.StaticLine(self, -1, style = wx.LI_HORIZONTAL) sizer.Add(line, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5) button_sizer = wx.StdDialogButtonSizer() button = wx.Button(self, wx.ID_OK) button.SetDefault() button_sizer.AddButton(button) button = wx.Button(self, wx.ID_CANCEL) button_sizer.AddButton(button) button_sizer.Realize() sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 5) self.SetSizerAndFit(sizer) __all__ = 'MetadataDialog', # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/text_browser.py0000644000000000000000000001170512111506274020374 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2010 Jakub Wilk # Copyright © 2009 Mateusz Turcza # # This package 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 2 dated June, 1991. # # This package 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. import wx import djvusmooth.models.text from djvusmooth import models from djvusmooth.varietes import replace_control_characters from djvusmooth.i18n import _ def get_label_for_node(node): zone_type = str(node.type) if node.is_inner(): return _(zone_type) else: return _(zone_type) + ': ' + replace_control_characters(' ', node.text) class PageTextCallback(models.text.PageTextCallback): def __init__(self, browser): self._browser = browser def notify_node_change(self, node): wx.CallAfter(lambda: self._browser.on_node_change(node)) def notify_node_children_change(self, node): wx.CallAfter(lambda: self._browser.on_tree_change(node)) # FIXME: consider something lighter here def notify_tree_change(self, node): wx.CallAfter(lambda: self._browser.on_tree_change(node)) def notify_node_deselect(self, node): pass def notify_node_select(self, node): wx.CallAfter(lambda: self._browser.on_node_select(node)) class TextBrowser(wx.TreeCtrl): def __init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS): wx.TreeCtrl.__init__(self, parent, id, pos, size, style) self._have_root = False self.page = None self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.on_begin_edit, self) self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.on_end_edit, self) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_selection_changed, self) def on_node_change(self, node): try: item = self._items[node] except KeyError: return if node.is_inner(): return self.SetItemText(item, get_label_for_node(node)) def on_node_select(self, node): try: item = self._items[node] except KeyError: return if self.GetSelection() != item: self.SelectItem(item) def on_tree_change(self, model_node): self.page = True @apply def page(): def get(self): return self._page def set(self, value): if value is not True: self._page = value if self._page is not None: self._callback = PageTextCallback(self) self._page.text.register_callback(self._callback) self._recreate_children() return property(get, set) def on_selection_changed(self, event): event.Skip() item = event.GetItem() if not item: return node = self.GetPyData(item) if node is None: return node.notify_select() def on_begin_edit(self, event): item = event.GetItem() if not self.do_begin_edit(item): event.Veto() def do_begin_edit(self, item): node = self.GetPyData(item) if node is None: return if node.is_leaf(): self.SetItemText(item, node.text) return True def on_end_edit(self, event): item = event.GetItem() if event.IsEditCancelled(): new_text = None else: new_text = event.GetLabel() if not self.do_end_edit(item, new_text): event.Veto() def do_end_edit(self, item, text): node = self.GetPyData(item) if node is None: return if text is None: text = node.text node.text = text return True def _add_children(self, item, parent_node): if not parent_node.is_inner(): return for node in parent_node: symbol = node.type label = get_label_for_node(node) child_item = self.AppendItem(item, label) self._add_children(child_item, node) self._items[node] = child_item self.SetPyData(child_item, node) def _recreate_children(self): self._items = {} root = self.GetRootItem() if root.IsOk(): self.Delete(root) if self.page is None: return node = self.page.text.root if node is not None: label = get_label_for_node(node) root = self.AddRoot(label) self._items[node] = root self.SetPyData(root, node) self._have_root = True self._add_children(root, node) __all__ = 'TextBrowser', # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/outline_browser.py0000644000000000000000000001763112111506274021073 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009, 2010, 2011, 2013 Jakub Wilk # Copyright © 2009 Mateusz Turcza # # This package 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 2 dated June, 1991. # # This package 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. import wx import djvusmooth.models.outline from djvusmooth import models from djvusmooth.varietes import replace_control_characters from djvusmooth.i18n import _ def get_label_for_node(node): return replace_control_characters(' ', node.text) class OutlineCallback(models.outline.OutlineCallback): def __init__(self, browser): self._browser = browser def notify_node_change(self, node): wx.CallAfter(lambda: self._browser.on_node_change(node)) def notify_node_select(self, node): wx.CallAfter(lambda: self._browser.on_node_select(node)) def notify_node_children_change(self, node): wx.CallAfter(lambda: self._browser.on_node_children_change(node)) def notify_tree_change(self, node): wx.CallAfter(lambda: self._browser.on_tree_change(node)) class OutlineBrowser(wx.TreeCtrl): def __init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS): wx.TreeCtrl.__init__(self, parent, id, pos, size, style) self._items = {} self._root_item = None self._document = None self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.on_begin_edit, self) self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.on_end_edit, self) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_selection_changed, self) self.Bind(wx.EVT_CHAR, self.on_char) self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.on_begin_drag) self.Bind(wx.EVT_TREE_END_DRAG, self.on_end_drag) def on_begin_drag(self, event): drag_item = event.GetItem() if drag_item == self._root_item: event.Veto() return self._drag_item = drag_item node = self.GetPyData(drag_item) if node is not None: event.Allow() def on_end_drag(self, event): source = self._drag_item del self._drag_item target = event.GetItem() if not target.IsOk(): return if source == target: return source_node = self.GetPyData(source) if source_node is None: return target_node = self.GetPyData(target) if target_node is None: return target_ancestor = target_node while True: if target_ancestor is source_node: # Cannot move a node to its child return try: target_ancestor = target_ancestor.parent except StopIteration: break source_node.delete() target_node.add_child(source_node) source_node.notify_select() def do_goto_node(self, node): uri = node.uri if uri.startswith('#'): try: n = int(buffer(uri, 1)) except ValueError: return # TODO: try to handle non-local URIs parent = wx.GetTopLevelParent(self) parent.page_no = n - 1 else: return # TODO: try to handle non-local URIs def do_delete_node(self, node): try: node.delete() except NotImplementedError: return _WXK_TO_METHOD = { wx.WXK_RETURN: do_goto_node, wx.WXK_DELETE: do_delete_node } def on_char(self, event): key_code = event.GetKeyCode() try: method = self._WXK_TO_METHOD[key_code] except KeyError: return item = self.GetSelection() node = self.GetPyData(item) if node is None: return method(self, node) def on_node_select(self, node): try: item = self._items[node] except KeyError: return if self.GetSelection() != item: self.SelectItem(item) def on_node_change(self, node): try: item = self._items[node] except KeyError: return self.SetItemText(item, get_label_for_node(node)) def on_tree_change(self, model_node): self.document = True def on_node_children_change(self, node): if self._root_item is None: self._create_root_item() try: item = self._items[node] except KeyError: if isinstance(node, models.outline.RootNode): item = self.GetRootItem() else: return self.DeleteChildren(item) self._add_children(item, node) @apply def document(): def get(self): return self._document def set(self, value): if value is not True: self._document = value if self._document is not None: self._callback = OutlineCallback(self) self._document.outline.register_callback(self._callback) self._recreate_children() return property(get, set) def on_selection_changed(self, event): event.Skip() item = event.GetItem() if not item: return node = self.GetPyData(item) if node is None: return node.notify_select() def on_begin_edit(self, event): item = event.GetItem() if not self.do_begin_edit(item): event.Veto() def do_begin_edit(self, item): if item == self._root_item: return node = self.GetPyData(item) if node is None: return self.SetItemText(item, node.text) return True def on_end_edit(self, event): item = event.GetItem() if event.IsEditCancelled(): new_text = None else: new_text = event.GetLabel() if not self.do_end_edit(item, new_text): event.Veto() def do_end_edit(self, item, text): node = self.GetPyData(item) if node is None: return if text is None: text = node.text node.text = text return True def DeleteChildren(self, item): child = self.GetFirstChild(item)[0] while child: next_child = self.GetNextSibling(child) self.Delete(child) child = next_child def Delete(self, item): self.DeleteChildren(item) node = self.GetPyData(item) self._items.pop(node, None) wx.TreeCtrl.Delete(self, item) def DeleteAllItems(self): wx.TreeCtrl.DeleteAllItems(self) self._items.clear() def _add_children(self, item, nodes): for node in nodes: symbol = node.type label = get_label_for_node(node) child_item = self.AppendItem(item, label) self._add_children(child_item, node) self._items[node] = child_item self.SetPyData(child_item, node) def _create_root_item(self): node = self.document.outline.root if node: type = str(node.type) self._root_item = self.AddRoot(_(type)) self.SetPyData(self._root_item, node) return True else: self._root_item = None return False def _recreate_children(self): self.DeleteAllItems() assert not self._items self._root_item = None if self.document is None: return if self._create_root_item(): self._add_children(self._root_item, self.document.outline.root) __all__ = 'OutlineBrowser', # vim:ts=4 sw=4 et djvusmooth-0.2.14/lib/gui/__init__.py0000644000000000000000000000000011570151711017366 0ustar rootroot00000000000000djvusmooth-0.2.14/lib/gui/maparea_menu.py0000644000000000000000000000371711573212252020304 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008, 2009 Jakub Wilk # Copyright © 2009 Mateusz Turcza # # This package 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 2 dated June, 1991. # # This package 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. import wx from djvusmooth.gui.maparea_properties import MapareaPropertiesDialog from djvusmooth.i18n import _ def show_menu(parent, annotations, node, point, origin=None): menu = wx.Menu() try: menu_item = menu.Append(wx.ID_ANY, _(u'&New hyperlink…')) parent.Bind(wx.EVT_MENU, lambda event: on_new_annotation(event, parent, annotations, origin), menu_item) if node is not None: menu_item = menu.Append(wx.ID_ANY, _(u'&Properties…')) parent.Bind(wx.EVT_MENU, lambda event: on_properties(event, parent, node), menu_item) menu_item = menu.Append(wx.ID_ANY, _('&Remove') + '\tDel') parent.Bind(wx.EVT_MENU, lambda event: on_delete(event, parent, node), menu_item) del menu_item parent.PopupMenu(menu, point) finally: menu.Destroy() def on_new_annotation(event, parent, annotations, origin): dialog = MapareaPropertiesDialog(parent, origin=origin) try: if dialog.ShowModal() != wx.ID_OK: return dialog.node.insert(annotations) finally: dialog.Destroy() def on_properties(event, parent, node): dialog = MapareaPropertiesDialog(parent, node) try: if dialog.ShowModal() != wx.ID_OK: return node.replace(dialog.node) finally: dialog.Destroy() def on_delete(event, parent, node): node.delete() # vim:ts=4 sw=4 et djvusmooth-0.2.14/djvusmooth.py0000644000000000000000000000013511573212252016511 0ustar rootroot00000000000000# encoding=UTF-8 import sys import lib sys.modules['djvusmooth'] = lib # vim:ts=4 sw=4 et djvusmooth-0.2.14/extra/0000755000000000000000000000000012111517063015056 5ustar rootroot00000000000000djvusmooth-0.2.14/extra/djvusmooth.desktop0000644000000000000000000000040411674154102020655 0ustar rootroot00000000000000[Desktop Entry] Version=1.0 Type=Application Name=djvusmooth GenericName=Graphical editor for DjVu GenericName[pl]=Graficzny edytor plików DjVu Exec=/usr/bin/djvusmooth %f Terminal=false MimeType=image/x-djvu;image/x.djvu;image/vnd.djvu; Categories=Graphics; djvusmooth-0.2.14/setup.py0000644000000000000000000001476412111505377015466 0ustar rootroot00000000000000#!/usr/bin/python # encoding=UTF-8 # Copyright © 2008, 2009, 2010, 2011, 2012, 2013 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. ''' *djvusmooth* is a graphical editor for `DjVu `_ documents. ''' from __future__ import with_statement classifiers = ''' Development Status :: 4 - Beta Environment :: Console Intended Audience :: End Users/Desktop License :: OSI Approved :: GNU General Public License (GPL) Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 2 Topic :: Text Processing Topic :: Multimedia :: Graphics '''.strip().splitlines() import glob import os import re import distutils.core import distutils.errors from distutils.command.build import build as distutils_build from distutils.command.clean import clean as distutils_clean from distutils.command.sdist import sdist as distutils_sdist from lib import __version__ data_files = [] if os.name == 'posix': data_files += [ (os.path.join('share', 'applications'), ['extra/djvusmooth.desktop']) ] class build_mo(distutils_build): description = 'build binary message catalogs' def run(self): if os.name != 'posix': return for poname in glob.glob(os.path.join('po', '*.po')): lang, _ = os.path.splitext(os.path.basename(poname)) modir = os.path.join('locale', lang, 'LC_MESSAGES') if not os.path.isdir(modir): os.makedirs(modir) moname = os.path.join(modir, 'djvusmooth.mo') command = ['msgfmt', '-o', moname, '--check', '--check-accelerators', poname] self.make_file([poname], moname, distutils.spawn.spawn, [command]) data_files.append((os.path.join('share', modir), [moname])) class check_po(distutils_build): description = 'perform some checks on message catalogs' def run(self): for poname in glob.glob(os.path.join('po', '*.po')): checkname = poname + '.sdist-check' with open(checkname, 'w'): pass try: for feature in 'untranslated', 'fuzzy': with open(checkname, 'w'): pass command = ['msgattrib', '--' + feature, '-o', checkname, poname] distutils.spawn.spawn(command) with open(checkname) as file: entries = file.read() if entries: raise IOError(None, '%s has %s entries' % (poname, feature)) finally: os.unlink(checkname) class build_doc(distutils_build): description = 'build documentation' _url_regex = re.compile( r'^(\\%http://.*)', re.MULTILINE ) _date_regex = re.compile( '"(?P[0-9]{2})/(?P[0-9]{2})/(?P[0-9]{4})"' ) def build_man(self, manname, commandline): self.spawn(commandline) with open(manname, 'r+') as file: contents = file.read() # Format URLs: contents = self._url_regex.sub( lambda m: r'\m[blue]\fI%s\fR\m[]' % m.groups(), contents, ) # Use RFC 3339 date format: contents = self._date_regex.sub( lambda m: '%(year)s-%(month)s-%(day)s' % m.groupdict(), contents ) file.seek(0) file.truncate() file.write(contents) def run(self): if os.name != 'posix': return for xmlname in glob.glob(os.path.join('doc', '*.xml')): manname = os.path.splitext(xmlname)[0] + '.1' command = [ 'xsltproc', '--nonet', '--param', 'man.authors.section.enabled', '0', '--param', 'man.charmap.use.subset', '0', '--param', 'man.font.links', '"I"', '--output', 'doc/', 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl', xmlname, ] self.make_file([xmlname], manname, self.build_man, [manname, command]) data_files.append(('share/man/man1', [manname])) distutils_build.sub_commands[:0] = [ ('build_doc', None), ('build_mo', None) ] class clean(distutils_clean): def run(self): distutils_clean.run(self) if not self.all: return for manname in glob.glob(os.path.join('doc', '*.1')): with open(manname, 'r') as file: stamp = file.readline() if stamp != sdist.manpage_stamp: self.execute(os.unlink, [manname], 'removing %s' % manname) class sdist(distutils_sdist): manpage_stamp = '''.\\" [created by setup.py sdist]\n''' def run(self): self.run_command('build_doc') self.run_command('check_po') self.run_command('build_mo') return distutils_sdist.run(self) def _rewrite_manpage(self, manname): with open(manname, 'r') as file: contents = file.read() os.unlink(manname) with open(manname, 'w') as file: file.write(self.manpage_stamp) file.write(contents) def make_release_tree(self, base_dir, files): distutils_sdist.make_release_tree(self, base_dir, files) for manname in glob.glob(os.path.join(base_dir, 'doc', '*.1')): self.execute(self._rewrite_manpage, [manname], 'rewriting %s' % manname) distutils.core.setup( name = 'djvusmooth', version = __version__, license = 'GNU GPL 2', description = 'graphical editor for DjVu', long_description = __doc__.strip(), classifiers = classifiers, url = 'http://jwilk.net/software/djvusmooth', author = 'Jakub Wilk', author_email = 'jwilk@jwilk.net', packages = ['djvusmooth'] + ['djvusmooth.%s' % x for x in 'gui models text'.split()], package_dir = dict(djvusmooth='lib'), scripts = ['djvusmooth'], data_files = data_files, cmdclass = dict( build_doc=build_doc, build_mo=build_mo, check_po=check_po, clean=clean, sdist=sdist, ), ) # vim:ts=4 sw=4 et djvusmooth-0.2.14/po/0000755000000000000000000000000012111517063014351 5ustar rootroot00000000000000djvusmooth-0.2.14/po/pl.po0000644000000000000000000002205612111200454015322 0ustar rootroot00000000000000# Polish translation of djvusmooth messages # Copyright © 2009 Mateusz Turcza # This file is distributed under the same license as the djvusmooth package. msgid "" msgstr "" "Project-Id-Version: djvusmooth 0.2.14\n" "Report-Msgid-Bugs-To: Jakub Wilk \n" "POT-Creation-Date: 2013-02-20 17:57+0100\n" "PO-Revision-Date: 2012-10-09 20:41+0200\n" "Last-Translator: Jakub Wilk \n" "Language-Team: none\n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "&About" msgstr "&O programie" msgid "&Background" msgstr "&Tło" msgid "&Bookmark this page" msgstr "Dodaj stronę do z&akładek" msgid "&Close" msgstr "&Zamknij" msgid "&Color" msgstr "&Kolor" msgid "&Edit" msgstr "&Edycja" msgid "&External editor" msgstr "&Zewnętrzny edytor" msgid "&File" msgstr "&Plik" msgid "&First page" msgstr "Pi&erwsza strona" msgid "&Flatten" msgstr "&Spłaszcz strukturę tekstu" msgid "&Foreground" msgstr "&Pierwszy plan" msgid "&Go" msgstr "&Idź" msgid "&Go to page…" msgstr "I&dź do strony…" msgid "&Help" msgstr "Pomo&c" msgid "&Hyperlinks" msgstr "&Hiperłącza" msgid "&Image" msgstr "O&braz" msgid "&Last page" msgstr "&Ostatnia strona" msgid "&Metadata" msgstr "&Metadane" msgid "&New hyperlink…" msgstr "&Nowe hiperłącze…" msgid "&Next page" msgstr "&Następna strona" msgid "&Non-raster data" msgstr "Dane &nierastrowe" msgid "&None" msgstr "&Brak" msgid "&Open" msgstr "&Otwórz" msgid "&Outline" msgstr "&Konspekt" msgid "&Previous page" msgstr "&Poprzednia strona" msgid "&Properties…" msgstr "&Właściwości…" msgid "&Quit" msgstr "Za&kończ" msgid "&Refresh" msgstr "&Odśwież" msgid "&Remove" msgstr "&Usuń" msgid "&Remove all" msgstr "&Usuń wszystko" msgid "&Save" msgstr "Za&pisz" msgid "&Settings" msgstr "&Ustawienia" msgid "&Stencil" msgstr "&Szablon" msgid "&Stretch" msgstr "&Rozciągnij" msgid "&Text" msgstr "&Tekst" msgid "&View" msgstr "&Widok" msgid "&Zoom" msgstr "&Powiększenie" msgid "(no title)" msgstr "(brak tytułu)" msgid "About…" msgstr "O programie…" msgid "Add the current to document outline" msgstr "Dodaj do konspektu dokumentu" msgid "Always visible" msgstr "Zawsze widoczny" msgid "" "An unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author.\n" "\n" msgstr "" "Wystąpił nieoczekiwany błąd. Jeśli ta sytuacja się powtórzy, powiadom autora programu.\n" "\n" msgid "Arrow" msgstr "Strzałka" msgid "Author" msgstr "Autor" msgid "Background color" msgstr "Kolor tła" msgid "Border" msgstr "Obramowanie" msgid "Cannot edit text with character zones." msgstr "Nie można edytować tekstu ze strefami znakowymi." msgid "Close the document" msgstr "Zamknij dokument" msgid "Comment" msgstr "Komentarz" msgid "Decrease the magnification" msgstr "Zmniejsz powiększenie" msgid "Display everything" msgstr "Pokaż wszystko" msgid "Display only the background layer" msgstr "Pokaż tylko tło" msgid "Display only the document bitonal stencil" msgstr "Pokaż tylko dwubarwny szablon dokumentu" msgid "Display only the foreground layer" msgstr "Pokaż tylko pierwszy plan" msgid "Display overprinted annotations" msgstr "Pokazuj obwódki wokół hiperłącz" msgid "Display the text layer" msgstr "Pokaż warstwę tekstową" msgid "DjVu files (*.djvu, *.djv)|*.djvu;*.djv|All files|*" msgstr "Pliki DjVu (*.djvu, *.djv)|*.djvu;*.djv|Wszystkie pliki|*" msgid "Do you want to save your changes?" msgstr "Czy chcesz zapisać zmiany?" msgid "Document metadata" msgstr "Metadane dokumentu" msgid "Don't display non-raster data" msgstr "Nie pokazuj danych nierastrowych" msgid "Edit document outline in an external editor" msgstr "Edytuj konspekt dokumentu w zewnętrznym edytorze" msgid "Edit metadata" msgstr "Edytuj metadane" msgid "Edit page text in an external editor" msgstr "Edytuj tekst strony w zewnętrznym edytorze" msgid "Edit the document or page metadata" msgstr "Edytuj metadane dokumentu lub strony" msgid "Enter path to your favourite text editor." msgstr "Podaj ścieżkę do edytora tekstowego." msgid "Error" msgstr "Błąd" msgid "Etched in" msgstr "Wklęsły" msgid "Etched out" msgstr "Wypukły" #, python-format msgid "" "External edit failed:\n" "%s" msgstr "" "Zewnętrzna edycja nie powiodła się:\n" "%s" msgid "External editor…" msgstr "Zewnętrzny edytor…" msgid "Fit &page" msgstr "D&opasuj do okna" msgid "Fit &width" msgstr "Dopasuj &szerokość" msgid "Flatten text" msgstr "Spłaszcz strukturę tekstu" msgid "Go to page" msgstr "Idź do strony" msgid "Highlight color" msgstr "Kolor podświetlenia" msgid "Highlight color and opacity" msgstr "Kolor i przezroczystość podświetlenia" msgid "Hyperlinks" msgstr "Hiperłącza" msgid "Increase the magnification" msgstr "Zwiększ powiększenie" msgid "Jump to first document page" msgstr "Przejdź do pierwszej strony dokumentu" msgid "Jump to last document page" msgstr "Przejdź do ostatniej strony dokumentu" msgid "Jump to next document page" msgstr "Przejdź do następnej strony dokumentu" msgid "Jump to page…" msgstr "Przejdź do strony…" msgid "Jump to previous document page" msgstr "Przejdź do poprzedniej strony dokumentu" msgid "License" msgstr "Licencja" msgid "Line" msgstr "Linia" msgid "Line color" msgstr "Kolor linii" msgid "Line width" msgstr "Szerokość linii" msgid "Line-specific properties" msgstr "Właściwości linii" #, python-format msgid "Link: %s" msgstr "Łącze: %s" #, python-format msgid "Magnify %d%%" msgstr "Powiększ do %d%%" msgid "Main properties" msgstr "Właściwości" msgid "More information about this program" msgstr "Więcej informacji o tym programie" msgid "Neither display the foreground layer nor the background layer" msgstr "Nie pokazuj pierwszego planu ani tła" msgid "No text layer to edit." msgstr "Brak warstwy tekstowej do edycji." msgid "None" msgstr "Brak" msgid "Number of lines changed." msgstr "Liczba linii uległa zmianie." msgid "One &to one" msgstr "&Jeden do jednego" msgid "Opacity" msgstr "Przezroczystość" msgid "Open &recent" msgstr "Ostatnio otwie&rane" msgid "Open a DjVu document" msgstr "Otwórz dokument DjVu" msgid "Outline" msgstr "Konspekt" msgid "Oval" msgstr "Elipsa" msgid "Overprinted annotation (hyperlink) properties" msgstr "Właściwości adnotacji (hiperłącza)" #, python-format msgid "Page %(pageno)d of %(npages)d" msgstr "Strona %(pageno)d z %(npages)d" #, python-format msgid "Page %d metadata" msgstr "Metadane strony %d" msgid "Polygon" msgstr "Wielokąt" msgid "Pushpin" msgstr "Pinezka" msgid "Quit the application" msgstr "Zamknij aplikację" msgid "Rectangle" msgstr "Prostokąt" msgid "Refresh the window" msgstr "Odśwież okno" msgid "Remove details" msgstr "Usuń szczegóły" msgid "Remove details from page text" msgstr "Usuń szczegóły z tekstu" msgid "Remove whole document outline" msgstr "Usuń cały konspekt dokumentu" msgid "Save the document" msgstr "Zapisz dokument" msgid "Saving document" msgstr "Zapisywanie dokumentu" #, python-format msgid "" "Saving document failed:\n" "%s" msgstr "" "Zapisanie dokumentu nie powiodło się:\n" "%s" msgid "Saving the document, please wait…" msgstr "Zapisywanie dokumentu, proszę czekać…" msgid "Scope" msgstr "Zakres" msgid "Set full resolution magnification." msgstr "Dopasuj powiększenie do pełnej rozdzielczości." msgid "Set magnification to fit page" msgstr "Dopasuj powiększenie do całej strony" msgid "Set magnification to fit page width" msgstr "Dopasuj powiększenie do całej szerokości strony" msgid "Setup an external editor" msgstr "Ustawienia zewnętrznego edytora" msgid "Shadow in" msgstr "Cień wewnątrz" msgid "Shadow out" msgstr "Cień na zewnątrz" msgid "Shape" msgstr "Kształt" msgid "Show &sidebar" msgstr "Pokaż panel &boczny" msgid "Show/hide the sidebar" msgstr "Pokaż/ukryj panel boczny" msgid "Solid color" msgstr "Jednolity kolor" msgid "Stretch the image to the window size" msgstr "Rozciągnij obraz do rozmiarów okna" msgid "Target frame" msgstr "Ramka docelowa" msgid "Text" msgstr "Tekst" msgid "Text color" msgstr "Kolor tekstu" msgid "Text-specific properties" msgstr "Właściwości tekstu" msgid "URI" msgstr "URI" #, python-format msgid "Unhandled exception: %s" msgstr "Nieobsłużony wyjątek: %s" msgid "Width" msgstr "Grubość" msgid "XOR" msgstr "XOR" msgid "Zoom &in" msgstr "Pow&iększ" msgid "Zoom &out" msgstr "P&omniejsz" msgid "[Text layer]" msgstr "[Warstwa tekstowa]" msgid "all" msgstr "wszystko" msgid "all pages" msgstr "wszystkie strony" msgid "bookmarks" msgstr "zakładki" msgid "char" msgstr "znak" msgid "characters" msgstr "znaki" msgid "column" msgstr "łam" msgid "columns" msgstr "łamy" msgid "current page" msgstr "bieżąca strona" msgid "key" msgstr "klucz" msgid "line" msgstr "linia" msgid "lines" msgstr "linie" msgid "page" msgstr "strona" msgid "para" msgstr "akapit" msgid "paragraphs" msgstr "akapity" msgid "region" msgstr "region" msgid "regions" msgstr "regiony" msgid "value" msgstr "wartość" msgid "word" msgstr "słowo" msgid "words" msgstr "słowa" djvusmooth-0.2.14/po/ru.po0000644000000000000000000002631012111431205015332 0ustar rootroot00000000000000# Russian translation for djvusmooth. # Copyright © 2010 Kyrill Detinov # This file is distributed under the same license as the djvusmooth package. # Kyrill Detinov , 2010. # Kyrill Detinov , 2011, 2012, 2013. # msgid "" msgstr "" "Project-Id-Version: djvusmooth 0.2.14\n" "Report-Msgid-Bugs-To: Jakub Wilk \n" "POT-Creation-Date: 2013-02-20 17:57+0100\n" "PO-Revision-Date: 2013-02-21 18:33+0300\n" "Last-Translator: Kyrill Detinov \n" "Language-Team: openSUSE Translation Team\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "&About" msgstr "&О программе" msgid "&Background" msgstr "&Фон" msgid "&Bookmark this page" msgstr "Добавить &закладку на эту страницу" msgid "&Close" msgstr "&Закрыть" msgid "&Color" msgstr "&Цвет" msgid "&Edit" msgstr "&Правка" msgid "&External editor" msgstr "&Внешний редактор" msgid "&File" msgstr "&Файл" msgid "&First page" msgstr "Перв&ая страница" msgid "&Flatten" msgstr "&Упростить структуру" msgid "&Foreground" msgstr "&Передний план" msgid "&Go" msgstr "П&ерейти" msgid "&Go to page…" msgstr "Перейти &на страницу…" msgid "&Help" msgstr "&Справка" msgid "&Hyperlinks" msgstr "&Ссылки" msgid "&Image" msgstr "&Изображение" msgid "&Last page" msgstr "Последн&яя страница" msgid "&Metadata" msgstr "&Метаданные" msgid "&New hyperlink…" msgstr "&Новая ссылка…" msgid "&Next page" msgstr "&Следующая страница" msgid "&Non-raster data" msgstr "&Нерастровые данные" msgid "&None" msgstr "&Ничего" msgid "&Open" msgstr "&Открыть" msgid "&Outline" msgstr "&Оглавление" msgid "&Previous page" msgstr "&Предыдущая страница" msgid "&Properties…" msgstr "&Свойства…" msgid "&Quit" msgstr "&Выйти" msgid "&Refresh" msgstr "&Обновить" msgid "&Remove" msgstr "&Удалить" msgid "&Remove all" msgstr "&Удалить всё" msgid "&Save" msgstr "&Сохранить" msgid "&Settings" msgstr "&Настройка" msgid "&Stencil" msgstr "&Шаблон" msgid "&Stretch" msgstr "&Растянуть" msgid "&Text" msgstr "&Текст" msgid "&View" msgstr "&Вид" msgid "&Zoom" msgstr "&Масштаб" msgid "(no title)" msgstr "(без названия)" msgid "About…" msgstr "О программе…" msgid "Add the current to document outline" msgstr "Добавить в оглавление" msgid "Always visible" msgstr "Всегда показывать" msgid "" "An unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author.\n" "\n" msgstr "" "Непредвиденная ситуация. Так не должно быть. Пожалуйста, сообщите об ошибке автору.\n" "\n" msgid "Arrow" msgstr "Стрелка" msgid "Author" msgstr "Автор" msgid "Background color" msgstr "Цвет фона" msgid "Border" msgstr "Рамка" msgid "Cannot edit text with character zones." msgstr "Невозможно редактировать текст с зоной символов" msgid "Close the document" msgstr "Закрыть документ" msgid "Comment" msgstr "Примечание" msgid "Decrease the magnification" msgstr "Уменьшить масштаб" msgid "Display everything" msgstr "Показать всё" msgid "Display only the background layer" msgstr "Показать только фон" msgid "Display only the document bitonal stencil" msgstr "Показать только битональный шаблон" msgid "Display only the foreground layer" msgstr "Показать только передний план" msgid "Display overprinted annotations" msgstr "Показывать комментарии (ссылки)" msgid "Display the text layer" msgstr "Показать текстовый слой" msgid "DjVu files (*.djvu, *.djv)|*.djvu;*.djv|All files|*" msgstr "DjVu файлы (*.djvu, *.djv)|*.djvu;*.djv|All files|*" msgid "Do you want to save your changes?" msgstr "Вы хотите сохранить изменения?" msgid "Document metadata" msgstr "Метаданные документа" msgid "Don't display non-raster data" msgstr "Не показывать нерастровые данные" msgid "Edit document outline in an external editor" msgstr "Редактировать оглавление документа в редакторе" msgid "Edit metadata" msgstr "Изменить метаданные" msgid "Edit page text in an external editor" msgstr "Редактировать во внешнем редакторе" msgid "Edit the document or page metadata" msgstr "Изменить документ или метаданные" msgid "Enter path to your favourite text editor." msgstr "Путь к текстовому редактору" msgid "Error" msgstr "Ошибка" msgid "Etched in" msgstr "Вогнутая" msgid "Etched out" msgstr "Выпуклая" #, python-format msgid "" "External edit failed:\n" "%s" msgstr "" "Ошибка внешнего редактирования:\n" "%s" msgid "External editor…" msgstr "&Внешний редактор…" msgid "Fit &page" msgstr "Страница &целиком" msgid "Fit &width" msgstr "По &ширине страницы" msgid "Flatten text" msgstr "Упростить структуру текста" msgid "Go to page" msgstr "Перейти к странице" msgid "Highlight color" msgstr "Подсветка" msgid "Highlight color and opacity" msgstr "Цвет и прозрачность" msgid "Hyperlinks" msgstr "Ссылки" msgid "Increase the magnification" msgstr "Увеличить масштаб" msgid "Jump to first document page" msgstr "Перейти к первой странице" msgid "Jump to last document page" msgstr "Перейти к последней странице" msgid "Jump to next document page" msgstr "Перейти к следующей странице" msgid "Jump to page…" msgstr "Перейти на страницу…" msgid "Jump to previous document page" msgstr "Перейти к предыдущей странице" msgid "License" msgstr "Лицензия" msgid "Line" msgstr "Линия" msgid "Line color" msgstr "Цвет линии" msgid "Line width" msgstr "Ширина линии" msgid "Line-specific properties" msgstr "Параметры, специфичные для строки" #, python-format msgid "Link: %s" msgstr "Ссылка: %s" #, python-format msgid "Magnify %d%%" msgstr "Увеличение %d%%" msgid "Main properties" msgstr "Основные свойства" msgid "More information about this program" msgstr "Больше информации о программе" msgid "Neither display the foreground layer nor the background layer" msgstr "Не показывать фон и передний план" msgid "No text layer to edit." msgstr "Нет текстового слоя для редактирования" msgid "None" msgstr "Ничего" msgid "Number of lines changed." msgstr "Количество строк изменено" msgid "One &to one" msgstr "&Один к одному" msgid "Opacity" msgstr "Прозрачность" msgid "Open &recent" msgstr "&Недавние файлы" msgid "Open a DjVu document" msgstr "Открыть DjVu документ" msgid "Outline" msgstr "Оглавление" msgid "Oval" msgstr "Овал" msgid "Overprinted annotation (hyperlink) properties" msgstr "Вид комментария (ссылки)" #, python-format msgid "Page %(pageno)d of %(npages)d" msgstr "Страница %(pageno)d из %(npages)d" #, python-format msgid "Page %d metadata" msgstr "Метаданные страницы %d" msgid "Polygon" msgstr "Многоугольник" msgid "Pushpin" msgstr "Канцелярская кнопка" msgid "Quit the application" msgstr "Выйти из программы" msgid "Rectangle" msgstr "Прямоугольник" msgid "Refresh the window" msgstr "Обновить окно" msgid "Remove details" msgstr "Удалить детали" msgid "Remove details from page text" msgstr "Уменьшить детализацию текста" msgid "Remove whole document outline" msgstr "Удалить оглавление всего документа" msgid "Save the document" msgstr "Сохранить документ" msgid "Saving document" msgstr "Сохранение документа" #, python-format msgid "" "Saving document failed:\n" "%s" msgstr "" "Не удалось сохранить файл:\n" "%s" msgid "Saving the document, please wait…" msgstr "Сохранение документа, подождите…" msgid "Scope" msgstr "Область" msgid "Set full resolution magnification." msgstr "Фактический размер" msgid "Set magnification to fit page" msgstr "Показать страницу целиком" msgid "Set magnification to fit page width" msgstr "Масштабировать по ширине страницы" msgid "Setup an external editor" msgstr "Выбрать внешний редактор" msgid "Shadow in" msgstr "Затемнение внутрь" msgid "Shadow out" msgstr "Затемнение наружу" msgid "Shape" msgstr "Форма" msgid "Show &sidebar" msgstr "Показать &боковую панель" msgid "Show/hide the sidebar" msgstr "Показать/скрыть боковую панель" msgid "Solid color" msgstr "Сплошная заливка" msgid "Stretch the image to the window size" msgstr "Масштабировать страницу по размеру окна" msgid "Target frame" msgstr "Цель фрейма" msgid "Text" msgstr "Текст" msgid "Text color" msgstr "Цвет текста" msgid "Text-specific properties" msgstr "Параметры, специфичные для текста" msgid "URI" msgstr "URI" #, python-format msgid "Unhandled exception: %s" msgstr "Непредвиденная ситуация: %s" msgid "Width" msgstr "Ширина" msgid "XOR" msgstr "XOR" msgid "Zoom &in" msgstr "У&величить" msgid "Zoom &out" msgstr "У&меньшить" msgid "[Text layer]" msgstr "[Текстовый слой]" msgid "all" msgstr "все" msgid "all pages" msgstr "все страницы" msgid "bookmarks" msgstr "закладки" msgid "char" msgstr "символ" msgid "characters" msgstr "символы" msgid "column" msgstr "столбец" msgid "columns" msgstr "столбцы" msgid "current page" msgstr "текущая страница" msgid "key" msgstr "ключ" msgid "line" msgstr "строка" msgid "lines" msgstr "строки" msgid "page" msgstr "страница" msgid "para" msgstr "параграф" msgid "paragraphs" msgstr "параграфы" msgid "region" msgstr "область" msgid "regions" msgstr "области" msgid "value" msgstr "значение" msgid "word" msgstr "слово" msgid "words" msgstr "слова" djvusmooth-0.2.14/po/es.po0000644000000000000000000002223312111366367015333 0ustar rootroot00000000000000# Spanish translation of djvusmooth messages. # Copyright (C) 2012 # This file is distributed under the same license as the djvusmooth package. msgid "" msgstr "" "Project-Id-Version: djvusmooth 0.2.14\n" "Report-Msgid-Bugs-To: Jakub Wilk \n" "POT-Creation-Date: 2013-02-20 17:57+0100\n" "PO-Revision-Date: 2012-05-21 00:20-0500\n" "Last-Translator: C. Daniel Sanchez R. \n" "Language-Team: \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "&About" msgstr "&Acerca de" msgid "&Background" msgstr "&Fondo" msgid "&Bookmark this page" msgstr "&Marcadores de esta página" msgid "&Close" msgstr "&Cerrar" msgid "&Color" msgstr "&Color" msgid "&Edit" msgstr "&Editar" msgid "&External editor" msgstr "&Editor externo" msgid "&File" msgstr "&Archivo" msgid "&First page" msgstr "&Primer página" msgid "&Flatten" msgstr "&Aplanar" msgid "&Foreground" msgstr "&Frente" msgid "&Go" msgstr "&Ir" msgid "&Go to page…" msgstr "&Ir a página..." msgid "&Help" msgstr "&Ayuda" msgid "&Hyperlinks" msgstr "&Hipervínculos" msgid "&Image" msgstr "&Imagen" msgid "&Last page" msgstr "&Última página" msgid "&Metadata" msgstr "&Metadatos" msgid "&New hyperlink…" msgstr "&Nuevo hipervínculo..." msgid "&Next page" msgstr "&Página siguiente" msgid "&Non-raster data" msgstr "&Datos ajenos al bitmap" msgid "&None" msgstr "&Ninguno" msgid "&Open" msgstr "&Abrir" msgid "&Outline" msgstr "&Bosquejo" msgid "&Previous page" msgstr "&Página anterior" msgid "&Properties…" msgstr "&Propiedades..." msgid "&Quit" msgstr "&Salir" msgid "&Refresh" msgstr "&Refrescar" msgid "&Remove" msgstr "&Eliminar" msgid "&Remove all" msgstr "&Eliminar todo" msgid "&Save" msgstr "&Guardar" msgid "&Settings" msgstr "&Opciones" msgid "&Stencil" msgstr "&Patrón" msgid "&Stretch" msgstr "&Estirar" msgid "&Text" msgstr "&Texto" msgid "&View" msgstr "&Ver" msgid "&Zoom" msgstr "&Zoom" msgid "(no title)" msgstr "(sin título)" msgid "About…" msgstr "Acerca de..." msgid "Add the current to document outline" msgstr "Agregar la página actual al bosquejo del documento" msgid "Always visible" msgstr "Siempre visible" msgid "" "An unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author.\n" "\n" msgstr "" "Ha ocurrido una excepción no controlada. Idealmente, esto no debería ocurrir. Por favor, informe el error al autor.\n" "\n" msgid "Arrow" msgstr "Flecha" msgid "Author" msgstr "Autor" msgid "Background color" msgstr "Color de fondo" msgid "Border" msgstr "Borde" msgid "Cannot edit text with character zones." msgstr "No se puede editar el texto que esté en una zona para caracteres." msgid "Close the document" msgstr "Cerrar el documento" msgid "Comment" msgstr "Comentario" msgid "Decrease the magnification" msgstr "Disminuir la ampliación" msgid "Display everything" msgstr "Mostrar todo" msgid "Display only the background layer" msgstr "Mostrar solo la capa del fondo" msgid "Display only the document bitonal stencil" msgstr "Mostrar solo el patrón bitonal del documento" msgid "Display only the foreground layer" msgstr "Mostrar solo la capa del frente" msgid "Display overprinted annotations" msgstr "Mostrar anotaciones encima" msgid "Display the text layer" msgstr "Mostrar la capa de texto" msgid "DjVu files (*.djvu, *.djv)|*.djvu;*.djv|All files|*" msgstr "Archivos DjVu (*.djvu, *.djv)|*.djvu;*.djv|Todos los archivos|*" msgid "Do you want to save your changes?" msgstr "¿Desea guardar los cambios?" msgid "Document metadata" msgstr "Metadatos del documento" msgid "Don't display non-raster data" msgstr "No mostrar los datos del bitmap" msgid "Edit document outline in an external editor" msgstr "Editar el bosquejo en un editor externo" msgid "Edit metadata" msgstr "Editar metadatos" msgid "Edit page text in an external editor" msgstr "Editar texto de la página en un editor externo" msgid "Edit the document or page metadata" msgstr "Editar los metadatos del documento o de la página" msgid "Enter path to your favourite text editor." msgstr "Ingrese la ruta hacia su editor de texto favorito." msgid "Error" msgstr "Error" msgid "Etched in" msgstr "Grabado in" msgid "Etched out" msgstr "Grabado out" #, python-format msgid "" "External edit failed:\n" "%s" msgstr "" "Falló la edición externa:\n" "%s" msgid "External editor…" msgstr "Editor externo..." msgid "Fit &page" msgstr "Ajustar &página" msgid "Fit &width" msgstr "Ajustar &ancho" msgid "Flatten text" msgstr "Aplanar el texto" msgid "Go to page" msgstr "Ir a la página" msgid "Highlight color" msgstr "Realzar color" msgid "Highlight color and opacity" msgstr "Realzar color y opacidad" msgid "Hyperlinks" msgstr "Hipervínculos" msgid "Increase the magnification" msgstr "Incrementar la ampliación" msgid "Jump to first document page" msgstr "Saltar a la primer página del documento" msgid "Jump to last document page" msgstr "Saltar a la última página del documento" msgid "Jump to next document page" msgstr "Saltar a la siguiente página del documento" msgid "Jump to page…" msgstr "Saltar a la página..." msgid "Jump to previous document page" msgstr "Saltar a la página anterior del documento" msgid "License" msgstr "Licencia" msgid "Line" msgstr "Línea" msgid "Line color" msgstr "Color de la línea" msgid "Line width" msgstr "Ancho de la línea" msgid "Line-specific properties" msgstr "Propiedades de la linea especifica" #, python-format msgid "Link: %s" msgstr "Vínculo: %s" #, python-format msgid "Magnify %d%%" msgstr "Aumentar %d%%" msgid "Main properties" msgstr "Propiedades principales" msgid "More information about this program" msgstr "Más información acerca de este programa" msgid "Neither display the foreground layer nor the background layer" msgstr "No mostrar las capas del frente ni del fondo" msgid "No text layer to edit." msgstr "No hay capa de texto para editar." msgid "None" msgstr "Ninguno" msgid "Number of lines changed." msgstr "Número de líneas cambiadas." msgid "One &to one" msgstr "Uno &a uno" msgid "Opacity" msgstr "Opacidad" msgid "Open &recent" msgstr "Abrir &reciente" msgid "Open a DjVu document" msgstr "Abrir un documento DjVu" msgid "Outline" msgstr "Bosquejo" msgid "Oval" msgstr "Óvalo" msgid "Overprinted annotation (hyperlink) properties" msgstr "Propiedades de la anotación (hipervínculo)" #, python-format msgid "Page %(pageno)d of %(npages)d" msgstr "Página %(pageno)d de %(npages)d" #, python-format msgid "Page %d metadata" msgstr "Metadatos de la página %d" msgid "Polygon" msgstr "Polígono" msgid "Pushpin" msgstr "Push-pin" msgid "Quit the application" msgstr "Salir de la aplicación" msgid "Rectangle" msgstr "Rectángulo" msgid "Refresh the window" msgstr "Refresca la ventana" msgid "Remove details" msgstr "Eliminar detalles" msgid "Remove details from page text" msgstr "Eliminar detalles del texto en la página" msgid "Remove whole document outline" msgstr "Eliminar bosquejo de todo el documento" msgid "Save the document" msgstr "Guardar el documento" msgid "Saving document" msgstr "Guardando documento" #, python-format msgid "" "Saving document failed:\n" "%s" msgstr "" "Falló el guardado del documento:\n" "%s" msgid "Saving the document, please wait…" msgstr "Guardando documento, espere por favor..." msgid "Scope" msgstr "Alcance" msgid "Set full resolution magnification." msgstr "Ajustar a la máxima resolución" msgid "Set magnification to fit page" msgstr "Ajustar ampliación al tamaño de la página" msgid "Set magnification to fit page width" msgstr "Ajustar ampliación al ancho de la página" msgid "Setup an external editor" msgstr "Configurar un editor externo" msgid "Shadow in" msgstr "Sombra in" msgid "Shadow out" msgstr "Sombra out" msgid "Shape" msgstr "Silueta" msgid "Show &sidebar" msgstr "Mostrar &barra lateral" msgid "Show/hide the sidebar" msgstr "Mostrar/ocultar la barra lateral" msgid "Solid color" msgstr "Color sólido" msgid "Stretch the image to the window size" msgstr "Estirar la imagen al tamaño de la ventana" msgid "Target frame" msgstr "Marco objetivo" msgid "Text" msgstr "Texto" msgid "Text color" msgstr "Color del texto" msgid "Text-specific properties" msgstr "Propiedades para un texto específico" msgid "URI" msgstr "URI" #, python-format msgid "Unhandled exception: %s" msgstr "Excepción no controlada: %s" msgid "Width" msgstr "Ancho" msgid "XOR" msgstr "XOR" msgid "Zoom &in" msgstr "Zoom &in" msgid "Zoom &out" msgstr "Zoom &out" msgid "[Text layer]" msgstr "[Capa de texto]" msgid "all" msgstr "Todo" msgid "all pages" msgstr "Todas las páginas" msgid "bookmarks" msgstr "Marcadores" msgid "char" msgstr "carácter" msgid "characters" msgstr "caracteres" msgid "column" msgstr "columna" msgid "columns" msgstr "columnas" msgid "current page" msgstr "Página actual" msgid "key" msgstr "clave" msgid "line" msgstr "línea" msgid "lines" msgstr "líneas" msgid "page" msgstr "página" msgid "para" msgstr "párrafo" msgid "paragraphs" msgstr "párrafos" msgid "region" msgstr "región" msgid "regions" msgstr "regiones" msgid "value" msgstr "valor" msgid "word" msgstr "palabra" msgid "words" msgstr "palabras" djvusmooth-0.2.14/COPYING0000644000000000000000000004325412010167027014775 0ustar rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. djvusmooth-0.2.14/djvusmooth0000755000000000000000000000137011573212252016067 0ustar rootroot00000000000000#!/usr/bin/python # encoding=UTF-8 # Copyright © 2008 Jakub Wilk # # This package 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 2 dated June, 1991. # # This package 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. if __name__ != '__main__': raise ImportError('This module is not intended for import') import sys from djvusmooth.gui.main import Application application = Application() application.start(sys.argv[1:]) # vim:ts=4 sw=4 et djvusmooth-0.2.14/PKG-INFO0000644000000000000000000000133512111517063015032 0ustar rootroot00000000000000Metadata-Version: 1.1 Name: djvusmooth Version: 0.2.14 Summary: graphical editor for DjVu Home-page: http://jwilk.net/software/djvusmooth Author: Jakub Wilk Author-email: jwilk@jwilk.net License: GNU GPL 2 Description: *djvusmooth* is a graphical editor for `DjVu `_ documents. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Topic :: Text Processing Classifier: Topic :: Multimedia :: Graphics djvusmooth-0.2.14/private/0000755000000000000000000000000012111517063015405 5ustar rootroot00000000000000djvusmooth-0.2.14/private/update-version0000755000000000000000000000053612111224024020274 0ustar rootroot00000000000000#!/bin/sh version=${1:?"no version number provided"} set -e set -x dch -m -v "$version" -c doc/changelog sed -i -r -e "s/^(__version__) = '[0-9.]+'$/\1 = '$version'/" lib/__init__.py sed -i -r -e "s/<(!ENTITY version) '[0-9.]+'>/<\1 '$version'>/" doc/*.xml sed -i -r -e "s/^(\"Project-Id-Version: djvusmooth) ([0-9.]+)/\1 $version/" po/*.po po/*.pot djvusmooth-0.2.14/private/update-i18n0000755000000000000000000000235012111223762017372 0ustar rootroot00000000000000#!/usr/bin/make -f # Copyright © 2009, 2011, 2012 Jakub Wilk # # This package 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 2 dated June, 1991. project_name = $(shell head -n1 doc/changelog | cut -d' ' -f1) project_version = $(shell head -n1 doc/changelog | sed -n -e 's/.*(\([0-9.]\+\)).*/\1/ p') bugs_address = $(shell sed -n -e '/ -- \(.*\) .*/ {s//\1/p;q}' doc/changelog) po_files = $(wildcard po/*.po) mo_files = $(patsubst po/%.po,locale/%/LC_MESSAGES/$(project_name).mo,$(po_files)) source_files = $(shell find -name '*.py' -o -name 'build' -prune -a -false) xgettext_options = \ --language=python \ --keyword=_ --keyword=N_ \ --package-name=$(project_name) \ --package-version=$(project_version) \ --msgid-bugs-address='$(bugs_address)' \ --no-location \ --sort-output .PHONY: all all: po/$(project_name).pot $(po_files) $(mo_files) po/$(project_name).pot: $(source_files) xgettext $(xgettext_options) $(source_files) -o $(@) %.po: po/$(project_name).pot msgmerge --no-wrap -o $(@) $(@) $(<) locale/%/LC_MESSAGES/$(project_name).mo: po/%.po python setup.py build_mo # vim:ts=4 sw=4 noet