python-djvulibre-0.3.9/0000755000000000000000000000000011731710327014770 5ustar rootroot00000000000000python-djvulibre-0.3.9/setup.cfg0000644000000000000000000000007311731710327016611 0ustar rootroot00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-djvulibre-0.3.9/MANIFEST.in0000644000000000000000000000043511725241367016536 0ustar rootroot00000000000000include MANIFEST.in include COPYING include doc/changelog include doc/*.txt include doc/source/conf.py include doc/source/*.txt include examples/* recursive-include djvu *.py *.pxi *.pxd *.pyx recursive-exclude djvu config.pxi recursive-include tests *.py Makefile *.jpeg *.tex *.djvu python-djvulibre-0.3.9/doc/0000755000000000000000000000000011731710327015535 5ustar rootroot00000000000000python-djvulibre-0.3.9/doc/source/0000755000000000000000000000000011731710327017035 5ustar rootroot00000000000000python-djvulibre-0.3.9/doc/source/expressions.txt0000644000000000000000000000601011440743731022157 0ustar rootroot00000000000000LISP S-expressions ================== Textual representation ---------------------- Special characters are: * the parenthesis ``(`` and ``)``, * the double quote ``"``, * the vertical bar ``|``. Symbols are represented by their name. Vertical bars ``|`` can be used to delimit names that contain blanks, special characters, non printable characters, non-ASCII characters, or can be confused as a number. Numbers follow the syntax specified by the C function ``strtol()`` with ``base=0``. Strings are delimited by double quotes. All C string escapes are recognized. Non-printable ASCII characters must be escaped. List are represented by an open parenthesis ``(`` followed by the space separated list elements, followed by a closing parenthesis ``)``. When the ``cdr`` of the last pair is non zero, the closed parenthesis is preceded by a space, a dot ``.``, a space, and the textual representation of the ``cdr``. (This is only partially supported by Python bindings.) Symbols ------- .. currentmodule:: djvu.sexpr .. class:: Symbol(str) >>> Symbol('ham') Symbol('ham') S-expressions ------------- .. currentmodule:: djvu.sexpr .. class:: Expression Inheritance diagram: .. inheritance-diagram:: IntExpression ListExpression StringExpression SymbolExpression :parts: 1 .. method:: as_string([width]) Return a string representation of the expression. .. method:: print_into(file[, width]) Print the expression into the file. .. attribute:: value The actual “pythonic” value of the expression. .. currentmodule:: djvu.sexpr .. class:: IntExpression :class:`IntExpression` can represent any integer in range :math:`\left[-2^{29}, 2^{29}\right)`. To create objects of this class, use the :class:`Expression` constructor: >>> x = Expression(42) >>> x Expression(42) >>> type(x) >>> x.as_string() '42' >>> x.value 42 .. currentmodule:: djvu.sexpr .. class:: ListExpression To create objects of this class, use the :class:`Expression` constructor: >>> x = Expression([4, 2]) >>> x Expression((4, 2)) >>> type(x) >>> x.as_string() '(4 2)' >>> x.value (4, 2) .. currentmodule:: djvu.sexpr .. class:: StringExpression To create objects of this class, use the :class:`Expression` constructor: >>> x = Expression('eggs') >>> x Expression('eggs') >>> type(x) >>> x.as_string() '"eggs"' >>> x.value 'eggs' .. currentmodule:: djvu.sexpr .. class:: SymbolExpression To create objects of this class, use the :class:`Expression` constructor: >>> x = Expression(Symbol('ham')) >>> x Expression(Symbol('ham')) >>> type(x) >>> x.as_string() 'ham' >>> x.value Symbol('ham') Variétés -------- .. data:: EMPTY_LIST Empty list S-expression. >>> EMPTY_LIST Expression(()) .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/exceptions.txt0000644000000000000000000000271311440743675021773 0ustar rootroot00000000000000Exceptions ========== Common exceptions ----------------- .. currentmodule:: djvu.decode .. exception:: NotAvailable A resource not (yet) available. .. currentmodule:: djvu.decode .. exception:: DjVuLibreBug A DjVuLibre bug was encountered. .. currentmodule:: djvu.sexpr .. exception:: ExpressionSyntaxError Syntax error while parsing an S-expression. .. currentmodule:: djvu.sexpr .. exception:: InvalidExpression Invalid S-expression. Job status ---------- .. currentmodule:: djvu.decode .. exception:: JobException Status of a job. Possibly, but not necessarily, exceptional. Inheritance diagram: .. inheritance-diagram:: JobNotDone JobNotStarted JobStarted JobDone JobOK JobFailed JobStopped :parts: 1 .. currentmodule:: djvu.decode .. exception:: JobNotDone Operation is not yet done. .. currentmodule:: djvu.decode .. exception:: JobNotStarted Operation was not even started. .. currentmodule:: djvu.decode .. exception:: JobStarted Operation is in progress. .. currentmodule:: djvu.decode .. exception:: JobDone Operation finished. .. currentmodule:: djvu.decode .. exception:: JobOK Operation finished successfully. .. currentmodule:: djvu.decode .. exception:: JobFailed Operation failed because of an error. .. currentmodule:: djvu.decode .. exception:: JobStopped Operation was interrupted by user. .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/outline.txt0000644000000000000000000000157411440743731021266 0ustar rootroot00000000000000Document outline ================ .. seealso:: |djvu3ref|_ (8.3.3 *Document Outline Chunk*). Representing outlines as S-expressions is DjVuLibre-specific; see *Outline/Bookmark syntax* in |djvused|_ for reference. .. currentmodule:: djvu.decode .. class:: DocumentOutline .. method:: wait() Wait until the associated S-expression is available. .. attribute:: sexpr :return: the associated S-expression. If the S-expression is not available, raise :exc:`NotAvailable` exception. Then, :class:`PageInfoMessage` messages with empty :attr:`~Message.page_job` may be emitted. :raise NotAvailable: see above. :raise JobFailed: on failure. .. data:: djvu.const.EMPTY_OUTLINE Empty outline S-expression. .. doctest:: >>> list(EMPTY_OUTLINE) [Symbol('bookmarks')] .. vim:ts=3 sw=3 et ft=rst .. vim:ts=3 sw=3 et python-djvulibre-0.3.9/doc/source/index.txt0000644000000000000000000000047411440743260020711 0ustar rootroot00000000000000Welcome to python-djvulibre's documentation! ============================================ Contents: .. toctree:: :maxdepth: 3 expressions event-model documents pages geometry files outline annotations text-zones messages exceptions * :ref:`search` .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/geometry.txt0000644000000000000000000001047311440743731021440 0ustar rootroot00000000000000Geometry and colors =================== Geometry -------- Transformations ~~~~~~~~~~~~~~~ .. currentmodule:: djvu.decode .. class:: AffineTransform((x0, y0, w0, h0), (x1, y1, w1, h1)) The object represents an affine coordinate transformation that maps points from rectangle (`x0`, `y0`, `w0`, `h0`) to rectangle (`x1`, `y1`, `w1`, `h1`). .. method:: rotate(n) Rotate the output rectangle counter-clockwise by `n` degrees. .. method:: apply((x, y)) apply((x, y, w, h)) Apply the coordinate transform to a point or a rectangle. .. method:: inverse((x, y)) inverse((x, y, w, h)) Apply the inverse coordinate transform to a point or a rectangle. .. method:: mirror_x() Reverse the X coordinates of the output rectangle. .. method:: mirror_y() Reverse the Y coordinates of the output rectangle. Pixel formats ------------- .. currentmodule:: djvu.decode .. class:: PixelFormat Abstract base for all pixel formats. Inheritance diagram: .. inheritance-diagram:: PixelFormatRgb PixelFormatRgbMask PixelFormatGrey PixelFormatPalette PixelFormatPackedBits :parts: 1 .. attribute:: rows_top_to_bottom Flag indicating whether the rows in the pixel buffer are stored starting from the top or the bottom of the image. Default ordering starts from the bottom of the image. This is the opposite of the X11 convention. .. attribute:: y_top_to_bottom Flag indicating whether the *y* coordinates in the drawing area are oriented from bottom to top, or from top to bottom. The default is bottom to top, similar to PostScript. This is the opposite of the X11 convention. .. attribute:: bpp Return the depth of the image, in bits per pixel. .. attribute:: dither_bpp The final depth of the image on the screen. This is used to decide which dithering algorithm should be used. The default is usually appropriate. .. attribute:: gamma Gamma of the display for which the pixels are intended. This will be combined with the gamma stored in DjVu documents in order to compute a suitable color correction. The default value is 2.2. .. currentmodule:: djvu.decode .. class:: PixelFormatRgb([byteorder='RGB']) 24-bit pixel format, with: - RGB (`byteorder` = ``'RGB'``) or - BGR (`byteorder` = ``'BGR'``) byte order. .. currentmodule:: djvu.decode .. class:: PixelFormatRgbMask(red_mask, green_mask, blue_mask[, xor_value], bpp=16) PixelFormatRgbMask(red_mask, green_mask, blue_mask[, xor_value], bpp=32) `red_mask`, `green_mask` and `blue_mask` are bit masks for color components for each pixel. The resulting color is then xored with the `xor_value`. For example, ``PixelFormatRgbMask(0xf800, 0x07e0, 0x001f, bpp=16)`` is a highcolor format with: - 5 (most significant) bits for red, - 6 bits for green, - 5 (least significant) bits for blue. .. currentmodule:: djvu.decode .. class:: PixelFormatGrey() 8-bit, grey pixel format. .. currentmodule:: djvu.decode .. class:: PixelFormatPalette(palette) Palette pixel format. `palette` must be a dictionary which contains 216 (6 x 6 x 6) entries of a web color cube, such that: - for each key ``(r, g, b)``: ``r in range(0, 6)``, ``g in range(0, 6)`` etc.; - for each value ``v``: ``v in range(0, 0x100)``. .. currentmodule:: djvu.decode .. class:: PixelFormatPackedBits(endianness) Bitonal, 1 bit per pixel format with: - most significant bits on the left (*endianness* = ``'>'``) or - least significant bits on the left (*endianness* = ``'<'``). Render modes ------------ .. currentmodule:: djvu.decode .. data:: djvu.decode.RENDER_COLOR Render color page or stencil. .. currentmodule:: djvu.decode .. data:: djvu.decode.RENDER_BLACK Render stencil or color page. .. currentmodule:: djvu.decode .. data:: djvu.decode.RENDER_COLOR_ONLY Render color page or fail. .. currentmodule:: djvu.decode .. data:: djvu.decode.RENDER_MASK_ONLY Render stencil or fail. .. currentmodule:: djvu.decode .. data:: djvu.decode.RENDER_BACKGROUND Render color background layer. .. currentmodule:: djvu.decode .. data:: djvu.decode.RENDER_FOREGROUND Render color foreground layer. .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/messages.txt0000644000000000000000000001325511440743731021415 0ustar rootroot00000000000000Messages ======== .. currentmodule:: djvu.decode .. class:: Message An abstract base for all messages. .. attribute:: context :return: the concerned context. :rtype: :class:`Context`. .. attribute:: document :return: the concerned document or ``None``. :type: :class:`Document` .. attribute:: page_job :return: the concerned page job or ``None``. :rtype: :class:`PageJob` .. attribute:: job :return: the concerned job or ``None``. :rtype: :class:`Job` Inheritance diagram: .. inheritance-diagram:: ErrorMessage InfoMessage NewStreamMessage DocInfoMessage PageInfoMessage ChunkMessage RelayoutMessage RedisplayMessage ThumbnailMessage ProgressMessage :parts: 1 .. class:: ErrorMessage An :class:`ErrorMessage` is generated whenever the decoder or the DDJVU API encounters an error condition. All errors are reported as error messages because they can occur asynchronously. .. attribute:: message :return: the actual error message, as text. .. attribute:: location :return: a (`function`, `filename`, `line_no`) tuple indicating where the error was detected. .. class:: InfoMessage A :class:`InfoMessage` provides informational text indicating the progress of the decoding process. This might be displayed in the browser status bar. .. attribute:: message :return: the actual error message, as text. .. class:: NewStreamMessage A :class:`NewStreamMessage` is generated whenever the decoder needs to access raw DjVu data. The caller must then provide the requested data using the :attr:`stream` file-like object. In the case of indirect documents, a single decoder might simultaneously request several streams of data. .. attribute:: name The first :class:`NewStreamMessage` message always has :attr:`name` set to ``None``. It indicates that the decoder needs to access the data in the main DjVu file. Further :class:`NewStreamMessage` messages messages are generated to access the auxiliary files of indirect or indexed DjVu documents. :attr:`name` then provides the base name of the auxiliary file. .. attribute:: uri :return: the requrested URI. URI is is set according to the `uri` argument provided to function :meth:`Context.new_document`. The first :class:`NewStreamMessage` message always contain the URI passed to :meth:`Context.new_document`. Subsequent :class:`NewStreamMessage` messages contain the URI of the auxiliary files for indirect or indexed DjVu documents. .. attribute:: stream :return: a data stream. .. class:: djvu.decode.Stream .. method:: close() Indicate that no more data will be provided on the particular stream. .. method:: abort() Indicate that no more data will be provided on the particular stream, because the user has interrupted the data transfer (for instance by pressing the stop button of a browser) and that the decoding threads should be stopped as soon as feasible. .. method:: flush() Do nothing. (This method is provided solely to implement Python's file-like interface.) .. method:: read([size]) :raise exceptions.IOError: always. (This method is provided solely to implement Python's file-like interface.) .. method:: write(data) Provide raw data to the DjVu decoder. This method should be called as soon as the data is available, for instance when receiving DjVu data from a network connection. .. class:: DocInfoMessage A :class:`DocInfoMessage` indicates that basic information about the document has been obtained and decoded. Not much can be done before this happens. Check the document's :attr:`~Document.decoding_status` to determine whether the operation was successful. .. class:: PageInfoMessage The page decoding process generates a :class:`PageInfoMessage`: - when basic page information is available and before any :class:`RelayoutMessage` or :class:`RedisplayMessage`, - when the page decoding thread terminates. You can distinguish both cases using the page job's :attr:`~Job.status`. A :class:`PageInfoMessage` may be also generated as a consequence of reading :attr:`Page.get_info()` or :attr:`Page.dump`. .. class:: ChunkMessage A :class:`ChunkMessage` indicates that an additional chunk of DjVu data has been decoded. .. class:: RelayoutMessage A :class:`RelayoutMessage` is generated when a DjVu viewer should recompute the layout of the page viewer because the page size and resolution information has been updated. .. class:: RedisplayMessage A :class:`RedisplayMessage` is generated when a DjVu viewer should call :meth:`PageJob.render` and redisplay the page. This happens, for instance, when newly decoded DjVu data provides a better image. .. class:: ThumbnailMessage A :class:`ThumbnailMessage` is sent when additional thumbnails are available. .. attribute:: thumbnail :rtype: :class:`Thumbnail` :raise NotAvailable: if the :class:`Document` has been garbage-collected. .. class:: ProgressMessage A :class:`ProgressMessage` is generated to indicate progress towards the completion of a print or save job. .. attribute:: percent :return: the percent of the job done. .. attribute:: status :return: a :class:`JobException` subclass indicating the current job status. .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/annotations.txt0000644000000000000000000002007511440743731022141 0ustar rootroot00000000000000Annotations =========== .. seealso:: - |djvu3ref|_: 8.3.4 *Annotation Chunk*; - |djvused|_: *Annotation syntax*. .. currentmodule:: djvu.decode .. class:: Annotations Abstract base for document and page annotations. Inheritance diagram: .. inheritance-diagram:: DocumentAnnotations PageAnnotations :parts: 1 .. method:: wait() Wait until the associated S-expression is available. .. attribute:: sexpr :return: the associated S-expression. :rtype: :class:`djvu.sexpr.Expression`. :raise NotAvailable: if the S-expression is not available; then, :class:`PageInfoMessage` messages with empty :attr:`~Message.page_job` may be emitted. :raise JobFailed: on failure. .. currentmodule:: djvu.decode .. class:: DocumentAnnotations(document[, shared=True]) If `shared` is true and no document-wide annotations are available, shared annotations are considered document-wide. .. seealso:: |djvuext|_: *Document annotations and metadata*. .. attribute:: document :return: the concerned document. :rtype: :class:`Document`. .. currentmodule:: djvu.decode .. class:: PageAnnotations(page) .. attribute:: page :return: the concerned page. :rtype: :class:`Page`. Mapareas (overprinted annotations) ---------------------------------- .. seealso:: |djvu3ref|_: 8.3.4.2 *Maparea (overprinted annotations)*. .. class:: Hyperlinks(annotations) A sequence of ``(maparea …)`` S-expressions. .. currentmodule:: djvu.decode The following symbols are pre-defined: .. data:: ANNOTATION_MAPAREA >>> ANNOTATION_MAPAREA Symbol('maparea') .. data:: MAPAREA_SHAPE_RECTANGLE >>> MAPAREA_SHAPE_RECTANGLE Symbol('rect') .. data:: MAPAREA_SHAPE_OVAL >>> MAPAREA_SHAPE_OVAL Symbol('oval') .. data:: MAPAREA_SHAPE_POLYGON >>> MAPAREA_SHAPE_POLYGON Symbol('poly') .. data:: MAPAREA_SHAPE_LINE >>> MAPAREA_SHAPE_LINE Symbol('line') .. data:: MAPAREA_SHAPE_TEXT >>> MAPAREA_SHAPE_TEXT Symbol('text') .. data:: MAPAREA_URI >>> MAPAREA_URI Symbol('url') .. data:: MAPAREA_URL Equivalent to :data:`MAPAREA_URI`. Border style ~~~~~~~~~~~~ .. seealso:: |djvu3ref|_: 8.3.4.2.3.1.1 *Border type*, 8.3.4.2.3.1.2 *Border always visible*. .. currentmodule:: djvu.const The following symbols are pre-defined: .. data:: MAPAREA_BORDER_NONE >>> MAPAREA_BORDER_NONE Symbol('none') .. data:: MAPAREA_BORDER_XOR >>> MAPAREA_BORDER_XOR Symbol('xor') .. data:: MAPAREA_BORDER_SOLID_COLOR >>> MAPAREA_BORDER_SOLID_COLOR Symbol('border') .. data:: MAPAREA_BORDER_SHADOW_IN >>> MAPAREA_BORDER_SHADOW_IN Symbol('shadow_in') .. data:: MAPAREA_BORDER_SHADOW_OUT >>> MAPAREA_BORDER_SHADOW_OUT Symbol('shadow_out') .. data:: MAPAREA_BORDER_ETCHED_IN >>> MAPAREA_BORDER_ETCHED_IN Symbol('shadow_ein') .. data:: MAPAREA_BORDER_ETCHED_OUT >>> MAPAREA_BORDER_ETCHED_OUT Symbol('shadow_eout') .. data:: MAPAREA_SHADOW_BORDERS A sequence of all shadow border types. >>> MAPAREA_SHADOW_BORDERS (Symbol('shadow_in'), Symbol('shadow_out'), Symbol('shadow_ein'), Symbol('shadow_eout')) .. data:: MAPAREA_BORDER_ALWAYS_VISIBLE >>> MAPAREA_BORDER_ALWAYS_VISIBLE Symbol('border_avis') The following numeric constant are pre-defined: .. data:: MAPAREA_SHADOW_BORDER_MIN_WIDTH >>> MAPAREA_SHADOW_BORDER_MIN_WIDTH 1 .. data:: MAPAREA_SHADOW_BORDER_MAX_WIDTH >>> MAPAREA_SHADOW_BORDER_MAX_WIDTH 32 Highlight color and opacity ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. seealso:: |djvu3ref|_: 8.3.4.2.3.1.3 *Highlight color and opacity*. .. currentmodule:: djvu.const The following symbols are pre-defined: .. data:: MAPAREA_HIGHLIGHT_COLOR >>> MAPAREA_HIGHLIGHT_COLOR Symbol('hilite') .. data:: MAPAREA_OPACITY >>> MAPAREA_OPACITY Symbol('opacity') .. currentmodule:: djvu.const The following numeric constant are pre-defined: .. data:: MAPAREA_OPACITY_MIN >>> MAPAREA_OPACITY_MIN 0 .. data:: MAPAREA_OPACITY_DEFAULT >>> MAPAREA_OPACITY_DEFAULT 50 .. data:: MAPAREA_OPACITY_MAX >>> MAPAREA_OPACITY_MAX 100 Line and text parameters ~~~~~~~~~~~~~~~~~~~~~~~~ .. seealso:: |djvu3ref|_: 8.3.4.2.3.1.4 *Line and Text parameters*. .. currentmodule:: djvu.const The following symbols are pre-defined: .. data:: MAPAREA_ARROW >>> MAPAREA_ARROW Symbol('arrow') .. data:: MAPAREA_LINE_WIDTH >>> MAPAREA_LINE_WIDTH Symbol('width') .. data:: MAPAREA_LINE_COLOR >>> MAPAREA_LINE_COLOR Symbol('lineclr') .. data:: MAPAREA_BACKGROUND_COLOR >>> MAPAREA_BACKGROUND_COLOR Symbol('backclr') .. data:: MAPAREA_TEXT_COLOR >>> MAPAREA_TEXT_COLOR Symbol('textclr') .. data:: MAPAREA_PUSHPIN >>> MAPAREA_PUSHPIN Symbol('pushpin') .. currentmodule:: djvu.const The following numeric constants are pre-defined: .. data:: MAPAREA_LINE_MIN_WIDTH >>> MAPAREA_LINE_MIN_WIDTH 1 The following default colors are pre-defined: .. data:: MAPAREA_LINE_COLOR_DEFAULT >>> MAPAREA_LINE_COLOR_DEFAULT '#000000' .. data:: MAPAREA_TEXT_COLOR_DEFAULT >>> MAPAREA_TEXT_COLOR_DEFAULT '#000000' Initial document view --------------------- .. seealso:: |djvu3ref|_: 8.3.4.1 *Initial Document View*. .. currentmodule:: djvu.const The following symbols are pre-defined: .. data:: ANNOTATION_BACKGROUND >>> ANNOTATION_BACKGROUND Symbol('background') .. data:: ANNOTATION_ZOOM >>> ANNOTATION_ZOOM Symbol('zoom') .. data:: ANNOTATION_MODE >>> ANNOTATION_MODE Symbol('mode') .. data:: ANNOTATION_ALIGN >>> ANNOTATION_ALIGN Symbol('align') Metadata -------- .. seealso:: |djvuext|_ (*Metadata Annotations*, *Document Annotations and Metadata*). .. _BibTeX: http://www.ctan.org/get/biblio/bibtex/contrib/doc/btxdoc.pdf .. currentmodule:: djvu.decode .. class:: Metadata A metadata mapping. .. currentmodule:: djvu.const The following sets contain noteworthy metadata keys: .. data:: METADATA_BIBTEX_KEYS Keys borrowed from the BibTeX_ bibliography system. >>> for key in sorted(METADATA_BIBTEX_KEYS): ... print repr(key) ... Symbol('address') Symbol('annote') Symbol('author') Symbol('booktitle') Symbol('chapter') Symbol('crossref') Symbol('edition') Symbol('editor') Symbol('howpublished') Symbol('institution') Symbol('journal') Symbol('key') Symbol('month') Symbol('note') Symbol('number') Symbol('organization') Symbol('pages') Symbol('publisher') Symbol('school') Symbol('series') Symbol('title') Symbol('type') Symbol('volume') Symbol('year') .. data:: METADATA_PDFINFO_KEYS Keys borrowed from the PDF DocInfo. >>> for key in sorted(METADATA_PDFINFO_KEYS): ... print repr(key) ... Symbol('Author') Symbol('CreationDate') Symbol('Creator') Symbol('Keywords') Symbol('ModDate') Symbol('Producer') Symbol('Subject') Symbol('Title') Symbol('Trapped') .. data:: METADATA_KEYS Sum of :data:`METADATA_BIBTEX_KEYS` and :data:`METADATA_PDFINFO_KEYS`. .. currentmodule:: djvu.const The following symbols are pre-defined: .. data:: ANNOTATION_METADATA >>> ANNOTATION_METADATA Symbol('metadata') Printed headers and footers --------------------------- .. seealso:: |djvu3ref|_ (8.3.4.3 *Printed headers and footers*) .. currentmodule:: djvu.const The following symbols are pre-defined: .. data:: ANNOTATION_PRINTED_HEADER >>> ANNOTATION_PRINTED_HEADER Symbol('phead') .. data:: ANNOTATION_PRINTED_FOOTER >>> ANNOTATION_PRINTED_FOOTER Symbol('pfoot') .. data:: PRINTER_HEADER_ALIGN_LEFT >>> PRINTER_HEADER_ALIGN_LEFT Symbol('left') .. data:: PRINTER_HEADER_ALIGN_CENTER >>> PRINTER_HEADER_ALIGN_CENTER Symbol('center') .. data:: PRINTER_HEADER_ALIGN_RIGHT >>> PRINTER_HEADER_ALIGN_RIGHT Symbol('right') .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/pages.txt0000644000000000000000000001751111440743731020704 0ustar rootroot00000000000000Document pages ============== .. currentmodule:: djvu.decode .. class:: DocumentPages Pages of a document. Use :attr:`Document.pages` to obtain instances of this class. Page indexing is zero-based, i.e. :attr:`~Document.pages`\ ``[0]`` stands for the very first page. ``len(pages)`` might return 1 when called before receiving a :class:`DocInfoMessage`. .. currentmodule:: djvu.decode .. class:: Page Page of a document. Use :attr:`Document.pages`\ ``[N]`` to obtain instances of this class. .. attribute:: document :rtype: :class:`Document` .. attribute:: file :return: a file associated with the page. :rtype: :class:`File`. .. attribute:: n :return: the page number. Page indexing is zero-based, i.e. 0 stands for the very first page. .. attribute:: thumbnail :return: a thumbnail for the page. :rtype: :class:`Thumbnail`. .. method:: get_info([wait=1]) Attempt to obtain information about the page without decoding the page. If `wait` is true, wait until the information is available. If the information is not available, raise :exc:`NotAvailable` exception. Then, start fetching the page data, which causes emission of :class:`PageInfoMessage` messages with empty :attr:`~Message.page_job`. :raise NotAvailable: see above. :raise JobFailed: on failure. .. attribute:: width :return: the page width, in pixels. :raise NotAvailable: see :meth:`get_info`. :raise JobFailed: on failure. .. attribute:: height :return: the page height, in pixels. :raise NotAvailable: see :meth:`get_info`. :raise JobFailed: on failure. .. attribute:: size :return: ``(page.width, page.height)`` :raise NotAvailable: see :meth:`get_info`. :raise JobFailed: on failure. .. attribute:: dpi :return: the page resolution, in pixels per inch. :raise NotAvailable: see :meth:`get_info`. :raise JobFailed: on failure. .. attribute:: rotation :return: the initial page rotation, in degrees. :raise NotAvailable: see :meth:`get_info`. :raise JobFailed: on failure. .. attribute:: version :return: the page version. :raise NotAvailable: see :meth:`get_info`. :raise JobFailed: on failure. .. attribute:: dump :return: a text describing the contents of the page using the same format as the ``djvudump`` command. If the information is not available, raise :exc:`NotAvailable` exception. Then :class:`PageInfoMessage` messages with empty :attr:`~Message.page_job` may be emitted. :raise NotAvailable: see above. .. method:: decode([wait=1]) Initiate data transfer and decoding threads for the page. If `wait` is true, wait until the job is done. :rtype: :class:`PageJob`. :raise NotAvailable: if called before receiving the :class:`DocInfoMessage`. :raise JobFailed: if document decoding failed. .. attribute:: annotations :rtype: :class:`PageAnnotations` .. attribute:: text :rtype: :class:`PageText` .. class:: PageJob Inheritance diagram: .. inheritance-diagram:: PageJob :parts: 1 A page decoding job. Use :meth:`Page.decode` to obtain instances of this class. .. attribute:: width :return: the page width in pixels. :raise NotAvailable: before receiving a :class:`PageInfoMessage`. .. attribute:: height :return: the page height in pixels. :raise NotAvailable: before receiving a :class:`PageInfoMessage`. .. attribute:: size :return: ``(page_job.width, page_job.height)`` :raise NotAvailable: before receiving a :class:`PageInfoMessage`. .. attribute:: dpi :return: the page resolution in pixels per inch. :raise NotAvailable: before receiving a :class:`PageInfoMessage`. .. attribute:: gamma :return: the gamma of the display for which this page was designed. :raise NotAvailable: before receiving a :class:`PageInfoMessage`. .. attribute:: version :return: the version of the DjVu file format. :raise NotAvailable: before receiving a :class:`PageInfoMessage`. .. attribute:: type :return: the type of the page data. Possible values are: .. data:: PAGE_TYPE_UNKNOWN .. data:: PAGE_TYPE_BITONAL .. data:: PAGE_TYPE_PHOTO .. data:: PAGE_TYPE_COMPOUND :raise NotAvailable: before receiving a :class:`PageInfoMessage`. .. attribute:: initial_rotation :return: the counter-clockwise page rotation angle (in degrees) specified by the orientation flags in the DjVu file. .. warning:: This is useful because ``maparea`` coordinates in the annotation chunks are expressed relative to the rotated coordinates whereas text coordinates in the hidden text data are expressed relative to the unrotated coordinates. .. attribute:: rotation :return: the counter-clockwise rotation angle (in degrees) for the page. The rotation is automatically taken into account by :meth:`render` method and :attr:`width` and :attr:`height` properties. .. method:: render(self, mode, page_rect, render_rect, pixel_format[, row_alignment=1][, buffer=None]) Render a segment of a page with arbitrary scale. `mode` indicates which image layers should be rendered: * :data:`~djvu.decode.RENDER_COLOR`, or * :data:`~djvu.decode.RENDER_BLACK`, or * :data:`~djvu.decode.RENDER_COLOR_ONLY`, or * :data:`~djvu.decode.RENDER_MASK_ONLY`, or * :data:`~djvu.decode.RENDER_BACKGROUND`, or * :data:`~djvu.decode.RENDER_FOREGROUND`. Conceptually this method renders the full page into a rectangle `page_rect` and copies the pixels specified by rectangle `render_rect` into a buffer. The actual code is much more efficient than that. `pixel_format` (a :class:`~djvu.decode.PixelFormat` instance) specifies the expected pixel format. Each row will start at `row_alignment` bytes boundary. Data will be saved to the provided buffer or to a newly created string. This method makes a best effort to compute an image that reflects the most recently decoded data. :raise NotAvailable: to indicate that no image could be computed at this point. .. currentmodule:: djvu.decode .. class:: Thumbnail Thumbnail for a page. Use :attr:`Page.thumbnail` to obtain instances of this class. .. attribute:: page :return: the page. .. attribute:: status Determine whether the thumbnail is available. :return: a :exc:`JobException` subclass indicating the current job status. .. method:: calculate() Determine whether the thumbnail is available. If it's not, initiate the thumbnail calculating job. Regardless of its success, the completion of the job is signalled by a subsequent :class:`ThumbnailMessage`. Return a :exc:`JobException` subclass indicating the current job status. .. method:: render((w0, h0)[, pixel_format][, row_alignment=1][, dry_run=False][, buffer=None]) Render the thumbnail: * not larger than `w0` × `h0` pixels; * using the `pixel_format` (a :class:`~djvu.decode.PixelFormat` instance) pixel format; * with each row starting at `row_alignment` bytes boundary; * into the provided buffer or to a newly created string. :return: a ((`w1`, `h1`, `row_size`), `data`) tuple. * `w1` and `h1` are actual thumbnail dimensions in pixels (`w1` ≤ `w0` and `h1` ≤ `h0`); * `row_size` is length of each image row, in bytes; * `data` is ``None`` if `dry_run` is true; otherwise is contains the actual image data. :raise NotAvailable: when no thumbnail is available. .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/files.txt0000644000000000000000000000546311440743675020721 0ustar rootroot00000000000000Document files ============== .. currentmodule:: djvu.decode .. class:: DocumentFiles Component files of a document. Use :attr:`Document.files` to obtain instances of this class. File indexing is zero-based, i.e. :attr:`~Document.files`\ ``[0]`` stands for the very first file. ``len(files)`` might raise :exc:`NotAvailable` when called before receiving a :class:`DocInfoMessage`. .. currentmodule:: djvu.decode .. class:: File Component file of a document. Use :attr:`Document.files`\ ``[N]`` to obtain instances of this class. .. attribute:: document :rtype: :class:`Document` .. attribute:: n :return: the component file number. File indexing is zero-based, i.e. 0 stands for the very first file. .. method:: get_info([wait=1]) Attempt to obtain information about the component file. If `wait` is true, wait until the information is available. :raise NotAvailable: if the information is not available. :raise JobFailed: on failure. .. attribute:: type :return: the type of the compound file. The following types are possible: .. data:: FILE_TYPE_PAGE .. data:: FILE_TYPE_THUMBNAILS .. data:: FILE_TYPE_INCLUDE :raise NotAvailable: see :meth:`~File.get_info`. :raise JobFailed: on failure. .. attribute:: n_page :return: the page number, or ``None`` when not applicable. Page indexing is zero-based, i.e. 0 stands for the very first page. :raise NotAvailable: see :meth:`~File.get_info`. :raise JobFailed: on failure. .. attribute:: page :return: the page, or ``None`` when not applicable. :raise NotAvailable: see :meth:`~File.get_info`. :raise JobFailed: on failure. .. attribute:: size :return: the compound file size, or ``None`` when unknown. :raise NotAvailable: see :meth:`~File.get_info`. :raise JobFailed: on failure. .. attribute:: id :return: the compound file identifier, or ``None``. :raise NotAvailable: see :meth:`~File.get_info`. :raise JobFailed: on failure. .. attribute:: name :return: the compound file name, or ``None``. :raise NotAvailable: see :meth:`~File.get_info`. :raise JobFailed: on failure. .. attribute:: title :return: the compound file title, or ``None``. :raise NotAvailable: see :meth:`~File.get_info`. :raise JobFailed: on failure. .. attribute:: dump :return: a text describing the contents of the file using the same format as the ``djvudump`` command. If the information is not available, raise :exc:`NotAvailable` exception. Then, :exc:`PageInfoMessage` messages with empty :attr:`~Message.page_job` may be emitted. :raise NotAvailable: see above. .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/documents.txt0000644000000000000000000001656611440743731021617 0ustar rootroot00000000000000DjVu documents ============== .. currentmodule:: djvu.decode .. class:: Document DjVu document. Use :meth:`Context.new_document` to obtain instances of this class. .. attribute:: decoding_status :return: a :exc:`JobException` subclass indicating the decoding job status. .. attribute:: decoding_error Indicate whether the decoding job failed. .. attribute:: decoding_done Indicate whether the decoding job is done. .. attribute:: decoding_job :rtype: :exc:`DocumentDecodingJob` .. attribute:: type :return: the type of the document. The following values are possible: :data:`~djvu.decode.DOCUMENT_TYPE_UNKNOWN` :data:`~djvu.decode.DOCUMENT_TYPE_SINGLE_PAGE` single-page document :data:`~djvu.decode.DOCUMENT_TYPE_BUNDLED` bundled multi-page document :data:`~djvu.decode.DOCUMENT_TYPE_INDIRECT` indirect multi-page document :data:`~djvu.decode.DOCUMENT_TYPE_OLD_BUNDLED` (obsolete) :data:`~djvu.decode.DOCUMENT_TYPE_OLD_INDEXED` (obsolete) Before receiving the :class:`DocInfoMessage`, :data:`~djvu.decode.DOCUMENT_TYPE_UNKNOWN` may be returned. .. attribute:: pages :rtype: :class:`DocumentPages`. .. attribute:: files :rtype: :class:`DocumentFiles`. .. attribute:: outline :rtype: :class:`DocumentOutline`. .. attribute:: annotations :rtype: :class:`DocumentAnnotations`. .. method:: save(file[, pages][, wait=True]) save(indirect[, pages][, wait=True]) Save the document as: * a bundled DjVu `file` or; * an indirect DjVu document with index file name `indirect`. `pages` argument specifies a subset of saved pages. If `wait` is true, wait until the job is done. :rtype: :class:`SaveJob`. .. warning:: Due to a DjVuLibre (≤ 3.5.20) bug, this method may be broken. See http://bugs.debian.org/467282 for details. .. method:: export_ps(file[, …][, wait=True]) Convert the document into PostScript. `pages` argument specifies a subset of saved pages. If `wait` is true, wait until the job is done. Additional options: `eps` Produce an *Encapsulated* PostScript file. Encapsulated PostScript files are suitable for embedding images into other documents. Encapsulated PostScript file can only contain a single page. Setting this option overrides the options `copies`, `orientation`, `zoom`, `crop_marks`, and `booklet`. `level` Selects the language level of the generated PostScript. Valid language levels are 1, 2, and 3. Level 3 produces the most compact and fast printing PostScript files. Some of these files however require a very modern printer. Level 2 is the default value. The generated PostScript files are almost as compact and work with all but the oldest PostScript printers. Level 1 can be used as a last resort option. `orientation` Specifies the pages orientation: :data:`~djvu.decode.PRINT_ORIENTATION_AUTO` automatic :data:`~djvu.decode.PRINT_ORIENTATION_LANDSCAPE` portrait :data:`~djvu.decode.PRINT_ORIENTATION_PORTRAIT` landscape `mode` Specifies how pages should be decoded: :data:`~djvu.decode.RENDER_COLOR` render all the layers of the DjVu documents :data:`~djvu.decode.RENDER_BLACK` render only the foreground layer mask :data:`~djvu.decode.RENDER_FOREGROUND` render only the foreground layer :data:`~djvu.decode.RENDER_BACKGROUND` render only the background layer `zoom` Specifies a zoom factor. The default zoom factor scales the image to fit the page. `color` Specifies whether to generate a color or a gray scale PostScript file. A gray scale PostScript files are smaller and marginally more portable. `srgb` The default value, True, generates a PostScript file using device independent colors in compliance with the sRGB specification. Modern printers then produce colors that match the original as well as possible. Specifying a false value generates a PostScript file using device dependent colors. This is sometimes useful with older printers. You can then use the `gamma` option to tune the output colors. `gamma` Specifies a gamma correction factor for the device dependent PostScript colors. Argument must be in range 0.3 to 5.0. Gamma correction normally pertains to cathodic screens only. It gets meaningful for printers because several models interpret device dependent RGB colors by emulating the color response of a cathodic tube. `copies` Specifies the number of copies to print. `frame`, If true, generate a thin gray border representing the boundaries of the document pages. `crop_marks` If true, generate crop marks indicating where pages should be cut. `text` Generate hidden text. This option is deprecated. See also the warning below. `booklet` :data:`~djvu.decode.PRINT_BOOKLET_NO` Disable booklet mode. This is the default. :data:`~djvu.decode.PRINT_BOOKLET_YES` Enable recto/verse booklet mode. :data:`~djvu.decode.PRINT_BOOKLET_RECTO` Enable recto booklet mode. :data:`~djvu.decode.PRINT_BOOKLET_VERSO` Enable verso booklet mode. `booklet_max` Specifies the maximal number of pages per booklet. A single printout might then be composed of several booklets. The argument is rounded up to the next multiple of 4. Specifying 0 sets no maximal number of pages and ensures that the printout will produce a single booklet. This is the default. `booklet_align` Specifies a positive or negative offset applied to the verso of each sheet. The argument is expressed in points [1]_. This is useful with certain printers to ensure that both recto and verso are properly aligned. The default value is 0. `booklet_fold` (= `(base, increment)`) Specifies the extra margin left between both pages on a single sheet. The base value is expressed in points [1]_. This margin is incremented for each outer sheet by value expressed in millipoints. The default value is (18, 200). .. [1] 1 pt = :math:`\frac1{72}` in = 0.3528 mm .. warning:: Due to a DjVuLibre (≤ 3.5.20) bug, this method may be broken. See http://bugs.debian.org/469122 for details. .. currentmodule:: djvu.decode .. class:: SaveJob Inheritance diagram: .. inheritance-diagram:: SaveJob :parts: 1 Document saving job. Use :meth:`Document.save` to obtain instances of this class. .. currentmodule:: djvu.decode .. class:: DocumentDecodingJob Inheritance diagram: .. inheritance-diagram:: DocumentDecodingJob :parts: 1 Document decoding job. Use :attr:`Document.decoding_job` to obtain instances of this class. .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/event-model.txt0000644000000000000000000000713411440743675022033 0ustar rootroot00000000000000Event model =========== The DDJVU API provides for efficiently decoding and displaying DjVu documents. It provides for displaying images without waiting for the complete DjVu data. Images can be displayed as soon as sufficient data is available. A higher quality image might later be displayed when further data is available. The DjVu library achieves this using a complicated scheme involving multiple threads. The DDJVU API hides this complexity with a familiar event model. .. currentmodule:: djvu.decode .. data:: DDJVU_VERSION Version of the DDJVU API. .. currentmodule:: djvu.decode .. class:: Context(argv0) .. method:: handle_message(message) This method is called, in a separate thread, for every received message, *before* any blocking method finishes. By default do something roughly equivalent to:: if message.job is not None: message.job.message_queue.put(message) elif message.document is not None: message.document.message_queue.put(message) else: message.context.message_queue.put(message) You may want to override this method to change this behaviour. All exceptions raised by this method will be ignored. .. attribute:: message_queue Return the internal message queue. .. method:: get_message([wait=True]) Get message from the internal context queue. :return: a :class:`Message` instance :return: ``None`` if `wait` is false and no message is available. .. method:: new_document(uri[ ,cache=True]) Creates a decoder for a DjVu document and starts decoding. This method returns immediately. The decoding job then generates messages to request the raw data and to indicate the state of the decoding process. `uri` specifies an optional URI for the document. The URI follows the usual syntax (``protocol://machine/path``). It should not end with a slash. It only serves two purposes: - The URI is used as a key for the cache of decoded pages. - The URI is used to document :class:`NewStreamMessage` messages. Setting argument `cache` to a true vaule indicates that decoded pages should be cached when possible. It is important to understand that the URI is not used to access the data. The document generates :class:`NewStreamMessage` messages to indicate which data is needed. The caller must then provide the raw data using a :attr:`NewStreamMessage.stream` object. .. class:: djvu.decode.FileUri(filename) To open a local file, provide a :class:`FileUri` instance as an `uri`. Localized characters in `uri` should be in URI-encoded. :rtype: :class:`Document` :raise JobFailed: on failure. .. attribute:: cache_size .. method:: clear_cache() .. currentmodule:: djvu.decode .. class:: Job A job. .. method:: get_message([wait=True]) Get message from the internal job queue. :return: a :class:`Message` instance. :return: ``None`` if `wait` is false and no message is available. .. attribute:: is_done Indicate whether the decoding job is done. .. attribute:: is_error Indicate whether the decoding job is done. .. attribute:: message_queue :return: the internal message queue. .. attribute:: status :return: a :exc:`JobException` subclass indicating the job status. .. method:: stop() Attempt to cancel the job. This is a best effort method. There no guarantee that the job will actually stop. .. method:: wait() Wait until the job is done. .. vim:ts=3 sw=3 et ft=rst python-djvulibre-0.3.9/doc/source/text-zones.txt0000644000000000000000000000633011440743731021722 0ustar rootroot00000000000000Text zones ========== .. seealso:: |djvu3ref|_ (8.3.5 *Text Chunk*). Representing text zones as S-expressions is DjVuLibre-specific; see |djvused|_ for reference. .. currentmodule:: djvu.decode .. class:: PageText(page[, details=TEXT_DETAILS_ALL]) A wrapper around page text. `details` controls the level of details in the returned S-expression: * :data:`~TEXT_DETAILS_PAGE`, or * :data:`~TEXT_DETAILS_COLUMN`, or * :data:`~TEXT_DETAILS_REGION`, or * :data:`~TEXT_DETAILS_PARAGRAPH`, or * :data:`~TEXT_DETAILS_LINE`, or * :data:`~TEXT_DETAILS_WORD`, or * :data:`~TEXT_DETAILS_CHARACTER`, or * :data:`~TEXT_DETAILS_ALL`. .. method:: wait() Wait until the associated S-expression is available. .. attribute:: page :rtype: :class:`Page` .. attribute:: sexpr :rtype: :class:`djvu.sexpr.Expression` :raise NotAvailable: if the S-expression is not available; then, :class:`PageInfoMessage` messages with empty :attr:`~Message.page_job` may be emitted. :raise JobFailed: on failure. .. currentmodule:: djvu.const .. class:: TextZoneType A type of a text zone. To create objects of this class, use the :func:`get_text_zone_type()` function. .. currentmodule:: djvu.const .. function:: get_text_zone_type(symbol) Return one of the following text zone types: .. data:: TEXT_ZONE_PAGE >>> get_text_zone_type(Symbol('page')) is TEXT_ZONE_PAGE True .. data:: TEXT_ZONE_COLUMN >>> get_text_zone_type(Symbol('column')) is TEXT_ZONE_COLUMN True .. data:: TEXT_ZONE_REGION >>> get_text_zone_type(Symbol('region')) is TEXT_ZONE_REGION True .. data:: TEXT_ZONE_PARAGRAPH >>> get_text_zone_type(Symbol('para')) is TEXT_ZONE_PARAGRAPH True .. data:: TEXT_ZONE_LINE >>> get_text_zone_type(Symbol('line')) is TEXT_ZONE_LINE True .. data:: TEXT_ZONE_WORD >>> get_text_zone_type(Symbol('word')) is TEXT_ZONE_WORD True .. data:: TEXT_ZONE_CHARACTER >>> get_text_zone_type(Symbol('char')) is TEXT_ZONE_CHARACTER True You can compare text zone types using the ``<`` operator: >>> TEXT_ZONE_PAGE < TEXT_ZONE_COLUMN < TEXT_ZONE_REGION < TEXT_ZONE_PARAGRAPH True >>> TEXT_ZONE_PARAGRAPH < TEXT_ZONE_LINE < TEXT_ZONE_WORD < TEXT_ZONE_CHARACTER True .. currentmodule:: djvu.decode .. function:: cmp_text_zone(zonetype1, zonetype2) :return: a negative integer if `zonetype1` is more concrete than `zonetype2`. :return: a negative integer if `zonetype1` is the same as `zonetype2`. :return: a positive integer if `zonetype1` is the general than `zonetype2`. .. currentmodule:: djvu.const .. data:: TEXT_ZONE_SEPARATORS Dictionary that maps text types to their seprators. >>> pprint(TEXT_ZONE_SEPARATORS) {: '', : ' ', : '\n', : '\x1f', : '\x1d', : '\x0b', : '\x0c'} .. vim:ts=3 sw=3 et ft=rst .. vim:ts=3 sw=3 et python-djvulibre-0.3.9/doc/source/conf.py0000644000000000000000000000251411731460600020332 0ustar rootroot00000000000000# encoding=UTF-8 import os import codecs extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.inheritance_diagram', ] templates_path = ['templates'] source_suffix = '.txt' source_encoding = 'UTF-8' master_doc = 'index' import setup as _setup project = _setup.setup_params['name'] version = release = _setup.__version__ _setup_file = codecs.open(os.path.splitext(_setup.__file__)[0] + '.py', 'r', encoding='UTF-8') try: for line in _setup_file: if line.startswith(u'# Copyright © '): copyright = line[14:].strip() break finally: _setup_file.close() del _setup, _setup_file pygments_style = 'sphinx' html_theme = 'default' html_use_modindex = True html_use_index = False intersphinx_mapping = {'http://docs.python.org/': None} rst_epilog = ''' .. |djvu3ref| replace:: Lizardtech DjVu Reference .. _djvu3ref: http://djvu.org/docs/DjVu3Spec.djvu .. |djvused| replace:: djvused manual .. _djvused: http://djvu.sourceforge.net/doc/man/djvused.html .. |djvuext| replace:: Actual and proposed changes to the DjVu format .. _djvuext: http://djvu.git.sourceforge.net/git/gitweb.cgi?p=djvu/djvulibre.git;a=blob;f=doc/djvuchanges.txt;hb=refs/tags/release.3.5.23 ''' # vim:ts=4 sw=4 et python-djvulibre-0.3.9/doc/credits.txt0000644000000000000000000000030111717773371017741 0ustar rootroot00000000000000Since May 2009 python-djvulibre 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). python-djvulibre-0.3.9/doc/changelog0000644000000000000000000001634011731706003017407 0ustar rootroot00000000000000python-djvulibre (0.3.9) unstable; urgency=low * Ensure that all S-expression output is 7-bit. This is work-around for . -- Jakub Wilk Mon, 19 Mar 2012 20:41:53 +0100 python-djvulibre (0.3.8) unstable; urgency=low * Ensure that S-expression input/output functions are always initialized. This is work-around for . -- Jakub Wilk Sun, 18 Mar 2012 23:44:55 +0100 python-djvulibre (0.3.7) unstable; urgency=low * Use floor division operator instead of relying on “classic” division semantics. -- Jakub Wilk Wed, 14 Mar 2012 08:32:35 +0100 python-djvulibre (0.3.6) unstable; urgency=low * Improve test suite: + Fix compatibility with Python 3.X (broken in 0.3.5). + Test expression parsing again. + Capture stderr output for tests which produce unhandled exceptions. * Fix compatibility with Python 2.5 (broken since 0.3.0). -- Jakub Wilk Tue, 06 Mar 2012 00:14:57 +0100 python-djvulibre (0.3.5) unstable; urgency=low * Improve setup.py: + Fix error message when pkg-config is not found (a regression introduced in 0.3.4). * Improve test suite: + Verify that LANGUAGE environment variable is unset before running tests sensitive to locale settings. Thanks to Daniel Stender for the bug report. + Skip some tests (instead of making them fail) when they are run in an unsuitable environment. Note that it's still recommended to use --no-skip when running the test suite. -- Jakub Wilk Sat, 18 Feb 2012 21:05:41 +0100 python-djvulibre (0.3.4) unstable; urgency=low * Optimize DocumentPages.__len__() and DocumentFiles.__len__(). * Fix compatibility with Python 3.X (broken in 0.3.3). * Update various external documentation URLs. http://bugs.debian.org/627290 * Improve test suite: + Normalize whitespace in ps2ascii output. http://bugs.debian.org/646177 * Improve setup.py: + Print a more meaningful error message if pkg-config fails. + build_sphinx is now available even without setuptools. + build_sphinx can now import extension modules from the build directory. -- Jakub Wilk Sat, 22 Oct 2011 01:36:38 +0200 python-djvulibre (0.3.3) unstable; urgency=low * Improve setup.py: + Add “Operating System :: Microsoft :: Windows” to trove classifiers. * Add Windows-specific module djvu.dllpath, which is aimed to ease finding DjVuLibre DLLs in non-standard locations. * Make expression and symbol objects picklable. * Add work-around for . * Fix test suite compatibility with Python 3. -- Jakub Wilk Mon, 04 Apr 2011 21:06:01 +0200 python-djvulibre (0.3.2) unstable; urgency=low * Python ≥ 2.6: make djvu.sexpr.ListExpression a “virtual subclass” of collections.MutableSequence. * Add append, count, extend, index, insert, pop, remove and reverse methods for list expressions. * Add ‘+=’ and ‘del’ operators for list expressions. * Fix compatibility with Cython 0.12. * Fix compatibility with some non-POSIX operating systems. * Add work-around for . * Improve setup.py: + Add work-around for . + Make ‘clean --all’ remove temporary *.pxi and *.c files. + Don't import Cython modules; calls the ‘cython’ binary instead. + Allow cross-compilation using MinGW cross compiler. -- Jakub Wilk Wed, 15 Dec 2010 21:17:39 +0100 python-djvulibre (0.3.1) unstable; urgency=low * Add another example program. * Fix encoding issues with djvu.decode.ErrorMessage. Thanks to Kyrill Detinov for the bug report. -- Jakub Wilk Fri, 19 Nov 2010 17:44:51 +0100 python-djvulibre (0.3.0) unstable; urgency=low * Add support for Python 3. * Fix tests on 64-bit architectures. * Fix compatibility with Cython 0.13. * Message.message are now always Unicode strings. -- Jakub Wilk Fri, 29 Oct 2010 01:51:33 +0200 python-djvulibre (0.1.18) unstable; urgency=low * Fix handling of non-ASCII metadata. -- Jakub Wilk Fri, 25 Jun 2010 23:32:05 +0200 python-djvulibre (0.1.17) unstable; urgency=low * Allow to render images directly into a writable buffer (e.g. an array), rather than to a newly created string. That should ease integration of python-djulibre with e.g. numpy or cairo. * Add two simple example programs. -- Jakub Wilk Thu, 11 Feb 2010 17:48:36 +0100 python-djvulibre (0.1.16) unstable; urgency=low * Make reading S-expression from streams more efficient. * Fix exception handling while reading/writing S-expression from/to streams. -- Jakub Wilk Sun, 03 Jan 2010 12:52:12 +0100 python-djvulibre (0.1.15) unstable; urgency=low * Don't let text zones compare equal to unrelated objects. -- Jakub Wilk Wed, 04 Nov 2009 20:02:07 +0100 python-djvulibre (0.1.14) unstable; urgency=low * Fix infinite recursion when comparing a text zone with a string. Thanks to Rogério Brito for the bug report. -- Jakub Wilk Thu, 03 Sep 2009 23:36:47 +0200 python-djvulibre (0.1.13) unstable; urgency=low * Fix major breakage introduced in 0.1.12. Thanks to Piotr Ożarowski for reporting that. * Get rid of some spurious warnings. -- Jakub Wilk Sun, 16 Aug 2009 10:49:42 +0200 python-djvulibre (0.1.12) unstable; urgency=low * Use Cython (rather than Pyrex) to compile sources. -- Jakub Wilk Thu, 13 Aug 2009 13:07:16 +0200 python-djvulibre (0.1.11) unstable; urgency=low * Provide a bit more comprehensive documentation. -- Jakub Wilk Tue, 14 Jul 2009 16:33:13 +0200 python-djvulibre (0.1.10) unstable; urgency=low * Remove the `PageInfo` class (it served no purpose). * Work around a Pyrex compilation bug. -- Jakub Wilk Tue, 07 Jul 2009 23:12:27 +0200 python-djvulibre (0.1.9) unstable; urgency=low * Fill up the package metadata. -- Jakub Wilk Sat, 16 May 2009 11:05:45 +0200 python-djvulibre (0.1.8) unstable; urgency=low * Enable compilation with recent Pyrex. -- Jakub Wilk Sat, 12 Jul 2008 12:10:08 +0200 python-djvulibre (0.1.7) unstable; urgency=low * Protect from collecting just created S-expressions. -- Jakub Wilk Fri, 20 Jun 2008 12:39:24 +0200 python-djvulibre (0.1.6) unstable; urgency=low * Call `Context.handle_message()` *before* any blocking method finishes. * Better error handling in `Page.decode()`. -- Jakub Wilk Wed, 14 May 2008 11:19:52 +0200 python-djvulibre (0.1.5) unstable; urgency=low * Fix a race condition in `DocumentDecodingJob.wait()`. * `DocumentPages` and `DocumentFiles` checks for actual length and raises `IndexError` exceptions. * Add `Page.size` and `PageJob.size` properties. * Remove spurious overflow checks. -- Jakub Wilk Wed, 07 May 2008 18:36:51 +0200 python-djvulibre (0.1.4) unstable; urgency=low * Initial release. -- Jakub Wilk Wed, 07 May 2008 00:17:17 +0200 python-djvulibre-0.3.9/examples/0000755000000000000000000000000011731710327016606 5ustar rootroot00000000000000python-djvulibre-0.3.9/examples/djvu-crop-text0000755000000000000000000000702711731460033021431 0ustar rootroot00000000000000#!/usr/bin/python # encoding=UTF-8 # Copyright © 2008-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 argparse import sys import subprocess import djvu.sexpr import djvu.decode import djvu.const EMPTY_TEXT_SEXPR = djvu.sexpr.Expression([djvu.const.TEXT_ZONE_PAGE, 0, 0, 0, 0, '']) class ArgumentParser(argparse.ArgumentParser): def __init__(self): argparse.ArgumentParser.__init__(self) self.add_argument('-p', '--pages', dest='pages', action='store', help='pages to process') self.add_argument('path', metavar='FILE', action='store', help='DjVu file to process') def parse_args(self): options = argparse.ArgumentParser.parse_args(self) try: if options.pages is not None: pages = [] for range in options.pages.split(','): if '-' in range: x, y = map(int, options.pages.split('-', 1)) pages += xrange(x, y + 1) else: pages += int(range), options.pages = pages except (TypeError, ValueError): self.error('Unable to parse page numbers') return options def crop_text(sexpr, width, height): if isinstance(sexpr, djvu.sexpr.ListExpression) and len(sexpr) >= 5: tp = sexpr[0] x0, y0, x1, y1 = (sexpr[i].value for i in xrange(1, 5)) if x1 < 0 or y1 < 0 or x0 >= width or y0 >= height: return x0 = max(0, x0) y0 = max(0, y0) x1 = min(x1, width) y1 = min(y1, height) children = (crop_text(child, width, height) for child in sexpr[5:]) children = [child for child in children if child is not None] if not children: return return djvu.sexpr.Expression([tp, x0, y0, x1, y1] + children) else: return sexpr class Context(djvu.decode.Context): def handle_message(self, message): if isinstance(message, djvu.decode.ErrorMessage): print >>sys.stderr, message sys.exit(1) def process_page(self, page): print >>sys.stderr, '- Page #%d' % (page.n + 1) page.get_info() text = crop_text(page.text.sexpr, page.width, page.height) if not text: text = EMPTY_TEXT_SEXPR return text def process(self, path, pages = None): print >>sys.stderr, 'Processing %r:' % path document = self.new_document(djvu.decode.FileURI(path)) document.decoding_job.wait() sed_file = sys.stdout if pages is None: pages = iter(document.pages) else: pages = (document.pages[i-1] for i in pages) sed_file.write('remove-txt\n') for page in pages: sed_file.write('select %d\n' % (page.n + 1)) sed_file.write('set-txt\n') self.process_page(page).print_into(sed_file) sed_file.write('\n.\n\n') def main(): parser = ArgumentParser() options = parser.parse_args() context = Context() context.process(options.path, options.pages) if __name__ == '__main__': main() # vim:ts=4 sw=4 et python-djvulibre-0.3.9/examples/djvu2png0000755000000000000000000000576511731460305020306 0ustar rootroot00000000000000#!/usr/bin/python # encoding=UTF-8 # Copyright © 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 argparse import sys import cairo import djvu.decode import numpy cairo_pixel_format = cairo.FORMAT_ARGB32 djvu_pixel_format = djvu.decode.PixelFormatRgbMask(0xff0000, 0xff00, 0xff, bpp=32) djvu_pixel_format.rows_top_to_bottom = 1 djvu_pixel_format.y_top_to_bottom = 0 class Context(djvu.decode.Context): def handle_message(self, message): if isinstance(message, djvu.decode.ErrorMessage): print >>sys.stderr, message sys.exit(1) def process(self, djvu_path, png_path, mode): document = self.new_document(djvu.decode.FileURI(djvu_path)) document.decoding_job.wait() for page in document.pages: page_job = page.decode(wait=True) width, height = page_job.size rect = (0, 0, width, height) bytes_per_line = cairo.ImageSurface.format_stride_for_width(cairo_pixel_format, width) color_buffer = numpy.zeros((height, bytes_per_line), dtype=numpy.uint32) page_job.render(mode, rect, rect, djvu_pixel_format, row_alignment=bytes_per_line, buffer=color_buffer) mask_buffer = numpy.zeros((height, bytes_per_line), dtype=numpy.uint32) if mode == djvu.decode.RENDER_FOREGROUND: page_job.render(djvu.decode.RENDER_MASK_ONLY, rect, rect, djvu_pixel_format, row_alignment=bytes_per_line, buffer=mask_buffer) mask_buffer <<= 24 color_buffer |= mask_buffer color_buffer ^= 0xff000000 surface = cairo.ImageSurface.create_for_data(color_buffer, cairo_pixel_format, width, height) surface.write_to_png(png_path) # Multi-page documents are not yet supported: break def main(): parser = argparse.ArgumentParser() parser.set_defaults(mode=djvu.decode.RENDER_COLOR_ONLY) group = parser.add_mutually_exclusive_group() group.add_argument('--foreground', dest='mode', action='store_const', const=djvu.decode.RENDER_FOREGROUND) group.add_argument('--background', dest='mode', action='store_const', const=djvu.decode.RENDER_BACKGROUND) group.add_argument('--mask', dest='mode', action='store_const', const=djvu.decode.RENDER_MASK_ONLY) parser.add_argument('djvu_path', metavar='') parser.add_argument('png_path', metavar='') options = parser.parse_args(sys.argv[1:]) context = Context() context.process(options.djvu_path, options.png_path, options.mode) if __name__ == '__main__': main() # vim:ts=4 sw=4 et python-djvulibre-0.3.9/examples/djvu-dump-text0000755000000000000000000000304111731460103021421 0ustar rootroot00000000000000#!/usr/bin/python # encoding=UTF-8 # Copyright © 2008-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 sys import djvu.decode def print_text(sexpr, level=0): if level > 0: print ' ' * (2 * level - 1), if isinstance(sexpr, djvu.sexpr.ListExpression): print str(sexpr[0].value), [sexpr[i].value for i in xrange(1, 5)] for child in sexpr[5:]: print_text(child, level + 1) else: print sexpr class Context(djvu.decode.Context): def handle_message(self, message): if isinstance(message, djvu.decode.ErrorMessage): print >>sys.stderr, message sys.exit(1) def process(self, path): document = self.new_document(djvu.decode.FileURI(path)) document.decoding_job.wait() for page in document.pages: page.get_info() text = print_text(page.text.sexpr) def main(): if len(sys.argv) != 2: print >>sys.stderr, 'Usage: %s ' % sys.argv[0] sys.exit(1) context = Context() context.process(sys.argv[1]) if __name__ == '__main__': main() # vim:ts=4 sw=4 et python-djvulibre-0.3.9/python_djvulibre.egg-info/0000755000000000000000000000000011731710327022051 5ustar rootroot00000000000000python-djvulibre-0.3.9/python_djvulibre.egg-info/top_level.txt0000644000000000000000000000000511731710327024576 0ustar rootroot00000000000000djvu python-djvulibre-0.3.9/python_djvulibre.egg-info/SOURCES.txt0000644000000000000000000000165211731710327023741 0ustar rootroot00000000000000COPYING MANIFEST.in setup.py djvu/__init__.py djvu/common.pxi djvu/const.py djvu/decode.pxd djvu/decode.pyx djvu/dllpath.py djvu/sexpr.pxd djvu/sexpr.pyx doc/changelog doc/credits.txt doc/source/annotations.txt doc/source/conf.py doc/source/documents.txt doc/source/event-model.txt doc/source/exceptions.txt doc/source/expressions.txt doc/source/files.txt doc/source/geometry.txt doc/source/index.txt doc/source/messages.txt doc/source/outline.txt doc/source/pages.txt doc/source/text-zones.txt examples/djvu-crop-text examples/djvu-dump-text examples/djvu2png python_djvulibre.egg-info/PKG-INFO python_djvulibre.egg-info/SOURCES.txt python_djvulibre.egg-info/dependency_links.txt python_djvulibre.egg-info/top_level.txt tests/common.py tests/sexpr-gc.py tests/test_const.py tests/test_decode.py tests/test_sexpr.py tests/images/Makefile tests/images/test0-image.jpeg tests/images/test0.djvu tests/images/test0.tex tests/images/test1.djvupython-djvulibre-0.3.9/python_djvulibre.egg-info/dependency_links.txt0000644000000000000000000000000111731710327026117 0ustar rootroot00000000000000 python-djvulibre-0.3.9/python_djvulibre.egg-info/PKG-INFO0000644000000000000000000000210111731710327023140 0ustar rootroot00000000000000Metadata-Version: 1.1 Name: python-djvulibre Version: 0.3.9 Summary: Python support for the DjVu image format Home-page: http://jwilk.net/software/python-djvulibre Author: Jakub Wilk Author-email: jwilk@jwilk.net License: GNU GPL 2 Description: *python-djvulibre* is a set of `Python `_ bindings for the `DjVuLibre `_ library, an open source implementation of `DjVu `_. Platform: all Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows :: Windows 95/98/2000 Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000 Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Multimedia :: Graphics Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion Classifier: Topic :: Text Processing python-djvulibre-0.3.9/tests/0000755000000000000000000000000011731710327016132 5ustar rootroot00000000000000python-djvulibre-0.3.9/tests/common.py0000644000000000000000000001071611731705500017776 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2010-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. from __future__ import with_statement import contextlib import locale import os import re import subprocess as ipc import sys from nose.tools import * from nose import SkipTest try: assert_multi_line_equal except NameError: assert_multi_line_equal = assert_equal else: try: assert_multi_line_equal.im_class.maxDiff = None except AttributeError: pass # FIXME: How to do it Python 3? try: locale.LC_MESSAGES except AttributeError: # A non-POSIX system. locale.LC_MESSAGES = locale.LC_ALL locale_encoding = locale.getpreferredencoding() if locale_encoding == 'ANSI_X3.4-1968': locale_encoding = 'UTF-8' py3k = sys.version_info >= (3, 0) if py3k: def u(s): return s else: def u(s): return s.decode('UTF-8') if py3k: def b(s): return s.encode('UTF-8') else: def b(s): return s if py3k: def L(i): return i else: def L(i): return long(i) if py3k: def cmp(x, y): if x == y: return 0 if x < y: return -1 if x > y: return 1 assert 0 if py3k: def blob(*args): return bytes(args) else: def blob(*args): return ''.join(map(chr, args)) if py3k: from io import StringIO else: from cStringIO import StringIO if py3k: unicode = str if py3k: maxsize = sys.maxsize else: maxsize = sys.maxint @contextlib.contextmanager def interim(obj, **override): copy = dict((key, getattr(obj, key)) for key in override) for key, value in override.items(): setattr(obj, key, value) try: yield finally: for key, value in copy.items(): setattr(obj, key, value) @contextlib.contextmanager def raises(exc_type, string=None, regex=None): if string is None and regex is None: string = '' # XXX assert (string is None) ^ (regex is None) try: yield None except exc_type: _, exc, _ = sys.exc_info() exc_string = str(exc) if string is not None: if string != exc_string: message = '%r != %r' % (exc_string, string) raise AssertionError(message) else: if not re.match(regex, exc_string): message = '%r !~ %r' % (exc_string, regex) raise AssertionError(message) else: message = '%s was not raised' % exc_type.__name__ raise AssertionError(message) @contextlib.contextmanager def amended_locale(**kwargs): old_locale = locale.setlocale(locale.LC_ALL) try: for category, value in kwargs.items(): category = getattr(locale, category) try: locale.setlocale(category, value) except locale.Error: _, exc, _ = sys.exc_info() raise SkipTest(exc) yield finally: locale.setlocale(locale.LC_ALL, old_locale) def assert_repr(self, expected): return assert_equal(repr(self), expected) def skip_unless_c_messages(): if locale.setlocale(locale.LC_MESSAGES) != 'C': raise SkipTest('you need to run this test with LC_MESSAGES=C') if os.getenv('LANGUAGE', '') != '': raise SkipTest('you need to run this test with LANGUAGE unset') def skip_unless_translation_exists(lang): messages = {} langs = ['C', lang] for lang in langs: with amended_locale(LC_ALL=lang): try: open(__file__ + '/') except EnvironmentError: _, exc, _ = sys.exc_info() messages[lang] = str(exc) messages = set(messages.values()) assert 1 <= len(messages) <= 2 if len(messages) == 1: raise SkipTest('libc translation not found: ' + lang) def skip_unless_command_exists(command): child = ipc.Popen('command -v ' + command, shell=True, stdout=ipc.PIPE, stderr=ipc.PIPE) if child.wait() == 0: return raise SkipTest('command not found: ' + command) # vim:ts=4 sw=4 et python-djvulibre-0.3.9/tests/sexpr-gc.py0000644000000000000000000000227211731461022020232 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2007-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. if __name__ != '__main__': raise ImportError('This module is not intended for import') from djvu.sexpr import * from os import getpid PROC_STATUS = '/proc/%d/status' % getpid() SCALE = dict(kB = 1024.0) def mem_info(key = 'VmSize'): try: file = open(PROC_STATUS) for line in file: if line.startswith('%(key)s:' % locals()): _, value, unit = line.split(None, 3) return float(value) * SCALE[unit] finally: file.close() STEP = 1 << 17 try: range = xrange except NameError: pass n = 0 while True: print('%.2fM' % mem_info()) [Expression(4) for i in range(STEP)] break # vim:ts=4 sw=4 et python-djvulibre-0.3.9/tests/test_decode.py0000644000000000000000000010074011731641112020763 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2007-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. from __future__ import with_statement import array import os import shutil import subprocess as ipc import tempfile from djvu.decode import * from djvu.sexpr import * from common import * images = os.path.join(os.path.dirname(__file__), 'images', '') def create_djvu(commands='', sexpr=''): skip_unless_command_exists('djvused') if sexpr: commands += '\nset-ant\n%s\n.\n' % sexpr file = tempfile.NamedTemporaryFile(prefix='test', suffix='djvu') file.seek(0) file.write(blob( 0x41, 0x54, 0x26, 0x54, 0x46, 0x4f, 0x52, 0x4d, 0x00, 0x00, 0x00, 0x22, 0x44, 0x4a, 0x56, 0x55, 0x49, 0x4e, 0x46, 0x4f, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x01, 0x18, 0x00, 0x2c, 0x01, 0x16, 0x01, 0x53, 0x6a, 0x62, 0x7a, 0x00, 0x00, 0x00, 0x04, 0xbc, 0x73, 0x1b, 0xd7, )) file.flush() djvused = ipc.Popen(['djvused', '-s', file.name], stdin=ipc.PIPE, stdout=ipc.PIPE, stderr=ipc.PIPE) djvused.stdin.write(commands.encode(locale_encoding)) djvused.stdin.close() assert_equal(djvused.wait(), 0) assert_equal(djvused.stdout.read(), ''.encode(locale_encoding)) assert_equal(djvused.stderr.read(), ''.encode(locale_encoding)) return file def test_context_cache(): context = Context() assert_equal(context.cache_size, 10 << 20) for n in -100, 0, 1 << 31: with raises(ValueError, '0 < cache_size < (2 ** 31) must be satisfied'): context.cache_size = n with raises(ValueError, '0 < cache_size < (2 ** 31) must be satisfied'): context.cache_size = 0 n = 1 while n < (1 << 31): context.cache_size = n assert_equal(context.cache_size, n) n = (n + 1) * 2 - 1 context.clear_cache() class test_documents: def test_bad_new(self): with raises(TypeError, "cannot create 'djvu.decode.Document' instances"): Document() def test_nonexistent(self): skip_unless_c_messages() context = Context() with raises(JobFailed): document = context.new_document(FileUri('__nonexistent__')) message = context.get_message() assert_equal(type(message), ErrorMessage) assert_equal(type(message.message), unicode) assert_equal(message.message, "[1-11711] Failed to open '__nonexistent__': No such file or directory.") assert_equal(str(message), message.message) assert_equal(unicode(message), message.message) def test_nonexistent_ja(self): skip_unless_c_messages() skip_unless_translation_exists('ja_JP.UTF-8') context = Context() with amended_locale(LC_ALL='ja_JP.UTF-8'): with raises(JobFailed): document = context.new_document(FileUri('__nonexistent__')) message = context.get_message() assert_equal(type(message), ErrorMessage) assert_equal(type(message.message), unicode) assert_equal(message.message, u("[1-11711] Failed to open '__nonexistent__': そのようなファイルやディレクトリはありません.")) assert_equal(str(message), "[1-11711] Failed to open '__nonexistent__': そのようなファイルやディレクトリはありません.") assert_equal(unicode(message), message.message) def test_new_document(self): context = Context() document = context.new_document(FileUri(images + 'test1.djvu')) assert_equal(type(document), Document) message = document.get_message() assert_equal(type(message), DocInfoMessage) assert_true(document.decoding_done) assert_false(document.decoding_error) assert_equal(document.decoding_status, JobOK) assert_equal(document.type, DOCUMENT_TYPE_SINGLE_PAGE) assert_equal(len(document.pages), 1) assert_equal(len(document.files), 1) decoding_job = document.decoding_job assert_true(decoding_job.is_done) assert_false(decoding_job.is_error) assert_equal(decoding_job.status, JobOK) file = document.files[0] assert_true(type(file) is File) assert_true(file.document is document) assert_true(file.get_info() is None) assert_equal(file.type, 'P') assert_equal(file.n_page, 0) page = file.page assert_equal(type(page), Page) assert_true(page.document is document) assert_equal(page.n, 0) assert_true(file.size is None) assert_equal(file.id, u('test1.djvu')) assert_equal(type(file.id), unicode) assert_equal(file.name, u('test1.djvu')) assert_equal(type(file.name), unicode) assert_equal(file.title, u('test1.djvu')) assert_equal(type(file.title), unicode) dump = document.files[0].dump assert_equal(type(dump), unicode) assert_equal( [line for line in dump.splitlines()], [ u(' FORM:DJVU [83] '), u(' INFO [10] DjVu 64x48, v24, 300 dpi, gamma=2.2'), u(' Sjbz [53] JB2 bilevel data'), ] ) page = document.pages[0] assert_equal(type(page), Page) assert_true(page.document is document) assert_true(page.get_info() is None) assert_equal(page.width, 64) assert_equal(page.height, 48) assert_equal(page.size, (64, 48)) assert_equal(page.dpi, 300) assert_equal(page.rotation, 0) assert_equal(page.version, 24) file = page.file assert_equal(type(file), File) assert_equal(file.id, u('test1.djvu')) assert_equal(type(file.id), unicode) dump = document.files[0].dump assert_equal(type(dump), unicode) assert_equal( [line for line in dump.splitlines()], [ u(' FORM:DJVU [83] '), u(' INFO [10] DjVu 64x48, v24, 300 dpi, gamma=2.2'), u(' Sjbz [53] JB2 bilevel data'), ] ) assert_true(document.get_message(wait=False) is None) assert_true(context.get_message(wait=False) is None) with raises(IndexError, 'file number out of range'): document.files[-1].get_info() assert_true(document.get_message(wait=False) is None) assert_true(context.get_message(wait=False) is None) with raises(IndexError, 'page number out of range'): document.pages[-1] with raises(IndexError, 'page number out of range'): document.pages[1] assert_true(document.get_message(wait=False) is None) assert_true(context.get_message(wait=False) is None) def test_save(self): skip_unless_command_exists('djvudump') context = Context() original_filename = images + 'test0.djvu' document = context.new_document(FileUri(original_filename)) message = document.get_message() assert_equal(type(message), DocInfoMessage) assert_true(document.decoding_done) assert_false(document.decoding_error) assert_equal(document.decoding_status, JobOK) assert_equal(document.type, DOCUMENT_TYPE_BUNDLED) assert_equal(len(document.pages), 6) assert_equal(len(document.files), 7) stdout0, stderr0 = ipc.Popen(['djvudump', original_filename], stdout=ipc.PIPE, stderr=ipc.PIPE, env={}).communicate() assert_equal(stderr0, b('')) stdout0 = stdout0.replace(b('\r\n'), b('\n')) tmpdir = tempfile.mkdtemp() try: tmp = open(os.path.join(tmpdir, 'tmp.djvu'), 'wb') job = document.save(tmp) assert_equal(type(job), SaveJob) assert_true(job.is_done) assert_false(job.is_error) tmp.close() stdout, stderr = ipc.Popen(['djvudump', tmp.name], stdout=ipc.PIPE, stderr=ipc.PIPE, env={}).communicate() assert_equal(stderr, b('')) stdout = stdout.replace(b('\r\n'), b('\n')) assert_equal(stdout, stdout0) finally: shutil.rmtree(tmpdir) tmp = None tmpdir = tempfile.mkdtemp() try: tmp = open(os.path.join(tmpdir, 'tmp.djvu'), 'wb') job = document.save(tmp, pages=(0,)) assert_equal(type(job), SaveJob) assert_true(job.is_done) assert_false(job.is_error) tmp.close() stdout, stderr = ipc.Popen(['djvudump', tmp.name], stdout=ipc.PIPE, stderr=ipc.PIPE, env={}).communicate() assert_equal(stderr, b('')) stdout = stdout.replace(b('\r\n'), b('\n')) stdout0 = stdout0.split(b('\n')) stdout = stdout.split(b('\n')) stdout[4] = stdout[4].replace(b(' (1)'), b('')) assert_equal(len(stdout), 10) assert_equal(stdout[3:-1], stdout0[4:10]) assert_equal(stdout[-1], b('')) finally: shutil.rmtree(tmpdir) tmp = None tmpdir = tempfile.mkdtemp() try: tmpfname = os.path.join(tmpdir, 'index.djvu') job = document.save(indirect=tmpfname) assert_equal(type(job), SaveJob) assert_true(job.is_done) assert_false(job.is_error) stdout, stderr = ipc.Popen(['djvudump', tmpfname], stdout=ipc.PIPE, stderr=ipc.PIPE, env={}).communicate() assert_equal(stderr, b('')) stdout = stdout.replace(b('\r\n'), b('\n')) stdout = stdout.split(b('\n')) stdout0 = ( [b(' shared_anno.iff -> shared_anno.iff')] + [b(' p%04d.djvu -> p%04d.djvu' % (n, n)) for n in range(1, 7)] ) assert_equal(len(stdout), 11) assert_equal(stdout[2:-2], stdout0) assert_equal(stdout[-1], b('')) finally: shutil.rmtree(tmpdir) tmpdir = tempfile.mkdtemp() try: tmpfname = os.path.join(tmpdir, 'index.djvu') job = document.save(indirect=tmpfname, pages=(0,)) assert_equal(type(job), SaveJob) assert_true(job.is_done) assert_false(job.is_error) stdout, stderr = ipc.Popen(['djvudump', tmpfname], stdout=ipc.PIPE, stderr=ipc.PIPE, env={}).communicate() stdout = stdout.replace(b('\r\n'), b('\n')) assert_equal(stderr, b('')) stdout = stdout.split(b('\n')) assert_equal(len(stdout), 5) assert_equal(stdout[2], b(' shared_anno.iff -> shared_anno.iff')) assert_equal(stdout[3], b(' p0001.djvu -> p0001.djvu')) assert_equal(stdout[-1], b('')) finally: shutil.rmtree(tmpdir) def test_export_ps(self): skip_unless_command_exists('ps2ascii') context = Context() document = context.new_document(FileUri(images + 'test0.djvu')) message = document.get_message() assert_equal(type(message), DocInfoMessage) assert_true(document.decoding_done) assert_false(document.decoding_error) assert_equal(document.decoding_status, JobOK) assert_equal(document.type, DOCUMENT_TYPE_BUNDLED) assert_equal(len(document.pages), 6) assert_equal(len(document.files), 7) tmp = tempfile.NamedTemporaryFile() try: job = document.export_ps(tmp.file) assert_equal(type(job), SaveJob) assert_true(job.is_done) assert_false(job.is_error) stdout, stderr = ipc.Popen(['ps2ascii', tmp.name], stdout=ipc.PIPE, stderr=ipc.PIPE, env={}).communicate() assert_equal(stderr, b('')) assert_equal(stdout, b('\x0c') * 6) finally: tmp.close() tmp = tempfile.NamedTemporaryFile() try: job = document.export_ps(tmp.file, pages=(2,), text=True) assert_equal(type(job), SaveJob) assert_true(job.is_done) assert_false(job.is_error) stdout, stderr = ipc.Popen(['ps2ascii', tmp.name], stdout=ipc.PIPE, stderr=ipc.PIPE, env={}).communicate() assert_equal(stderr, b('')) stdout = stdout.split(b('\n')) stdout = [b(' ').join(line.split()) for line in stdout] assert_equal(stdout, [ b(''), b(''), b('3C'), b('red green blue cyan magenta yellow'), b(''), b('red green blue cyan magenta yellow'), b(''), b('3'), ]) finally: del tmp class test_pixel_formats(): def test_bad_new(self): with raises(TypeError, "cannot create 'djvu.decode.PixelFormat' instances"): PixelFormat() def test_rgb(self): pf = PixelFormatRgb() assert_equal(repr(pf), "djvu.decode.PixelFormatRgb(byte_order = 'RGB', bpp = 24)") pf = PixelFormatRgb('RGB') assert_equal(repr(pf), "djvu.decode.PixelFormatRgb(byte_order = 'RGB', bpp = 24)") pf = PixelFormatRgb('BGR') assert_equal(repr(pf), "djvu.decode.PixelFormatRgb(byte_order = 'BGR', bpp = 24)") def test_rgb_mask(self): pf = PixelFormatRgbMask(0xff, 0xf00, 0x1f000, 0, 16) assert_equal(repr(pf), "djvu.decode.PixelFormatRgbMask(red_mask = 0x00ff, green_mask = 0x0f00, blue_mask = 0xf000, xor_value = 0x0000, bpp = 16)") pf = PixelFormatRgbMask(0xff000000, 0xff0000, 0xff00, 0xff, 32) assert_equal(repr(pf), "djvu.decode.PixelFormatRgbMask(red_mask = 0xff000000, green_mask = 0x00ff0000, blue_mask = 0x0000ff00, xor_value = 0x000000ff, bpp = 32)") def test_grey(self): pf = PixelFormatGrey() assert_equal(repr(pf), "djvu.decode.PixelFormatGrey(bpp = 8)") def test_palette(self): with raises(KeyError, repr((0, 0, 0))): pf = PixelFormatPalette({}) data = dict(((i, j, k), i + 7 * j + 37 + k) for i in range(6) for j in range(6) for k in range(6)) pf = PixelFormatPalette(data) data_repr = ', '.join('%r: 0x%02x' % (k, v) for k, v in sorted(data.items())) assert_equal(repr(pf), "djvu.decode.PixelFormatPalette({%s}, bpp = 8)" % data_repr) def test_packed_bits(self): pf = PixelFormatPackedBits('<') assert_equal(repr(pf), "djvu.decode.PixelFormatPackedBits('<')") assert_equal(pf.bpp, 1) pf = PixelFormatPackedBits('>') assert_equal(repr(pf), "djvu.decode.PixelFormatPackedBits('>')") assert_equal(pf.bpp, 1) class test_page_jobs(): def test_bad_new(self): with raises(TypeError, "cannot create 'djvu.decode.PageJob' instances"): PageJob() def test_decode(self): context = Context() document = context.new_document(FileUri(images + 'test1.djvu')) message = document.get_message() assert_equal(type(message), DocInfoMessage) page_job = document.pages[0].decode() assert_true(page_job.is_done) assert_equal(type(page_job), PageJob) assert_true(page_job.is_done) assert_false(page_job.is_error) assert_equal(page_job.status, JobOK) assert_equal(page_job.width, 64) assert_equal(page_job.height, 48) assert_equal(page_job.size, (64, 48)) assert_equal(page_job.dpi, 300) assert_equal(page_job.gamma, 2.2) assert_equal(page_job.version, 24) assert_equal(page_job.type, PAGE_TYPE_BITONAL) assert_equal((page_job.rotation, page_job.initial_rotation), (0, 0)) with raises(ValueError, 'rotation must be equal to 0, 90, 180, or 270'): page_job.rotation = 100 page_job.rotation = 180 assert_equal((page_job.rotation, page_job.initial_rotation), (180, 0)) del page_job.rotation assert_equal((page_job.rotation, page_job.initial_rotation), (0, 0)) with raises(ValueError, 'page_rect width/height must be a positive integer'): page_job.render(RENDER_COLOR, (0, 0, -1, -1), (0, 0, 10, 10), PixelFormatRgb()) with raises(ValueError, 'render_rect width/height must be a positive integer'): page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, -1, -1), PixelFormatRgb()) with raises(ValueError, 'render_rect must be inside page_rect'): page_job.render(RENDER_COLOR, (0, 0, 10, 10), (2, 2, 10, 10), PixelFormatRgb()) with raises(ValueError, 'row_alignment must be a positive integer'): page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 10, 10), PixelFormatRgb(), -1) with raises(MemoryError, regex='^Unable to allocate [0-9]+ bytes for an image memory$'): x = int((maxsize//2) ** 0.5) page_job.render(RENDER_COLOR, (0, 0, x, x), (0, 0, x, x), PixelFormatRgb(), 8) s = page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 4, 4), PixelFormatGrey(), 1) assert_equal(s, blob(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xa4, 0xff, 0xff, 0xff, 0xb8)) buffer = array.array('B', blob(0)) with raises(ValueError, 'Image buffer is too small (16 > 1)'): page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 4, 4), PixelFormatGrey(), 1, buffer) buffer = array.array('B', blob(*([0] * 16))) assert_true(page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 4, 4), PixelFormatGrey(), 1, buffer) is buffer) s = buffer.tostring() assert_equal(s, blob(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xa4, 0xff, 0xff, 0xff, 0xb8)) class test_thumbnails: def test(self): context = Context() document = context.new_document(FileUri(images + 'test1.djvu')) message = document.get_message() assert_equal(type(message), DocInfoMessage) thumbnail = document.pages[0].thumbnail assert_equal(thumbnail.status, JobOK) assert_equal(thumbnail.calculate(), JobOK) message = document.get_message() assert_equal(type(message), ThumbnailMessage) assert_equal(message.thumbnail.page.n, 0) (w, h, r), pixels = thumbnail.render((5, 5), PixelFormatGrey(), dry_run=True) assert_equal((w, h, r), (5, 3, 5)) assert_true(pixels is None) (w, h, r), pixels = thumbnail.render((5, 5), PixelFormatGrey()) assert_equal((w, h, r), (5, 3, 5)) assert_equal(pixels[:15], blob(0xff, 0xeb, 0xa7, 0xf2, 0xff, 0xff, 0xbf, 0x86, 0xbe, 0xff, 0xff, 0xe7, 0xd6, 0xe7, 0xff)) buffer = array.array('B', blob(0)) with raises(ValueError, 'Image buffer is too small (25 > 1)'): (w, h, r), pixels = thumbnail.render((5, 5), PixelFormatGrey(), buffer=buffer) buffer = array.array('B', blob(*([0] * 25))) (w, h, r), pixels = thumbnail.render((5, 5), PixelFormatGrey(), buffer=buffer) assert_true(pixels is buffer) s = buffer[:15].tostring() assert_equal(s, blob(0xff, 0xeb, 0xa7, 0xf2, 0xff, 0xff, 0xbf, 0x86, 0xbe, 0xff, 0xff, 0xe7, 0xd6, 0xe7, 0xff)) def test_jobs(): with raises(TypeError, "cannot create 'djvu.decode.Job' instances"): Job() with raises(TypeError, "cannot create 'djvu.decode.DocumentDecodingJob' instances"): DocumentDecodingJob() class test_affine_transforms(): def test_bad_args(self): with raises(ValueError, 'need more than 2 values to unpack'): AffineTransform((1, 2), (3, 4, 5)) def test1(self): af = AffineTransform((0, 0, 10, 10), (17, 42, 42, 100)) assert_equal(type(af), AffineTransform) assert_equal(af((0, 0)), (17, 42)) assert_equal(af((0, 10)), (17, 142)) assert_equal(af((10, 0)), (59, 42)) assert_equal(af((10, 10)), (59, 142)) assert_equal(af((0, 0, 10, 10)), (17, 42, 42, 100)) assert_equal(af(x for x in (0, 0, 10, 10)), (17, 42, 42, 100)) assert_equal(af.apply((123, 456)), af((123, 456))) assert_equal(af.apply((12, 34, 56, 78)), af((12, 34, 56, 78))) assert_equal(af.inverse((17, 42)), (0, 0)) assert_equal(af.inverse((17, 142)), (0, 10)) assert_equal(af.inverse((59, 42)), (10, 0)) assert_equal(af.inverse((59, 142)), (10, 10)) assert_equal(af.inverse((17, 42, 42, 100)), (0, 0, 10, 10)) assert_equal(af.inverse(x for x in (17, 42, 42, 100)), (0, 0, 10, 10)) assert_equal(af.inverse(af((234, 567))), (234, 567)) assert_equal(af.inverse(af((23, 45, 67, 78))), (23, 45, 67, 78)) class test_messages(): def test_bad_new(self): with raises(TypeError, "cannot create 'djvu.decode.Message' instances"): Message() class test_streams: def test_bad_new(self): with raises(TypeError, "Argument 'document' has incorrect type (expected djvu.decode.Document, got NoneType)"): Stream(None, 42) def test(self): context = Context() document = context.new_document('dummy://dummy.djvu') message = document.get_message() assert_equal(type(message), NewStreamMessage) assert_equal(message.name, 'dummy.djvu') assert_equal(message.uri, 'dummy://dummy.djvu') assert_equal(type(message.stream), Stream) with raises(NotAvailable): document.outline.sexpr with raises(NotAvailable): document.annotations.sexpr with raises(NotAvailable): document.pages[0].text.sexpr with raises(NotAvailable): document.pages[0].annotations.sexpr try: message.stream.write(open(images + 'test1.djvu', 'rb').read()) finally: message.stream.close() with raises(IOError, 'I/O operation on closed file'): message.stream.write(b('eggs')) message = document.get_message() assert_equal(type(message), DocInfoMessage) outline = document.outline outline.wait() x = outline.sexpr assert_equal(x, Expression([])) anno = document.annotations anno.wait() x = anno.sexpr assert_equal(x, Expression([])) text = document.pages[0].text text.wait() x = text.sexpr assert_equal(x, Expression([])) anno = document.pages[0].annotations anno.wait() x = anno.sexpr assert_equal(x, Expression([])) def test_metadata(): model_metadata = { 'English': 'eggs', u('Русский'): u('яйца'), } test_script = 'set-meta\n%s\n.\n' % '\n'.join('|%s| %s' % (k, v) for k, v in model_metadata.items()) try: test_file = create_djvu(test_script) except UnicodeEncodeError: raise AssertionError('You need to run this test with an UTF-8 locale') try: context = Context() document = context.new_document(FileUri(test_file.name)) message = document.get_message() assert_equal(type(message), DocInfoMessage) annotations = document.annotations assert_equal(type(annotations), DocumentAnnotations) annotations.wait() metadata = annotations.metadata assert_equal(type(metadata), Metadata) assert_equal(len(metadata), len(model_metadata)) assert_equal(sorted(metadata), sorted(model_metadata)) if not py3k: assert_equal(sorted(metadata.iterkeys()), sorted(model_metadata.iterkeys())) assert_equal(sorted(metadata.keys()), sorted(model_metadata.keys())) if not py3k: assert_equal(sorted(metadata.itervalues()), sorted(model_metadata.itervalues())) assert_equal(sorted(metadata.values()), sorted(model_metadata.values())) if not py3k: assert_equal(sorted(metadata.iteritems()), sorted(model_metadata.iteritems())) assert_equal(sorted(metadata.items()), sorted(model_metadata.items())) for k in metadata: assert_equal(type(k), unicode) assert_equal(type(metadata[k]), unicode) for k in None, 42, '+'.join(model_metadata): with raises(KeyError, repr(k)): metadata[k] finally: test_file.close() class test_sexpr: def test(self): context = Context() document = context.new_document(FileUri(images + 'test0.djvu')) assert_equal(type(document), Document) message = document.get_message() assert_equal(type(message), DocInfoMessage) anno = DocumentAnnotations(document, shared=False) assert_equal(type(anno), DocumentAnnotations) anno.wait() x = anno.sexpr assert_equal(x, Expression([])) anno = document.annotations assert_equal(type(anno), DocumentAnnotations) anno.wait() assert_true(anno.background_color is None) assert_true(anno.horizontal_align is None) assert_true(anno.vertical_align is None) assert_true(anno.mode is None) assert_true(anno.zoom is None) x = anno.sexpr assert_equal(repr(x), r"""Expression(((Symbol('metadata'), (Symbol('ModDate'), '2010-06-24 01:17:29+02:00'), (Symbol('CreationDate'), '2010-06-24 01:17:29+02:00'), (Symbol('Producer'), 'pdfTeX-1.40.10'), (Symbol('Creator'), 'LaTeX with hyperref package'), (Symbol('Author'), 'Jakub Wilk')), (Symbol('xmp'), 'Jakub Wilkimage/vnd.djvupdfTeX-1.40.10LaTeX with hyperref package2010-06-24T01:17:29+02:002010-06-24T01:17:29+02:002010-06-23T23:17:36+00:00\n')))""") metadata = anno.metadata assert_equal(type(metadata), Metadata) hyperlinks = anno.hyperlinks assert_equal(type(hyperlinks), Hyperlinks) assert_equal(len(hyperlinks), 0) assert_equal(list(hyperlinks), []) outline = document.outline assert_equal(type(outline), DocumentOutline) outline.wait() x = outline.sexpr assert_equal(repr(x), r"""Expression((Symbol('bookmarks'), ('A', '#p0001.djvu'), ('B', '#p0002.djvu'), ('C', '#p0003.djvu'), ('D', '#p0004.djvu'), ('E', '#p0005.djvu', ('E1', '#p0005.djvu'), ('E2', '#p0005.djvu')), ('F', '#p0006.djvu')))""") page = document.pages[4] anno = page.annotations assert_equal(type(anno), PageAnnotations) anno.wait() assert_true(anno.background_color is None) assert_true(anno.horizontal_align is None) assert_true(anno.vertical_align is None) assert_true(anno.mode is None) assert_true(anno.zoom is None) x = anno.sexpr assert_equal(repr(x), r"""Expression(((Symbol('metadata'), (Symbol('ModDate'), '2010-06-24 01:17:29+02:00'), (Symbol('CreationDate'), '2010-06-24 01:17:29+02:00'), (Symbol('Producer'), 'pdfTeX-1.40.10'), (Symbol('Creator'), 'LaTeX with hyperref package'), (Symbol('Author'), 'Jakub Wilk')), (Symbol('xmp'), 'Jakub Wilkimage/vnd.djvupdfTeX-1.40.10LaTeX with hyperref package2010-06-24T01:17:29+02:002010-06-24T01:17:29+02:002010-06-23T23:17:36+00:00\n'), (Symbol('maparea'), '#p0002.djvu', '', (Symbol('rect'), 587, 2346, 60, 79), (Symbol('border'), Symbol('#ff0000'))), (Symbol('maparea'), 'http://jwilk.net/', '', (Symbol('rect'), 458, 1910, 1061, 93), (Symbol('border'), Symbol('#00ffff')))))""") page_metadata = anno.metadata assert_equal(type(page_metadata), Metadata) assert_equal(page_metadata.keys(), metadata.keys()) assert_equal([page_metadata[k] == metadata[k] for k in metadata], [True, True, True, True, True]) hyperlinks = anno.hyperlinks assert_equal(type(hyperlinks), Hyperlinks) assert_equal(len(hyperlinks), 2) assert_equal(repr(list(hyperlinks)), r"""[Expression((Symbol('maparea'), '#p0002.djvu', '', (Symbol('rect'), 587, 2346, 60, 79), (Symbol('border'), Symbol('#ff0000')))), Expression((Symbol('maparea'), 'http://jwilk.net/', '', (Symbol('rect'), 458, 1910, 1061, 93), (Symbol('border'), Symbol('#00ffff'))))]""") text = page.text assert_equal(type(text), PageText) text.wait() text_s = text.sexpr text_s_detail = [PageText(page, details).sexpr for details in (TEXT_DETAILS_PAGE, TEXT_DETAILS_COLUMN, TEXT_DETAILS_REGION, TEXT_DETAILS_PARAGRAPH, TEXT_DETAILS_LINE, TEXT_DETAILS_WORD, TEXT_DETAILS_CHARACTER, TEXT_DETAILS_ALL)] if py3k: # Representations of expression are slightly different in Python ≥ 3.0. def m(s): return s.replace(r'\xe2\x86\x92', r'→') else: def m(s): return s assert_equal(text_s_detail[0], text_s_detail[1]) assert_equal(text_s_detail[1], text_s_detail[2]) assert_equal(text_s_detail[2], text_s_detail[3]) assert_equal(repr(text_s_detail[0]), m(r"""Expression((Symbol('page'), 0, 0, 2550, 3300, '5E \n5.1 E1 \n\xe2\x86\x921 \n5.2 E2 \nhttp://jwilk.net/ \n5 \n'))""")) assert_equal(repr(text_s_detail[4]), m(r"""Expression((Symbol('page'), 0, 0, 2550, 3300, (Symbol('line'), 462, 2726, 615, 2775, '5E '), (Symbol('line'), 462, 2544, 663, 2586, '5.1 E1 '), (Symbol('line'), 466, 2349, 631, 2421, '\xe2\x86\x921 '), (Symbol('line'), 462, 2124, 665, 2166, '5.2 E2 '), (Symbol('line'), 465, 1911, 1504, 2000, 'http://jwilk.net/ '), (Symbol('line'), 1259, 374, 1280, 409, '5 ')))""")) assert_true(text_s_detail[5] == text_s) assert_true(text_s_detail[6] == text_s) assert_true(text_s_detail[7] == text_s) assert_equal(repr(text_s), m(r"""Expression((Symbol('page'), 0, 0, 2550, 3300, (Symbol('line'), 462, 2726, 615, 2775, (Symbol('word'), 462, 2726, 615, 2775, '5E')), (Symbol('line'), 462, 2544, 663, 2586, (Symbol('word'), 462, 2544, 533, 2586, '5.1'), (Symbol('word'), 596, 2545, 663, 2586, 'E1')), (Symbol('line'), 466, 2349, 631, 2421, (Symbol('word'), 466, 2349, 631, 2421, '\xe2\x86\x921')), (Symbol('line'), 462, 2124, 665, 2166, (Symbol('word'), 462, 2124, 535, 2166, '5.2'), (Symbol('word'), 596, 2125, 665, 2166, 'E2')), (Symbol('line'), 465, 1911, 1504, 2000, (Symbol('word'), 465, 1911, 1504, 2000, 'http://jwilk.net/')), (Symbol('line'), 1259, 374, 1280, 409, (Symbol('word'), 1259, 374, 1280, 409, '5'))))""")) with raises(TypeError, 'details must be a symbol or none'): PageText(page, 'eggs') with raises(ValueError, 'details must be equal to TEXT_DETAILS_PAGE, or TEXT_DETAILS_COLUMN, or TEXT_DETAILS_REGION, or TEXT_DETAILS_PARAGRAPH, or TEXT_DETAILS_LINE, or TEXT_DETAILS_WORD, or TEXT_DETAILS_CHARACTER or TEXT_DETAILS_ALL'): PageText(page, Symbol('eggs')) # vim:ts=4 sw=4 et python-djvulibre-0.3.9/tests/images/0000755000000000000000000000000011731710327017377 5ustar rootroot00000000000000python-djvulibre-0.3.9/tests/images/test1.djvu0000644000000000000000000000013711514565551021340 0ustar rootroot00000000000000AT&TFORMSDJVUINFO @0,Sjbz5kf#S0+sr;v-UKåeM>#nZq6C}7python-djvulibre-0.3.9/tests/images/test0.djvu0000644000000000000000000007100311720004011021311 0ustar rootroot00000000000000AT&TFORMqDJVMDIRMdX^#-:1)lȨ nB&+o%[{Sթf?}IXnNAVM6bDW]2YѝɂwJMpRݔFORMDJVIANTz|K'8܇C?lW6=VkW8꼨9'-&鈗zbR@3 JH c88AS&ق|r_Ar|#XӠR'mBѠVŰqI 7 fYMϹR WJ"VHm1X?jtz陸Gn2:J$dDMg#_W7ݐ#rӉnE:5ι<;U*?RaNT 3w "2*&WGawr9Vpv3՛kkr ӾfeJ3m.(FT&`\KӢK  G$K˪h rz*#ie-nǞ )E@,la$œQA+|]t#N;/6NK N )E!)>sBRN ͫ¸Z&I=(-daD~6䟊qhD"b궬2rV-g7yHw qHmGThQ6ɖvZzor,X\fڦҕK5w&r3!.& ,>9ޠSU(-8Me"&^Fso`|st|ϋ;`rqY }wH 4 @oGKy8{hl Xᙲy%:(4/7_%|JFwp'4Ĝ)=u#a" eQa% .^ p(Nd@bvؚUs݈K)pqݒY]zGKp7Bm;|#eB*!4چVG@N%f^3 Q~=t{m$jX*";fZ%c?*x pvWf@(1,5 A75g1E&~-: y7DᜳXg1DtݽH ~eKݣfҰg2n- ܷ"&_R.aW¦gX,d wvhixx=EDC. +j8bG kpS$:s=(eZeպ*{F?8oav.*eT*/y\EER6j[ 7`@: 4q/$ xpdx7d]w.r Y~n 低2>H=6 (uK:lO'v T.\߮51̚$/o>P ggih[RZaqs23 20Wxyڽ?ۑA>)F[E e .1_LklM2^0GǑoQ \gj|%-c"+6/nuy|< )WBwm)ZvjX襚31g#0u Q3:mL.v|ŜJL=.&?=qgOUgjL9>'pd(]+C&2ojDmn,%qoZ}ƌ*E):I#Dsf' (,Zr0؊ƢDq**7.*C[ ]BQD_>1qG` c2Y$GrC9X;!Alp@%; Il[sY=}C"A2ުek\\pޚƥzuv+(Y©,pȫ~/g-VpG0].uWr%'1ΚS[q9O͙F9hI6IaRݙB-¹r=k"H{>9;dF*9p`U>c=&m.TUMkKLAP..~\ Rs" I:>!5f -(@⧊a| ڥuJBƾշ߂a{zzwsg8K;u>^ n=In 55=/?5fW/{GSNlgύ>Yb 1kR+ v!)EƊ e%oݙ/?te^,ꏪC}ߚXKzVWLr*}f3z&h.Nkʙ?g\zMG~fīF .ʷ/s <p#hlkبF6ō uaΠM0l tZ[f-[Ds8VawG3F:1d"ox)F"ߛ*#x.&&*QS wA8*!_ӵ ЫrYb\$ 8NĔs8$&ߔ}3|^$=Dos s,_jG)E 8]K*UZ`/3KZ~M^s`%c&9t|sY|@-f'}+Nwf"!I#^Ú d6k$]s۲6a#0T[,lv~cė!vUMm[2yS3h+-I}(U7Oeb^~.EAz]zJ>%5x;$?9*齰C v*]HTF_J>O1o2 ]N ,[s@nk|aSSS ;hB(x=Cِ9;x$;kٝtv2ak|el(D8IN,(KV|ZƟ^;}rspgo|5dp}m T tɇτR[AJ}h0g)S/c7xAj\Pvw64 *o9,z=O/;DX\/'"}{h,>@uj?=}T!m(389x"+b.E>xeo/B'c=;o }Gm*aN/TQ`ɓҬIг\ċˋ4៧pyӕىb/6 HHor#{ht[aD)X[QJDrV# 0h} 88Y'%2C>Nt21%;ZA%r9ʕƿWJ.ў: ,}Y۔j}"#J{ #ͲSXQt|Z%A̷*4I3%2BezJx7ֆ$)kns,zNJcjf1@o㳲n-$K6\=>yBy?F'_gQe?ks=Up0xݏV4(# {̃q,dPm#tFmvl^S1ʳI*J Q8}:fJ`~`TOjq &)^l{VxLAʺH*/I0)`}J!<+iqB1O]"~bZFDX*dG*_~uc)"RZC suij[3Zʽ#mDaFCiJJ)n'aҪF#xQ ;3S_X$ic C?XDh(%'pޭTÙhu=:zTLd[/٥l*.]uKt/&2#ߏM74Z=2J LOˢxX}v`NX =^!^:쫗>1ȆFVկSY=42ivѼ_lYInUvɤޫyڋ+Pv Œ翷%c_Ţuy@{#7V4 A3m Fwy>[bB4.^ Sj2=7vU/>_p5t pHL uLjl*mn=' ɕ,5b~ :o_P 4Af&T͓ qr抸_/jx5]7Ǘ㱗TXTz\ZH5!{xWv5Y(s6 1vAܯZ(:hhU>4'0~e ?2\w{?զ.*;8vusUGIi/Kt@M); E 5x9L6ƅ ˥.XxthHFORM DJVUINFO ,INCLshared_anno.iffSjbz sՀ ˔cɌkRߓݺуmG+ߣ5d =c\ %J?oFFTW)};1?Šwn/iZq'L:YԱQUY|ޱdAdoOAlp=Q kg pTS6Cf"}9sXUNSbc' t5MP(+e6yG>cpuѼsؚo}dٔ꛷ٷ,259=p@q. ΐvN,AuR/p^B^&Nlaݟ8J; ) ,o)"zQ:-Wb/QC-KRB$G`XA 鯕MFWDa^ж[eKHKMz *x C\^ҘуY-׃a8,'t(&vJ۔:yGFDn~ڻ :K8Cs,58pN<ӗ}K ]Ji"R!$H)msyJdP2q&=35$1)o<&c7V5[ïG޻2Ϟ~DSTZVyex?z b -6B1 =qm /Πdb }|y|`QN+v$̳Z>3qWeNj*QA%Ha!n|D :{W2bϕ:mLd!tg"CCf*ZG7ВkFzE%h8PJ`- '(KPE }I9"=~RfrqܱU?7slb\%t{ិ}2>?e'/- 9}B%V W<իdm ZV>c-rUfI$0g-:e$ESG\yl+țԮJ4x#+Dh|vg$NuٓqMjv br|1*_v[RZ+Q`.ma gmG*F6j?յMt~'[s::2ad4Cߣwv#7} UZ6ѶJ@_9 -x XqhqB&jvf]6%RT l?XMC7߁Ŭ `zJuY n=UHq͛ ܩo8ڈ;1'7O-q$> DF0fPU H)e&J46}5 [sz_Z C?BG44Wa]?Az"5 dTXTzhZGYx[tE?2Cu'"g5TQUaqȣw" _MW6K[sg iyjn~3w˪Z"F7TLM7Am-+͕r(ȩaӑ "#Ra(J䧔IJhGs㴪-T)sFORM DJVUINFO ,INCLshared_anno.iffSjbzkՀv&TEjJI"\%a P!<^2t@JN,`.5tLu#wʮ.4gGW8n,8W]=gGގ,+>܈i0Τ-ݐHwC(z{eYav2_6ٚi:$OnyJ1볺#;֖fϮȐ]::fK<\;_\mռ^CMqji -znUߎ-^p:+@PecŇiO%³aXZv@rK=4Ltx~Bb!G&Ăej_Q^E#[!3^=΃.l۸J7]OΈѡ;5jHG\ؽtY0!Ƈg%g0TO6o܈8_L2UM]R~F\ &An{I)7S@?n\ nRpLtK\9h~,M?#t,5XsU" vStf&ƳNOVn(YTIT0i.pXA[E ; N~6%x!3xs x3uH)#qnOmLջBAƀbιi(ٿMp=ϟaO|o0Y&Y/JǵJ<jZJ}ס-\vN?5m͉Fy#Ɯ*w $epEEzwfq#ўKmPګ h CDp>ħN Niay,M[űY\4BʛY[bNK,stRDO˵O& Z>:X^f_ݯ&ҜmF ឈ}UG%S\77^G}lVxDi_Nrmhi>oCSnAOZ~,1-y4\l+|`x*~聎mڋTJp"-SqJig!x*Aox6kf4h%z_Dɭz˧.6K!-bpP&t>ϻgʥ+cYSD&wWeF":u`!s,{mAd}_=G?seXnqjȴtAl]|m`jy: <їu@)D]I vV5s4P WOy4r8Fc Zh,@VEliS3+{2%F=G XL@20_/=?FGbzʟ3BG44a}4ff[xZtRV NnEONI%(}4ff[xZtRV N4!?Z /Λg3~!gah^y!t< )? 0D7TXTz:ZHMX~ςnUR\#6aw7#46bbI C=݌KBLȆ>|T bW JYRL>;( ARZ82ɜa).!88X\ 3&pKq gygPGUyҸ.,l|rwSBK#_z1b-Ur u%kjoL*etT(ʈ3 8("Ԧ",@SG wxy?E~QK)WkG^ө@}Fn[=J6wKdҍ'ORHvUQ I᤾ҋq B饲py:-E !LT \lK̽؎Y܇Cf=r*歧]'^vA_+wM/Y`rԮ ^k {%=9k=neA7H6p>@{e2;vWx i0FORMzDJVUINFO ,INCLshared_anno.iffSjbz"Հb:1ȹZ;|d~Lhk,+'S:JJꓪ}{!Ҋ^pb5q}`nćɳ" @=Н\Ѕи&4gEi61bCN"l!qقmF<&lT&v6+~ru1,TX?B]PdlY[|zv8*8M CBl/+-c4: ܰPYmr;V6dQ$M2׀-Q.Rɓc6p*kw<$I|G V%gq01=c-%#(1 SI9y((qE&`F+_rep*d)likCf5|=5yI&w`N@NdDGxN9r0N\(]Ytk$bUkAQHvxB;B| D7T/ YV6mר͢`9FS, qZR9XCXmxʵmBz;in!M_?EV7 <#48o[}T0Zu .zdIxr562<+sMNH#߰l\v#B0ҍUDoSEz!Ko|~O'gzioS]!Qj0;GU6,̰8KBx[1N IyȄzۗANTzks|$a- ͡^r7/{R6mJ|P]XA[>Z[z{qRyޤf,d yQ%ķ/VTXTzˇZGxkӟr ݁RJ0r8~r4kMP ) F-s$n(´kdRL*]E%a/ E3 Zk?%U~^(]kUWAnZBpi`?mcev>1fFORM@?DJVUINFO ,INCLshared_anno.iffSjbzՀ ̨} )AQw[T1Q~{R7  Tئ-G|j/xE:Ѭt0=F4I\+ Kk<خ=%aɟfq AqÐ >XKYXFGbzIBG44|HRL $LѥbӨ2Hsg^FaaMچc}=;'ԗo` v{ o2J7K^g-yb*QVQu[h+lf}:@i84cǃ Q;p;>p,c$)}?2N8VG)WV2mu #/ۆ%4!0Ь_UsyLG)eYrCs))aqEk$FQt>Tm %n #\$/,$/,$/,$/,$/,$/nxT"7.U%|js*ht#bRF m1>O9Q7 q8njɳ崛>WZ?9&` 3a'}7NX])CW/]&{ {$_:-B;UJn[<#]!/EͿ\MG8P!UNTp! &z"t Hj [ ޯ3$F N\:=p$3*ǘGc!.|GuQFcɬRk4I/>554@dԮW2);4S~hyw}D(1b/=4t{q{^ 8oAQ;lZ1/t ]xCգodmFA갹A9=Hn&>\P LzAT`JI"1VlTF/ zPr5KeE踤%y*D /le9O5-ww~ŨqU~q?$xZ$({ Uﲯ_ 4ڣnͨW ٵ +R9ѥ4OX&@{+XKY,to2]|W F,Ob0 s\i^!,=V 6v߮*OʹnaqvMtDǦX|z~mgk3V$uPU(tDHBc }_),o\ p'. o)E,Wt5tt4=sE;9"f,K!gt0/~i"l'EX:|` ?*ȧk@4"`KLէ<,>ޡl2U2kH|"y*3όto~ y1? ёnby5c3*soRds 4b1\?(GS?Mȯ.-Mf J\m\13RYZ7Y2 C 7\# <bONc{ł-b=K=f!.4L76rԚ|:kDIMi% jn:g{HyGT2kL=ϣ\zB5t>Ds+Ie!`u+o2h*pL3ϠAto]FO'j{ Q4#jU}ͩ&#sRO(8޾ y 63nړ:U1_LǺC _`8jUl4X-nhh9.~Q*zm73fyLy)fR=u0o,ě²4w|Ii<9ôm>o* 3"ZIc1ޏ!FO# ՞6r:; #tz:: yQ5~З7Z ĉ~H "W-dY|z 0,vY^ܩ6pELo}y+XmOk˨0uMʹ F³ӠȌ24程 YS56#vZ%i!0V9B!8γʣtL޼ANwVU1y+>):_(bSjEcG"e.>[R4O1-anQrNEPs-8EKw͸ƿxr{">Oi  }bC5)+}ܛCen,Kh絉3Iã%@&\JAREw2*J`=1^BaA5>&YUu"2l7;&3SE=&Πb54O>0VkJ~Do_`B2xuI%څ#W=AE,1ىH 좼'0vI&"5ho@H/TH-X:(ڶ]Lh;+u/ Z@kvs_nT>2iTЅb8?:/SΥ?Ǐg)VYBG44  f5ƠU`@my]L@5-ϒ=X{}H_PesnUy։{?x{5oϦډ㋁BBǩ:զM#k, S|pZg`DI:}!DNRZ0aVՕ:x}88ѝ۱مOX9ln:KR_,n=v.(8'y>9:BX>v JBn7) 3>8fo+ x_@R\zg]z0yWT2D|2糦t=T.bRڟM'𜾼N(I8PօTTwb=لVYUS_.?ש˰*ㄿhDG UbrŐ$ӯ:30e(J]X~('bh`mg#We2̨pl6<*eG:دuАe(oIzw9|J[tVH o '3[VtW>|:2gF)gFLɝGP3! jZs RWtuGP*`[|BhkuFqEy#v@p*=NgQd-b%R퍃Ge|̒5Z%^^(Cw/hx9wשȀ&)tvA#Rmk+ +iߤhQ􍏺 BЦ:7MaSڸ1@펶ϥg9ʙeg/oTȺX[I3k"n 㷽/@[ŭO把:4C+yz+TOC l|jeT#:c[9]f$~ n]F.%KrE%xySF0olȐs($&sg.T1rQY)@ՃdW J!?KgqH2S"CG7|Y$Оd9~>Hˉ@VtՋXAdzg[PԲfl>k% \*T.':Y$P8RQ+z#0ʸ_] 8߲Չy{(Z-Z]^p $IYEmK]w!ũaUa4q"t8ݯpYc{=~|)8A]ҹ? 4:3[ !2dj=V! G~}DHX>аrE,Bra8'߂2n_дdt'. 1Ilf*ַCėUh.7BAPyn7F":9E ׳HNYĂ Y-A#:fgߐMTc1~SE/ٍ@pԯ7{9Co'C[zwbː_%Ap.ek3TܵqFJy9f?dxJ7TxnWjpKo֡[~ Si83|uEm8/S6>Q|҅LZ@mM.q|*6sarmu ҙy>?ic9K; ٥ygj=b2g팵٩GՌ\qͶ\p^ߏFGika wԾęxLqa"O׫_ÎToƉuV60-J܂4y{PAeOćރit;% rBy 0+QareF 'x^b^{ 6#0lxc#S,kA'l}n-+:AnXYtLo7@ST1iV`Q_Q=a8 ߰Wシ26Lu7yUl&G ӛl r6$ΏMV' 8ʕ x-j^'Z9&ę,$G*v@u`A +\'-,n+'Ȫ" ?UeW5-`M)Do]7 rۊF6d51"BG44  "6xpXZoߡf"QJi(# U?~>DXh>di l/.זȀ̇Hr6"ˡ>dˊS4=-$U5F o"_z EJ+>R4t\YƝF.}UqLm_A$ =M rFE97;*Ԟ X$Ys&{L֫pL>$z=JbԥRUz`RJ_%eQ$ =JMaT}RK/J|ԣ ڹbL[q L1Ɛ?Yf,QO# jN<'[㯱0 5!WX޹4BEk{!Æ㩉DHh@3Vʜz_t u%?`!webmpV3fP&TZ߷b&f H6x%oԷcov%DiWUr]-#W~a䳡Om$2s\r%4N0fe+[T"WCQD qtZ-#x0j&!7 F~^4`.NFO5)qt OSK_ 2='>I~?{Ś W\OPuFP{ !]ypqOEȊS$fwOB$Jx:X1"ڎ~MZ~ko%0dkW _Cp&?WKjLpDϹwCнRTG,^5){f%1|U }^p\K5mΩF-yr%LXq;q+)KUKƢ|C{3t9G8(,(Ukxiwe}^j\_lojs 2bNS]}υ^F>x$䎨FFYzO ᒬP]hh`&Ƀ9RP^1][(?Ղ* +.4Њ< E(j'bw_9'6!*M漗*(u,=ui$y|y@u9aظ%fy& 2\vƔ(t[K.#ݲ  ̑:x`IF*zۇ4y\E|24_XhQnd[MرnV=BX O^u(aqn l^>KکgctMy ];.Kp=qߚ R C|bP%D !-t}q{hp!T$Hz+sh^tX,YriH?zC @I^DMUǀvyW aQ PIZ Y؂iʪG/]Ug]Nڀ?Hҵy۵_j"<G=v[{D #${rzsvTSxRWr lF3jL-P4sB}n|kc)GZ&fbIBmnݱS\Wto$ntOe U2DMӗ?IQ:8)fH>鳌yUAhk= 6Z1<U>aF ^#vScv`Et%>2j7(yٴb#JsxkWC6Xc.+a5Run9I*rYmPdY+.tBxʭh ĉԧN{59{p1[1 !~#G?1?5薏E^GRw~+%hR۽eLG#l蒧xq1U,oM=g*;U=eAzi28>V夎QqCy^Ӏ̩%K12/&7HnzN-:fH9\a 6?e%ıwSSR@iwb83މQ4"w"w=71[S]uk.V]qFj۬wE@g# ?c4!vV·Al%2=BCD5N 0<6{&5{Q=|^=3gE %X{*^83CGY2.i1SAOc/Ɠv`z@gf#FwJQ ;fa`%VbV/nh_6r_f+l}ViCa$,2wg 閔b4[o}Tܔ㛰Gkw*wF0) .}U>AވW؁I;9Q0Ϊxf0XVlQ ܱWU}Tmd;"GU?O<' "3mNw[}%GQm~ f>(ϕ;̴``Q`Rn [^Ka.#l7XEEW2ރ"QJE@ûZn=Xv=U`ѕ G:S!OI[.nJ+/ 1;׹~ʛoZUDح@~my[v% m;EYf=lfJ ؓ:3 @Hh麇6 Uـnm{F2zG&6ѵ87&V*k% ڧtAN:OɡbjiFCJƈV6jfvCꋝ|Tᘌr7cN&~h y@gmv`U#7[LдhC{a$dF&ޣmR` 5 ~ƽ{T.!Cuzf;p8gWQ > 3OG$K |=Wcnt‰+,fu)TSE+x)1ůBG44 @Zc;~wO]H FWuK@ |@#HK%9u(Ue6OFTUS_*Pxq=7?H? O||_Kқ'Ս ZJi-+\ % :=kLKc@X׸,wlg2_{;_e.Gc쪮/&*O[i ͒gT# _nq܉X@<|h۵i``=SHb-N$.BCe!=(ED d<ݝܾJ-ߒ͜2T)hk4sW$2@m- =wVKnսw->:-I`>d5Bv ܳa4L k8>߸qoC=l"Ikt)N1ƓYHt֦ݧ!Zrj鍛;Uڝ|! [MCٞm6ӆKMHgͥR37 Z`K*L\yn-0jhEۖz!98z+GE͜ɢ۝)ڕ]=2+xZ7h}UYݛǥ8ov ڇ%}()db*Q6s̐s%lB(1 c)Q9 ,ѵJ-q,d*[z i'M,-2 {GUmFp# n 3rϫ8YXstvi-@Bk5 y;5ATNz-u"m8NݬřYbkL=aQ*r3)J48@%Y`* 8 hTX/i>&U; qL`΅@iglb-ٗH+vR'#ʘ-|@s9bf0 n˩^֡RweuX0N;*?yk2=ꏈE]XAzW͜tW2>E_xr81\.ijU:1#[$:*H&oH+MWtL£Bn.g1%W@wʣGܱ »qoCPB(#xge[ "~Lw Fuب\CXMlH\٘;)~9B:`ldo]!¾FwZ@c/*`{1äD(3r?p;.T2cHS`x1w\e[ @̹Psَ"'L@*ߙrl:DN2A74HOَ"'L8 Fh0e L! QnDd[{!M͞Uua@uUTR>lU~t\!01]*(o)F5,|UB1"-7>sE@V!c&uRkG{KOtQx5P6jUIσ*w:p]ӏa[x|0xDG@+'M}ޤ5f+o[ h<~Go, xʈHǡJ.ǙdZՀj% >Yۭw):{HVUx#um 4_q<5( 4_s+e#x,U} f,Ǒ6~$8m7hGGB8LMWUEC̲-t5A@3G" S$4'gU8⎿#/vvX U4nED_:xù8gs\04]mss%|q/Kp |ľG2D~?}ёZ~?|>z?}GSG9n~~?|K~?|Q;kƀ^i88VOj~ťVٷVZt{ԛZy(2􍏺;pÒzba`{S)fŁ "4y3w <Eȶ|N;V|4~ւ|H,TPwo;b]"4? u4 {6$@p´Zqc0QˠH"!eF#s_lnxf!5zQW/N; "x6T1wox54wƕWp ϛ/@DD[,S5E.=jEG'zH?BkԸ"WSх[X62.Xd̕лNEƙ?}o ux뿉ՓVcSYxI 䡪ӣU5hϯԓbQrNk}Ck`ҎIA{*b FthdͭlUDuѫԖ 3Q6d1F=Jqt˂6. [` tAfM7 fH>40bʠ$)yhVK۝$`vޛ[hNr&4,ҹzrLf^[dW _;C "O\(_>9yzr `wmC`B<#mH-찵1E 7+h҅x-ug$ply-9x_9.F:RGA䋳 U۽y$z,nP-'k`cqKU 'V r쇻My Nfͬ]xER) xH4(6QL(y ?Ԍmk,N7W’" Nl~I9~,せ&-; ӖJ"=Y1bX|WR%opz:ڰC"P3@\FEO}S}gbPCU'InnW烀q2d^^y~t8g?(pyOAXo>/e dz#EK=f\){;CLTF*4U]iniE#9諓<\V*A&*r \G|U\} ;梽W;5Fg+-M/ׇ,I68W2* xt;|]j}EP#-¢>:9Cah%6~v^Ofmēx4 T/{csL"}7 |W$jXM+la#" Lf?V0O[[ǟ,=? ksC) %D>7wZ]ٕ9UԜYm:)P@[H RRB+3K #?7i)+D/:BͶ H\7g㲼K44ϡuGLҎ\ܙEGWTI)6ۇhG;_KTknpi3AyH 5} wϛTU9՟ͳ9NL` =~0̠}v'- =Rpu+ nM Iu7yG{u(5[֓cW $.s如 |xOLp5 3IՉʽ0S].\Q{[Grv2+.yL*\ٗ wX t{y7W:<ɠ2rM\r&Wy)=saLVNwnhaؽ_2']; 8<}>TSu#- bݨ$Ё q}4TgOje "ϗ0-r;/9L=-G1߼0ظk)csYU'`*X.^|]&?ayr0e)ip&DIM▂Vs'py vYHrLɜPqCWFТINzO}˱܍Jmg#,FZI03i]ψBR %OJ&8 5ޔ]%~B ?:*!Ƙt"WۣweocgVK샜OнP&͜ѹ̈Q^ETXTz?ZC䇏d_Ř6 [͍iL >4l#gpython-djvulibre-0.3.9/tests/images/test0-image.jpeg0000644000000000000000000004322511440743731022375 0ustar rootroot00000000000000JFIF   (1#%(:3=<9387@H\N@DWE78PmQW_bghg>Mqypdx\egc//cB8Bcccccccccccccccccccccccccccccccccccccccccccccccccc! }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?;QHaE0KRZ (hwF+XD@0@61 ͳSjSXSg)yħUs#;Ƨڥ9SRA\1xxꂓܸnfAXRu zcEjj#1+@b?٦:ZCL*KqF,QTfP BLBRb J@41HBRPRPR`!mj+)iZkxo# "l& (M6k{@cU̪&5mÃU)KC;v0Kc n%=(ԕ=Fu{\ndJ LS@/Z @z1Wq 4 lxw?&[/j Q44sɦX-4f%11i))))iJ(PRPQ@ 4BJ@h`%%P#E%vQ@hZJ hx"ayhj/å'I#)jJ+ꆂ*-GOQΈlk6`_n*L{NZ MІu>c@:Qֆ"l| SF=XKtUyi42訩G5ƞ~؞)(PE JPRPJLRPHiJJ!J,H,G_Jˑv9_J֔+,QKHR(*կިдj"9HU ^Bci*M Qi荶=>jh(ߌ5ҕᱫրTc=NQA E uxCP.F(n/Ǡ4 E4BㅬSOTev'$KNWFh争.?G~UJK.L]MJ6T.m-ckT~7dRҟo=%Qf4\ӨJZBP 4 JJB J`%% )i-b;StS*=XGA%l1iE"BSۜ0Ľu\%C4U!iHJm&j{M$qXz3 VQޞƖr1֚s** HZuvdOl"Z67&NjXHz£Rv1*܄78QL\QBT0=*"3N (49u#̹B=Eb?GڙsM8Q[ ҄SqMTf9S*c њ2*Xbyj)cR݄:kyaXsPn;no;vJia[ u4&Wy6bf'+ULu \A&z6ˆ77SVLs:Ѹzw㱁iRSn1֪Jzֵl,0Km7s3Af  4 E! I@Ĥ4!4 Hi`W[P9˹՝$ZVhQJ&\VzvO#V?hp5mȣc{LWң##:SXoi H<J]<4AY>:ȮL ]Ei YfaMÌeOEqPr?JӴ-gs)M4P-<̌(. w f(ERv4ݱ@>X+,'VO{ҋr]1?;iهM!{11NkA>͇ڗ}(0֗֋_C75=VZFKW5@ KN84ͫ1ؑ-ϥJ=*\؍5@P5o[Isf DZ(L+f45;(o>n@X7R,.s@$P;ǹH,-%P );Kc9Ge57yXzu%27QKQr%a4oclsHbQ@&ZP*( m$q^))'a RRVP&!$Bd >\0~UcQO4VǗHi(! %y֔WQ_PE&1ph%4jЙQ%t-16hr'c<,!uf^bF-Sb"iGˊG+X27̦(%J<;S!N"Vj(fZjI@TҸm]̘Q&8N FА'œFm]BLpQI\6R.7e)i1B mh hb yq1KH.bB B)"M&K튫+D#V#r8fX/h[2hrw0tQi) MNktե`49/5{tdR{n2ixWl1L5R6 !o*k?xE] ݩՕE)Mn斸&*펪7|h3duROnCQӕU8Kh84=+={ ؊d2)6Rd; qOCZz 3{Fx68nުB'2!ʓR:扒VeYKE O In)@l!$iv@J b Vai;i\,M&.;ZR\,7jDM"C)1 )1\Bd61 ̞&bj Ћ©sI rOAH#!SczJ*t&Ĥ4yK{ \a4sVGs5:Be$PIKSܒ5!^+ "( Udۊo!ظ棐޲`5 4dI f7*}h.9MޭKΚ," pM>mDօ"aFVv#⋗}G6 Xh22I$1]pÂ97z][x#Ovi8*̬!^iqJ䵨OM SCOhҊdX)ɠ,L;<.HSM2И1bH=( !AHhaZL aqQQa6!aL-RHi44ɊWƴJ*4ݪ6.RCKzԈ* ⫑DGZ :>8"] -L^K+$ĤA{"u!4O)ı Tu3*d\2eb0r(GYZk0^pny ྴ$ ip)qx҄ pQQͷ(*:: qYh{DGi5'j CNOPm"u+ـҊlcXIQb%YzF|ir9sCTՎ&L^1VDR(j4єr4ٕ )nõ BE0d47!P 'QAQB*L1L4SD Rni\ML-T .t( \S2hf)⥉ ) aY-ijzȕ*xaQX Y(LjJHGTGO! DU%ryV(Ѣ9uVZkS6RbI{ThGRNTZe֐ǎHhiM&M%8$@MJ})JNd4 S1B$wv-3R<&hBf84z0 84UcE*| 1S!i6&imL4X*BKREZΈ"`EC$GCzb 15HLfhcc+CO#"Ja\RM5-HNRj[!Lhn-FBM[H&}iꙃCĔ@dM)4̃[dl`⛊&kr! Uy"͜URaZULv:(qN+N $SKRM$TM&i\BnEw@yn 29T!=ޑ# I!.)L{)T ȝqC=)^Ɲ ΤnqT@晊4x<1+: R im(Ld8&)bFM m HPRL(@Pmjig)!DRmdGbUsS'a2sV-Yɪ2%H_?2}*ajtFi3X09ۚǭqMdۚSK8gtL8 ԚȮF( \FLT.*rZOapˉb4QRaTYCN ]i .)ÊcDB#"l4`R `.((Vun6-$+LRsT&M0T`Ɩ9!.**RIFvR qEq f-Rs"l1֠hʚW"HI6J3Q&sU1QS}7^I qIM;HlT-\UIhFWzPܠx;Ik`Gƃuaԛu9}neq>_d ~.m \Nqv‹4sNyTPRhDchi51+cjw{K)"IJ{RRcǨ&(ؤ˸i˚w&:Piqfni<@84,AU4S"O5`Ftr*,`Ӹy91܈$L)aRI؍uD-܅w2d,ySK--  AhFh0SrGbV*䄮*Rg6FDjG,/1@+┈"9GImaN(\IZ fhrrA ּΠ"RcZq؆Fdzaj= |Q1i@q\vd =JR&1ьVRѪBP 4^CG4)H_,]X/1Қc⋖Qhm* ԩT]-PdTY0jV`T-YyBTEj̃iˀhXJ*3Ta@8fylf)S$!Hj) J(-(!U!yQ$G j)K=@Z# M!4[݆Ijg)3Li+SIj)@z8 \ͱC MRziT2ƓqFw1qw:{4Qh+uJY $n·#sO3[ 4H(2M&iUsڟCTMɤhQfi= [ԽD2E;5&F(fB_-ˊbpjd&bg٤` ZP("ni ա!H@iAhxR(zj[!ƛR9g6U䜚,DbWg̀V{wODޓ~{դgpN 1JDZPj-rZhFawR1Xpj ;aDHl4Pi QOS~FEWrsi b 4VhHi3TRgJX W*g %i2ꤴ-j Ls,U9N) 2A%86jZ5L9&nr&&1He6.ie5V$ZXI4WN*$ZɃS1٠P14(n  @M z(⟚c&4I䒠ykQ6BTLխ!big,p-2b(QN R5RitRi*2isIa٤!00f!bM<s(aVk*hZep ҃ SN hڕ3֦[KqJ/uA5Fii i(bz&S'W/Pjihe!)7dTFSq0H`Z9] Je4Fw3 ҃@NR ҃HcMf!cQTi1UI)M"QKT wSLR1lrz!i hM4JҹqHJUy6Pȵr)Cce v֢/,9L6f@hٚGJwPitS&ɐbSFihhaB(Ewtrv4VBF4H\f5)9@1_oJ]45]i%R3Wc4&3FJHF(,bII;̧+楚)#qRirH<ոy);1\J*l4rЮ4ڤdc-H13M&D0NئM枦!iPfH2& ˞iJDEVd؆S&M&qB%+N(bi 856)/(.*[!ONrAE-m`m:f+BGXlqOZ2'LtgQw5)"D+.*YCH i4Ӱ\JJWhSFr6,VaM-IL3R ɋ`U9$Vfп`=Fhi\ )i%!RS%z)&dD& lK4;i8J)HnjbRU"BDLSlAKB$J1CA*S` CYbPx0`)T0c JOAKjQZG #*g͢71S+3w4J䅀TNWl&hLJ.IIU)z!#a4iqMLTȸIUV~F,Gص"E4jlhE"KSiPRf!;wvBfqp!ph4"i0EZZ&FϚ2ciip%&!hĉKBRPiRoPbTt-8 ]IlZ R LҊQՀ(J64G"qUZR7Y#=X]MVrZ(/0lJ(!I@ŠvE\) 1 1!;Ee-*L81SԻ)2@<=<5&j85(4.h$X80JH'_/+s(2=DMlmYCM4#6PRBRb )4T E.(bخ41O8 R[H =.(@ v"lCJ#5lJM7*ɚ~sBw)aM*  j )QR(bRcBe 1)6YߩhNZF*䢔 j2r3@IQGA EU IKJ J({RC%ҹ-1JڈpS&;m(q@TG".j&ZOcH&sS5L%W< W) j *؅ƘNEyXrj,1R Ip@8 p8Qj)dӖ'R}k` %}+D=~)>;ҚQ B!dڛr_d;#python-djvulibre-0.3.9/tests/images/test0.tex0000644000000000000000000000343411440743731021166 0ustar rootroot00000000000000\documentclass[12pt]{article} \usepackage[pdftex,unicode,pdfauthor={Jakub Wilk},bookmarks,plainpages=false]{hyperref} \usepackage[greek,russian,english,polish]{babel} \usepackage[utf8]{inputenc} \usepackage{color} \usepackage{graphicx} \usepackage{times} \setlength{\parindent}{0mm} \begin{document} \section{A} \tiny Inventore Labore Aperiam Soluta \\ \scriptsize Eligendi delectus, quidem placeat \\ \footnotesize Eos exercitationem amet velit ullam \\ \small Aspernatur modi cumque, deserunt \\ \normalsize Nihil accusantium repellat soluta \\ \large Eligendi repellendus cum culpa \\ \Large Architecto, laudantium molestias \\ \LARGE Cum blanditiis vitae libero \\ \huge Explicabo vero inventore vel, est \\ \Huge Reiciendis tempora vitae? \newpage \section{B} \begin{equation} e^{i\pi} + 1 = 0 \label{A} \end{equation} \newpage \section{C} \par {\color{red} red} {\color{green} green} {\color{blue} blue} {\color{cyan} cyan} {\color{magenta} magenta} {\color{yellow} yellow} \par \colorbox{green}{\color{red} red} \colorbox{blue}{\color{green} green} \colorbox{cyan}{\color{blue} blue} \colorbox{magenta}{\color{cyan} cyan} \colorbox{yellow}{\color{magenta} magenta} \colorbox{red}{\color{yellow} yellow} \newpage \pagecolor{red} \section{D} Optio reprehenderit molestias amet aliquam, similique doloremque fuga labore voluptatum voluptatem, commodi culpa voluptas, officia tenetur expedita quidem hic repellat molestiae quis accusamus dolores repudiandae, quidem in ad voluptas eligendi maiores placeat ex consectetur at tenetur amet. \newpage \pagecolor{white} \section{E} \subsection{E1} $\to$ \ref{A} \subsection{E2} \url{http://jwilk.net/} \newpage \section{F} \includegraphics[width=\textwidth]{test0-image.jpeg} \end{document} python-djvulibre-0.3.9/tests/images/Makefile0000644000000000000000000000047011440743731021042 0ustar rootroot00000000000000tex_files = $(wildcard *.tex) pdf_files = $(tex_files:.tex=.pdf) djvu_files = $(pdf_files:.pdf=.djvu) .PHONY: all all: $(djvu_files) %.pdf: %.tex pdflatex $(<) pdflatex $(<) %.djvu: %.pdf pdf2djvu $(<) -o $(@) .PHONY: clean clean: rm -f $(pdf_files) $(djvu_files) *.log *.out *.aux # vim:ts=4 sw=4 noet python-djvulibre-0.3.9/tests/test_const.py0000644000000000000000000000630011731460053020666 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008-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. from __future__ import with_statement from djvu.const import * from djvu.sexpr import * from common import * class test_text_zones(): zones = [ TEXT_ZONE_PAGE, TEXT_ZONE_COLUMN, TEXT_ZONE_REGION, TEXT_ZONE_PARAGRAPH, TEXT_ZONE_LINE, TEXT_ZONE_WORD, TEXT_ZONE_CHARACTER ] def test_type(self): for zone in self.zones: assert_equal(type(zone), TextZoneType) assert_true(isinstance(zone, Symbol)) def test_repr(self): assert_repr(TEXT_ZONE_PAGE, '') assert_repr(TEXT_ZONE_COLUMN, '') assert_repr(TEXT_ZONE_REGION, '') assert_repr(TEXT_ZONE_PARAGRAPH, '') assert_repr(TEXT_ZONE_LINE, '') assert_repr(TEXT_ZONE_WORD, '') assert_repr(TEXT_ZONE_CHARACTER, '') def test_identity(self): assert_true(TEXT_ZONE_PAGE is get_text_zone_type(Symbol('page'))) assert_true(TEXT_ZONE_COLUMN is get_text_zone_type(Symbol('column'))) assert_true(TEXT_ZONE_REGION is get_text_zone_type(Symbol('region'))) assert_true(TEXT_ZONE_PARAGRAPH is get_text_zone_type(Symbol('para'))) assert_true(TEXT_ZONE_LINE is get_text_zone_type(Symbol('line'))) assert_true(TEXT_ZONE_WORD is get_text_zone_type(Symbol('word'))) assert_true(TEXT_ZONE_CHARACTER is get_text_zone_type(Symbol('char'))) def test_comparison1(self): assert_not_equal(TEXT_ZONE_PAGE, '') assert_not_equal(TEXT_ZONE_PAGE, 42) with raises(TypeError, 'cannot compare text zone type with other object'): TEXT_ZONE_PAGE < 42 with raises(TypeError, 'cannot compare text zone type with other object'): TEXT_ZONE_PAGE <= 42 with raises(TypeError, 'cannot compare text zone type with other object'): TEXT_ZONE_PAGE > 42 with raises(TypeError, 'cannot compare text zone type with other object'): TEXT_ZONE_PAGE >= 42 def test_comparison2(self): assert_equal(self.zones, sorted(self.zones, reverse=True)) assert_equal( [[cmp(z1, z2) for z1 in self.zones] for z2 in self.zones], [ [0, -1, -1, -1, -1, -1, -1], [+1, 0, -1, -1, -1, -1, -1], [+1, +1, 0, -1, -1, -1, -1], [+1, +1, +1, 0, -1, -1, -1], [+1, +1, +1, +1, 0, -1, -1], [+1, +1, +1, +1, +1, 0, -1], [+1, +1, +1, +1, +1, +1, 0], ] ) # vim:ts=4 sw=4 et python-djvulibre-0.3.9/tests/test_sexpr.py0000644000000000000000000004156511731705636020726 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2007-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. from __future__ import with_statement import collections import copy import tempfile import pickle try: import cPickle as cpickle except ImportError: cpickle = None from djvu.sexpr import * from common import * def assert_pickle_equal(obj): for pickle_module in pickle, cpickle: if pickle_module is None: continue for protocol in 0, 1, 2: pickled_obj = pickle_module.dumps(obj, protocol=protocol) repickled_obj = pickle_module.loads(pickled_obj) assert_equal(obj, repickled_obj) class test_int_expressions(): def test_short(self): x = Expression(3) assert_repr(x, 'Expression(3)') assert_true(x is Expression(x)) assert_equal(x.value, 3) assert_equal(str(x), '3') assert_repr(x, repr(Expression.from_string(str(x)))) assert_equal(int(x), 3) if not py3k: long_x = long(x) assert_equal(type(long_x), long) assert_equal(long_x, L(3)) assert_equal(x, Expression(3)) assert_not_equal(x, Expression(-3)) assert_equal(hash(x), x.value) assert_not_equal(x, 3) def test_long(self): x = Expression(L(42)) assert_repr(x, 'Expression(42)') def test_limits(self): assert_equal(Expression((1 << 29) - 1).value, (1 << 29) - 1) assert_equal(Expression(-1 << 29).value, -1 << 29) with raises(ValueError, 'value not in range(-2 ** 29, 2 ** 29)'): Expression(1 << 29) with raises(ValueError, 'value not in range(-2 ** 29, 2 ** 29)'): Expression((-1 << 29) - 1) def test_bool(self): assert_equal(Expression(1) and 42, 42) assert_equal(Expression(0) or 42, 42) def test_pickle(self): x = Expression(42) assert_pickle_equal(x) def test_symbols(): for name in 'eggs', u('ветчина'): symbol = Symbol(name) assert_equal(type(symbol), Symbol) assert_equal(symbol, Symbol(name)) assert_true(symbol is Symbol(name)) if py3k: assert_equal(str(symbol), name) else: assert_equal(str(symbol), name.encode('UTF-8')) assert_equal(unicode(symbol), name) assert_not_equal(symbol, name) assert_not_equal(symbol, name.encode('UTF-8')) assert_equal(hash(symbol), hash(name.encode('UTF-8'))) assert_pickle_equal(symbol) def test_expressions(): x = Expression(Symbol('eggs')) assert_repr(x, "Expression(Symbol('eggs'))") assert_true(x is Expression(x)) assert_equal(x.value, Symbol('eggs')) assert_equal(str(x), 'eggs') assert_repr(x, repr(Expression.from_string(str(x)))) assert_equal(x, Expression(Symbol('eggs'))) assert_not_equal(x, Expression('eggs')) assert_not_equal(x, Symbol('eggs')) assert_equal(hash(x), hash('eggs')) assert_pickle_equal(x) def test_string_expressions(): x = Expression('eggs') assert_repr(x, "Expression('eggs')") assert_true(x is Expression(x)) assert_equal(x.value, 'eggs') assert_equal(str(x), '"eggs"') assert_repr(x, repr(Expression.from_string(str(x)))) assert_equal(x, Expression('eggs')) assert_not_equal(x, Expression(Symbol('eggs'))) assert_not_equal(x, 'eggs') assert_equal(hash(x), hash('eggs')) assert_pickle_equal(x) class test_unicode_expressions(): def test1(self): x = Expression(u('eggs')) assert_repr(x, "Expression('eggs')") assert_true(x is Expression(x)) def test2(self): x = Expression(u('żółw')) if py3k: assert_repr(x, "Expression('żółw')") else: assert_repr(x, r"Expression('\xc5\xbc\xc3\xb3\xc5\x82w')") class test_list_expressions(): def test1(self): x = Expression(()) assert_repr(x, "Expression(())") y = Expression(x) assert_true(x is y) assert_equal(x.value, ()) assert_equal(len(x), 0) assert_equal(bool(x), False) assert_equal(list(x), []) def test2(self): x = Expression([[1, 2], 3, [4, 5, Symbol('baz')], ['quux']]) assert_repr(x, "Expression(((1, 2), 3, (4, 5, Symbol('baz')), ('quux',)))") y = Expression(x) assert_repr(y, repr(x)) assert_false(x is y) assert_equal(x.value, ((1, 2), 3, (4, 5, Symbol('baz')), ('quux',))) assert_equal(str(x), '((1 2) 3 (4 5 baz) ("quux"))') assert_repr(x, repr(Expression.from_string(str(x)))) assert_equal(len(x), 4) assert_equal(bool(x), True) assert_equal(tuple(x), (Expression((1, 2)), Expression(3), Expression((4, 5, Symbol('baz'))), Expression(('quux',)))) with raises(TypeError, 'key must be an integer or a slice'): x[object()] assert_equal(x[1], Expression(3)) assert_equal(x[-1][0], Expression('quux')) with raises(IndexError, 'list index of out range'): x[6] with raises(IndexError, 'list index of out range'): x[-6] assert_equal(x[:].value, x.value) assert_repr(x[1:], "Expression((3, (4, 5, Symbol('baz')), ('quux',)))") assert_repr(x[-2:], "Expression(((4, 5, Symbol('baz')), ('quux',)))") x[-2:] = 4, 5, 6 assert_repr(x, 'Expression(((1, 2), 3, 4, 5, 6))') x[0] = 2 assert_repr(x, 'Expression((2, 3, 4, 5, 6))') x[:] = (1, 3, 5) assert_repr(x, 'Expression((1, 3, 5))') x[3:] = 7, assert_repr(x, 'Expression((1, 3, 5, 7))') with raises(NotImplementedError, 'only [n:] slices are supported'): x[object():] with raises(NotImplementedError, 'only [n:] slices are supported'): x[:2] with raises(NotImplementedError, 'only [n:] slices are supported'): x[object():] = [] with raises(NotImplementedError, 'only [n:] slices are supported'): x[:2] = [] with raises(TypeError, 'can only assign a list expression'): x[:] = 0 assert_equal(x, Expression((1, 3, 5, 7))) assert_not_equal(x, Expression((2, 4, 6))) assert_not_equal(x, (1, 3, 5, 7)) with raises(TypeError, "unhashable type: 'ListExpression'"): hash(x) def test_insert(self): lst = [] expr = Expression(()) for pos in [-8, 4, 6, -5, -7, 5, 7, 2, -3, 8, 10, -2, 1, -9, -10, -4, -6, 0, 9, 3, -1]: lst.insert(pos, pos) assert_true(expr.insert(pos, pos) is None) assert_equal(expr, Expression(lst)) assert_equal(list(expr.value), lst) def test_append(self): expr = Expression(()) for i in range(10): assert_true(expr.append(i) is None) assert_equal(expr, Expression(range(i + 1))) assert_equal(list(expr.value), list(range(i + 1))) def test_extend(self): lst = [] expr = Expression(()) for ext in [1], [], [2, 3]: lst.extend(ext) expr.extend(ext) assert_equal(expr, Expression(lst)) assert_equal(list(expr.value), lst) with raises(TypeError, "'int' object is not iterable"): expr.extend(0) def test_inplace_add(self): lst = [] expr0 = expr = Expression(()) for ext in [], [1], [], [2, 3]: lst += ext expr += ext assert_equal(expr, Expression(lst)) assert_equal(list(expr.value), lst) assert_true(expr is expr0) with raises(TypeError, "'int' object is not iterable"): expr += 0 def test_pop(self): expr = Expression([0, 1, 2, 3, 4, 5, 6]) assert_equal(expr.pop(0), Expression(0)) assert_equal(expr, Expression([1, 2, 3, 4, 5, 6])) with raises(IndexError, 'pop index of out range'): expr.pop(6) assert_equal(expr.pop(5), Expression(6)) assert_equal(expr, Expression([1, 2, 3, 4, 5])) assert_equal(expr.pop(-1), Expression(5)) assert_equal(expr, Expression([1, 2, 3, 4])) assert_equal(expr.pop(-2), Expression(3)) assert_equal(expr, Expression([1, 2, 4])) assert_equal(expr.pop(1), Expression(2)) assert_equal(expr, Expression([1, 4])) expr.pop() expr.pop() with raises(IndexError, 'pop from empty list'): expr.pop() for i in range(-2, 3): with raises(IndexError, 'pop from empty list'): expr.pop(i) def test_delitem(self): expr = Expression([0, 1, 2, 3, 4, 5, 6]) del expr[0] assert_equal(expr, Expression([1, 2, 3, 4, 5, 6])) with raises(IndexError, 'pop index of out range'): expr.pop(6) del expr[5] assert_equal(expr, Expression([1, 2, 3, 4, 5])) del expr[-1] assert_equal(expr, Expression([1, 2, 3, 4])) del expr[-2] assert_equal(expr, Expression([1, 2, 4])) del expr[1] assert_equal(expr, Expression([1, 4])) del expr[1:] assert_equal(expr, Expression([1])) del expr[:] assert_equal(expr, Expression([])) for i in range(-2, 3): with raises(IndexError, 'pop from empty list'): del expr[i] def test_remove(self): expr = Expression([0, 1, 2, 3, 4, 5, 6]) expr.remove(Expression(0)) assert_equal(expr, Expression([1, 2, 3, 4, 5, 6])) with raises(IndexError, 'item not in list'): expr.remove(Expression(0)) expr.remove(Expression(6)) assert_equal(expr, Expression([1, 2, 3, 4, 5])) expr.remove(Expression(5)) assert_equal(expr, Expression([1, 2, 3, 4])) expr.remove(Expression(3)) assert_equal(expr, Expression([1, 2, 4])) expr.remove(Expression(2)) assert_equal(expr, Expression([1, 4])) expr.remove(Expression(4)) expr.remove(Expression(1)) with raises(IndexError, 'item not in list'): expr.remove(Expression(-1)) def test_contains(self): expr = Expression(()) assert_false(Expression(42) in expr) lst = (1, 2, 3) expr = Expression(lst) for x in lst: assert_false(x in expr) assert_true(Expression(x) in expr) assert_false(Expression(max(lst) + 1) in expr) def test_index(self): expr = Expression(()) with raises(ValueError, 'value not in list'): expr.index(Expression(42)) lst = [1, 2, 3] expr = Expression(lst) for x in lst: i = lst.index(x) j = expr.index(Expression(x)) assert_equal(i, j) with raises(ValueError, 'value not in list'): expr.index(Expression(max(lst) + 1)) def test_count(self): lst = [1, 2, 2, 3, 2] expr = Expression(lst) for x in lst + [max(lst) + 1]: i = lst.count(x) j = expr.count(Expression(x)) assert_equal(i, j) def test_reverse(self): for lst in (), (1, 2, 3): expr = Expression(lst) assert_equal( Expression(reversed(expr)), Expression(reversed(lst)) ) assert_equal( Expression(reversed(expr)).value, tuple(reversed(lst)) ) assert_true(expr.reverse() is None) assert_equal( expr, Expression(reversed(lst)) ) assert_equal( expr.value, tuple(reversed(lst)) ) def test_copy1(self): x = Expression([1, [2], 3]) y = Expression(x) x[1][0] = 0 assert_repr(x, 'Expression((1, (0,), 3))') assert_repr(y, 'Expression((1, (0,), 3))') x[1] = 0 assert_repr(x, 'Expression((1, 0, 3))') assert_repr(y, 'Expression((1, (0,), 3))') def test_copy2(self): x = Expression([1, [2], 3]) y = copy.copy(x) x[1][0] = 0 assert_repr(x, 'Expression((1, (0,), 3))') assert_repr(y, 'Expression((1, (0,), 3))') x[1] = 0 assert_repr(x, 'Expression((1, 0, 3))') assert_repr(y, 'Expression((1, (0,), 3))') def test_copy3(self): x = Expression([1, [2], 3]) y = copy.deepcopy(x) x[1][0] = 0 assert_repr(x, 'Expression((1, (0,), 3))') assert_repr(y, 'Expression((1, (2,), 3))') x[1] = 0 assert_repr(x, 'Expression((1, 0, 3))') assert_repr(y, 'Expression((1, (2,), 3))') if sys.version_info >= (2, 6): def test_abc(self): x = Expression(()) assert_true(isinstance(x, collections.MutableSequence)) assert_true(isinstance(iter(x), collections.Iterator)) def test_pickle(self): for lst in (), (1, 2, 3), (1, (2, 3)): x = Expression(lst) assert_pickle_equal(x) def strip_line_numbers_from_traceback(s): s = re.sub('(?<=[.]c):[0-9]+(?=[)])', '', s) s = re.sub(', line [0-9]+(?=, )', '', s) return s class test_expression_parser(): def test_badstring(self): with raises(ExpressionSyntaxError): Expression.from_string('(1') def test_attr_from_file(self): assert getattr(Expression, 'from_file', None) is None def test_bad_io(self): stderr = StringIO() with interim(sys, stderr=stderr): with raises(ExpressionSyntaxError): Expression.from_stream(42) stderr = strip_line_numbers_from_traceback(stderr.getvalue()) assert_multi_line_equal(stderr, '''\ Unhandled exception (42) Traceback (most recent call last): File "sexpr.pyx", in djvu.sexpr._myio_getc (djvu/sexpr.c) AttributeError: 'int' object has no attribute 'read' ''') def test_stringio(self): fp = StringIO('(eggs) (ham)') def read(): return Expression.from_stream(fp) x = read() assert_repr(x, "Expression((Symbol('eggs'),))") x = read() assert_repr(x, "Expression((Symbol('ham'),))") with raises(ExpressionSyntaxError): x = read() def test_fileio_text(self): fp = tempfile.TemporaryFile(mode='w+t') def read(): return Expression.from_stream(fp) if not py3k: assert_equal(type(fp), file) fp.write('(eggs) (ham)') fp.flush() fp.seek(0) x = read() assert_repr(x, "Expression((Symbol('eggs'),))") x = read() assert_repr(x, "Expression((Symbol('ham'),))") with raises(ExpressionSyntaxError): x = read() def test_fileio_binary(self): fp = tempfile.TemporaryFile(mode='w+b') def read(): return Expression.from_stream(fp) if not py3k: assert_equal(type(fp), file) fp.write(b('(eggs) (ham)')) fp.flush() fp.seek(0) x = read() assert_repr(x, "Expression((Symbol('eggs'),))") x = read() assert_repr(x, "Expression((Symbol('ham'),))") with raises(ExpressionSyntaxError): x = read() class test_expression_writer(): expr = Expression([Symbol('eggs'), Symbol('ham')]) repr = '(eggs ham)' def test_bad_io(self): stderr = StringIO() with interim(sys, stderr=stderr): self.expr.print_into(42) stderr = strip_line_numbers_from_traceback(stderr.getvalue()) expected_stderr = '''\ Unhandled exception (42) Traceback (most recent call last): File "sexpr.pyx", in djvu.sexpr._myio_puts (djvu/sexpr.c) AttributeError: 'int' object has no attribute 'write' ''' expected_stderr *= len(stderr) // len(expected_stderr) assert_multi_line_equal(stderr, expected_stderr) def test_stringio(self): fp = StringIO() self.expr.print_into(fp) assert_equal(fp.getvalue(), self.repr) def test_fileio_text(self): fp = tempfile.TemporaryFile(mode='w+t') if not py3k and os.name == 'posix': assert_equal(type(fp), file) self.expr.print_into(fp) fp.seek(0) assert_equal(fp.read(), self.repr) def test_fileio_binary(self): fp = tempfile.TemporaryFile(mode='w+b') if not py3k and os.name == 'posix': assert_equal(type(fp), file) self.expr.print_into(fp) fp.seek(0) assert_equal(fp.read(), b(self.repr)) class test_expression_writer_nonascii(test_expression_writer): expr = Expression(u('żółw')) repr = r'"\305\274\303\263\305\202w"' # vim:ts=4 sw=4 et python-djvulibre-0.3.9/setup.py0000644000000000000000000002002511731460517016504 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2007-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. ''' *python-djvulibre* is a set of `Python `_ bindings for the `DjVuLibre `_ library, an open source implementation of `DjVu `_. ''' classifiers = ''' Development Status :: 4 - Beta Intended Audience :: Developers License :: OSI Approved :: GNU General Public License (GPL) Operating System :: POSIX Operating System :: Microsoft :: Windows :: Windows 95/98/2000 Operating System :: Microsoft :: Windows :: Windows NT/2000 Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 3 Topic :: Multimedia :: Graphics Topic :: Multimedia :: Graphics :: Graphics Conversion Topic :: Text Processing '''.strip().split('\n') import glob import os import sys import subprocess as ipc if os.name == 'posix' and os.getenv('python_djvulibre_mingw32'): import mingw32cross else: mingw32cross = None # Just to make sure setuptools won't try to be clever: fake_module = type(sys)('fake_module') fake_module.build_ext = None sys.modules['Pyrex'] = sys.modules['Pyrex.Distutils'] = sys.modules['Pyrex.Distutils.build_ext'] = fake_module del fake_module try: import setuptools as distutils_core import setuptools.extension assert setuptools.extension.have_pyrex except ImportError: import distutils.core as distutils_core import distutils import distutils.ccompiler import distutils.command.clean import distutils.command.build_ext import distutils.dep_util try: import sphinx.setup_command as sphinx_setup_command except ImportError: sphinx_setup_command = None def ext_modules(): for pyx_file in glob.glob(os.path.join('djvu', '*.pyx')): module, _ = os.path.splitext(os.path.basename(pyx_file)) yield module ext_modules = list(ext_modules()) def get_version(): if sys.version_info >= (3, 0): extra = dict(encoding='UTF-8') else: extra = {} changelog = open(os.path.join(os.path.dirname(__file__), 'doc', 'changelog'), **extra) try: return changelog.readline().split()[1].strip('()') finally: changelog.close() PKG_CONFIG_FLAG_MAP = {'-I': 'include_dirs', '-L': 'library_dirs', '-l': 'libraries'} def pkg_config(*packages, **kwargs): try: pkgconfig = ipc.Popen( ['pkg-config', '--libs', '--cflags'] + list(packages), stdout=ipc.PIPE, stderr=ipc.PIPE ) except OSError: _, ex, _ = sys.exc_info() ex.strerror = 'pkg-config: ' + ex.strerror raise stdout, stderr = pkgconfig.communicate() stdout = stdout.decode('ASCII', 'replace') stderr = stderr.decode('ASCII', 'replace') if pkgconfig.returncode: raise IOError('[pkg-config] ' + stderr.strip()) kwargs.setdefault('extra_link_args', []) kwargs.setdefault('extra_compile_args', ['-Wno-uninitialized']) for argument in stdout.split(): key = argument[:2] try: value = argument[2:] kwargs.setdefault(PKG_CONFIG_FLAG_MAP[key], []).append(value) except KeyError: kwargs['extra_link_args'].append(argument) kwargs['extra_compile_args'].append(argument) return kwargs if mingw32cross: def pkg_config(*packages, **kwargs): return dict( libraries=['libdjvulibre'], ) return kwargs __version__ = get_version() # Work-around for : try: del os.environ['CFLAGS'] except KeyError: pass class build_ext(distutils.command.build_ext.build_ext): config_filename = 'djvu/config.pxi' def run(self): new_config = [ 'DEF PY3K = %d' % (sys.version_info >= (3, 0)), 'DEF PYTHON_DJVULIBRE_VERSION = "%s"' % __version__, 'DEF HAVE_LANGINFO_H = %d' % (os.name == 'posix' and not mingw32cross), ] try: old_config = open(self.config_filename, 'rt').read() except IOError: old_config = '' if '\n'.join(new_config).strip() != old_config.strip(): distutils.log.info('creating %r' % self.config_filename) distutils.file_util.write_file(self.config_filename, new_config) distutils.command.build_ext.build_ext.run(self) def build_extensions(self): self.check_extensions_list(self.extensions) for ext in self.extensions: ext.sources = list(self.cython_sources(ext)) self.build_extension(ext) def cython_sources(self, ext): for source in ext.sources: assert source.endswith('.pyx') target = '%s.c' % source[:-4] yield target depends = [source, self.config_filename] + ext.depends if not (self.force or distutils.dep_util.newer_group(depends, target)): distutils.log.debug('not cythoning %r (up-to-date)', ext.name) continue distutils.log.info('cythoning %r extension', ext.name) def build_c(source, target): distutils.spawn.spawn(['cython', source]) # XXX This is needed to work around . # Fortunately, python-djvulibre doesn't really need __Pyx_GetVtable(). distutils.spawn.spawn(['sed', '-i~', '-e', r's/\(static int __Pyx_GetVtable(PyObject [*]dict, void [*]vtabptr) {\)/\1 return 0;/', target ]) self.make_file(depends, target, build_c, [source, target]) class clean(distutils.command.clean.clean): def run(self): if self.all: for wildcard in 'djvu/*.c', 'djvu/*.c~', 'djvu/config.pxi': filenames = glob.glob(wildcard) if filenames: distutils.log.info('removing %r', wildcard) if self.dry_run: continue for filename in glob.glob(wildcard): os.remove(filename) return distutils.command.clean.clean.run(self) if sphinx_setup_command: class build_sphinx(sphinx_setup_command.BuildDoc): def run(self): # Make sure that djvu module is imported from the correct # directory. # # The current directory (which is normally in sys.path[0]) is # typically a wrong choice: it contains djvu/__init__.py but not # the extension modules. Prepend the directory that build_ext would # use instead. build_ext = self.get_finalized_command('build_ext') sys.path[:0] = [build_ext.build_lib] import djvu del sys.path[0] sphinx_setup_command.BuildDoc.run(self) else: build_sphinx = None setup_params = dict( name = 'python-djvulibre', version = __version__, author = 'Jakub Wilk', author_email = 'jwilk@jwilk.net', license = 'GNU GPL 2', description = 'Python support for the DjVu image format', long_description = __doc__.strip(), classifiers = classifiers, url = 'http://jwilk.net/software/python-djvulibre', platforms = ['all'], packages = ['djvu'], ext_modules = [ distutils.command.build_ext.Extension( 'djvu.%s' % name, ['djvu/%s.pyx' % name], depends = ['djvu/common.pxi'] + glob.glob('djvu/*.pxd'), **pkg_config('ddjvuapi') ) for name in ext_modules ], py_modules = ['djvu.const'], cmdclass = dict( (cmd.__name__, cmd) for cmd in (build_ext, clean, build_sphinx) if cmd is not None ) ) if __name__ == '__main__': distutils_core.setup(**setup_params) # vim:ts=4 sw=4 et python-djvulibre-0.3.9/djvu/0000755000000000000000000000000011731710327015740 5ustar rootroot00000000000000python-djvulibre-0.3.9/djvu/decode.pxd0000644000000000000000000002136011612141162017673 0ustar rootroot00000000000000# Copyright © 2007, 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. cdef extern from 'stdio.h': ctypedef struct FILE from djvu.sexpr cimport cexpr_t, _WrappedCExpr from djvu.sexpr cimport public_c2py as cexpr2py from djvu.sexpr cimport public_py2c as py2cexpr cdef extern from 'libdjvu/ddjvuapi.h': struct ddjvu_context_s union ddjvu_message_s struct ddjvu_job_s struct ddjvu_document_s struct ddjvu_page_s struct ddjvu_format_s struct ddjvu_rect_s struct ddjvu_rectmapper_s ctypedef ddjvu_context_s ddjvu_context_t ctypedef ddjvu_message_s ddjvu_message_t ctypedef ddjvu_job_s ddjvu_job_t ctypedef ddjvu_document_s ddjvu_document_t ctypedef ddjvu_page_s ddjvu_page_t ctypedef ddjvu_format_s ddjvu_format_t ctypedef ddjvu_rect_s ddjvu_rect_t ctypedef ddjvu_rectmapper_s ddjvu_rectmapper_t ctypedef void (*ddjvu_message_callback_t)(ddjvu_context_t* context, void* closure) nogil cdef enum ddjvu_status_e: DDJVU_JOB_NOTSTARTED DDJVU_JOB_STARTED DDJVU_JOB_OK DDJVU_JOB_FAILED DDJVU_JOB_STOPPED ctypedef ddjvu_status_e ddjvu_status_t cdef enum ddjvu_message_tag_t: DDJVU_ERROR DDJVU_INFO DDJVU_NEWSTREAM DDJVU_DOCINFO DDJVU_PAGEINFO DDJVU_RELAYOUT DDJVU_REDISPLAY DDJVU_CHUNK DDJVU_THUMBNAIL DDJVU_PROGRESS cdef struct ddjvu_message_any_s: ddjvu_message_tag_t tag ddjvu_context_t* context ddjvu_document_t* document ddjvu_page_t* page ddjvu_job_t* job ctypedef ddjvu_message_any_s ddjvu_message_any_t cdef struct ddjvu_message_error_s: ddjvu_message_any_t any char* message char* function char* filename int lineno cdef struct ddjvu_message_info_s: ddjvu_message_any_t any char* message cdef struct ddjvu_message_newstream_s: ddjvu_message_any_t any int streamid char* name char* url cdef struct ddjvu_message_docinfo_s: ddjvu_message_any_t any cdef enum ddjvu_document_type_t: DDJVU_DOCTYPE_UNKNOWN DDJVU_DOCTYPE_SINGLEPAGE DDJVU_DOCTYPE_BUNDLED DDJVU_DOCTYPE_INDIRECT DDJVU_DOCTYPE_OLD_BUNDLED DDJVU_DOCTYPE_OLD_INDEXED cdef struct ddjvu_fileinfo_s: char type int pageno int size char* id char* name char* title ctypedef ddjvu_fileinfo_s ddjvu_fileinfo_t cdef struct ddjvu_pageinfo_s: int width int height int dpi int rotation int version ctypedef ddjvu_pageinfo_s ddjvu_pageinfo_t cdef struct ddjvu_message_pageinfo_s: ddjvu_message_any_t any cdef struct ddjvu_message_relayout_s: ddjvu_message_any_t any cdef struct ddjvu_message_redisplay_s: ddjvu_message_any_t any cdef struct ddjvu_message_chunk_s: ddjvu_message_any_t any char* chunkid cdef enum ddjvu_page_type_s: DDJVU_PAGETYPE_UNKNOWN DDJVU_PAGETYPE_BITONAL DDJVU_PAGETYPE_PHOTO DDJVU_PAGETYPE_COMPOUND ctypedef ddjvu_page_type_s ddjvu_page_type_t cdef enum ddjvu_page_rotation_s: DDJVU_ROTATE_0 DDJVU_ROTATE_90 DDJVU_ROTATE_180 DDJVU_ROTATE_270 ctypedef ddjvu_page_rotation_s ddjvu_page_rotation_t cdef enum ddjvu_render_mode_s: DDJVU_RENDER_COLOR DDJVU_RENDER_BLACK DDJVU_RENDER_COLORONLY DDJVU_RENDER_MASKONLY DDJVU_RENDER_BACKGROUND DDJVU_RENDER_FOREGROUND ctypedef int ddjvu_render_mode_t cdef struct ddjvu_rect_s: int x, y unsigned int w, h cdef enum ddjvu_format_style_s: DDJVU_FORMAT_BGR24 DDJVU_FORMAT_RGB24 DDJVU_FORMAT_RGBMASK16 DDJVU_FORMAT_RGBMASK32 DDJVU_FORMAT_GREY8 DDJVU_FORMAT_PALETTE8 DDJVU_FORMAT_MSBTOLSB DDJVU_FORMAT_LSBTOMSB ctypedef int ddjvu_format_style_t cdef struct ddjvu_message_thumbnail_s: ddjvu_message_any_t any int pagenum cdef struct ddjvu_message_progress_s: ddjvu_message_any_t any ddjvu_status_t status int percent cdef union ddjvu_message_s: ddjvu_message_any_s m_any ddjvu_message_error_s m_error ddjvu_message_info_s m_info ddjvu_message_newstream_s m_newstream ddjvu_message_docinfo_s m_docinfo ddjvu_message_pageinfo_s m_pageinfo ddjvu_message_chunk_s m_chunk ddjvu_message_relayout_s m_relayout ddjvu_message_redisplay_s m_redisplay ddjvu_message_thumbnail_s m_thumbnail ddjvu_message_progress_s m_progress cdef class Context cdef class Document cdef class DocumentExtension: cdef Document _document cdef class DocumentPages(DocumentExtension): pass cdef class DocumentFiles(DocumentExtension): cdef object _page_map cdef class Document: cdef ddjvu_document_t* ddjvu_document cdef Context _context cdef DocumentPages _pages cdef DocumentFiles _files cdef object _queue cdef object _condition cdef object __weakref__ cdef object __init(self, Context context, ddjvu_document_t* ddjvu_document) cdef object __clear(self) cdef class _SexprWrapper: cdef object _document_weakref cdef cexpr_t _cexpr cdef class DocumentOutline(DocumentExtension): cdef _SexprWrapper _sexpr cdef object _update_sexpr(self) cdef class Annotations: cdef _SexprWrapper _sexpr cdef object _update_sexpr(self) cdef Document _document cdef class DocumentAnnotations(Annotations): cdef int _compat cdef class Hyperlinks: cdef object _sexpr cdef class Metadata: cdef Annotations _annotations cdef object _keys cdef class File: cdef int _n cdef int _have_info cdef ddjvu_fileinfo_t ddjvu_fileinfo cdef Document _document cdef object _get_info(self) cdef class Page: cdef Document _document cdef ddjvu_pageinfo_t ddjvu_pageinfo cdef int _have_info cdef int _n cdef object _get_info(self) cdef class PageAnnotations(Annotations): cdef Page _page cdef class PageText: cdef Page _page cdef object _details cdef _SexprWrapper _sexpr cdef object _update_sexpr(self) cdef class Context: cdef ddjvu_context_t* ddjvu_context cdef object _queue cdef class PixelFormat: cdef ddjvu_format_t* ddjvu_format cdef int _bpp cdef int _dither_bpp cdef int _row_order cdef int _y_direction cdef double _gamma cdef class PixelFormatRgb(PixelFormat): cdef int _rgb cdef class PixelFormatRgbMask(PixelFormat): cdef unsigned int _params[4] cdef class PixelFormatGrey(PixelFormat): pass cdef class PixelFormatPalette(PixelFormat): cdef unsigned int _palette[216] cdef class PixelFormatPackedBits(PixelFormat): cdef int _little_endian pass cdef class Job: cdef Context _context cdef ddjvu_job_t* ddjvu_job cdef object _queue cdef object _condition cdef object __init(self, Context context, ddjvu_job_t *ddjvu_job) cdef object __clear(self) cdef object __weakref__ cdef class PageJob(Job): pass cdef class SaveJob(Job): cdef object _file cdef class DocumentDecodingJob(Job): cdef object _document cdef object __init_ddj(self, Document document) cdef class AffineTransform: cdef ddjvu_rectmapper_t* ddjvu_rectmapper cdef class Message: cdef ddjvu_message_t* ddjvu_message cdef Context _context cdef Document _document cdef PageJob _page_job cdef Job _job cdef object __init(self) cdef class ErrorMessage(Message): cdef object _message cdef object _location cdef class InfoMessage(Message): cdef object _message cdef class Stream: cdef int _streamid cdef int _open cdef Document _document cdef class NewStreamMessage(Message): cdef object _name cdef object _uri cdef Stream _stream cdef class DocInfoMessage(Message): pass cdef class PageInfoMessage(Message): pass cdef class ChunkMessage(Message): pass cdef class RelayoutMessage(ChunkMessage): pass cdef class RedisplayMessage(ChunkMessage): pass cdef class ThumbnailMessage(Message): cdef int _page_no cdef class ProgressMessage(Message): cdef int _percent cdef ddjvu_status_t _status cdef class Thumbnail: cdef Page _page # vim:ts=4 sw=4 et ft=pyrex python-djvulibre-0.3.9/djvu/dllpath.py0000644000000000000000000000320611731460234017742 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 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. ''' Module aimed to ease finding DjVuLibre DLLs in non-standard locations. ''' import os if os.name != 'nt': raise ImportError('This module is for Windows only') def guess_dll_path(): import os import _winreg as winreg registry = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) try: key = winreg.OpenKey(registry, r'Software\Microsoft\Windows\CurrentVersion\Uninstall\DjVuLibre+DjView') try: value, tp = winreg.QueryValueEx(key, 'UninstallString') if not isinstance(value, unicode): return path = os.path.dirname(value) if os.path.isfile(os.path.join(path, 'libdjvulibre.dll')): return path finally: winreg.CloseKey(key) except WindowsError: return finally: winreg.CloseKey(registry) def set_dll_search_path(path=None): if path is None: path = guess_dll_path() if path is None: return if not isinstance(path, unicode): raise TypeError import ctypes ctypes.windll.kernel32.SetDllDirectoryW(path) return path del os # vim:ts=4 sw=4 et python-djvulibre-0.3.9/djvu/const.py0000644000000000000000000001452611731460164017451 0ustar rootroot00000000000000# encoding=UTF-8 # Copyright © 2008-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. '''DjVuLibre bindings: various constants.''' import djvu.sexpr EMPTY_LIST = djvu.sexpr.Expression([]) EMPTY_OUTLINE = djvu.sexpr.Expression([djvu.sexpr.Symbol('bookmarks')]) METADATA_BIBTEX_KEYS = frozenset(djvu.sexpr.Symbol(x) for x in '''\ address annote author booktitle chapter crossref edition editor howpublished institution journal key month note number organization pages publisher school series title type volume year'''.split()) # Retrieved from METADATA_PDFINFO_KEYS = frozenset(djvu.sexpr.Symbol(x) for x in '''\ Author CreationDate Creator Keywords ModDate Producer Subject Title Trapped'''.split()) # Retrived from the PDF specification METADATA_KEYS = METADATA_BIBTEX_KEYS | METADATA_PDFINFO_KEYS class TextZoneType(djvu.sexpr.Symbol): ''' A type of a text zone. You can compare text zone types with the < operator. To create objects of this class, use the get_text_zone_type() function. ''' __cache = {} @classmethod def from_symbol(cls, symbol): return cls.__cache[symbol] def __new__(cls, value, rank): self = djvu.sexpr.Symbol.__new__(cls, value) TextZoneType.__cache[self] = self return self def __init__(self, value, rank): self.__rank = rank def __lt__(self, other): if not isinstance(other, TextZoneType): raise TypeError('cannot compare text zone type with other object') return self.__rank < other.__rank def __le__(self, other): if not isinstance(other, TextZoneType): raise TypeError('cannot compare text zone type with other object') return self.__rank <= other.__rank def __gt__(self, other): if not isinstance(other, TextZoneType): raise TypeError('cannot compare text zone type with other object') return self.__rank > other.__rank def __ge__(self, other): if not isinstance(other, TextZoneType): raise TypeError('cannot compare text zone type with other object') return self.__rank >= other.__rank def __repr__(self): return '<%s.%s: %s>' % (self.__module__, self.__class__.__name__, self) TEXT_ZONE_PAGE = TextZoneType('page', 7) TEXT_ZONE_COLUMN = TextZoneType('column', 6) TEXT_ZONE_REGION = TextZoneType('region', 5) TEXT_ZONE_PARAGRAPH = TextZoneType('para', 4) TEXT_ZONE_LINE = TextZoneType('line', 3) TEXT_ZONE_WORD = TextZoneType('word', 2) TEXT_ZONE_CHARACTER = TextZoneType('char', 1) def get_text_zone_type(symbol): return TextZoneType.from_symbol(symbol) TEXT_ZONE_SEPARATORS = \ { TEXT_ZONE_PAGE: '\f', # Form Feed (FF) TEXT_ZONE_COLUMN: '\v', # Vertical tab (VT, LINE TABULATION) TEXT_ZONE_REGION: '\035', # Group Separator (GS, INFORMATION SEPARATOR THREE) TEXT_ZONE_PARAGRAPH: '\037', # Unit Separator (US, INFORMATION SEPARATOR ONE) TEXT_ZONE_LINE: '\n', # Line Feed (LF) TEXT_ZONE_WORD: ' ', # space TEXT_ZONE_CHARACTER: '' } # 8.3.4.2 Maparea (overprinted annotations) ANNOTATION_MAPAREA = djvu.sexpr.Symbol('maparea') # 8.3.4.2 Maparea (overprinted annotations): MAPAREA_SHAPE_RECTANGLE = djvu.sexpr.Symbol('rect') MAPAREA_SHAPE_OVAL = djvu.sexpr.Symbol('oval') MAPAREA_SHAPE_POLYGON = djvu.sexpr.Symbol('poly') MAPAREA_SHAPE_LINE = djvu.sexpr.Symbol('line') MAPAREA_SHAPE_TEXT = djvu.sexpr.Symbol('text') MAPAREA_URI = MAPAREA_URL = djvu.sexpr.Symbol('url') # 8.3.4.2.3.1.1 Border type: MAPAREA_BORDER_NONE = djvu.sexpr.Symbol('none') MAPAREA_BORDER_XOR = djvu.sexpr.Symbol('xor') MAPAREA_BORDER_SOLID_COLOR = djvu.sexpr.Symbol('border') # 8.3.4.2.3.1.1 Border type: MAPAREA_BORDER_SHADOW_IN = djvu.sexpr.Symbol('shadow_in') MAPAREA_BORDER_SHADOW_OUT = djvu.sexpr.Symbol('shadow_out') MAPAREA_BORDER_ETCHED_IN = djvu.sexpr.Symbol('shadow_ein') MAPAREA_BORDER_ETCHED_OUT = djvu.sexpr.Symbol('shadow_eout') MAPAREA_SHADOW_BORDERS = (MAPAREA_BORDER_SHADOW_IN, MAPAREA_BORDER_SHADOW_OUT, MAPAREA_BORDER_ETCHED_IN, MAPAREA_BORDER_ETCHED_OUT) MAPAREA_SHADOW_BORDER_MIN_WIDTH = 1 MAPAREA_SHADOW_BORDER_MAX_WIDTH = 32 # 8.3.4.2.3.1.2 Border always visible MAPAREA_BORDER_ALWAYS_VISIBLE = djvu.sexpr.Symbol('border_avis') # 8.3.4.2.3.1.3 Highlight color and opacity: MAPAREA_HIGHLIGHT_COLOR = djvu.sexpr.Symbol('hilite') MAPAREA_OPACITY = djvu.sexpr.Symbol('opacity') MAPAREA_OPACITY_MIN = 0 MAPAREA_OPACITY_DEFAULT = 50 MAPAREA_OPACITY_MAX = 100 # 8.3.4.2.3.1.4 Line and Text parameters: MAPAREA_ARROW = djvu.sexpr.Symbol('arrow') MAPAREA_LINE_WIDTH = djvu.sexpr.Symbol('width') MAPAREA_LINE_COLOR = djvu.sexpr.Symbol('lineclr') MAPAREA_LINE_MIN_WIDTH = 1 MAPAREA_LINE_COLOR_DEFAULT = '#000000' # 8.3.4.2.3.1.4 Line and Text parameters: MAPAREA_BACKGROUND_COLOR = djvu.sexpr.Symbol('backclr') MAPAREA_TEXT_COLOR = djvu.sexpr.Symbol('textclr') MAPAREA_PUSHPIN = djvu.sexpr.Symbol('pushpin') MAPAREA_TEXT_COLOR_DEFAULT = '#000000' # 8.3.4.1 Initial Document View : ANNOTATION_BACKGROUND = djvu.sexpr.Symbol('background') # 8.3.4.1.1 Background Color ANNOTATION_ZOOM = djvu.sexpr.Symbol('zoom') # 8.3.4.1.2 Initial Zoom ANNOTATION_MODE = djvu.sexpr.Symbol('mode') # 8.3.4.1.3 Initial Display level ANNOTATION_ALIGN = djvu.sexpr.Symbol('align') # 8.3.4.1.4 Alignment # djvuchanges.txt, sections "Metadata Annotations" and "Document Annotations and Metadata": ANNOTATION_METADATA = djvu.sexpr.Symbol('metadata') # 8.3.4.3 Printed headers and footers: ANNOTATION_PRINTED_HEADER = djvu.sexpr.Symbol('phead') ANNOTATION_PRINTED_FOOTER = djvu.sexpr.Symbol('pfoot') PRINTER_HEADER_ALIGN_LEFT = PRINTED_FOOTER_ALIGN_LEFT = djvu.sexpr.Symbol('left') PRINTER_HEADER_ALIGN_CENTER = PRINTED_FOOTER_ALIGN_CENTER = djvu.sexpr.Symbol('center') PRINTER_HEADER_ALIGN_RIGHT = PRINTED_FOOTER_ALIGN_RIGHT = djvu.sexpr.Symbol('right') # vim:ts=4 sw=4 et python-djvulibre-0.3.9/djvu/decode.pyx0000644000000000000000000033013511731457471017742 0ustar rootroot00000000000000# Copyright © 2007-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. #cython: autotestdict=False ''' DjVuLibre bindings: module for efficiently decoding and displaying DjVu documents. Summary ------- The DDJVU API provides for efficiently decoding and displaying DjVu documents. It provides for displaying images without waiting for the complete DjVu data. Images can be displayed as soon as sufficient data is available. A higher quality image might later be displayed when further data is available. The DjVu library achieves this using a complicated scheme involving multiple threads. The DDJVU API hides this complexity with a familiar event model. ''' include 'common.pxi' cdef object weakref import weakref cdef object thread IF PY3K: import _thread as thread ELSE: import thread cdef object Queue, Empty IF PY3K: from queue import Queue, Empty ELSE: from Queue import Queue, Empty cdef object Condition from threading import Condition cdef object imap, izip IF PY3K: imap = map izip = zip ELSE: from itertools import imap, izip cdef object sys, devnull, format_exc import sys from os import devnull from traceback import format_exc # The two lines below are solely to work-around Cython bug: # http://bugs.debian.org/620859 cdef object MemoryError IF PY3K: from builtins import MemoryError ELSE: from exceptions import MemoryError cdef object StringIO IF PY3K: from io import StringIO ELSE: from cStringIO import StringIO cdef object Symbol, SymbolExpression, InvalidExpression from djvu.sexpr import Symbol, SymbolExpression, InvalidExpression cdef object the_sentinel the_sentinel = object() cdef object _context_loft, _document_loft, _document_weak_loft, _job_loft, _job_weak_loft cdef Lock loft_lock _context_loft = {} _document_loft = set() _document_weak_loft = weakref.WeakValueDictionary() _job_loft = set() _job_weak_loft = weakref.WeakValueDictionary() loft_lock = allocate_lock() cdef extern from 'libdjvu/ddjvuapi.h': ddjvu_context_t* ddjvu_context_create(char* program_name) nogil void ddjvu_context_release(ddjvu_context_t* context) nogil void ddjvu_cache_set_size(ddjvu_context_t* context, unsigned long cachesize) nogil unsigned long ddjvu_cache_get_size(ddjvu_context_t* context) nogil void ddjvu_cache_clear(ddjvu_context_t* context) nogil ddjvu_message_t* ddjvu_message_peek(ddjvu_context_t* context) nogil ddjvu_message_t* ddjvu_message_wait(ddjvu_context_t* context) nogil void ddjvu_message_pop(ddjvu_context_t* context) nogil void ddjvu_message_set_callback(ddjvu_context_t* context, ddjvu_message_callback_t callback, void* closure) nogil ddjvu_status_t ddjvu_job_status(ddjvu_job_t* job) nogil int ddjvu_job_done(ddjvu_job_t* job) nogil int ddjvu_job_error(ddjvu_job_t* job) nogil void ddjvu_job_stop(ddjvu_job_t* job) nogil void ddjvu_job_set_user_data(ddjvu_job_t* job, void* userdata) nogil void* ddjvu_job_get_user_data(ddjvu_job_t* job) nogil void ddjvu_job_release(ddjvu_job_t* job) nogil ddjvu_document_t* ddjvu_document_create(ddjvu_context_t* context, char* url, int cache) nogil ddjvu_document_t* ddjvu_document_create_by_filename(ddjvu_context_t* context, char* filename, int cache) nogil ddjvu_job_t* ddjvu_document_job(ddjvu_document_t* document) nogil void ddjvu_document_release(ddjvu_document_t* document) nogil void ddjvu_document_set_user_data(ddjvu_document_t* document, void* userdata) nogil void* ddjvu_document_get_user_data(ddjvu_document_t* document) nogil ddjvu_status_t ddjvu_document_decoding_status(ddjvu_document_t* document) nogil int ddjvu_document_decoding_done(ddjvu_document_t* document) nogil int ddjvu_document_decoding_error(ddjvu_document_t* document) nogil void ddjvu_stream_write(ddjvu_document_t* document, int streamid, char* data, unsigned long datalen) nogil void ddjvu_stream_close(ddjvu_document_t* document, int streamid, int stop) nogil ddjvu_document_type_t ddjvu_document_get_type(ddjvu_document_t* document) nogil int ddjvu_document_get_pagenum(ddjvu_document_t* document) nogil int ddjvu_document_get_filenum(ddjvu_document_t* document) nogil ddjvu_status_t ddjvu_document_get_fileinfo(ddjvu_document_t* document, int fileno, ddjvu_fileinfo_t* info) nogil int ddjvu_document_check_pagedata(ddjvu_document_t* document, int pageno) nogil ddjvu_status_t ddjvu_document_get_pageinfo(ddjvu_document_t* document, int pageno, ddjvu_pageinfo_t* info) nogil ddjvu_status_t ddjvu_document_get_pageinfo_imp(ddjvu_document_t* document, int pageno, ddjvu_pageinfo_t* info, unsigned int infosz) nogil char* ddjvu_document_get_pagedump(ddjvu_document_t* document, int pageno) nogil char* ddjvu_document_get_filedump(ddjvu_document_t* document, int fileno) nogil ddjvu_page_t* ddjvu_page_create_by_pageno(ddjvu_document_t* document, int pageno) nogil ddjvu_job_t* ddjvu_page_job(ddjvu_page_t* page) nogil void ddjvu_page_release(ddjvu_page_t* page) nogil void ddjvu_page_set_user_data(ddjvu_page_t* page, void* userdata) nogil void* ddjvu_page_get_user_data(ddjvu_page_t* page) nogil ddjvu_status_t ddjvu_page_decoding_status(ddjvu_page_t* page) nogil int ddjvu_page_decoding_done(ddjvu_page_t* page) nogil int ddjvu_page_decoding_error(ddjvu_page_t* page) nogil int ddjvu_page_get_width(ddjvu_page_t* page) nogil int ddjvu_page_get_height(ddjvu_page_t* page) nogil int ddjvu_page_get_resolution(ddjvu_page_t* page) nogil double ddjvu_page_get_gamma(ddjvu_page_t* page) nogil int ddjvu_page_get_version(ddjvu_page_t* page) nogil int ddjvu_code_get_version() nogil ddjvu_page_type_t ddjvu_page_get_type(ddjvu_page_t* page) nogil void ddjvu_page_set_rotation(ddjvu_page_t* page, ddjvu_page_rotation_t rot) nogil ddjvu_page_rotation_t ddjvu_page_get_rotation(ddjvu_page_t* page) nogil ddjvu_page_rotation_t ddjvu_page_get_initial_rotation(ddjvu_page_t* page) nogil int ddjvu_page_render(ddjvu_page_t* page, ddjvu_render_mode_t mode, ddjvu_rect_t* pagerect, ddjvu_rect_t* renderrect, ddjvu_format_t* pixelformat, unsigned long rowsize, char* imagebuffer) nogil ddjvu_rectmapper_t* ddjvu_rectmapper_create(ddjvu_rect_t* input, ddjvu_rect_t* output) nogil void ddjvu_rectmapper_modify(ddjvu_rectmapper_t* mapper, int rotation, int mirrorx, int mirrory) nogil void ddjvu_rectmapper_release(ddjvu_rectmapper_t* mapper) nogil void ddjvu_map_point(ddjvu_rectmapper_t* mapper, int* x, int* y) nogil void ddjvu_map_rect(ddjvu_rectmapper_t* mapper, ddjvu_rect_t* rect) nogil void ddjvu_unmap_point(ddjvu_rectmapper_t* mapper, int* x, int* y) nogil void ddjvu_unmap_rect(ddjvu_rectmapper_t* mapper, ddjvu_rect_t* rect) nogil ddjvu_format_t* ddjvu_format_create(ddjvu_format_style_t style, int nargs, unsigned int* args) nogil void ddjvu_format_set_row_order(ddjvu_format_t* format, int top_to_bottom) nogil void ddjvu_format_set_y_direction(ddjvu_format_t* format, int top_to_bottom) nogil void ddjvu_format_set_ditherbits(ddjvu_format_t* format, int bits) nogil void ddjvu_format_set_gamma(ddjvu_format_t* format, double gamma) nogil void ddjvu_format_release(ddjvu_format_t* format) nogil ddjvu_status_t ddjvu_thumbnail_status(ddjvu_document_t* document, int pagenum, int start) nogil int ddjvu_thumbnail_render(ddjvu_document_t* document, int pagenum, int* wptr, int* hptr, ddjvu_format_t* pixelformat, unsigned long rowsize, char* imagebuffer) nogil ddjvu_job_t* ddjvu_document_print(ddjvu_document_t* document, FILE* output, int optc, char** optv) nogil ddjvu_job_t* ddjvu_document_save(ddjvu_document_t* document, FILE* output, int optc, char** optv) nogil void ddjvu_miniexp_release(ddjvu_document_t* document, cexpr_t expr) nogil cexpr_t ddjvu_document_get_outline(ddjvu_document_t* document) nogil cexpr_t ddjvu_document_get_anno(ddjvu_document_t* document, int compat) nogil cexpr_t ddjvu_document_get_pagetext(ddjvu_document_t* document, int pageno, char* maxdetail) nogil cexpr_t ddjvu_document_get_pageanno(ddjvu_document_t* document, int pageno) nogil char* ddjvu_anno_get_bgcolor(cexpr_t annotations) nogil char* ddjvu_anno_get_zoom(cexpr_t annotations) nogil char* ddjvu_anno_get_mode(cexpr_t annotations) nogil char* ddjvu_anno_get_horizalign(cexpr_t annotations) nogil char* ddjvu_anno_get_vertalign(cexpr_t annotations) nogil cexpr_t* ddjvu_anno_get_hyperlinks(cexpr_t annotations) nogil cexpr_t* ddjvu_anno_get_metadata_keys(cexpr_t annotations) nogil char* ddjvu_anno_get_metadata(cexpr_t annotations, cexpr_t key) nogil cdef extern from 'unistd.h': int dup(int) FILE *fdopen(int, char*) int fclose(FILE *) IF HAVE_LANGINFO_H: cdef extern from 'langinfo.h': ctypedef enum nl_item: CODESET char *nl_langinfo(nl_item item) DDJVU_VERSION = ddjvu_code_get_version() FILE_TYPE_PAGE = 'P' FILE_TYPE_THUMBNAILS = 'T' FILE_TYPE_INCLUDE = 'I' DOCUMENT_TYPE_UNKNOWN = DDJVU_DOCTYPE_UNKNOWN DOCUMENT_TYPE_SINGLE_PAGE = DDJVU_DOCTYPE_SINGLEPAGE DOCUMENT_TYPE_BUNDLED = DDJVU_DOCTYPE_BUNDLED DOCUMENT_TYPE_INDIRECT = DDJVU_DOCTYPE_INDIRECT DOCUMENT_TYPE_OLD_BUNDLED = DDJVU_DOCTYPE_OLD_BUNDLED DOCUMENT_TYPE_OLD_INDEXED = DDJVU_DOCTYPE_OLD_INDEXED cdef object check_sentinel(self, kwargs): if kwargs.get('sentinel') is not the_sentinel: raise_instantiation_error(type(self)) cdef object write_unraisable_exception(object cause): try: message = format_exc() except AttributeError: # This mostly happens during interpreter cleanup. # It's worthless to try to recover. raise SystemExit sys.stderr.write('Unhandled exception in thread started by %r\n%s\n' % (cause, message)) cdef class _FileWrapper: cdef object _file cdef FILE *cfile def __cinit__(self, object file, object mode): self._file = file self.cfile = NULL if not is_file(file): raise TypeError('file must be a real file object') IF PY3K: fd = file_to_fd(file) if fd == -1: posix_error(OSError) fd = dup(fd) if fd == -1: posix_error(OSError) self.cfile = fdopen(fd, mode) if self.cfile == NULL: posix_error(OSError) ELSE: self.cfile = file_to_cfile(file) cdef object close(self): IF PY3K: cdef int rc if self.cfile == NULL: return rc = fclose(self.cfile) self.cfile = NULL if rc != 0: posix_error(OSError) ELSE: if self._file is not None: self._file.flush() self._file = None self.cfile = NULL IF PY3K: def __dealloc__(self): cdef int rc if self.cfile == NULL: return rc = fclose(self.cfile) # XXX It's too late to handle errors. class NotAvailable(Exception): ''' A resource not (yet) available. ''' cdef object _NotAvailable_ _NotAvailable_ = NotAvailable class DjVuLibreBug(Exception): ''' A DjVuLibre bug was encountered. ''' def __init__(self, debian_bug_no): Exception.__init__( self, 'A DjVuLibre bug has been encountered.\n' 'See for details.\n' 'Please upgrade your DjVuLibre.' % (debian_bug_no,) ) cdef class DocumentExtension: property document: ''' Return the concerned Document. ''' def __get__(self): return self._document cdef class DocumentPages(DocumentExtension): ''' Pages of a document. Use document.pages to obtain instances of this class. Page indexing is zero-based, i.e. pages[0] stands for the very first page. len(pages) might return 1 when called before receiving a DocInfoMessage. ''' def __cinit__(self, Document document not None, **kwargs): check_sentinel(self, kwargs) self._document = document def __len__(self): return ddjvu_document_get_pagenum(self._document.ddjvu_document) def __getitem__(self, key): if is_int(key): if key < 0 or key >= len(self): raise IndexError('page number out of range') return Page(self.document, key) else: raise TypeError('page numbers must be integers') cdef class Page: ''' Page of a document. Use document.pages[N] to obtain instances of this class. ''' def __cinit__(self, Document document not None, int n): self._document = document self._have_info = 0 self._n = n property document: ''' Return the Document which includes the page. ''' def __get__(self): return self._document property file: ''' Return a File associated with the page. ''' def __get__(self): return self._document.files[self] property n: ''' Return the page number. Page indexing is zero-based, i.e. 0 stands for the very first page. ''' def __get__(self): return self._n property thumbnail: ''' Return a Thumbnail for the page. ''' def __get__(self): return Thumbnail(self) cdef object _get_info(self): cdef ddjvu_status_t status if self._have_info: return status = ddjvu_document_get_pageinfo(self._document.ddjvu_document, self._n, &self.ddjvu_pageinfo) ex = JobException_from_c(status) if ex is JobOK: return elif ex is JobStarted: raise _NotAvailable_ else: raise ex def get_info(self, wait=1): ''' P.get_info(wait=True) -> None Attempt to obtain information about the page without decoding the page. If wait is true, wait until the information is available. If the information is not available, raise NotAvailable exception. Then, start fetching the page data, which causes emission of PageInfoMessage messages with empty .page_job. Possible exceptions: NotAvailable, JobFailed. ''' cdef ddjvu_status_t status if self._have_info: return if not wait: return self._get_info() while 1: self._document._condition.acquire() try: status = ddjvu_document_get_pageinfo(self._document.ddjvu_document, self._n, &self.ddjvu_pageinfo) ex = JobException_from_c(status) if ex is JobOK: self._have_info = 1 return elif ex is JobStarted: self._document._condition.wait() else: raise ex finally: self._document._condition.release() property width: ''' Return the page width, in pixels. Possible exceptions: NotAvailable, JobFailed. See Page.get_info() for details. ''' def __get__(self): self._get_info() return self.ddjvu_pageinfo.width property height: ''' Return the page height, in pixels. Possible exceptions: NotAvailable, JobFailed. See Page.get_info() for details. ''' def __get__(self): self._get_info() return self.ddjvu_pageinfo.height property size: ''' page.size == (page.width, page.height) Possible exceptions: NotAvailable, JobFailed. See Page.get_info() for details. ''' def __get__(self): self._get_info() return self.ddjvu_pageinfo.width, self.ddjvu_pageinfo.height property dpi: ''' Return the page resolution, in pixels per inch. Possible exceptions: NotAvailable, JobFailed. See Page.get_info() for details. ''' def __get__(self): self._get_info() return self.ddjvu_pageinfo.dpi property rotation: ''' Return the initial page rotation, in degrees. Possible exceptions: NotAvailable, JobFailed. See Page.get_info() for details. ''' def __get__(self): self._get_info() return self.ddjvu_pageinfo.rotation * 90 property version: ''' Return the page version. Possible exceptions: NotAvailable, JobFailed. See Page.get_info() for details. ''' def __get__(self): self._get_info() return self.ddjvu_pageinfo.version property dump: ''' Return a text describing the contents of the page using the same format as the djvudump command. If the information is not available, raise NotAvailable exception. Then PageInfoMessage messages with empty page_job may be emitted. Possible exceptions: NotAvailable. ''' def __get__(self): cdef char* s s = ddjvu_document_get_pagedump(self._document.ddjvu_document, self._n) if s == NULL: raise _NotAvailable_ try: return decode_utf8(s) finally: libc_free(s) def decode(self, wait=1): ''' P.decode(wait=True) -> a PageJob Initiate data transfer and decoding threads for the page. If wait is true, wait until the job is done. Possible exceptions: - NotAvailable (if called before receiving the DocInfoMessage). - JobFailed (if document decoding failed). ''' cdef PageJob job cdef ddjvu_job_t* ddjvu_job with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: ddjvu_job = ddjvu_page_create_by_pageno(self._document.ddjvu_document, self._n) if ddjvu_job == NULL: raise _NotAvailable_ if ddjvu_document_decoding_error(self._document.ddjvu_document): raise JobException_from_c(ddjvu_document_decoding_status(self._document.ddjvu_document)) job = PageJob(sentinel = the_sentinel) job.__init(self._document._context, ddjvu_job) finally: release_lock(loft_lock) if wait: job.wait() return job property annotations: ''' Return PageAnnotations for the page. ''' def __get__(self): return PageAnnotations(self) property text: ''' Return PageText for the page. ''' def __get__(self): return PageText(self) def __repr__(self): return '%s(%r, %r)' % (get_type_name(Page), self._document, self._n) cdef class Thumbnail: ''' Thumbnail for a page. Use page.thumbnail to obtain instances of this class. ''' def __cinit__(self, Page page not None): self._page = page property page: ''' Return the page. ''' def __get__(self): return self._page property status: ''' Determine whether the thumbnail is available. Return a JobException subclass indicating the current job status. ''' def __get__(self): return JobException_from_c(ddjvu_thumbnail_status(self._page._document.ddjvu_document, self._page._n, 0)) def calculate(self): ''' T.calculate() -> a JobException Determine whether the thumbnail is available. If it's not, initiate the thumbnail calculating job. Regardless of its success, the completion of the job is signalled by a subsequent ThumbnailMessage. Return a JobException subclass indicating the current job status. ''' return JobException_from_c(ddjvu_thumbnail_status(self._page._document.ddjvu_document, self._page._n, 1)) def render(self, size, PixelFormat pixel_format not None, long row_alignment=1, dry_run=0, buffer=None): ''' T.render((w0, h0), pixel_format, row_alignment=1, dry_run=False, buffer=None) -> ((w1, h1, row_size), data) Render the thumbnail: * not larger than w0 x h0 pixels; * using the pixel_format pixel format; * with each row starting at row_alignment bytes boundary; * into the provided buffer or to a newly created string. Raise NotAvailable when no thumbnail is available. Otherwise, return a ((w1, h1, row_size), data) tuple: * w1 and h1 are actual thumbnail dimensions in pixels (w1 <= w0 and h1 <= h0); * row_size is length of each image row, in bytes; * data is None if dry_run is true; otherwise is contains the actual image data. ''' cdef int iw, ih cdef long w, h, row_size cdef void* memory if row_alignment <= 0: raise ValueError('row_alignment must be a positive integer') w, h = size if w <= 0 or h <= 0: raise ValueError('size width/height must a positive integer') iw, ih = w, h if iw != w or ih != h: raise OverflowError('size width/height is too large') row_size = calculate_row_size(w, row_alignment, pixel_format._bpp) if dry_run: result = None memory = NULL else: result = allocate_image_memory(row_size, h, buffer, &memory) if ddjvu_thumbnail_render(self._page._document.ddjvu_document, self._page._n, &iw, &ih, pixel_format.ddjvu_format, row_size, memory): return (iw, ih, row_size), result else: raise _NotAvailable_ def __repr__(self): return '%s(%r)' % (get_type_name(Thumbnail), self._page) cdef class DocumentFiles(DocumentExtension): ''' Component files of a document. Use document.files to obtain instances of this class. File indexing is zero-based, i.e. files[0] stands for the very first file. len(files) might raise NotAvailable when called before receiving a DocInfoMessage. ''' def __cinit__(self, Document document not None, **kwargs): check_sentinel(self, kwargs) self._page_map = None self._document = document def __len__(self): cdef int result result = ddjvu_document_get_filenum(self._document.ddjvu_document) if result is None: raise _NotAvailable_ return result def __getitem__(self, key): cdef int i if is_int(key): if key < 0 or key >= len(self): raise IndexError('file number out of range') return File(self._document, key, sentinel = the_sentinel) elif typecheck(key, Page): if (key)._document is not self._document: raise KeyError(key) if self._page_map is None: self._page_map = {} for i from 0 <= i < len(self): file = File(self._document, i, sentinel = the_sentinel) n_page = file.n_page if n_page is not None: self._page_map[n_page] = file try: return self._page_map[(key)._n] except KeyError: raise KeyError(key) else: raise TypeError('DocumentFiles indices must be integers or Page instances') cdef class File: ''' Component file of a document. Use document.files[N] to obtain instances of this class. ''' def __cinit__(self, Document document not None, int n, **kwargs): check_sentinel(self, kwargs) self._document = document self._have_info = 0 self._n = n property document: '''Return the Document which includes the component file.''' def __get__(self): return self._document property n: ''' Return the component file number. File indexing is zero-based, i.e. 0 stands for the very first file. ''' def __get__(self): return self._n cdef object _get_info(self): cdef ddjvu_status_t status if self._have_info: return status = ddjvu_document_get_fileinfo(self._document.ddjvu_document, self._n, &self.ddjvu_fileinfo) ex = JobException_from_c(status) if ex is JobOK: return elif ex is JobStarted: raise _NotAvailable_ else: raise ex def get_info(self, wait=1): ''' F.get_info(wait=True) -> None Attempt to obtain information about the component file. If wait is true, wait until the information is available. Possible exceptions: NotAvailable, JobFailed. ''' cdef ddjvu_status_t status if self._have_info: return if not wait: return self._get_info() while 1: self._document._condition.acquire() try: status = ddjvu_document_get_fileinfo(self._document.ddjvu_document, self._n, &self.ddjvu_fileinfo) ex = JobException_from_c(status) if ex is JobOK: self._have_info = 1 return elif ex is JobStarted: self._document._condition.wait() else: raise ex finally: self._document._condition.release() property type: ''' Return the type of the compound file: * FILE_TYPE_PAGE, * FILE_TYPE_THUMBNAILS, * FILE_TYPE_INCLUDE. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): cdef char buffer[2] self._get_info() buffer[0] = self.ddjvu_fileinfo.type buffer[1] = '\0' return charp_to_string(buffer) property n_page: ''' Return the page number, or None when not applicable. Page indexing is zero-based, i.e. 0 stands for the very first page. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): self._get_info() if self.ddjvu_fileinfo.pageno < 0: return else: return self.ddjvu_fileinfo.pageno property page: ''' Return the page, or None when not applicable. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): self._get_info() if self.ddjvu_fileinfo.pageno < 0: return else: return self._document.pages[self.ddjvu_fileinfo.pageno] property size: ''' Return the compound file size, or None when unknown. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): self._get_info() if self.ddjvu_fileinfo.size < 0: return else: return self.ddjvu_fileinfo.size property id: ''' Return the compound file identifier, or None. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): self._get_info() cdef char* result result = self.ddjvu_fileinfo.id if result == NULL: return else: return decode_utf8(result) property name: ''' Return the compound file name, or None. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): self._get_info() cdef char* result result = self.ddjvu_fileinfo.name if result == NULL: return else: return decode_utf8(result) property title: ''' Return the compound file title, or None. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): self._get_info() cdef char* result result = self.ddjvu_fileinfo.title if result == NULL: return else: return decode_utf8(result) property dump: ''' Return a text describing the contents of the file using the same format as the djvudump command. If the information is not available, raise NotAvailable exception. Then, PageInfoMessage messages with empty page_job may be emitted. Possible exceptions: NotAvailable. ''' def __get__(self): cdef char* s s = ddjvu_document_get_filedump(self._document.ddjvu_document, self._n) if s == NULL: raise _NotAvailable_ try: return decode_utf8(s) finally: libc_free(s) cdef object pages_to_opt(object pages, int sort_uniq): if sort_uniq: pages = sorted(frozenset(pages)) else: pages = list(pages) for i from 0 <= i < len(pages): if not is_int(pages[i]): raise TypeError('page numbers must be integers') if pages[i] < 0: raise ValueError('page number out of range') pages[i] = pages[i] + 1 result = '--pages=' + (','.join(imap(str, pages))) if is_unicode(result): result = encode_utf8(result) return result PRINT_ORIENTATION_AUTO = None PRINT_ORIENTATION_LANDSCAPE = 'landscape' PRINT_ORIENTATION_PORTRAIT = 'portrait' cdef object PRINT_RENDER_MODE_MAP PRINT_RENDER_MODE_MAP = \ { DDJVU_RENDER_COLOR: None, DDJVU_RENDER_BLACK: 'bw', DDJVU_RENDER_FOREGROUND: 'fore', DDJVU_RENDER_BACKGROUND: 'back' } PRINT_BOOKLET_NO = None PRINT_BOOKLET_YES = 'yes' PRINT_BOOKLET_RECTO = 'recto' PRINT_BOOKLET_VERSO = 'verso' cdef object PRINT_BOOKLET_OPTIONS PRINT_BOOKLET_OPTIONS = (PRINT_BOOKLET_NO, PRINT_BOOKLET_YES, PRINT_BOOKLET_RECTO, PRINT_BOOKLET_VERSO) cdef class SaveJob(Job): ''' Document saving job. Use document.save(...) to obtain instances of this class. ''' def __cinit__(self, **kwargs): self._file = None def wait(self): Job.wait(self) # Ensure that the underlying file is flushed. # FIXME: In Python 3, the file might be never flushed if you don't use wait()! if self._file is not None: (<_FileWrapper> self._file).close() self._file = None cdef class DocumentDecodingJob(Job): ''' Document decoding job. Use document.decoding_job to obtain instances of this class. ''' cdef object __init_ddj(self, Document document): self._context = document._context self._document = document self._condition = document._condition self._queue = document._queue self.ddjvu_job = document.ddjvu_document def __dealloc__(self): self.ddjvu_job = NULL # Don't allow Job.__dealloc__ to release the job. def __repr__(self): return '<%s for %r>' % (get_type_name(DocumentDecodingJob), self._document) cdef class Document: ''' DjVu document. Use context.new_document(...) to obtain instances of this class. ''' def __cinit__(self, **kwargs): self.ddjvu_document = NULL check_sentinel(self, kwargs) self._pages = DocumentPages(self, sentinel = the_sentinel) self._files = DocumentFiles(self, sentinel = the_sentinel) self._context = None self._queue = Queue() self._condition = Condition() cdef object __init(self, Context context, ddjvu_document_t *ddjvu_document): # Assumption: loft_lock is already acquired. assert context != None and ddjvu_document != NULL self.ddjvu_document = ddjvu_document self._context = context _document_loft.add(self) _document_weak_loft[voidp_to_int(ddjvu_document)] = self cdef object __clear(self): with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: _document_loft.discard(self) finally: release_lock(loft_lock) property decoding_status: ''' Return a JobException subclass indicating the decoding job status. ''' def __get__(self): return JobException_from_c(ddjvu_document_decoding_status(self.ddjvu_document)) property decoding_error: ''' Indicate whether the decoding job failed. ''' def __get__(self): return bool(ddjvu_document_decoding_error(self.ddjvu_document)) property decoding_done: ''' Indicate whether the decoding job is done. ''' def __get__(self): return bool(ddjvu_document_decoding_done(self.ddjvu_document)) property decoding_job: ''' Return the DocumentDecodingJob. ''' def __get__(self): cdef DocumentDecodingJob job job = DocumentDecodingJob(sentinel = the_sentinel) job.__init_ddj(self) return job property type: ''' Return the type of the document. The following values are possible: * DOCUMENT_TYPE_UNKNOWN; * DOCUMENT_TYPE_SINGLE_PAGE: single-page document; * DOCUMENT_TYPE_BUNDLED: bundled mutli-page document; * DOCUMENT_TYPE_INDIRECT: indirect multi-page document; * (obsolete) DOCUMENT_TYPE_OLD_BUNDLED, * (obsolete) DOCUMENT_TYPE_OLD_INDEXED. Before receiving the DocInfoMessage, DOCUMENT_TYPE_UNKNOWN may be returned. ''' def __get__(self): return ddjvu_document_get_type(self.ddjvu_document) property pages: ''' Return the DocumentPages. ''' def __get__(self): return self._pages property files: ''' Return the DocumentPages. ''' def __get__(self): return self._files property outline: ''' Return the DocumentOutline. ''' def __get__(self): return DocumentOutline(self) property annotations: ''' Return the DocumentAnnotations. ''' def __get__(self): return DocumentAnnotations(self) def __dealloc__(self): if self.ddjvu_document == NULL: return ddjvu_document_release(self.ddjvu_document) def save(self, file=None, indirect=None, pages=None, wait=1): ''' D.save(file=None, indirect=None, pages=, wait=True) -> a SaveJob Save the document as: * a bundled DjVu file or; * an indirect DjVu document with index file name indirect. pages argument specifies a subset of saved pages. If wait is true, wait until the job is done. .. warning:: Due to a DjVuLibre (<= 3.5.20) bug, this method may be broken. See http://bugs.debian.org/467282 for details. ''' cdef char * optv[2] cdef int optc cdef SaveJob job optc = 0 cdef FILE* output cdef Py_ssize_t i cdef _FileWrapper file_wrapper if indirect is None: file_wrapper = _FileWrapper(file, "wb") output = file_wrapper.cfile else: if file is not None: raise TypeError('file must be None if indirect is specified') if not is_string(indirect): raise TypeError('indirect must be a string') # XXX ddjvu API documentation says that output should be NULL, # but we'd like to spot the DjVuLibre bug open(indirect, 'wb').close() file = open(devnull, 'wb') file_wrapper = _FileWrapper(file, "wb") output = file_wrapper.cfile s1 = '--indirect=' + indirect if is_unicode(s1): s1 = encode_utf8(s1) optv[optc] = s1 optc = optc + 1 if pages is not None: s2 = pages_to_opt(pages, 1) optv[optc] = s2 optc = optc + 1 with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: job = SaveJob(sentinel = the_sentinel) job.__init(self._context, ddjvu_document_save(self.ddjvu_document, output, optc, optv)) job._file = file_wrapper finally: release_lock(loft_lock) if wait: job.wait() if indirect is not None: file = open(indirect, 'rb') file.seek(0, 2) if file.tell() == 0: raise DjVuLibreBug(467282) return job def export_ps(self, file, pages=None, eps=0, level=None, orientation=PRINT_ORIENTATION_AUTO, mode=DDJVU_RENDER_COLOR, zoom=None, color=1, srgb=1, gamma=None, copies=1, frame=0, crop_marks=0, text=0, booklet=PRINT_BOOKLET_NO, booklet_max=0, booklet_align=0, booklet_fold=(18, 200), wait=1): ''' D.export_ps(file, pages=, ..., wait=True) -> a Job Convert the document into PostScript. pages argument specifies a subset of saved pages. If wait is true, wait until the job is done. Additional options ------------------ eps Produce an *Encapsulated* PostScript file. Encapsulated PostScript files are suitable for embedding images into other documents. Encapsulated PostScript file can only contain a single page. Setting this option overrides the options copies, orientation, zoom, crop_marks, and booklet. level Selects the language level of the generated PostScript. Valid language levels are 1, 2, and 3. Level 3 produces the most compact and fast printing PostScript files. Some of these files however require a very modern printer. Level 2 is the default value. The generated PostScript files are almost as compact and work with all but the oldest PostScript printers. Level 1 can be used as a last resort option. orientation Specifies the pages orientation: PRINT_ORIENTATION_AUTO automatic PRINT_ORIENTATION_PORTRAIT portrait PRINT_ORIENTATION_LANDSCAPE landscape mode Specifies how pages should be decoded: RENDER_COLOR render all the layers of the DjVu documents RENDER_BLACK render only the foreground layer mask RENDER_FOREGROUND render only the foreground layer RENDER_BACKGROUND redner only the background layer zoom Specifies a zoom factor. The default zoom factor scales the image to fit the page. color Specifies whether to generate a color or a gray scale PostScript file. A gray scale PostScript files are smaller and marginally more portable. srgb The default value, True, generates a PostScript file using device independent colors in compliance with the sRGB specification. Modern printers then produce colors that match the original as well as possible. Specifying a false value generates a PostScript file using device dependent colors. This is sometimes useful with older printers. You can then use the gamma option to tune the output colors. gamma Specifies a gamma correction factor for the device dependent PostScript colors. Argument must be in range 0.3 to 5.0. Gamma correction normally pertains to cathodic screens only. It gets meaningful for printers because several models interpret device dependent RGB colors by emulating the color response of a cathodic tube. copies Specifies the number of copies to print. frame, If true, generate a thin gray border representing the boundaries of the document pages. crop_marks If true, generate crop marks indicating where pages should be cut. text Generate hidden text. This option is deprecated. See also the warning below. booklet * PRINT_BOOKLET_NO Disable booklet mode. This is the default. * PRINT_BOOKLET_YES: Enable recto/verse booklet mode. * PRINT_BOOKLET_RECTO Enable recto booklet mode. * PRINT_BOOKLET_VERSO Enable verso booklet mode. booklet_max Specifies the maximal number of pages per booklet. A single printout might then be composed of several booklets. The argument is rounded up to the next multiple of 4. Specifying 0 sets no maximal number of pages and ensures that the printout will produce a single booklet. This is the default. booklet_align Specifies a positive or negative offset applied to the verso of each sheet. The argument is expressed in points[1]_. This is useful with certain printers to ensure that both recto and verso are properly aligned. The default value is 0. booklet_fold (= (base, increment)) Specifies the extra margin left between both pages on a single sheet. The base value is expressed in points[1]_. This margin is incremented for each outer sheet by value expressed in millipoints. The default value is (18, 200). .. [1] 1 pt = 1/72 in = 0.3528 mm **Warning*** ------------ Due to a DjVuLibre (<= 3.5.20) bug, this method may be broken. See http://bugs.debian.org/469122 for details. ''' cdef FILE* output cdef SaveJob job cdef _FileWrapper file_wrapper options = [] file_wrapper = _FileWrapper(file, "wb") output = file_wrapper.cfile if pages is not None: list_append(options, pages_to_opt(pages, 0)) if eps: list_append(options, '--format=eps') if level is not None: if not is_int(level): raise TypeError('level must be an integer') list_append(options, '--level=%d' % level) if orientation is not None: if not is_string(orientation): raise TypeError('orientation must be a string or none') list_append(options, '--orientation=' + orientation) if not is_int(mode): raise TypeError('mode must be an integer') try: mode = PRINT_RENDER_MODE_MAP[mode] if mode is not None: list_append(options, '--mode=' + mode) except KeyError: raise ValueError('mode must be equal to RENDER_COLOR, or RENDER_BLACK, or RENDER_FOREGROUND, or RENDER_BACKGROUND') if zoom is not None: if not is_int(zoom): raise TypeError('zoom must be an integer or none') list_append(options, '--zoom=%d' % zoom) if not color: list_append(options, '--color=no') if not srgb: list_append(options, '--srgb=no') if gamma is not None: if not is_int(gamma) and not is_float(gamma): raise TypeError('gamma must be a number or none') list_append(options, '--gamma=%.16f' % gamma) if not is_int(copies): raise TypeError('copies must be an integer') if copies != 1: list_append(options, '--options=%d' % copies) if frame: list_append(options, '--frame') if crop_marks: list_append(options, '--cropmarks') if text: list_append(options, '--text') if booklet is not None: if not is_string(booklet): raise TypeError('booklet must be a string or none') if options not in PRINT_BOOKLET_OPTIONS: raise ValueError('booklet must be equal to PRINT_BOOKLET_NO, or PRINT_BOOKLET_YES, or PRINT_BOOKLET_VERSO, or PRINT_BOOKLET_RECTO') list_append(options, '--booklet=' + booklet) if not is_int(booklet_max): raise TypeError('booklet_max must be an integer') if booklet_max: list_append(options, '--bookletmax=%d' % booklet_max) if not is_int(booklet_align): raise TypeError('booklet_align must be an integer') if booklet_align: list_append(options, '--bookletalign=%d' % booklet_align) if is_int(booklet_fold): list_append(options, '--bookletfold=%d' % booklet_fold) else: try: fold_base, fold_incr = booklet_fold if not is_int(fold_base) or not is_int(fold_incr): raise TypeError except TypeError: raise TypeError('booklet_fold must a be an integer or a pair of integers') list_append(options, '--bookletfold=%d+%d' % (fold_base, fold_incr)) cdef char **optv cdef int optc cdef size_t buffer_size optc = 0 buffer_size = len(options) * sizeof (char*) optv = py_malloc(buffer_size) if optv == NULL: raise MemoryError('Unable to allocate %d bytes for print options' % buffer_size) try: for optc from 0 <= optc < len(options): option = options[optc] if is_unicode(option): options[optc] = option = encode_utf8(option) optv[optc] = option assert optc == len(options) with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: job = SaveJob(sentinel = the_sentinel) job.__init(self._context, ddjvu_document_print(self.ddjvu_document, output, optc, optv)) job._file = file_wrapper finally: release_lock(loft_lock) finally: py_free(optv) if wait: job.wait() return job property message_queue: ''' Return the internal message queue. ''' def __get__(self): return self._queue def get_message(self, wait=1): ''' D.get_message(wait=True) -> a Message or None Get message from the internal document queue. Return None if wait is false and no message is available. ''' try: return self._queue.get(wait) except Empty: return def __iter__(self): return self def __next__(self): return self.get_message() cdef Document Document_from_c(ddjvu_document_t* ddjvu_document): cdef Document result if ddjvu_document == NULL: result = None else: with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: result = _document_weak_loft.get(voidp_to_int(ddjvu_document)) finally: release_lock(loft_lock) return result class FileUri(str): ''' See the Document.new_document() method. ''' FileURI = FileUri cdef object Context_message_distributor def _Context_message_distributor(Context self not None, **kwargs): cdef Message message cdef Document document cdef Job job cdef PageJob page_job cdef ddjvu_message_t* ddjvu_message check_sentinel(self, kwargs) while 1: with nogil: ddjvu_message = ddjvu_message_wait(self.ddjvu_context) try: try: message = Message_from_c(ddjvu_message) finally: ddjvu_message_pop(self.ddjvu_context) if message is None: raise SystemError self.handle_message(message) # XXX Order of branches below is *crucial*. Do not change. if message._job is not None: job = message._job job._condition.acquire() try: job._condition.notifyAll() finally: job._condition.release() if job.is_done: job.__clear() elif message._page_job is not None: raise SystemError # should not happen elif message._document is not None: document = message._document document._condition.acquire() try: document._condition.notifyAll() finally: document._condition.release() if document.decoding_done: document.__clear() except KeyboardInterrupt: return except SystemExit: return except Exception: write_unraisable_exception(self) Context_message_distributor = _Context_message_distributor del _Context_message_distributor cdef class Context: def __cinit__(self, argv0=None): if argv0 is None: argv0 = sys.argv[0] if is_unicode(argv0): argv0 = encode_utf8(argv0) with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: self.ddjvu_context = ddjvu_context_create(argv0) if self.ddjvu_context == NULL: raise MemoryError('Unable to create DjVu context') _context_loft[voidp_to_int(self.ddjvu_context)] = self finally: release_lock(loft_lock) self._queue = Queue() thread.start_new_thread(Context_message_distributor, (self,), {'sentinel': the_sentinel}) property cache_size: def __set__(self, value): if 0 < value < (1 << 31): ddjvu_cache_set_size(self.ddjvu_context, value) else: raise ValueError('0 < cache_size < (2 ** 31) must be satisfied') def __get__(self): return ddjvu_cache_get_size(self.ddjvu_context) def handle_message(self, Message message not None): ''' C.handle_message(message) -> None This method is called, in a separate thread, for every received message, *before* any blocking method finishes. By default do something roughly equivalent to:: if message.job is not None: message.job.message_queue.put(message) elif message.document is not None: message.document.message_queue.put(message) else: message.context.message_queue.put(message) You may want to override this method to change this behaviour. All exceptions raised by this method will be ignored. ''' # XXX Order of branches below is *crucial*. Do not change. if message._job is not None: message._job._queue.put(message) elif message._page_job is not None: raise SystemError # should not happen elif message._document is not None: message._document._queue.put(message) else: message._context._queue.put(message) property message_queue: ''' Return the internal message queue. ''' def __get__(self): return self._queue def get_message(self, wait=1): ''' C.get_message(wait=True) -> a Message or None Get message from the internal context queue. Return None if wait is false and no message is available. ''' try: return self._queue.get(wait) except Empty: return def new_document(self, uri, cache=1): ''' C.new_document(uri, cache=True) -> a Document Creates a decoder for a DjVu document and starts decoding. This method returns immediately. The decoding job then generates messages to request the raw data and to indicate the state of the decoding process. uri specifies an optional URI for the document. The URI follows the usual syntax (protocol://machine/path). It should not end with a slash. It only serves two purposes: - The URI is used as a key for the cache of decoded pages. - The URI is used to document NewStreamMessage messages. Setting argument cache to a true vaule indicates that decoded pages should be cached when possible. It is important to understand that the URI is not used to access the data. The document generates NewStreamMessage messages to indicate which data is needed. The caller must then provide the raw data using a NewStreamMessage.stream object. To open a local file, provide a FileUri instance as an uri. Localized characters in uri should be in URI-encoded. Possible exceptions: JobFailed. ''' cdef Document document cdef ddjvu_document_t* ddjvu_document with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: if typecheck(uri, FileUri): IF PY3K: uri = encode_utf8(uri) ddjvu_document = ddjvu_document_create_by_filename(self.ddjvu_context, uri, cache) else: IF PY3K: uri = encode_utf8(uri) ddjvu_document = ddjvu_document_create(self.ddjvu_context, uri, cache) if ddjvu_document == NULL: raise JobFailed document = Document(sentinel = the_sentinel) document.__init(self, ddjvu_document) finally: release_lock(loft_lock) return document def __iter__(self): return self def __next__(self): return self.get_message() def clear_cache(self): ''' C.clear_cache() -> None ''' ddjvu_cache_clear(self.ddjvu_context) def __dealloc__(self): ddjvu_context_release(self.ddjvu_context) cdef Context Context_from_c(ddjvu_context_t* ddjvu_context): cdef Context result if ddjvu_context == NULL: result = None else: with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: try: result = _context_loft[voidp_to_int(ddjvu_context)] except KeyError: raise SystemError finally: release_lock(loft_lock) return result RENDER_COLOR = DDJVU_RENDER_COLOR RENDER_BLACK = DDJVU_RENDER_BLACK RENDER_COLOR_ONLY = DDJVU_RENDER_COLORONLY RENDER_MASK_ONLY = DDJVU_RENDER_MASKONLY RENDER_BACKGROUND = DDJVU_RENDER_BACKGROUND RENDER_FOREGROUND = DDJVU_RENDER_FOREGROUND PAGE_TYPE_UNKNOWN = DDJVU_PAGETYPE_UNKNOWN PAGE_TYPE_BITONAL = DDJVU_PAGETYPE_BITONAL PAGE_TYPE_PHOTO = DDJVU_PAGETYPE_PHOTO PAGE_TYPE_COMPOUND = DDJVU_PAGETYPE_COMPOUND cdef class PixelFormat: ''' Abstract pixel format. Don't use this class directly, use one of its subclasses. ''' def __cinit__(self, *args, **kwargs): self._row_order = 0 self._y_direction = 0 self._dither_bpp = 32 self._gamma = 2.2 self.ddjvu_format = NULL for cls in (PixelFormatRgb, PixelFormatRgbMask, PixelFormatGrey, PixelFormatPalette, PixelFormatPackedBits): if typecheck(self, cls): return raise_instantiation_error(type(self)) property rows_top_to_bottom: ''' Flag indicating whether the rows in the pixel buffer are stored starting from the top or the bottom of the image. Default ordering starts from the bottom of the image. This is the opposite of the X11 convention. ''' def __get__(self): return bool(self._row_order) def __set__(self, value): ddjvu_format_set_row_order(self.ddjvu_format, not not value) property y_top_to_bottom: ''' Flag indicating whether the *y* coordinates in the drawing area are oriented from bottom to top, or from top to bottom. The default is bottom to top, similar to PostScript. This is the opposite of the X11 convention. ''' def __get__(self): return bool(self._row_order) def __set__(self, value): ddjvu_format_set_y_direction(self.ddjvu_format, not not value) property bpp: ''' Return the depth of the image, in bits per pixel. ''' def __get__(self): return self._bpp property dither_bpp: ''' The final depth of the image on the screen. This is used to decide which dithering algorithm should be used. The default is usually appropriate. ''' def __get__(self): return self._dither_bpp def __set__(self, int value): if (0 < value < 64): ddjvu_format_set_ditherbits(self.ddjvu_format, value) self._dither_bpp = value else: raise ValueError('0 < value < 64 must be satisfied') property gamma: ''' Gamma of the display for which the pixels are intended. This will be combined with the gamma stored in DjVu documents in order to compute a suitable color correction. The default value is 2.2. ''' def __get__(self): return self._gamma def __set__(self, double value): if (0.5 <= value <= 5.0): ddjvu_format_set_gamma(self.ddjvu_format, value) else: raise ValueError('0.5 <= value <= 5.0 must be satisfied') def __dealloc__(self): if self.ddjvu_format != NULL: ddjvu_format_release(self.ddjvu_format) def __repr__(self): return '%s()' % (get_type_name(type(self)),) cdef class PixelFormatRgb(PixelFormat): ''' PixelFormatRgb([byteorder='RGB']) -> a pixel format 24-bit pixel format, with: - RGB (byteorder == 'RGB') or - BGR (byteorder == 'BGR') byte order. ''' def __cinit__(self, byte_order='RGB', unsigned int bpp=24): cdef unsigned int _format if byte_order == 'RGB': self._rgb = 1 _format = DDJVU_FORMAT_RGB24 elif byte_order == 'BGR': self._rgb = 0 _format = DDJVU_FORMAT_BGR24 else: raise ValueError("byte_order must be equal to 'RGB' or 'BGR'") if bpp != 24: raise ValueError('bpp must be equal to 24') self._bpp = 24 self.ddjvu_format = ddjvu_format_create(_format, 0, NULL) property byte_order: ''' Return the byte order: - 'RGB' or - 'BGR'. ''' def __get__(self): if self._rgb: return 'RGB' else: return 'BGR' def __repr__(self): return '%s(byte_order = %r, bpp = %d)' % \ ( get_type_name(PixelFormatRgb), self.byte_order, self.bpp ) cdef class PixelFormatRgbMask(PixelFormat): ''' PixelFormatRgbMask(red_mask, green_mask, blue_mask[, xor_value], bpp=16) -> a pixel format PixelFormatRgbMask(red_mask, green_mask, blue_mask[, xor_value], bpp=32) -> a pixel format red_mask, green_mask and blue_mask are bit masks for color components for each pixel. The resulting color is then xored with the xor_value. For example, PixelFormatRgbMask(0xf800, 0x07e0, 0x001f, bpp=16) is a highcolor format with: - 5 (most significant) bits for red, - 6 bits for green, - 5 (least significant) bits for blue. ''' def __cinit__(self, unsigned int red_mask, unsigned int green_mask, unsigned int blue_mask, unsigned int xor_value = 0, unsigned int bpp = 16): cdef unsigned int _format if bpp == 16: _format = DDJVU_FORMAT_RGBMASK16 red_mask = red_mask & 0xffff blue_mask = blue_mask & 0xffff green_mask = green_mask & 0xffff xor_value = xor_value & 0xffff elif bpp == 32: _format = DDJVU_FORMAT_RGBMASK32 red_mask = red_mask & 0xffffffff blue_mask = blue_mask & 0xffffffff green_mask = green_mask & 0xffffffff xor_value = xor_value & 0xffffffff else: raise ValueError('bpp must be equal to 16 or 32') self._bpp = self._dither_bpp = bpp (self._params[0], self._params[1], self._params[2], self._params[3]) = (red_mask, green_mask, blue_mask, xor_value) self.ddjvu_format = ddjvu_format_create(_format, 4, self._params) def __repr__(self): return '%s(red_mask = 0x%0*x, green_mask = 0x%0*x, blue_mask = 0x%0*x, xor_value = 0x%0*x, bpp = %d)' % \ ( get_type_name(PixelFormatRgbMask), self.bpp//4, self._params[0], self.bpp//4, self._params[1], self.bpp//4, self._params[2], self.bpp//4, self._params[3], self.bpp, ) cdef class PixelFormatGrey(PixelFormat): ''' PixelFormatGrey() -> a pixel format 8-bit, grey pixel format. ''' def __cinit__(self, unsigned int bpp = 8): cdef unsigned int params[4] if bpp != 8: raise ValueError('bpp must be equal to 8') self._bpp = self._dither_bpp = bpp self.ddjvu_format = ddjvu_format_create(DDJVU_FORMAT_GREY8, 0, NULL) def __repr__(self): return '%s(bpp = %d)' % (get_type_name(PixelFormatGrey), self.bpp) cdef class PixelFormatPalette(PixelFormat): ''' PixelFormatPalette(palette) -> a pixel format Palette pixel format. palette must be a dictionary which contains 216 (6 * 6 * 6) entries of a web color cube, such that: - for each key (r, g, b): r in range(0, 6), g in range(0, 6) etc.; - for each value v: v in range(0, 0x100). ''' def __cinit__(self, palette, unsigned int bpp = 8): cdef int i, j, k, n for i from 0 <= i < 6: for j from 0 <= j < 6: for k from 0 <= k < 6: n = palette[(i, j, k)] if not 0 <= n < 0x100: raise ValueError('palette entries must be in range(0, 0x100)') self._palette[i*6*6 + j*6 + k] = n if bpp != 8: raise ValueError('bpp must be equal to 8') self._bpp = self._dither_bpp = bpp self.ddjvu_format = ddjvu_format_create(DDJVU_FORMAT_PALETTE8, 216, self._palette) def __repr__(self): cdef int i io = StringIO() io.write('%s({' % (get_type_name(PixelFormatPalette),)) for i from 0 <= i < 6: for j from 0 <= j < 6: for k from 0 <= k < 6: io.write('(%d, %d, %d): 0x%02x' % (i, j, k, self._palette[i * 6 * 6 + j * 6 + k])) if not (i == j == k == 5): io.write(', ') io.write('}, bpp = %d)' % self.bpp) return io.getvalue() cdef class PixelFormatPackedBits(PixelFormat): ''' PixelFormatPackedBits(endianness) -> a pixel format Bitonal, 1 bit per pixel format with: - most significant bits on the left (endianness=='>') or - least significant bits on the left (endianness=='<'). ''' def __cinit__(self, endianness): cdef int _format if endianness == '<': self._little_endian = 1 _format = DDJVU_FORMAT_LSBTOMSB elif endianness == '>': self._little_endian = 0 _format = DDJVU_FORMAT_MSBTOLSB else: raise ValueError("endianness must be equal to '<' or '>'") self._bpp = 1 self._dither_bpp = 1 self.ddjvu_format = ddjvu_format_create(_format, 0, NULL) property endianness: ''' The endianness: - '<' (most significant bits on the left) or - '>' (least significant bits on the left). ''' def __get__(self): if self._little_endian: return '<' else: return '>' def __repr__(self): return '%s(%r)' % (get_type_name(PixelFormatPackedBits), self.endianness) cdef object calculate_row_size(long width, long row_alignment, int bpp): cdef long result cdef object row_size if bpp == 1: row_size = (width >> 3) + ((width & 7) != 0) elif bpp & 7 == 0: row_size = width row_size = row_size * (bpp >> 3) else: raise SystemError result = ((row_size + (row_alignment - 1)) // row_alignment) * row_alignment return result cdef object allocate_image_memory(long width, long height, object buffer, void **memory): cdef Py_ssize_t c_requested_size cdef Py_ssize_t c_memory_size py_requested_size = int(width) * int(height) try: c_requested_size = py_requested_size except OverflowError: raise MemoryError('Unable to allocate %d bytes for an image memory' % py_requested_size) if buffer is None: result = charp_to_bytes(NULL, c_requested_size) memory[0] = result else: result = buffer buffer_to_writable_memory(buffer, memory, &c_memory_size) if c_memory_size < c_requested_size: raise ValueError('Image buffer is too small (%d > %d)' % (c_requested_size, c_memory_size)) return result cdef class PageJob(Job): ''' A page decoding job. Use page.decode(...) to obtain instances of this class. ''' cdef object __init(self, Context context, ddjvu_job_t *ddjvu_job): Job.__init(self, context, ddjvu_job) property width: ''' Return the page width in pixels. Possible exceptions: NotAvailable (before receiving a PageInfoMessage). ''' def __get__(self): cdef int width width = ddjvu_page_get_width( self.ddjvu_job) if width == 0: raise _NotAvailable_ else: return width property height: ''' Return the page height in pixels. Possible exceptions: NotAvailable (before receiving a PageInfoMessage). ''' def __get__(self): cdef int height height = ddjvu_page_get_height( self.ddjvu_job) if height == 0: raise _NotAvailable_ else: return height property size: ''' page_job.size == (page_job.width, page_job.height) Possible exceptions: NotAvailable (before receiving a PageInfoMessage). ''' def __get__(self): cdef int width cdef int height width = ddjvu_page_get_width( self.ddjvu_job) height = ddjvu_page_get_height( self.ddjvu_job) if width == 0 or height == 0: raise _NotAvailable_ else: return width, height property dpi: ''' Return the page resolution in pixels per inch. Possible exceptions: NotAvailable (before receiving a PageInfoMessage). ''' def __get__(self): cdef int dpi dpi = ddjvu_page_get_resolution( self.ddjvu_job) if dpi == 0: raise _NotAvailable_ else: return dpi property gamma: ''' Return the gamma of the display for which this page was designed. Possible exceptions: NotAvailable (before receiving a PageInfoMessage). ''' def __get__(self): return ddjvu_page_get_gamma( self.ddjvu_job) property version: ''' Return the version of the DjVu file format. Possible exceptions: NotAvailable (before receiving a PageInfoMessage). ''' def __get__(self): return ddjvu_page_get_version( self.ddjvu_job) property type: ''' Return the type of the page data. Possible values are: * PAGE_TYPE_UNKNOWN, * PAGE_TYPE_BITONAL, * PAGE_TYPE_PHOTO, * PAGE_TYPE_COMPOUND. Possible exceptions: NotAvailable (before receiving a PageInfoMessage). ''' def __get__(self): cdef ddjvu_page_type_t type cdef int is_done is_done = self.is_done type = ddjvu_page_get_type( self.ddjvu_job) if type == DDJVU_PAGETYPE_UNKNOWN and not is_done: # XXX An unavoidable race condition raise _NotAvailable_ return type property initial_rotation: ''' Return the counter-clockwise page rotation angle (in degrees) specified by the orientation flags in the DjVu file. Brain damage warning -------------------- This is useful because maparea coordinates in the annotation chunks are expressed relative to the rotated coordinates whereas text coordinates in the hidden text data are expressed relative to the unrotated coordinates. ''' def __get__(self): return 90 * ddjvu_page_get_initial_rotation( self.ddjvu_job) property rotation: ''' Return the counter-clockwise rotation angle (in degrees) for the page. The rotation is automatically taken into account by render(...) method and width and height properties. ''' def __get__(self): return 90 * ddjvu_page_get_rotation( self.ddjvu_job) def __set__(self, int value): cdef ddjvu_page_rotation_t rotation if value == 0: rotation = DDJVU_ROTATE_0 elif value == 90: rotation = DDJVU_ROTATE_90 elif value == 180: rotation = DDJVU_ROTATE_180 elif value == 270: rotation = DDJVU_ROTATE_180 else: raise ValueError('rotation must be equal to 0, 90, 180, or 270') ddjvu_page_set_rotation( self.ddjvu_job, rotation) def __del__(self): ddjvu_page_set_rotation( self.ddjvu_job, ddjvu_page_get_initial_rotation( self.ddjvu_job)) def render(self, int mode, page_rect, render_rect, PixelFormat pixel_format not None, long row_alignment=1, buffer=None): ''' J.render(mode, page_rect, render_rect, pixel_format, row_alignment=1, buffer=None) -> data Render a segment of a page with arbitrary scale. mode indicates which image layers should be rendered: RENDER_COLOR color page or stencil RENDER_BLACK stencil or color page RENDER_COLOR_ONLY color page or fail RENDER_MASK_ONLY stencil or fail RENDER_BACKGROUND color background layer RENDER_FOREGROUND color foreground layer Conceptually this method renders the full page into a rectangle page_rect and copies the pixels specified by rectangle render_rect into a buffer. The actual code is much more efficient than that. pixel_format specifies the expected pixel format. Each row will start at row_alignment bytes boundary. Data will be saved to the provided buffer or to a newly created string. This method makes a best effort to compute an image that reflects the most recently decoded data. Possible exceptions: NotAvailable (to indicate that no image could be computed at this point.) ''' cdef ddjvu_rect_t c_page_rect cdef ddjvu_rect_t c_render_rect cdef Py_ssize_t buffer_size cdef long row_size cdef int bpp cdef long x, y, w, h cdef void *memory if row_alignment <= 0: raise ValueError('row_alignment must be a positive integer') x, y, w, h = page_rect if w <= 0 or h <= 0: raise ValueError('page_rect width/height must be a positive integer') c_page_rect.x, c_page_rect.y, c_page_rect.w, c_page_rect.h = x, y, w, h if c_page_rect.x != x or c_page_rect.y != y or c_page_rect.w != w or c_page_rect.h != h: raise OverflowError('render_rect coordinates are too large') x, y, w, h = render_rect if w <= 0 or h <= 0: raise ValueError('render_rect width/height must be a positive integer') c_render_rect.x, c_render_rect.y, c_render_rect.w, c_render_rect.h = x, y, w, h if c_render_rect.x != x or c_render_rect.y != y or c_render_rect.w != w or c_render_rect.h != h: raise OverflowError('render_rect coordinates are too large') if ( c_page_rect.x > c_render_rect.x or c_page_rect.y > c_render_rect.y or c_page_rect.x + c_page_rect.w < c_render_rect.x + c_render_rect.w or c_page_rect.y + c_page_rect.h < c_render_rect.y + c_render_rect.h ): raise ValueError('render_rect must be inside page_rect') row_size = calculate_row_size(c_render_rect.w, row_alignment, pixel_format._bpp) result = allocate_image_memory(row_size, c_render_rect.h, buffer, &memory) if ddjvu_page_render( self.ddjvu_job, mode, &c_page_rect, &c_render_rect, pixel_format.ddjvu_format, row_size, memory) == 0: raise _NotAvailable_ return result def __dealloc__(self): if self.ddjvu_job == NULL: return ddjvu_page_release( self.ddjvu_job) self.ddjvu_job = NULL cdef PageJob PageJob_from_c(ddjvu_page_t* ddjvu_page): cdef PageJob job job = Job_from_c( ddjvu_page) return job cdef class Job: ''' A job. ''' def __cinit__(self, **kwargs): check_sentinel(self, kwargs) self._context = None self.ddjvu_job = NULL self._condition = Condition() self._queue = Queue() cdef object __init(self, Context context, ddjvu_job_t *ddjvu_job): # Assumption: loft_lock is already acquired. assert context != None and ddjvu_job != NULL self._context = context self.ddjvu_job = ddjvu_job _job_loft.add(self) _job_weak_loft[voidp_to_int(ddjvu_job)] = self cdef object __clear(self): with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: _job_loft.discard(self) finally: release_lock(loft_lock) property status: ''' Return a JobException subclass indicating the job status. ''' def __get__(self): return JobException_from_c(ddjvu_job_status(self.ddjvu_job)) property is_error: ''' Indicate whether the job failed. ''' def __get__(self): return bool(ddjvu_job_error(self.ddjvu_job)) property is_done: ''' Indicate whether the decoding job is done. ''' def __get__(self): return bool(ddjvu_job_done(self.ddjvu_job)) def wait(self): ''' J.wait() -> None Wait until the job is done. ''' while 1: self._condition.acquire() try: if ddjvu_job_done(self.ddjvu_job): break self._condition.wait() finally: self._condition.release() def stop(self): ''' J.stop() -> None Attempt to cancel the job. This is a best effort method. There no guarantee that the job will actually stop. ''' ddjvu_job_stop(self.ddjvu_job) property message_queue: ''' Return the internal message queue. ''' def __get__(self): return self._queue def get_message(self, wait=1): ''' J.get_message(wait=True) -> a Message or None Get message from the internal job queue. Return None if wait is false and no message is available. ''' try: return self._queue.get(wait) except Empty: return def __iter__(self): return self def __next__(self): return self.get_message() def __dealloc__(self): if self.ddjvu_job == NULL: return ddjvu_job_release(self.ddjvu_job) self.ddjvu_job = NULL cdef Job Job_from_c(ddjvu_job_t* ddjvu_job): cdef Job result if ddjvu_job == NULL: result = None else: with nogil: acquire_lock(loft_lock, WAIT_LOCK) try: result = _job_weak_loft.get(voidp_to_int(ddjvu_job)) finally: release_lock(loft_lock) return result cdef class AffineTransform: ''' AffineTransform((x0, y0, w0, h0), (x1, y1, w1, h1)) -> an affine coordinate transformation The object represents an affine coordinate transformation that maps points from rectangle (x0, y0, w0, h0) to rectangle (x1, y1, w1, h1). ''' def __cinit__(self, input, output): cdef ddjvu_rect_t c_input cdef ddjvu_rect_t c_output self.ddjvu_rectmapper = NULL (c_input.x, c_input.y, c_input.w, c_input.h) = input (c_output.x, c_output.y, c_output.w, c_output.h) = output self.ddjvu_rectmapper = ddjvu_rectmapper_create(&c_input, &c_output) def rotate(self, int n): ''' A.rotate(n) -> None Rotate the output rectangle counter-clockwise by n degrees. ''' if n % 90: raise ValueError('n must a multiple of 90') else: ddjvu_rectmapper_modify(self.ddjvu_rectmapper, n/90, 0, 0) def __call__(self, value): cdef ddjvu_rect_t rect IF PY3K: next = iter(value).__next__ ELSE: next = iter(value).next try: rect.x = next() rect.y = next() except StopIteration: raise ValueError('value must be a pair or a 4-tuple') try: rect.w = next() except StopIteration: ddjvu_map_point(self.ddjvu_rectmapper, &rect.x, &rect.y) return (rect.x, rect.y) try: rect.h = next() except StopIteration: raise ValueError('value must be a pair or a 4-tuple') try: next() except StopIteration: pass else: raise ValueError('value must be a pair or a 4-tuple') ddjvu_map_rect(self.ddjvu_rectmapper, &rect) return (rect.x, rect.y, int(rect.w), int(rect.h)) def apply(self, value): ''' A.apply((x0, y0)) -> (x1, y1) A.apply((x0, y0, w0, h0)) -> (x1, y1, w1, h1) Apply the coordinate transform to a point or a rectangle. ''' return self(value) def inverse(self, value): ''' A.inverse((x0, y0)) -> (x1, y1) A.inverse((x0, y0, w0, h0)) -> (x1, y1, w1, h1) Apply the inverse coordinate transform to a point or a rectangle. ''' cdef ddjvu_rect_t rect IF PY3K: next = iter(value).__next__ ELSE: next = iter(value).next try: rect.x = next() rect.y = next() except StopIteration: raise ValueError('value must be a pair or a 4-tuple') try: rect.w = next() except StopIteration: ddjvu_unmap_point(self.ddjvu_rectmapper, &rect.x, &rect.y) return (rect.x, rect.y) try: rect.h = next() except StopIteration: raise ValueError('value must be a pair or a 4-tuple') try: next() except StopIteration: pass else: raise ValueError('value must be a pair or a 4-tuple') ddjvu_unmap_rect(self.ddjvu_rectmapper, &rect) return (rect.x, rect.y, int(rect.w), int(rect.h)) def mirror_x(self): ''' A.mirror_x() Reverse the X coordinates of the output rectangle. ''' ddjvu_rectmapper_modify(self.ddjvu_rectmapper, 0, 1, 0) def mirror_y(self): ''' A.mirror_y() Reverse the Y coordinates of the output rectangle. ''' ddjvu_rectmapper_modify(self.ddjvu_rectmapper, 0, 0, 1) def __dealloc__(self): if self.ddjvu_rectmapper != NULL: ddjvu_rectmapper_release(self.ddjvu_rectmapper) cdef class Message: ''' An abstract message. ''' def __cinit__(self, **kwargs): check_sentinel(self, kwargs) self.ddjvu_message = NULL cdef object __init(self): if self.ddjvu_message == NULL: raise SystemError self._context = Context_from_c(self.ddjvu_message.m_any.context) self._document = Document_from_c(self.ddjvu_message.m_any.document) self._page_job = PageJob_from_c(self.ddjvu_message.m_any.page) self._job = Job_from_c(self.ddjvu_message.m_any.job) property context: ''' Return the concerned Context. ''' def __get__(self): return self._context property document: ''' Return the concerned Document or None. ''' def __get__(self): return self._document property page_job: ''' Return the concerned PageJob or None. ''' def __get__(self): return self._page_job property job: ''' Return the concerned Job or None. ''' def __get__(self): return self._job cdef class ErrorMessage(Message): ''' An ErrorMessage is generated whenever the decoder or the DDJVU API encounters an error condition. All errors are reported as error messages because they can occur asynchronously. ''' cdef object __init(self): Message.__init(self) IF HAVE_LANGINFO_H: locale_encoding = charp_to_string(nl_langinfo(CODESET)) ELSE: # Presumably a Windows system. import locale locale_encoding = locale.getpreferredencoding() if self.ddjvu_message.m_error.message != NULL: # Things can go awry if user calls setlocale() between the time the # message was created and the time it was received. Let's hope it # never happens, but don't throw an exception if it did anyway. self._message = self.ddjvu_message.m_error.message.decode(locale_encoding, 'replace') else: self._message = None if self.ddjvu_message.m_error.function != NULL: # Should be ASCII-only, so don't care about encoding. function = charp_to_string(self.ddjvu_message.m_error.function) else: function = None if self.ddjvu_message.m_error.filename != NULL: # Should be ASCII-only, so don't care about encoding. filename = charp_to_string(self.ddjvu_message.m_error.filename) else: filename = None self._location = (function, filename, self.ddjvu_message.m_error.lineno) property message: ''' Return the actual error message, as text. ''' def __get__(self): return self._message property location: ''' Return a (function, filename, line_no) tuple indicating where the error was detected. ''' def __get__(self): return self._location IF PY3K: def __str__(self): return self.message ELSE: def __str__(self): IF HAVE_LANGINFO_H: locale_encoding = charp_to_string(nl_langinfo(CODESET)) ELSE: # Presumably a Windows system. import locale locale_encoding = locale.getpreferredencoding() return self.message.encode(locale_encoding, 'replace') def __unicode__(self): return self.message def __repr__(self): return '<%s: %r at %r>' % (get_type_name(ErrorMessage), self.message, self.location) cdef class InfoMessage(Message): ''' A InfoMessage provides informational text indicating the progress of the decoding process. This might be displayed in the browser status bar. ''' cdef object __init(self): Message.__init(self) self._message = charp_to_string(self.ddjvu_message.m_error.message) property message: ''' Return the actual information message, as text. ''' def __get__(self): return self._message cdef class Stream: ''' Data stream. Use new_stream_message.stream to obtain instances of this class. ''' def __cinit__(self, Document document not None, int streamid, **kwargs): check_sentinel(self, kwargs) self._streamid = streamid self._document = document self._open = 1 def close(self): ''' S.close() -> None Indicate that no more data will be provided on the particular stream. ''' ddjvu_stream_close(self._document.ddjvu_document, self._streamid, 0) self._open = 0 def abort(self): ''' S.abort() -> None Indicate that no more data will be provided on the particular stream, because the user has interrupted the data transfer (for instance by pressing the stop button of a browser) and that the decoding threads should be stopped as soon as feasible. ''' ddjvu_stream_close(self._document.ddjvu_document, self._streamid, 1) self._open = 0 def flush(self): ''' S.flush() -> None Do nothing. (This method is provided solely to implement Python's file-like interface.) ''' def read(self, size=None): ''' S.read([size]) Raise IOError. (This method is provided solely to implement Python's file-like interface.) ''' raise IOError('write-only data stream') def write(self, data): ''' S.write(data) -> None Provide raw data to the DjVu decoder. This method should be called as soon as the data is available, for instance when receiving DjVu data from a network connection. ''' cdef char* raw_data cdef Py_ssize_t length if self._open: bytes_to_charp(data, &raw_data, &length) ddjvu_stream_write(self._document.ddjvu_document, self._streamid, raw_data, length) else: raise IOError('I/O operation on closed file') def __dealloc__(self): if self._document is None: return if self._open: ddjvu_stream_close(self._document.ddjvu_document, self._streamid, 1) cdef class NewStreamMessage(Message): ''' A NewStreamMessage is generated whenever the decoder needs to access raw DjVu data. The caller must then provide the requested data using the .stream file-like object. In the case of indirect documents, a single decoder might simultaneously request several streams of data. ''' cdef object __init(self): Message.__init(self) self._stream = Stream(self.document, self.ddjvu_message.m_newstream.streamid, sentinel = the_sentinel) self._name = charp_to_string(self.ddjvu_message.m_newstream.name) self._uri = charp_to_string(self.ddjvu_message.m_newstream.url) property stream: ''' Return the concerned Stream. ''' def __get__(self): return self._stream property name: ''' The first NewStreamMessage message always has .name set to None. It indicates that the decoder needs to access the data in the main DjVu file. Further NewStreamMessage messages messages are generated to access the auxiliary files of indirect or indexed DjVu documents. .name then provides the base name of the auxiliary file. ''' def __get__(self): return self._name property uri: ''' Return the requrested URI. URI is is set according to the uri argument provided to function Context.new_document(). The first NewMessageStream message always contain the URI passed to Context.new_document(). Subsequent NewMessageStream messages contain the URI of the auxiliary files for indirect or indexed DjVu documents. ''' def __get__(self): return self._uri cdef class DocInfoMessage(Message): ''' A DocInfoMessage indicates that basic information about the document has been obtained and decoded. Not much can be done before this happens. Check Document.decoding_status to determine whether the operation was successful. ''' cdef class PageInfoMessage(Message): ''' The page decoding process generates a PageInfoMessage: - when basic page information is available and before any RelayoutMessage or RedisplayMessage, - when the page decoding thread terminates. You can distinguish both cases using PageJob.status. A PageInfoMessage may be also generated as a consequence of reading Page.get_info() or Page.dump. ''' cdef class ChunkMessage(Message): ''' A ChunkMessage indicates that an additional chunk of DjVu data has been decoded. ''' cdef class RelayoutMessage(ChunkMessage): ''' A RelayoutMessage is generated when a DjVu viewer should recompute the layout of the page viewer because the page size and resolution information has been updated. ''' cdef class RedisplayMessage(ChunkMessage): ''' A RedisplayMessage is generated when a DjVu viewer should call PageJob.render() and redisplay the page. This happens, for instance, when newly decoded DjVu data provides a better image. ''' cdef class ThumbnailMessage(Message): ''' A ThumbnailMessage is sent when additional thumbnails are available. ''' cdef object __init(self): Message.__init(self) self._page_no = self.ddjvu_message.m_thumbnail.pagenum property thumbnail: ''' Return the Thumbnail. Raise NotAvailable if the Document has been garbage-collected. ''' def __get__(self): if self._document is None: raise _NotAvailable_ return self._document.pages[self._page_no].thumbnail cdef class ProgressMessage(Message): ''' A ProgressMessage is generated to indicate progress towards the completion of a print or save job. ''' cdef object __init(self): Message.__init(self) self._percent = self.ddjvu_message.m_progress.percent self._status = self.ddjvu_message.m_progress.status property percent: ''' Return the percent of the job done. ''' def __get__(self): return self._percent property status: ''' Return a JobException subclass indicating the current job status. ''' def __get__(self): return JobException_from_c(self._status) cdef object MESSAGE_MAP MESSAGE_MAP = \ { DDJVU_ERROR: ErrorMessage, DDJVU_INFO: InfoMessage, DDJVU_NEWSTREAM: NewStreamMessage, DDJVU_DOCINFO: DocInfoMessage, DDJVU_PAGEINFO: PageInfoMessage, DDJVU_RELAYOUT: RelayoutMessage, DDJVU_REDISPLAY: RedisplayMessage, DDJVU_CHUNK: ChunkMessage, DDJVU_THUMBNAIL: ThumbnailMessage, DDJVU_PROGRESS: ProgressMessage } cdef Message Message_from_c(ddjvu_message_t* ddjvu_message): cdef Message message if ddjvu_message == NULL: return try: klass = MESSAGE_MAP[ddjvu_message.m_any.tag] except KeyError: raise SystemError message = klass(sentinel = the_sentinel) message.ddjvu_message = ddjvu_message message.__init() return message cdef object JOB_EXCEPTION_MAP cdef object JOB_FAILED_SYMBOL, JOB_STOPPED_SYMBOL JOB_FAILED_SYMBOL = Symbol('failed') JOB_STOPPED_SYMBOL = Symbol('stopped') cdef object JobException_from_sexpr(object sexpr): if typecheck(sexpr, SymbolExpression): if sexpr.value is JOB_FAILED_SYMBOL: raise JobFailed elif sexpr.valu is JOB_STOPPED_SYMBOL: raise JobStopped cdef JobException_from_c(ddjvu_status_t code): try: return JOB_EXCEPTION_MAP[code] except KeyError: raise SystemError class JobException(Exception): ''' Status of a job. Possibly, but not necessarily, exceptional. ''' class JobNotDone(JobException): ''' Operation is not yet done. ''' class JobNotStarted(JobNotDone): ''' Operation was not even started. ''' class JobStarted(JobNotDone): ''' Operation is in progress. ''' class JobDone(JobException): ''' Operation finished. ''' class JobOK(JobDone): ''' Operation finished successfully. ''' class JobFailed(JobDone): ''' Operation failed because of an error. ''' class JobStopped(JobFailed): ''' Operation was interrupted by user. ''' JOB_EXCEPTION_MAP = \ { DDJVU_JOB_NOTSTARTED: JobNotStarted, DDJVU_JOB_STARTED: JobStarted, DDJVU_JOB_OK: JobOK, DDJVU_JOB_FAILED: JobFailed, DDJVU_JOB_STOPPED: JobStopped } cdef class _SexprWrapper: def __cinit__(self, document, **kwargs): check_sentinel(self, kwargs) self._document_weakref = weakref.ref(document) def __call__(self): return cexpr2py(self._cexpr) def __dealloc__(self): cdef Document document if self._cexpr == NULL: return document = self._document_weakref() if document is None: return ddjvu_miniexp_release(document.ddjvu_document, self._cexpr) cdef _SexprWrapper wrap_sexpr(Document document, cexpr_t cexpr): cdef _SexprWrapper result result = _SexprWrapper(document, sentinel = the_sentinel) result._cexpr = cexpr return result cdef class DocumentOutline(DocumentExtension): ''' DocumentOutline(document) -> a document outline ''' def __cinit__(self, Document document not None): self._document = document self._sexpr = None cdef object _update_sexpr(self): if self._sexpr is not None: return self._sexpr = wrap_sexpr( self._document, ddjvu_document_get_outline(self._document.ddjvu_document) ) def wait(self): ''' O.wait() -> None Wait until the associated S-expression is available. ''' while 1: self._document._condition.acquire() try: try: self.sexpr return except NotAvailable: self._document._condition.wait() finally: self._document._condition.release() property sexpr: ''' Return the associated S-expression. See "Outline/Bookmark syntax" in the djvused manual page. If the S-expression is not available, raise NotAvailable exception. Then, PageInfoMessage messages with empty page_job may be emitted. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): self._update_sexpr() try: sexpr = self._sexpr() exception = JobException_from_sexpr(sexpr) if exception is not None: raise exception return sexpr except InvalidExpression: self._sexpr = None raise _NotAvailable_ def __repr__(self): return '%s(%r)' % (get_type_name(DocumentOutline), self._document) cdef class Annotations: ''' Document or page annotation. Don't use this class directly, use one of its subclasses. ''' def __cinit__(self, *args, **kwargs): if typecheck(self, DocumentAnnotations): return if typecheck(self, PageAnnotations): return raise_instantiation_error(type(self)) cdef object _update_sexpr(self): raise NotImplementedError def wait(self): ''' A.wait() -> None Wait until the associated S-expression is available. ''' while 1: self._document._condition.acquire() try: try: self.sexpr return except NotAvailable: self._document._condition.wait() finally: self._document._condition.release() property sexpr: ''' Return the associated S-expression. See "Annotation syntax" in the djvused manual page. If the S-expression is not available, raise NotAvailable exception. Then, PageInfoMessage messages with empty page_job may be emitted. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): self._update_sexpr() try: sexpr = self._sexpr() exception = JobException_from_sexpr(sexpr) if exception is not None: raise exception return sexpr except InvalidExpression: self._sexpr = None raise _NotAvailable_ property background_color: ''' Parse the annotations and extract the desired background color as a color string '(#FFFFFF)'. See '(background ...)' in the djvused manual page. Return None if this information is not specified. ''' def __get__(self): cdef char* result result = ddjvu_anno_get_bgcolor(self._sexpr._cexpr) if result == NULL: return return result property zoom: ''' Parse the annotations and extract the desired zoom factor. See '(zoom ...)' in the djvused manual page. Return None if this information is not specified. ''' def __get__(self): cdef char* result result = ddjvu_anno_get_zoom(self._sexpr._cexpr) if result == NULL: return return result property mode: ''' Parse the annotations and extract the desired display mode. See '(mode ...)' in the djvused manual page. Return zero if this information is not specified. ''' def __get__(self): cdef char* result result = ddjvu_anno_get_mode(self._sexpr._cexpr) if result == NULL: return return result property horizontal_align: ''' Parse the annotations and extract how the page image should be aligned horizontally. See '(align ...)' in the djvused manual page. Return None if this information is not specified. ''' def __get__(self): cdef char* result result = ddjvu_anno_get_horizalign(self._sexpr._cexpr) if result == NULL: return return result property vertical_align: ''' Parse the annotations and extract how the page image should be aligned vertically. See '(align ...)' in the djvused manual page. Return None if this information is not specified. ''' def __get__(self): cdef char* result result = ddjvu_anno_get_vertalign(self._sexpr._cexpr) if result == NULL: return return result property hyperlinks: ''' Return an associated Hyperlinks object. ''' def __get__(self): return Hyperlinks(self) property metadata: ''' Return an associated Metadata object. ''' def __get__(self): return Metadata(self) cdef class DocumentAnnotations(Annotations): ''' DocumentAnnotations(document[, shared=True]) -> document-wide annotations If shared is true and no document-wide annotations are available, shared annotations are considered document-wide. See also "Document annotations and metadata" in the djvuchanges.txt file. ''' def __cinit__(self, Document document not None, shared=1): self._document = document self._compat = shared self._sexpr = None cdef object _update_sexpr(self): if self._sexpr is not None: return self._sexpr = wrap_sexpr( self._document, ddjvu_document_get_anno(self._document.ddjvu_document, self._compat) ) property document: ''' Return the concerned Document. ''' def __get__(self): return self._document cdef class PageAnnotations(Annotations): ''' PageAnnotation(page) -> page annotations ''' def __cinit__(self, Page page not None): self._document = page._document self._page = page self._sexpr = None cdef object _update_sexpr(self): if self._sexpr is not None: return self._sexpr = wrap_sexpr( self._page._document, ddjvu_document_get_pageanno(self._page._document.ddjvu_document, self._page._n) ) property page: ''' Return the concerned page. ''' def __get__(self): return self._page TEXT_DETAILS_PAGE = Symbol('page') TEXT_DETAILS_COLUMN = Symbol('column') TEXT_DETAILS_REGION = Symbol('region') TEXT_DETAILS_PARAGRAPH = Symbol('para') TEXT_DETAILS_LINE = Symbol('line') TEXT_DETAILS_WORD = Symbol('word') TEXT_DETAILS_CHARACTER = Symbol('char') TEXT_DETAILS_ALL = None cdef object TEXT_DETAILS TEXT_DETAILS = { TEXT_DETAILS_PAGE: 7, TEXT_DETAILS_COLUMN: 6, TEXT_DETAILS_REGION: 5, TEXT_DETAILS_PARAGRAPH: 4, TEXT_DETAILS_LINE: 3, TEXT_DETAILS_WORD: 2, TEXT_DETAILS_CHARACTER: 1, } def cmp_text_zone(zonetype1, zonetype2): ''' cmp_text_zone(zonetype1, zonetype2) -> integer Return: - negative if zonetype1 is more concrete than zonetype2; - zero if zonetype1 == zonetype2; - positive if zonetype1 is more general than zonetype2. Possible zone types: - TEXT_ZONE_PAGE, - TEXT_ZONE_COLUMN, - TEXT_ZONE_REGION, - TEXT_ZONE_PARAGRAPH, - TEXT_ZONE_LINE, - TEXT_ZONE_WORD, - TEXT_ZONE_CHARACTER. ''' if not typecheck(zonetype1, Symbol) or not typecheck(zonetype2, Symbol): raise TypeError('zonetype must be a symbol') try: n1 = TEXT_DETAILS[zonetype1] n2 = TEXT_DETAILS[zonetype2] except KeyError: raise ValueError('zonetype must be equal to TEXT_ZONE_PAGE, or TEXT_ZONE_COLUMN, or TEXT_ZONE_REGION, or TEXT_ZONE_PARAGRAPH, or TEXT_ZONE_LINE, or TEXT_ZONE_WORD, or TEXT_ZONE_CHARACTER') if n1 < n2: return -1 elif n1 > n2: return 1 else: return 0 cdef class PageText: ''' PageText(page, details=TEXT_DETAILS_ALL) -> wrapper around page text details controls the level of details in the returned S-expression: - TEXT_DETAILS_PAGE, - TEXT_DETAILS_COLUMN, - TEXT_DETAILS_REGION, - TEXT_DETAILS_PARAGRAPH, - TEXT_DETAILS_LINE, - TEXT_DETAILS_WORD, - TEXT_DETAILS_CHARACTER, - TEXT_DETAILS_ALL. ''' def __cinit__(self, Page page not None, details=TEXT_DETAILS_ALL): if details is None: self._details = charp_to_bytes('', 0) elif not typecheck(details, Symbol): raise TypeError('details must be a symbol or none') elif details not in TEXT_DETAILS: raise ValueError('details must be equal to TEXT_DETAILS_PAGE, or TEXT_DETAILS_COLUMN, or TEXT_DETAILS_REGION, or TEXT_DETAILS_PARAGRAPH, or TEXT_DETAILS_LINE, or TEXT_DETAILS_WORD, or TEXT_DETAILS_CHARACTER or TEXT_DETAILS_ALL') else: self._details = details.bytes self._page = page self._sexpr = None cdef object _update_sexpr(self): if self._sexpr is None: self._sexpr = wrap_sexpr( self._page._document, ddjvu_document_get_pagetext(self._page._document.ddjvu_document, self._page._n, self._details) ) def wait(self): ''' PT.wait() -> None Wait until the associated S-expression is available. ''' while 1: self._page._document._condition.acquire() try: try: self.sexpr return except NotAvailable: self._page._document._condition.wait() finally: self._page._document._condition.release() property page: ''' Return the concerned page. ''' def __get__(self): return self._page property sexpr: ''' Return the associated S-expression. See "Hidden text syntax" in the djvused manual page. If the S-expression is not available, raise NotAvailable exception. Then, PageInfoMessage messages with empty page_job may be emitted. Possible exceptions: NotAvailable, JobFailed. ''' def __get__(self): self._update_sexpr() try: sexpr = self._sexpr() exception = JobException_from_sexpr(sexpr) if exception is not None: raise exception return sexpr except InvalidExpression: self._sexpr = None raise _NotAvailable_ cdef class Hyperlinks: ''' Hyperlinks(annotations) -> sequence of hyperlinks Parse the annotations and return a sequence of '(maparea ...)' S-expressions. See also '(maparea ...)' in the djvused manual page. ''' def __cinit__(self, Annotations annotations not None): cdef cexpr_t* all cdef cexpr_t* current all = ddjvu_anno_get_hyperlinks(annotations._sexpr._cexpr) if all == NULL: raise MemoryError try: current = all self._sexpr = [] while current[0]: list_append(self._sexpr, wrap_sexpr(annotations._document, current[0])) current = current + 1 finally: libc_free(all) def __len__(self): return len(self._sexpr) def __getitem__(self, Py_ssize_t n): return self._sexpr[n]() cdef class Metadata: ''' Metadata(annotations) -> mapping of metadata Parse the annotations and return a mapping of metadata. See also '(metadata ...)' in the djvused manual page. ''' def __cinit__(self, Annotations annotations not None): cdef cexpr_t* all cdef cexpr_t* current self._annotations = annotations all = ddjvu_anno_get_metadata_keys(annotations._sexpr._cexpr) if all == NULL: raise MemoryError try: current = all keys = [] while current[0]: list_append(keys, unicode(wrap_sexpr(annotations._document, current[0])().value)) current = current + 1 self._keys = frozenset(keys) finally: libc_free(all) def __len__(self): return len(self._keys) def __getitem__(self, key): cdef _WrappedCExpr cexpr_key cdef char *s cexpr_key = py2cexpr(Symbol(key)) s = ddjvu_anno_get_metadata(self._annotations._sexpr._cexpr, cexpr_key.cexpr()) if s == NULL: raise KeyError(key) return decode_utf8(s) def keys(self): ''' M.keys() -> sequence of M's keys ''' return self._keys IF not PY3K: def iterkeys(self): ''' M.iterkeys() -> an iterator over the keys of M ''' return iter(self) def __iter__(self): return iter(self._keys) def values(self): ''' M.values() -> list of M's values ''' return map(self.__getitem__, self._keys) IF not PY3K: def itervalues(self): ''' M.itervalues() -> an iterator over values of M ''' return imap(self.__getitem__, self._keys) def items(self): ''' M.items() -> list of M's (key, value) pairs, as 2-tuples ''' return zip(self._keys, imap(self.__getitem__, self._keys)) IF not PY3K: def iteritems(self): ''' M.iteritems() -> an iterator over the (key, value) items of M ''' return izip(self._keys, imap(self.__getitem__, self._keys)) IF not PY3K: def has_key(self, k): ''' M.has_key(k) -> True if D has a key k, else False ''' return k in self def __contains__(self, k): return k in self._keys __author__ = 'Jakub Wilk ' IF PY3K: __version__ = decode_utf8(PYTHON_DJVULIBRE_VERSION) ELSE: __version__ = PYTHON_DJVULIBRE_VERSION __version__ = '%s/%d' % (__version__, DDJVU_VERSION) # vim:ts=4 sw=4 et ft=pyrex python-djvulibre-0.3.9/djvu/sexpr.pyx0000644000000000000000000007134511731632402017652 0ustar rootroot00000000000000# Copyright © 2007-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. #cython: autotestdict=False ''' DjVuLibre bindings: module for handling Lisp S-expressions ''' include 'common.pxi' cdef extern from 'libdjvu/miniexp.h': int cexpr_is_int 'miniexp_numberp'(cexpr_t sexp) nogil int cexpr_to_int 'miniexp_to_int'(cexpr_t sexp) nogil cexpr_t int_to_cexpr 'miniexp_number'(int n) nogil int cexpr_is_symbol 'miniexp_symbolp'(cexpr_t sexp) nogil char* cexpr_to_symbol 'miniexp_to_name'(cexpr_t sexp) nogil cexpr_t symbol_to_cexpr 'miniexp_symbol'(char* name) nogil cexpr_t cexpr_nil 'miniexp_nil' cexpr_t cexpr_dummy 'miniexp_dummy' int cexpr_is_list 'miniexp_listp'(cexpr_t exp) nogil int cexpr_is_nonempty_list 'miniexp_consp'(cexpr_t exp) nogil int cexpr_length 'miniexp_length'(cexpr_t exp) nogil cexpr_t cexpr_head 'miniexp_car'(cexpr_t exp) nogil cexpr_t cexpr_tail 'miniexp_cdr'(cexpr_t exp) nogil cexpr_t cexpr_nth 'miniexp_nth'(int n, cexpr_t exp) nogil cexpr_t pair_to_cexpr 'miniexp_cons'(cexpr_t head, cexpr_t tail) nogil cexpr_t cexpr_replace_head 'miniexp_rplaca'(cexpr_t exp, cexpr_t new_head) nogil cexpr_t cexpr_replace_tail 'miniexp_rplacd'(cexpr_t exp, cexpr_t new_tail) nogil cexpr_t cexpr_reverse_list 'miniexp_reverse'(cexpr_t exp) nogil int cexpr_is_str 'miniexp_stringp'(cexpr_t cexpr) nogil char* cexpr_to_str 'miniexp_to_str'(cexpr_t cexpr) nogil cexpr_t str_to_cexpr 'miniexp_string'(char* s) nogil cexpr_t cexpr_substr 'miniexp_substring'(char* s, int n) nogil cexpr_t cexpr_concat 'miniexp_concat'(cexpr_t cexpr_list) nogil cexpr_t gc_lock 'minilisp_acquire_gc_lock'(cexpr_t cexpr) nogil cexpr_t gc_unlock 'minilisp_release_gc_lock'(cexpr_t cexpr) nogil cvar_t* cvar_new 'minivar_alloc'() nogil void cvar_free 'minivar_free'(cvar_t* v) nogil cexpr_t* cvar_ptr 'minivar_pointer'(cvar_t* v) nogil int io_7bit 'minilisp_print_7bits' int (*io_puts 'minilisp_puts')(char *s) int (*io_getc 'minilisp_getc')() int (*io_ungetc 'minilisp_ungetc')(int c) void io_set_output 'minilisp_set_output'(FILE *f) void io_set_input 'minilisp_set_input'(FILE *f) cexpr_t cexpr_read 'miniexp_read'() cexpr_t cexpr_print 'miniexp_prin'(cexpr_t cexpr) cexpr_t cexpr_printw 'miniexp_pprin'(cexpr_t cexpr, int width) cdef extern from 'stdio.h': int EOF cdef object sys import sys cdef object format_exc from traceback import format_exc cdef object StringIO IF PY3K: from io import StringIO ELSE: from cStringIO import StringIO cdef object weakref import weakref cdef object symbol_dict symbol_dict = weakref.WeakValueDictionary() cdef Lock _myio_lock _myio_lock = allocate_lock() cdef object _myio_stdin cdef object _myio_stdout cdef int _myio_stdout_binary cdef object _myio_buffer _myio_buffer = [] cdef int _backup_io_7bit cdef int (*_backup_io_puts)(char *s) cdef int (*_backup_io_getc)() cdef int (*_backup_io_ungetc)(int c) cdef object write_unraisable_exception(object cause): message = format_exc() sys.stderr.write('Unhandled exception (%r)\n%s\n' % (cause, message)) cdef void myio_set(stdin, stdout): global _myio_stdin, _myio_stdout, _myio_stdout_binary, _myio_buffer global _backup_io_7bit, _backup_io_puts, _backup_io_getc, _backup_io_ungetc global io_7bit, io_puts, io_getc, io_ungetc cdef int opt_stdin, opt_stdout with nogil: acquire_lock(_myio_lock, WAIT_LOCK) _backup_io_7bit = io_7bit _backup_io_puts = io_puts _backup_io_getc = io_getc _backup_io_ungetc = io_ungetc _myio_stdin = stdin IF PY3K: # TODO opt_stdin = opt_stdout = 0 ELSE: opt_stdin = is_file(stdin) opt_stdout = is_file(stdout) if opt_stdin: IF not PY3K: io_set_input(file_to_cfile(stdin)) ELSE: pass # TODO else: io_getc = _myio_getc io_ungetc = _myio_ungetc _myio_stdout = stdout IF PY3K: _myio_stdout_binary = not hasattr(stdout, 'encoding') ELSE: _myio_stdout_binary = 1 if opt_stdout: IF not PY3K: io_set_output(file_to_cfile(stdout)) ELSE: pass # TODO else: io_puts = _myio_puts io_7bit = 1 _myio_buffer = [] cdef void myio_reset(): global _myio_stdin, _myio_stdout, _myio_stdout_binary, _myio_buffer global io_7bit, io_puts, io_getc, io_ungetc _myio_stdin = None _myio_stdout = None _myio_stdout_binary = 0 _myio_buffer = None io_7bit = _backup_io_7bit io_puts = _backup_io_puts io_getc = _backup_io_getc io_ungetc = _backup_io_ungetc release_lock(_myio_lock) cdef int _myio_puts(char *s): try: if _myio_stdout_binary: _myio_stdout.write(s) else: _myio_stdout.write(decode_utf8(s)) except: write_unraisable_exception(_myio_stdout) return EOF cdef int _myio_getc(): global _myio_buffer cdef int result if _myio_buffer: return _myio_buffer.pop() else: try: s = _myio_stdin.read(1) except: write_unraisable_exception(_myio_stdin) return EOF if s: if is_unicode(s): s = s.encode('UTF-8') IF PY3K: _myio_buffer += reversed(s) ELSE: _myio_buffer += map(ord, reversed(s)) return _myio_buffer.pop() else: return EOF cdef int _myio_ungetc(int c): global _myio_buffer _myio_buffer += (c,) cdef object the_sentinel the_sentinel = object() cdef class _WrappedCExpr: def __cinit__(self, object sentinel): if sentinel is not the_sentinel: raise_instantiation_error(type(self)) self.cvar = cvar_new() cdef cexpr_t cexpr(self): return cvar_ptr(self.cvar)[0] cdef object print_into(self, object stdout, object width): cdef cexpr_t cexpr if width is None: pass elif not is_int(width): raise TypeError('width must be an integer') elif width <= 0: raise ValueError('width <= 0') cexpr = self.cexpr() myio_set(None, stdout) try: if width is None: cexpr_print(cexpr) else: cexpr_printw(cexpr, width) finally: myio_reset() cdef object as_string(self, object width): stdout = StringIO() try: self.print_into(stdout, width) return stdout.getvalue() finally: stdout.close() def __dealloc__(self): cvar_free(self.cvar) cdef _WrappedCExpr wexpr(cexpr_t cexpr): cdef _WrappedCExpr wexpr wexpr = _WrappedCExpr(sentinel = the_sentinel) cvar_ptr(wexpr.cvar)[0] = cexpr return wexpr cdef class _MissingCExpr(_WrappedCExpr): cdef object print_into(self, object stdout, object width): raise NotImplementedError cdef object as_string(self, object width): raise NotImplementedError cdef _MissingCExpr wexpr_missing(): return _MissingCExpr(the_sentinel) cdef class BaseSymbol: cdef object __weakref__ cdef object _bytes def __cinit__(self, bytes): cdef char *cbytes cbytes = bytes self._bytes = cbytes def __repr__(self): IF PY3K: try: string = self._bytes.decode('UTF-8') except UnicodeDecodeError: string = self._bytes ELSE: string = self._bytes return '%s(%r)' % (get_type_name(_Symbol_), string) def __richcmp__(self, object other, int op): cdef BaseSymbol _self, _other if not typecheck(self, BaseSymbol) or not typecheck(other, BaseSymbol): return NotImplemented _self = self _other = other if op == 2 or op == 3: return richcmp(_self._bytes, _other._bytes, op) return NotImplemented def __hash__(self): return hash(self._bytes) property bytes: def __get__(self): return self._bytes IF not PY3K: def __str__(self): return self._bytes IF PY3K: def __str__(self): return self._bytes.decode('UTF-8') ELSE: def __unicode__(self): return self._bytes.decode('UTF-8') def __reduce__(self): return (Symbol, (self._bytes,)) def Symbol__new__(cls, name): ''' Symbol(name) -> a symbol ''' self = None if is_unicode(name): name = encode_utf8(name) try: if cls is _Symbol_: self = symbol_dict[name] except KeyError: pass if self is None: if not is_bytes(name): name = str(name) IF PY3K: name = encode_utf8(name) self = BaseSymbol.__new__(cls, name) if cls is _Symbol_: symbol_dict[name] = self return self class Symbol(BaseSymbol): __new__ = staticmethod(Symbol__new__) cdef object _Symbol_ _Symbol_ = Symbol del Symbol__new__ def Expression__new__(cls, value): ''' Expression(value) -> an expression ''' if typecheck(value, _Expression_) and (not typecheck(value, ListExpression) or not value): return value if is_int(value): return IntExpression(value) elif typecheck(value, _Symbol_): return SymbolExpression(value) elif is_unicode(value): return StringExpression(encode_utf8(value)) elif is_bytes(value): if PY3K: return StringExpression(bytes(value)) else: return StringExpression(str(value)) else: return ListExpression(iter(value)) def _expression_from_stream(stdin): ''' Expression.from_stream(stream) -> an expression Read an expression from a stream. ''' try: myio_set(stdin, None) try: return _c2py(cexpr_read()) except InvalidExpression: raise ExpressionSyntaxError finally: myio_reset() def _expression_from_string(str): ''' Expression.from_string(s) -> an expression Read an expression from a string. ''' stdin = StringIO(str) try: return _Expression_.from_stream(stdin) finally: stdin.close() class Expression(BaseExpression): ''' Notes about the textual representation of S-expressions ------------------------------------------------------- Special characters are: * the parenthesis '(' and ')', * the double quote '"', * the vertical bar '|'. Symbols are represented by their name. Vertical bars | can be used to delimit names that contain blanks, special characters, non printable characters, non-ASCII characters, or can be confused as a number. Numbers follow the syntax specified by the C function strtol() with base=0. Strings are delimited by double quotes. All C string escapes are recognized. Non-printable ASCII characters must be escaped. List are represented by an open parenthesis '(' followed by the space separated list elements, followed by a closing parenthesis ')'. When the cdr of the last pair is non zero, the closed parenthesis is preceded by a space, a dot '.', a space, and the textual representation of the cdr. (This is only partially supported by Python bindings.) ''' __new__ = staticmethod(Expression__new__) from_string = staticmethod(_expression_from_string) from_stream = staticmethod(_expression_from_stream) cdef object _Expression_ _Expression_ = Expression del Expression__new__ cdef object BaseExpression_richcmp(object left, object right, int op): if not typecheck(left, BaseExpression): return NotImplemented elif not typecheck(right, BaseExpression): return NotImplemented return richcmp(left.value, right.value, op) cdef class BaseExpression: ''' Don't use this class directly. Use the Expression class instead. ''' cdef _WrappedCExpr wexpr def __cinit__(self, *args, **kwargs): self.wexpr = wexpr_missing() def print_into(self, stdout, width=None): ''' expr.print_into(file[, width]) -> None Print the expression into the file. ''' self.wexpr.print_into(stdout, width) def as_string(self, width=None): ''' expr.as_string([width]) -> a string Return a string representation of the expression. ''' return self.wexpr.as_string(width) def __str__(self): return self.as_string() property value: ''' The actual "pythonic" value of the expression. ''' def __get__(self): return self._get_value() def _get_value(self): raise NotImplementedError def __richcmp__(self, other, int op): return BaseExpression_richcmp(self, other, op) def __repr__(self): return '%s(%r)' % (get_type_name(_Expression_), self.value) def __copy__(self): # Most of S-expressions are immutable. # Mutable S-expressions should override this method. return self def __deepcopy__(self, memo): # Most of S-expressions are immutable. # Mutable S-expressions should override this method. return self def __reduce__(self): return (_expression_from_string, (self.as_string(),)) def IntExpression__new__(cls, value): ''' IntExpression(n) -> an integer expression ''' cdef BaseExpression self self = BaseExpression.__new__(cls) if typecheck(value, _WrappedCExpr): self.wexpr = value elif is_int(value): if -1 << 29 <= value < 1 << 29: self.wexpr = wexpr(int_to_cexpr(value)) else: raise ValueError('value not in range(-2 ** 29, 2 ** 29)') else: raise TypeError('value must be an integer') return self class IntExpression(_Expression_): ''' IntExpression can represent any integer in range(-2 ** 29, 2 ** 29). To create objects of this class, use the Expression class constructor. ''' __new__ = staticmethod(IntExpression__new__) IF PY3K: def __bool__(self): return bool(self.value) ELSE: def __nonzero__(self): return bool(self.value) def __int__(self): return self.value def __long__(self): return 0L + self.value def _get_value(BaseExpression self not None): return cexpr_to_int(self.wexpr.cexpr()) def __richcmp__(self, other, int op): return BaseExpression_richcmp(self, other, op) def __hash__(self): return hash(self.value) del IntExpression__new__ def SymbolExpression__new__(cls, value): ''' SymbolExpression(Symbol(s)) -> a symbol expression ''' cdef BaseExpression self cdef BaseSymbol symbol self = BaseExpression.__new__(cls) if typecheck(value, _WrappedCExpr): self.wexpr = value elif typecheck(value, _Symbol_): symbol = value self.wexpr = wexpr(symbol_to_cexpr(symbol._bytes)) else: raise TypeError('value must be a Symbol') return self class SymbolExpression(_Expression_): ''' To create objects of this class, use the Expression class constructor. ''' __new__ = staticmethod(SymbolExpression__new__) def _get_value(BaseExpression self not None): return _Symbol_(cexpr_to_symbol(self.wexpr.cexpr())) def __richcmp__(self, other, int op): return BaseExpression_richcmp(self, other, op) def __hash__(self): return hash(self.value) del SymbolExpression__new__ def StringExpression__new__(cls, value): ''' SymbolExpression(s) -> a string expression ''' cdef BaseExpression self self = BaseExpression.__new__(cls) if typecheck(value, _WrappedCExpr): self.wexpr = value elif is_bytes(value): gc_lock(NULL) # protect from collecting a just-created object try: self.wexpr = wexpr(str_to_cexpr(value)) finally: gc_unlock(NULL) else: raise TypeError('value must be a byte string') return self class StringExpression(_Expression_): ''' To create objects of this class, use the Expression class constructor. ''' __new__ = staticmethod(StringExpression__new__) def bytes(BaseExpression self not None): return cexpr_to_str(self.wexpr.cexpr()) bytes = property(bytes) def _get_value(BaseExpression self not None): cdef char *bytes bytes = cexpr_to_str(self.wexpr.cexpr()) IF PY3K: return decode_utf8(bytes) ELSE: return bytes IF PY3K: def __repr__(BaseExpression self not None): cdef char *bytes bytes = cexpr_to_str(self.wexpr.cexpr()) try: string = decode_utf8(bytes) except UnicodeDecodeError: string = bytes return '%s(%r)' % (get_type_name(_Expression_), string) def __richcmp__(self, other, int op): return BaseExpression_richcmp(self, other, op) def __hash__(self): return hash(self.value) del StringExpression__new__ class InvalidExpression(ValueError): pass class ExpressionSyntaxError(Exception): ''' Invalid expression syntax. ''' pass cdef _WrappedCExpr public_py2c(object o): cdef BaseExpression pyexpr pyexpr = _Expression_(o) if pyexpr is None: raise TypeError return pyexpr.wexpr cdef object public_c2py(cexpr_t cexpr): return _c2py(cexpr) cdef BaseExpression _c2py(cexpr_t cexpr): if cexpr == cexpr_dummy: raise InvalidExpression _wexpr = wexpr(cexpr) if cexpr_is_int(cexpr): result = IntExpression(_wexpr) elif cexpr_is_symbol(cexpr): result = SymbolExpression(_wexpr) elif cexpr_is_list(cexpr): result = ListExpression(_wexpr) elif cexpr_is_str(cexpr): result = StringExpression(_wexpr) else: raise ValueError return result cdef _WrappedCExpr _build_list_cexpr(object items): cdef cexpr_t cexpr cdef BaseExpression citem gc_lock(NULL) # protect from collecting a just-created object try: cexpr = cexpr_nil for item in items: if typecheck(item, BaseExpression): citem = item else: citem = _Expression_(item) if citem is None: raise TypeError cexpr = pair_to_cexpr(citem.wexpr.cexpr(), cexpr) cexpr = cexpr_reverse_list(cexpr) return wexpr(cexpr) finally: gc_unlock(NULL) def ListExpression__new__(cls, items): ''' ListExpression(iterable) -> a list expression ''' cdef BaseExpression self self = BaseExpression.__new__(cls) if typecheck(items, _WrappedCExpr): self.wexpr = items else: self.wexpr = _build_list_cexpr(items) return self class ListExpression(_Expression_): ''' To create objects of this class, use the Expression class constructor. ''' __new__ = staticmethod(ListExpression__new__) IF PY3K: def __bool__(BaseExpression self not None): return self.wexpr.cexpr() != cexpr_nil ELSE: def __nonzero__(BaseExpression self not None): return self.wexpr.cexpr() != cexpr_nil def __len__(BaseExpression self not None): cdef cexpr_t cexpr cdef int n cexpr = self.wexpr.cexpr() n = 0 while cexpr != cexpr_nil: cexpr = cexpr_tail(cexpr) n = n + 1 return n def __getitem__(BaseExpression self not None, key): cdef cexpr_t cexpr cdef int n cexpr = self.wexpr.cexpr() if is_int(key): n = key if n < 0: n = n + len(self) if n < 0: raise IndexError('list index of out range') while 1: if cexpr == cexpr_nil: raise IndexError('list index of out range') if n > 0: n = n - 1 cexpr = cexpr_tail(cexpr) else: cexpr = cexpr_head(cexpr) break elif is_slice(key): if (is_int(key.start) or key.start is None) and key.stop is None and key.step is None: n = key.start or 0 if n < 0: n = n + len(self) while n > 0 and cexpr != cexpr_nil: cexpr = cexpr_tail(cexpr) n = n - 1 else: raise NotImplementedError('only [n:] slices are supported') else: raise TypeError('key must be an integer or a slice') return _c2py(cexpr) def __setitem__(BaseExpression self not None, key, value): cdef cexpr_t cexpr cdef cexpr_t prev_cexpr cdef cexpr_t new_cexpr cdef int n cdef BaseExpression pyexpr cexpr = self.wexpr.cexpr() pyexpr = _Expression_(value) new_cexpr = pyexpr.wexpr.cexpr() if is_int(key): n = key if n < 0: n = n + len(self) if n < 0: raise IndexError('list index of out range') while 1: if cexpr == cexpr_nil: raise IndexError('list index of out range') if n > 0: n = n - 1 cexpr = cexpr_tail(cexpr) else: cexpr_replace_head(cexpr, new_cexpr) break elif is_slice(key): if not cexpr_is_list(new_cexpr): raise TypeError('can only assign a list expression') if (is_int(key.start) or key.start is None) and key.stop is None and key.step is None: n = key.start or 0 if n < 0: n = n + len(self) prev_cexpr = cexpr_nil while n > 0 and cexpr != cexpr_nil: prev_cexpr = cexpr cexpr = cexpr_tail(cexpr) n = n - 1 if prev_cexpr == cexpr_nil: self.wexpr = wexpr(new_cexpr) else: cexpr_replace_tail(prev_cexpr, new_cexpr) else: raise NotImplementedError('only [n:] slices are supported') else: raise TypeError('key must be an integer or a slice') def __delitem__(BaseExpression self not None, key): if is_int(key): self.pop(key) elif is_slice(key): self[key] = () else: raise TypeError('key must be an integer or a slice') def extend(self, iterable): # Normally one would use # self[len(self):] = … # but Cython (at least 0.13) generates broken code for such a statement. # http://bugs.debian.org/604963 iter(iterable) self[slice(len(self), None, None)] = iterable def __iadd__(self, iterable): # Normally one would use # self[len(self):] = … # but Cython (at least 0.13) generates broken code for such a statement. # http://bugs.debian.org/604963 iter(iterable) self[slice(len(self), None, None)] = iterable return self def insert(BaseExpression self not None, long index, item): cdef cexpr_t cexpr, new_cexpr cdef BaseExpression citem cexpr = self.wexpr.cexpr() if index < 0: index += len(self) if index < 0: index = 0 if typecheck(item, BaseExpression): citem = item else: citem = _Expression_(item) if citem is None: raise TypeError if index == 0 or cexpr == cexpr_nil: gc_lock(NULL) # protect from collecting a just-created object try: new_cexpr = pair_to_cexpr(citem.wexpr.cexpr(), cexpr) self.wexpr = wexpr(new_cexpr) finally: gc_unlock(NULL) return while 1: assert cexpr != cexpr_nil if index > 1 and cexpr_tail(cexpr) != cexpr_nil: index = index - 1 cexpr = cexpr_tail(cexpr) else: gc_lock(NULL) # protect from collecting a just-created object try: new_cexpr = pair_to_cexpr(citem.wexpr.cexpr(), cexpr_tail(cexpr)) cexpr_replace_tail(cexpr, new_cexpr) finally: gc_unlock(NULL) break def append(BaseExpression self not None, item): return self.insert(len(self), item) def reverse(BaseExpression self not None): cdef cexpr_t cexpr, new_cexpr gc_lock(NULL) # protect from collecting a just-created object try: new_cexpr = cexpr_reverse_list(self.wexpr.cexpr()) self.wexpr = wexpr(new_cexpr) finally: gc_unlock(NULL) def pop(BaseExpression self not None, long index=-1): cdef cexpr_t cexpr, citem cexpr = self.wexpr.cexpr() if cexpr == cexpr_nil: raise IndexError('pop from empty list') if index < 0: index += len(self) if index < 0: raise IndexError('pop index of out range') if index == 0: result = _c2py(cexpr_head(cexpr)) self.wexpr = wexpr(cexpr_tail(cexpr)) return result while cexpr_tail(cexpr) != cexpr_nil: if index > 1: index = index - 1 cexpr = cexpr_tail(cexpr) else: result = _c2py(cexpr_head(cexpr_tail(cexpr))) cexpr_replace_tail(cexpr, cexpr_tail(cexpr_tail(cexpr))) return result raise IndexError('pop index of out range') def remove(BaseExpression self not None, item): cdef cexpr_t cexpr cdef BaseExpression citem cexpr = self.wexpr.cexpr() if cexpr == cexpr_nil: raise IndexError('item not in list') if _c2py(cexpr_head(cexpr)) == item: self.wexpr = wexpr(cexpr_tail(cexpr)) return while 1: assert cexpr != cexpr_nil if cexpr_tail(cexpr) == cexpr_nil: raise IndexError('item not in list') if _c2py(cexpr_head(cexpr_tail(cexpr))) == item: cexpr_replace_tail(cexpr, cexpr_tail(cexpr_tail(cexpr))) return cexpr = cexpr_tail(cexpr) def index(self, value): # TODO: optimize for i, v in enumerate(self): if v == value: return i raise ValueError('value not in list') def count(self, value): # TODO: optimize cdef long counter = 0 for v in self: if v == value: counter += 1 return counter def __iter__(self): return _ListExpressionIterator(self) # TODO: Once we don't support Python <2.6, this can be replaced by simpler: # __hash__ = None def __hash__(self): raise TypeError('unhashable type: \'%s\'' % (get_type_name(type(self)),)) def _get_value(BaseExpression self not None): cdef cexpr_t current current = self.wexpr.cexpr() result = [] while current != cexpr_nil: list_append(result, _c2py(cexpr_head(current))._get_value()) current = cexpr_tail(current) return tuple(result) def __copy__(self): return _Expression_(self) def __deepcopy__(self, memo): return _Expression_(self._get_value()) del ListExpression__new__ if sys.version_info >= (2, 6): import collections collections.MutableSequence.register(ListExpression) del collections cdef class _ListExpressionIterator: cdef BaseExpression expression cdef cexpr_t cexpr def __cinit__(self, BaseExpression expression not None): self.expression = expression self.cexpr = expression.wexpr.cexpr() def __next__(self): cdef cexpr_t cexpr cexpr = self.cexpr if cexpr == cexpr_nil: raise StopIteration self.cexpr = cexpr_tail(cexpr) cexpr = cexpr_head(cexpr) return _c2py(cexpr) def __iter__(self): return self __all__ = ('Symbol', 'Expression', 'IntExpression', 'SymbolExpression', 'StringExpression', 'ListExpression', 'InvalidExpression', 'ExpressionSyntaxError') __author__ = 'Jakub Wilk ' IF PY3K: __version__ = decode_utf8(PYTHON_DJVULIBRE_VERSION) ELSE: __version__ = PYTHON_DJVULIBRE_VERSION # vim:ts=4 sw=4 et ft=pyrex python-djvulibre-0.3.9/djvu/__init__.py0000644000000000000000000000000011515354147020043 0ustar rootroot00000000000000python-djvulibre-0.3.9/djvu/common.pxi0000644000000000000000000001021411731457606017760 0ustar rootroot00000000000000# Copyright © 2008-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. include 'config.pxi' # python-distutils preprocessor macros cdef extern from *: char* PYTHON_DJVULIBRE_VERSION # C library ctypedef int size_t cdef extern from 'stdio.h': ctypedef struct FILE cdef extern from 'stdlib.h': void libc_free 'free'(void* ptr) nogil cdef extern from 'string.h': int strcmp(char *s1, char *s2) nogil size_t strlen(char *s) nogil # Python library cdef extern from 'Python.h': void* py_malloc 'PyMem_Malloc'(size_t) void py_free 'PyMem_Free'(void*) int is_short_int 'PyInt_Check'(object) int is_long_int 'PyLong_Check'(object) int is_number 'PyNumber_Check'(object) int is_float 'PyFloat_Check'(object) int is_slice 'PySlice_Check'(object) int is_unicode 'PyUnicode_Check'(object) int is_string 'PyString_Check'(object) IF PY3K: int is_bytes 'PyBytes_Check'(object) ELSE: int is_bytes 'PyString_Check'(object) object encode_utf8 'PyUnicode_AsUTF8String'(object) object decode_utf8_ex 'PyUnicode_DecodeUTF8'(char *, Py_ssize_t, char *) IF PY3K: int bytes_to_charp 'PyBytes_AsStringAndSize'(object, char**, Py_ssize_t*) except -1 object charp_to_bytes 'PyBytes_FromStringAndSize'(char *, Py_ssize_t) ELSE: int bytes_to_charp 'PyString_AsStringAndSize'(object, char**, Py_ssize_t*) except -1 object charp_to_bytes 'PyString_FromStringAndSize'(char *, Py_ssize_t) IF PY3K: object charp_to_string 'PyUnicode_FromString'(char *) ELSE: object charp_to_string 'PyString_FromString'(char *) int buffer_to_writable_memory 'PyObject_AsWriteBuffer'(object, void **, Py_ssize_t *) IF PY3K: object int 'PyNumber_Long'(object) ELSE: object int 'PyNumber_Int'(object) object bool 'PyBool_FromLong'(long) object voidp_to_int 'PyLong_FromVoidPtr'(void *) IF PY3K: object posix_error 'PyErr_SetFromErrno'(object) int file_to_fd 'PyObject_AsFileDescriptor'(object) ELSE: FILE* file_to_cfile 'PyFile_AsFile'(object) int list_append 'PyList_Append'(object, object) except -1 cdef object richcmp 'PyObject_RichCompare'(object, object, int) cdef extern from 'pythread.h': ctypedef void* Lock 'PyThread_type_lock' cdef Lock allocate_lock 'PyThread_allocate_lock'() cdef void free_lock 'PyThread_free_lock'(Lock lock) cdef int acquire_lock 'PyThread_acquire_lock'(Lock lock, int mode) nogil cdef void release_lock 'PyThread_release_lock'(Lock lock) ctypedef enum: WAIT_LOCK NOWAIT_LOCK cdef extern from 'object.h': ctypedef struct PyTypeObject: char *tp_name ctypedef struct PyObject: PyTypeObject *ob_type int _typecheck 'PyObject_TypeCheck'(object o, PyTypeObject* type) cdef int is_int(object o): return is_short_int(o) or is_long_int(o) cdef object type(object o): return ((o).ob_type) IF PY3K: cdef object get_type_name(object type): return decode_utf8((type).tp_name) ELSE: cdef char* get_type_name(object type): return (type).tp_name cdef int typecheck(object o, object type): return _typecheck(o, type) cdef int is_file(object o): IF PY3K: return not is_number(o) and file_to_fd(o) != -1 ELSE: return typecheck(o, file) cdef void raise_instantiation_error(object cls) except *: raise TypeError, 'cannot create \'%s\' instances' % get_type_name(cls) cdef object decode_utf8(char* s): return decode_utf8_ex(s, strlen(s), NULL) cdef extern from 'pyerrors.h': ctypedef class __builtin__.Exception [object PyBaseExceptionObject]: pass # vim:ts=4 sw=4 et ft=pyrex python-djvulibre-0.3.9/djvu/sexpr.pxd0000644000000000000000000000171311501754041017614 0ustar rootroot00000000000000# Copyright © 2007, 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. cdef extern from 'libdjvu/miniexp.h': struct cexpr_s 'miniexp_s' ctypedef cexpr_s* cexpr_t 'miniexp_t' cdef extern struct cvar_s 'minivar_s' ctypedef cvar_s cvar_t 'minivar_t' cdef class _WrappedCExpr: cdef cvar_t* cvar cdef cexpr_t cexpr(self) cdef object print_into(self, object, object) cdef object as_string(self, object) cdef object public_c2py(cexpr_t) cdef _WrappedCExpr public_py2c(object) # vim:ts=4 sw=4 et ft=pyrex python-djvulibre-0.3.9/COPYING0000644000000000000000000004310311211760140016013 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. python-djvulibre-0.3.9/PKG-INFO0000644000000000000000000000210111731710327016057 0ustar rootroot00000000000000Metadata-Version: 1.1 Name: python-djvulibre Version: 0.3.9 Summary: Python support for the DjVu image format Home-page: http://jwilk.net/software/python-djvulibre Author: Jakub Wilk Author-email: jwilk@jwilk.net License: GNU GPL 2 Description: *python-djvulibre* is a set of `Python `_ bindings for the `DjVuLibre `_ library, an open source implementation of `DjVu `_. Platform: all Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows :: Windows 95/98/2000 Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000 Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Multimedia :: Graphics Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion Classifier: Topic :: Text Processing