Full Code of RussCoder/djvujs for AI

master b41bd0557bd5 cached
308 files
724.3 KB
191.8k tokens
701 symbols
1 requests
Download .txt
Showing preview only (799K chars total). Download the full file or copy to clipboard to get everything.
Repository: RussCoder/djvujs
Branch: master
Commit: b41bd0557bd5
Files: 308
Total size: 724.3 KB

Directory structure:
gitextract_y7vg5pf7/

├── .gitattributes
├── .gitignore
├── .js
├── GNU_GPL_v2
├── LICENSE.md
├── README.md
├── THE_UNLICENSE
├── TRANSLATION.md
├── extension/
│   ├── background.js
│   ├── content.js
│   ├── initializer.js
│   ├── manifest_v2.json
│   ├── manifest_v3.json
│   └── viewer.html
├── library/
│   ├── .gitignore
│   ├── API.md
│   ├── CHANGELOG.md
│   ├── README.md
│   ├── app/
│   │   ├── app.css
│   │   ├── app.html
│   │   └── app.js
│   ├── assets/
│   │   ├── DjVu3Spec.djvu
│   │   ├── DjVu3Spec_5-10.djvu
│   │   ├── DjVu3Spec_bundled.djvu
│   │   ├── DjVu3Spec_contents.json
│   │   ├── DjVu3Spec_indirect/
│   │   │   ├── dict0020.iff
│   │   │   ├── dict0040.iff
│   │   │   ├── dict0060.iff
│   │   │   ├── dict0071.iff
│   │   │   ├── index.djvu
│   │   │   ├── p0001_1.djvu
│   │   │   ├── p0002.djvu
│   │   │   ├── p0003.djvu
│   │   │   ├── p0004.djvu
│   │   │   ├── p0005.djvu
│   │   │   ├── p0006.djvu
│   │   │   ├── p0007.djvu
│   │   │   ├── p0008.djvu
│   │   │   ├── p0009.djvu
│   │   │   ├── p0010.djvu
│   │   │   ├── p0011.djvu
│   │   │   ├── p0012.djvu
│   │   │   ├── p0013.djvu
│   │   │   ├── p0014.djvu
│   │   │   ├── p0015.djvu
│   │   │   ├── p0016.djvu
│   │   │   ├── p0017.djvu
│   │   │   ├── p0018.djvu
│   │   │   ├── p0019.djvu
│   │   │   ├── p0020.djvu
│   │   │   ├── p0021.djvu
│   │   │   ├── p0022.djvu
│   │   │   ├── p0023.djvu
│   │   │   ├── p0024.djvu
│   │   │   ├── p0025.djvu
│   │   │   ├── p0026.djvu
│   │   │   ├── p0027.djvu
│   │   │   ├── p0028.djvu
│   │   │   ├── p0029.djvu
│   │   │   ├── p0030.djvu
│   │   │   ├── p0031.djvu
│   │   │   ├── p0032.djvu
│   │   │   ├── p0033.djvu
│   │   │   ├── p0034.djvu
│   │   │   ├── p0035.djvu
│   │   │   ├── p0036.djvu
│   │   │   ├── p0037.djvu
│   │   │   ├── p0038.djvu
│   │   │   ├── p0039.djvu
│   │   │   ├── p0040.djvu
│   │   │   ├── p0041.djvu
│   │   │   ├── p0042.djvu
│   │   │   ├── p0043.djvu
│   │   │   ├── p0044.djvu
│   │   │   ├── p0045.djvu
│   │   │   ├── p0046.djvu
│   │   │   ├── p0047.djvu
│   │   │   ├── p0048.djvu
│   │   │   ├── p0049.djvu
│   │   │   ├── p0050.djvu
│   │   │   ├── p0051.djvu
│   │   │   ├── p0052.djvu
│   │   │   ├── p0053.djvu
│   │   │   ├── p0054.djvu
│   │   │   ├── p0055.djvu
│   │   │   ├── p0056.djvu
│   │   │   ├── p0057.djvu
│   │   │   ├── p0058.djvu
│   │   │   ├── p0059.djvu
│   │   │   ├── p0060.djvu
│   │   │   ├── p0061.djvu
│   │   │   ├── p0062.djvu
│   │   │   ├── p0063.djvu
│   │   │   ├── p0064.djvu
│   │   │   ├── p0065.djvu
│   │   │   ├── p0066.djvu
│   │   │   ├── p0067.djvu
│   │   │   ├── p0068.djvu
│   │   │   ├── p0069.djvu
│   │   │   ├── p0070.djvu
│   │   │   ├── p0071.djvu
│   │   │   ├── thum0001.thumb
│   │   │   ├── thum0002.thumb
│   │   │   ├── thum0003.thumb
│   │   │   ├── thum0004.thumb
│   │   │   ├── thum0005.thumb
│   │   │   ├── thum0006.thumb
│   │   │   ├── thum0007.thumb
│   │   │   ├── thum0008.thumb
│   │   │   ├── thum0009.thumb
│   │   │   └── thum0010.thumb
│   │   ├── big-scanned-page.djvu
│   │   ├── boy.djvu
│   │   ├── boy_and_chicken.djvu
│   │   ├── boy_jb2.djvu
│   │   ├── boy_jb2_rotate180.djvu
│   │   ├── boy_jb2_rotate270.djvu
│   │   ├── boy_jb2_rotate90.djvu
│   │   ├── carte.djvu
│   │   ├── ccitt_2.djvu
│   │   ├── century_dict/
│   │   │   ├── index08.djvu
│   │   │   ├── p6683.djvu
│   │   │   └── p6698.djvu
│   │   ├── chicken.djvu
│   │   ├── colorbook.djvu
│   │   ├── czech.djvu
│   │   ├── czech_1-3.djvu
│   │   ├── czech_indirect/
│   │   │   ├── anno0001.iff
│   │   │   ├── black_1.djvu
│   │   │   ├── dict0085.iff
│   │   │   ├── index.djvu
│   │   │   ├── p0001.djvu
│   │   │   ├── p0002.djvu
│   │   │   └── slovnik
│   │   ├── deutsch.djvu
│   │   ├── djvu3spec+.djvu
│   │   ├── happy_birthday.djvu
│   │   ├── history.djvu
│   │   ├── history_2.djvu
│   │   ├── irish.djvu
│   │   ├── links.djvu
│   │   ├── malliavin.djvu
│   │   ├── navm_fgbz.djvu
│   │   ├── polish_indirect/
│   │   │   ├── index.djvu
│   │   │   ├── shared_anno.iff
│   │   │   └── sw1-0002.djvu
│   │   ├── problem_page.djvu
│   │   ├── slow.djvu
│   │   └── vega.djvu
│   ├── debug/
│   │   ├── async.html
│   │   ├── css/
│   │   │   └── style.css
│   │   ├── examples.html
│   │   ├── index.html
│   │   ├── js/
│   │   │   ├── DjVuGlobals.js
│   │   │   ├── DjVuViewer.js
│   │   │   ├── async.js
│   │   │   ├── debug.js
│   │   │   ├── examples.js
│   │   │   ├── handler.js
│   │   │   ├── initScript.js
│   │   │   └── reloader.js
│   │   └── sync.html
│   ├── package.json
│   ├── rollup.config.js
│   ├── server.js
│   ├── src/
│   │   ├── ByteStream.js
│   │   ├── ByteStreamWriter.js
│   │   ├── DjVu.js
│   │   ├── DjVuDocument.js
│   │   ├── DjVuErrors.js
│   │   ├── DjVuPage.js
│   │   ├── DjVuWorker.js
│   │   ├── DjVuWorkerScript.js
│   │   ├── DjVuWriter.js
│   │   ├── ZPCodec.js
│   │   ├── bzz/
│   │   │   ├── BZZDecoder.js
│   │   │   └── BZZEncoder.js
│   │   ├── chunks/
│   │   │   ├── DirmChunk.js
│   │   │   ├── DjViChunk.js
│   │   │   ├── DjVuAnno.js
│   │   │   ├── DjVuPalette.js
│   │   │   ├── DjVuText.js
│   │   │   ├── IFFChunks.js
│   │   │   ├── NavmChunk.js
│   │   │   └── ThumChunk.js
│   │   ├── index.js
│   │   ├── iw44/
│   │   │   ├── IWCodecBaseClass.js
│   │   │   ├── IWDecoder.js
│   │   │   ├── IWEncoder.js
│   │   │   ├── IWImage.js
│   │   │   ├── IWImageWriter.js
│   │   │   └── IWStructures.js
│   │   ├── jb2/
│   │   │   ├── JB2Codec.js
│   │   │   ├── JB2Dict.js
│   │   │   ├── JB2Image.js
│   │   │   └── JB2Structures.js
│   │   └── methods/
│   │       ├── bundle.js
│   │       └── load.js
│   └── tests/
│       ├── embed.html
│       ├── tests.css
│       ├── tests.html
│       └── tests.js
├── package.json
└── viewer/
    ├── .gitignore
    ├── CHANGELOG.md
    ├── cypress/
    │   ├── e2e/
    │   │   ├── fullscreen_mode.cy.js
    │   │   ├── initial_screen.cy.js
    │   │   ├── menu.cy.js
    │   │   ├── mobile_version.cy.js
    │   │   ├── modal_windows.cy.js
    │   │   └── toolbar.cy.js
    │   ├── shared.js
    │   └── utils.js
    ├── cypress.config.js
    ├── index.html
    ├── jsconfig.json
    ├── package.json
    ├── public/
    │   └── manifest.json
    ├── src/
    │   ├── App.test.js
    │   ├── DjVu.js
    │   ├── DjVuViewer.jsx
    │   ├── actions/
    │   │   └── actions.js
    │   ├── components/
    │   │   ├── App.jsx
    │   │   ├── AppContext.jsx
    │   │   ├── ErrorPage.jsx
    │   │   ├── FileBlock.jsx
    │   │   ├── FileLoadingScreen.jsx
    │   │   ├── ImageBlock/
    │   │   │   ├── CanvasImage.jsx
    │   │   │   ├── ComplexImage.jsx
    │   │   │   ├── ImageBlock.jsx
    │   │   │   ├── TextLayer.jsx
    │   │   │   └── VirtualList.jsx
    │   │   ├── InitialScreen/
    │   │   │   ├── FileZone.jsx
    │   │   │   ├── InitialScreen.jsx
    │   │   │   ├── LinkBlock.jsx
    │   │   │   └── ThemeSwitcher.jsx
    │   │   ├── Language/
    │   │   │   ├── AddLanguageButton.jsx
    │   │   │   ├── IncompleteTranslationWindow.jsx
    │   │   │   ├── LanguagePanel.jsx
    │   │   │   ├── LanguageSelector.jsx
    │   │   │   └── LanguageWarningSign.jsx
    │   │   ├── LeftPanel/
    │   │   │   ├── ContentsPanel.jsx
    │   │   │   ├── LeftPanel.jsx
    │   │   │   └── TreeItem.jsx
    │   │   ├── LoadingLayer.jsx
    │   │   ├── Main.jsx
    │   │   ├── Menu.jsx
    │   │   ├── ModalWindows/
    │   │   │   ├── ErrorWindow.jsx
    │   │   │   ├── HelpWindow.jsx
    │   │   │   ├── ModalWindow.jsx
    │   │   │   ├── OptionsWindow.jsx
    │   │   │   ├── PrintDialog.jsx
    │   │   │   └── SaveDialog.jsx
    │   │   ├── StyledPrimitives.jsx
    │   │   ├── TextBlock.jsx
    │   │   ├── Toolbar/
    │   │   │   ├── ContentsButton.jsx
    │   │   │   ├── CursorModeButtonGroup.jsx
    │   │   │   ├── HideButton.jsx
    │   │   │   ├── MenuButton.jsx
    │   │   │   ├── PageNumber.jsx
    │   │   │   ├── PageNumberBlock.jsx
    │   │   │   ├── PinButton.jsx
    │   │   │   ├── RotationControl.jsx
    │   │   │   ├── ScaleGizmo.jsx
    │   │   │   ├── Toolbar.jsx
    │   │   │   └── ViewModeButtons.jsx
    │   │   ├── Translation.jsx
    │   │   ├── cssMixins.js
    │   │   ├── helpers.js
    │   │   └── misc/
    │   │       ├── CloseButton.jsx
    │   │       ├── FullPageViewButton.jsx
    │   │       ├── FullscreenButton.jsx
    │   │       ├── HelpButton.jsx
    │   │       ├── LoadingPhrase.jsx
    │   │       ├── OptionsButton.jsx
    │   │       ├── ProgressBar.jsx
    │   │       ├── SaveButton.jsx
    │   │       └── SaveNotification.jsx
    │   ├── constants/
    │   │   ├── Constants.js
    │   │   ├── actionTypes.js
    │   │   └── index.js
    │   ├── hotkeys.js
    │   ├── index.js
    │   ├── locales/
    │   │   ├── ChineseSimplified.js
    │   │   ├── English.js
    │   │   ├── French.js
    │   │   ├── Italian.js
    │   │   ├── Portuguese.js
    │   │   ├── Russian.js
    │   │   ├── Spanish.js
    │   │   ├── Swedish.js
    │   │   ├── Ukrainian.js
    │   │   └── index.js
    │   ├── reducers/
    │   │   ├── commonReducer.js
    │   │   ├── fileLoadingReducer.js
    │   │   ├── fileProcessingReducer.js
    │   │   ├── index.js
    │   │   ├── pageReducer.js
    │   │   └── printReducer.js
    │   ├── sagas/
    │   │   ├── ContinuousScrollManager.js
    │   │   ├── PageStorage.js
    │   │   ├── PagesCache.js
    │   │   ├── PrintManager.js
    │   │   └── rootSaga.js
    │   ├── store.js
    │   └── utils.js
    ├── syncLocales.js
    └── vite.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
# .bin files containing text are used as test assets
*.bin binary

================================================
FILE: .gitignore
================================================
.idea
.vscode/
node_modules
_src
build
dist
extension/web-ext-artifacts
extension/manifest.json

================================================
FILE: .js
================================================
/**
 * Used in npm scripts to copy files
 */

'use strict';

const fs = require('fs');

async function copy() {
    const buildFolder = 'build/';
    const extensionFolder = 'extension/dist/'
    if (!fs.existsSync(buildFolder)) fs.mkdirSync(buildFolder);
    if (!fs.existsSync(extensionFolder)) fs.mkdirSync(extensionFolder);

    const copyFile = (path) => {
        const fileName = path.split('/').at(-1);
        fs.copyFileSync(path, buildFolder + fileName);
        fs.copyFileSync(path, extensionFolder + fileName);
    }

    copyFile('viewer/dist/djvu_viewer.js');
    copyFile('library/dist/djvu.js');

    console.info('Dist files are copied to the ./build/ and ./extension/ directories');
}

async function prepareManifest(v = 2) {
    fs.copyFileSync(`./extension/manifest_v${v}.json`, `./extension/manifest.json`);
    console.info(`Copied manifest_v${v} to manifest.json`);
}

async function main() {
    const command = process.argv[2];

    switch (command) {
        case 'copy':
            return await copy();
        case 'v2':
            return prepareManifest(2);
        case 'v3':
            return prepareManifest(3);
        default:
            throw new Error('Unsupported command: ' + command);
    }

}

void main();

================================================
FILE: GNU_GPL_v2
================================================
                    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.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    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.

  <signature of Ty Coon>, 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.


================================================
FILE: LICENSE.md
================================================
The DjVu.js Library (everything that is in `library/src` directory) is subject
to, and may be distributed under,
the [GNU General Public License, Version 2](GNU_GPL_v2).

Everything else in this repository (including the DjVu.js Viewer and the browser
extension) is distributed under the terms of [The Unlicense](THE_UNLICENSE).

The choice of GNU GPL v2 for the library is conditioned by the fact DjVu.js
contains some code fragments copied from DjVuLibre or Java DjVu libraries, which
are distributed under this very license.

The DjVu.js Library isn't a port of DjVuLibre or Java DjVu, but the published
specification of the DjVu format refers to DjVuLibre as the de facto standard of
the DjVu format and advises to study its code. Also, some parts of DjVu codecs
aren't described in details and can be gotten only from the source code of
DjVuLibre.

Here I put all the copyright notices, which I found in the source files of both
DjVuLibre and Java DjVu.

```
DjVuLibre-3.5
Copyright (c) 2002  Leon Bottou and Yann Le Cun.
Copyright (c) 2001  AT&T

DjVu (r) Reference Library (v. 3.5)
Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.

Java DjVu (r) (v. 0.8)
Copyright (c) 2004-2005 LizardTech, Inc.  All Rights Reserved.
```

================================================
FILE: README.md
================================================
# DjVu.js

## About / О проекте

**DjVu.js** is a program library for working with `.djvu` online. It's written
in JavaScript and can be run in a web browser without any connection with a
server. DjVu.js can be used for splitting (and concatenation) of `.djvu` files,
rendering pages of a `.djvu` document, converting (and compressing) images
into `.djvu` documents and for analyzing of metadata of `.djvu` documents.

**DjVu.js Viewer** is an app which uses DjVu.js to render DjVu
documents. The app may be easily included into any html page. You can look at it
and try it out on the official website (the link is below).

**DjVu.js Viewer browser extension**. By and large, it's a copy of the viewer,
but also it allows opening links to `.djvu` files right in the browser without
downloading them explicitly. The links to the extension are below.

<hr>

**DjVu.js** - это программная библиотека написанная на JavaScript и
предназначенная для работы с файлами формата `.djvu` онлайн. DjVu.js
ориентирована на исполнение в браузере пользователя без связи с сервером.
Библиотека может быть использована для разделения (объединения) файлов `.djvu`,
преобразования картинок в документы `.djvu`, отрисовки страниц
документов `.djvu`, а также для анализа мета данных и структуры `.djvu`
документов.

**DjVu.js Viewer** - приложение, которое можно легко встроить в любую
html-страницу. Данное приложение служит для просмотра документов DjVu
непосредственно в браузере. Вы можете ознакомиться с ним по ссылке ниже.

**Расширение для браузера DjVu.js Viewer**. По большей части это копия
приложения DjVu.js Viewer, однако также расширение позволяет открывать ссылки
на `.djvu` файлы прямо в браузере, не скачивая их явно. Ссылки на расширение
доступны ниже.

## Translation (localization)

If you want to add a new translation to the viewer [read here](TRANSLATION.md)
how to do it.

## Tools and supported browsers

You need to have Node.js 18+ (although older versions should work too)
and npm 9+ installed to work with the project.

The viewer and the library are supposed to run in a browser. Technically,
it should not be difficult to update the library so that it could be used
in Node.js projects - the main code is pure JS and doesn't rely on
browser specific APIs.

Currently, the following browsers are supported:

```
Chrome >= 88
Firefox >= 78
Safari >= 14
Edge >= 88
```

The list above is conditioned by the [default Vite settings](https://vitejs.dev/guide/build.html#browser-compatibility)
and the support of the [`:where` CSS pseudo class](https://caniuse.com/mdn-css_selectors_where).

## How to build it

Clone the repo and run:

```sh
npm run make
```` 

in the root folder of the repository. The command will install all dependencies
and create bundles of the library and viewer (the `build` folder should
appear).

There is another variant:

```sh
npm run remake
```

It does the same as `make`, but first it removes all git-ignored files 
(including dependencies).

## How to run it locally

If you want to work with the library you should read [the library's README](./library/README.md).

As for the viewer, you have to build the library once and start the dev server.
It can be achieved with the following commands:

```sh
npm run make # run it only once
cd viewer
npm start
```

A page with the viewer will open automatically.

### Tests

Once the dev server has been started, you can run E2E tests via `test*` npm scripts that you can find in
the `viewer/package.json` file.

## How to pack the extension

After the project has been built (`npm run make`), the extension
folder contains all the necessary files. However, there are two manifests:
v2 and v3. You should copy and rename one of them to `manifest.json`.

After it's done, the folder is an unpacked extension - it can be
installed in the "developer mode".

If you want to pack the extension, you can either zip it yourself
or run:

```sh
npm run ext # it uses "npx web-ext", so you will be asked to install the package
```

It will pack the extension with both v2 and v3 manifests.

## Links

- The **official website** with the DjVu.js Viewer demo is https://djvu.js.org
- You may **download the library** and the viewer
  on https://djvu.js.org/downloads
- The **browser extension**
  for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/djvu-js-viewer/)
- The **browser extension**
  for [Google Chrome](https://chrome.google.com/webstore/detail/djvujs-viewer/bpnedgjmphmmdgecmklcopblfcbhpefm)
- The **technical documentation** of the library is
  available [in the wiki](https://github.com/RussCoder/djvujs/wiki/DjVu.js-Documentation)
- [CHANGELOG of the library](library/CHANGELOG.md)
- [CHANGELOG of the viewer](viewer/CHANGELOG.md)

## License / Лицензия

The DjVu.js Library is distributed under the terms of [GNU GPL v2](GNU_GPL_v2).
Everything else in this repository (including the DjVu.js Viewer and the browser
extension) is under [The Unlicense](THE_UNLICENSE). Read more in
the [LICENSE file](LICENSE.md).

<hr>

Библиотека DjVu.js распространяется под лицензией [GNU GPL v2](GNU_GPL_v2). Все
остальное в этом репозитории (включая DjVu.js Viewer и расширение для браузера)
является общественным достоянием ([The Unlicense](THE_UNLICENSE)). Читайте
подробнее в [файле лицензии](LICENSE.md).

================================================
FILE: THE_UNLICENSE
================================================
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org>


================================================
FILE: TRANSLATION.md
================================================
# How to add a new translation to the viewer or improve an existing one

If you want to add one more translation to the viewer, 
you need to fulfill the following steps:

1. Copy [the Russian dictionary file](viewer/src/locales/Russian.js) and rename
   it according to the name of your language in English. Put your file into the
   same directory, where the Russian file is.

2. Then change all the Russian translations of English phrases with your own.
   You can look at
   the  [the English dictionary file](viewer/src/locales/English.js), which
   essentially does not translate anything. But it may serve you as an
   additional example.

3. Pay attention to **the topmost comments** in the Russian dictionary file.
   Especially, read about placeholders which start with #, e.g. #helpButton.
   Other comments throughout the file will help you to find where a phrase is
   used in the app. Some phrases you will not see if you start the viewer
   locally (not as in the extension). While others you can see only if you
   remove some phrases from a dictionary
   (namely notifications that the translation isn't complete), and start the
   viewer locally (it's written in [README](README.md) how to do it). But you do
   not need to find all phrases, you can translate some of them blindly.

4. If you want to **improve an existing translation**, the notification window
   should have told you what phrases are missing. So find where those phrases
   are placed in the Russian dictionary and add them with translations to the
   dictionary you want to improve. However, most probably untranslated phrases
   have been already added to the file, but with `null` values as placeholders.
   In this case, replace all `null` values with corresponding translations.
   Also, if there are missing phrases, but they are not present in the file, you
   can add them via the command `npm run syncLocales`. It should be run
   inside `viewer` directory.

You do not need to connect the dictionary to the code, I will do it myself.
However, if you want you can find where it's connected in the code and add it
there.
**But in general you need only to create a dictionary and nothing more.**

It's better to create **a pull request on GitHub**, but if you do not know how
to do it, and do not want to learn how to do it, you can just send the
dictionary at djvujs@yandex.ru and I will add it to the project myself.


================================================
FILE: extension/background.js
================================================
/**
 * The execution starts in the main() function
 */

'use strict';

function isManifestV3() {
    return chrome.runtime.getManifest().manifest_version === 3;
}

const extensionUrl = chrome.runtime.getURL('viewer.html');
const httpRedirectRuleId = 1;
const fileRedirectRuleId = 2;

function updateContextMenu() {
    chrome.contextMenus.removeAll();
    chrome.contextMenus.create({
        id: 'open_with',
        title: 'Open with DjVu.js Viewer',
        contexts: ['link'],
        targetUrlPatterns: [
            '*://*/*.djvu',
            '*://*/*.djv',
            '*://*/*.djvu?*',
            '*://*/*.djv?*',

            '*://*/*.DJVU',
            '*://*/*.DJV',
            '*://*/*.DJVU?*',
            '*://*/*.DJV?*',
        ]
    });
    chrome.contextMenus.onClicked.addListener(info => {
        if (info.menuItemId === 'open_with') {
            openViewerTab(info.linkUrl);
        }
    });
}

function promisify(func) {
    return function (...args) {
        return new Promise(resolve => {
            func(...args, resolve);
        });
    };
}

const getViewerUrl = (djvuUrl = null, djvuName = null) => {
    const params = new URLSearchParams();
    djvuUrl && params.set('url', djvuUrl);
    djvuName && params.set('name', djvuName);
    const queryString = params.toString();
    return extensionUrl + (queryString ? '?' + queryString : '');
};

const executeScript = (src, sender) => {
    if (isManifestV3()) {
        return chrome.scripting.executeScript({
            files: [src],
            target: {
                tabId: sender.tab.id,
                frameIds: [sender.frameId],
            }
        });
    }

    return promisify(chrome.tabs.executeScript)(sender.tab.id, {
        frameId: sender.frameId,
        file: src,
        runAt: 'document_end'
    });
}

function listenForMessages() {
    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
        if (sender.tab && message === 'include_scripts') {
            Promise.all([
                executeScript('dist/djvu.js', sender),
                executeScript('dist/djvu_viewer.js', sender),
            ]).then(() => {
                sendResponse();
            })
            return true; // do not send response immediately
        }

        if (message.command === 'open_viewer_tab') {
            openViewerTab(message.url);
        }

        sendResponse();
    });
}

function openViewerTab(djvuUrl = null) {
    chrome.tabs.create({ url: getViewerUrl(djvuUrl) });
}

function enableFileOpeningInterception() {
    if (isManifestV3()) {
        chrome.declarativeNetRequest.updateDynamicRules({
            removeRuleIds: [fileRedirectRuleId],
            addRules: [{
                id: fileRedirectRuleId,
                action: {
                    type: 'redirect',
                    redirect: { regexSubstitution: `${extensionUrl}?url=\\0` },
                },
                condition: {
                    isUrlFilterCaseSensitive: false,
                    regexFilter: '^file:///.+\\.djvu?$',
                    resourceTypes: ['main_frame'],
                },
            }],
        });
    } else {
        chrome.webRequest.onBeforeRequest.addListener(details => {
                return { redirectUrl: getViewerUrl(details.url) };
            }, {
                urls: [
                    'file:///*/*.djvu',
                    'file:///*/*.djvu?*',
                    'file:///*/*.djv',
                    'file:///*/*.djv?*',

                    'file:///*/*.DJVU',
                    'file:///*/*.DJVU?*',
                    'file:///*/*.DJV',
                    'file:///*/*.DJV?*',
                ],
                types: ['main_frame']
            },
            ['blocking']
        );
    }
}

// it shouldn't be the same function as the file opening interceptor,
// since this event listener can be removed independently of the file opening interceptor
const requestInterceptor = details => {
    // http://*/*.djvu also corresponds to "http://localhost/page.php?file=doc.djvu"
    // so we have to add this additional check, because it's not a link to a file.
    if (/\.djvu?$/i.test(new URL(details.url).pathname)) {
        return { redirectUrl: getViewerUrl(details.url) };
    }
}

// it's "undefined" for manifest v3, because the "webRequest" permission isn't requested
const onBeforeRequest = chrome.webRequest?.onBeforeRequest;
const onHeadersReceived = chrome.webRequest?.onHeadersReceived;

// Detect djvu only by URL
const enableHttpIntercepting = () => {
    if (isManifestV3()) {
        chrome.declarativeNetRequest.updateDynamicRules({
            removeRuleIds: [httpRedirectRuleId],
            addRules: [{
                id: httpRedirectRuleId,
                action: {
                    type: 'redirect',
                    redirect: { regexSubstitution: `${extensionUrl}?url=\\0` },
                },
                condition: {
                    isUrlFilterCaseSensitive: false,
                    regexFilter: '^https?://[^?]+\\.djvu?(\\?.*)?',
                    resourceTypes: ['main_frame', 'sub_frame'],
                },
            }],
        });
    } else {
        !onBeforeRequest.hasListener(requestInterceptor) && onBeforeRequest.addListener(requestInterceptor, {
                urls: [
                    'http://*/*.djvu',
                    'http://*/*.djvu?*',
                    'https://*/*.djvu',
                    'https://*/*.djvu?*',
                    'http://*/*.djv',
                    'http://*/*.djv?*',
                    'https://*/*.djv',
                    'https://*/*.djv?*',

                    'http://*/*.DJVU',
                    'http://*/*.DJVU?*',
                    'https://*/*.DJVU',
                    'https://*/*.DJVU?*',
                    'http://*/*.DJV',
                    'http://*/*.DJV?*',
                    'https://*/*.DJV',
                    'https://*/*.DJV?*',
                ],
                types: ['main_frame', 'sub_frame'],
            },
            ['blocking']
        );
    }
};

const disableHttpIntercepting = () => {
    if (isManifestV3()) {
        chrome.declarativeNetRequest.updateDynamicRules({ removeRuleIds: [httpRedirectRuleId] });
    } else {
        onBeforeRequest.hasListener(requestInterceptor) && onBeforeRequest.removeListener(requestInterceptor);
    }
};

const headersAnalyzer = details => {
    const getFileName = () => {
        const contentDisposition = details.responseHeaders.find(item => item.name.toLowerCase() === 'content-disposition');
        if (contentDisposition) {
            // In fact, there may be also filename*= in the header, so perhaps, it will be needed for someone in the future
            const matches = /(?:attachment|inline);\s+filename="(.+\.djvu?)"/.exec(contentDisposition.value);
            return matches && matches[1];
        }
    };

    const contentType = details.responseHeaders.find(item => item.name.toLowerCase() === 'content-type');
    if (contentType) {
        if (contentType.value === 'image/vnd.djvu' || contentType.value === 'image/x.djvu') {
            // analyse Content-Disposition only if there is no filename in the URL
            return { redirectUrl: getViewerUrl(details.url, /\.djvu?(?:\?.*)?$/.test(details.url) ? null : getFileName()) };
        } else if (contentType.value === 'application/octet-stream') {
            const fileName = getFileName();
            if (fileName) {
                return { redirectUrl: getViewerUrl(details.url, fileName) };
            }
        }
    }
};

const enableHeadersAnalysis = () => {
    !onHeadersReceived.hasListener(headersAnalyzer) && onHeadersReceived.addListener(headersAnalyzer, {
        urls: [
            'http://*/*',
            'https://*/*',
        ],
        types: ['main_frame', 'sub_frame'],
    }, ['blocking', 'responseHeaders']);
};

const disableHeadersAnalysis = () => {
    onHeadersReceived.hasListener(headersAnalyzer) && onHeadersReceived.removeListener(headersAnalyzer)
};

const defaultOptions = Object.freeze({
    // here we duplicated only the options, which are used by the extension code
    interceptHttpRequests: true,
    analyzeHeaders: false,
});

const onOptionsChanged = json => {
    let parsedOptions = {};
    try {
        parsedOptions = json ? JSON.parse(json) : {};
    } catch (e) {
        console.error('DjVu.js Extension: cannot parse options json from the storage. The json: \n', json);
        console.error(e);
    }

    try {
        const options = { ...defaultOptions, ...parsedOptions };
        if (options.interceptHttpRequests) {
            enableHttpIntercepting();
        } else {
            disableHttpIntercepting();
        }

        if (isManifestV3()) return;

        if (options.interceptHttpRequests && options.analyzeHeaders) {
            enableHeadersAnalysis();
        } else {
            disableHeadersAnalysis();
        }
    } catch (e) {
        console.error('DjVu.js Extension: some options might not have been applied due to an error.');
        console.error(e);
    }
};

function applySavedOptions() {
    chrome.storage.local.get('djvu_js_options', options => onOptionsChanged(options['djvu_js_options']));
}

function listenForOptionChanges() {
    chrome.storage.onChanged.addListener((changes, area) => {
        if (area === 'local' && changes['djvu_js_options']) {
            if (changes['djvu_js_options'].newValue) {
                onOptionsChanged(changes['djvu_js_options'].newValue);
            }
        }
    });
}

function main() {
    // For manifest v3 onInstalled and onStartup events could be used to update the context menu
    // and to register the file opening interception rules, but it seems to work well
    // this way - it's updated every time the service worker is started.
    updateContextMenu();
    enableFileOpeningInterception();
    chrome[isManifestV3() ? 'action' : 'browserAction'].onClicked.addListener(() => openViewerTab());
    listenForMessages();
    listenForOptionChanges();
    applySavedOptions();
}

main();


================================================
FILE: extension/content.js
================================================
(function () {
    'use strict';

    var includeScriptsPromise = null;
    function includeScripts() {
        return includeScriptsPromise || (includeScriptsPromise = new Promise(resolve => {
            chrome.runtime.sendMessage("include_scripts", resolve);
        }));
    }

    function processTag(tag, src) {
        function isJustNumber(value) {
            return Number(value).toString() === String(value).trim();
        }

        var div = document.createElement('div');
        div.style.minWidth = '600px'; // to fit the toolbar
        div.style.minHeight = '200px';

        if (tag.height) { // deliberately use attribute, not styles
            div.style.height = isJustNumber(tag.height) ? Number(tag.height) + "px" : tag.height;
        } else {
            div.style.height = '90vh';
            div.style.maxHeight = '90%';
        }
        if (tag.width) {
            div.style.width = isJustNumber(tag.width) ? Number(tag.width) + "px" : tag.width;
        }

        div.style.overflow = "hidden";
        div.className = "djvu_js_viewer_container";
        tag.parentNode.replaceChild(div, tag);

        var viewer = new DjVu.Viewer();
        viewer.loadDocumentByUrl(src);
        viewer.render(div);
    }

    const objects = document.querySelectorAll(
        'object[classid="clsid:0e8d0700-75df-11d3-8b4a-0008c7450c4a"]'
        + ', object[type="image/x.djvu"]'
    );
    if (objects.length) {
        includeScripts().then(() => {
            objects.forEach(object => {
                var srcParam = object.querySelector('param[name="src"]');
                if (srcParam && srcParam.value) {
                    processTag(object, srcParam.value);
                }
            });
            processEmbeds();
        })
    } else {
        processEmbeds();
    }

    function processEmbeds() { // should be processed after objects, since embeds may be nested in objects as a fallback
        const embeds = document.querySelectorAll('embed[type="image/x-djvu"], embed[type="image/vnd.djvu"]');
        if (embeds.length) {
            includeScripts().then(() => {
                embeds.forEach(embed => {
                    processTag(embed, embed.src);
                });
            });
        }
    }
})();


================================================
FILE: extension/initializer.js
================================================
'use strict';

window.onload = () => {
    const viewer = new DjVu.Viewer({
        uiOptions: {
            hideFullPageSwitch: true,
        }
    });
    viewer.render(document.getElementById('root'));

    viewer.on(DjVu.Viewer.Events.DOCUMENT_CHANGED, () => {
        document.title = viewer.getDocumentName();
    });

    viewer.on(DjVu.Viewer.Events.DOCUMENT_CLOSED, () => {
        document.title = 'DjVu.js Viewer';
    });

    const params = new URLSearchParams(location.search.slice(1));
    if (params.get('url')) {
        viewer.loadDocumentByUrl(params.get('url'), params.get('name') ? { name: params.get('name') } : undefined);
    }
};

================================================
FILE: extension/manifest_v2.json
================================================
{
    "manifest_version": 2,
    "name": "DjVu.js Viewer",
    "short_name": "DV",
    "version": "0.10.1.0",
    "author": "RussCoder",
    "homepage_url": "https://github.com/RussCoder/djvujs",
    "description": "Opens links to .djvu files. Allows opening files from a local disk. Processes <object> & <embed> tags.",
    "background": {
        "scripts": [
            "background.js"
        ]
    },
    "content_security_policy": "script-src 'self'; object-src 'self';",
    "content_scripts": [
        {
            "matches": [
                "*://*/*"
            ],
            "js": [
                "content.js"
            ],
            "all_frames": true,
            "run_at": "document_end"
        }
    ],
    "permissions": [
        "storage",
        "webRequest",
        "webRequestBlocking",
        "<all_urls>",
        "contextMenus"
    ],
    "web_accessible_resources": [
        "viewer.html"
    ],
    "icons": {
        "16": "djvu16.png",
        "32": "djvu32.png",
        "48": "djvu48.png",
        "64": "djvu64.png",
        "96": "djvu96.png"
    },
    "browser_action": {
        "default_icon": {
            "16": "djvu16.png",
            "32": "djvu32.png",
            "48": "djvu48.png",
            "64": "djvu64.png",
            "96": "djvu96.png"
        }
    }
}

================================================
FILE: extension/manifest_v3.json
================================================
{
    "manifest_version": 3,
    "name": "DjVu.js Viewer",
    "short_name": "DV",
    "version": "0.10.1.0",
    "author": "RussCoder",
    "homepage_url": "https://github.com/RussCoder/djvujs",
    "description": "Opens links to .djvu files. Allows opening files from a local disk. Processes <object> & <embed> tags.",
    "background": {
        "service_worker": "background.js"
    },
    "content_security_policy": {
        "extension_pages": "script-src 'self'; object-src 'self';"
    },
    "content_scripts": [
        {
            "matches": [
                "*://*/*"
            ],
            "js": [
                "content.js"
            ],
            "all_frames": true,
            "run_at": "document_end"
        }
    ],
    "permissions": [
        "storage",
        "declarativeNetRequest",
        "scripting",
        "contextMenus"
    ],
    "host_permissions": [
        "<all_urls>"
    ],
    "web_accessible_resources": [
        {
            "resources": ["viewer.html"],
            "matches": ["<all_urls>"]
        }
    ],
    "icons": {
        "16": "djvu16.png",
        "32": "djvu32.png",
        "48": "djvu48.png",
        "64": "djvu64.png",
        "96": "djvu96.png"
    },
    "action": {
        "default_icon": {
            "16": "djvu16.png",
            "32": "djvu32.png",
            "48": "djvu48.png",
            "64": "djvu64.png",
            "96": "djvu96.png"
        }
    }
}

================================================
FILE: extension/viewer.html
================================================
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="shortcut icon" href="djvu32.png">
    <title>DjVu.js Viewer</title>
    <input type="hidden" id="djvu_js_extension_main_page">
    <style>
        html, body, #root {
            height: 100% !important;
            margin: 0;
            padding: 0;
            font-family: sans-serif;
        }
    </style>
    <script id="djvu_js_lib" src="dist/djvu.js"></script>
    <script src="dist/djvu_viewer.js"></script>
    <script src="initializer.js"></script>
</head>

<body>
    <noscript>
        You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
</body>

</html>

================================================
FILE: library/.gitignore
================================================
.idea/
node_modules/
/samples/
.vscode/
access.log
error.log
favicon.ico
jsconfig.json

================================================
FILE: library/API.md
================================================
# DjVu.js Library API

> The library is supposed to work in the browser. Theoretically, it should work
> in Node.js too (with some limitations), but I have never did it, and the
> current bundle is just an IIFE. 

The whole API is available in two forms - synchronous, when all operations are
run in the main thread, and the asynchronous, when all operations are run in the
Web Worker. The last one is preferred in case of browsers, because it may take
up to several seconds to render a page, and no one wants to freeze the UI for
such a long time.

However, the async API is mostly a wrapper around the sync one, so all methods are
described in their sync version.

The library adds one object to the global scope - `DjVu`. 

## Synchronous API 

The sync API is represented via the `DjVu.Document` constructor:

```js
DjVu.Document(arrayBuffer, { baseUrl = null, memoryLimit = MEMORY_LIMIT } = {})
```

Arguments:
- `arrayBuffer` is an `ArrayBuffer` object representing the file. 
- `baseUrl` is the URL to the folder where the indirect djvu is stored. It's
  required only in case of indirect djvu documents (the documents where each
  page is a separate file) to construct an absolute URL to the pages (cause
  inside the document all references are relative).
- `memoryLimit` - shouldn't be provided at all in most cases. The default value
  is 50 MB. This value is the upper border of the memory used to store pages of
  an indirect djvu. If the total size of downloaded pages exceeds this limit,
  the library removes some of them before downloading new pages. 

And example for a bundled (one file) djvu document:

```js
const bundledDjVuArrayBuffer = await fetch('/bundled.djvu').then(r => r.arrayBuffer());
const doc = new DjVu.Document(bundledDjVuArrayBuffer);
```

And example for an indirect (multi-file) djvu document:

```js
const indexFileBuffer = await fetch('/some_indirect_djvu/index.djvu').then(r => r.arrayBuffer());
const doc = new DjVu.Document(indexFileBuffer, { baseUrl: '/some_indirect_djvu' });
```

The constructor creates a `DjVuDocument` instance which has the following methods:

- `getPagesSizes(): Array<{width: number, height: number, dpi: number}>` -
  returns an array of pages sizes. Needed for the continuous scroll view mode in
  the viewer to determine the total height of the view area and of each page.
- `isBundled(): boolean` - returns `true` if the document is bundled (one-file).
  `false` if it's indirect (multi-file).
- `getPagesQuantity(): number` - returns the total number of pages in the
  document.
- `getContents(): Array<Bookmark>`, where `Bookmark` is   
  `{description: string, url: string, children?: Array<Bookmark>}` - returns the
  table of contents, if it exists in the document.
- `getMemoryUsage(): number` - returns the amount of the memory used to store
   parts of an indirect djvu document.
- `getMemoryLimit(): number` - returns the current memory limit for an indirect
  djvu.
- `setMemoryLimit(limit = MEMORY_LIMIT): void` - sets the memory limit.
- `getPageNumberByUrl(url: string): ?number` - returns the page number
  corresponding to the `url` from a `Bookmark`, that is, from the table of
  contents. If the page cannot be found, `null` is returned. 
- `async getPage(number: number): Promise<DjVuPage>` - this method is async,
  cause it works both in case of a single-file djvu and an indirect one, and in
  the latter case the page and its dependencies have to be downloaded first. It
  accepts the page number starting from 1 (not from 0). What's more, this method
  automatically reset the previously requested page (read about it in the
  methods of `DjVuPage`), which allows you not to care about memory leaks.
- `getPageUnsafe(number: number): DjVuPage` - in case of a bundled djvu, you can
  get a page synchronously. But you will have to `page.reset()` manually after
  you finished working with the page. Otherwise, you risk overusing memory.
  Prefer `getPage()` to this method.
- `createObjectURL(): string` - creates a url to download the file (it should be
  revoked afterwards).
- `slice(from = 1, to = this.getPagesQuantity()): DjVuDocument` - creates a
  document from a subset of pages, including the first and the last page. Pages
  are counted from 1. This method isn't production-ready. It may work
  incorrectly in some cases, and it doesn't split the table of contents, but
  copies it completely to the new document.
- `async bundle(progressCallback: (progress: number) => void): Promise<DjVuDocument>`
  \- downloads and bundles an indirect djvu into one-file document. Accepts a
  callback which is invoked with a number parameter which takes values from 0 to
  1 and provides an ability to track the progress.
- `toString(): string` - returns metadata describing the structure of the
  document. Useful if you are familiar with the DjVu Specification.

The most important method is `async getPage(number)` which returns `DjVuPage`
with the following methods: 

- `getWidth(): number` - width in pixels.
- `getHeight(): number` - height in pixels.
- `getDpi(): number` - returns the dpi value. This value is required to
  determine the "100%" scale factor. E.g. a usual monitor has 96 dots per inch
  (let's say 100). If a document has 300 dpi (more precisely, it was scanned
  with the resolution of 300 dpi), it means that its "real size" is 300 / 100 =
  3 times smaller than its full size in pixels.
- `getRotation(): 0 | 90 | 180 | 270` - the rotation of the page. It's needed
  only to show it properly to the user.
- `getImageData(rotate = true): ImageData` - returns `ImageData` object
  representing the page. By default, it has been already rotated (if it's
  required), and you do not need `getRotation()` at all.
- `async createPngObjectUrl(): Promise<PngObjectData>` - creates a PNG image of
  the page, and forms a URL via `URL.createObjectURL()`. It means that you have
  to `URL.revokeObjectURL(url)` (or `worker.revokeObjectURL(url)` in case of the
  async API) once you need it no longer, otherwise there will be memory leaks.
  The `PngObjectData` has the following structure:

  ```ts
  {
    url: string, // do not forget to revoke it
    byteLength: number, // the size of the PNG image retained by the URL
    width: number,
    height: number,
    dpi: number,
  }
  ```
  This method uses `OffscreenCanvas`, but if it's not available (as in Firefox)
  it uses `png.js` library as a fallback. `png.js` is the only dependency of the
  library, and it takes more than 50% of the eventual bundle.

  The method itself is very useful, because a djvu page can easily take 30 MB of
  memory (and more) as a raw `ImageData` object (4 bytes per a pixel), while the
  same image in the PNG format takes less than 0.5 MB. Also, images are much
  better scaled via CSS than canvases. The continuous scroll mode would be
  impossible without this method, because it would take too much memory to
  render many pages on canvases.

- `getText(): string` - returns the page's text as one string, if it exists on
  the page.
- `getNormalizedTextZones(): ?Array<TextZone>` - returns the array of text zones
  to form a text layer above the page's image. The `TextZone` object is the
  following:

  ```ts
  {
    x: number,
    y: number,
    width: number,
    height: number,
    text: string,
  }
  ```

  Its coordinates are relative to the page's top left corner, that is, all zones
  should be absolutely positioned.
- `toString(): string` - returns metadata describing the structure of the page.
  Useful if you are familiar with the DjVu Specification.
- `reset(): void` - resets the page's inner structures. During the decoding
  phase, which is called lazily when different parts of the page's data are
  requested, a lot of temporary structures are allocated. To release the memory,
  you have to reset the page. Otherwise, it will retain a lot of memory for that
  structures. Page objects are created in the constructor of the document, so
  they are not garbage collected, until the document is removed. If you get
  pages via `await doc.getPage(number)` method, you can do nothing since it
  takes care to reset a page when the next one is requested.


## Asynchronous API

The asynchronous API is represented via the `DjVu.Worker` constructor: 

```js
new DjVu.Worker(urlToTheLibrary = DEFAULT_VALUE);
```

It may accept a URL to the DjVu.js Library, but in a normal case you do not have
to provide it explicitly at all, since the library creates an ObjectURL from its
code automatically. This param can be required only if you run the code in some
environment which prohibits to execute code from ObjectURL or data URIs, e.g. in
case of a browser extension. But in case of a usual web page it's not needed.

So the example is:

```js
const worker = new DjVu.Worker();
```

The `DjVuWorker` instance has the following methods and props: 

- `async createDocument(buffer: ArrayBuffer, options: Object): Promise` -
  invokes the
  `DjVu.Document` constructor in the Web Worker. Accepts the same parameters.
  Note that `buffer` is transferred to the Web Worker, so it will be unavailable
  after you call his method.
- `async run(): Promise` - a special methods to execute a `DjVuWorkerTask`
  object (or several). Read about the `doc` property to understand how to use
  it.
- `get doc: DjVuWorkerTask` - a read only property which is the heart of the
  async API. It mimics the `DjVuDocument` object, but in fact it's
  a `DjVuWorkerTask` (which is a  `Proxy`), and you can call any method on it,
  and it always returns another `DjVuWorkerTask` (until you call the `run()`
  method). It's better to look at the examples first:

  ```js
  const [text, textZones] = await worker.run(
    worker.doc.getPage(pageNumber).getText(),
    worker.doc.getPage(pageNumber).getNormalizedTextZones(),
  );

  const pagesSizes = await worker.doc.getPagesSizes().run();
  ```

  In the first example two tasks are run in one bunch, and the array of results
  is returned (inside a `Promise` of course). In the second example only one
  task is executed via a special method `run()` which is the same as to do:

  ```js
  const pagesSizes = await worker.run(worker.doc.getPagesSizes());
  ```

  Using this API you can call any chain of methods on the `DjVuDocument` inside
  the Web Worker. However, you should remember, that you **cannot get complex
  objects like `DjVuPage`** (but you still **can pass callbacks to the worker**,
  e.g. in case of the `bundle()` method). You can only get the eventual results
  like  `ArrayBuffer`'s, `ImageData`'s, strings, plain objects and numbers.
  Also, despite the fact `DjVuDocument.getPage()` method is async, you can use
  in as a sync one in the methods chain. The same takes place in case of any
  other async methods.

  The fact we cannot access the `DjVuPage` directly via the async API conditions
  the current architecture, due to which we have to `reset()` pages manually in
  case of the sync API - otherwise two tasks in one bunch would require to
  decode the page twice, while now it's decoded lazily and only once.

  In essence, when you call methods on a `DjVuWorkerTask` object it just pushes
  the method's name and its arguments into an array, which is passed to the Web
  Worker when you call the `run()` method. All those methods are applied to the
  `DjVuDocument` instance one by one, and the eventual result is passed back.

- `cancelTask(promise: Promise): void` - cancels the task. Accept the promise
  returned by the `run()` method. 
- `emptyTaskQueue(): void` - cancels all tasks except the current one.
- `dropCurrentTask(): void` - forgets about the current task (it cannot be
  really stopped once it began to execute).
- `cancelAllTasks(): void` - invokes two previous methods.

  It's worth saying that if you initiate a lot of tasks via the `run()` method,
  they are not passed to the Web Worker at once, so they can be just deleted
  from the queue. But when a task has been sent, there is no way to stop it,
  except for the Web Worker termination and recreation, but in this case you
  will lose the `DjVuDocument` created inside. 

  Since the library doesn't work too fast, these "cancel task" methods are
  useful in some cases, e.g. the DjVu.js Viewer renders the current page, the
  previous and the next in case of the single page mode, and 15 pages back and
  forward in case of the continuous scroll mode. If a user looks at the
  current page, the viewer at the same time may be rendering other pages to
  show them quickly when the user turns a page over. But if he just jumps in
  100 pages, there is no use in those pages which were to be rendered, so
  those tasks have to be cancelled and new tasks are to be initiated. The same
  need to cancel a task occurs when the user quickly clicks on the next page
  button.

- `isTaskInQueue(promise: Promise): boolean` - checks whether the task is in the
  queue.
- `isTaskInProcess(promise: Promise): boolean` - checks whether the task has
  been already started.
- `revokeObjectURL(url: string): void` - formerly, if an ObjectURL had been
  created inside a worker it could be revoked only inside this very worker. I
  checked it by myself, but now it seems that this behavior has been fixed and
  usual `URL.revokeObjectURL()` works too. But this method is still available if
  you wish to revoke the URL inside the worker in which it was created.
- `reset(): void` - recreates the worker.

## Additional Notes

Besides `DjVu.Worker` and `DjVu.Document`, there are also:

- `DjVu.VERSION` - a string with the current version.
- `DjVu.ErrorCodes` - an object with all possible error codes created by the
  library. Perhaps, it's worth describing them more profoundly. But for now,
  just print it to the console to see the codes. These error codes I use mostly
  in tests, they are not something too important.

If you want more practical examples of the library usage, you can take a look at
`viewer/src/sagas` files. There are real examples of the async API usage.

If you want to know more about the inner DjVu structure, it's worth reading the
[DjVu Specification](./assets/DjVu3Spec.djvu?raw=true).

If something isn't intelligible enough, feel free to create an issue.

================================================
FILE: library/CHANGELOG.md
================================================
# DjVu.js Library's Changelog

## v.0.5.4 (01.02.2023)

- Fix: error messages are sent from the worker again.

## v.0.5.3 (18.02.2021)

- Error handlers for unhandled promise rejections and errors in the worker.
- Object URL to the whole library code is created only once to avoid memory
  leaks.

## v.0.5.2 (18.02.2021)

- Use DIRM registry to get a page number by its id. This approach works the same
  for bundled and indirect documents.

## v.0.5.1 (09.01.2021)

- More robust memory management for document creation (usage of
  `WebAssembly.Memory` with its `grow()` method instead of manual `ArrayBuffer`
  expansion).
- `'use strict';` in the Web Worker (typo correction).
- Returned to the old behaviour: wait for the completion of a forgotten task,
  before sending the next to the Web Worker.

## v.0.5.0 (06.12.2020)

- Feature: bundle indirect djvu documents.
- Removed old redundant DjVuWorker's methods duplicating "doc" proxy API.
- Now callbacks can be passed to the DjVuWorker.
- Minor improvements.

## v.0.4.5 (18.11.2020)

- Use standard TextDecoder API to handle ill-formed utf-8 arrays.

## v.0.4.4 (28.10.2020)

- Significant reduction of memory consumption in IWDecoder (LazyBlock).
- Automatic reset of temporary IW structures after the decoding phase, if the image is big.

## v.0.4.3 (30.07.2020)

- Fixed a bug due to which an empty DJVI chunk caused an error.

## v.0.4.2 (30.06.2020)

- Fixed an error, which took place when there is no location.origin (when a web page is opened directly in a browser).

## v.0.4.1 (22.04.2020)

- Wrapped some loop's bodies into functions to avoid code deoptimizations in Chrome in some cases.

## v.0.4.0 (18.05.2019)

- png.js was integrated into djvu.js to create png files (and Object URLs to them) of the pages inside a worker. 
It's required for the continuous scroll mode, since a png file is much less than a raw ImageData object.

## v.0.3.5 (03.04.2019)

- Fixed a bug having taken place when there were more than 1 block in bzz encoded data.

## v.0.3.4 (30.03.2019)

- Now XHR is used instead of fetch(), since the latter can't load local files (i.e. file:/// urls).

## v.0.3.3 (02.03.2019)

- Fixed a bug. Now empty edges are removed for all symbols added to the dict.

## v.0.3.2 (11.02.2019)

- Fixed a bug when baseline (y coord) was computed incorrectly.

## v.0.3.1 (15.11.2018)

- New method for getting quantity of pages.
- Correct processing of page urls with leading zeros (like "#002").

## v.0.3.0 (12.10.2018)

- The support of indirect djvu files.
- Bug fixes.

## v.0.2.2 (14.09.2018)

- Rotation flags are processed now. A image of page is rotated by default if required.

## v.0.2.1 (20.08.2018)

- Empty pages are processed correctly.

## v.0.2.0 (16.06.2018)

- DjVuWorker is created from the Data URL which is generated automatically, so there is no need in explicit script URL. 
- Additional method run() for the DjVuWorkerTask (the proxy object which is return by the "doc" property of the worker).
- Utils.loadFile() is deprecated now.
- The whole script is available through the DjVu.DjVuScript() method, which is added as a wrapper in the build process. 

## v.0.1.9 (25.05.2018)

- TXT* chunks are decoded completely - text zones are decoded.
- Normalized text zones for the text layer of page. 

## v.0.1.8 (15.05.2018)

- New universal Proxy-based DjVuWorker API, allowing to automatically use most of methods of DjVuDocument.

## v.0.1.7 (19.04.2018)

- UTF-8 ids of pages and dictionaries are supported.

## v.0.1.6 (15.04.2018)

- JB2 codec performance optimizations (more efficient memory access)

## v.0.1.5 (05.04.2018)

- Old files with INFO chunks less than 10 bytes are supported.
- A specific error for corrupted files. 

## v.0.1.4 (27.03.2018)

- A table of contents can be gotten.
- A page number may be gotten by a url. 

## v.0.1.3 (25.03.2018)

- All worker tasks-promises can be cancelled now.
- A task is posted to the worker only after the previous one is fulfilled.

## v.0.1.2 (24.03.2018)

- Unified style of DjVuErrors, which are errors that are thrown manually, when a file is corrupted, there is no requested page and so on. 
- DjVuErrors are rather simple objects that may be copied between workers. 

## v.0.1.1 (19.03.2018)

- UTF-8 strings are decoded correctly now.

## v.0.1.0 (14.03.2018)

- IW44, BZZ and ZP codecs are fully implemented with some constraints in case of BZZ codec.
- JB2 codec is implemented only for decoding.
- BGjp, FGjp, Smmr are not supported at all.
- ANTa, ANTz, NAVM, FORM:THUM and TH44 are not supported, but there are dummies for them, so they are processed somehow.
- Support of TXTz and TXTa is implemented partly, only pure text is decoded.
- The library can split a djvu file, render pages, generate metadata of djvu files, and create a document from a set of images (using IW44 codec).

================================================
FILE: library/README.md
================================================
# DjVu.js Library

This file contains some information about the inner structure of the project and about how to use the library.
It may be useful for you, if you want to play with code or contribute to the project.

It's implied that you have run `npm install` and all dependencies are installed correctly.

## Documentation

If you are interested only in the library's API [read it here](./API.md).

## How to use it

Besides the API docs, there is a good example script with many comments, which can give you a rather good understanding
how to use the library.
It's located at `library/debug/js/examples.js`.
To run it, you should clone the repository and in the root directory do the following:

```
cd library
npm install
npm start
```

After the debug server is run (usually on 9000 port) access `http://localhost:9000/examples.html` to see the results,
and then read the code
and the comments to understand how it works. You can edit the code (and the page will reload automatically).

Also, you can read source code of `DjVuDocument.js`, `DjVuPage.js` and `DjVuWorker.js` in the `src` directory to know
more about the API.

If you have more questions, feel free to create an issue.

## The structure

There are the following directories:

- `app` - contains an old application, which is poorly maintained now. It can split a djvu file, convert images to a
  document, and show metadata of a document.
- `assets` - contains test .djvu files and images. They are used in the automatic tests.
- `debug` - contains css and js files for debugging, which are not the part of the source code of the library.
- `dist` - a directory where the final bundle file is saved to (the eventual `djvu.js` file).
- `src` - a main directory, containing the source code of the library. Its inner structure is self-descriptive, at least
  I think so.
- `tests` - a directory containing tests, which are run in a browser.

There are the following npm commands that may be run:

- `start` - starts a local static server and runs a rollup watch command, which build the library and rebuild it on each
  change. Also, on each change of the bundle or a file from `js` folder, the server sends a message to a client script
  to reload the page.
- `watch` - just runs a rollup watch method, which builds the library and rebuilds it on each change.
- `build` - just builds the library once.

So if you don't know what to start with, run `npm start` and head to `http://localhost:9000/` - you will see the old
app.  
`http://localhost:9000/sync.html` - is a debug page, which I use most often.

If you decide to create your own debug page I suggest you to add a `/debug/js/reloader.js` script to your page, as it's
done in case of `/tests/tests.html` and other pages, and your page will be reloaded on each change of the library source
code.

## Tests

There are some automatic tests. In order to run them you should run `npm start` and then
open `http://localhost:9000/tests`.

The tests are run automatically when the page loads. If everything is ok, you will see that all messages are green. If
you see a green message with an orange message, it means that a test has passed, but your browser differs from mine. The
thing is that different browsers differently render `.png` files, which are used for tests. So I use Opera, and all
tests pass well in my case. In case of other browsers there may be some problems. So, if you use Mozilla, you should
write `about:config` in the address line and then find the parameter `gfx.color_management.mode` and set it to `0`.
After it, all tests should be green, except for one, which has an orange message as well.

When you change the source code of the library, you may open the tests page (which reloads automatically) and check
whether your changes break the current functionality or not.

## Build process

The library is built with Rollup. I chose it rather than Webpack, since Rollup creates a very simple and light bundle (
eventually it just copies all classes in one file in right order and wrap them with an anonymous function).

All files are es6 modules with corresponding import and export statements. So when you create a new file, it's
automatically added to the bundle (if you import something from it to the other files). 

================================================
FILE: library/app/app.css
================================================

.func_menu_block {
    display: flex;
    justify-content: space-between;
    flex-flow: row wrap;
    color: gray;
    max-width: 50em;
    margin: 1em auto;
    box-shadow: 0 0 1px gray;
    padding: 1em;
    overflow: hidden;
}

.wrapper {
    color: gray;
    max-width: 50em;
    margin: 1em auto;
    box-shadow: 0 0 1px gray;
    padding: 1em;
    overflow: hidden;
}

.djvu_version {
    position: relative;
    color: gray;
    text-shadow: 0 0 1px lightgray;
    top: 0;
    left: 0;
}

.additionalBlock {
    height: 5em;
    text-align: center;
}

.funcelem, .disabledfunc {
    font-size: 1.1em;
    font-weight: bold; 
    flex: 0 0 auto;
    box-sizing: content-box;
    width: 10em;
    height: 2em;
    box-shadow: 0 0 1px gray;
    text-align: center;   
    color: gray;
    margin: 1em;
    padding: 1em;
}

.disabledfunc {
    text-shadow: 0 0 1px gray;
    background: #eee;
}

.funcelem:hover {
    cursor: pointer;
    background: #eee;
}

.inputext {
    display: inline-block;
    margin: 5px;
    width: 150px;
}

#finput {
    margin: 10px;
}

.filehref, #filehref {
    text-decoration: none;
    border: 1px solid orangered;
    padding: 3px;
    margin: 3px;
    border-radius: 5px;
    color: orangered;
    display: none;
}

.filehref:hover, #filehref:hover {
    text-shadow: 0 0 1px orangered;
}

#sliceblock, .funcblock {
    display: none;
}

#warnmess {
    color: orangered;
    font-weight: bold;
}
#procmess {
    color: blue;
}

#metaDataBlock #metadata {
    color: black;
    border: dotted 1px black;
    padding: 5px;
}

.activebut {
    cursor: pointer;
    background: white;
    text-decoration: none;
    text-align: center;
    border: 1px solid #0f0f0f;
    padding: 0.3em;
    margin: 0.1em;
    border-radius: 5px;
    color: #0f0f0f;
    outline: none;

}

.activebut:hover {
    box-shadow: 0 0 1px gray;
}

.activebut:active {
    box-shadow: 0 0 1px gray inset;
}

.activebut:disabled {
    cursor: not-allowed;
    opacity: 0.5;
    box-shadow: none;
}

#backbutton {
    display: none;
}

.djvu_viewer {
    overflow: hidden;
    position: relative;
    box-shadow: 0 0 4px gray;
    margin: 1em auto;
    padding: 1em;
}

.djvu_viewer .controls {
    display: block;
    position: absolute;
    bottom: 0px;
    margin: 1em;
    width: 100%;
    height: 5%;
    text-align: center;
}

.djvu_viewer .controls .scale_label {
    display: inline-block;
    min-width: 3em;
}

.djvu_viewer .scale {
    display: inline-block;
    width: 10em;
}

.image_wrapper {
    overflow: auto;
    height: 95%;
    text-align: center;
}

.djvu_viewer .image {
    margin: 0.5em;
    box-shadow: 0 0 1px lightgray;
}

.djvu_viewer .page_number {
    width: 5em;
}

================================================
FILE: library/app/app.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <link rel="shortcut icon" href="/catfav.png" type="image/x-icon" />
    <title>DjVu.js | Работа с DjVu файлами онлайн</title>
    <script type="text/javascript" src="jquery-3.2.1.min.js"></script>
    <script type="text/javascript" src="../dist/djvu.js"></script>
    <script type="text/javascript" src="app.js" defer></script>
    <link rel="stylesheet" href="app.css">
</head>

<body>
    <div id="djvu_app" class="djvu_app">
        <div class="additionalBlock">
            <input id="backbutton" type="button" class="activebut" value="Назад к выбору функций">
        </div>
        <div class="func_menu_block" id="funcmenublock">
            <div id="slicefunc" class="funcelem" title="Разделить DjVu документ онлайн">
                Разделить DjVu
            </div>
            <div id="picturefunc" class="funcelem" title="Создать DjVu из картинок онлайн">
                Картинки в DjVu
            </div>        
            <div id="metadatafunc" class="funcelem">
                Метаданные DjVu
            </div>
        </div>
        <div id="funcblock" class="wrapper funcblock">
            <input name="finput" type="file" class="activebut" id="finput">
            <br>
            <p id="warnmess"></p>

            <div class="funcblock" id="sliceblock">
                <p class="describing">
                    Выберите djvu документ. Введите номер первой и последней страницы, которые Вы хотите поместить в новый документ.
                </p>
                <p class="info"></p>
                <p class="message"></p>
                <span class="inputext">Номер первой страницы</span>
                <input type="number" id="firstnum"><br>
                <span class="inputext">Номер последней страницы</span>
                <input type="number" id="secondnum"><br>
                <input type="button" class="activebut" value="Разделить файл" id="slicebut" disabled>
            </div>

            <div class="funcblock" id="pictureblock">
                <p class="describing">
                    Выберите одну или несколько картинок для создания djvu документа. Можно настраивать качество изображение (влияет на размер
                    файла).
                </p>
                <p class="info"></p>
                <p>Выбетите качество кодирования. При хорошем качестве изображение в djvu весит примерно вдвое меньше, чем в
                    формате jpeg. Другие варианты, еще более экономичны.</p>
                <form>
                    <input name="imagequality" type="radio" value="100">Хорошее
                    <input name="imagequality" type="radio" value="90" checked>Среднее
                    <input name="imagequality" type="radio" value="80">Плохое
                </form><br><br>
                <input type="checkbox" id="grayscale" value="1">Серое изображение (отбросить цвета при кодировании)
                <br><br>
                <input type="button" class="activebut" value="Создать документ" id="picturebut" disabled>
            </div>

            <div class="funcblock" id="metaDataBlock">
                <p class="describing">
                    Выберите djvu документ. Метаданные представляют структуру djvu файла. Каждая единица (порция) данных или Data Chunk расположены
                    в том порядке, в котором они встречаются в файле. Некоторые порции описаны подробно в соответствии с
                    их назначение и устройством, другие же характеризуются лишь заголовком и длиной, так как библиотека способна
                    читать не все порции данных, или же не представляется возможным вывести информацию в текстовом виде кратко.
                    Перед каждой страницей или словарем выводится id машинного оглавления.
                </p>
                <p class="info"></p>
                <p id="metadata"></p>
            </div>
            <br>
            <p id="procmess"></p>
            <a href="" id="filehref" download="djvujs_file.djvu"> Сохранить файл </a>
        </div>

    </div>

</body>

</html>

================================================
FILE: library/app/app.js
================================================
'use strict';

var djvuWorker = new DjVu.Worker();

function initDjVuApplication() {
    $('#backbutton').click(reset);
    $('.funcelem').on('click', () => {
        $('#backbutton').show(400);
    });
    $('#slicefunc').click(sliceFuncPrepare);
    $('#picturefunc').click(pictureFuncPrepare);
    $('#metadatafunc').click(metaDataFuncPrepare);

    if (DjVu.VERSION) {
        $('#djvu_app').prepend('<div class="djvu_version">djvu.js version: ' + DjVu.VERSION + '</div>');
    }
}

function reset(event) {
    event.preventDefault();
    event.stopPropagation();
    $('.funcblock').hide(400);
    $('#backbutton').hide(400);
    $('#funcmenublock').show(400);
    $('#finput').wrap('<form>').closest('form').get(0).reset();
    $('#finput').unwrap().removeAttr('multiple').off('change');
    $('.info').text('');
    $('#procmess').text('');
    $('#filehref').hide();
    djvuWorker && djvuWorker.reset();
}

function metaDataFuncPrepare() {
    $('#funcmenublock').hide(400);
    $('#funcblock').show(400);
    var mtblock = $('#metaDataBlock').show(400);
    $("#finput").change(metaDataFunc);
    $("#procmess").text("");
    $("#metaDataBlock #metadata").html('');
}

function metaDataFunc() {
    $("#procmess").text("");
    $("#metaDataBlock #metadata").html('');
    if (this.files.length) {
        if (this.files[0].name.substr(-5) !== '.djvu') {
            $('#warnmess').text("Расширение файла не .djvu !!!");
            return;
        }
        $('#warnmess').text("");
        var fr = new FileReader();
        fr.readAsArrayBuffer($("#finput")[0].files[0]);
        $("#procmess").text('Загрузка документа ...');
        fr.onload = () => {
            var buf = fr.result;
            djvuWorker.createDocument(buf)

                .then(() => {
                    $("#procmess").text('Задание выполняется ...');
                    return djvuWorker.doc.toString(true).run();
                })

                .then(str => {
                    $("#procmess").text("Задание выполнено !");
                    $("#metaDataBlock #metadata").html(str);
                })

                .catch(() => {
                    $("#procmess").text("Ошибка при обработке файла !!!");
                });
        }
    }
}

function pictureFuncPrepare() {
    $('#funcmenublock').hide(400);
    $('#funcblock').show(400);
    var picuture = $('#pictureblock').show(400);
    $('#finput').prop('multiple', true).off('change').change(function () {
        $('#filehref').hide();
        $('#picturebut').prop('disabled', false);

    });
    $('#picturebut').click(readImagesAndCreateDocument);
}

function readImagesAndCreateDocument() {
    var delayInit = 0;
    var slices = +$('input[name=imagequality]:checked').val();
    var grayscale = $('#grayscale').prop('checked') ? 1 : 0;

    var files = $('#finput')[0].files;
    djvuWorker.startMultyPageDocument(slices, delayInit, grayscale);
    $('#filehref').hide();
    var i = 0;
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    $("#procmess").text("Задание выполняется ...");

    var func = () => {
        createImageBitmap(files[i])
            .then((image) => {
                canvas.width = image.width;
                canvas.height = image.height;
                ctx.drawImage(image, 0, 0);
                var imageData = ctx.getImageData(0, 0, image.width, image.height);
                return djvuWorker.addPageToDocument(imageData);
            },
                (e) => {
                    $("#procmess").text("Ошибка при загрузке файлов! " + e.message);
                })
            .then(() => {
                if (++i < files.length) {
                    $("#procmess").text("Задание выполняется ... " + Math.round(i / files.length * 100) + ' %');
                    func();
                }
                else {
                    $("#procmess").text("Сборка файла ... ");
                    djvuWorker.endMultyPageDocument()
                        .then((buffer) => {
                            $("#procmess").text("Задание выполненено !!!");
                            $('#filehref').prop('href', URL.createObjectURL(new Blob([buffer]))).show(400);
                        });
                }
            });
    }
    func();
}


function createPicDocument(imageArray) {
    var delayInit = 0;
    var slices = +$('input[name=imagequality]:checked').val();
    var grayscale = $('#grayscale').prop('checked') ? 1 : 0;

    djvuWorker.createDocumentFromPictures(imageArray, slices, delayInit, grayscale)
        .then((buffer) => {
            $("#procmess").text("Задание выполненено !!!");
            $('#filehref').prop('href', URL.createObjectURL(new Blob([buffer]))).show(400);
        },
            () => {
                $("#procmess").text("Ошибка при обработке файла !!!");
            });
    djvuWorker.onprocess = (percent) => {
        $("#procmess").text("Задание выполняется ... " + (percent * 100 >> 0) + '%');
    }
}


function sliceFuncPrepare() {
    $('#funcmenublock').hide(400);
    $('#funcblock').show(400);
    var sliceblock = $('#sliceblock').show(400);
    $("#finput").off('change').change(function () {
        $('#filehref').hide();
        if (this.files.length) {
            if (this.files[0].name.substr(-5) !== '.djvu') {
                $('#warnmess').text("Расширение файла не .djvu !!!");
                return;
            }
            $('#warnmess').text("");

            sliceblock.find('.info').text('');
            var fr = new FileReader();
            fr.readAsArrayBuffer($("#finput")[0].files[0]);
            fr.onload = () => {
                var buf = fr.result;
                djvuWorker.createDocument(buf)
                    .then(() => djvuWorker.doc.getPagesQuantity().run())
                    .then(pageCount => {
                        $("#procmess").text('');
                        sliceblock.find('.info').text('Документ содержит ' + pageCount
                            + ' страниц. Вы можете ввести значение от 1 до ' + pageCount);
                        $('#slicebut').off('off').click(sliceFunc).prop('disabled', false);
                    },
                        () => {
                            $("#procmess").text("Ошибка при обработке файла !!!");
                        });
            }
        }
        else {
            $('#slicebut').prop('disabled', true);
        }
    });
}

function sliceFunc() {
    $("#procmess").text("Задание выполняется ...");
    $('#filehref').hide();
    var from = +$("#firstnum").val();
    var to = +$("#secondnum").val();
    djvuWorker.slice(from, to)
        .then((buffer) => {
            $("#procmess").text("Задание выполненено !!!");
            $('#filehref').prop('href', URL.createObjectURL(new Blob([buffer]))).show(400);
        },
            (e) => { // reject
                console.error(e);
                $("#procmess").text("Ошибка при обработке файла !!!");
            });
}

initDjVuApplication();

================================================
FILE: library/assets/DjVu3Spec_contents.json
================================================
[{"description":"DjVu3SpecFinal.djvu","url":"","children":[{"description":"Introduction","url":"#1","children":[{"description":"p0001.djvu","url":"#1"}]},{"description":"Document organization","url":"#2","children":[{"description":"p0002.djvu","url":"#p0002.djvu"}]},{"description":"Overview","url":"#2","children":[{"description":"p0003.djvu","url":"#p0003.djvu"}]},{"description":"What's new","url":"#3","children":[{"description":"p0004.djvu","url":"#p0004.djvu"}]},{"description":"Acknowledgements","url":"#4","children":[{"description":"p0005.djvu","url":"#p0005.djvu"}]},{"description":"References","url":"#4"},{"description":"Component Pieces (IFF chunks)","url":"#5","children":[{"description":"p0006.djvu","url":"#p0006.djvu"},{"description":"p0007.djvu","url":"#p0007.djvu"}]},{"description":"Low-level chunk structure and definition","url":"#8","children":[{"description":"Header","url":"#8","children":[{"description":"p0008.djvu","url":"#p0008.djvu"}]},{"description":"DjVu file structure","url":"#8","children":[{"description":"IFF wrapper","url":"#8"},{"description":"Chunk summary","url":"#9","children":[{"description":"p0009.djvu","url":"#p0009.djvu"}]}]},{"description":"IFF chunk types","url":"#10","children":[{"description":"Container chunk:  FORM","url":"#10","children":[{"description":"p0010.djvu","url":"#p0010.djvu"},{"description":"p0011.djvu","url":"#p0011.djvu"}]}]},{"description":"Directory chunk:  DIRM","url":"#12","children":[{"description":"p0012.djvu","url":"#p0012.djvu"}]},{"description":"Document Outline chunk:  NAVM","url":"#13","children":[{"description":"p0013.djvu","url":"#p0013.djvu"},{"description":"p0014.djvu","url":"#p0014.djvu"}]},{"description":"Annotation chunk:  ANTa, ANTz","url":"#15","children":[{"description":"p0015.djvu","url":"#p0015.djvu"},{"description":"p0016.djvu","url":"#p0016.djvu"},{"description":"p0017.djvu","url":"#p0017.djvu"},{"description":"p0018.djvu","url":"#p0018.djvu"}]},{"description":"Text chunk:  TXTa, TXTz","url":"#19","children":[{"description":"p0019.djvu","url":"#p0019.djvu"},{"description":"p0020.djvu","url":"#p0020.djvu"},{"description":"p0021.djvu","url":"#p0021.djvu"},{"description":"p0022.djvu","url":"#p0022.djvu"}]},{"description":"Bitonal Mask chunk:  Sjbz","url":"#23","children":[{"description":"p0023.djvu","url":"#p0023.djvu"}]},{"description":"Wavelet chunks:  FG44, BG44, TH44","url":"#23"},{"description":"Foreground Color JB2 chunk:  FGbz","url":"#23"},{"description":"Document Info chunk:  INFO","url":"#24","children":[{"description":"p0024.djvu","url":"#p0024.djvu"}]},{"description":"Included chunk:  INCL","url":"#25","children":[{"description":"p0025.djvu","url":"#p0025.djvu"}]},{"description":"Alternative encoding chunks:  BGjp, BFjp, Smmr","url":"#25"}]},{"description":"DjVu in the raw","url":"#27","children":[{"description":"p0027.djvu","url":"#p0027.djvu"},{"description":"p0028.djvu","url":"#p0028.djvu"},{"description":"p0029.djvu","url":"#p0029.djvu"}]},{"description":"Appendix 1:  IW44 coding","url":"","children":[{"description":"p0030.djvu","url":"#p0030.djvu"},{"description":"p0031.djvu","url":"#p0031.djvu"},{"description":"p0032.djvu","url":"#p0032.djvu"},{"description":"p0033.djvu","url":"#p0033.djvu"},{"description":"p0034.djvu","url":"#p0034.djvu"},{"description":"p0035.djvu","url":"#p0035.djvu"},{"description":"p0036.djvu","url":"#p0036.djvu"},{"description":"p0037.djvu","url":"#p0037.djvu"},{"description":"p0038.djvu","url":"#p0038.djvu"},{"description":"p0039.djvu","url":"#p0039.djvu"},{"description":"p0040.djvu","url":"#p0040.djvu"},{"description":"p0041.djvu","url":"#p0041.djvu"},{"description":"p0042.djvu","url":"#p0042.djvu"},{"description":"p0043.djvu","url":"#p0043.djvu"}]},{"description":"Appendix 2:  JB2 coding","url":"#44","children":[{"description":"p0044.djvu","url":"#p0044.djvu"},{"description":"p0045.djvu","url":"#p0045.djvu"},{"description":"p0046.djvu","url":"#p0046.djvu"},{"description":"p0047.djvu","url":"#p0047.djvu"},{"description":"p0048.djvu","url":"#p0048.djvu"},{"description":"p0049.djvu","url":"#p0049.djvu"},{"description":"p0050.djvu","url":"#p0050.djvu"},{"description":"p0051.djvu","url":"#p0051.djvu"},{"description":"p0052.djvu","url":"#p0052.djvu"},{"description":"p0053.djvu","url":"#p0053.djvu"},{"description":"p0054.djvu","url":"#p0054.djvu"},{"description":"p0055.djvu","url":"#p0055.djvu"},{"description":"p0056.djvu","url":"#p0056.djvu"}]},{"description":"Appendix 3:  Z´coding ","url":"#57","children":[{"description":"p0057.djvu","url":"#p0057.djvu"},{"description":"p0058.djvu","url":"#p0058.djvu"},{"description":"p0059.djvu","url":"#p0059.djvu"},{"description":"p0060.djvu","url":"#p0060.djvu"},{"description":"p0061.djvu","url":"#p0061.djvu"},{"description":"p0062.djvu","url":"#p0062.djvu"},{"description":"p0063.djvu","url":"#p0063.djvu"}]},{"description":"Appendix 4:  BZZ coding","url":"#64","children":[{"description":"p0064.djvu","url":"#p0064.djvu"},{"description":"p0065.djvu","url":"#p0065.djvu"},{"description":"p0066.djvu","url":"#p0066.djvu"},{"description":"p0067.djvu","url":"#p0067.djvu"},{"description":"p0068.djvu","url":"#p0068.djvu"},{"description":"p0069.djvu","url":"#p0069.djvu"},{"description":"p0070.djvu","url":"#p0070.djvu"},{"description":"p0071.djvu","url":"#p0071.djvu"}]}]}]

================================================
FILE: library/debug/async.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>DjVu.js</title>
    <script type="text/javascript" src="js/reloader.js"></script>
    <script type="text/javascript" src="dist/djvu.js"></script>
    <script type="text/javascript" src="js/DjVuGlobals.js" defer></script>
    <script type="text/javascript" src="js/debug.js"></script>
    <script type="text/javascript" src="js/async.js" defer></script>
    <link rel="stylesheet" href="css/style.css">
</head>

<body>
    <!-- <div id="viewer" class="djvu_viewer">
        <div class="image_wrapper">
            <img class="image" />
            <canvas class="image"></canvas>
        </div>
        <div class="controls">
            <input type="button" class="navbut prev" value="&#9668;">
            <input class="page_number" type="number">
            <input type="button" class="navbut next" value="&#9658;">
            <input class="scale" type="range" min="0" max="200" step="1" value="100"><span class="scale_label">100</span>%
        </div>
    </div> -->

    <div class="control_block">
        <input type="button" id="rerun" value="rerun">
        <input type="button" id="redraw" value="redraw">

        <input type="button" id="next" value="next">
        <input type="button" id="prev" value="prev">

        <span id="time_output"></span>
        <span id="render_time_output"></span>
    </div>

    <canvas width="192" height="256" id="canvas"></canvas>
    <img id="img" src="" /><br>
    <a id="dochref" href="#" download="file.djvu">Скачать</a>
    <canvas width="200" height="260" id="canvas2"></canvas>
    <div id="output2"></div>
    <input type="file" multiple onchange="main(this.files)" />
    <div id="output"></div>

</body>

</html>

================================================
FILE: library/debug/css/style.css
================================================
.control_block{
    box-shadow: 0 0 1px gray;
    padding: 0.5em;
    margin: 0.5em;
}

#time_output {
    color: blue;
}

#canvas, #canvas2 {
    box-shadow: 0 0 1px gray;
}
#img {
    box-shadow: 0 0 1px gold;
}
#dochref {
   text-decoration: none;
   border: 1px solid blue;
   padding: 3px;
   margin: 3px;
   border-radius: 5px;
   color: blue;
   display: inline-block;
}

#dochref:hover {
    color: white;
    background: blue;
}

.djvu_viewer {
    overflow: hidden;
    position: relative;
    box-shadow: 0 0 4px gray;
    margin: 1em auto;
    padding: 1em;
}

.djvu_viewer .controls {
    display: block;
    position: absolute;
    bottom: 0px;
    margin: 1em;
    width: 100%;
    height: 5%;
    text-align: center;
}

.djvu_viewer .controls .scale_label {
    display: inline-block;
    min-width: 3em;
}

.image_wrapper {
    overflow: auto;
    height: 95%;
    text-align: center;
}

.djvu_viewer .image {
    margin: 0.5em;
    box-shadow: 0 0 1px lightgray;
}

.djvu_viewer .page_number {
    width: 5em;
}

================================================
FILE: library/debug/examples.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>DjVu.js usage examples</title>
    <link rel="shortcut icon" href="/catfav.png" type="image/x-icon" />
    <script type="text/javascript" src="/js/reloader.js"></script>
    <script type="text/javascript" src="/dist/djvu.js"></script>
    <script type="text/javascript" src="/js/examples.js"></script>
    <style>
        img,
        canvas {
            max-width: 90%;
            height: auto;
            border: 1px solid black;
        }
    </style>
</head>

<body>
    <h1>DjVu.js library usage examples</h1>
    <h2>(open the console and read the source code to know how it works)</h2>
    <h3>Canvas gotten with sync interface</h3>
    <canvas id="sync-canvas"></canvas>
    <h3>Canvas gotten with async interface</h3>
    <canvas id="async-canvas"></canvas>
    <h3>Image gotten with async interface (as an image URL)</h3>
    <img id="async-image" />
</body>

</html>

================================================
FILE: library/debug/index.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <link rel="shortcut icon" href="/catfav.png" type="image/x-icon" />
    <title>DjVu.js | Работа с DjVu файлами онлайн</title>

    <style>
        html, body {
            height: 100%;
            margin: 0;
            padding: 0;
        }
        iframe {
            display: block;
            height: 94%;
            width: 100%;
            border: none;
            box-sizing: border-box;
        }
        .header, .footer {
            height: 3%;
            background-color: gray;
        }


    </style>
</head>

<body style="height: 100%">
    <div class="header"></div>
    <iframe src="app/app.html"></iframe>
    <div class="footer"></div>
</body>

</html>

================================================
FILE: library/debug/js/DjVuGlobals.js
================================================
'use strict';

/**
 * Just a set of debug functions. 
 */

function writeln(str) {
    str = str || "";
    output.innerHTML += str + "<br>";
}

function write(str) {
    output.innerHTML += str;
}
function clear() {
    output.innerHTML = "";
}

// вспомогательный класс для быстрого доступа к разделяемым ресурсам
/**
 * @type {DjVuGlobals}
 */
var Globals = {

    init() {
        var canvas = document.getElementById('canvas');
        var c = canvas.getContext('2d');
        this.defaultDPI = 100; // число точек на дюйм для монитора, в реальности 96.
        this.Timer = new DebugTimer();
        this.canvas = canvas;
        this.canvasCtx = c;
        this.dict = [];
        this.img = document.getElementById('img');
        this.counter = 0;
    },

    clearCanvas() {
        this.canvasCtx.fillStyle = 'white';
        this.canvasCtx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    },

    /**
     * @returns {Promise<ArrayBuffer>}
     */
    loadFile(url) {
        return new Promise(resolve => {
            var xhr = new XMLHttpRequest();
            xhr.open("GET", url);
            xhr.responseType = "arraybuffer";
            xhr.onload = (e) => {
                DjVu.IS_DEBUG && console.log("File loaded: ", e.loaded);
                resolve(xhr.response);
            };
            xhr.send();
        });
    },

    drawImage(image, dpi) {
        var tmp;
        var scale = dpi ? dpi / Globals.defaultDPI : 1;
        var time = performance.now();
        Globals.canvas.width = image.width / scale;
        this.canvas.height = image.height / scale;

        var oc = document.createElement('canvas');
        var octx = oc.getContext('2d');
        oc.width = image.width;
        oc.height = image.height;
        octx.putImageData(image, 0, 0);

        var tmpH, tmpW, tmpH2, tmpW2;
        tmpH = tmpH2 = oc.height;
        tmpW = tmpW2 = oc.width;

        if (scale > 4) {
            tmpH = oc.height / scale * 4;
            tmpW = oc.width / scale * 4;
            //первое сжатие
            octx.drawImage(oc, 0, 0, tmpW, tmpH);
        }
        if (scale > 2) {
            tmpH2 = oc.height / scale * 2;
            tmpW2 = oc.width / scale * 2;
            //второе сжатие
            octx.drawImage(oc, 0, 0, tmpW, tmpH, 0, 0, tmpW2, tmpH2);
        }
        //итоговое сжатие
        //this.canvasCtx.translate(- this.canvas.width / 2, - this.canvas.height / 2);
        // this.canvasCtx.translate(this.canvas.width / 2, this.canvas.height / 2);
        // this.canvasCtx.rotate(180* Math.PI / 180);
        // this.canvasCtx.translate(-this.canvas.width / 2, -this.canvas.height / 2);
        this.canvasCtx.drawImage(oc, 0, 0, tmpW2, tmpH2,
            0, 0, canvas.width, canvas.height);
        DjVu.IS_DEBUG && console.log("Canvas resizing time = ", performance.now() - time);
        //this.canvasCtx.setTransform(1, 0, 0, 1, 0, 0);
    },

    drawImageNS(image, dpi) {
        Globals.Timer.start('drawImageNS');
        var tmp;
        var scale = dpi ? Globals.defaultDPI / dpi : 1;
        var time = performance.now();
        Globals.canvas.width = image.width / scale;
        this.canvas.height = image.height / scale;

        var oc = document.createElement('canvas');
        var octx = oc.getContext('2d');
        oc.width = image.width;
        oc.height = image.height;
        octx.putImageData(image, 0, 0);
        var resImg = downScaleCanvas(oc, scale);
        this.canvas.width = resImg.width;
        this.canvas.height = resImg.height;
        this.canvasCtx.putImageData(resImg, 0, 0);
        Globals.Timer.end('drawImageNS');
    },

    drawImageSmooth(image, dpi) {
        var time = performance.now();
        var tmp;
        var scale = dpi ? dpi / Globals.defaultDPI : 1;

        Globals.canvas.width = image.width;
        this.canvas.height = image.height;

        Globals.canvasCtx.putImageData(image, 0, 0);

        this.img.src = this.canvas.toDataURL();
        DjVu.IS_DEBUG && console.log("DataURL creating time = ", performance.now() - time);
        this.img.width = image.width / scale;
        (tmp = this.canvas.parentNode) ? tmp.removeChild(this.canvas) : 0;
        DjVu.IS_DEBUG && console.log("DataURL creating time = ", performance.now() - time);
    },

    /** рисует символ на хосте с учетом его координат (можно посимвольно рисовать картинку) - отладочная функция*/
    drawBitmapOnImageCanvas(bm, x, y, jb2Image) {
        if (this._drawTime && (Date.now() - this._drawTime) < 100) { // если не под отладчиком, то не рисовать
            return;
        } else {
            this._drawTime = Date.now();
        }
        if (!this.testImageData) {
            console.warn("Debug draw function is enabled!");
            this.testImageData = document.createElement('canvas')
                .getContext('2d')
                .createImageData(jb2Image.width, jb2Image.height);
            this.testImageData.data.fill(255); // все белым непрозрачным
        }
        var pixelArray = this.testImageData.data;
        for (var i = y, k = 0; k < bm.height; k++ , i++) {
            for (var j = x, t = 0; t < bm.width; t++ , j++) {
                if (bm.get(k, t)) {
                    var pixelIndex = ((jb2Image.height - i - 1) * jb2Image.width + j) * 4;
                    pixelArray[pixelIndex] = 0;
                    pixelArray[pixelIndex + 1] = 0;
                    pixelArray[pixelIndex + 2] = 0;
                }
            }
        }
        Globals.drawImage(this.testImageData, 300);
    }
};

function downScaleCanvas(cv, scale) {
    if (!(scale < 1) || !(scale > 0))
        throw ('scale must be a positive number <1 ');
    var sqScale = scale * scale;
    // square scale = area of source pixel within target
    var sw = cv.width;
    // source image width
    var sh = cv.height;
    // source image height
    var tw = Math.floor(sw * scale);
    // target image width
    var th = Math.floor(sh * scale);
    // target image height
    var sx = 0
        , sy = 0
        , sIndex = 0;
    // source x,y, index within source array
    var tx = 0
        , ty = 0
        , yIndex = 0
        , tIndex = 0;
    // target x,y, x,y index within target array
    var tX = 0
        , tY = 0;
    // rounded tx, ty
    var w = 0
        , nw = 0
        , wx = 0
        , nwx = 0
        , wy = 0
        , nwy = 0;
    // weight / next weight x / y
    // weight is weight of current source point within target.
    // next weight is weight of current source point within next target's point.
    var crossX = false;
    // does scaled px cross its current px right border ?
    var crossY = false;
    // does scaled px cross its current px bottom border ?
    var sBuffer = cv.getContext('2d').
        getImageData(0, 0, sw, sh).data;
    // source buffer 8 bit rgba
    var tBuffer = new Float32Array(3 * tw * th);
    // target buffer Float32 rgb
    var sR = 0
        , sG = 0
        , sB = 0;
    // source's current point r,g,b
    /* untested !
    var sA = 0;  //source alpha  */

    for (sy = 0; sy < sh; sy++) {
        ty = sy * scale;
        // y src position within target
        tY = 0 | ty;
        // rounded : target pixel's y
        yIndex = 3 * tY * tw;
        // line index within target array
        crossY = (tY != (0 | ty + scale));
        if (crossY) {
            // if pixel is crossing botton target pixel
            wy = (tY + 1 - ty);
            // weight of point within target pixel
            nwy = (ty + scale - tY - 1);
            // ... within y+1 target pixel
        }
        for (sx = 0; sx < sw; sx++ ,
            sIndex += 4) {
            tx = sx * scale;
            // x src position within target
            tX = 0 | tx;
            // rounded : target pixel's x
            tIndex = yIndex + tX * 3;
            // target pixel index within target array
            crossX = (tX != (0 | tx + scale));
            if (crossX) {
                // if pixel is crossing target pixel's right
                wx = (tX + 1 - tx);
                // weight of point within target pixel
                nwx = (tx + scale - tX - 1);
                // ... within x+1 target pixel
            }
            sR = sBuffer[sIndex];
            // retrieving r,g,b for curr src px.
            sG = sBuffer[sIndex + 1];
            sB = sBuffer[sIndex + 2];

            /* !! untested : handling alpha !!
               sA = sBuffer[sIndex + 3];
               if (!sA) continue;
               if (sA != 0xFF) {
                   sR = (sR * sA) >> 8;  // or use /256 instead ??
                   sG = (sG * sA) >> 8;
                   sB = (sB * sA) >> 8;
               }
            */
            if (!crossX && !crossY) {
                // pixel does not cross
                // just add components weighted by squared scale.
                tBuffer[tIndex] += sR * sqScale;
                tBuffer[tIndex + 1] += sG * sqScale;
                tBuffer[tIndex + 2] += sB * sqScale;
            } else if (crossX && !crossY) {
                // cross on X only
                w = wx * scale;
                // add weighted component for current px
                tBuffer[tIndex] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tX+1) px                
                nw = nwx * scale
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
            } else if (crossY && !crossX) {
                // cross on Y only
                w = wy * scale;
                // add weighted component for current px
                tBuffer[tIndex] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tY+1) px                
                nw = nwy * scale
                tBuffer[tIndex + 3 * tw] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
            } else {
                // crosses both x and y : four target points involved
                // add weighted component for current px
                w = wx * wy;
                tBuffer[tIndex] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // for tX + 1; tY px
                nw = nwx * wy;
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
                // for tX ; tY + 1 px
                nw = wx * nwy;
                tBuffer[tIndex + 3 * tw] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
                // for tX + 1 ; tY +1 px
                nw = nwx * nwy;
                tBuffer[tIndex + 3 * tw + 3] += sR * nw;
                tBuffer[tIndex + 3 * tw + 4] += sG * nw;
                tBuffer[tIndex + 3 * tw + 5] += sB * nw;
            }
        }
        // end for sx 
    }
    // end for sy

    // create result canvas
    var resCV = document.createElement('canvas');
    resCV.width = tw;
    resCV.height = th;
    var resCtx = resCV.getContext('2d');
    var imgRes = resCtx.getImageData(0, 0, tw, th);
    var tByteBuffer = imgRes.data;
    // convert float32 array into a UInt8Clamped Array
    var pxIndex = 0;
    //  
    for (sIndex = 0,
        tIndex = 0; pxIndex < tw * th; sIndex += 3,
        tIndex += 4,
        pxIndex++) {
        tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]);
        tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]);
        tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]);
        tByteBuffer[tIndex + 3] = 255;
    }
    // writing result to canvas.
    resCtx.putImageData(imgRes, 0, 0);
    return imgRes;
}


================================================
FILE: library/debug/js/DjVuViewer.js
================================================
'use strict';

/**
 * Old Viewer with manual DOM manipulation. Saved just for history. 
 * Isn't used anymore. 
 */

class DjVuViewer {
    constructor(selector, worker) {
        this.defaultDPI = 100;
        this.selector = selector;
        this.element = document.querySelector(selector);
        this.fileReader = new FileReader();
        this.tmpCanvas = document.createElement('canvas');
        this.tmpCanvasCtx = this.tmpCanvas.getContext('2d');
        this.prevBut = this.element.querySelector('.controls .navbut.prev');
        this.nextBut = this.element.querySelector('.controls .navbut.next');
        this.pageNumberBox = this.element.querySelector('.controls .page_number');
        this.scaleSlider = this.element.querySelector('.controls .scale');
        this.scaleLabel = this.element.querySelector('.controls .scale_label');
        this.img = this.element.querySelector('.image_wrapper img');
        this.img.style.display = 'none';
        this.canvas = this.element.querySelector('.image_wrapper canvas');
        this.canvasCtx = this.canvas.getContext('2d');
        this.imgWrapper = this.element.querySelector('.image_wrapper');
        this._curPage = 0;
        this.pageNumber = null;
        this.stdWidth
        /** @type {DjVuWorker} */
        this.worker = worker || new DjVuWorker();

        this.isCanvasMode = true;

        this.element.style.width = window.innerWidth * 0.9 + 'px';
        this.element.style.height = window.innerHeight * 0.9 + 'px';

        this.nextBut.onclick = () => this.showNextPage();
        this.prevBut.onclick = () => this.showPrevPage();
        this.pageNumberBox.onblur = (e) => this.renderEnteredPage(e);
        this.pageNumberBox.onkeypress = (e) => this.renderEnteredPageByEnter(e);
        this.scaleSlider.oninput = () => this.changeScale();
    }

    reset() {
        clearTimeout(this.improveImageTimeout);
        if (this.nextBut) {
            this.nextBut.onclick = null;
            this.prevBut.onclick = null;
            this.pageNumberBox.onblur = null;
            this.pageNumberBox.onkeypress = null;
            this.scaleSlider.oninput = null;
            this.worker = null;
            this.img.src = '';
        }
    }

    changeScale() {
        this.scaleLabel.innerText = this.scaleSlider.value;
        this.isCanvasMode ? this.drawImageOnCanvas() : this.rescaleImageOnImg();
    }

    renderEnteredPageByEnter(e) {
        if (e.keyCode === 13) {
            this.pageNumberBox.blur(); // it will call showEnteredPage() as the event handler
        }
    }

    renderEnteredPage(e) {
        var page = +this.pageNumberBox.value;
        this.curPage = page;
    }

    get curPage() {
        return this._curPage + 1;
    }

    set curPage(value) {
        this.setPage(value);
    }

    showNextPage() {
        this.curPage += 1;
    }

    showPrevPage() {
        this.curPage -= 1;
    }

    lockNavButtons() {
        this.nextBut.disabled = true;
        this.prevBut.disabled = true;
    }

    unlockNavButtons() {
        this.nextBut.disabled = false;
        this.prevBut.disabled = false;
    }

    getScaledImageWidth() {
        return this.imageData.width / this.standardScale * (+this.scaleSlider.value / 100);
    }

    renderCurPage() {
        clearTimeout(this.improveImageTimeout);
        return this.worker.getPageImageDataWithDPI(this._curPage).then(obj => {
            this.imageData = obj.imageData;
            this.imageDPI = obj.dpi;
            this.standardScale = this.imageDPI ? this.imageDPI / this.defaultDPI : 1;
            this.drawImageOnCanvas();

            this.improveImageTimeout = setTimeout(() => {
                this.drawImageViaImg();
            }, 1000);
        });
    }

    /**
     * @returns {Promise<ArrayBuffer>}
     */
    loadFile(url) {
        return new Promise(resolve => {
            var xhr = new XMLHttpRequest();
            xhr.open("GET", url);
            xhr.responseType = "arraybuffer";
            xhr.onload = (e) => {
                DjVu.IS_DEBUG && console.log("File loaded: ", e.loaded);
                resolve(xhr.response);
            };
            xhr.send();
        });
    }

    loadDjVu(url) { // debug functions
        return this.loadFile(url)
            .then(buffer => this.worker.createDocument(buffer))
            .then(() => this.worker.getPageNumber())
            .then(number => {
                this.pageNumber = number;
                this.curPage = 1;
            });
    }

    loadDjVuFromBuffer(buffer) {
        return this.worker.createDocument(buffer)
            .then(() => this.worker.getPageNumber())
            .then(number => {
                this.pageNumber = number;
                this.curPage = 1;
            });
    }

    relockNavButtons() {
        this.unlockNavButtons();
        if (this._curPage === 0) {
            this.prevBut.disabled = true;
        }
        if (this._curPage === this.pageNumber - 1) {
            this.nextBut.disabled = true;
        }
    }

    setPage(page) {
        page--;
        if (page < 0) {
            page = 0;
        } else if (page > this.pageNumber - 1) {
            page = this.pageNumber - 1;
        }
        this._curPage = page;
        this.relockNavButtons();
        this.pageNumberBox.value = this.curPage;
        this.lockNavButtons();
        this.renderCurPage().then(() => {
            this.relockNavButtons();
        });
    }

    _switchToImageMode() {
        this.img.style.display = 'block';
        this.canvas.style.display = 'none';
        this.isCanvasMode = false;
    }

    _switchToCanvasMode() {
        this.img.style.display = 'none';
        this.canvas.style.display = 'block';
        this.isCanvasMode = true;
    }

    getImageDataURL() {
        this.tmpCanvas.width = this.imageData.width;
        this.tmpCanvas.height = this.imageData.height;
        this.tmpCanvasCtx.putImageData(this.imageData, 0, 0);
        return this.tmpCanvas.toDataURL();
    }

    getImageDataURLAsync() {
        return new Promise(resolve => {
            this.tmpCanvas.width = this.imageData.width;
            this.tmpCanvas.height = this.imageData.height;
            this.tmpCanvasCtx.putImageData(this.imageData, 0, 0);
            this.tmpCanvas.toBlob(imageBlob => {
                this.fileReader.onload = event => {
                    resolve(event.target.result);
                };
                this.fileReader.readAsDataURL(imageBlob);
            });
        });
    }

    drawImageViaImg() {
        this.img.src = this.getImageDataURL();
        this.rescaleImageOnImg();
        this._switchToImageMode();
    }

    rescaleImageOnImg() {
        this.img.width = this.getScaledImageWidth();
    }

    drawImageOnCanvas() {
        //var time = performance.now();
        var image = this.imageData;
        var scale = this.imageDPI ? this.imageDPI / this.defaultDPI : 1;
        scale /= (+this.scaleSlider.value / 100)
        this.stdWidth = image.width / scale * (+this.scaleSlider.value / 100);
        this.stdHeight = image.height / scale * (+this.scaleSlider.value / 100);

        this.tmpCanvas.width = image.width;
        this.tmpCanvas.height = image.height;
        this.tmpCanvasCtx.putImageData(image, 0, 0);

        var tmpH, tmpW, tmpH2, tmpW2;
        tmpH = tmpH2 = this.tmpCanvas.height;
        tmpW = tmpW2 = this.tmpCanvas.width;

        if (scale > 4) {
            tmpH = this.tmpCanvas.height / scale * 4;
            tmpW = this.tmpCanvas.width / scale * 4;
            //первое сжатие
            this.tmpCanvasCtx.drawImage(this.tmpCanvas, 0, 0, tmpW, tmpH);
        }
        if (scale > 2) {
            tmpH2 = this.tmpCanvas.height / scale * 2;
            tmpW2 = this.tmpCanvas.width / scale * 2;
            //второе сжатие
            this.tmpCanvasCtx.drawImage(this.tmpCanvas, 0, 0, tmpW, tmpH, 0, 0, tmpW2, tmpH2);
        }
        //итоговое сжатие
        this.canvas.width = image.width / scale;
        this.canvas.height = image.height / scale;
        this.canvasCtx.drawImage(this.tmpCanvas, 0, 0, tmpW2, tmpH2,
            0, 0, this.canvas.width, this.canvas.height);
        this._switchToCanvasMode();
        //console.log('Render time', performance.now() - time, scale);
    }
}

================================================
FILE: library/debug/js/async.js
================================================
"use strict";

/**
 * Скрипт для тестирования библиотеки через Web Worker
 */

var fileSize = 0;
var output;
var worker;

var timeOutput = document.querySelector('#time_output');
var renderTimeOutput = document.querySelector('#render_time_output');
var rerunButton = document.querySelector('#rerun');
rerunButton.onclick = rerun;
document.querySelector('#redraw').onclick = redrawPage;

var pageNumber = 1;
var djvuUrl = '/assets/DjVu3Spec_indirect/index.djvu';
var baseUrl = '/assets/DjVu3Spec_indirect/';

document.querySelector('#next').onclick = () => {
    pageNumber++;
    redrawPage();
};

document.querySelector('#prev').onclick = () => {
    pageNumber--;
    redrawPage();
};

window.onload = function () {
    output = document.getElementById("output");
    var canvas = document.getElementById('canvas');
    var c = canvas.getContext('2d');
    Globals.defaultDPI = 100;
    Globals.Timer = new DebugTimer();
    Globals.canvas = canvas;
    Globals.canvasCtx = c;
    Globals.dict = [];
    Globals.img = document.getElementById('img');
    // testFunc();
    //loadPicture();
    renderDjVu();
    //initViewer();
    //Globals.loadFile('samples/csl.djvu').then(buf => showMetaData(buf));
}

function initViewer() {
    /** @type {DjVuViewer} */
    var viewer = new DjVuViewer('.djvu_viewer');
    viewer.loadDjVu('samples/csl.djvu');
}

async function renderDjVu() {
    /** @type {DjVuWorker} */
    worker = new DjVu.Worker();
    const buffer = await fetch(djvuUrl).then(r => r.arrayBuffer());
    await worker.createDocument(buffer, { baseUrl });

    // const bundle = await worker.doc.bundle(progress => {
    //     console.log(progress);
    // }).run();

    await redrawPage();
}

async function redrawPage() {
    console.log('**** Render Page ****');
    var time = performance.now();
    var [imageData, dpi] = await worker.run(
        worker.doc.getPage(pageNumber).getImageData(),
        worker.doc.getPage(pageNumber).getDpi()
    );
    Globals.drawImage(imageData, dpi * 1.5);
    time = performance.now() - time;
    console.log("Redraw time", time);
    console.log('**** ***** **** ****');
    renderTimeOutput.innerText = Math.round(time);
}

function loadPicture() {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "samples/bear.jpg");
    xhr.responseType = "arraybuffer";
    xhr.onload = function (e) {
        console.log(e.loaded);
        fileSize = e.loaded;
        var buf = xhr.response;
        readPicture(buf);
    }
    xhr.send();
}
function readPicture(buffer) {

    createImageBitmap(new Blob([buffer])).then(function (image) {
        var pictureTotalTime = performance.now();
        var canvas = document.getElementById('canvas2');
        var c = canvas.getContext('2d');
        canvas.width = image.width;
        canvas.height = image.height;

        c.drawImage(image, 0, 0);
        var imageData = c.getImageData(0, 0, image.width, image.height);
        var iwiw = new IWImageWriter(90, 0, 0);
        var doc = iwiw.createMultyPageDocument([imageData, imageData, imageData]);
        // var doc = iwiw.createOnePageDocument(imageData);
        console.log('docCreateTime = ', performance.now() - pictureTotalTime);
        var link = document.querySelector('#dochref');
        link.href = doc.createObjectURL();

        c.putImageData(doc.pages[0].getImage(), 0, 0);
        console.log('Counter', Globals.counter);
        //console.log('PZP', Globals.pzp.log.length, ' ', Globals.pzp.offset );
        writeln(doc.toString());
        console.log('pictureTotalTime = ', performance.now() - pictureTotalTime);
    });

}

function showMetaData(buffer) {
    var worker = new DjVuWorker();
    worker.createDocument(buffer)
        .then(() => worker.getDocumentMetaData(true))
        .then(text => writeln(text));
}

function readDjvu(buf) {
    console.log("DJ1");
    var link = document.querySelector('#dochref');
    var time = performance.now();
    console.log("Buffer length = " + buf.byteLength);
    //var doc = new DjVuDocument(buf);
    Globals.counter = 0;
    var worker = new DjVuWorker();

    setTimeout(() => {
        Globals.Timer.start('TotalTime');
        worker.createDocument(buf)
            .then(() => {
                Globals.Timer.end('TotalTime', true);
                return worker.getDocumentMetaData(true);
            })
            .then((str) => {
                //link.href = URL.createObjectURL(new Blob([buffer]))
                writeln(str);
                Globals.Timer.end('TotalTime', true);
            });

    }, 1000);


    console.log(Globals.Timer.toString());
    console.log("Total execution time = ", performance.now() - time);
}

/**
 * Функция для работы с файлами загруженными вручную.
 */
function main(files) {
    clear();
    console.log(files.length);
    //readFile(file);
    var fileReader = new FileReader();
    var doc1, doc2;
    fileReader.onload = function () {
        if (!doc1) {
            doc1 = new DjVuDocument(this.result);
            fileReader.readAsArrayBuffer(files[1]);
            return;
        }

        doc2 = new DjVuDocument(this.result);
        testFunc(doc1, doc2);

    };
    if (files.length > 0) {
        fileReader.readAsArrayBuffer(files[0]);
    }
}

function testFunc(doc1, doc2) {
    var doc = DjVuDocument.concat(doc1, doc2);
    Globals.drawImageSmooth(doc.pages[0].getImage(), 600);
    writeln(doc.toString());
    var link = document.querySelector('#dochref');
    link.href = doc.createObjectURL();

}


================================================
FILE: library/debug/js/debug.js
================================================
'use strict';

/**
 * One more set of debug funcitions that was used in development of the library.
 * Saved mostly for history. 
 */

//класс для измерения времени с точностью до микросекунд
class DebugTimer {
    constructor() {
        this.timers = {};
    }
    start(id) {
        var timer;
        if (this.timers[id]) {
            timer = this.timers[id];
        } else {
            timer = {
                totalTime: 0,
                timeArray: [],
                startTime: 0
            };
            this.timers[id] = timer;
        }
        timer.startTime = performance.now();
    }
    end(id, print) {
        if (!this.timers[id]) {
            console.log("Несуществующий таймер: ", id);
        }
        var timer = this.timers[id];
        var time = performance.now() - timer.startTime
        timer.totalTime += time;
        timer.timeArray.push(time);
        if (print) {
            console.log("Timer '", id, "'", time);
        }
    }
    toString() {
        var str = '**DebugTimer**\n';
        for (var p in this.timers) {
            str += ">>" + p + " " + this.timers[p].totalTime + "\n" + /*JSON.stringify(this.timers[p].timeArray) + '\n'*/ + '<<\n';
        }
        str += "**DebugTimer**\n";
        return str;
    }
}
/*
* Псевдо ZPСoder для того чтобы видеть битовый поток.
*/
class PseudoZP {
    constructor() {
        this.log = [];
        this.offset = 0;
    }
    encode(bit, ctx, n) {
        bit = +bit;
        if (ctx) {
            var tmp = {
                bit: bit,
                ctx: ctx[n],
                off: n,
                len: this.log.length
            };
        } else {
            var tmp = {
                bit: bit,
                ctx: -1,
                off: -1,
                len: this.log.length
            };
        }
        this.log.push(tmp);
    }
    decode(ctx, n) {
        var tmp = this.log[this.offset++];
        if(!tmp) { Globals.counter++; return 1;}
        if (ctx) {
            var cv = ctx[n];
            if (!(tmp.ctx === cv && n === tmp.off && tmp.len === (this.offset - 1))) {
                4;
                throw new Error("Context dismatch");
            }
        } else {
            if (!(tmp.ctx === -1 && tmp.off === -1 && tmp.len === (this.offset - 1))) {
                throw new Error("Context dismatch");
            }
        }
        return tmp.bit;
    }
    eflush() {
        console.log("PseudoZP eflushed");
    }
}
function tmpFunc(doc) {
    var writer = new DjVuWriter(1000000);
    writer.writeStr("AT&T");
    writer.writeStr("FORM");
    //todo переделать
    writer.writeInt32(0);
    writer.writeStr("DJVU");
    var page = doc.pages[3];
    writer.writeChunk(page.info);
    for (var i = 0; i < page.bg44arr.length; i++) {
        writer.writeChunk(page.bg44arr[i]);
    }
    var bs = writer.getByteStream();
    console.log(bs.readStr4());
    var link = document.querySelector('#dochref');
    var nb = writer.getBuffer();
    var blob = new Blob([nb]);
    var url = URL.createObjectURL(blob);
    link.href = url;
    var dd = new DjVuDocument(nb);
    Globals.drawImage(dd.pages[0].getImage())
}
function ZPtest() {
    var bsw = new ByteStreamWriter(100000);
    var zp = new ZPEncoder(bsw);
    var n = 64;
    var ctx = [0];
    var arr = [];
    for (var i = 0; i < n; arr.push(Math.random() * 2 >> 0),
    i++) {}
    for (i = 0; i < n; i++) {
        var byte = arr[i];
        var mask = 128;
        for (var j = 7; j >= 0; j--) {
            var bit = (byte & mask) >> j;
            mask >>= 1;
            zp.encode(bit, ctx, 0);
        }
    }
    zp.eflush();
    console.log(arr);
    ctx = [0];
    var bs = new ByteStream(bsw.getBuffer());
    var zp = new ZPDecoder(bs);
    for (i = 0; i < n; i++) {
        var byte = 0;
        for (var j = 7; j >= 0; j--) {
            var bit = zp.decode(ctx, 0);
            byte = (byte << 1) | bit;
        }
        arr[i] = byte;
    }
    console.log(arr);
    console.log("Full length = ", n, " Coded length = ", bs.length);
}
function BZZtest() {
    var bs = new ByteStreamWriter();
    var zp = new ZPEncoder(bs);
    var pzp = new PseudoZP();
    var bzz = new BZZEncoder(zp);
    var data = Uint8Array.of(11, 3, 2, 10, 2, 10, 2, 0);
    bzz.encode(data.buffer);
    var bsbs = new ByteStream(bs.getBuffer());
    var zp2 = new ZPDecoder(bsbs);
    zp2.pzp = zp.pzp;
    var bzz = new BZZCodec(zp2);
    bzz.decode();
    var bsz = bzz.getByteStream();
    data = new Uint8Array(bsz.buffer);
    console.log(data);
}
/*
function tmpFunc() {
    var zigzagRow = [];
    var zigzagCol = [];
    for (var i = 0; i < 1024; i++) {
        var bits = [];
        for (let j = 0; j < 10; j++) {
            bits.push((i & Math.pow(2, j)) >> j);
        }
        let row = 16 * bits[1] + 8 * bits[3] + 4 * bits[5] + 2 * bits[7] + bits[9];
        let col = 16 * bits[0] + 8 * bits[2] + 4 * bits[4] + 2 * bits[6] + bits[8];
        zigzagRow.push(row);
        zigzagCol.push(col);
    }
    //console.log(JSON.stringify(zigzagRow));
    //console.log(JSON.stringify(zigzagCol));
    let r = "[";
    let c = "[";
    let k = 0;
    for (let i = 0; i < 1024; i++) {
        r += zigzagRow[i] + ',';
        c += zigzagCol[i] + ',';
        k++;
        if (k === 16) {
            k = 0;
            r += '\n';
            c += '\n';
        }
    }
    r += ']';
    c += ']';
    console.log(r);
    console.log(c);

}*/


================================================
FILE: library/debug/js/examples.js
================================================
/**
 * This code serves as an example of the API provided by the DjVu.js library.
 * This very API is used by the DjVu.js Viewer.
 * 
 * Start to read the code form the main() function in the same direction as it is executed.
 */

'use strict';

async function syncInterfaceExamples(djvuDocument) {
    // The method is async just because it can be used for indirect djvu files, and then 
    // different parts of a document are loaded lazily. 
    // In case of bundled djvu (one file djvu) there are no async operations actually.
    const djvuPage = await djvuDocument.getPage(1); // note that pages start with 1 NOT WITH 0

    // we can get page size even before it's decoded
    console.log("Page width", djvuPage.getWidth());
    console.log("Page height", djvuPage.getHeight());

    // get the standard ImageData object representing the page
    const imageData = djvuPage.getImageData(); // it's the longest operation, here the page is decoded and image is created.

    // Let's draw the ImageData on a canvas
    const canvas = document.querySelector('#sync-canvas');
    canvas.width = imageData.width;
    canvas.height = imageData.height;
    const ctx = canvas.getContext('2d');
    ctx.putImageData(imageData, 0, 0); // But it will be much 

    // In fact, our image is very large, so we need to scale it somehow. Let's use its DPI.
    // DPI is needed to render an image in so called 100% scale.
    // A usual monitor has 96 dpi (let's say 100). Thus, if an image save with 300 dpi, then
    // on a usual monitor you should decrease it 300 / 100 = 3 times.
    // The DjVu.js Viewer uses css scaling only for the initial render. Then it rewrites imageData
    // on the canvas several times, decreasing it 2 times on each render (or less on the last render). 
    // Such manual scaling gives much better quality, than css scaling. 
    // In case of continuous scroll mode, it uses <img>'s rather than canvases, and imgs are scaled via css much better.
    // But here we use only css scaling on a canvas.
    const imageDpi = djvuPage.getDpi();
    canvas.style.width = imageData.width / (imageDpi / 100) + 'px';

    // when a document has a contents table you can get it
    const contents = djvuDocument.getContents();
    console.log('DjVu Document contents \n\n', contents);

    // if you want a raw text of the page. This text is used in the DjVu.js Viewer's text mode.
    const text = djvuPage.getText();
    console.log('DjVu Page text \n\n', text);

    // if you want a structured text zones to create a text layer over an image.
    const topTextZone = djvuPage.getPageTextZone();
    console.log('DjVu Page top text zone \n\n', topTextZone);

    // or another variant (more convenient for absolute positioning, look at the structure of output to understand the difference) 
    const textZones = djvuPage.getNormalizedTextZones();
    console.log('DjVu Page text zones \n\n', textZones);

    // get the number of page in a document
    const pageCount = djvuDocument.getPagesQuantity();
    console.log("There are ", pageCount, " pages in the document");

    // we can get sizes of all pages too. E.g. to render empty pages of an appropriate size while they are being loaded.
    const pageSizes = djvuDocument.getPagesSizes();
    console.log("Pages sizes ", pageSizes);
}

async function asyncInterfaceExamples(djvuWorker) {
    // In case of the worker, you cannot get a DjVuPage object explicitly.
    // You can get only eventual results. 
    // The async interface is similar to the sync one. 
    // djvuWorker.doc is a proxy object, called DjVuTask, which remembers what methods you have called.
    // Each method of the task returns another task. When it's run, the consequence of methods is executed on 
    // a DjVuDocument object which is created inside the Web Worker, and the result is transferred to the main thread.

    // You can execute task in batches (it's faster than one by one)
    // You will get an array of results.
    const [width, height] = await djvuWorker.run(
        djvuWorker.doc.getPage(60).getWidth(),
        djvuWorker.doc.getPage(60).getHeight(),
        // here you can add more tasks
    );

    console.log('Page size is', width, height)

    // But there is a convenience method .run() to execute one task
    const imageData = await djvuWorker.doc.getPage(60).getImageData().run();

    // Let's draw the ImageData on a canvas
    const canvas = document.querySelector('#async-canvas');
    canvas.width = imageData.width;
    canvas.height = imageData.height;
    const ctx = canvas.getContext('2d');
    ctx.putImageData(imageData, 0, 0); // But it will be much 

    // You can execute only one task via djvuWorker.run() too.
    // In this case you will get only the result, not an array of results. 
    const imageDpi = await djvuWorker.run(djvuWorker.doc.getPage(60).getDpi()); // actually could be executed in one batch with getImageData()
    canvas.style.width = imageData.width / (imageDpi / 100) + 'px';

    // let's execute more operations 
    const [contents, count, sizes, test, textZones] = await djvuWorker.run(
        djvuWorker.doc.getContents(),
        djvuWorker.doc.getPagesQuantity(),
        djvuWorker.doc.getPagesSizes(),
        djvuWorker.doc.getPage(60).getText(),
        djvuWorker.doc.getPage(60).getNormalizedTextZones(),
    );

    console.log('More data from async interface: ', contents, count, sizes, test, textZones);

    // What's more is that the async interface allow you to create image URLs in a Web Worker
    // asynchronously. If you get an ImageData and then create a URL from it via a canvas element,
    // you still execute a rather costly operation on the main thread. So it's better to do it on the background thread.
    // Namely for this feature, DjVu.js is bundled with Png.js. In Chrome, an image URL can be created via OffscreenCanvas
    // in a web worker, but since Firefox hasn't supported OffscreenCanvas yet, Png.js is used as a polyfill.

    // The Viewer's continuous scroll mode is based on this very feature.
    // This method returns not a simple URL, but an object with some additional data
    // to simplify rendering and memory management.
    const pageData = await djvuWorker.doc.getPage(21).createPngObjectUrl().run();
    console.log('Page image URL with some data', pageData);

    const img = document.querySelector('#async-image');
    img.src = pageData.url;
    img.style.width = pageData.width / (pageData.dpi / 100) + 'px'; // scale it as well as canvases

    // Internally URL.createObjectURL is used. It doesn't create a dataURI, but a short url to a blob inside the worker's memory.
    // It's much faster than to create a dataURI string, but such a URL should be revoked manually after it is used, 
    // otherwise you will get a memory leak. Also, as practice has shown, you can't revoke a URL, created in a web worker,
    // on the main thread, so you should revoke it in a web worker via djvuWorker.revokeObjectURL() method.
    // The byteLength property allow you to count how much memory image blobs occupy in the memory now. Since png format is used,
    // blobs are rather small compared to raw ImageData objects, but still if you don't revoke URLs at all you can get a
    // noticeable memory leak. It should be mentioned, that you will not see this memory leak in a JS profiler - it can be seen
    // only in an OS task manager.
    img.onload = () => {
        djvuWorker.revokeObjectURL(pageData.url);
        console.log(pageData.byteLength, 'bytes were released in the worker memory after the image URL was revoked');
    }

    // In fact this feature with Object URL of pages can be used in the sync interface too.
    // However there is not much sense in it, because it uses OffscreenCanvas or Png.js, while on the main thread you can
    // just use a normal canvas element and control the process by yourself.
}

async function main() {
    // A DjVuDocument is built on top of an ArrayBuffer representing a file. 
    // In case of one-file bundled djvu an ArrayBuffer is all you need to construct a document.
    const arrayBuffer = await fetch('/assets/DjVu3Spec.djvu').then(res => res.arrayBuffer());

    // The sync interface is represented by DjVu.Document.
    // Usually, you should use DjVu.Document only for debug purposes or maybe on server side (don't know will it work there).
    // Synchronous operations on the main thread block UI and it spoils user experience badly.
    // The DjVu.js Viewer uses only async interface (DjVu.Worker).
    // But since the async API is based on the sync one, you should look at the sync interface first.
    console.log('%c\n\n\n Sync Interface results \n\n\n\n', "font-size: 3em; color: blue");
    const djvuDocument = new DjVu.Document(arrayBuffer);

    await syncInterfaceExamples(djvuDocument);

    // The async interface is represented by DjVu.Worker.
    // DjVu.Worker implicitly creates a Web Worker and all operations are executed on a background thread, 
    // not blocking the UI. For this reason, in case of the browser, 
    // it's by all means recommended to always use DjVu.Worker instead of DjVu.Document.
    // We copy the array buffer, since the buffer is transferred to the worker and will not be available on the main thread anymore.
    // Read about Transferable object here https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage
    console.log('%c\n\n\n Async Interface results \n\n\n\n', "font-size: 3em; color: green");
    const arrayBufferCopy = arrayBuffer.slice(0);
    const djvuWorker = new DjVu.Worker(); // do not pass anything to the constructor!
    await djvuWorker.createDocument(arrayBufferCopy);

    await asyncInterfaceExamples(djvuWorker);

    // After all operation are done or you want to load another document to the worker, 
    // you may reset it to free inner structure and recreate a Web Worker. 
    // Right now there is no method to destroy it, but you can do it via djvuWorker.worker.terminate().
    djvuWorker.reset();

    // When you work with both async or sync interfaces you should remember about one thing.
    // When a page is decoded, a lot of memory is allocated for inner structures during a decoding process.
    // So a decoded page object takes about 30 MB of memory 
    // (by the way, an ImageData of 2539 * 3295 pixels takes 2539 * 3295 * 4 / 1024 / 1024 = 31.9 MB too).
    // So if you decode 10 pages you get an overhead of 300 MB. It is pretty much.
    // For this reason, .getPage() method saves the last requested page and reset it (clearing all inner buffers),
    // when another page is requested. Thus, if you access pages only via .getPage() you will not get 300 MB overhead.
    // But such behaviour means, that you should avoid accessing pages arbitrary. In other words, try to get all required data
    // from one page before getting the second, otherwise a page will be decoded several times, every time you access it, and 
    // it's a rather long process, it can take from 100 to 1100 ms depending on a document.
    // Actually, if you want to get something like width, height or dpi of a page, it's not decoded fully, and this metadata
    // is extracted rather quickly, but you should remember: once you requested another page your current one is reset, and needs a full
    // decoding to create an ImageData one more time.
}

void main();



================================================
FILE: library/debug/js/handler.js
================================================
'use strict';
(function() {
var canvas = document.getElementById("canvas");
var output = document.getElementById("output2");
function write(str) {
    output.innerText = str;
}
canvas.onclick = function (e) {
    var rect = this.getBoundingClientRect();
    write((e.clientX - rect.left) + " " + (e.clientY - rect.top));
}
})();

================================================
FILE: library/debug/js/initScript.js
================================================
"use strict";

/**
 * Скрипт для тестирования библиотеки непосредственно в синхронном режиме
 */

DjVu.setDebugMode(true);

var fileSize = 0;
var output;
var djvuArrayBuffer;
var djvuDocument;
var timeOutput = document.querySelector('#time_output');
var renderTimeOutput = document.querySelector('#render_time_output');
var rerunButton = document.querySelector('#rerun');
rerunButton.onclick = rerun;
document.querySelector('#redraw').onclick = redrawPage;

var pageNumber = 1;
var djvuUrl = 'assets/DjVu3Spec_5-10.djvu';
// var djvuUrl = 'assets/carte.djvu';
var baseUrl = 'assets/DjVu3Spec_indirect/';

document.querySelector('#next').onclick = () => {
    pageNumber++;
    redrawPage();
};

document.querySelector('#prev').onclick = () => {
    pageNumber--;
    redrawPage();
}

function saveStringAsFile(string) {
    var link = document.createElement('a');
    link.download = 'string.txt';
    var blob = new Blob([string], { type: 'text/plain' });
    link.href = window.URL.createObjectURL(blob);
    link.click();
}

function saveImage(imageData) {
    var canvas = document.createElement('canvas');
    canvas.width = imageData.width;
    canvas.height = imageData.height;
    var ctx = canvas.getContext('2d');
    ctx.putImageData(imageData, 0, 0);
    var link = document.createElement('a');
    link.download = 'image.png';
    link.href = canvas.toDataURL();
    link.click();
}

function saveStringAsBinFile(string) {
    var link = document.createElement('a');
    link.download = 'string.bin';
    var array = new Uint16Array(string.length);
    for (var i = 0; i < string.length; i++) {
        array[i] = string.charCodeAt(i);
    }
    var blob = new Blob([array], { type: 'application/octet-binary' });
    link.href = window.URL.createObjectURL(blob);
    link.click();
}

function rerun() {
    Globals.init();
    Globals.clearCanvas();

    setTimeout(async () => {
        var start = performance.now();
        await readDjvu(djvuArrayBuffer);
        var time = performance.now() - start;
        timeOutput.innerText = Math.round(time);
    }, 0);
}

window.onload = function () {
    output = document.getElementById("output");
    Globals.init();
    // testFunc();
    loadDjVu();
    //loadPicture(); 
}

function loadDjVu() {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", djvuUrl);
    xhr.responseType = "arraybuffer";
    xhr.onload = function (e) {
        console.log(e.loaded);
        fileSize = e.loaded;
        djvuArrayBuffer = xhr.response;
        rerun();
        //splitDjvu(buf);
    }
    xhr.send();
}

function loadPicture() {
    Globals.loadFile('samples/csl.djvu').then(buffer => {
        readDjvu(buffer);
    });
}

function readPicture(buffer) {

    createImageBitmap(new Blob([buffer])).then(function (image) {
        var pictureTotalTime = performance.now();
        var canvas = document.getElementById('canvas2');
        var c = canvas.getContext('2d');
        canvas.width = image.width;
        canvas.height = image.height;

        c.drawImage(image, 0, 0);
        var imageData = c.getImageData(0, 0, image.width, image.height);
        var iwiw = new IWImageWriter(90, 0, 1);
        // var doc = iwiw.createMultyPageDocument([imageData, imageData, imageData]);
        iwiw.startMultyPageDocument();
        iwiw.addPageToDocument(imageData);
        //for (var i = 0; i < 5; i++) 
        var buffer = iwiw.endMultyPageDocument();
        //var doc = new DjVuDocument(buffer);
        // var doc = iwiw.createOnePageDocument(imageData);
        console.log('docCreateTime = ', performance.now() - pictureTotalTime);
        var link = document.querySelector('#dochref');
        link.href = URL.createObjectURL(new Blob([buffer]));

        // c.putImageData(doc.pages[0].getImage(), 0, 0);
        console.log('Counter', Globals.counter);
        //console.log('PZP', Globals.pzp.log.length, ' ', Globals.pzp.offset );
        // writeln(doc.toString());
        console.log('pictureTotalTime = ', performance.now() - pictureTotalTime);
    });
}

const sleep = (timeout = 0) => new Promise(resolve => setTimeout(resolve, timeout));

async function readDjvu(buf) {
    console.log('redraw');
    var link = document.querySelector('#dochref');
    var time = performance.now();
    console.log("Buffer length = " + buf.byteLength);
    djvuDocument = new DjVu.Document(buf, { baseUrl: baseUrl });
    //console.log(djvuDocument.toString());
    Globals.counter = 0;

    // console.time('Document bundle');
    // const bundle = await djvuDocument.bundle(p => {
    //     console.log(p  * 100);
    // });
    // console.timeEnd('Document bundle');

    // link.href = bundle.createObjectURL();

    //writeln(djvuDocument.toString(true));

    //saveStringAsFile(djvuDocument.toString())
    //return;
    //saveStringAsFile(JSON.stringify(djvuDocument.getContents()));

    // const text = (await djvuDocument.getPage(pageNumber)).getText();
    // console.log(text.length, text);
    // writeln(text);
    await redrawPage();
    //saveStringAsBinFile(djvuDocument.toString());
    // doc.countFiles();
    //console.log(Globals.Timer.toString());
    console.log("Total execution time = ", performance.now() - time);
}

async function redrawPage() {
    console.log('**** Render Page ****');
    var time = performance.now();
    var page = await djvuDocument.getPage(pageNumber);
    var imageData = page.getImageData();
    const dpi = page.getDpi();
    //page.reset();
    //saveImage(imageData);
    Globals.drawImage(
        imageData,
        dpi * 4
    );
    //console.log(doc.pages[pageNumber].getText());
    time = performance.now() - time;
    console.log("Redraw time", time);
    console.log('**** ***** **** ****');

    renderTimeOutput.innerText = Math.round(time);

    // setTimeout(() => {
    //     console.log('**** Refine Page ****');
    //     var time = performance.now();
    //     Globals.drawImage(
    //         page.getImageData(),
    //         page.getDpi() * 1.5
    //     );
    //     time = performance.now() - time;
    //     console.log("Refine time", time);
    //     console.log('**** ***** **** ****');
    // }, 50);

}

function prepareIframe() {
    const iframe = document.createElement('iframe');
    iframe.style.cssText = `
        width: 0;
        height: 0;
        position: absolute;
        left: 0;
        top: 0;
        opacity: 0;
    `;
    document.body.appendChild(iframe);
    return iframe;
}

document.querySelector(('#print_button')).onclick = async () => {
    const iframe = prepareIframe();

    // await sleep(1);

    console.log(iframe.contentWindow);
    const promises = [];
    for (let i = 1; i <= 2; i++) {
        const page = await djvuDocument.getPage(i);
        const image = await page.createPngObjectUrl();

        const img = iframe.contentWindow.document.createElement('img');
        promises.push(new Promise(resolve => img.onload = resolve));
        img.style.display = 'block';
        img.style.breakAfter = 'page';
        img.style.breakInside = 'avoid';
        img.style.margin = '0 auto';
        img.src = image.url;
        img.width = image.width;
        img.height = image.height;
        img.style.width = (image.width / image.dpi) + 'in';
        img.style.height = (image.height / image.dpi) + 'in';
        iframe.contentWindow.document.body.appendChild(img);
    }

    window.w = iframe.contentWindow;

    if (/Firefox/.test(navigator.userAgent)) {
        iframe.contentWindow.print();
    } else {
        await Promise.all(promises);
        iframe.contentWindow.print();
    }
}

function splitDjvu(buf) {
    var link = document.querySelector('#dochref');
    console.log("Buffer length = " + buf.byteLength);
    var doc = new DjVuDocument(buf);
    var slice = doc.slice(0, 11);
    link.href = slice.createObjectURL();
}

/**
 * Функция для работы с файлами загруженными вручную.
 */
function main(files) {
    clear();
    console.log(files.length);
    //readFile(file);
    var fileReader = new FileReader();
    var doc1, doc2;
    fileReader.onload = function () {
        if (!doc1) {
            doc1 = new DjVuDocument(this.result);
            fileReader.readAsArrayBuffer(files[1]);
            return;
        }

        doc2 = new DjVuDocument(this.result);
        testFunc(doc1, doc2);

    };
    if (files.length > 0) {
        fileReader.readAsArrayBuffer(files[0]);
    }
}

function testFunc(doc1, doc2) {
    var doc = DjVuDocument.concat(doc1, doc2);
    Globals.drawImageSmooth(doc.pages[0].getImage(), 600);
    writeln(doc.toString());
    var link = document.querySelector('#dochref');
    link.href = doc.createObjectURL();
}


================================================
FILE: library/debug/js/reloader.js
================================================
/**
 * A simple websocket client reloading a page when a bundle is updated.
 */
(function () {
    'use strict';
    function setConnection() {
        var address = 'ws://' + location.host;

        console.log(`%cTrying to open a connention with ${address} ...`, "color: blue");
        var ws = new WebSocket(address);
        ws.onopen = () => console.info(`%cConnection is opened with ${address}. The page will be reloaded on each update.`, "color: green");

        ws.onmessage = message => {
            if (message.data === 'reload') {
                window.location.reload();
            }
        };

        ws.onclose = (e) => {
            console.info(`%cConnection is closed!`, 'color: red');
            setConnection();
        }
    }

    setConnection();

})();

================================================
FILE: library/debug/sync.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>DjVu.js</title>
    <link rel="shortcut icon" href="/catfav.png" type="image/x-icon" />
    <script type="text/javascript" src="/js/reloader.js"></script>
    <script type="text/javascript" src="/dist/djvu.js"></script>
    <script type="text/javascript" src="/js/DjVuGlobals.js" defer></script>
    <script type="text/javascript" src="/js/debug.js"></script>
    <script type="text/javascript" src="/js/initScript.js" defer></script>
    <link rel="stylesheet" href="/css/style.css">
</head>

<body>
    <div class="control_block">
        <input type="button" id="rerun" value="rerun">
        <input type="button" id="redraw" value="redraw">

        <input type="button" id="next" value="next">
        <input type="button" id="prev" value="prev">

        <span id="time_output"></span>
        <span id="render_time_output"></span>
    </div>
    <pre id="output"></pre>
    <canvas width="192" height="256" id="canvas"></canvas>
    <img id="img" src="" /><br>
    <a id="dochref" href="#" download="file.djvu">Скачать</a>
    <a id="print_button" href="#">Печать</a>
    <canvas width="200" height="260" id="canvas2"></canvas>
    <div id="output2"></div>
    <input type="file" multiple onchange="main(this.files)" />

</body>

</html>

================================================
FILE: library/package.json
================================================
{
  "name": "DjVu.js_Library",
  "scripts": {
    "start": "node server.js",
    "watch": "rollup --config --watch",
    "build": "rollup --config"
  },
  "devDependencies": {
    "colors": "^1.4.0",
    "express": "^4.18.2",
    "rollup": "^3.28.0",
    "rollup-plugin-cleanup": "^3.2.1",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "ws": "^8.13.0"
  },
  "dependencies": {
    "pngjs": "5.0.0"
  }
}


================================================
FILE: library/rollup.config.js
================================================
'use strict';

const cleanup = require('rollup-plugin-cleanup');
const resolve = require('rollup-plugin-node-resolve');
const commonjs = require('rollup-plugin-commonjs');

const outputTemplate = {
    format: 'iife',
    name: 'DjVu',
    intro: "function DjVuScript() {\n'use strict';",
    outro: "}\nreturn Object.assign(DjVuScript(), {DjVuScript});"
};

module.exports = {
    input: './src/index.js',
    output: [
        Object.assign({ file: 'dist/djvu.js' }, outputTemplate),
        Object.assign({ file: '../viewer/public/tmp/djvu.js' }, outputTemplate),
        Object.assign({ file: '../extension/dist/djvu.js' }, outputTemplate)
    ],
    plugins: [
        resolve(),
        commonjs(),
        cleanup()
    ]
};

================================================
FILE: library/server.js
================================================
/**
 * A server used for debugging.
 */

'use strict';

const http = require('http');
const fs = require('fs');
const WebSocket = require('ws');
require('colors');
const path = require('path');
const rollup = require('rollup');
const express = require('express');
const rollupConfig = require('./rollup.config.js');

// you can pass the parameter in the command line. e.g. node server.js 3000
const port = process.argv[2] || 9000;

const app = express();

app.use(express.static(__dirname));
app.use(express.static(__dirname + '/debug'));
app.use('/tests', express.static('./tests', { index: 'tests.html' }));

// used to debug the request interception in the extension according to the headers
app.get('/file_without_extension', (req, res) => {
    res.sendFile(path.resolve('./assets/DjVu3Spec.djvu'), {
        headers: {
            'Content-Type': 'application/octet-stream',
            'Content-Disposition': 'inline; filename="TestFileName.djvu"',
        }
    });
});

app.get('/get_embed_djvu_html', (req, res) => {
    res.send(`
<!DOCTYPE html>
<html lang="en">
   <body><embed type="image/x-djvu" src="${req.query.file}" width="600"></body>     
</html>
`);
});

app.use((req, res) => {
    res.status(404).end('No such a page! 404 error!');
});

const server = http.createServer(app);

const wss = new WebSocket.Server({ server: server });
wss.broadcast = function (data) {
    this.clients.forEach(client => {
        client.send(data);
    });
}

fs.watch('./debug/', { recursive: true }, () => wss.broadcast('reload')); // watch debug .js files
fs.watch('./tests/', () => wss.broadcast('reload')); // watch tests files

const watcher = rollup.watch(rollupConfig);

watcher.on('event', event => {
    switch (event.code) {
        case 'BUNDLE_START':
            console.log('Start building ...'.blue.bold);
            break;
        case 'BUNDLE_END':
            console.log(`Bundle was created in ${event.duration} ms! \n`.green.bold);
            wss.broadcast('reload');
            break;
        case 'ERROR':
            console.log('\n Error occured! \n'.red.bold);
            console.log(event);
            console.log('\n');
            break;
        case 'FATAL':
            console.log('\n Fatal Error occured! \n'.red.bold);
            console.log(event);
            process.exit();
            break;
    }
});

server.listen(parseInt(port));
console.log(`Http and WebSocket servers are listening on port ${port}`);

================================================
FILE: library/src/ByteStream.js
================================================
import { createStringFromUtf8Array } from './DjVu'

/** @typedef {ByteStream} ByteStream */

/**
 * Объект байтового потока. Предоставляет API для чтения сырого ArrayBuffer как потока байт.
 * После вызова каждого метода чтения, внутренний указатель смещается автоматически.
 * Можно читать числа, строки, массив байт разной длины. 
 */
export default class ByteStream {
    constructor(buffer, offsetx, length) {
        this._buffer = buffer;
        this.offsetx = offsetx || 0;
        this.offset = 0;
        this._length = length || buffer.byteLength;
        if (this._length + offsetx > buffer.byteLength) {
            this._length = buffer.byteLength - offsetx;
            console.error("Incorrect length in ByteStream!");
        }
        this.viewer = new DataView(this._buffer, this.offsetx, this._length);
    }

    /** @returns {number} */
    get length() { return this._length; }

    /** @returns {ArrayBuffer} */
    get buffer() { return this._buffer; }

    // "читает" следующие length байт в массив, возвращает массив основанный на том же ArrayBuffer
    getUint8Array(length = this.remainingLength()) {
        var off = this.offset;
        this.offset += length;
        return new Uint8Array(this._buffer, this.offsetx + off, length);
    }

    // возвращает массив полностью представляющий весь поток
    toUint8Array() {
        return new Uint8Array(this._buffer, this.offsetx, this._length);
    }

    remainingLength() {
        return this._length - this.offset;
    }

    reset() {
        this.offset = 0;
    }

    byte() { // the function is used inside other codecs (look at ZPCodec)
        if (this.offset >= this._length) {
            this.offset++;
            return 0xff;
        }
        return this.viewer.getUint8(this.offset++);
    }

    getInt8() {
        return this.viewer.getInt8(this.offset++);
    }
    getInt16() {
        var tmp = this.viewer.getInt16(this.offset);
        this.offset += 2;
        return tmp;
    }
    getUint16() {
        var tmp = this.viewer.getUint16(this.offset);
        this.offset += 2;
        return tmp;
    }
    getInt32() {
        var tmp = this.viewer.getInt32(this.offset);
        this.offset += 4;
        return tmp;
    }
    getUint8() {
        return this.viewer.getUint8(this.offset++);
    }

    getInt24() {
        var uint = this.getUint24();
        return (uint & 0x800000) ? (0xffffff - val + 1) * -1 : uint
    }

    getUint24() {
        return (this.byte() << 16) | (this.byte() << 8) | this.byte();
    }

    jump(length) {
        this.offset += length;
        return this;
    }

    setOffset(offset) {
        this.offset = offset;
    }

    readStr4() { // used to read chunk names, just ASCII characters
        return String.fromCharCode(...this.getUint8Array(4));
    }

    readStrNT() {
        var array = [];
        var byte = this.getUint8();
        while (byte) {
            array.push(byte);
            byte = this.getUint8();
        }
        return createStringFromUtf8Array(new Uint8Array(array));
    }

    readStrUTF(byteLength) {
        return createStringFromUtf8Array(this.getUint8Array(byteLength));
    }

    fork(length = this.remainingLength()) {
        return new ByteStream(this._buffer, this.offsetx + this.offset, length);
    }

    clone() {
        return new ByteStream(this._buffer, this.offsetx, this._length);
    }

    isEmpty() {
        return this.offset >= this._length;
    }
}


================================================
FILE: library/src/ByteStreamWriter.js
================================================
import { stringToCodePoints, codePointsToUtf8 } from './DjVu';

const pageSize = 64 * 1024;
const growthLimit = 20 * 1024 * 1024 / pageSize;

export default class ByteStreamWriter {
    constructor(length = 0) {
        // As the practice has shown, usage of WebAssembly.Memory and its grow() method
        // is more robust than the manual expansion of ArrayBuffer 
        // via `new Uint8Array(newBuffer).set(new Uint8Array(oldBuffer))`.
        // In particular, with WebAssembly.Memory it's possible to download and bundle
        // a document that is about 1.7 GB in size, while with raw ArrayBuffers 
        // a browser tab crashes (in Chrome) when the buffer reaches about 1.5 GB 
        // (or there is an error that a buffer cannot be allocated).
        this.memory = new WebAssembly.Memory({ initial: Math.ceil(length / pageSize), maximum: 65536 });
        this.assignBufferFromMemory();

        this.offset = 0;
        this.offsetMarks = {};
    }

    assignBufferFromMemory() {
        this.buffer = this.memory.buffer;
        this.viewer = new DataView(this.buffer);
    }

    /**
     * Переводит смещение на начало и зачищает сохраненные смещения
     */
    reset() {
        this.offset = 0;
        this.offsetMarks = {};
    }

    saveOffsetMark(mark) {
        this.offsetMarks[mark] = this.offset;
        return this;
    }

    writeByte(byte) {
        this.checkOffset(1);
        this.viewer.setUint8(this.offset++, byte);
        return this;
    }

    writeStr(str) {
        this.writeArray(codePointsToUtf8(stringToCodePoints(str)));
        return this;
    }

    writeInt32(val) {
        this.checkOffset(4);
        this.viewer.setInt32(this.offset, val);
        this.offset += 4;
        return this;
    }

    /**
     * Перезапись числа. Принимает смещение или метку смещения и число
     */
    rewriteInt32(off, val) {
        var xoff = off;
        if (typeof (xoff) === 'string') {
            xoff = this.offsetMarks[off];
            this.offsetMarks[off] += 4;
        }
        this.viewer.setInt32(xoff, val);
    }


    /**
     * Перезапись размера в 4 байта по сохраненной метке
     */
    rewriteSize(offmark) {
        if (!this.offsetMarks[offmark]) throw new Error('Unexisting offset mark');
        var xoff = this.offsetMarks[offmark];
        this.viewer.setInt32(xoff, this.offset - xoff - 4);
    }

    getBuffer() {
        if (this.offset === this.buffer.byteLength) {
            return this.buffer;
        }
        return this.buffer.slice(0, this.offset);
    }

    checkOffset(requiredBytesNumber = 0) {
        const bool = this.offset + requiredBytesNumber > this.buffer.byteLength;
        if (bool) {
            this._expand(requiredBytesNumber);
        }
        return bool;
    }

    _expand(requiredBytesNumber) {
        this.memory.grow(Math.max(
            Math.ceil(requiredBytesNumber / pageSize),
            Math.min(this.memory.buffer.byteLength / pageSize, growthLimit)
        ));
        this.assignBufferFromMemory();
    }

    //смещение на length байт
    jump(length) {
        length = +length;
        if (length > 0) {
            this.checkOffset(length);
        }
        this.offset += length;
        return this;
    }

    writeByteStream(bs) {
        this.writeArray(bs.toUint8Array());
    }

    writeArray(arr) {
        while (this.checkOffset(arr.length)) { }
        new Uint8Array(this.buffer).set(arr, this.offset);
        this.offset += arr.length;
    }

    writeBuffer(buffer) {
        this.writeArray(new Uint8Array(buffer));
    }

    writeStrNT(str) {
        this.writeStr(str);
        this.writeByte(0);
    }

    writeInt16(val) {
        this.checkOffset(2);
        this.viewer.setInt16(this.offset, val);
        this.offset += 2;
        return this;
    }

    writeUint16(val) {
        this.checkOffset(2);
        this.viewer.setUint16(this.offset, val);
        this.offset += 2;
        return this;
    }

    writeInt24(val) {
        this.writeByte((val >> 16) & 0xff)
            .writeByte((val >> 8) & 0xff)
            .writeByte(val & 0xff);
        return this;
    }
}


================================================
FILE: library/src/DjVu.js
================================================
var DjVu = {
    VERSION: '0.5.4',
    IS_DEBUG: false,
    setDebugMode: (flag) => DjVu.IS_DEBUG = flag
};

export function pLimit(limit = 4) {
    const queue = [];
    let running = 0;

    const runNext = async () => {
        if (!queue.length || running >= limit) return;
        const func = queue.shift();

        try {
            running++;
            await func();
        } finally {
            running--;
            runNext();
        }
    };

    return func => new Promise((resolve, reject) => {
        queue.push(() => func().then(resolve, reject));
        runNext();
    });
}

/**
 *  @returns {Promise<ArrayBuffer>}
 */
export function loadFileViaXHR(url, responseType = 'arraybuffer') {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.responseType = responseType;
        xhr.onload = (e) => resolve(xhr);
        xhr.onerror = (e) => reject(xhr);
        xhr.send();
    });
}

const utf8Decoder = self.TextDecoder ? new self.TextDecoder() : {
    decode(utf8array) {
        const codePoints = utf8ToCodePoints(utf8array);
        return String.fromCodePoint(...codePoints);
    }
};

export function createStringFromUtf8Array(utf8array) {
    return utf8Decoder.decode(utf8array);
}

/**
 * Creates an array of Unicode code points from an array, representing a utf8 encoded string
 * The code assumes that the utf-8 input is well formed. Otherwise, can produce illegal code 
 * points. As the practice has shown, there are ill-formed utf-8 arrays in some djvu files.
 * 
 * This function should be removed in the future. The standard TextDecoder/TextEncoder should
 * be used instead. Its was initially written only for the old Edge browser 
 * which didn't support TextDecoder.
 */
export function utf8ToCodePoints(utf8array) {
    var i, c;
    var codePoints = [];

    i = 0;
    while (i < utf8array.length) {
        c = utf8array[i++];
        switch (c >> 4) {
            case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
                // 0xxx xxxx
                codePoints.push(c);
                break;
            case 12: case 13:
                // 110x xxxx   10xx xxxx
                codePoints.push(((c & 0x1F) << 6) | (utf8array[i++] & 0x3F));
                break;
            case 14:
                // 1110 xxxx  10xx xxxx  10xx xxxx      
                codePoints.push(
                    ((c & 0x0F) << 12) |
                    ((utf8array[i++] & 0x3F) << 6) |
                    (utf8array[i++] & 0x3F)
                );
                break;
            case 15:
                // 1111 0xxx  10xx xxxx  10xx xxxx  10xx xxxx
                codePoints.push(
                    ((c & 0x07) << 18) |
                    ((utf8array[i++] & 0x3F) << 12) |
                    ((utf8array[i++] & 0x3F) << 6) |
                    (utf8array[i++] & 0x3F)
                );
                break;
        }
    }
    return codePoints.map(codePoint => {
        return codePoint > 0x10FFFF ? 120 : codePoint; // replace all bad code points with "x"
    });
}

export function codePointsToUtf8(codePoints) {
    var utf8array = [];
    codePoints.forEach(codePoint => {
        if (codePoint < 0x80) {
            utf8array.push(codePoint);
        } else if (codePoint < 0x800) {
            utf8array.push(0xC0 | (codePoint >> 6));
            utf8array.push(0x80 | (codePoint & 0x3F));
        } else if (codePoint < 0x10000) {
            utf8array.push(0xE0 | (codePoint >> 12));
            utf8array.push(0x80 | ((codePoint >> 6) & 0x3F));
            utf8array.push(0x80 | (codePoint & 0x3F));
        } else {
            utf8array.push(0xF0 | (codePoint >> 18));
            utf8array.push(0x80 | ((codePoint >> 12) & 0x3F));
            utf8array.push(0x80 | ((codePoint >> 6) & 0x3F));
            utf8array.push(0x80 | (codePoint & 0x3F));
        }
    });

    return new Uint8Array(utf8array);
}

export function stringToCodePoints(str) {
    var codePoints = [];
    for (var i = 0; i < str.length; i++) {
        var code = str.codePointAt(i);
        codePoints.push(code);
        if (code > 65535) {
            i++; // skip the second part of 4 byte symbol
        }
    }

    return codePoints;
}

// unicode test: symbols of 1, 2, 4 and 3 bytes (in utf8 encoding) are encoded and decoded
// var str = 'szвф' + String.fromCodePoint(0x1F702) + String.fromCodePoint(0x1F704) + String.fromCodePoint(0x2C00) + String.fromCodePoint(0x2C08);
// var str2 = String.fromCodePoint(...utf8ToCodePoints(codePointsToUtf8(stringToCodePoints(str))));
// console.log(str, str2, str === str2);

export default DjVu;

================================================
FILE: library/src/DjVuDocument.js
================================================
import DjViChunk from './chunks/DjViChunk';
import DjVuPage from './DjVuPage';
import DIRMChunk from './chunks/DirmChunk';
import NAVMChunk from './chunks/NavmChunk';
import DjVuWriter from './DjVuWriter';
import DjVu from './DjVu';
import ThumChunk from './chunks/ThumChunk';
import ByteStream from './ByteStream';
import { loadPageDependency, loadPage } from './methods/load';
import {
    IncorrectFileFormatDjVuError,
    NoSuchPageDjVuError,
    CorruptedFileDjVuError,
    NoBaseUrlDjVuError,
} from './DjVuErrors';

/** @typedef {DjVuDocument} DjVuDocument */

const MEMORY_LIMIT = 50 * 1024 * 1024; // 50 MB

export default class DjVuDocument {
    constructor(arraybuffer, { baseUrl = null, memoryLimit = MEMORY_LIMIT } = {}) {
        this.buffer = arraybuffer;
        this.baseUrl = baseUrl && baseUrl.trim();
        if (typeof this.baseUrl === 'string') {
            if (this.baseUrl[this.baseUrl.length - 1] !== '/') {
                this.baseUrl += '/';
            }
            if (!/^[A-Za-z]+:\/\//.test(this.baseUrl)) { // a relative URL
                // all URL in a worker should be absolute
                // in case of a local web page opened as file:/// there is no location.origin.
                this.baseUrl = location.origin && (new URL(this.baseUrl, location.origin).href);
            }
        }
        this.memoryLimit = memoryLimit; // required to limit the size of cache in case of indirect djvu

        this.djvi = {}; //разделяемые ресурсы. Могут потребоваться и в случае одностраничного документа
        this.getINCLChunkCallback = id => this.djvi[id].innerChunk;

        this.bs = new ByteStream(arraybuffer);
        this.formatID = this.bs.readStr4();
        if (this.formatID !== 'AT&T') {
            throw new IncorrectFileFormatDjVuError();
        }
        this.id = this.bs.readStr4();
        this.length = this.bs.getInt32();
        this.id += this.bs.readStr4();
        if (this.id === 'FORMDJVM') {
            this._initMultiPageDocument();
        } else if (this.id === 'FORMDJVU') {
            this.bs.jump(-12);
            this.pages = [new DjVuPage(this.bs.fork(this.length + 8), this.getINCLChunkCallback)];
        } else {
            throw new CorruptedFileDjVuError(
                `The id of the first chunk of the document should be either FORMDJVM or FORMDJVU, but there is ${this.id}`
            );
        }
    }

    _initMultiPageDocument() { // for FORMDJVM
        this._readMetaDataChunk();
        this._readContentsChunkIfExists();

        /**
         * @type {Array<DjVuPage>}
         */
        this.pages = []; //страницы FORMDJVU
        this.thumbs = [];

        if (this.dirm.isBundled) {
            this._parseComponents();
        } else {
            this.pages = new Array(this.dirm.getPagesQuantity()); // fixed length array in order to know what pages are loaded and what are not.
            this.memoryUsage = this.bs.buffer.byteLength;
            this.loadedPageNumbers = [];
        }
    }

    _readMetaDataChunk() { // DIRM chunk
        var id = this.bs.readStr4();
        if (id !== 'DIRM') {
            throw new CorruptedFileDjVuError("The DIRM chunk must be the first but there is " + id + " instead!");
        }
        var length = this.bs.getInt32();
        this.bs.jump(-8);
        this.dirm = new DIRMChunk(this.bs.fork(length + 8)); // document directory, metadata for multi-page documents
        this.bs.jump(8 + length + (length & 1 ? 1 : 0));
    }

    _readContentsChunkIfExists() { // NAVM chunk
        this.navm = null; // человеческое оглавление 
        if (this.bs.remainingLength() > 8) {
            var id = this.bs.readStr4();
            var length = this.bs.getInt32();
            this.bs.jump(-8);
            if (id === 'NAVM') {
                this.navm = new NAVMChunk(this.bs.fork(length + 8))
            }
        }
    }

    _parseComponents() {
        // all chunks of the file in the order which they are listed in the DIRM chunk
        this.dirmOrderedChunks = new Array(this.dirm.getFilesQuantity());

        for (var i = 0; i < this.dirm.offsets.length; i++) {
            this.bs.setOffset(this.dirm.offsets[i]);
            var id = this.bs.readStr4();
            var length = this.bs.getInt32();
            id += this.bs.readStr4();
            this.bs.jump(-12);
            switch (id) {
                case "FORMDJVU":
                    this.pages.push(this.dirmOrderedChunks[i] = new DjVuPage(
                        this.bs.fork(length + 8),
                        this.getINCLChunkCallback
                    ));
                    break;
                case "FORMDJVI":
                    //через строчку id chunk INCL ссылается на нужный ресурс
                    this.dirmOrderedChunks[i] = this.djvi[this.dirm.ids[i]] = new DjViChunk(this.bs.fork(length + 8));
                    break;
                case "FORMTHUM":
                    this.thumbs.push(this.dirmOrderedChunks[i] = new ThumChunk(this.bs.fork(length + 8)));
                    break;
                default:
                    console.error("Incorrect chunk ID: ", id);
            }
        }
    }

    /**
     * @returns {Array<{ width: number, height: number, dpi: number }>} 
     */
    getPagesSizes() {
        var sizes = this.pages.map(page => {
            return {
                width: page.getWidth(),
                height: page.getHeight(),
                dpi: page.getDpi(),
            };
        });
        this.pages.forEach(page => page.reset());
        return sizes;
    }

    isBundled() {
        return this.dirm ? this.dirm.isBundled : true;
    }

    getPagesQuantity() {
        return this.dirm ? this.dirm.getPagesQuantity() : 1;
    }

    /** @returns {import('./chunks/NavmChunk').Contents} */
    getContents() {
        return this.navm ? this.navm.getContents() : null;
    }

    getMemoryUsage() {
        return this.memoryUsage;
    }

    getMemoryLimit() {
        return this.memoryLimit;
    }

    setMemoryLimit(limit = MEMORY_LIMIT) {
        this.memoryLimit = limit;
    }

    getPageNumberByUrl(url) {
        if (url[0] !== '#') {
            return null;
        }

        const ref = url.slice(1);
        let pageNumber = this.dirm.getPageNumberByItsId(ref);
        if (!pageNumber) {
            const num = Math.round(Number(ref));
            if (num >= 1 && num <= this.pages.length) { // there can be refs like "#057";
                pageNumber = num;
            }
        }

        return pageNumber || null;
    }

    releaseMemoryIfRequired(preservedDependencies = null) {
        if (this.memoryUsage <= this.memoryLimit) {
            //console.log(`%c Memory wasnt released  ${this.memoryUsage}, ${this.memoryLimit}, ${this.loadedPageNumbers.length}, ${Object.keys(this.djvi).length}`, "color: green");
            return;
        }
        //var was = this.memoryUsage;
        while (this.memoryUsage > this.memoryLimit && this.loadedPageNumbers.length) {
            var number = this.loadedPageNumbers.shift();
            this.memoryUsage -= this.pages[number].bs.buffer.byteLength;
            this.pages[number] = null;
        }

        if (this.memoryUsage > this.memoryLimit && !this.loadedPageNumbers.length) { // remove all dictionaries, if there is no pages
            this.resetLastRequestedPage();

            var newDjVi = {};
            if (preservedDependencies) {
                preservedDependencies.forEach(id => {
                    newDjVi[id] = this.djvi[id];
                    this.memoryUsage += newDjVi[id].bs.buffer.byteLength; // will be subtracted back further
                });
            }
            Object.keys(this.djvi).forEach(key => {
                this.memoryUsage -= this.djvi[key].bs.buffer.byteLength;
            });

            this.djvi = newDjVi;
        }
        //console.log(`%c Memory was released ${was}, ${this.memoryUsage}, ${this.loadedPageNumbers.length}, ${Object.keys(this.djvi).length}`, "color: red");
    }

    _getUrlByPageNumber(number) {
        return this.baseUrl + this.dirm.getPageNameByItsNumber(number);
    }

    async getPage(number) {
        var page = this.pages[number - 1];
        if (this.lastRequestedPage && this.lastRequestedPage !== page) {
            this.lastRequestedPage.reset();
        }
        this.lastRequestedPage = page;

        if (!page) {
            if (number < 1 || number > this.pages.length || this.isBundled()) {
                throw new NoSuchPageDjVuError(number);
            } else {
                if (this.baseUrl === null) {
                    throw new NoBaseUrlDjVuError();
                }
                const bs = await loadPage(
                    number,
                    this._getUrlByPageNumber(number)
                );

                const page = new DjVuPage(bs, this.getINCLChunkCallback);
                this.memoryUsage += bs.buffer.byteLength;
                await this._loadDependencies(page.getDependencies(), number);

                this.releaseMemoryIfRequired(page.getDependencies()); // should be called before the page are added to the pages array
                this.pages[number - 1] = page;
                this.loadedPageNumbers.push(number - 1);
                this.lastRequestedPage = page;
            }
        } else if (!this.isOnePageDependenciesLoaded && this.id === "FORMDJVU") { // single page document
            var dependencies = page.getDependencies();
            if (dependencies.length) {
                await this._loadDependencies(dependencies, 1);
            }
            this.isOnePageDependenciesLoaded = true;
        }

        return this.lastRequestedPage;
    }

    async _loadDependencies(dependencies, pageNumber = null) {
        var unloadedDependencies = dependencies.filter(id => !this.djvi[id]);
        if (!unloadedDependencies.length) {
            return;
        }
        await Promise.all(unloadedDependencies.map(async id => {
            const bs = await loadPageDependency(
                id,
                this.dirm ? this.dirm.getComponentNameByItsId(id) : id,
                this.baseUrl,
                pageNumber
            );

            this.djvi[id] = new DjViChunk(bs);
            this.memoryUsage += bs.buffer.byteLength;
        }));
    }

    getPageUnsafe(number) {
        return this.pages[number - 1];
    }

    resetLastRequestedPage() {
        this.lastRequestedPage && this.lastRequestedPage.reset();
        this.lastRequestedPage = null;
    }

    /** A debug function, isn't actually used */
    countFiles() {
        var count = 0;
        var bs = this.bs.clone();
        bs.jump(16);
        while (!bs.isEmpty()) {
            var id = bs.readStr4();
            var length = bs.getInt32();
            // перепрыгнули к следующей порции
            bs.jump(length + (length & 1 ? 1 : 0));
            if (id === 'FORM') {
                count++;
            }
        }
        return count;
    }

    /**
     * Возвращает метаданные документа. 
     * @param {Boolean} html - заменять ли \n на <br>
     * @returns {string} строка метаданных
     */
    toString(html) {
        var str = this.formatID + '\n';
        if (this.dirm) { // multi page document
            str += this.id + " " + this.length + '\n\n';
            str += this.dirm.toString();
            str += this.navm ? this.navm.toString() : '';
            if (this.isBundled()) {
                this.dirmOrderedChunks.forEach((chunk, i) => {
                    str += this.dirm.getMetadataStringByIndex(i) + chunk.toString();
                });
            } else {
                for (let i = 0; i < this.dirm.getFilesQuantity(); i++) {
                    str += this.dirm.getMetadataStringByIndex(i);
                }
            }
        } else { // single page document
            str += this.pages[0].toString();
        }

        return html ? str.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : str;
    }

    /**
     * Создает ссылку для скачивания документа
     */
    createObjectURL() {
        var blob = new Blob([this.bs.buffer]);
        var url = URL.createObjectURL(blob);
        return url;
    }

    /**
     *  Creates a new DjVuDocument with pages from "from" to "to", including first and last pages.
     */
    slice(from = 1, to = this.pages.length) {
        const djvuWriter = new DjVuWriter();
        djvuWriter.startDJVM();
        const dirm = {
            dflags: this.dirm.dflags,
            flags: [],
            names: [],
            titles: [],
            sizes: [],
            ids: [],
        };
        const chunkByteStreams = [];
        const totalPageCount = to - from + 1;
        // все зависимости страниц в новом документе
        // нужно чтобы не копировать лишние словари
        const dependencies = {};
        const filesQuantity = this.dirm.getFilesQuantity();

        // находим все зависимости в первом проходе
        for (
            let i = 0, pageIndex = 0, addedPageCount = 0;
            i < filesQuantity && addedPageCount < totalPageCount;
            i++
        ) {
            const isPage = (this.dirm.flags[i] & 63) === 1;
            if (!isPage) continue;
            pageIndex++;
            if (pageIndex < from) continue;

            addedPageCount++;
            const pageByteStream = new ByteStream(this.buffer, this.dirm.offsets[i], this.dirm.sizes[i]);
            const deps = new DjVuPage(pageByteStream).getDependencies();
            for (const dependencyId of deps) {
                dependencies[dependencyId] = 1;
            }
        }

        // теперь все словари и страницы, которые нужны
        for (
            let i = 0, pageIndex = 0, addedPageCount = 0;
            // ?? maybe dicts can go after pages and we should check all chunks (remove addedPageCount < totalPageCount)
            i < filesQuantity && addedPageCount < totalPageCount;
            i++
        ) {
            const isPage = (this.dirm.flags[i] & 63) === 1;
            if (isPage) {
                pageIndex++;
                //если она не входит в заданный диапазон
                if (pageIndex < from) continue;
                addedPageCount++;
            }


            //копируем страницы и словари. Эскизы пропускаем - пока что это не реализовано
            if ((this.dirm.ids[i] in dependencies) || isPage) {
                dirm.flags.push(this.dirm.flags[i]);
                dirm.sizes.push(this.dirm.sizes[i]);
                dirm.ids.push(this.dirm.ids[i]);
                dirm.names.push(this.dirm.names[i]);
                dirm.titles.push(this.dirm.titles[i]);
                chunkByteStreams.push(
                    new ByteStream(this.buffer, this.dirm.offsets[i], this.dirm.sizes[i])
                );
            }
        }

        djvuWriter.writeDirmChunk(dirm);
        if (this.navm) {
            djvuWriter.writeChunk(this.navm);
        }

        for (const chunkByteStream of chunkByteStreams) {
            djvuWriter.writeFormChunkBS(chunkByteStream);
        }
        const newBuffer = djvuWriter.getBuffer();
        DjVu.IS_DEBUG && console.log("New Buffer size = ", newBuffer.byteLength);

        return new DjVuDocument(newBuffer);
    }

    /**
     * Функция склейки двух документов
     */
    static concat(doc1, doc2) {
        var dirm = {};
        var length = doc1.pages.length + doc2.pages.length;
        dirm.dflags = 129;
        dirm.flags = [];
        dirm.sizes = [];
        dirm.ids = [];
        var pages = [];
        var idset = new Set(); // чтобы убрать повторяющиеся id

        if (!doc1.dirm) { // тогда  записываем свой id
            dirm.flags.push(1);
            dirm.sizes.push(doc1.pages[0].bs.length);
            dirm.ids.push('single');
            idset.add('single');
            pages.push(doc1.pages[0]);
        }
        else {
            for (var i = 0; i < doc1.pages.length; i++) {
                dirm.flags.push(doc1.dirm.flags[i]);
                dirm.sizes.push(doc1.dirm.sizes[i]);
                dirm.ids.push(doc1.dirm.ids[i]);
                idset.add(doc1.dirm.ids[i]);
                pages.push(doc1.pages[i]);
            }
        }
        if (!doc2.dirm) { // тогда  записываем свой id
            dirm.flags.push(1);
            dirm.sizes.push(doc2.pages[0].bs.length);

            var newid = 'single2';
            var tmp = 0;
            while (idset.has(newid)) { // генерируем уникальный id
                newid = 'single2' + tmp.toString();
                tmp++;
            }
            dirm.ids.push(newid);
            pages.push(doc2.pages[0]);
        }
        else {
            for (var i = 0; i < doc2.pages.length; i++) {
                dirm.flags.push(doc2.dirm.flags[i]);
                dirm.sizes.push(doc2.dirm.sizes[i]);
                var newid = doc2.dirm.ids[i];
                var tmp = 0;
                while (idset.has(newid)) { // генерируем уникальный id
                    newid = doc2.dirm.ids[i] + tmp.toString();
                    tmp++;
                }
                dirm.ids.push(newid);
                idset.add(newid);
                pages.push(doc2.pages[i]);
            }
        }

        var dw = new DjVuWriter();
        dw.startDJVM();
        dw.writeDirmChunk(dirm);
        for (var i = 0; i < length; i++) {
            dw.writeFormChunkBS(pages[i].bs);
        }

        return new DjVuDocument(dw.getBuffer());
    }
}

import bundle from './methods/bundle';

Object.assign(DjVuDocument.prototype, {
    bundle,
});

================================================
FILE: library/src/DjVuErrors.js
================================================
/**
 * Простейший класс ошибки, не содержит рекурсивных данных, чтобы иметь возможность копироваться
 * между потоками в сообщениях
 */
export class DjVuError {
    constructor(code, message, additionalData = null) {
        this.code = code;
        this.message = message;
        if (additionalData) this.additionalData = additionalData;
    }
}

export class IncorrectFileFormatDjVuError extends DjVuError {
    constructor() {
        super(DjVuErrorCodes.INCORRECT_FILE_FORMAT, "The provided file is not a .djvu file!");
    }
}

export class NoSuchPageDjVuError extends DjVuError {
    constructor(pageNumber) {
        super(DjVuErrorCodes.NO_SUCH_PAGE, "There is no page with the number " + pageNumber + " !");
        this.pageNumber = pageNumber;
    }
}

export class CorruptedFileDjVuError extends DjVuError {
    constructor(message = "", data = null) {
        super(DjVuErrorCodes.FILE_IS_CORRUPTED, "The file is corrupted! " + message, data);
    }
}

export class UnableToTransferDataDjVuError extends DjVuError {
    constructor(tasks) {
        super(DjVuErrorCodes.DATA_CANNOT_BE_TRANSFERRED,
            "The data cannot be transferred from the worker to the main page! " +
            "Perhaps, you requested a complex object like DjVuPage, but only simple objects can be transferred between workers."
        );
        this.tasks = tasks;
    }
}

export class IncorrectTaskDjVuError extends DjVuError {
    constructor(task) {
        super(DjVuErrorCodes.INCORRECT_TASK, "The task contains an incorrect sequence of functions!");
        this.task = task;
    }
}

export class NoBaseUrlDjVuError extends DjVuError {
    constructor() {
        super(DjVuErrorCodes.NO_BASE_URL,
            "The base URL is required for the indirect djvu to load components," +
            " but no base URL was provided to the document constructor!"
        );
    }
}

function getErrorMessageByData(data) {
    var message = '';
    if (data.pageNumber) {
        if (data.dependencyId) {
            message = `A dependency ${data.dependencyId} for the page number ${data.pageNumber} can't be loaded!\n`;
        } else {
            message = `The page number ${data.pageNumber} can't be loaded!`;
        }
    } else if (data.dependencyId) {
        message = `A dependency ${data.dependencyId} can't be loaded!\n`;
    }
    return message;
}

export class UnsuccessfulRequestDjVuError extends DjVuError {
    constructor(xhr, data = { pageNumber: null, dependencyId: null }) {
        var message = getErrorMessageByData(data);
        super(DjVuErrorCodes.UNSUCCESSFUL_REQUEST,
            message + '\n' +
            `The request to ${xhr.responseURL} wasn't successful.\n` +
            `The response status is ${xhr.status}.\n` +
            `The response status text is: "${xhr.statusText}".`
        );
        this.status = xhr.status;
        this.statusText = xhr.statusText;
        this.url = xhr.responseURL;
        if (data.pageNumber) {
            this.pageNumber = data.pageNumber;
        }
        if (data.dependencyId) {
            this.dependencyId = data.dependencyId;
        }
    }
}

export class NetworkDjVuError extends DjVuError {
    constructor(data = { pageNumber: null, dependencyId: null, url: null }) {
        super(DjVuErrorCodes.NETWORK_ERROR,
            getErrorMessageByData(data) + '\n' +
            "A network error occurred! Check your network connection!"
        );
        if (data.pageNumber) {
            this.pageNumber = data.pageNumber;
        }
        if (data.dependencyId) {
            this.dependencyId = data.dependencyId;
        }
        if (data.url) {
            this.url = data.url;
        }
    }
}

export const DjVuErrorCodes = Object.freeze({
    FILE_IS_CORRUPTED: 'FILE_IS_CORRUPTED',
    INCORRECT_FILE_FORMAT: 'INCORRECT_FILE_FORMAT',
    NO_SUCH_PAGE: 'NO_SUCH_PAGE',
    UNEXPECTED_ERROR: 'UNEXPECTED_ERROR',
    DATA_CANNOT_BE_TRANSFERRED: 'DATA_CANNOT_BE_TRANSFERRED',
    INCORRECT_TASK: 'INCORRECT_TASK',
    NO_BASE_URL: 'NO_BASE_URL',
    NETWORK_ERROR: 'NETWORK_ERROR',
    UNSUCCESSFUL_REQUEST: 'UNSUCCESSFUL_REQUEST',
});

================================================
FILE: library/src/DjVuPage.js
================================================
import { INCLChunk, ColorChunk, CIDaChunk, IFFChunk, INFOChunk, CompositeChunk, ErrorChunk } from './chunks/IFFChunks';
import JB2Dict from './jb2/JB2Dict';
import JB2Image from './jb2/JB2Image';
import DjVuPalette from './chunks/DjVuPalette';
import IWImage from './iw44/IWImage';
import DjVuText from './chunks/DjVuText';
import { ZPDecoder } from './ZPCodec';
import DjVu from './DjVu';
import { CorruptedFileDjVuError } from './DjVuErrors';
import png from 'pngjs/browser';

const offscreenCanvas = self.OffscreenCanvas ? new OffscreenCanvas(0, 0) : null;
const ctx = offscreenCanvas ? offscreenCanvas.getContext('2d') : null;

async function createBlobFromImageData(imageData) {
    if (!offscreenCanvas) {
        return null;
    }
    offscreenCanvas.width = imageData.width;
    offscreenCanvas.height = imageData.height;
    ctx.putImageData(imageData, 0, 0);
    const blob = await offscreenCanvas.convertToBlob();
    offscreenCanvas.width = 0;
    offscreenCanvas.height = 0;
    return blob;
}

/**
 * Страница документа
 */
export default class DjVuPage extends CompositeChunk {

    /**
     * @param {import('./ByteStream').ByteStream} bs
     * @param {Function} getINCLChunkCallback
     */
    constructor(bs, getINCLChunkCallback) {
        super(bs);
        this.getINCLChunkCallback = getINCLChunkCallback; // метод для получения глобальной порции данных (словарь обычно) от документа по id
        this.reset();
    }

    reset() {
        this.bs.setOffset(12); // skip id, length and secondary id
        this.djbz = null;
        this.bg44arr = new Array();
        this.fg44 = null;

        /**
         * @type {?IWImage}
         */
        this.bgimage = null;
        /**
         * @type {?IWImage}
         */
        this.fgimage = null;
        /**
         * @type {?JB2Image}
         */
        this.sjbz = null;
        /**
         * @type {?DjVuPalette}
         */
        this.fgbz = null;

        /** @type {?DjVuText} */
        this.text = null;

        this.decoded = false;
        this.isBackgroundCompletelyDecoded = false;
        this.isFirstBgChunkDecoded = false;
        this.info = null;


        // список всех порций данных - для toString
        this.iffchunks = [];
        // id разделяемых данных (в частности словарей)
        this.dependencies = null;
        //this.init();
    }

    /**
     * Свойство необходимое для корректного отображения страницы - влияет на 100% масштаб.
     */
    getDpi() {
        if (this.info) {
            return this.info.dpi;
        } else {
            return this.init().info.dpi;
        }
    }

    getHeight() {
        return this.info ? this.info.height : this.init().info.height;
    }

    getWidth() {
        return this.info ? this.info.width : this.init().info.width;
    }

    async createPngObjectUrl() {
        var time = performance.now();
        var imageData = this.getImageData();
        var imageBlob = await createBlobFromImageData(imageData);
        if (!imageBlob) {
            const pngImage = png.PNG.sync.write(this.getImageData())
            imageBlob = new Blob([pngImage.buffer]);
        }
        DjVu.IS_DEBUG && console.log("Png creation time = ", performance.now() - time);
        var url = URL.createObjectURL(imageBlob);
        return {
            //url: URL.createObjectURL(new Blob([new ArrayBuffer(10 * 1024 * 1024)])),
            url: url,
            byteLength: imageBlob.size,
            width: this.getWidth(),
            height: this.getHeight(),
            dpi: this.getDpi(),
        };
    }

    // метод поиска зависимостей, то есть INCLChunk
    // возвращает массив id
    /** @returns {Array<string>} */
    getDependencies() {
        //чтобы не вызывалось более 1 раза
        if (this.info || this.dependencies) {
            return this.dependencies;
        }
        this.dependencies = [];
        var bs = this.bs.fork();
        while (!bs.isEmpty()) {
            var chunk;
            var id = bs.readStr4();
            var length = bs.getInt32();
            bs.jump(-8);
            // вернулись назад
            var chunkBs = bs.fork(length + 8);
            bs.jump(8 + length + (length & 1 ? 1 : 0));
            // перепрыгнули к следующей порции
            if (id === "INCL") {
                chunk = new INCLChunk(chunkBs);
                this.dependencies.push(chunk.ref);
            }
        }
        return this.dependencies;
    }

    /**
     * Метод предварительного разбора страницы.
     * Вызывается вручную или автоматически
     * @returns {DjVuPage}
     */
    init() {
        //чтобы не вызывалось более 1 раза
        if (this.info) {
            return this;
        }
        this.dependencies = [];

        var id = this.bs.readStr4();
        if (id !== 'INFO') {
            throw new CorruptedFileDjVuError("The very first chunk must be INFO chunk, but we got " + id + '!')
        }
        var length = this.bs.getInt32();
        this.bs.jump(-8);
        this.info = new INFOChunk(this.bs.fork(length + 8));
        this.bs.jump(8 + length + (this.info.length & 1));
        this.iffchunks.push(this.info);

        while (!this.bs.isEmpty()) {
            var chunk;
            var id = this.bs.readStr4();
            var length = this.bs.getInt32();

            this.bs.jump(-8); // вернулись назад
            var chunkBs = this.bs.fork(length + 8); // создали поток включающий только 1 порцию
            this.bs.jump(8 + length + (length & 1)); // перепрыгнули к следующей порции

            if (!length) { // empty chunk
                chunk = new IFFChunk(chunkBs); // save it for metadata
            } else if (id == "FG44") {
                chunk = this.fg44 = new ColorChunk(chunkBs);
            } else if (id == "BG44") {
                this.bg44arr.push(chunk = new ColorChunk(chunkBs));
            } else if (id == 'Sjbz') {
                chunk = this.sjbz = new JB2Image(chunkBs);
            } else if (id === "INCL") {
                chunk = this.incl = new INCLChunk(chunkBs);
                var inclChunk = this.getINCLChunkCallback(this.incl.ref);
                if (inclChunk) { // it takes place in case of polish_indirect, where shared_anno.iff is empty
                    inclChunk.id === "Djbz" ? this.djbz = inclChunk : this.iffchunks.push(inclChunk);
                }
                this.dependencies.push(chunk.ref);
            } else if (id === "CIDa") {
                try {
                    chunk = new CIDaChunk(chunkBs);
                } catch (e) {
                    chunk = new ErrorChunk('CIDa', e);
                }
            } else if (id === 'Djbz') {
                chunk = this.djbz = new JB2Dict(chunkBs);
            } else if (id === 'FGbz') {
                chunk = this.fgbz = new DjVuPalette(chunkBs);
            } else if (id === 'TXTa' || id === 'TXTz') {
                chunk = this.text = new DjVuText(chunkBs);
            } else {
                chunk = new IFFChunk(chunkBs);
            }
            //тут все порции в том порядке, в каком встретились при разборе 
            this.iffchunks.push(chunk);
        }
        return this;
    }

    getRotation() {
        switch (this.info.flags) {
            case 5: return 90;
            case 2: return 180;
            case 6: return 270;
            default: return 0;
        }
    }

    rotateIfRequired(imageData) {
        if (this.info.flags === 5 || this.info.flags === 6) {
            var newImageData = new ImageData(this.info.height, this.info.width);
            var newPixelArray = new Uint32Array(newImageData.data.buffer);
            var oldPixelArray = new Uint32Array(imageData.data.buffer);
            var height = this.info.height;
            var width = this.info.width;

            if (this.info.flags === 6) { // 270
                for (var i = 0; i < width; i++) {
                    var rowOffset = (width - i - 1) * height;
                    var to = height + rowOffset;
                    for (var newIndex = rowOffset, oldIndex = i; newIndex < to; newIndex++, oldIndex += width) {
                        newPixelArray[newIndex] = oldPixelArray[oldIndex];
                    }
                }
            } else { // 90
                for (var i = 0; i < width; i++) {
                    var rowOffset = i * height;
                    var from = height + rowOffset - 1;
                    for (var newIndex = from, oldIndex = i; newIndex >= rowOffset; newIndex--, oldIndex += width) {
                        newPixelArray[newIndex] = oldPixelArray[oldIndex];
                    }
                }
            }

            return newImageData;
        }

        if (this.info.flags === 2) { // 180
            new Uint32Array(imageData.data.buffer).reverse();
            return imageData;
        }

        return imageData;
    }

    getImageData(rotate = true) {
        const image = this._getImageData();
        const rotatedImage = rotate ? this.rotateIfRequired(image) : image;

        // In the decoding phase, each pixel is stored in 6 bytes in YCbCr 
        // and converted to 4 bytes RGBA ImageData, that means that in this moment about
        // (4 + 6) * width * height bytes of RAM are used.
        // 10 000 000 pixels corresponds to about 95 MB of RAM. If more we should
        // reset the page to free all the memory retained by YCbCr pixels.
        // (however this very measure doesn't help reduce the peak me
Download .txt
gitextract_y7vg5pf7/

├── .gitattributes
├── .gitignore
├── .js
├── GNU_GPL_v2
├── LICENSE.md
├── README.md
├── THE_UNLICENSE
├── TRANSLATION.md
├── extension/
│   ├── background.js
│   ├── content.js
│   ├── initializer.js
│   ├── manifest_v2.json
│   ├── manifest_v3.json
│   └── viewer.html
├── library/
│   ├── .gitignore
│   ├── API.md
│   ├── CHANGELOG.md
│   ├── README.md
│   ├── app/
│   │   ├── app.css
│   │   ├── app.html
│   │   └── app.js
│   ├── assets/
│   │   ├── DjVu3Spec.djvu
│   │   ├── DjVu3Spec_5-10.djvu
│   │   ├── DjVu3Spec_bundled.djvu
│   │   ├── DjVu3Spec_contents.json
│   │   ├── DjVu3Spec_indirect/
│   │   │   ├── dict0020.iff
│   │   │   ├── dict0040.iff
│   │   │   ├── dict0060.iff
│   │   │   ├── dict0071.iff
│   │   │   ├── index.djvu
│   │   │   ├── p0001_1.djvu
│   │   │   ├── p0002.djvu
│   │   │   ├── p0003.djvu
│   │   │   ├── p0004.djvu
│   │   │   ├── p0005.djvu
│   │   │   ├── p0006.djvu
│   │   │   ├── p0007.djvu
│   │   │   ├── p0008.djvu
│   │   │   ├── p0009.djvu
│   │   │   ├── p0010.djvu
│   │   │   ├── p0011.djvu
│   │   │   ├── p0012.djvu
│   │   │   ├── p0013.djvu
│   │   │   ├── p0014.djvu
│   │   │   ├── p0015.djvu
│   │   │   ├── p0016.djvu
│   │   │   ├── p0017.djvu
│   │   │   ├── p0018.djvu
│   │   │   ├── p0019.djvu
│   │   │   ├── p0020.djvu
│   │   │   ├── p0021.djvu
│   │   │   ├── p0022.djvu
│   │   │   ├── p0023.djvu
│   │   │   ├── p0024.djvu
│   │   │   ├── p0025.djvu
│   │   │   ├── p0026.djvu
│   │   │   ├── p0027.djvu
│   │   │   ├── p0028.djvu
│   │   │   ├── p0029.djvu
│   │   │   ├── p0030.djvu
│   │   │   ├── p0031.djvu
│   │   │   ├── p0032.djvu
│   │   │   ├── p0033.djvu
│   │   │   ├── p0034.djvu
│   │   │   ├── p0035.djvu
│   │   │   ├── p0036.djvu
│   │   │   ├── p0037.djvu
│   │   │   ├── p0038.djvu
│   │   │   ├── p0039.djvu
│   │   │   ├── p0040.djvu
│   │   │   ├── p0041.djvu
│   │   │   ├── p0042.djvu
│   │   │   ├── p0043.djvu
│   │   │   ├── p0044.djvu
│   │   │   ├── p0045.djvu
│   │   │   ├── p0046.djvu
│   │   │   ├── p0047.djvu
│   │   │   ├── p0048.djvu
│   │   │   ├── p0049.djvu
│   │   │   ├── p0050.djvu
│   │   │   ├── p0051.djvu
│   │   │   ├── p0052.djvu
│   │   │   ├── p0053.djvu
│   │   │   ├── p0054.djvu
│   │   │   ├── p0055.djvu
│   │   │   ├── p0056.djvu
│   │   │   ├── p0057.djvu
│   │   │   ├── p0058.djvu
│   │   │   ├── p0059.djvu
│   │   │   ├── p0060.djvu
│   │   │   ├── p0061.djvu
│   │   │   ├── p0062.djvu
│   │   │   ├── p0063.djvu
│   │   │   ├── p0064.djvu
│   │   │   ├── p0065.djvu
│   │   │   ├── p0066.djvu
│   │   │   ├── p0067.djvu
│   │   │   ├── p0068.djvu
│   │   │   ├── p0069.djvu
│   │   │   ├── p0070.djvu
│   │   │   ├── p0071.djvu
│   │   │   ├── thum0001.thumb
│   │   │   ├── thum0002.thumb
│   │   │   ├── thum0003.thumb
│   │   │   ├── thum0004.thumb
│   │   │   ├── thum0005.thumb
│   │   │   ├── thum0006.thumb
│   │   │   ├── thum0007.thumb
│   │   │   ├── thum0008.thumb
│   │   │   ├── thum0009.thumb
│   │   │   └── thum0010.thumb
│   │   ├── big-scanned-page.djvu
│   │   ├── boy.djvu
│   │   ├── boy_and_chicken.djvu
│   │   ├── boy_jb2.djvu
│   │   ├── boy_jb2_rotate180.djvu
│   │   ├── boy_jb2_rotate270.djvu
│   │   ├── boy_jb2_rotate90.djvu
│   │   ├── carte.djvu
│   │   ├── ccitt_2.djvu
│   │   ├── century_dict/
│   │   │   ├── index08.djvu
│   │   │   ├── p6683.djvu
│   │   │   └── p6698.djvu
│   │   ├── chicken.djvu
│   │   ├── colorbook.djvu
│   │   ├── czech.djvu
│   │   ├── czech_1-3.djvu
│   │   ├── czech_indirect/
│   │   │   ├── anno0001.iff
│   │   │   ├── black_1.djvu
│   │   │   ├── dict0085.iff
│   │   │   ├── index.djvu
│   │   │   ├── p0001.djvu
│   │   │   ├── p0002.djvu
│   │   │   └── slovnik
│   │   ├── deutsch.djvu
│   │   ├── djvu3spec+.djvu
│   │   ├── happy_birthday.djvu
│   │   ├── history.djvu
│   │   ├── history_2.djvu
│   │   ├── irish.djvu
│   │   ├── links.djvu
│   │   ├── malliavin.djvu
│   │   ├── navm_fgbz.djvu
│   │   ├── polish_indirect/
│   │   │   ├── index.djvu
│   │   │   ├── shared_anno.iff
│   │   │   └── sw1-0002.djvu
│   │   ├── problem_page.djvu
│   │   ├── slow.djvu
│   │   └── vega.djvu
│   ├── debug/
│   │   ├── async.html
│   │   ├── css/
│   │   │   └── style.css
│   │   ├── examples.html
│   │   ├── index.html
│   │   ├── js/
│   │   │   ├── DjVuGlobals.js
│   │   │   ├── DjVuViewer.js
│   │   │   ├── async.js
│   │   │   ├── debug.js
│   │   │   ├── examples.js
│   │   │   ├── handler.js
│   │   │   ├── initScript.js
│   │   │   └── reloader.js
│   │   └── sync.html
│   ├── package.json
│   ├── rollup.config.js
│   ├── server.js
│   ├── src/
│   │   ├── ByteStream.js
│   │   ├── ByteStreamWriter.js
│   │   ├── DjVu.js
│   │   ├── DjVuDocument.js
│   │   ├── DjVuErrors.js
│   │   ├── DjVuPage.js
│   │   ├── DjVuWorker.js
│   │   ├── DjVuWorkerScript.js
│   │   ├── DjVuWriter.js
│   │   ├── ZPCodec.js
│   │   ├── bzz/
│   │   │   ├── BZZDecoder.js
│   │   │   └── BZZEncoder.js
│   │   ├── chunks/
│   │   │   ├── DirmChunk.js
│   │   │   ├── DjViChunk.js
│   │   │   ├── DjVuAnno.js
│   │   │   ├── DjVuPalette.js
│   │   │   ├── DjVuText.js
│   │   │   ├── IFFChunks.js
│   │   │   ├── NavmChunk.js
│   │   │   └── ThumChunk.js
│   │   ├── index.js
│   │   ├── iw44/
│   │   │   ├── IWCodecBaseClass.js
│   │   │   ├── IWDecoder.js
│   │   │   ├── IWEncoder.js
│   │   │   ├── IWImage.js
│   │   │   ├── IWImageWriter.js
│   │   │   └── IWStructures.js
│   │   ├── jb2/
│   │   │   ├── JB2Codec.js
│   │   │   ├── JB2Dict.js
│   │   │   ├── JB2Image.js
│   │   │   └── JB2Structures.js
│   │   └── methods/
│   │       ├── bundle.js
│   │       └── load.js
│   └── tests/
│       ├── embed.html
│       ├── tests.css
│       ├── tests.html
│       └── tests.js
├── package.json
└── viewer/
    ├── .gitignore
    ├── CHANGELOG.md
    ├── cypress/
    │   ├── e2e/
    │   │   ├── fullscreen_mode.cy.js
    │   │   ├── initial_screen.cy.js
    │   │   ├── menu.cy.js
    │   │   ├── mobile_version.cy.js
    │   │   ├── modal_windows.cy.js
    │   │   └── toolbar.cy.js
    │   ├── shared.js
    │   └── utils.js
    ├── cypress.config.js
    ├── index.html
    ├── jsconfig.json
    ├── package.json
    ├── public/
    │   └── manifest.json
    ├── src/
    │   ├── App.test.js
    │   ├── DjVu.js
    │   ├── DjVuViewer.jsx
    │   ├── actions/
    │   │   └── actions.js
    │   ├── components/
    │   │   ├── App.jsx
    │   │   ├── AppContext.jsx
    │   │   ├── ErrorPage.jsx
    │   │   ├── FileBlock.jsx
    │   │   ├── FileLoadingScreen.jsx
    │   │   ├── ImageBlock/
    │   │   │   ├── CanvasImage.jsx
    │   │   │   ├── ComplexImage.jsx
    │   │   │   ├── ImageBlock.jsx
    │   │   │   ├── TextLayer.jsx
    │   │   │   └── VirtualList.jsx
    │   │   ├── InitialScreen/
    │   │   │   ├── FileZone.jsx
    │   │   │   ├── InitialScreen.jsx
    │   │   │   ├── LinkBlock.jsx
    │   │   │   └── ThemeSwitcher.jsx
    │   │   ├── Language/
    │   │   │   ├── AddLanguageButton.jsx
    │   │   │   ├── IncompleteTranslationWindow.jsx
    │   │   │   ├── LanguagePanel.jsx
    │   │   │   ├── LanguageSelector.jsx
    │   │   │   └── LanguageWarningSign.jsx
    │   │   ├── LeftPanel/
    │   │   │   ├── ContentsPanel.jsx
    │   │   │   ├── LeftPanel.jsx
    │   │   │   └── TreeItem.jsx
    │   │   ├── LoadingLayer.jsx
    │   │   ├── Main.jsx
    │   │   ├── Menu.jsx
    │   │   ├── ModalWindows/
    │   │   │   ├── ErrorWindow.jsx
    │   │   │   ├── HelpWindow.jsx
    │   │   │   ├── ModalWindow.jsx
    │   │   │   ├── OptionsWindow.jsx
    │   │   │   ├── PrintDialog.jsx
    │   │   │   └── SaveDialog.jsx
    │   │   ├── StyledPrimitives.jsx
    │   │   ├── TextBlock.jsx
    │   │   ├── Toolbar/
    │   │   │   ├── ContentsButton.jsx
    │   │   │   ├── CursorModeButtonGroup.jsx
    │   │   │   ├── HideButton.jsx
    │   │   │   ├── MenuButton.jsx
    │   │   │   ├── PageNumber.jsx
    │   │   │   ├── PageNumberBlock.jsx
    │   │   │   ├── PinButton.jsx
    │   │   │   ├── RotationControl.jsx
    │   │   │   ├── ScaleGizmo.jsx
    │   │   │   ├── Toolbar.jsx
    │   │   │   └── ViewModeButtons.jsx
    │   │   ├── Translation.jsx
    │   │   ├── cssMixins.js
    │   │   ├── helpers.js
    │   │   └── misc/
    │   │       ├── CloseButton.jsx
    │   │       ├── FullPageViewButton.jsx
    │   │       ├── FullscreenButton.jsx
    │   │       ├── HelpButton.jsx
    │   │       ├── LoadingPhrase.jsx
    │   │       ├── OptionsButton.jsx
    │   │       ├── ProgressBar.jsx
    │   │       ├── SaveButton.jsx
    │   │       └── SaveNotification.jsx
    │   ├── constants/
    │   │   ├── Constants.js
    │   │   ├── actionTypes.js
    │   │   └── index.js
    │   ├── hotkeys.js
    │   ├── index.js
    │   ├── locales/
    │   │   ├── ChineseSimplified.js
    │   │   ├── English.js
    │   │   ├── French.js
    │   │   ├── Italian.js
    │   │   ├── Portuguese.js
    │   │   ├── Russian.js
    │   │   ├── Spanish.js
    │   │   ├── Swedish.js
    │   │   ├── Ukrainian.js
    │   │   └── index.js
    │   ├── reducers/
    │   │   ├── commonReducer.js
    │   │   ├── fileLoadingReducer.js
    │   │   ├── fileProcessingReducer.js
    │   │   ├── index.js
    │   │   ├── pageReducer.js
    │   │   └── printReducer.js
    │   ├── sagas/
    │   │   ├── ContinuousScrollManager.js
    │   │   ├── PageStorage.js
    │   │   ├── PagesCache.js
    │   │   ├── PrintManager.js
    │   │   └── rootSaga.js
    │   ├── store.js
    │   └── utils.js
    ├── syncLocales.js
    └── vite.config.js
Download .txt
SYMBOL INDEX (701 symbols across 82 files)

FILE: extension/background.js
  function isManifestV3 (line 7) | function isManifestV3() {
  function updateContextMenu (line 15) | function updateContextMenu() {
  function promisify (line 40) | function promisify(func) {
  function listenForMessages (line 74) | function listenForMessages() {
  function openViewerTab (line 94) | function openViewerTab(djvuUrl = null) {
  function enableFileOpeningInterception (line 98) | function enableFileOpeningInterception() {
  function applySavedOptions (line 279) | function applySavedOptions() {
  function listenForOptionChanges (line 283) | function listenForOptionChanges() {
  function main (line 293) | function main() {

FILE: extension/content.js
  function includeScripts (line 5) | function includeScripts() {
  function processTag (line 11) | function processTag(tag, src) {
  function processEmbeds (line 57) | function processEmbeds() { // should be processed after objects, since e...

FILE: library/app/app.js
  function initDjVuApplication (line 5) | function initDjVuApplication() {
  function reset (line 19) | function reset(event) {
  function metaDataFuncPrepare (line 33) | function metaDataFuncPrepare() {
  function metaDataFunc (line 42) | function metaDataFunc() {
  function pictureFuncPrepare (line 75) | function pictureFuncPrepare() {
  function readImagesAndCreateDocument (line 87) | function readImagesAndCreateDocument() {
  function createPicDocument (line 131) | function createPicDocument(imageArray) {
  function sliceFuncPrepare (line 150) | function sliceFuncPrepare() {
  function sliceFunc (line 187) | function sliceFunc() {

FILE: library/debug/js/DjVuGlobals.js
  function writeln (line 7) | function writeln(str) {
  function write (line 12) | function write(str) {
  function clear (line 15) | function clear() {
  method init (line 25) | init() {
  method clearCanvas (line 37) | clearCanvas() {
  method loadFile (line 45) | loadFile(url) {
  method drawImage (line 58) | drawImage(image, dpi) {
  method drawImageNS (line 98) | drawImageNS(image, dpi) {
  method drawImageSmooth (line 118) | drawImageSmooth(image, dpi) {
  method drawBitmapOnImageCanvas (line 136) | drawBitmapOnImageCanvas(bm, x, y, jb2Image) {
  function downScaleCanvas (line 164) | function downScaleCanvas(cv, scale) {

FILE: library/debug/js/DjVuViewer.js
  class DjVuViewer (line 8) | class DjVuViewer {
    method constructor (line 9) | constructor(selector, worker) {
    method reset (line 44) | reset() {
    method changeScale (line 57) | changeScale() {
    method renderEnteredPageByEnter (line 62) | renderEnteredPageByEnter(e) {
    method renderEnteredPage (line 68) | renderEnteredPage(e) {
    method curPage (line 73) | get curPage() {
    method curPage (line 77) | set curPage(value) {
    method showNextPage (line 81) | showNextPage() {
    method showPrevPage (line 85) | showPrevPage() {
    method lockNavButtons (line 89) | lockNavButtons() {
    method unlockNavButtons (line 94) | unlockNavButtons() {
    method getScaledImageWidth (line 99) | getScaledImageWidth() {
    method renderCurPage (line 103) | renderCurPage() {
    method loadFile (line 120) | loadFile(url) {
    method loadDjVu (line 133) | loadDjVu(url) { // debug functions
    method loadDjVuFromBuffer (line 143) | loadDjVuFromBuffer(buffer) {
    method relockNavButtons (line 152) | relockNavButtons() {
    method setPage (line 162) | setPage(page) {
    method _switchToImageMode (line 178) | _switchToImageMode() {
    method _switchToCanvasMode (line 184) | _switchToCanvasMode() {
    method getImageDataURL (line 190) | getImageDataURL() {
    method getImageDataURLAsync (line 197) | getImageDataURLAsync() {
    method drawImageViaImg (line 211) | drawImageViaImg() {
    method rescaleImageOnImg (line 217) | rescaleImageOnImg() {
    method drawImageOnCanvas (line 221) | drawImageOnCanvas() {

FILE: library/debug/js/async.js
  function initViewer (line 48) | function initViewer() {
  function renderDjVu (line 54) | async function renderDjVu() {
  function redrawPage (line 67) | async function redrawPage() {
  function loadPicture (line 81) | function loadPicture() {
  function readPicture (line 93) | function readPicture(buffer) {
  function showMetaData (line 120) | function showMetaData(buffer) {
  function readDjvu (line 127) | function readDjvu(buf) {
  function main (line 159) | function main(files) {
  function testFunc (line 181) | function testFunc(doc1, doc2) {

FILE: library/debug/js/debug.js
  class DebugTimer (line 9) | class DebugTimer {
    method constructor (line 10) | constructor() {
    method start (line 13) | start(id) {
    method end (line 27) | end(id, print) {
    method toString (line 39) | toString() {
  class PseudoZP (line 51) | class PseudoZP {
    method constructor (line 52) | constructor() {
    method encode (line 56) | encode(bit, ctx, n) {
    method decode (line 75) | decode(ctx, n) {
    method eflush (line 91) | eflush() {
  function tmpFunc (line 95) | function tmpFunc(doc) {
  function ZPtest (line 117) | function ZPtest() {
  function BZZtest (line 150) | function BZZtest() {

FILE: library/debug/js/examples.js
  function syncInterfaceExamples (line 10) | async function syncInterfaceExamples(djvuDocument) {
  function asyncInterfaceExamples (line 67) | async function asyncInterfaceExamples(djvuWorker) {
  function main (line 145) | async function main() {

FILE: library/debug/js/handler.js
  function write (line 5) | function write(str) {

FILE: library/debug/js/initScript.js
  function saveStringAsFile (line 34) | function saveStringAsFile(string) {
  function saveImage (line 42) | function saveImage(imageData) {
  function saveStringAsBinFile (line 54) | function saveStringAsBinFile(string) {
  function rerun (line 66) | function rerun() {
  function loadDjVu (line 86) | function loadDjVu() {
  function loadPicture (line 100) | function loadPicture() {
  function readPicture (line 106) | function readPicture(buffer) {
  function readDjvu (line 139) | async function readDjvu(buf) {
  function redrawPage (line 172) | async function redrawPage() {
  function prepareIframe (line 205) | function prepareIframe() {
  function splitDjvu (line 254) | function splitDjvu(buf) {
  function main (line 265) | function main(files) {
  function testFunc (line 287) | function testFunc(doc1, doc2) {

FILE: library/debug/js/reloader.js
  function setConnection (line 6) | function setConnection() {

FILE: library/src/ByteStream.js
  class ByteStream (line 10) | class ByteStream {
    method constructor (line 11) | constructor(buffer, offsetx, length) {
    method length (line 24) | get length() { return this._length; }
    method buffer (line 27) | get buffer() { return this._buffer; }
    method getUint8Array (line 30) | getUint8Array(length = this.remainingLength()) {
    method toUint8Array (line 37) | toUint8Array() {
    method remainingLength (line 41) | remainingLength() {
    method reset (line 45) | reset() {
    method byte (line 49) | byte() { // the function is used inside other codecs (look at ZPCodec)
    method getInt8 (line 57) | getInt8() {
    method getInt16 (line 60) | getInt16() {
    method getUint16 (line 65) | getUint16() {
    method getInt32 (line 70) | getInt32() {
    method getUint8 (line 75) | getUint8() {
    method getInt24 (line 79) | getInt24() {
    method getUint24 (line 84) | getUint24() {
    method jump (line 88) | jump(length) {
    method setOffset (line 93) | setOffset(offset) {
    method readStr4 (line 97) | readStr4() { // used to read chunk names, just ASCII characters
    method readStrNT (line 101) | readStrNT() {
    method readStrUTF (line 111) | readStrUTF(byteLength) {
    method fork (line 115) | fork(length = this.remainingLength()) {
    method clone (line 119) | clone() {
    method isEmpty (line 123) | isEmpty() {

FILE: library/src/ByteStreamWriter.js
  class ByteStreamWriter (line 6) | class ByteStreamWriter {
    method constructor (line 7) | constructor(length = 0) {
    method assignBufferFromMemory (line 22) | assignBufferFromMemory() {
    method reset (line 30) | reset() {
    method saveOffsetMark (line 35) | saveOffsetMark(mark) {
    method writeByte (line 40) | writeByte(byte) {
    method writeStr (line 46) | writeStr(str) {
    method writeInt32 (line 51) | writeInt32(val) {
    method rewriteInt32 (line 61) | rewriteInt32(off, val) {
    method rewriteSize (line 74) | rewriteSize(offmark) {
    method getBuffer (line 80) | getBuffer() {
    method checkOffset (line 87) | checkOffset(requiredBytesNumber = 0) {
    method _expand (line 95) | _expand(requiredBytesNumber) {
    method jump (line 104) | jump(length) {
    method writeByteStream (line 113) | writeByteStream(bs) {
    method writeArray (line 117) | writeArray(arr) {
    method writeBuffer (line 123) | writeBuffer(buffer) {
    method writeStrNT (line 127) | writeStrNT(str) {
    method writeInt16 (line 132) | writeInt16(val) {
    method writeUint16 (line 139) | writeUint16(val) {
    method writeInt24 (line 146) | writeInt24(val) {

FILE: library/src/DjVu.js
  function pLimit (line 7) | function pLimit(limit = 4) {
  function loadFileViaXHR (line 33) | function loadFileViaXHR(url, responseType = 'arraybuffer') {
  method decode (line 45) | decode(utf8array) {
  function createStringFromUtf8Array (line 51) | function createStringFromUtf8Array(utf8array) {
  function utf8ToCodePoints (line 64) | function utf8ToCodePoints(utf8array) {
  function codePointsToUtf8 (line 104) | function codePointsToUtf8(codePoints) {
  function stringToCodePoints (line 127) | function stringToCodePoints(str) {

FILE: library/src/DjVuDocument.js
  constant MEMORY_LIMIT (line 19) | const MEMORY_LIMIT = 50 * 1024 * 1024;
  class DjVuDocument (line 21) | class DjVuDocument {
    method constructor (line 22) | constructor(arraybuffer, { baseUrl = null, memoryLimit = MEMORY_LIMIT ...
    method _initMultiPageDocument (line 60) | _initMultiPageDocument() { // for FORMDJVM
    method _readMetaDataChunk (line 79) | _readMetaDataChunk() { // DIRM chunk
    method _readContentsChunkIfExists (line 90) | _readContentsChunkIfExists() { // NAVM chunk
    method _parseComponents (line 102) | _parseComponents() {
    method getPagesSizes (line 135) | getPagesSizes() {
    method isBundled (line 147) | isBundled() {
    method getPagesQuantity (line 151) | getPagesQuantity() {
    method getContents (line 156) | getContents() {
    method getMemoryUsage (line 160) | getMemoryUsage() {
    method getMemoryLimit (line 164) | getMemoryLimit() {
    method setMemoryLimit (line 168) | setMemoryLimit(limit = MEMORY_LIMIT) {
    method getPageNumberByUrl (line 172) | getPageNumberByUrl(url) {
    method releaseMemoryIfRequired (line 189) | releaseMemoryIfRequired(preservedDependencies = null) {
    method _getUrlByPageNumber (line 220) | _getUrlByPageNumber(number) {
    method getPage (line 224) | async getPage(number) {
    method _loadDependencies (line 263) | async _loadDependencies(dependencies, pageNumber = null) {
    method getPageUnsafe (line 281) | getPageUnsafe(number) {
    method resetLastRequestedPage (line 285) | resetLastRequestedPage() {
    method countFiles (line 291) | countFiles() {
    method toString (line 312) | toString(html) {
    method createObjectURL (line 337) | createObjectURL() {
    method slice (line 346) | slice(from = 1, to = this.pages.length) {
    method concat (line 429) | static concat(doc1, doc2) {

FILE: library/src/DjVuErrors.js
  class DjVuError (line 5) | class DjVuError {
    method constructor (line 6) | constructor(code, message, additionalData = null) {
  class IncorrectFileFormatDjVuError (line 13) | class IncorrectFileFormatDjVuError extends DjVuError {
    method constructor (line 14) | constructor() {
  class NoSuchPageDjVuError (line 19) | class NoSuchPageDjVuError extends DjVuError {
    method constructor (line 20) | constructor(pageNumber) {
  class CorruptedFileDjVuError (line 26) | class CorruptedFileDjVuError extends DjVuError {
    method constructor (line 27) | constructor(message = "", data = null) {
  class UnableToTransferDataDjVuError (line 32) | class UnableToTransferDataDjVuError extends DjVuError {
    method constructor (line 33) | constructor(tasks) {
  class IncorrectTaskDjVuError (line 42) | class IncorrectTaskDjVuError extends DjVuError {
    method constructor (line 43) | constructor(task) {
  class NoBaseUrlDjVuError (line 49) | class NoBaseUrlDjVuError extends DjVuError {
    method constructor (line 50) | constructor() {
  function getErrorMessageByData (line 58) | function getErrorMessageByData(data) {
  class UnsuccessfulRequestDjVuError (line 72) | class UnsuccessfulRequestDjVuError extends DjVuError {
    method constructor (line 73) | constructor(xhr, data = { pageNumber: null, dependencyId: null }) {
  class NetworkDjVuError (line 93) | class NetworkDjVuError extends DjVuError {
    method constructor (line 94) | constructor(data = { pageNumber: null, dependencyId: null, url: null }) {

FILE: library/src/DjVuPage.js
  function createBlobFromImageData (line 15) | async function createBlobFromImageData(imageData) {
  class DjVuPage (line 31) | class DjVuPage extends CompositeChunk {
    method constructor (line 37) | constructor(bs, getINCLChunkCallback) {
    method reset (line 43) | reset() {
    method getDpi (line 85) | getDpi() {
    method getHeight (line 93) | getHeight() {
    method getWidth (line 97) | getWidth() {
    method createPngObjectUrl (line 101) | async createPngObjectUrl() {
    method getDependencies (line 124) | getDependencies() {
    method init (line 153) | init() {
    method getRotation (line 215) | getRotation() {
    method rotateIfRequired (line 224) | rotateIfRequired(imageData) {
    method getImageData (line 261) | getImageData(rotate = true) {
    method _getImageData (line 282) | _getImageData() {
    method createImageFromMaskImageAndPixelMaps (line 355) | createImageFromMaskImageAndPixelMaps(maskImage, fgpixelmap, bgpixelmap...
    method createImageFromMaskImageAndBackgroundPixelMap (line 382) | createImageFromMaskImageAndBackgroundPixelMap(coloredMaskImage, bgpixe...
    method decodeForeground (line 404) | decodeForeground() {
    method decodeBackground (line 419) | decodeBackground(isOnlyFirstChunk = false) {
    method decode (line 452) | decode() {
    method getBackgroundImageData (line 479) | getBackgroundImageData() {
    method getForegroundImageData (line 496) | getForegroundImageData() {
    method getMaskImageData (line 509) | getMaskImageData() {
    method getText (line 514) | getText() {
    method getPageTextZone (line 520) | getPageTextZone() { // returns the top text zone of the whole page (wh...
    method getNormalizedTextZones (line 526) | getNormalizedTextZones() { // returns a flat array of zones without ne...
    method toString (line 531) | toString() {

FILE: library/src/DjVuWorker.js
  function getLinkToTheWholeLibrary (line 12) | function getLinkToTheWholeLibrary() {
  class DjVuWorker (line 26) | class DjVuWorker {
    method constructor (line 27) | constructor(path = getLinkToTheWholeLibrary()) {
    method reset (line 32) | reset() {
    method registerHyperCallback (line 49) | registerHyperCallback(func) {
    method unregisterHyperCallback (line 55) | unregisterHyperCallback(id) {
    method terminate (line 59) | terminate() {
    method doc (line 63) | get doc() {
    method errorHandler (line 67) | errorHandler(event) {
    method cancelTask (line 71) | cancelTask(promise) {
    method dropCurrentTask (line 79) | dropCurrentTask() {
    method emptyTaskQueue (line 85) | emptyTaskQueue() {
    method cancelAllTasks (line 89) | cancelAllTasks() {
    method createNewPromise (line 98) | createNewPromise(commandObj, transferList = undefined) {
    method prepareCommandObject (line 114) | prepareCommandObject(commandObj) {
    method runNextTask (line 141) | runNextTask() {
    method isTaskInProcess (line 162) | isTaskInProcess(promise) {
    method isTaskInQueue (line 166) | isTaskInQueue(promise) {
    method processAction (line 170) | processAction(obj) { // usually progress messages, not the commands' f...
    method messageHandler (line 181) | messageHandler({ data: obj }) {
    method restoreValueAfterTransfer (line 245) | restoreValueAfterTransfer(value) {
    method run (line 255) | run(...tasks) {
    method revokeObjectURL (line 264) | revokeObjectURL(url) { // if an ObjectURL was created inside a worker ...
    method startMultiPageDocument (line 271) | startMultiPageDocument(slicenumber, delayInit, grayscale) {
    method addPageToDocument (line 280) | addPageToDocument(imageData) {
    method endMultiPageDocument (line 292) | endMultiPageDocument() {
    method createDocument (line 296) | createDocument(buffer, options) {
    method createDocumentFromPictures (line 300) | createDocumentFromPictures(imageArray, slicenumber, delayInit, graysca...
  class DjVuWorkerTask (line 323) | class DjVuWorkerTask {
    method instance (line 333) | static instance(worker, funcs = [], args = []) {
    method emptyFunc (line 352) | static emptyFunc() { }

FILE: library/src/DjVuWorkerScript.js
  function initWorker (line 8) | function initWorker() {

FILE: library/src/DjVuWriter.js
  class DjVuWriter (line 10) | class DjVuWriter {
    method constructor (line 11) | constructor(length) {
    method startDJVM (line 15) | startDJVM() {
    method writeDirmChunk (line 21) | writeDirmChunk(dirm) {
    method offset (line 67) | get offset() {
    method writeByte (line 71) | writeByte(byte) {
    method writeStr (line 76) | writeStr(str) {
    method writeInt32 (line 81) | writeInt32(val) {
    method writeFormChunkBS (line 86) | writeFormChunkBS(bs) {
    method writeFormChunkBuffer (line 97) | writeFormChunkBuffer(buffer) {
    method writeChunk (line 107) | writeChunk(chunk) {
    method getBuffer (line 120) | getBuffer() {

FILE: library/src/ZPCodec.js
  class ZPEncoder (line 3) | class ZPEncoder {
    method constructor (line 4) | constructor(bsw) {
    method outbit (line 18) | outbit(bit) {
    method zemit (line 36) | zemit(b) {
    method encode (line 68) | encode(bit, ctx, n) {
    method IWencode (line 118) | IWencode(bit) {
    method _ptencode (line 124) | _ptencode(bit, z) {
    method eflush (line 151) | eflush() {
  class ZPDecoder (line 176) | class ZPDecoder {
    method constructor (line 177) | constructor(bs) {
    method preload (line 204) | preload() {
    method ffz (line 213) | ffz(x) {
    method decode (line 218) | decode(ctx, n) {
    method IWdecode (line 289) | IWdecode() {
    method _ptdecode (line 293) | _ptdecode(z) {

FILE: library/src/bzz/BZZDecoder.js
  class BZZDecoder (line 5) | class BZZDecoder {
    method constructor (line 6) | constructor(zp) {
    method decode_raw (line 18) | decode_raw(bits) {
    method decode_binary (line 28) | decode_binary(ctxoff, bits) {
    method _decode (line 41) | _decode() {
    method getByteStream (line 269) | getByteStream() {
    method decodeByteStream (line 288) | static decodeByteStream(bs) {

FILE: library/src/bzz/BZZEncoder.js
  class BZZEncoder (line 7) | class BZZEncoder {
    method constructor (line 8) | constructor(zp) {
    method blocksort (line 22) | blocksort(arr) {
    method encode_raw (line 64) | encode_raw(bits, x) {
    method encode_binary (line 75) | encode_binary(cxtoff, bits, x) {
    method encode (line 88) | encode(buffer) {

FILE: library/src/chunks/DirmChunk.js
  class DIRMChunk (line 8) | class DIRMChunk extends IFFChunk {
    method constructor (line 9) | constructor(bs) {
    method isPageIndex (line 50) | isPageIndex(i) {
    method isThumbnailIndex (line 54) | isThumbnailIndex(i) {
    method getPageNameByItsNumber (line 62) | getPageNameByItsNumber(number) {
    method getPageNumberByItsId (line 66) | getPageNumberByItsId(id) {
    method getComponentNameByItsId (line 71) | getComponentNameByItsId(id) {
    method getPagesQuantity (line 75) | getPagesQuantity() {
    method getFilesQuantity (line 79) | getFilesQuantity() {
    method getMetadataStringByIndex (line 83) | getMetadataStringByIndex(i) {
    method toString (line 90) | toString() {

FILE: library/src/chunks/DjViChunk.js
  class DjViChunk (line 5) | class DjViChunk extends CompositeChunk {
    method constructor (line 6) | constructor(bs) {
    method init (line 12) | init() {
    method toString (line 41) | toString() {

FILE: library/src/chunks/DjVuAnno.js
  class DjVuAnno (line 3) | class DjVuAnno extends IFFChunk { }

FILE: library/src/chunks/DjVuPalette.js
  class DjVuPalette (line 5) | class DjVuPalette extends IFFChunk {
    method constructor (line 8) | constructor(bs) {
    method getDataSize (line 40) | getDataSize() {
    method getPixelByBlitIndex (line 44) | getPixelByBlitIndex(index) {
    method toString (line 54) | toString() {

FILE: library/src/chunks/DjVuText.js
  class DjVuText (line 29) | class DjVuText extends IFFChunk {
    method constructor (line 30) | constructor(bs) {
    method decode (line 37) | decode() {
    method decodeZone (line 59) | decodeZone(parent = null, prev = null) {
    method getText (line 102) | getText() {
    method getPageZone (line 109) | getPageZone() {
    method getNormalizedZones (line 115) | getNormalizedZones() {
    method toString (line 155) | toString() {

FILE: library/src/chunks/IFFChunks.js
  class IFFChunk (line 6) | class IFFChunk {
    method constructor (line 9) | constructor(bs) {
    method toString (line 14) | toString() {
  class CompositeChunk (line 19) | class CompositeChunk extends IFFChunk {
    method constructor (line 22) | constructor(bs) {
    method toString (line 27) | toString(innerString = '') {
  class ColorChunk (line 32) | class ColorChunk extends IFFChunk {
    method constructor (line 35) | constructor(bs) {
    method toString (line 39) | toString() {
  class INFOChunk (line 47) | class INFOChunk extends IFFChunk {
    method constructor (line 50) | constructor(bs) {
    method toString (line 81) | toString() {
  class ColorChunkDataHeader (line 98) | class ColorChunkDataHeader {
    method constructor (line 101) | constructor(bs) {
    method toString (line 122) | toString() {
  class INCLChunk (line 127) | class INCLChunk extends IFFChunk {
    method constructor (line 130) | constructor(bs) {
    method toString (line 134) | toString() {
  class CIDaChunk (line 145) | class CIDaChunk extends INCLChunk { }
  class ErrorChunk (line 147) | class ErrorChunk {
    method constructor (line 148) | constructor(id, e) {
    method toString (line 153) | toString() {

FILE: library/src/chunks/NavmChunk.js
  class NAVMChunk (line 16) | class NAVMChunk extends IFFChunk {
    method constructor (line 17) | constructor(bs) {
    method getContents (line 27) | getContents() {
    method decode (line 32) | decode() {
    method decodeBookmark (line 48) | decodeBookmark(bs) {
    method toString (line 68) | toString() {

FILE: library/src/chunks/ThumChunk.js
  class ThumChunk (line 3) | class ThumChunk extends CompositeChunk { }

FILE: library/src/iw44/IWCodecBaseClass.js
  class IWCodecBaseClass (line 12) | class IWCodecBaseClass {
    method constructor (line 13) | constructor() {
    method getBandBuckets (line 38) | getBandBuckets(band) {
    method is_null_slice (line 43) | is_null_slice() {
    method finish_code_slice (line 65) | finish_code_slice() {

FILE: library/src/iw44/IWDecoder.js
  class IWDecoder (line 5) | class IWDecoder extends IWCodecBaseClass {
    method constructor (line 7) | constructor() {
    method init (line 11) | init(imageinfo) {
    method decodeSlice (line 18) | decodeSlice(zp, imageinfo) {
    method previouslyActiveCoefficientDecodingPass (line 40) | previouslyActiveCoefficientDecodingPass(block) {
    method newlyActiveCoefficientDecodingPass (line 70) | newlyActiveCoefficientDecodingPass(block, band) {
    method bucketDecodingPass (line 111) | bucketDecodingPass(block, band) {
    method blockBandDecodingPass (line 143) | blockBandDecodingPass() {
    method preliminaryFlagComputation (line 156) | preliminaryFlagComputation(block) {
    method getBytemap (line 194) | getBytemap() {
    method inverseWaveletTransform (line 242) | inverseWaveletTransform(bitmap) {

FILE: library/src/iw44/IWEncoder.js
  class IWEncoder (line 4) | class IWEncoder extends IWCodecBaseClass {
    method constructor (line 6) | constructor(bytemap) {
    method inverseWaveletTransform (line 17) | inverseWaveletTransform(bytemap) {
    method filter_fv (line 28) | filter_fv(s, bitmap) {
    method filter_fh (line 74) | filter_fh(s, bitmap) {
    method createBlocks (line 117) | createBlocks(bitmap) {
    method encodeSlice (line 154) | encodeSlice(zp) {
    method previouslyActiveCoefficientEncodingPass (line 173) | previouslyActiveCoefficientEncodingPass(block, eblock) {
    method newlyActiveCoefficientEncodingPass (line 201) | newlyActiveCoefficientEncodingPass(block, eblock) {
    method bucketEncodingPass (line 245) | bucketEncodingPass(eblock) {
    method blockBandEncodingPass (line 275) | blockBandEncodingPass() {
    method preliminaryFlagComputation (line 287) | preliminaryFlagComputation(block, eblock) {

FILE: library/src/iw44/IWImage.js
  class IWImage (line 5) | class IWImage {
    method constructor (line 6) | constructor() {
    method resetCodecs (line 12) | resetCodecs() {
    method decodeChunk (line 19) | decodeChunk(zp, header) {
    method createPixelmap (line 40) | createPixelmap() {
    method getImage (line 63) | getImage() {

FILE: library/src/iw44/IWImageWriter.js
  class IWImageWriter (line 8) | class IWImageWriter {
    method constructor (line 10) | constructor(slicenumber, delayInit, grayscale) {
    method width (line 20) | get width() {
    method height (line 23) | get height() {
    method startMultiPageDocument (line 27) | startMultiPageDocument() {
    method addPageToDocument (line 41) | addPageToDocument(imageData) {
    method endMultiPageDocument (line 51) | endMultiPageDocument() {
    method createMultiPageDocument (line 64) | createMultiPageDocument(imageArray) {
    method writeImagePage (line 99) | writeImagePage(bsw, imageData) {
    method createOnePageDocument (line 145) | createOnePageDocument(imageData) {
    method RGBtoY (line 159) | RGBtoY(imageData) {
    method RGBtoCb (line 194) | RGBtoCb(imageData) {
    method RGBtoCr (line 223) | RGBtoCr(imageData) {

FILE: library/src/iw44/IWStructures.js
  function _normalize (line 1) | function _normalize(val) {
  class LazyPixelmap (line 11) | class LazyPixelmap {
    method constructor (line 12) | constructor(ybytemap, cbbytemap, crbytemap) {
    method writeGrayScalePixel (line 20) | writeGrayScalePixel(index, pixelArray, pixelIndex) {
    method writeColoredPixel (line 27) | writeColoredPixel(index, pixelArray, pixelIndex) {
  class Pixelmap (line 41) | class Pixelmap {
    method constructor (line 42) | constructor(ybytemap, cbbytemap, crbytemap) {
    method _constructGrayScalePixelMap (line 57) | _constructGrayScalePixelMap(yArray) {
    method _constructColorfulPixelMap (line 63) | _constructColorfulPixelMap(yArray, cbArray, crArray) {
    method writePixel (line 80) | writePixel(index, pixelArray, pixelIndex) {
  class LinearBytemap (line 125) | class LinearBytemap {
    method constructor (line 126) | constructor(width, height) {
    method get (line 131) | get(i, j) {
    method set (line 135) | set(i, j, val) {
    method sub (line 139) | sub(i, j, val) {
    method add (line 143) | add(i, j, val) {
  class Bytemap (line 149) | class Bytemap extends Array {
    method constructor (line 150) | constructor(width, height) {
  class Block (line 161) | class Block {
    method constructor (line 162) | constructor(buffer, offset, withBuckets = false) {
    method setBucketCoef (line 174) | setBucketCoef(bucketNumber, index, value) {
    method getBucketCoef (line 178) | getBucketCoef(bucketNumber, index) {
    method getCoef (line 182) | getCoef(n) {
    method setCoef (line 186) | setCoef(n, val) {
    method createBlockArray (line 194) | static createBlockArray(length) {
  class BlockMemoryManager (line 204) | class BlockMemoryManager {
    method constructor (line 205) | constructor() {
    method ensureBuffer (line 212) | ensureBuffer() {
    method allocateBucket (line 221) | allocateBucket() {
  class LazyBlock (line 230) | class LazyBlock {
    method constructor (line 231) | constructor(memoryManager) {
    method setBucketCoef (line 236) | setBucketCoef(bucketNumber, index, value) {
    method getBucketCoef (line 243) | getBucketCoef(bucketNumber, index) {
    method getCoef (line 247) | getCoef(n) {
    method setCoef (line 251) | setCoef(n, val) {
    method createBlockArray (line 259) | static createBlockArray(length) {

FILE: library/src/jb2/JB2Codec.js
  class JB2Codec (line 5) | class JB2Codec extends IFFChunk {
    method constructor (line 6) | constructor(bs) {
    method resetNumContexts (line 15) | resetNumContexts() {
    method decodeNum (line 80) | decodeNum(low, high, numctx) { // this implementation was copied from ...
    method decodeBitmap (line 134) | decodeBitmap(width, height) {
    method getCtxIndex (line 145) | getCtxIndex(bm, i, j) {
    method decodeBitmapRef (line 165) | decodeBitmapRef(width, height, mbm) {
    method getCtxIndexRef (line 178) | getCtxIndexRef(cbm, mbm, alignInfo, i, j) {
    method alignBitmaps (line 203) | alignBitmaps(cbm, mbm) {
    method decodeComment (line 217) | decodeComment() {
    method drawBitmap (line 228) | drawBitmap(bm) {

FILE: library/src/jb2/JB2Dict.js
  class JB2Dict (line 3) | class JB2Dict extends JB2Codec {
    method constructor (line 4) | constructor(bs) {
    method decode (line 10) | decode(djbz) {

FILE: library/src/jb2/JB2Image.js
  class JB2Image (line 5) | class JB2Image extends JB2Codec {
    method constructor (line 6) | constructor(bs) {
    method addBlit (line 20) | addBlit(bitmap, x, y) {
    method init (line 25) | init() {
    method toString (line 53) | toString() {
    method decode (line 59) | decode(djbz) {
    method decodeAbsoluteLocationCoords (line 185) | decodeAbsoluteLocationCoords(width, height) {
    method decodeSymbolCoords (line 194) | decodeSymbolCoords(width, height) {
    method copyToBitmap (line 222) | copyToBitmap(bm, x, y) {
    method getBitmap (line 236) | getBitmap() {
    method getMaskImage (line 243) | getMaskImage() {
    method getImage (line 272) | getImage(palette = null, isMarkMaskPixels = false) {
    method getImageFromBitmap (line 306) | getImageFromBitmap() { // debug function mostly

FILE: library/src/jb2/JB2Structures.js
  class Bitmap (line 1) | class Bitmap {
    method constructor (line 2) | constructor(width, height) {
    method getBits (line 9) | getBits(i, j, bitNumber) {
    method get (line 30) | get(i, j) {
    method set (line 41) | set(i, j) { // сделать "пиксель" черным
    method hasRow (line 50) | hasRow(r) {
    method removeEmptyEdges (line 54) | removeEmptyEdges() {
  class NumContext (line 114) | class NumContext {
    method constructor (line 115) | constructor() {
    method left (line 121) | get left() {
    method right (line 128) | get right() {
  class Baseline (line 137) | class Baseline {
    method constructor (line 138) | constructor() {
    method add (line 144) | add(val) {
    method getVal (line 151) | getVal() { // возвращает медианное значение
    method fill (line 164) | fill(val) { // инициализируем все 3 значения положением 1 символа на с...

FILE: library/src/methods/bundle.js
  function bundle (line 9) | async function bundle(progressCallback = () => { }) {

FILE: library/src/methods/load.js
  function loadByteStream (line 15) | async function loadByteStream(url, errorData = {}) {
  function checkAndCropByteStream (line 31) | function checkAndCropByteStream(bs, compositeChunkId = null, errorData =...
  function loadPage (line 54) | async function loadPage(number, url) {
  function loadPageDependency (line 60) | async function loadPageDependency(id, name, baseUrl, pageNumber = null) {
  function loadThumbnail (line 66) | async function loadThumbnail(url, id = null) {

FILE: library/tests/tests.js
  function createBaseUrl (line 7) | function createBaseUrl(url) {
  function runAllTests (line 16) | async function runAllTests() {
  method renderImageData (line 71) | renderImageData(imageData) {
  method writeLog (line 79) | writeLog(message, color = "black") {
  method endTestBlock (line 87) | endTestBlock() {
  method getHashOfArray (line 91) | getHashOfArray(array) {
  method getImageDataByImageURI (line 102) | getImageDataByImageURI(imageURI, rotate = 0) {
  method compareArrayBuffers (line 131) | compareArrayBuffers(canonicBuffer, resultBuffer) {
  method compareImageData (line 146) | compareImageData(canonicImageData, resultImageData) {
  method _imageTest (line 210) | async _imageTest(djvuName, pageNumber, imageName = null, hash = null, ro...
  method _imageTestX (line 220) | async _imageTestX({ djvuUrl, baseUrl = null, pageNumber, imageUrl = null...
  method _sliceTest (line 251) | async _sliceTest(source, from, to, result) {
  method _testText (line 263) | async _testText(djvuUrl, pageNumber, txtUrl) {
  method _testTextUtf8 (line 281) | async _testTextUtf8(djvuUrl, pageNumber, txtUrl) {
  method _testTextZones (line 297) | async _testTextZones(djvuUrl, pageNumber, txtUrl, isNormalized = false) {
  method testIncorrectFileFormatError (line 318) | testIncorrectFileFormatError() {
  method testNoSuchPageError (line 333) | async testNoSuchPageError() {
  method testMetaDataOfDocWithShortINFOChunk (line 349) | async testMetaDataOfDocWithShortINFOChunk() {
  method testPageTextZone (line 353) | testPageTextZone() {
  method testNormalizedTextZones (line 357) | testNormalizedTextZones() {
  method testContents (line 361) | async testContents() {
  method testPageUrlWithLeadingZero (line 376) | async testPageUrlWithLeadingZero() {
  method testGetPageNumberByUrl (line 391) | async testGetPageNumberByUrl() {
  method testCancelAllWorkerTasks (line 409) | async testCancelAllWorkerTasks() {
  method testCancelOneWorkerTask (line 430) | async testCancelOneWorkerTask() {
  method testGetEnglishText (line 451) | testGetEnglishText() {
  method testGetCzechText (line 455) | testGetCzechText() {
  method testGetIncorrectlyEncodedUtf8Text (line 459) | testGetIncorrectlyEncodedUtf8Text() {
  method testCreateDocumentFromPictures (line 463) | testCreateDocumentFromPictures() {
  method testBundleDocument (line 480) | async testBundleDocument() {
  method testSliceDocument (line 494) | testSliceDocument() {
  method testSliceDocumentWithAnnotations (line 498) | testSliceDocumentWithAnnotations() {
  method testSliceDocumentWithCyrillicIds (line 502) | testSliceDocumentWithCyrillicIds() {
  method testIndirectDjVu (line 506) | async testIndirectDjVu() {
  method testOpenIndirectDjVuPageDirectly (line 568) | testOpenIndirectDjVuPageDirectly() {
  method testOpenIndirectDjVuWithEmptyDjVi (line 578) | testOpenIndirectDjVuWithEmptyDjVi() {
  method testPageWithEmptyLastChunk (line 588) | testPageWithEmptyLastChunk() {
  method testGrayscaleBG44 (line 597) | testGrayscaleBG44() {
  method testColorBG44 (line 601) | testColorBG44() {
  method testJB2Pure (line 605) | testJB2Pure() {
  method testRotate90 (line 609) | testRotate90() {
  method testRotate180 (line 613) | testRotate180() {
  method testRotate270 (line 617) | testRotate270() {
  method testJB2WithBitOfBackground (line 621) | testJB2WithBitOfBackground() {
  method testJB2WhereRemovingOfEmptyEdgesOfBitmapsBeforeAddingToDictRequired (line 625) | testJB2WhereRemovingOfEmptyEdgesOfBitmapsBeforeAddingToDictRequired() {
  method testFGbzColoredMask (line 629) | testFGbzColoredMask() {
  method testPageWithCyrillicId (line 633) | testPageWithCyrillicId() {
  method testEmptyPage (line 637) | async testEmptyPage() {
  method testDeutschBaseline (line 646) | testDeutschBaseline() { // документ в котором Baseline считался неправил...
  method testNewJB2SymbolWithEmptyEdges (line 655) | testNewJB2SymbolWithEmptyEdges() {
  method testFileWith2BZZEncodedBlocks (line 664) | testFileWith2BZZEncodedBlocks() {

FILE: viewer/cypress.config.js
  method setupNodeEvents (line 9) | setupNodeEvents(on, config) {}

FILE: viewer/cypress/shared.js
  function helpWindowShouldBeOpen (line 3) | function helpWindowShouldBeOpen() {
  function optionsWindowShouldBeOpen (line 11) | function optionsWindowShouldBeOpen() {
  function closeModalWindow (line 19) | function closeModalWindow() {
  function initialScreenShouldBeVisible (line 24) | function initialScreenShouldBeVisible() {

FILE: viewer/cypress/utils.js
  function loadDocument (line 34) | function loadDocument() {

FILE: viewer/src/DjVuViewer.jsx
  class DjVuViewer (line 18) | class DjVuViewer extends EventEmitter {
    method getAvailableLanguages (line 27) | static getAvailableLanguages() {
    method constructor (line 35) | constructor(config = null) {
    method getPageNumber (line 76) | getPageNumber() {
    method getDocumentName (line 80) | getDocumentName() {
    method _render (line 84) | _render() {
    method render (line 92) | render(element) {
    method unmount (line 101) | unmount() {
    method destroy (line 107) | destroy() {
    method configure (line 130) | configure({
    method loadDocument (line 147) | loadDocument(buffer, name = "***", config = {}) {
    method loadDocumentByUrl (line 155) | loadDocumentByUrl(url, config = null) {

FILE: viewer/src/actions/actions.js
  method setPageByUrlAction (line 91) | setPageByUrlAction(url, closeContentsOnSuccess = false) {

FILE: viewer/src/components/AppContext.jsx
  function useAppSize (line 18) | function useAppSize(ref) {
  function useFullscreen (line 45) | function useFullscreen(ref) {

FILE: viewer/src/components/FileBlock.jsx
  class FileBlock (line 41) | class FileBlock extends React.Component {
    method render (line 67) | render() {

FILE: viewer/src/components/ImageBlock/CanvasImage.jsx
  class CanvasImage (line 12) | class CanvasImage extends React.Component {
    method constructor (line 20) | constructor(props) {
    method componentWillUnmount (line 28) | componentWillUnmount() {
    method componentDidUpdate (line 32) | componentDidUpdate() {
    method componentDidMount (line 36) | componentDidMount() {
    method getScaleFactor (line 40) | getScaleFactor() {
    method getScaledImageWidth (line 44) | getScaledImageWidth() {
    method getScaledImageHeight (line 48) | getScaledImageHeight() {
    method updateImageIfRequired (line 52) | updateImageIfRequired() {
    method logarithmicScale (line 67) | logarithmicScale() {
    method drawImageOnCanvas (line 95) | drawImageOnCanvas() {
    method putImageData (line 100) | putImageData(imageData) {
    method render (line 121) | render() {

FILE: viewer/src/components/ImageBlock/ComplexImage.jsx
  class ComplexImage (line 46) | class ComplexImage extends React.PureComponent {
    method render (line 129) | render() {

FILE: viewer/src/components/ImageBlock/ImageBlock.jsx
  function resetEventListener (line 55) | function resetEventListener(node, event, handler, options = undefined) {
  class ImageBlock (line 63) | class ImageBlock extends React.Component {
    method getSnapshotBeforeUpdate (line 75) | getSnapshotBeforeUpdate() {
    method scrollCurrentPageIntoViewIfRequired (line 92) | scrollCurrentPageIntoViewIfRequired(prevProps) {
    method componentDidUpdate (line 111) | componentDidUpdate(prevProps, prevState, snapshot) {
    method componentDidMount (line 150) | componentDidMount() {
    method componentWillUnmount (line 157) | componentWillUnmount() {
    method isGrabMode (line 305) | isGrabMode() {
    method setNewPageNumber (line 311) | setNewPageNumber(pageNumber) {
    method render (line 380) | render() {

FILE: viewer/src/components/ImageBlock/VirtualList.jsx
  class VirtualList (line 25) | class VirtualList extends React.PureComponent {
    method viewportHeight (line 46) | get viewportHeight() {
    method componentDidMount (line 50) | componentDidMount() {
    method componentWillUnmount (line 55) | componentWillUnmount() {
    method itemTops (line 71) | get itemTops() {
    method contentHeight (line 75) | get contentHeight() {
    method itemStyles (line 79) | get itemStyles() {
    method findItemIndexByScrollTop (line 83) | findItemIndexByScrollTop(scrollTop) {
    method renderItems (line 128) | renderItems() {
    method isItemVisible (line 153) | isItemVisible(index) {
    method getCurrentVisibleItemIndex (line 164) | getCurrentVisibleItemIndex() {
    method scrollToItem (line 173) | scrollToItem(index) {
    method render (line 179) | render() {

FILE: viewer/src/components/InitialScreen/FileZone.jsx
  class FileZone (line 50) | class FileZone extends React.Component {
    method processFile (line 69) | processFile(file) {
    method render (line 100) | render() {

FILE: viewer/src/components/LeftPanel/ContentsPanel.jsx
  class ContentsPanel (line 35) | class ContentsPanel extends React.Component {
    method convertBookmarkArrayToTreeItemDataArray (line 47) | convertBookmarkArrayToTreeItemDataArray(bookmarkArray) {
    method makeTreeItemDataByBookmark (line 51) | makeTreeItemDataByBookmark(bookmark) {
    method render (line 60) | render() {

FILE: viewer/src/components/LeftPanel/LeftPanel.jsx
  class LeftPanel (line 59) | class LeftPanel extends React.Component {
    method componentDidUpdate (line 105) | componentDidUpdate(prevProps, prevState, snapshot) {
    method render (line 121) | render() {

FILE: viewer/src/components/LeftPanel/TreeItem.jsx
  class TreeItem (line 26) | class TreeItem extends React.Component {
    method constructor (line 35) | constructor(props) {
    method renderChildren (line 44) | renderChildren() {
    method render (line 61) | render() {

FILE: viewer/src/components/LoadingLayer.jsx
  class LoadingLayer (line 29) | class LoadingLayer extends React.Component {
    method constructor (line 30) | constructor(props) {
    method componentDidMount (line 36) | componentDidMount() {
    method componentWillUnmount (line 43) | componentWillUnmount() {
    method render (line 47) | render() {

FILE: viewer/src/components/ModalWindows/ModalWindow.jsx
  class ModalWindow (line 65) | class ModalWindow extends React.Component {
    method render (line 74) | render() {

FILE: viewer/src/components/ModalWindows/PrintDialog.jsx
  function renderPageNumberOptions (line 23) | function renderPageNumberOptions(pagesQuantity) {

FILE: viewer/src/components/Toolbar/PageNumber.jsx
  class PageNumber (line 36) | class PageNumber extends React.Component {
    method constructor (line 44) | constructor(props) {
    method componentDidUpdate (line 52) | componentDidUpdate() { // тупо костыль, так как Firefox на autoFocus к...
    method setNewPageNumber (line 66) | setNewPageNumber(number) {
    method render (line 108) | render() {

FILE: viewer/src/components/Toolbar/PageNumberBlock.jsx
  class PageNumberBlock (line 40) | class PageNumberBlock extends React.Component {
    method setNewPageNumber (line 49) | setNewPageNumber(number, isNext = true) {
    method render (line 69) | render() {

FILE: viewer/src/components/Toolbar/ScaleGizmo.jsx
  class ScaleGizmo (line 30) | class ScaleGizmo extends React.Component {
    method constructor (line 32) | constructor(props) {
    method render (line 79) | render() {

FILE: viewer/src/components/Translation.jsx
  function createTranslator (line 27) | function createTranslator(dict) {

FILE: viewer/src/components/helpers.js
  function getHeaderAndErrorMessage (line 31) | function getHeaderAndErrorMessage(t, error) {

FILE: viewer/src/constants/Constants.js
  function constant (line 45) | function constant(obj) {

FILE: viewer/src/hotkeys.js
  function initHotkeys (line 5) | function initHotkeys(store) {

FILE: viewer/src/reducers/commonReducer.js
  function getInitialStateWithOptions (line 40) | function getInitialStateWithOptions(state) {

FILE: viewer/src/reducers/fileLoadingReducer.js
  function fileLoadingReducer (line 10) | function fileLoadingReducer(state = initialState, action) {

FILE: viewer/src/reducers/fileProcessingReducer.js
  function fileProcessingReducer (line 11) | function fileProcessingReducer(state = initialState, action) {

FILE: viewer/src/reducers/pageReducer.js
  function pageReducer (line 23) | function pageReducer(state = initialState, action) {

FILE: viewer/src/reducers/printReducer.js
  function fileProcessingReducer (line 11) | function fileProcessingReducer(state = initialState, action) {

FILE: viewer/src/sagas/ContinuousScrollManager.js
  class ContinuousScrollManager (line 17) | class ContinuousScrollManager {
    method constructor (line 18) | constructor(djvuWorker, pagesCount, pageStorage) {
    method _reset (line 22) | _reset(djvuWorker, pagesCount, pageStorage) {
    method reset (line 33) | * reset() {
    method setPageNumber (line 38) | setPageNumber(pageNumber) {
    method updateRegistries (line 63) | updateRegistries() {
    method dropAllPages (line 72) | * dropAllPages() {
    method dropPage (line 77) | * dropPage(pageNumber) {
    method removeObsoletePagesIfRequired (line 82) | * removeObsoletePagesIfRequired() {
    method loadPageFromLastPromise (line 92) | * loadPageFromLastPromise() {
    method loadPage (line 103) | * loadPage(pageNumber) {
    method startDataFetching (line 122) | * startDataFetching() {

FILE: viewer/src/sagas/PageStorage.js
  class PageStorage (line 8) | class PageStorage {
    method constructor (line 9) | constructor() {
    method reset (line 13) | reset() {
    method getAllPageNumbers (line 17) | getAllPageNumbers() {
    method getPage (line 21) | getPage(number) {
    method addPage (line 25) | addPage(number, data) {
    method removePage (line 29) | removePage(number) {
    method removeAllPages (line 36) | removeAllPages() {

FILE: viewer/src/sagas/PagesCache.js
  class PagesCache (line 11) | class PagesCache {
    method constructor (line 13) | constructor(djvuWorker) {
    method cancelCachingTask (line 23) | cancelCachingTask() {
    method resetPagesCache (line 34) | resetPagesCache() {
    method fetchCurrentPageByNumber (line 41) | * fetchCurrentPageByNumber(currentPageNumber, pagesQuantity) {
    method cachePages (line 76) | * cachePages(pageNumbersToCache) {
    method fetchImageDataByPageNumber (line 82) | * fetchImageDataByPageNumber(pageNumber) {

FILE: viewer/src/sagas/PrintManager.js
  class PrintManager (line 4) | class PrintManager {
    method constructor (line 5) | constructor(worker, pageStorage) {
    method preparePagesForPrinting (line 10) | * preparePagesForPrinting(from, to) {

FILE: viewer/src/sagas/rootSaga.js
  class RootSaga (line 21) | class RootSaga {
    method constructor (line 22) | constructor(dispatch) {
    method getImageData (line 44) | * getImageData() {
    method fetchPageData (line 63) | * fetchPageData() {
    method fetchPageText (line 86) | * fetchPageText(pageNumber) {
    method fetchPageTextIfRequired (line 104) | * fetchPageTextIfRequired() {
    method prepareForContinuousMode (line 116) | * prepareForContinuousMode() {
    method configure (line 122) | * configure({ pageNumber, pageRotation, viewMode, pageScale, language,...
    method createDocumentFromArrayBuffer (line 157) | * createDocumentFromArrayBuffer({ arrayBuffer, fileName, config }) {
    method loadContents (line 200) | * loadContents() {
    method resetCurrentPageNumber (line 208) | * resetCurrentPageNumber() {
    method setPageByUrl (line 215) | * setPageByUrl(action) {
    method withErrorHandler (line 252) | withErrorHandler(func) {
    method saveDocument (line 264) | * saveDocument() {
    method resetWorkerAndStorages (line 277) | resetWorkerAndStorages() {
    method setCallback (line 287) | setCallback(action) {
    method switchToContinuousScrollMode (line 291) | * switchToContinuousScrollMode(notSave = false) {
    method switchToSinglePageMode (line 301) | * switchToSinglePageMode(notSave = false) {
    method switchToTextMode (line 310) | * switchToTextMode() {
    method handleViewModeSwitch (line 318) | * handleViewModeSwitch({ notSave = false } = {}) {
    method updateOptions (line 335) | * updateOptions(action) {
    method loadOptions (line 348) | * loadOptions() {
    method loadDocumentByUrl (line 384) | * loadDocumentByUrl({ url, config }) {
    method bundleDocument (line 438) | * bundleDocument() {
    method hardReloadIfRequired (line 457) | * hardReloadIfRequired() {
    method preparePagesForPrinting (line 470) | * preparePagesForPrinting(action) {
    method cancelPrintTaskIfRequired (line 475) | * cancelPrintTaskIfRequired() {
    method main (line 488) | * main() {

FILE: viewer/src/utils.js
  function loadFile (line 8) | function loadFile(url, progressHandler) {

FILE: viewer/syncLocales.js
  function getFilePathByDict (line 15) | function getFilePathByDict(dict) {
  function getDictWithQuotes (line 20) | function getDictWithQuotes(dict) {
  function syncDict (line 35) | function syncDict(dict, dictWithQuotes) {
  function main (line 56) | function main(newFileName) {

FILE: viewer/vite.config.js
  method configureServer (line 16) | configureServer(server) {
Condensed preview — 308 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (800K chars).
[
  {
    "path": ".gitattributes",
    "chars": 65,
    "preview": "# .bin files containing text are used as test assets\n*.bin binary"
  },
  {
    "path": ".gitignore",
    "chars": 95,
    "preview": ".idea\n.vscode/\nnode_modules\n_src\nbuild\ndist\nextension/web-ext-artifacts\nextension/manifest.json"
  },
  {
    "path": ".js",
    "chars": 1252,
    "preview": "/**\n * Used in npm scripts to copy files\n */\n\n'use strict';\n\nconst fs = require('fs');\n\nasync function copy() {\n    cons"
  },
  {
    "path": "GNU_GPL_v2",
    "chars": 18092,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "LICENSE.md",
    "chars": 1241,
    "preview": "The DjVu.js Library (everything that is in `library/src` directory) is subject\nto, and may be distributed under,\nthe [GN"
  },
  {
    "path": "README.md",
    "chars": 5300,
    "preview": "# DjVu.js\n\n## About / О проекте\n\n**DjVu.js** is a program library for working with `.djvu` online. It's written\nin JavaS"
  },
  {
    "path": "THE_UNLICENSE",
    "chars": 1210,
    "preview": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, c"
  },
  {
    "path": "TRANSLATION.md",
    "chars": 2412,
    "preview": "# How to add a new translation to the viewer or improve an existing one\n\nIf you want to add one more translation to the "
  },
  {
    "path": "extension/background.js",
    "chars": 10119,
    "preview": "/**\n * The execution starts in the main() function\n */\n\n'use strict';\n\nfunction isManifestV3() {\n    return chrome.runti"
  },
  {
    "path": "extension/content.js",
    "chars": 2265,
    "preview": "(function () {\n    'use strict';\n\n    var includeScriptsPromise = null;\n    function includeScripts() {\n        return i"
  },
  {
    "path": "extension/initializer.js",
    "chars": 654,
    "preview": "'use strict';\n\nwindow.onload = () => {\n    const viewer = new DjVu.Viewer({\n        uiOptions: {\n            hideFullPag"
  },
  {
    "path": "extension/manifest_v2.json",
    "chars": 1324,
    "preview": "{\n    \"manifest_version\": 2,\n    \"name\": \"DjVu.js Viewer\",\n    \"short_name\": \"DV\",\n    \"version\": \"0.10.1.0\",\n    \"autho"
  },
  {
    "path": "extension/manifest_v3.json",
    "chars": 1446,
    "preview": "{\n    \"manifest_version\": 3,\n    \"name\": \"DjVu.js Viewer\",\n    \"short_name\": \"DV\",\n    \"version\": \"0.10.1.0\",\n    \"autho"
  },
  {
    "path": "extension/viewer.html",
    "chars": 778,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-widt"
  },
  {
    "path": "library/.gitignore",
    "chars": 86,
    "preview": ".idea/\nnode_modules/\n/samples/\n.vscode/\naccess.log\nerror.log\nfavicon.ico\njsconfig.json"
  },
  {
    "path": "library/API.md",
    "chars": 14232,
    "preview": "# DjVu.js Library API\n\n> The library is supposed to work in the browser. Theoretically, it should work\n> in Node.js too "
  },
  {
    "path": "library/CHANGELOG.md",
    "chars": 4856,
    "preview": "# DjVu.js Library's Changelog\n\n## v.0.5.4 (01.02.2023)\n\n- Fix: error messages are sent from the worker again.\n\n## v.0.5."
  },
  {
    "path": "library/README.md",
    "chars": 4261,
    "preview": "# DjVu.js Library\n\nThis file contains some information about the inner structure of the project and about how to use the"
  },
  {
    "path": "library/app/app.css",
    "chars": 2709,
    "preview": "\n.func_menu_block {\n    display: flex;\n    justify-content: space-between;\n    flex-flow: row wrap;\n    color: gray;\n   "
  },
  {
    "path": "library/app/app.html",
    "chars": 4132,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <link rel=\""
  },
  {
    "path": "library/app/app.js",
    "chars": 7019,
    "preview": "'use strict';\n\nvar djvuWorker = new DjVu.Worker();\n\nfunction initDjVuApplication() {\n    $('#backbutton').click(reset);\n"
  },
  {
    "path": "library/assets/DjVu3Spec_contents.json",
    "chars": 5304,
    "preview": "[{\"description\":\"DjVu3SpecFinal.djvu\",\"url\":\"\",\"children\":[{\"description\":\"Introduction\",\"url\":\"#1\",\"children\":[{\"descri"
  },
  {
    "path": "library/debug/async.html",
    "chars": 1788,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <title>DjVu"
  },
  {
    "path": "library/debug/css/style.css",
    "chars": 1029,
    "preview": ".control_block{\n    box-shadow: 0 0 1px gray;\n    padding: 0.5em;\n    margin: 0.5em;\n}\n\n#time_output {\n    color: blue;\n"
  },
  {
    "path": "library/debug/examples.html",
    "chars": 949,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>DjVu.js usage examples</title>\n    <link rel=\"short"
  },
  {
    "path": "library/debug/index.html",
    "chars": 787,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <link rel=\""
  },
  {
    "path": "library/debug/js/DjVuGlobals.js",
    "chars": 12005,
    "preview": "'use strict';\n\n/**\n * Just a set of debug functions. \n */\n\nfunction writeln(str) {\n    str = str || \"\";\n    output.inner"
  },
  {
    "path": "library/debug/js/DjVuViewer.js",
    "chars": 8297,
    "preview": "'use strict';\n\n/**\n * Old Viewer with manual DOM manipulation. Saved just for history. \n * Isn't used anymore. \n */\n\ncla"
  },
  {
    "path": "library/debug/js/async.js",
    "chars": 5506,
    "preview": "\"use strict\";\n\n/**\n * Скрипт для тестирования библиотеки через Web Worker\n */\n\nvar fileSize = 0;\nvar output;\nvar worker;"
  },
  {
    "path": "library/debug/js/debug.js",
    "chars": 5450,
    "preview": "'use strict';\n\n/**\n * One more set of debug funcitions that was used in development of the library.\n * Saved mostly for "
  },
  {
    "path": "library/debug/js/examples.js",
    "chars": 11427,
    "preview": "/**\n * This code serves as an example of the API provided by the DjVu.js library.\n * This very API is used by the DjVu.j"
  },
  {
    "path": "library/debug/js/handler.js",
    "chars": 328,
    "preview": "'use strict';\n(function() {\nvar canvas = document.getElementById(\"canvas\");\nvar output = document.getElementById(\"output"
  },
  {
    "path": "library/debug/js/initScript.js",
    "chars": 8675,
    "preview": "\"use strict\";\n\n/**\n * Скрипт для тестирования библиотеки непосредственно в синхронном режиме\n */\n\nDjVu.setDebugMode(true"
  },
  {
    "path": "library/debug/js/reloader.js",
    "chars": 783,
    "preview": "/**\n * A simple websocket client reloading a page when a bundle is updated.\n */\n(function () {\n    'use strict';\n    fun"
  },
  {
    "path": "library/debug/sync.html",
    "chars": 1360,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <title>DjVu"
  },
  {
    "path": "library/package.json",
    "chars": 447,
    "preview": "{\n  \"name\": \"DjVu.js_Library\",\n  \"scripts\": {\n    \"start\": \"node server.js\",\n    \"watch\": \"rollup --config --watch\",\n   "
  },
  {
    "path": "library/rollup.config.js",
    "chars": 731,
    "preview": "'use strict';\n\nconst cleanup = require('rollup-plugin-cleanup');\nconst resolve = require('rollup-plugin-node-resolve');\n"
  },
  {
    "path": "library/server.js",
    "chars": 2455,
    "preview": "/**\n * A server used for debugging.\n */\n\n'use strict';\n\nconst http = require('http');\nconst fs = require('fs');\nconst We"
  },
  {
    "path": "library/src/ByteStream.js",
    "chars": 3465,
    "preview": "import { createStringFromUtf8Array } from './DjVu'\n\n/** @typedef {ByteStream} ByteStream */\n\n/**\n * Объект байтового пот"
  },
  {
    "path": "library/src/ByteStreamWriter.js",
    "chars": 4142,
    "preview": "import { stringToCodePoints, codePointsToUtf8 } from './DjVu';\n\nconst pageSize = 64 * 1024;\nconst growthLimit = 20 * 102"
  },
  {
    "path": "library/src/DjVu.js",
    "chars": 4680,
    "preview": "var DjVu = {\n    VERSION: '0.5.4',\n    IS_DEBUG: false,\n    setDebugMode: (flag) => DjVu.IS_DEBUG = flag\n};\n\nexport func"
  },
  {
    "path": "library/src/DjVuDocument.js",
    "chars": 17594,
    "preview": "import DjViChunk from './chunks/DjViChunk';\nimport DjVuPage from './DjVuPage';\nimport DIRMChunk from './chunks/DirmChunk"
  },
  {
    "path": "library/src/DjVuErrors.js",
    "chars": 4132,
    "preview": "/**\n * Простейший класс ошибки, не содержит рекурсивных данных, чтобы иметь возможность копироваться\n * между потоками в"
  },
  {
    "path": "library/src/DjVuPage.js",
    "chars": 18482,
    "preview": "import { INCLChunk, ColorChunk, CIDaChunk, IFFChunk, INFOChunk, CompositeChunk, ErrorChunk } from './chunks/IFFChunks';\n"
  },
  {
    "path": "library/src/DjVuWorker.js",
    "chars": 11854,
    "preview": "/**\n * @typedef {{\n *      command: string,\n *      data?: {funcs: string[], args: any[][]}[]\n *  } & Partial<Record<str"
  },
  {
    "path": "library/src/DjVuWorkerScript.js",
    "chars": 5859,
    "preview": "import DjVuDocument from './DjVuDocument';\nimport IWImageWriter from './iw44/IWImageWriter';\nimport { DjVuError, DjVuErr"
  },
  {
    "path": "library/src/DjVuWriter.js",
    "chars": 3660,
    "preview": "import ByteStreamWriter from './ByteStreamWriter';\nimport { ZPEncoder } from './ZPCodec';\nimport BZZEncoder from './bzz/"
  },
  {
    "path": "library/src/ZPCodec.js",
    "chars": 17508,
    "preview": "import ByteStreamWriter from './ByteStreamWriter';\n\nexport class ZPEncoder {\n    constructor(bsw) {\n        //byteStream"
  },
  {
    "path": "library/src/bzz/BZZDecoder.js",
    "chars": 8267,
    "preview": "import { ZPDecoder } from '../ZPCodec';\nimport ByteStreamWriter from '../ByteStreamWriter';\nimport ByteStream from '../B"
  },
  {
    "path": "library/src/bzz/BZZEncoder.js",
    "chars": 7996,
    "preview": "import { ZPEncoder } from '../ZPCodec';\n\n/*\n* Предполагается, что все данные будут закодированы одним блоком.\n* Причем б"
  },
  {
    "path": "library/src/chunks/DirmChunk.js",
    "chars": 2934,
    "preview": "import { IFFChunk } from './IFFChunks';\nimport BZZDecoder from '../bzz/BZZDecoder';\n\n/**\n * Порция данных машинного огла"
  },
  {
    "path": "library/src/chunks/DjViChunk.js",
    "chars": 1405,
    "preview": "import JB2Dict from '../jb2/JB2Dict';\nimport { IFFChunk, CompositeChunk } from './IFFChunks';\nimport DjVuAnno from './Dj"
  },
  {
    "path": "library/src/chunks/DjVuAnno.js",
    "chars": 91,
    "preview": "import { IFFChunk } from './IFFChunks';\n\nexport default class DjVuAnno extends IFFChunk { }"
  },
  {
    "path": "library/src/chunks/DjVuPalette.js",
    "chars": 1925,
    "preview": "import { IFFChunk } from './IFFChunks';\nimport BZZDecoder from '../bzz/BZZDecoder';\nimport DjVu from '../DjVu';\n\nexport "
  },
  {
    "path": "library/src/chunks/DjVuText.js",
    "chars": 4986,
    "preview": "import { IFFChunk } from './IFFChunks';\nimport BZZDecoder from '../bzz/BZZDecoder';\nimport { createStringFromUtf8Array }"
  },
  {
    "path": "library/src/chunks/IFFChunks.js",
    "chars": 4528,
    "preview": "import { CorruptedFileDjVuError } from '../DjVuErrors';\n\n/** @typedef {import('../ByteStream').ByteStream} ByteStream */"
  },
  {
    "path": "library/src/chunks/NavmChunk.js",
    "chars": 2336,
    "preview": "import { IFFChunk } from './IFFChunks';\nimport BZZDecoder from '../bzz/BZZDecoder';\n\n/**\n * @typedef {Object} Bookmark\n "
  },
  {
    "path": "library/src/chunks/ThumChunk.js",
    "chars": 104,
    "preview": "import { CompositeChunk } from './IFFChunks';\n\nexport default class ThumChunk extends CompositeChunk { }"
  },
  {
    "path": "library/src/index.js",
    "chars": 785,
    "preview": "/**\n * Throughout the code mostly vars are used, not consts or lets. \n * It's because of that in 2015-2016, when the lib"
  },
  {
    "path": "library/src/iw44/IWCodecBaseClass.js",
    "chars": 11265,
    "preview": "\n//класс общих данных для кодирования и декодирования картинки\n/**\n * There are 4 magic values: \n * 1 for ZERO // this c"
  },
  {
    "path": "library/src/iw44/IWDecoder.js",
    "chars": 13587,
    "preview": "import IWCodecBaseClass from './IWCodecBaseClass';\nimport { LinearBytemap, Block, LazyBlock } from './IWStructures';\nimp"
  },
  {
    "path": "library/src/iw44/IWEncoder.js",
    "chars": 13295,
    "preview": "import IWCodecBaseClass from './IWCodecBaseClass';\nimport { Block } from './IWStructures';\n\nexport default class IWEncod"
  },
  {
    "path": "library/src/iw44/IWImage.js",
    "chars": 3710,
    "preview": "import IWDecoder from './IWDecoder';\nimport { LazyPixelmap, Pixelmap } from './IWStructures';\nimport DjVu from '../DjVu'"
  },
  {
    "path": "library/src/iw44/IWImageWriter.js",
    "chars": 9191,
    "preview": "import DjVuWriter from '../DjVuWriter';\nimport DjVuDocument from '../DjVuDocument';\nimport ByteStreamWriter from '../Byt"
  },
  {
    "path": "library/src/iw44/IWStructures.js",
    "chars": 8186,
    "preview": "function _normalize(val) {\n    val = (val + 32) >> 6;   // убираем 6 дробных бит в этом псевдо дробном числе\n    if (val"
  },
  {
    "path": "library/src/jb2/JB2Codec.js",
    "chars": 8500,
    "preview": "import { ZPDecoder } from '../ZPCodec';\nimport { Bitmap, NumContext } from './JB2Structures';\nimport { IFFChunk } from '"
  },
  {
    "path": "library/src/jb2/JB2Dict.js",
    "chars": 3084,
    "preview": "import JB2Codec from './JB2Codec';\n\n export default class JB2Dict extends JB2Codec {\n    constructor(bs) {\n        super"
  },
  {
    "path": "library/src/jb2/JB2Image.js",
    "chars": 13782,
    "preview": "import JB2Codec from './JB2Codec';\nimport { Baseline, Bitmap } from './JB2Structures';\nimport DjVu from '../DjVu';\n\nexpo"
  },
  {
    "path": "library/src/jb2/JB2Structures.js",
    "chars": 4877,
    "preview": "export class Bitmap {\n    constructor(width, height) {\n        var length = Math.ceil(width * height / 8); // число байт"
  },
  {
    "path": "library/src/methods/bundle.js",
    "chars": 2798,
    "preview": "import { loadPage, loadPageDependency, loadThumbnail } from './load';\nimport DjVuWriter from '../DjVuWriter';\nimport { p"
  },
  {
    "path": "library/src/methods/load.js",
    "chars": 2101,
    "preview": "/** \n * Logic related to loading pages and dictionaries\n * for indirect djvu documents.\n */\n\nimport { loadFileViaXHR } f"
  },
  {
    "path": "library/tests/embed.html",
    "chars": 1508,
    "preview": "<!-- A page to test the browser extension, namely how the viewer builds into a third party page-->\n\n<!DOCTYPE html>\n<htm"
  },
  {
    "path": "library/tests/tests.css",
    "chars": 533,
    "preview": "html,\nbody {\n    height: 100%;\n}\n\n#test_results_wrapper {\n    font-family: monospace;\n    box-shadow: 0 0 1px lightgray;"
  },
  {
    "path": "library/tests/tests.html",
    "chars": 558,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charse"
  },
  {
    "path": "library/tests/tests.js",
    "chars": 24845,
    "preview": "'use strict';\n\nvar djvuWorker = new DjVu.Worker();\n\nvar outputBlock = $('#test_results_wrapper');\n\nfunction createBaseUr"
  },
  {
    "path": "package.json",
    "chars": 665,
    "preview": "{\n  \"name\": \"DjVu.js_Project\",\n  \"scripts\": {\n    \"clean\": \"git clean -fdX --exclude=!/.*/\",\n    \"install\": \"cd library "
  },
  {
    "path": "viewer/.gitignore",
    "chars": 306,
    "preview": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# testing\n/cov"
  },
  {
    "path": "viewer/CHANGELOG.md",
    "chars": 7055,
    "preview": "# DjVu.js Viewer's Changelog\n\n## v.0.10.1 (30.05.2024)\n\n- Hide the \"Analyze headers\" option in the manifest v3 extension"
  },
  {
    "path": "viewer/cypress/e2e/fullscreen_mode.cy.js",
    "chars": 3255,
    "preview": "import { customId, customClass, renderViewer, loadDocument } from \"../utils\";\n\ndescribe('Full page mode', () => {\n    be"
  },
  {
    "path": "viewer/cypress/e2e/initial_screen.cy.js",
    "chars": 1203,
    "preview": "import { getByCustomId, haveCustomClass, hexToRGB, notHaveCustomClass, renderViewer } from \"../utils\";\nimport { initialS"
  },
  {
    "path": "viewer/cypress/e2e/menu.cy.js",
    "chars": 2423,
    "preview": "import { customClass, customId, loadDocument, renderViewer } from \"../utils\";\nimport { helpWindowShouldBeOpen, initialSc"
  },
  {
    "path": "viewer/cypress/e2e/mobile_version.cy.js",
    "chars": 2884,
    "preview": "import { customClass, customId, loadDocument, renderViewer } from \"../utils\";\n\ndescribe('Adaptive layout', () => {\n    b"
  },
  {
    "path": "viewer/cypress/e2e/modal_windows.cy.js",
    "chars": 1050,
    "preview": "import { customClass, customId, renderViewer } from \"../utils\";\nimport { closeModalWindow, helpWindowShouldBeOpen, optio"
  },
  {
    "path": "viewer/cypress/e2e/toolbar.cy.js",
    "chars": 1733,
    "preview": "import {  customId, loadDocument, renderViewer } from \"../utils\";\n\ndescribe('Toolbar controls', () => {\n    beforeEach(("
  },
  {
    "path": "viewer/cypress/shared.js",
    "chars": 973,
    "preview": "import { customClass } from \"./utils\";\n\nexport function helpWindowShouldBeOpen() {\n    cy.get(customClass('modal_window'"
  },
  {
    "path": "viewer/cypress/utils.js",
    "chars": 1573,
    "preview": "export const hexToRGB = (string) => {\n    if ((string.length !== 4 && string.length !== 7) || string[0] !== '#') {\n     "
  },
  {
    "path": "viewer/cypress.config.js",
    "chars": 305,
    "preview": "import { defineConfig } from 'cypress'\n\nexport default defineConfig({\n    viewportWidth: 1200,\n    viewportHeight: 900,\n"
  },
  {
    "path": "viewer/index.html",
    "chars": 2010,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-widt"
  },
  {
    "path": "viewer/jsconfig.json",
    "chars": 134,
    "preview": "{\n    \"compilerOptions\": {\n        \"target\": \"ES6\"\n    },\n    \"exclude\": [\n        \"node_modules\",\n        \"**/node_modu"
  },
  {
    "path": "viewer/package.json",
    "chars": 1037,
    "preview": "{\n    \"name\": \"DjVu.js_Viewer\",\n    \"private\": true,\n    \"type\": \"module\",\n    \"devDependencies\": {\n        \"@vitejs/plu"
  },
  {
    "path": "viewer/public/manifest.json",
    "chars": 304,
    "preview": "{\n  \"short_name\": \"DjVu Viewer\",\n  \"name\": \"DjVu Viewer\",\n  \"icons\": [\n    {\n      \"src\": \"djvu.src\",\n      \"sizes\": \"64"
  },
  {
    "path": "viewer/src/App.test.js",
    "chars": 208,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nit('renders without crashing', ()"
  },
  {
    "path": "viewer/src/DjVu.js",
    "chars": 447,
    "preview": "/**\n * The module is required due to the bug https://bugzilla.mozilla.org/show_bug.cgi?id=1408996\n * Because of which I "
  },
  {
    "path": "viewer/src/DjVuViewer.jsx",
    "chars": 4805,
    "preview": "import React from 'react';\nimport { createRoot } from 'react-dom/client';\nimport { Provider } from 'react-redux'\nimport "
  },
  {
    "path": "viewer/src/actions/actions.js",
    "chars": 3935,
    "preview": "import Constants, { ActionTypes } from '../constants';\nimport { get } from '../reducers';\nimport DjVu from '../DjVu';\n\nc"
  },
  {
    "path": "viewer/src/components/App.jsx",
    "chars": 5565,
    "preview": "import React from 'react';\nimport { useSelector } from 'react-redux';\nimport { createGlobalStyle, css, /* StyleSheetMana"
  },
  {
    "path": "viewer/src/components/AppContext.jsx",
    "chars": 3618,
    "preview": "import React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { ThemeContext, ThemeProvider } from \"styled"
  },
  {
    "path": "viewer/src/components/ErrorPage.jsx",
    "chars": 948,
    "preview": "import React from 'react';\nimport { useTranslation } from \"./Translation\";\nimport styled from 'styled-components';\nimpor"
  },
  {
    "path": "viewer/src/components/FileBlock.jsx",
    "chars": 2249,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { FaUpload "
  },
  {
    "path": "viewer/src/components/FileLoadingScreen.jsx",
    "chars": 1169,
    "preview": "import React from 'react';\nimport { useSelector } from 'react-redux';\n\nimport { get } from '../reducers';\nimport styled "
  },
  {
    "path": "viewer/src/components/ImageBlock/CanvasImage.jsx",
    "chars": 3892,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport Constants from '../../constants';\n\n/**\n * A compo"
  },
  {
    "path": "viewer/src/components/ImageBlock/ComplexImage.jsx",
    "chars": 6974,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport CanvasImage from './CanvasImage';\nimport TextLaye"
  },
  {
    "path": "viewer/src/components/ImageBlock/ImageBlock.jsx",
    "chars": 15574,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { connect, useSelector } from 'react-redux';\nimpor"
  },
  {
    "path": "viewer/src/components/ImageBlock/TextLayer.jsx",
    "chars": 2619,
    "preview": "import React, { useEffect, useRef } from 'react';\nimport Constants from '../../constants';\nimport styled from 'styled-co"
  },
  {
    "path": "viewer/src/components/ImageBlock/VirtualList.jsx",
    "chars": 6345,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport memoize from 'memoize-one';\nimport { createDeferre"
  },
  {
    "path": "viewer/src/components/InitialScreen/FileZone.jsx",
    "chars": 3296,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { FaUpload "
  },
  {
    "path": "viewer/src/components/InitialScreen/InitialScreen.jsx",
    "chars": 2706,
    "preview": "import React from 'react';\n\nimport HelpButton from '../misc/HelpButton';\nimport FileZone from './FileZone';\nimport DjVu "
  },
  {
    "path": "viewer/src/components/InitialScreen/LinkBlock.jsx",
    "chars": 1716,
    "preview": "import React from 'react';\n\nimport styled from 'styled-components';\nimport { useDispatch } from 'react-redux';\nimport { "
  },
  {
    "path": "viewer/src/components/InitialScreen/ThemeSwitcher.jsx",
    "chars": 1316,
    "preview": "import React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport styled, { css } from 'styled-c"
  },
  {
    "path": "viewer/src/components/Language/AddLanguageButton.jsx",
    "chars": 790,
    "preview": "import React from \"react\";\nimport Constants from \"../../constants/Constants\";\nimport { IoAddCircleOutline } from \"react-"
  },
  {
    "path": "viewer/src/components/Language/IncompleteTranslationWindow.jsx",
    "chars": 1344,
    "preview": "import ModalWindow from \"../ModalWindows/ModalWindow\";\nimport React from \"react\";\nimport styled from \"styled-components\""
  },
  {
    "path": "viewer/src/components/Language/LanguagePanel.jsx",
    "chars": 2080,
    "preview": "import React from 'react';\nimport styled, { css } from \"styled-components\";\nimport { useDispatch, useSelector } from \"re"
  },
  {
    "path": "viewer/src/components/Language/LanguageSelector.jsx",
    "chars": 1486,
    "preview": "import { ActionTypes } from \"../../constants\";\nimport dictionaries from \"../../locales\";\nimport LanguageWarningSign from"
  },
  {
    "path": "viewer/src/components/Language/LanguageWarningSign.jsx",
    "chars": 1271,
    "preview": "import React from \"react\";\nimport { FaExclamationTriangle } from \"react-icons/fa\";\nimport styled from \"styled-components"
  },
  {
    "path": "viewer/src/components/LeftPanel/ContentsPanel.jsx",
    "chars": 2410,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\n\nimport Actions fr"
  },
  {
    "path": "viewer/src/components/LeftPanel/LeftPanel.jsx",
    "chars": 5459,
    "preview": "import React from 'react';\nimport { connect } from 'react-redux';\n\nimport ContentsPanel from './ContentsPanel';\nimport {"
  },
  {
    "path": "viewer/src/components/LeftPanel/TreeItem.jsx",
    "chars": 1942,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { FaRegPlusSquare, FaRegMinusSquare, FaCircle } fr"
  },
  {
    "path": "viewer/src/components/LoadingLayer.jsx",
    "chars": 1368,
    "preview": "import React from 'react';\nimport styled from 'styled-components';\nimport LoadingPhrase from './misc/LoadingPhrase';\n\nco"
  },
  {
    "path": "viewer/src/components/Main.jsx",
    "chars": 1922,
    "preview": "import React from 'react';\nimport { useSelector } from 'react-redux';\nimport styled from 'styled-components';\nimport { g"
  },
  {
    "path": "viewer/src/components/Menu.jsx",
    "chars": 7036,
    "preview": "import React from \"react\";\nimport styled, { css } from \"styled-components\";\nimport CloseButton from \"./misc/CloseButton\""
  },
  {
    "path": "viewer/src/components/ModalWindows/ErrorWindow.jsx",
    "chars": 1143,
    "preview": "import React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\n\nimport ModalWindow from './ModalWind"
  },
  {
    "path": "viewer/src/components/ModalWindows/HelpWindow.jsx",
    "chars": 3383,
    "preview": "import React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { FaExpand, FaCompress } from "
  },
  {
    "path": "viewer/src/components/ModalWindows/ModalWindow.jsx",
    "chars": 3331,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\nimport styled, { css } "
  },
  {
    "path": "viewer/src/components/ModalWindows/OptionsWindow.jsx",
    "chars": 3569,
    "preview": "import React from 'react';\nimport { useSelector, useDispatch } from 'react-redux';\nimport { get } from '../../reducers';"
  },
  {
    "path": "viewer/src/components/ModalWindows/PrintDialog.jsx",
    "chars": 7120,
    "preview": "import React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\n\nimport ModalWindow from './ModalWind"
  },
  {
    "path": "viewer/src/components/ModalWindows/SaveDialog.jsx",
    "chars": 4313,
    "preview": "import React from 'react';\nimport styled from \"styled-components\";\nimport ModalWindow from \"./ModalWindow\";\nimport { use"
  },
  {
    "path": "viewer/src/components/StyledPrimitives.jsx",
    "chars": 915,
    "preview": "import styled, { keyframes } from 'styled-components';\nimport { controlButton } from './cssMixins';\nimport { FaSpinner }"
  },
  {
    "path": "viewer/src/components/TextBlock.jsx",
    "chars": 778,
    "preview": "import React from 'react';\nimport styled from 'styled-components';\nimport LoadingPhrase from './misc/LoadingPhrase';\nimp"
  },
  {
    "path": "viewer/src/components/Toolbar/ContentsButton.jsx",
    "chars": 889,
    "preview": "import { IoListCircleSharp, IoListCircleOutline } from \"react-icons/io5\";\nimport React from \"react\";\nimport { useDispatc"
  },
  {
    "path": "viewer/src/components/Toolbar/CursorModeButtonGroup.jsx",
    "chars": 1556,
    "preview": "import React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { FaRegHandPaper, FaICursor } "
  },
  {
    "path": "viewer/src/components/Toolbar/HideButton.jsx",
    "chars": 1210,
    "preview": "import styled, { css } from \"styled-components\";\nimport { FiChevronsDown } from \"react-icons/fi\";\n\nconst hiddenStyle = c"
  },
  {
    "path": "viewer/src/components/Toolbar/MenuButton.jsx",
    "chars": 439,
    "preview": "import { FiCommand } from \"react-icons/fi\";\nimport React from \"react\";\nimport { iconButton } from \"../cssMixins\";\nimport"
  },
  {
    "path": "viewer/src/components/Toolbar/PageNumber.jsx",
    "chars": 3381,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport { styledIn"
  },
  {
    "path": "viewer/src/components/Toolbar/PageNumberBlock.jsx",
    "chars": 2558,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { FaRegArro"
  },
  {
    "path": "viewer/src/components/Toolbar/PinButton.jsx",
    "chars": 727,
    "preview": "import { TiPin } from 'react-icons/ti';\nimport styled from \"styled-components\";\nimport { useTranslation } from \"../Trans"
  },
  {
    "path": "viewer/src/components/Toolbar/RotationControl.jsx",
    "chars": 1443,
    "preview": "import React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { FaUndo } from \"react-icons/f"
  },
  {
    "path": "viewer/src/components/Toolbar/ScaleGizmo.jsx",
    "chars": 2940,
    "preview": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport Actions fro"
  },
  {
    "path": "viewer/src/components/Toolbar/Toolbar.jsx",
    "chars": 4187,
    "preview": "import React from 'react';\nimport PageNumberBlock from './PageNumberBlock'\nimport ScaleGizmo from './ScaleGizmo';\nimport"
  },
  {
    "path": "viewer/src/components/Toolbar/ViewModeButtons.jsx",
    "chars": 6144,
    "preview": "import React from 'react';\nimport { connect, useDispatch, useSelector } from 'react-redux';\nimport { FaRegFileAlt, FaReg"
  },
  {
    "path": "viewer/src/components/Translation.jsx",
    "chars": 1958,
    "preview": "import React from 'react';\nimport { useSelector } from \"react-redux\";\nimport { get } from \"../reducers\";\nimport dictiona"
  },
  {
    "path": "viewer/src/components/cssMixins.js",
    "chars": 456,
    "preview": "import { css } from \"styled-components\";\n\nexport const iconButton = css`\n    cursor: pointer;\n    flex: 0 0 auto;\n\n    &"
  },
  {
    "path": "viewer/src/components/helpers.js",
    "chars": 3275,
    "preview": "import DjVu from \"../DjVu\";\n\n/**\n * Delays execution of a callback for a while in order to wait whether there will be on"
  },
  {
    "path": "viewer/src/components/misc/CloseButton.jsx",
    "chars": 371,
    "preview": "import React from \"react\";\nimport { iconButton } from \"../cssMixins\";\nimport { FaRegTimesCircle } from \"react-icons/fa\";"
  },
  {
    "path": "viewer/src/components/misc/FullPageViewButton.jsx",
    "chars": 947,
    "preview": "import React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { FaExpand, FaCompress } from "
  },
  {
    "path": "viewer/src/components/misc/FullscreenButton.jsx",
    "chars": 823,
    "preview": "import React from \"react\";\nimport styled from \"styled-components\";\nimport { IoDesktopOutline } from \"react-icons/io5\";\ni"
  },
  {
    "path": "viewer/src/components/misc/HelpButton.jsx",
    "chars": 914,
    "preview": "import React from 'react';\nimport { useDispatch } from 'react-redux';\nimport { FaRegQuestionCircle } from \"react-icons/f"
  },
  {
    "path": "viewer/src/components/misc/LoadingPhrase.jsx",
    "chars": 515,
    "preview": "import React from 'react';\nimport { useTranslation } from '../Translation';\nimport { Spinner } from \"../StyledPrimitives"
  },
  {
    "path": "viewer/src/components/misc/OptionsButton.jsx",
    "chars": 898,
    "preview": "import React from 'react';\nimport { useDispatch } from 'react-redux';\nimport { FaCog } from \"react-icons/fa\";\n\nimport { "
  },
  {
    "path": "viewer/src/components/misc/ProgressBar.jsx",
    "chars": 463,
    "preview": "import React from \"react\";\nimport styled from \"styled-components\";\n\nconst ProgressBar = styled.div`\n    border: 1px soli"
  },
  {
    "path": "viewer/src/components/misc/SaveButton.jsx",
    "chars": 1446,
    "preview": "import React from \"react\";\nimport { ControlButton, ControlButtonWrapper } from \"../StyledPrimitives\";\nimport { FaDownloa"
  },
  {
    "path": "viewer/src/components/misc/SaveNotification.jsx",
    "chars": 1680,
    "preview": "import React from \"react\";\nimport ModalWindow from \"../ModalWindows/ModalWindow\";\nimport { TextButton } from \"../StyledP"
  },
  {
    "path": "viewer/src/constants/Constants.js",
    "chars": 1624,
    "preview": "const Constants = {\n    TRANSLATION_PAGE_URL: \"https://github.com/RussCoder/djvujs/blob/master/TRANSLATION.md\",\n    DEFA"
  },
  {
    "path": "viewer/src/constants/actionTypes.js",
    "chars": 866,
    "preview": "import { constant } from './Constants';\n\nexport const ActionTypes = constant({\n    LOAD_DOCUMENT_BY_URL: null,\n    CONFI"
  },
  {
    "path": "viewer/src/constants/index.js",
    "chars": 98,
    "preview": "export { default } from './Constants';\nexport * from './Constants';\nexport * from './actionTypes';"
  },
  {
    "path": "viewer/src/hotkeys.js",
    "chars": 1135,
    "preview": "import Actions from './actions/actions';\nimport { ActionTypes } from './constants/index';\nimport { get } from './reducer"
  },
  {
    "path": "viewer/src/index.js",
    "chars": 1828,
    "preview": "import DjVu from './DjVu';\nimport DjVuViewer from './DjVuViewer';\n\nDjVu.Viewer = DjVuViewer;\n\nif (process.env.NODE_ENV !"
  },
  {
    "path": "viewer/src/locales/ChineseSimplified.js",
    "chars": 8740,
    "preview": "/**\n * Some phrases contain insertions, e.g. icons and buttons, which are inserted in the code.\n * Here instead of visua"
  },
  {
    "path": "viewer/src/locales/English.js",
    "chars": 10716,
    "preview": "/**\n * Some phrases contain insertions, e.g. icons and buttons, which are inserted in the code.\n * Here instead of visua"
  },
  {
    "path": "viewer/src/locales/French.js",
    "chars": 11119,
    "preview": "/**\n * Some phrases contain insertions, e.g. icons and buttons, which are inserted in the code.\n * Here instead of visua"
  },
  {
    "path": "viewer/src/locales/Italian.js",
    "chars": 10906,
    "preview": "/**\n * Some phrases contain insertions, e.g. icons and buttons, which are inserted in the code.\n * Here instead of visua"
  },
  {
    "path": "viewer/src/locales/Portuguese.js",
    "chars": 10907,
    "preview": "/**\n * Some phrases contain insertions, e.g. icons and buttons, which are inserted in the code.\n * Here instead of visua"
  },
  {
    "path": "viewer/src/locales/Russian.js",
    "chars": 11368,
    "preview": "/**\n * The exemplary dictionary which should be used for the creation of other localizations.\n * Copy this file and chan"
  },
  {
    "path": "viewer/src/locales/Spanish.js",
    "chars": 11227,
    "preview": "/**\n * Some phrases contain insertions, e.g. icons and buttons, which are inserted in the code.\n * Here instead of visua"
  },
  {
    "path": "viewer/src/locales/Swedish.js",
    "chars": 10631,
    "preview": "/**\n * Some phrases contain insertions, e.g. icons and buttons, which are inserted in the code.\n * Here instead of visua"
  },
  {
    "path": "viewer/src/locales/Ukrainian.js",
    "chars": 10830,
    "preview": "/**\n * Some phrases contain insertions, e.g. icons and buttons, which are inserted in the code.\n * Here instead of visua"
  },
  {
    "path": "viewer/src/locales/index.js",
    "chars": 884,
    "preview": "/**\n * .js extension should be used in all imports, because this file is used in a node script (syncLocales.js)\n */\n\nimp"
  },
  {
    "path": "viewer/src/reducers/commonReducer.js",
    "chars": 6399,
    "preview": "import Constants from '../constants';\nimport { ActionTypes } from \"../constants\";\nimport dictionaries from '../locales';"
  },
  {
    "path": "viewer/src/reducers/fileLoadingReducer.js",
    "chars": 1049,
    "preview": "import { createSelector } from 'reselect';\nimport Constants, { ActionTypes } from '../constants';\n\nconst initialState = "
  },
  {
    "path": "viewer/src/reducers/fileProcessingReducer.js",
    "chars": 1199,
    "preview": "import { createSelector } from 'reselect';\nimport { ActionTypes } from '../constants';\n\nconst initialState = Object.free"
  },
  {
    "path": "viewer/src/reducers/index.js",
    "chars": 890,
    "preview": "import fileLoadingReducer, { get as fileLoadingGet } from './fileLoadingReducer';\nimport pageReducer, { get as pageGet }"
  },
  {
    "path": "viewer/src/reducers/pageReducer.js",
    "chars": 4045,
    "preview": "import { createSelector } from 'reselect';\nimport Constants from '../constants';\nimport { ActionTypes } from '../constan"
  },
  {
    "path": "viewer/src/reducers/printReducer.js",
    "chars": 1472,
    "preview": "import { createSelector } from 'reselect';\nimport { ActionTypes } from '../constants';\n\nconst initialState = Object.free"
  },
  {
    "path": "viewer/src/sagas/ContinuousScrollManager.js",
    "chars": 5701,
    "preview": "/**\n * The logic related to page caching in the continuous scroll mode.\n * It pre-fetches pages depending on the current"
  },
  {
    "path": "viewer/src/sagas/PageStorage.js",
    "chars": 942,
    "preview": "/**\n * All URLs should be revoked after they are not needed any more.\n * Also these URLs can be used not only in the con"
  },
  {
    "path": "viewer/src/sagas/PagesCache.js",
    "chars": 4082,
    "preview": "/**\n * The logic of extracting and caching pages of a document in the single page view mode.\n * The current, the previou"
  },
  {
    "path": "viewer/src/sagas/PrintManager.js",
    "chars": 1352,
    "preview": "import { put } from \"redux-saga/effects\";\nimport { ActionTypes } from \"../constants\";\n\nexport default class PrintManager"
  },
  {
    "path": "viewer/src/sagas/rootSaga.js",
    "chars": 20716,
    "preview": "/**\n * All side-effect logic is here (all logic which isn't related directly to the UI)\n */\nimport { parse as parseConte"
  },
  {
    "path": "viewer/src/store.js",
    "chars": 816,
    "preview": "import { createStore, applyMiddleware } from 'redux';\nimport thunkMiddleware from 'redux-thunk';\nimport createSagaMiddle"
  },
  {
    "path": "viewer/src/utils.js",
    "chars": 1688,
    "preview": "import DjVu from './DjVu';\n\n/**\n * We use the error codes form the library just to unify the structure of error objects "
  },
  {
    "path": "viewer/syncLocales.js",
    "chars": 2517,
    "preview": "/**\n * A script for automatic synchronization of the Russian dictionary with others.\n * It automatically generates Engli"
  },
  {
    "path": "viewer/vite.config.js",
    "chars": 1365,
    "preview": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\nimport fs from 'fs';\nimport serveStatic fro"
  }
]

// ... and 127 more files (download for full content)

About this extraction

This page contains the full source code of the RussCoder/djvujs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 308 files (724.3 KB), approximately 191.8k tokens, and a symbol index with 701 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!