@ -0,0 +1,661 @@ |
||||
GNU AFFERO GENERAL PUBLIC LICENSE |
||||
Version 3, 19 November 2007 |
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
||||
Everyone is permitted to copy and distribute verbatim copies |
||||
of this license document, but changing it is not allowed. |
||||
|
||||
Preamble |
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for |
||||
software and other kinds of works, specifically designed to ensure |
||||
cooperation with the community in the case of network server software. |
||||
|
||||
The licenses for most software and other practical works are designed |
||||
to take away your freedom to share and change the works. By contrast, |
||||
our General Public Licenses are intended to guarantee your freedom to |
||||
share and change all versions of a program--to make sure it remains free |
||||
software for all its users. |
||||
|
||||
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 |
||||
them 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. |
||||
|
||||
Developers that use our General Public Licenses protect your rights |
||||
with two steps: (1) assert copyright on the software, and (2) offer |
||||
you this License which gives you legal permission to copy, distribute |
||||
and/or modify the software. |
||||
|
||||
A secondary benefit of defending all users' freedom is that |
||||
improvements made in alternate versions of the program, if they |
||||
receive widespread use, become available for other developers to |
||||
incorporate. Many developers of free software are heartened and |
||||
encouraged by the resulting cooperation. However, in the case of |
||||
software used on network servers, this result may fail to come about. |
||||
The GNU General Public License permits making a modified version and |
||||
letting the public access it on a server without ever releasing its |
||||
source code to the public. |
||||
|
||||
The GNU Affero General Public License is designed specifically to |
||||
ensure that, in such cases, the modified source code becomes available |
||||
to the community. It requires the operator of a network server to |
||||
provide the source code of the modified version running there to the |
||||
users of that server. Therefore, public use of a modified version, on |
||||
a publicly accessible server, gives the public access to the source |
||||
code of the modified version. |
||||
|
||||
An older license, called the Affero General Public License and |
||||
published by Affero, was designed to accomplish similar goals. This is |
||||
a different license, not a version of the Affero GPL, but Affero has |
||||
released a new version of the Affero GPL which permits relicensing under |
||||
this license. |
||||
|
||||
The precise terms and conditions for copying, distribution and |
||||
modification follow. |
||||
|
||||
TERMS AND CONDITIONS |
||||
|
||||
0. Definitions. |
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License. |
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of |
||||
works, such as semiconductor masks. |
||||
|
||||
"The Program" refers to any copyrightable work licensed under this |
||||
License. Each licensee is addressed as "you". "Licensees" and |
||||
"recipients" may be individuals or organizations. |
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work |
||||
in a fashion requiring copyright permission, other than the making of an |
||||
exact copy. The resulting work is called a "modified version" of the |
||||
earlier work or a work "based on" the earlier work. |
||||
|
||||
A "covered work" means either the unmodified Program or a work based |
||||
on the Program. |
||||
|
||||
To "propagate" a work means to do anything with it that, without |
||||
permission, would make you directly or secondarily liable for |
||||
infringement under applicable copyright law, except executing it on a |
||||
computer or modifying a private copy. Propagation includes copying, |
||||
distribution (with or without modification), making available to the |
||||
public, and in some countries other activities as well. |
||||
|
||||
To "convey" a work means any kind of propagation that enables other |
||||
parties to make or receive copies. Mere interaction with a user through |
||||
a computer network, with no transfer of a copy, is not conveying. |
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" |
||||
to the extent that it includes a convenient and prominently visible |
||||
feature that (1) displays an appropriate copyright notice, and (2) |
||||
tells the user that there is no warranty for the work (except to the |
||||
extent that warranties are provided), that licensees may convey the |
||||
work under this License, and how to view a copy of this License. If |
||||
the interface presents a list of user commands or options, such as a |
||||
menu, a prominent item in the list meets this criterion. |
||||
|
||||
1. Source Code. |
||||
|
||||
The "source code" for a work means the preferred form of the work |
||||
for making modifications to it. "Object code" means any non-source |
||||
form of a work. |
||||
|
||||
A "Standard Interface" means an interface that either is an official |
||||
standard defined by a recognized standards body, or, in the case of |
||||
interfaces specified for a particular programming language, one that |
||||
is widely used among developers working in that language. |
||||
|
||||
The "System Libraries" of an executable work include anything, other |
||||
than the work as a whole, that (a) is included in the normal form of |
||||
packaging a Major Component, but which is not part of that Major |
||||
Component, and (b) serves only to enable use of the work with that |
||||
Major Component, or to implement a Standard Interface for which an |
||||
implementation is available to the public in source code form. A |
||||
"Major Component", in this context, means a major essential component |
||||
(kernel, window system, and so on) of the specific operating system |
||||
(if any) on which the executable work runs, or a compiler used to |
||||
produce the work, or an object code interpreter used to run it. |
||||
|
||||
The "Corresponding Source" for a work in object code form means all |
||||
the source code needed to generate, install, and (for an executable |
||||
work) run the object code and to modify the work, including scripts to |
||||
control those activities. However, it does not include the work's |
||||
System Libraries, or general-purpose tools or generally available free |
||||
programs which are used unmodified in performing those activities but |
||||
which are not part of the work. For example, Corresponding Source |
||||
includes interface definition files associated with source files for |
||||
the work, and the source code for shared libraries and dynamically |
||||
linked subprograms that the work is specifically designed to require, |
||||
such as by intimate data communication or control flow between those |
||||
subprograms and other parts of the work. |
||||
|
||||
The Corresponding Source need not include anything that users |
||||
can regenerate automatically from other parts of the Corresponding |
||||
Source. |
||||
|
||||
The Corresponding Source for a work in source code form is that |
||||
same work. |
||||
|
||||
2. Basic Permissions. |
||||
|
||||
All rights granted under this License are granted for the term of |
||||
copyright on the Program, and are irrevocable provided the stated |
||||
conditions are met. This License explicitly affirms your unlimited |
||||
permission to run the unmodified Program. The output from running a |
||||
covered work is covered by this License only if the output, given its |
||||
content, constitutes a covered work. This License acknowledges your |
||||
rights of fair use or other equivalent, as provided by copyright law. |
||||
|
||||
You may make, run and propagate covered works that you do not |
||||
convey, without conditions so long as your license otherwise remains |
||||
in force. You may convey covered works to others for the sole purpose |
||||
of having them make modifications exclusively for you, or provide you |
||||
with facilities for running those works, provided that you comply with |
||||
the terms of this License in conveying all material for which you do |
||||
not control copyright. Those thus making or running the covered works |
||||
for you must do so exclusively on your behalf, under your direction |
||||
and control, on terms that prohibit them from making any copies of |
||||
your copyrighted material outside their relationship with you. |
||||
|
||||
Conveying under any other circumstances is permitted solely under |
||||
the conditions stated below. Sublicensing is not allowed; section 10 |
||||
makes it unnecessary. |
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
||||
|
||||
No covered work shall be deemed part of an effective technological |
||||
measure under any applicable law fulfilling obligations under article |
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
||||
similar laws prohibiting or restricting circumvention of such |
||||
measures. |
||||
|
||||
When you convey a covered work, you waive any legal power to forbid |
||||
circumvention of technological measures to the extent such circumvention |
||||
is effected by exercising rights under this License with respect to |
||||
the covered work, and you disclaim any intention to limit operation or |
||||
modification of the work as a means of enforcing, against the work's |
||||
users, your or third parties' legal rights to forbid circumvention of |
||||
technological measures. |
||||
|
||||
4. Conveying Verbatim Copies. |
||||
|
||||
You may convey 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; |
||||
keep intact all notices stating that this License and any |
||||
non-permissive terms added in accord with section 7 apply to the code; |
||||
keep intact all notices of the absence of any warranty; and give all |
||||
recipients a copy of this License along with the Program. |
||||
|
||||
You may charge any price or no price for each copy that you convey, |
||||
and you may offer support or warranty protection for a fee. |
||||
|
||||
5. Conveying Modified Source Versions. |
||||
|
||||
You may convey a work based on the Program, or the modifications to |
||||
produce it from the Program, in the form of source code under the |
||||
terms of section 4, provided that you also meet all of these conditions: |
||||
|
||||
a) The work must carry prominent notices stating that you modified |
||||
it, and giving a relevant date. |
||||
|
||||
b) The work must carry prominent notices stating that it is |
||||
released under this License and any conditions added under section |
||||
7. This requirement modifies the requirement in section 4 to |
||||
"keep intact all notices". |
||||
|
||||
c) You must license the entire work, as a whole, under this |
||||
License to anyone who comes into possession of a copy. This |
||||
License will therefore apply, along with any applicable section 7 |
||||
additional terms, to the whole of the work, and all its parts, |
||||
regardless of how they are packaged. This License gives no |
||||
permission to license the work in any other way, but it does not |
||||
invalidate such permission if you have separately received it. |
||||
|
||||
d) If the work has interactive user interfaces, each must display |
||||
Appropriate Legal Notices; however, if the Program has interactive |
||||
interfaces that do not display Appropriate Legal Notices, your |
||||
work need not make them do so. |
||||
|
||||
A compilation of a covered work with other separate and independent |
||||
works, which are not by their nature extensions of the covered work, |
||||
and which are not combined with it such as to form a larger program, |
||||
in or on a volume of a storage or distribution medium, is called an |
||||
"aggregate" if the compilation and its resulting copyright are not |
||||
used to limit the access or legal rights of the compilation's users |
||||
beyond what the individual works permit. Inclusion of a covered work |
||||
in an aggregate does not cause this License to apply to the other |
||||
parts of the aggregate. |
||||
|
||||
6. Conveying Non-Source Forms. |
||||
|
||||
You may convey a covered work in object code form under the terms |
||||
of sections 4 and 5, provided that you also convey the |
||||
machine-readable Corresponding Source under the terms of this License, |
||||
in one of these ways: |
||||
|
||||
a) Convey the object code in, or embodied in, a physical product |
||||
(including a physical distribution medium), accompanied by the |
||||
Corresponding Source fixed on a durable physical medium |
||||
customarily used for software interchange. |
||||
|
||||
b) Convey the object code in, or embodied in, a physical product |
||||
(including a physical distribution medium), accompanied by a |
||||
written offer, valid for at least three years and valid for as |
||||
long as you offer spare parts or customer support for that product |
||||
model, to give anyone who possesses the object code either (1) a |
||||
copy of the Corresponding Source for all the software in the |
||||
product that is covered by this License, on a durable physical |
||||
medium customarily used for software interchange, for a price no |
||||
more than your reasonable cost of physically performing this |
||||
conveying of source, or (2) access to copy the |
||||
Corresponding Source from a network server at no charge. |
||||
|
||||
c) Convey individual copies of the object code with a copy of the |
||||
written offer to provide the Corresponding Source. This |
||||
alternative is allowed only occasionally and noncommercially, and |
||||
only if you received the object code with such an offer, in accord |
||||
with subsection 6b. |
||||
|
||||
d) Convey the object code by offering access from a designated |
||||
place (gratis or for a charge), and offer equivalent access to the |
||||
Corresponding Source in the same way through the same place at no |
||||
further charge. You need not require recipients to copy the |
||||
Corresponding Source along with the object code. If the place to |
||||
copy the object code is a network server, the Corresponding Source |
||||
may be on a different server (operated by you or a third party) |
||||
that supports equivalent copying facilities, provided you maintain |
||||
clear directions next to the object code saying where to find the |
||||
Corresponding Source. Regardless of what server hosts the |
||||
Corresponding Source, you remain obligated to ensure that it is |
||||
available for as long as needed to satisfy these requirements. |
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided |
||||
you inform other peers where the object code and Corresponding |
||||
Source of the work are being offered to the general public at no |
||||
charge under subsection 6d. |
||||
|
||||
A separable portion of the object code, whose source code is excluded |
||||
from the Corresponding Source as a System Library, need not be |
||||
included in conveying the object code work. |
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any |
||||
tangible personal property which is normally used for personal, family, |
||||
or household purposes, or (2) anything designed or sold for incorporation |
||||
into a dwelling. In determining whether a product is a consumer product, |
||||
doubtful cases shall be resolved in favor of coverage. For a particular |
||||
product received by a particular user, "normally used" refers to a |
||||
typical or common use of that class of product, regardless of the status |
||||
of the particular user or of the way in which the particular user |
||||
actually uses, or expects or is expected to use, the product. A product |
||||
is a consumer product regardless of whether the product has substantial |
||||
commercial, industrial or non-consumer uses, unless such uses represent |
||||
the only significant mode of use of the product. |
||||
|
||||
"Installation Information" for a User Product means any methods, |
||||
procedures, authorization keys, or other information required to install |
||||
and execute modified versions of a covered work in that User Product from |
||||
a modified version of its Corresponding Source. The information must |
||||
suffice to ensure that the continued functioning of the modified object |
||||
code is in no case prevented or interfered with solely because |
||||
modification has been made. |
||||
|
||||
If you convey an object code work under this section in, or with, or |
||||
specifically for use in, a User Product, and the conveying occurs as |
||||
part of a transaction in which the right of possession and use of the |
||||
User Product is transferred to the recipient in perpetuity or for a |
||||
fixed term (regardless of how the transaction is characterized), the |
||||
Corresponding Source conveyed under this section must be accompanied |
||||
by the Installation Information. But this requirement does not apply |
||||
if neither you nor any third party retains the ability to install |
||||
modified object code on the User Product (for example, the work has |
||||
been installed in ROM). |
||||
|
||||
The requirement to provide Installation Information does not include a |
||||
requirement to continue to provide support service, warranty, or updates |
||||
for a work that has been modified or installed by the recipient, or for |
||||
the User Product in which it has been modified or installed. Access to a |
||||
network may be denied when the modification itself materially and |
||||
adversely affects the operation of the network or violates the rules and |
||||
protocols for communication across the network. |
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, |
||||
in accord with this section must be in a format that is publicly |
||||
documented (and with an implementation available to the public in |
||||
source code form), and must require no special password or key for |
||||
unpacking, reading or copying. |
||||
|
||||
7. Additional Terms. |
||||
|
||||
"Additional permissions" are terms that supplement the terms of this |
||||
License by making exceptions from one or more of its conditions. |
||||
Additional permissions that are applicable to the entire Program shall |
||||
be treated as though they were included in this License, to the extent |
||||
that they are valid under applicable law. If additional permissions |
||||
apply only to part of the Program, that part may be used separately |
||||
under those permissions, but the entire Program remains governed by |
||||
this License without regard to the additional permissions. |
||||
|
||||
When you convey a copy of a covered work, you may at your option |
||||
remove any additional permissions from that copy, or from any part of |
||||
it. (Additional permissions may be written to require their own |
||||
removal in certain cases when you modify the work.) You may place |
||||
additional permissions on material, added by you to a covered work, |
||||
for which you have or can give appropriate copyright permission. |
||||
|
||||
Notwithstanding any other provision of this License, for material you |
||||
add to a covered work, you may (if authorized by the copyright holders of |
||||
that material) supplement the terms of this License with terms: |
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the |
||||
terms of sections 15 and 16 of this License; or |
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or |
||||
author attributions in that material or in the Appropriate Legal |
||||
Notices displayed by works containing it; or |
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or |
||||
requiring that modified versions of such material be marked in |
||||
reasonable ways as different from the original version; or |
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or |
||||
authors of the material; or |
||||
|
||||
e) Declining to grant rights under trademark law for use of some |
||||
trade names, trademarks, or service marks; or |
||||
|
||||
f) Requiring indemnification of licensors and authors of that |
||||
material by anyone who conveys the material (or modified versions of |
||||
it) with contractual assumptions of liability to the recipient, for |
||||
any liability that these contractual assumptions directly impose on |
||||
those licensors and authors. |
||||
|
||||
All other non-permissive additional terms are considered "further |
||||
restrictions" within the meaning of section 10. If the Program as you |
||||
received it, or any part of it, contains a notice stating that it is |
||||
governed by this License along with a term that is a further |
||||
restriction, you may remove that term. If a license document contains |
||||
a further restriction but permits relicensing or conveying under this |
||||
License, you may add to a covered work material governed by the terms |
||||
of that license document, provided that the further restriction does |
||||
not survive such relicensing or conveying. |
||||
|
||||
If you add terms to a covered work in accord with this section, you |
||||
must place, in the relevant source files, a statement of the |
||||
additional terms that apply to those files, or a notice indicating |
||||
where to find the applicable terms. |
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the |
||||
form of a separately written license, or stated as exceptions; |
||||
the above requirements apply either way. |
||||
|
||||
8. Termination. |
||||
|
||||
You may not propagate or modify a covered work except as expressly |
||||
provided under this License. Any attempt otherwise to propagate or |
||||
modify it is void, and will automatically terminate your rights under |
||||
this License (including any patent licenses granted under the third |
||||
paragraph of section 11). |
||||
|
||||
However, if you cease all violation of this License, then your |
||||
license from a particular copyright holder is reinstated (a) |
||||
provisionally, unless and until the copyright holder explicitly and |
||||
finally terminates your license, and (b) permanently, if the copyright |
||||
holder fails to notify you of the violation by some reasonable means |
||||
prior to 60 days after the cessation. |
||||
|
||||
Moreover, your license from a particular copyright holder is |
||||
reinstated permanently if the copyright holder notifies you of the |
||||
violation by some reasonable means, this is the first time you have |
||||
received notice of violation of this License (for any work) from that |
||||
copyright holder, and you cure the violation prior to 30 days after |
||||
your receipt of the notice. |
||||
|
||||
Termination of your rights under this section does not terminate the |
||||
licenses of parties who have received copies or rights from you under |
||||
this License. If your rights have been terminated and not permanently |
||||
reinstated, you do not qualify to receive new licenses for the same |
||||
material under section 10. |
||||
|
||||
9. Acceptance Not Required for Having Copies. |
||||
|
||||
You are not required to accept this License in order to receive or |
||||
run a copy of the Program. Ancillary propagation of a covered work |
||||
occurring solely as a consequence of using peer-to-peer transmission |
||||
to receive a copy likewise does not require acceptance. However, |
||||
nothing other than this License grants you permission to propagate or |
||||
modify any covered work. These actions infringe copyright if you do |
||||
not accept this License. Therefore, by modifying or propagating a |
||||
covered work, you indicate your acceptance of this License to do so. |
||||
|
||||
10. Automatic Licensing of Downstream Recipients. |
||||
|
||||
Each time you convey a covered work, the recipient automatically |
||||
receives a license from the original licensors, to run, modify and |
||||
propagate that work, subject to this License. You are not responsible |
||||
for enforcing compliance by third parties with this License. |
||||
|
||||
An "entity transaction" is a transaction transferring control of an |
||||
organization, or substantially all assets of one, or subdividing an |
||||
organization, or merging organizations. If propagation of a covered |
||||
work results from an entity transaction, each party to that |
||||
transaction who receives a copy of the work also receives whatever |
||||
licenses to the work the party's predecessor in interest had or could |
||||
give under the previous paragraph, plus a right to possession of the |
||||
Corresponding Source of the work from the predecessor in interest, if |
||||
the predecessor has it or can get it with reasonable efforts. |
||||
|
||||
You may not impose any further restrictions on the exercise of the |
||||
rights granted or affirmed under this License. For example, you may |
||||
not impose a license fee, royalty, or other charge for exercise of |
||||
rights granted under this License, and you may not initiate litigation |
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that |
||||
any patent claim is infringed by making, using, selling, offering for |
||||
sale, or importing the Program or any portion of it. |
||||
|
||||
11. Patents. |
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this |
||||
License of the Program or a work on which the Program is based. The |
||||
work thus licensed is called the contributor's "contributor version". |
||||
|
||||
A contributor's "essential patent claims" are all patent claims |
||||
owned or controlled by the contributor, whether already acquired or |
||||
hereafter acquired, that would be infringed by some manner, permitted |
||||
by this License, of making, using, or selling its contributor version, |
||||
but do not include claims that would be infringed only as a |
||||
consequence of further modification of the contributor version. For |
||||
purposes of this definition, "control" includes the right to grant |
||||
patent sublicenses in a manner consistent with the requirements of |
||||
this License. |
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free |
||||
patent license under the contributor's essential patent claims, to |
||||
make, use, sell, offer for sale, import and otherwise run, modify and |
||||
propagate the contents of its contributor version. |
||||
|
||||
In the following three paragraphs, a "patent license" is any express |
||||
agreement or commitment, however denominated, not to enforce a patent |
||||
(such as an express permission to practice a patent or covenant not to |
||||
sue for patent infringement). To "grant" such a patent license to a |
||||
party means to make such an agreement or commitment not to enforce a |
||||
patent against the party. |
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, |
||||
and the Corresponding Source of the work is not available for anyone |
||||
to copy, free of charge and under the terms of this License, through a |
||||
publicly available network server or other readily accessible means, |
||||
then you must either (1) cause the Corresponding Source to be so |
||||
available, or (2) arrange to deprive yourself of the benefit of the |
||||
patent license for this particular work, or (3) arrange, in a manner |
||||
consistent with the requirements of this License, to extend the patent |
||||
license to downstream recipients. "Knowingly relying" means you have |
||||
actual knowledge that, but for the patent license, your conveying the |
||||
covered work in a country, or your recipient's use of the covered work |
||||
in a country, would infringe one or more identifiable patents in that |
||||
country that you have reason to believe are valid. |
||||
|
||||
If, pursuant to or in connection with a single transaction or |
||||
arrangement, you convey, or propagate by procuring conveyance of, a |
||||
covered work, and grant a patent license to some of the parties |
||||
receiving the covered work authorizing them to use, propagate, modify |
||||
or convey a specific copy of the covered work, then the patent license |
||||
you grant is automatically extended to all recipients of the covered |
||||
work and works based on it. |
||||
|
||||
A patent license is "discriminatory" if it does not include within |
||||
the scope of its coverage, prohibits the exercise of, or is |
||||
conditioned on the non-exercise of one or more of the rights that are |
||||
specifically granted under this License. You may not convey a covered |
||||
work if you are a party to an arrangement with a third party that is |
||||
in the business of distributing software, under which you make payment |
||||
to the third party based on the extent of your activity of conveying |
||||
the work, and under which the third party grants, to any of the |
||||
parties who would receive the covered work from you, a discriminatory |
||||
patent license (a) in connection with copies of the covered work |
||||
conveyed by you (or copies made from those copies), or (b) primarily |
||||
for and in connection with specific products or compilations that |
||||
contain the covered work, unless you entered into that arrangement, |
||||
or that patent license was granted, prior to 28 March 2007. |
||||
|
||||
Nothing in this License shall be construed as excluding or limiting |
||||
any implied license or other defenses to infringement that may |
||||
otherwise be available to you under applicable patent law. |
||||
|
||||
12. No Surrender of Others' Freedom. |
||||
|
||||
If 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 convey a |
||||
covered work so as to satisfy simultaneously your obligations under this |
||||
License and any other pertinent obligations, then as a consequence you may |
||||
not convey it at all. For example, if you agree to terms that obligate you |
||||
to collect a royalty for further conveying from those to whom you convey |
||||
the Program, the only way you could satisfy both those terms and this |
||||
License would be to refrain entirely from conveying the Program. |
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License. |
||||
|
||||
Notwithstanding any other provision of this License, if you modify the |
||||
Program, your modified version must prominently offer all users |
||||
interacting with it remotely through a computer network (if your version |
||||
supports such interaction) an opportunity to receive the Corresponding |
||||
Source of your version by providing access to the Corresponding Source |
||||
from a network server at no charge, through some standard or customary |
||||
means of facilitating copying of software. This Corresponding Source |
||||
shall include the Corresponding Source for any work covered by version 3 |
||||
of the GNU General Public License that is incorporated pursuant to the |
||||
following paragraph. |
||||
|
||||
Notwithstanding any other provision of this License, you have |
||||
permission to link or combine any covered work with a work licensed |
||||
under version 3 of the GNU General Public License into a single |
||||
combined work, and to convey the resulting work. The terms of this |
||||
License will continue to apply to the part which is the covered work, |
||||
but the work with which it is combined will remain governed by version |
||||
3 of the GNU General Public License. |
||||
|
||||
14. Revised Versions of this License. |
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of |
||||
the GNU Affero 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 that a certain numbered version of the GNU Affero General |
||||
Public License "or any later version" applies to it, you have the |
||||
option of following the terms and conditions either of that numbered |
||||
version or of any later version published by the Free Software |
||||
Foundation. If the Program does not specify a version number of the |
||||
GNU Affero General Public License, you may choose any version ever published |
||||
by the Free Software Foundation. |
||||
|
||||
If the Program specifies that a proxy can decide which future |
||||
versions of the GNU Affero General Public License can be used, that proxy's |
||||
public statement of acceptance of a version permanently authorizes you |
||||
to choose that version for the Program. |
||||
|
||||
Later license versions may give you additional or different |
||||
permissions. However, no additional obligations are imposed on any |
||||
author or copyright holder as a result of your choosing to follow a |
||||
later version. |
||||
|
||||
15. Disclaimer of Warranty. |
||||
|
||||
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. |
||||
|
||||
16. Limitation of Liability. |
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
||||
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. |
||||
|
||||
17. Interpretation of Sections 15 and 16. |
||||
|
||||
If the disclaimer of warranty and limitation of liability provided |
||||
above cannot be given local legal effect according to their terms, |
||||
reviewing courts shall apply local law that most closely approximates |
||||
an absolute waiver of all civil liability in connection with the |
||||
Program, unless a warranty or assumption of liability accompanies a |
||||
copy of the Program in return for a fee. |
||||
|
||||
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 |
||||
state 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 Affero General Public License as published by |
||||
the Free Software Foundation, either version 3 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 Affero General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Affero General Public License |
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
Also add information on how to contact you by electronic and paper mail. |
||||
|
||||
If your software can interact with users remotely through a computer |
||||
network, you should also make sure that it provides a way for users to |
||||
get its source. For example, if your program is a web application, its |
||||
interface could display a "Source" link that leads users to an archive |
||||
of the code. There are many ways you could offer source, and different |
||||
solutions will be better for different programs; see section 13 for the |
||||
specific requirements. |
||||
|
||||
You should also get your employer (if you work as a programmer) or school, |
||||
if any, to sign a "copyright disclaimer" for the program, if necessary. |
||||
For more information on this, and how to apply and follow the GNU AGPL, see |
||||
<http://www.gnu.org/licenses/>. |
||||
@ -0,0 +1,29 @@ |
||||
/** |
||||
* Copyright (C) 2013-2014 KO GmbH <copyright@kogmbh.com> |
||||
* |
||||
* @licstart |
||||
* This file is part of WebODF. |
||||
* |
||||
* WebODF is free software: you can redistribute it and/or modify it |
||||
* under the terms of the GNU Affero General Public License (GNU AGPL) |
||||
* as published by the Free Software Foundation, either version 3 of |
||||
* the License, or (at your option) any later version. |
||||
* |
||||
* WebODF 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 Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with WebODF. If not, see <http://www.gnu.org/licenses/>. |
||||
* @licend |
||||
* |
||||
* @source: http://www.webodf.org/ |
||||
* @source: https://github.com/kogmbh/WebODF/ |
||||
*/ |
||||
|
||||
@namespace cursor url(urn:webodf:names:cursor); |
||||
|
||||
.caret { |
||||
opacity: 0 !important; |
||||
} |
||||
@ -0,0 +1,219 @@ |
||||
/** |
||||
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com> |
||||
* |
||||
* @licstart |
||||
* This file is part of WebODF. |
||||
* |
||||
* WebODF is free software: you can redistribute it and/or modify it |
||||
* under the terms of the GNU Affero General Public License (GNU AGPL) |
||||
* as published by the Free Software Foundation, either version 3 of |
||||
* the License, or (at your option) any later version. |
||||
* |
||||
* WebODF 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 Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||
* @licend |
||||
* |
||||
* @source: http://www.webodf.org/
|
||||
* @source: https://github.com/kogmbh/WebODF/
|
||||
*/ |
||||
|
||||
/*global runtime, document, odf, gui, console, webodf*/ |
||||
|
||||
function ODFViewerPlugin() { |
||||
"use strict"; |
||||
|
||||
function init(callback) { |
||||
var lib = document.createElement('script'), |
||||
pluginCSS; |
||||
|
||||
lib.async = false; |
||||
lib.src = './webodf.js'; |
||||
lib.type = 'text/javascript'; |
||||
lib.onload = function () { |
||||
runtime.loadClass('gui.HyperlinkClickHandler'); |
||||
runtime.loadClass('odf.OdfCanvas'); |
||||
runtime.loadClass('ops.Session'); |
||||
runtime.loadClass('gui.CaretManager'); |
||||
runtime.loadClass("gui.HyperlinkTooltipView"); |
||||
runtime.loadClass('gui.SessionController'); |
||||
runtime.loadClass('gui.SvgSelectionView'); |
||||
runtime.loadClass('gui.SelectionViewManager'); |
||||
runtime.loadClass('gui.ShadowCursor'); |
||||
runtime.loadClass('gui.SessionView'); |
||||
|
||||
callback(); |
||||
}; |
||||
|
||||
document.getElementsByTagName('head')[0].appendChild(lib); |
||||
|
||||
pluginCSS = document.createElement('link'); |
||||
pluginCSS.setAttribute("rel", "stylesheet"); |
||||
pluginCSS.setAttribute("type", "text/css"); |
||||
pluginCSS.setAttribute("href", "./ODFViewerPlugin.css"); |
||||
document.head.appendChild(pluginCSS); |
||||
} |
||||
|
||||
// that should probably be provided by webodf
|
||||
function nsResolver(prefix) { |
||||
var ns = { |
||||
'draw' : "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", |
||||
'presentation' : "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0", |
||||
'text' : "urn:oasis:names:tc:opendocument:xmlns:text:1.0", |
||||
'office' : "urn:oasis:names:tc:opendocument:xmlns:office:1.0" |
||||
}; |
||||
return ns[prefix] || console.log('prefix [' + prefix + '] unknown.'); |
||||
} |
||||
|
||||
var self = this, |
||||
pluginName = "WebODF", |
||||
pluginURL = "http://webodf.org", |
||||
odfCanvas = null, |
||||
odfElement = null, |
||||
initialized = false, |
||||
root = null, |
||||
documentType = null, |
||||
pages = [], |
||||
currentPage = null; |
||||
|
||||
this.initialize = function (viewerElement, documentUrl) { |
||||
// If the URL has a fragment (#...), try to load the file it represents
|
||||
init(function () { |
||||
var session, |
||||
sessionController, |
||||
sessionView, |
||||
odtDocument, |
||||
shadowCursor, |
||||
selectionViewManager, |
||||
caretManager, |
||||
localMemberId = 'localuser', |
||||
hyperlinkTooltipView, |
||||
eventManager; |
||||
|
||||
odfElement = document.getElementById('canvas'); |
||||
odfCanvas = new odf.OdfCanvas(odfElement); |
||||
odfCanvas.load(documentUrl); |
||||
|
||||
odfCanvas.addListener('statereadychange', function () { |
||||
root = odfCanvas.odfContainer().rootElement; |
||||
initialized = true; |
||||
documentType = odfCanvas.odfContainer().getDocumentType(root); |
||||
if (documentType === 'text') { |
||||
odfCanvas.enableAnnotations(true, false); |
||||
|
||||
session = new ops.Session(odfCanvas); |
||||
odtDocument = session.getOdtDocument(); |
||||
shadowCursor = new gui.ShadowCursor(odtDocument); |
||||
sessionController = new gui.SessionController(session, localMemberId, shadowCursor, {}); |
||||
eventManager = sessionController.getEventManager(); |
||||
caretManager = new gui.CaretManager(sessionController); |
||||
selectionViewManager = new gui.SelectionViewManager(gui.SvgSelectionView); |
||||
sessionView = new gui.SessionView({ |
||||
caretAvatarsInitiallyVisible: false |
||||
}, localMemberId, session, sessionController.getSessionConstraints(), caretManager, selectionViewManager); |
||||
selectionViewManager.registerCursor(shadowCursor); |
||||
hyperlinkTooltipView = new gui.HyperlinkTooltipView(odfCanvas, |
||||
sessionController.getHyperlinkClickHandler().getModifier); |
||||
eventManager.subscribe("mousemove", hyperlinkTooltipView.showTooltip); |
||||
eventManager.subscribe("mouseout", hyperlinkTooltipView.hideTooltip); |
||||
|
||||
var op = new ops.OpAddMember(); |
||||
op.init({ |
||||
memberid: localMemberId, |
||||
setProperties: { |
||||
fillName: runtime.tr("Unknown Author"), |
||||
color: "blue" |
||||
} |
||||
}); |
||||
session.enqueue([op]); |
||||
sessionController.insertLocalCursor(); |
||||
} |
||||
|
||||
self.onLoad(); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
this.isSlideshow = function () { |
||||
return documentType === 'presentation'; |
||||
}; |
||||
|
||||
this.onLoad = function () {}; |
||||
|
||||
this.getWidth = function () { |
||||
return odfElement.clientWidth; |
||||
}; |
||||
|
||||
this.getHeight = function () { |
||||
return odfElement.clientHeight; |
||||
}; |
||||
|
||||
this.fitToWidth = function (width) { |
||||
odfCanvas.fitToWidth(width); |
||||
}; |
||||
|
||||
this.fitToHeight = function (height) { |
||||
odfCanvas.fitToHeight(height); |
||||
}; |
||||
|
||||
this.fitToPage = function (width, height) { |
||||
odfCanvas.fitToContainingElement(width, height); |
||||
}; |
||||
|
||||
this.fitSmart = function (width) { |
||||
odfCanvas.fitSmart(width); |
||||
}; |
||||
|
||||
this.getZoomLevel = function () { |
||||
return odfCanvas.getZoomLevel(); |
||||
}; |
||||
|
||||
this.setZoomLevel = function (value) { |
||||
odfCanvas.setZoomLevel(value); |
||||
}; |
||||
|
||||
// return a list of tuples (pagename, pagenode)
|
||||
this.getPages = function () { |
||||
var pageNodes = Array.prototype.slice.call(root.getElementsByTagNameNS(nsResolver('draw'), 'page')), |
||||
pages = [], |
||||
i, |
||||
tuple; |
||||
|
||||
for (i = 0; i < pageNodes.length; i += 1) { |
||||
tuple = [ |
||||
pageNodes[i].getAttribute('draw:name'), |
||||
pageNodes[i] |
||||
]; |
||||
pages.push(tuple); |
||||
} |
||||
return pages; |
||||
}; |
||||
|
||||
this.showPage = function (n) { |
||||
odfCanvas.showPage(n); |
||||
}; |
||||
|
||||
this.getPluginName = function () { |
||||
return pluginName; |
||||
}; |
||||
|
||||
this.getPluginVersion = function () { |
||||
var version; |
||||
|
||||
if (String(typeof webodf) !== "undefined") { |
||||
version = webodf.Version; |
||||
} else { |
||||
version = "Unknown"; |
||||
} |
||||
|
||||
return version; |
||||
}; |
||||
|
||||
this.getPluginURL = function () { |
||||
return pluginURL; |
||||
}; |
||||
} |
||||
@ -0,0 +1,36 @@ |
||||
.page { |
||||
margin: 7px auto 7px auto; |
||||
position: relative; |
||||
overflow: hidden; |
||||
background-clip: content-box; |
||||
background-color: white; |
||||
|
||||
box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
-webkit-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
-moz-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
-ms-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
-o-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
} |
||||
|
||||
.textLayer { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
color: #000; |
||||
font-family: sans-serif; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.textLayer > div { |
||||
color: transparent; |
||||
position: absolute; |
||||
line-height: 1; |
||||
white-space: pre; |
||||
cursor: text; |
||||
} |
||||
|
||||
::selection { background:rgba(0,0,255,0.3); } |
||||
::-moz-selection { background:rgba(0,0,255,0.3); } |
||||
|
||||
@ -0,0 +1,364 @@ |
||||
/** |
||||
* @license |
||||
* Copyright (C) 2013-2014 KO GmbH <copyright@kogmbh.com> |
||||
* |
||||
* @licstart |
||||
* The JavaScript code in this page is free software: you can redistribute it |
||||
* and/or modify it under the terms of the GNU Affero General Public License |
||||
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of |
||||
* the License, or (at your option) any later version. The code is distributed |
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this code. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
* As additional permission under GNU AGPL version 3 section 7, you |
||||
* may distribute non-source (e.g., minimized or compacted) forms of |
||||
* that code without the copy of the GNU GPL normally required by |
||||
* section 4, provided you include this license notice and a URL |
||||
* through which recipients can access the Corresponding Source. |
||||
* |
||||
* As a special exception to the AGPL, any HTML file which merely makes function |
||||
* calls to this code, and for that purpose includes it by reference shall be |
||||
* deemed a separate work for copyright law purposes. In addition, the copyright |
||||
* holders of this code give you permission to combine this code with free |
||||
* software libraries that are released under the GNU LGPL. You may copy and |
||||
* distribute such a system following the terms of the GNU AGPL for this code |
||||
* and the LGPL for the libraries. If you modify this code, you may extend this |
||||
* exception to your version of the code, but you are not obligated to do so. |
||||
* If you do not wish to do so, delete this exception statement from your |
||||
* version. |
||||
* |
||||
* This license applies to this entire compilation. |
||||
* @licend |
||||
* @source: http://viewerjs.org/
|
||||
* @source: http://github.com/kogmbh/ViewerJS
|
||||
*/ |
||||
|
||||
/*global document, PDFJS, console, TextLayerBuilder*/ |
||||
|
||||
|
||||
function PDFViewerPlugin() { |
||||
"use strict"; |
||||
|
||||
function loadScript(path, callback) { |
||||
var script = document.createElement('script'); |
||||
script.async = false; |
||||
script.src = path; |
||||
script.type = 'text/javascript'; |
||||
script.onload = callback || script.onload; |
||||
document.getElementsByTagName('head')[0].appendChild(script); |
||||
} |
||||
|
||||
function init(callback) { |
||||
var pdfLib, textLayerLib, pluginCSS; |
||||
|
||||
loadScript('./compatibility.js', function () { |
||||
loadScript('./pdf.js'); |
||||
loadScript('./pdf_find_bar.js'); |
||||
loadScript('./pdf_find_controller.js'); |
||||
loadScript('./ui_utils.js'); |
||||
loadScript('./text_layer_builder.js'); |
||||
loadScript('./pdfjsversion.js', callback); |
||||
}); |
||||
|
||||
pluginCSS = document.createElement('link'); |
||||
pluginCSS.setAttribute("rel", "stylesheet"); |
||||
pluginCSS.setAttribute("type", "text/css"); |
||||
pluginCSS.setAttribute("href", "./PDFViewerPlugin.css"); |
||||
document.head.appendChild(pluginCSS); |
||||
} |
||||
|
||||
var self = this, |
||||
pages = [], |
||||
domPages = [], |
||||
pageText = [], |
||||
renderingStates = [], |
||||
RENDERING = { |
||||
BLANK: 0, |
||||
RUNNING: 1, |
||||
FINISHED: 2 |
||||
}, |
||||
startedTextExtraction = false, |
||||
container = null, |
||||
initialized = false, |
||||
pdfDocument = null, |
||||
pageViewScroll = null, |
||||
isPresentationMode = false, |
||||
scale = 1, |
||||
currentPage = 1, |
||||
pageWidth, |
||||
pageHeight, |
||||
createdPageCount = 0; |
||||
|
||||
function scrollIntoView(elem) { |
||||
elem.parentNode.scrollTop = elem.offsetTop; |
||||
} |
||||
|
||||
function isScrolledIntoView(elem) { |
||||
var docViewTop = container.scrollTop, |
||||
docViewBottom = docViewTop + container.clientHeight, |
||||
elemTop = elem.offsetTop, |
||||
elemBottom = elemTop + elem.clientHeight; |
||||
|
||||
// Is in view if either the top or the bottom of the page is between the
|
||||
// document viewport bounds,
|
||||
// or if the top is above the viewport and the bottom is below it.
|
||||
return (elemTop >= docViewTop && elemTop < docViewBottom) |
||||
|| (elemBottom >= docViewTop && elemBottom < docViewBottom) |
||||
|| (elemTop < docViewTop && elemBottom >= docViewBottom); |
||||
} |
||||
|
||||
function getDomPage(page) { |
||||
return domPages[page.pageInfo.pageIndex]; |
||||
} |
||||
function getPageText(page) { |
||||
return pageText[page.pageInfo.pageIndex]; |
||||
} |
||||
function getRenderingStatus(page) { |
||||
return renderingStates[page.pageInfo.pageIndex]; |
||||
} |
||||
function setRenderingStatus(page, renderStatus) { |
||||
renderingStates[page.pageInfo.pageIndex] = renderStatus; |
||||
} |
||||
|
||||
function updatePageDimensions(page, width, height) { |
||||
var domPage = getDomPage(page), |
||||
canvas = domPage.getElementsByTagName('canvas')[0], |
||||
textLayer = domPage.getElementsByTagName('div')[0], |
||||
cssScale = 'scale(' + scale + ', ' + scale + ')'; |
||||
|
||||
domPage.style.width = width + "px"; |
||||
domPage.style.height = height + "px"; |
||||
|
||||
canvas.width = width; |
||||
canvas.height = height; |
||||
|
||||
textLayer.style.width = width + "px"; |
||||
textLayer.style.height = height + "px"; |
||||
|
||||
CustomStyle.setProp('transform', textLayer, cssScale); |
||||
CustomStyle.setProp('transformOrigin', textLayer, '0% 0%'); |
||||
|
||||
// Once the page dimension is updated, the rendering state is blank.
|
||||
setRenderingStatus(page, RENDERING.BLANK); |
||||
} |
||||
|
||||
function renderPage(page) { |
||||
var domPage = getDomPage(page), |
||||
textLayer = getPageText(page), |
||||
canvas = domPage.getElementsByTagName('canvas')[0]; |
||||
|
||||
if (getRenderingStatus(page) === RENDERING.BLANK) { |
||||
setRenderingStatus(page, RENDERING.RUNNING); |
||||
page.render({ |
||||
canvasContext: canvas.getContext('2d'), |
||||
textLayer: textLayer, |
||||
viewport: page.getViewport(scale) |
||||
}).promise.then(function () { |
||||
setRenderingStatus(page, RENDERING.FINISHED); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
function createPage(page) { |
||||
var pageNumber, |
||||
textLayerDiv, |
||||
textLayer, |
||||
canvas, |
||||
domPage, |
||||
viewport; |
||||
|
||||
pageNumber = page.pageInfo.pageIndex + 1; |
||||
|
||||
viewport = page.getViewport(scale); |
||||
|
||||
domPage = document.createElement('div'); |
||||
domPage.id = 'pageContainer' + pageNumber; |
||||
domPage.className = 'page'; |
||||
|
||||
canvas = document.createElement('canvas'); |
||||
canvas.id = 'canvas' + pageNumber; |
||||
|
||||
textLayerDiv = document.createElement('div'); |
||||
textLayerDiv.className = 'textLayer'; |
||||
textLayerDiv.id = 'textLayer' + pageNumber; |
||||
|
||||
container.appendChild(domPage); |
||||
domPage.appendChild(canvas); |
||||
domPage.appendChild(textLayerDiv); |
||||
|
||||
pages.push(page); |
||||
domPages.push(domPage); |
||||
renderingStates.push(RENDERING.BLANK); |
||||
|
||||
updatePageDimensions(page, viewport.width, viewport.height); |
||||
pageWidth = viewport.width; |
||||
pageHeight = viewport.height; |
||||
|
||||
textLayer = new TextLayerBuilder({ |
||||
textLayerDiv: textLayerDiv, |
||||
pageIndex: pageNumber - 1 |
||||
}); |
||||
page.getTextContent().then(function (textContent) { |
||||
textLayer.setTextContent(textContent); |
||||
}); |
||||
pageText.push(textLayer); |
||||
|
||||
createdPageCount += 1; |
||||
if (createdPageCount === (pdfDocument.numPages)) { |
||||
if (self.isSlideshow()) { |
||||
domPages.forEach(function (pageElement) { |
||||
pageElement.style.display = "none"; |
||||
}); |
||||
self.showPage(1); |
||||
} |
||||
self.onLoad(); |
||||
} |
||||
} |
||||
|
||||
this.initialize = function (viewContainer, location) { |
||||
var self = this, |
||||
i, |
||||
pluginCSS; |
||||
|
||||
init(function () { |
||||
PDFJS.workerSrc = "./pdf.worker.js"; |
||||
PDFJS.getDocument(location).then(function loadPDF(doc) { |
||||
pdfDocument = doc; |
||||
container = viewContainer; |
||||
|
||||
for (i = 0; i < pdfDocument.numPages; i += 1) { |
||||
pdfDocument.getPage(i + 1).then(createPage); |
||||
} |
||||
|
||||
initialized = true; |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
this.isSlideshow = function () { |
||||
// A very simple but generally true guess - if the width is greater than the height, treat it as a slideshow
|
||||
return pageWidth > pageHeight; |
||||
}; |
||||
|
||||
this.onLoad = function () {}; |
||||
|
||||
this.getPages = function () { |
||||
return domPages; |
||||
}; |
||||
|
||||
this.getWidth = function () { |
||||
return pageWidth; |
||||
}; |
||||
|
||||
this.getHeight = function () { |
||||
return pageHeight; |
||||
}; |
||||
|
||||
this.fitToWidth = function (width) { |
||||
var zoomLevel; |
||||
|
||||
if (self.getWidth() === width) { |
||||
return; |
||||
} |
||||
zoomLevel = width / pageWidth; |
||||
self.setZoomLevel(zoomLevel); |
||||
}; |
||||
|
||||
this.fitToHeight = function (height) { |
||||
var zoomLevel; |
||||
|
||||
if (self.getHeight() === height) { |
||||
return; |
||||
} |
||||
zoomLevel = height / pageHeight; |
||||
self.setZoomLevel(zoomLevel); |
||||
}; |
||||
|
||||
this.fitToPage = function (width, height) { |
||||
var zoomLevel = width / pageWidth; |
||||
if (height / pageHeight < zoomLevel) { |
||||
zoomLevel = height / pageHeight; |
||||
} |
||||
self.setZoomLevel(zoomLevel); |
||||
}; |
||||
|
||||
this.fitSmart = function (width, height) { |
||||
var zoomLevel = width / pageWidth; |
||||
if (height && (height / pageHeight) < zoomLevel) { |
||||
zoomLevel = height / pageHeight; |
||||
} |
||||
zoomLevel = Math.min(1.0, zoomLevel); |
||||
self.setZoomLevel(zoomLevel); |
||||
}; |
||||
|
||||
this.setZoomLevel = function (zoomLevel) { |
||||
var i; |
||||
|
||||
if (scale !== zoomLevel) { |
||||
scale = zoomLevel; |
||||
|
||||
for (i = 0; i < pages.length; i += 1) { |
||||
updatePageDimensions(pages[i], pageWidth * scale, pageHeight * scale); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
this.getZoomLevel = function () { |
||||
return scale; |
||||
}; |
||||
|
||||
this.onScroll = function () { |
||||
var i; |
||||
|
||||
for (i = 0; i < domPages.length; i += 1) { |
||||
if (isScrolledIntoView(domPages[i])) { |
||||
if (getRenderingStatus(pages[i]) === RENDERING.BLANK) { |
||||
renderPage(pages[i]); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
this.getPageInView = function () { |
||||
var i; |
||||
|
||||
if (self.isSlideshow()) { |
||||
return currentPage; |
||||
} else { |
||||
for (i = 0; i < domPages.length; i += 1) { |
||||
if (isScrolledIntoView(domPages[i])) { |
||||
return i + 1; |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
this.showPage = function (n) { |
||||
if (self.isSlideshow()) { |
||||
domPages[currentPage - 1].style.display = "none"; |
||||
currentPage = n; |
||||
domPages[n - 1].style.display = "block"; |
||||
} else { |
||||
scrollIntoView(domPages[n - 1]); |
||||
} |
||||
}; |
||||
|
||||
this.getPluginName = function () { |
||||
return "PDF.js" |
||||
}; |
||||
|
||||
this.getPluginVersion = function () { |
||||
var version = (String(typeof pdfjs_version) !== "undefined" |
||||
? pdfjs_version |
||||
: "From Source" |
||||
); |
||||
return version; |
||||
}; |
||||
|
||||
this.getPluginURL = function () { |
||||
return "https://github.com/mozilla/pdf.js/"; |
||||
}; |
||||
} |
||||
@ -0,0 +1,86 @@ |
||||
/** |
||||
* @license |
||||
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com> |
||||
* |
||||
* @licstart |
||||
* The JavaScript code in this page is free software: you can redistribute it |
||||
* and/or modify it under the terms of the GNU Affero General Public License |
||||
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of |
||||
* the License, or (at your option) any later version. The code is distributed |
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this code. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
* As additional permission under GNU AGPL version 3 section 7, you |
||||
* may distribute non-source (e.g., minimized or compacted) forms of |
||||
* that code without the copy of the GNU GPL normally required by |
||||
* section 4, provided you include this license notice and a URL |
||||
* through which recipients can access the Corresponding Source. |
||||
* |
||||
* As a special exception to the AGPL, any HTML file which merely makes function |
||||
* calls to this code, and for that purpose includes it by reference shall be |
||||
* deemed a separate work for copyright law purposes. In addition, the copyright |
||||
* holders of this code give you permission to combine this code with free |
||||
* software libraries that are released under the GNU LGPL. You may copy and |
||||
* distribute such a system following the terms of the GNU AGPL for this code |
||||
* and the LGPL for the libraries. If you modify this code, you may extend this |
||||
* exception to your version of the code, but you are not obligated to do so. |
||||
* If you do not wish to do so, delete this exception statement from your |
||||
* version. |
||||
* |
||||
* This license applies to this entire compilation. |
||||
* @licend |
||||
* @source: http://viewerjs.org/
|
||||
* @source: http://github.com/kogmbh/ViewerJS
|
||||
*/ |
||||
|
||||
/*global document, window, Viewer, ODFViewerPlugin, PDFViewerPlugin*/ |
||||
|
||||
var viewer; |
||||
|
||||
function loadPlugin(pluginName, callback) { |
||||
"use strict"; |
||||
var script, style; |
||||
|
||||
// Load script
|
||||
script = document.createElement('script'); |
||||
script.async = false; |
||||
script.onload = callback; |
||||
script.src = pluginName + '.js'; |
||||
script.type = 'text/javascript'; |
||||
document.getElementsByTagName('head')[0].appendChild(script); |
||||
} |
||||
|
||||
function loadDocument(documentUrl) { |
||||
"use strict"; |
||||
|
||||
if (documentUrl) { |
||||
var extension = documentUrl.split('.').pop(), |
||||
Plugin; |
||||
extension = extension.toLowerCase(); |
||||
|
||||
switch (extension) { |
||||
case 'odt': |
||||
case 'odp': |
||||
case 'ods': |
||||
case 'fodt': |
||||
loadPlugin('./ODFViewerPlugin', function () { |
||||
Plugin = ODFViewerPlugin; |
||||
}); |
||||
break; |
||||
case 'pdf': |
||||
loadPlugin('./PDFViewerPlugin', function () { |
||||
Plugin = PDFViewerPlugin; |
||||
}); |
||||
break; |
||||
} |
||||
|
||||
window.onload = function () { |
||||
if (Plugin) { |
||||
viewer = new Viewer(new Plugin()); |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,491 @@ |
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* Copyright 2012 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/* globals VBArray, PDFJS */ |
||||
|
||||
'use strict'; |
||||
|
||||
// Initializing PDFJS global object here, it case if we need to change/disable
|
||||
// some PDF.js features, e.g. range requests
|
||||
if (typeof PDFJS === 'undefined') { |
||||
(typeof window !== 'undefined' ? window : this).PDFJS = {}; |
||||
} |
||||
|
||||
// Checking if the typed arrays are supported
|
||||
(function checkTypedArrayCompatibility() { |
||||
if (typeof Uint8Array !== 'undefined') { |
||||
// some mobile versions do not support subarray (e.g. safari 5 / iOS)
|
||||
if (typeof Uint8Array.prototype.subarray === 'undefined') { |
||||
Uint8Array.prototype.subarray = function subarray(start, end) { |
||||
return new Uint8Array(this.slice(start, end)); |
||||
}; |
||||
Float32Array.prototype.subarray = function subarray(start, end) { |
||||
return new Float32Array(this.slice(start, end)); |
||||
}; |
||||
} |
||||
|
||||
// some mobile version might not support Float64Array
|
||||
if (typeof Float64Array === 'undefined') |
||||
window.Float64Array = Float32Array; |
||||
|
||||
return; |
||||
} |
||||
|
||||
function subarray(start, end) { |
||||
return new TypedArray(this.slice(start, end)); |
||||
} |
||||
|
||||
function setArrayOffset(array, offset) { |
||||
if (arguments.length < 2) |
||||
offset = 0; |
||||
for (var i = 0, n = array.length; i < n; ++i, ++offset) |
||||
this[offset] = array[i] & 0xFF; |
||||
} |
||||
|
||||
function TypedArray(arg1) { |
||||
var result; |
||||
if (typeof arg1 === 'number') { |
||||
result = []; |
||||
for (var i = 0; i < arg1; ++i) |
||||
result[i] = 0; |
||||
} else if ('slice' in arg1) { |
||||
result = arg1.slice(0); |
||||
} else { |
||||
result = []; |
||||
for (var i = 0, n = arg1.length; i < n; ++i) { |
||||
result[i] = arg1[i]; |
||||
} |
||||
} |
||||
|
||||
result.subarray = subarray; |
||||
result.buffer = result; |
||||
result.byteLength = result.length; |
||||
result.set = setArrayOffset; |
||||
|
||||
if (typeof arg1 === 'object' && arg1.buffer) |
||||
result.buffer = arg1.buffer; |
||||
|
||||
return result; |
||||
} |
||||
|
||||
window.Uint8Array = TypedArray; |
||||
|
||||
// we don't need support for set, byteLength for 32-bit array
|
||||
// so we can use the TypedArray as well
|
||||
window.Uint32Array = TypedArray; |
||||
window.Int32Array = TypedArray; |
||||
window.Uint16Array = TypedArray; |
||||
window.Float32Array = TypedArray; |
||||
window.Float64Array = TypedArray; |
||||
})(); |
||||
|
||||
// URL = URL || webkitURL
|
||||
(function normalizeURLObject() { |
||||
if (!window.URL) { |
||||
window.URL = window.webkitURL; |
||||
} |
||||
})(); |
||||
|
||||
// Object.create() ?
|
||||
(function checkObjectCreateCompatibility() { |
||||
if (typeof Object.create !== 'undefined') |
||||
return; |
||||
|
||||
Object.create = function objectCreate(proto) { |
||||
function Constructor() {} |
||||
Constructor.prototype = proto; |
||||
return new Constructor(); |
||||
}; |
||||
})(); |
||||
|
||||
// Object.defineProperty() ?
|
||||
(function checkObjectDefinePropertyCompatibility() { |
||||
if (typeof Object.defineProperty !== 'undefined') { |
||||
var definePropertyPossible = true; |
||||
try { |
||||
// some browsers (e.g. safari) cannot use defineProperty() on DOM objects
|
||||
// and thus the native version is not sufficient
|
||||
Object.defineProperty(new Image(), 'id', { value: 'test' }); |
||||
// ... another test for android gb browser for non-DOM objects
|
||||
var Test = function Test() {}; |
||||
Test.prototype = { get id() { } }; |
||||
Object.defineProperty(new Test(), 'id', |
||||
{ value: '', configurable: true, enumerable: true, writable: false }); |
||||
} catch (e) { |
||||
definePropertyPossible = false; |
||||
} |
||||
if (definePropertyPossible) return; |
||||
} |
||||
|
||||
Object.defineProperty = function objectDefineProperty(obj, name, def) { |
||||
delete obj[name]; |
||||
if ('get' in def) |
||||
obj.__defineGetter__(name, def['get']); |
||||
if ('set' in def) |
||||
obj.__defineSetter__(name, def['set']); |
||||
if ('value' in def) { |
||||
obj.__defineSetter__(name, function objectDefinePropertySetter(value) { |
||||
this.__defineGetter__(name, function objectDefinePropertyGetter() { |
||||
return value; |
||||
}); |
||||
return value; |
||||
}); |
||||
obj[name] = def.value; |
||||
} |
||||
}; |
||||
})(); |
||||
|
||||
// Object.keys() ?
|
||||
(function checkObjectKeysCompatibility() { |
||||
if (typeof Object.keys !== 'undefined') |
||||
return; |
||||
|
||||
Object.keys = function objectKeys(obj) { |
||||
var result = []; |
||||
for (var i in obj) { |
||||
if (obj.hasOwnProperty(i)) |
||||
result.push(i); |
||||
} |
||||
return result; |
||||
}; |
||||
})(); |
||||
|
||||
// No readAsArrayBuffer ?
|
||||
(function checkFileReaderReadAsArrayBuffer() { |
||||
if (typeof FileReader === 'undefined') |
||||
return; // FileReader is not implemented
|
||||
var frPrototype = FileReader.prototype; |
||||
// Older versions of Firefox might not have readAsArrayBuffer
|
||||
if ('readAsArrayBuffer' in frPrototype) |
||||
return; // readAsArrayBuffer is implemented
|
||||
Object.defineProperty(frPrototype, 'readAsArrayBuffer', { |
||||
value: function fileReaderReadAsArrayBuffer(blob) { |
||||
var fileReader = new FileReader(); |
||||
var originalReader = this; |
||||
fileReader.onload = function fileReaderOnload(evt) { |
||||
var data = evt.target.result; |
||||
var buffer = new ArrayBuffer(data.length); |
||||
var uint8Array = new Uint8Array(buffer); |
||||
|
||||
for (var i = 0, ii = data.length; i < ii; i++) |
||||
uint8Array[i] = data.charCodeAt(i); |
||||
|
||||
Object.defineProperty(originalReader, 'result', { |
||||
value: buffer, |
||||
enumerable: true, |
||||
writable: false, |
||||
configurable: true |
||||
}); |
||||
|
||||
var event = document.createEvent('HTMLEvents'); |
||||
event.initEvent('load', false, false); |
||||
originalReader.dispatchEvent(event); |
||||
}; |
||||
fileReader.readAsBinaryString(blob); |
||||
} |
||||
}); |
||||
})(); |
||||
|
||||
// No XMLHttpRequest.response ?
|
||||
(function checkXMLHttpRequestResponseCompatibility() { |
||||
var xhrPrototype = XMLHttpRequest.prototype; |
||||
if (!('overrideMimeType' in xhrPrototype)) { |
||||
// IE10 might have response, but not overrideMimeType
|
||||
Object.defineProperty(xhrPrototype, 'overrideMimeType', { |
||||
value: function xmlHttpRequestOverrideMimeType(mimeType) {} |
||||
}); |
||||
} |
||||
if ('response' in xhrPrototype || |
||||
'mozResponseArrayBuffer' in xhrPrototype || |
||||
'mozResponse' in xhrPrototype || |
||||
'responseArrayBuffer' in xhrPrototype) |
||||
return; |
||||
// IE9 ?
|
||||
if (typeof VBArray !== 'undefined') { |
||||
Object.defineProperty(xhrPrototype, 'response', { |
||||
get: function xmlHttpRequestResponseGet() { |
||||
return new Uint8Array(new VBArray(this.responseBody).toArray()); |
||||
} |
||||
}); |
||||
return; |
||||
} |
||||
|
||||
// other browsers
|
||||
function responseTypeSetter() { |
||||
// will be only called to set "arraybuffer"
|
||||
this.overrideMimeType('text/plain; charset=x-user-defined'); |
||||
} |
||||
if (typeof xhrPrototype.overrideMimeType === 'function') { |
||||
Object.defineProperty(xhrPrototype, 'responseType', |
||||
{ set: responseTypeSetter }); |
||||
} |
||||
function responseGetter() { |
||||
var text = this.responseText; |
||||
var i, n = text.length; |
||||
var result = new Uint8Array(n); |
||||
for (i = 0; i < n; ++i) |
||||
result[i] = text.charCodeAt(i) & 0xFF; |
||||
return result; |
||||
} |
||||
Object.defineProperty(xhrPrototype, 'response', { get: responseGetter }); |
||||
})(); |
||||
|
||||
// window.btoa (base64 encode function) ?
|
||||
(function checkWindowBtoaCompatibility() { |
||||
if ('btoa' in window) |
||||
return; |
||||
|
||||
var digits = |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; |
||||
|
||||
window.btoa = function windowBtoa(chars) { |
||||
var buffer = ''; |
||||
var i, n; |
||||
for (i = 0, n = chars.length; i < n; i += 3) { |
||||
var b1 = chars.charCodeAt(i) & 0xFF; |
||||
var b2 = chars.charCodeAt(i + 1) & 0xFF; |
||||
var b3 = chars.charCodeAt(i + 2) & 0xFF; |
||||
var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4); |
||||
var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64; |
||||
var d4 = i + 2 < n ? (b3 & 0x3F) : 64; |
||||
buffer += (digits.charAt(d1) + digits.charAt(d2) + |
||||
digits.charAt(d3) + digits.charAt(d4)); |
||||
} |
||||
return buffer; |
||||
}; |
||||
})(); |
||||
|
||||
// window.atob (base64 encode function) ?
|
||||
(function checkWindowAtobCompatibility() { |
||||
if ('atob' in window) |
||||
return; |
||||
|
||||
// https://github.com/davidchambers/Base64.js
|
||||
var digits = |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; |
||||
window.atob = function (input) { |
||||
input = input.replace(/=+$/, ''); |
||||
if (input.length % 4 == 1) throw new Error('bad atob input'); |
||||
for ( |
||||
// initialize result and counters
|
||||
var bc = 0, bs, buffer, idx = 0, output = ''; |
||||
// get next character
|
||||
buffer = input.charAt(idx++); |
||||
// character found in table?
|
||||
// initialize bit storage and add its ascii value
|
||||
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, |
||||
// and if not first of each 4 characters,
|
||||
// convert the first 8 bits to one ascii character
|
||||
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 |
||||
) { |
||||
// try to find character in table (0-63, not found => -1)
|
||||
buffer = digits.indexOf(buffer); |
||||
} |
||||
return output; |
||||
}; |
||||
})(); |
||||
|
||||
// Function.prototype.bind ?
|
||||
(function checkFunctionPrototypeBindCompatibility() { |
||||
if (typeof Function.prototype.bind !== 'undefined') |
||||
return; |
||||
|
||||
Function.prototype.bind = function functionPrototypeBind(obj) { |
||||
var fn = this, headArgs = Array.prototype.slice.call(arguments, 1); |
||||
var bound = function functionPrototypeBindBound() { |
||||
var args = Array.prototype.concat.apply(headArgs, arguments); |
||||
return fn.apply(obj, args); |
||||
}; |
||||
return bound; |
||||
}; |
||||
})(); |
||||
|
||||
// HTMLElement dataset property
|
||||
(function checkDatasetProperty() { |
||||
var div = document.createElement('div'); |
||||
if ('dataset' in div) |
||||
return; // dataset property exists
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'dataset', { |
||||
get: function() { |
||||
if (this._dataset) |
||||
return this._dataset; |
||||
|
||||
var dataset = {}; |
||||
for (var j = 0, jj = this.attributes.length; j < jj; j++) { |
||||
var attribute = this.attributes[j]; |
||||
if (attribute.name.substring(0, 5) != 'data-') |
||||
continue; |
||||
var key = attribute.name.substring(5).replace(/\-([a-z])/g, |
||||
function(all, ch) { return ch.toUpperCase(); }); |
||||
dataset[key] = attribute.value; |
||||
} |
||||
|
||||
Object.defineProperty(this, '_dataset', { |
||||
value: dataset, |
||||
writable: false, |
||||
enumerable: false |
||||
}); |
||||
return dataset; |
||||
}, |
||||
enumerable: true |
||||
}); |
||||
})(); |
||||
|
||||
// HTMLElement classList property
|
||||
(function checkClassListProperty() { |
||||
var div = document.createElement('div'); |
||||
if ('classList' in div) |
||||
return; // classList property exists
|
||||
|
||||
function changeList(element, itemName, add, remove) { |
||||
var s = element.className || ''; |
||||
var list = s.split(/\s+/g); |
||||
if (list[0] === '') list.shift(); |
||||
var index = list.indexOf(itemName); |
||||
if (index < 0 && add) |
||||
list.push(itemName); |
||||
if (index >= 0 && remove) |
||||
list.splice(index, 1); |
||||
element.className = list.join(' '); |
||||
return (index >= 0); |
||||
} |
||||
|
||||
var classListPrototype = { |
||||
add: function(name) { |
||||
changeList(this.element, name, true, false); |
||||
}, |
||||
contains: function(name) { |
||||
return changeList(this.element, name, false, false); |
||||
}, |
||||
remove: function(name) { |
||||
changeList(this.element, name, false, true); |
||||
}, |
||||
toggle: function(name) { |
||||
changeList(this.element, name, true, true); |
||||
} |
||||
}; |
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'classList', { |
||||
get: function() { |
||||
if (this._classList) |
||||
return this._classList; |
||||
|
||||
var classList = Object.create(classListPrototype, { |
||||
element: { |
||||
value: this, |
||||
writable: false, |
||||
enumerable: true |
||||
} |
||||
}); |
||||
Object.defineProperty(this, '_classList', { |
||||
value: classList, |
||||
writable: false, |
||||
enumerable: false |
||||
}); |
||||
return classList; |
||||
}, |
||||
enumerable: true |
||||
}); |
||||
})(); |
||||
|
||||
// Check console compatibility
|
||||
(function checkConsoleCompatibility() { |
||||
if (!('console' in window)) { |
||||
window.console = { |
||||
log: function() {}, |
||||
error: function() {}, |
||||
warn: function() {} |
||||
}; |
||||
} else if (!('bind' in console.log)) { |
||||
// native functions in IE9 might not have bind
|
||||
console.log = (function(fn) { |
||||
return function(msg) { return fn(msg); }; |
||||
})(console.log); |
||||
console.error = (function(fn) { |
||||
return function(msg) { return fn(msg); }; |
||||
})(console.error); |
||||
console.warn = (function(fn) { |
||||
return function(msg) { return fn(msg); }; |
||||
})(console.warn); |
||||
} |
||||
})(); |
||||
|
||||
// Check onclick compatibility in Opera
|
||||
(function checkOnClickCompatibility() { |
||||
// workaround for reported Opera bug DSK-354448:
|
||||
// onclick fires on disabled buttons with opaque content
|
||||
function ignoreIfTargetDisabled(event) { |
||||
if (isDisabled(event.target)) { |
||||
event.stopPropagation(); |
||||
} |
||||
} |
||||
function isDisabled(node) { |
||||
return node.disabled || (node.parentNode && isDisabled(node.parentNode)); |
||||
} |
||||
if (navigator.userAgent.indexOf('Opera') != -1) { |
||||
// use browser detection since we cannot feature-check this bug
|
||||
document.addEventListener('click', ignoreIfTargetDisabled, true); |
||||
} |
||||
})(); |
||||
|
||||
// Checks if possible to use URL.createObjectURL()
|
||||
(function checkOnBlobSupport() { |
||||
// sometimes IE loosing the data created with createObjectURL(), see #3977
|
||||
if (navigator.userAgent.indexOf('Trident') >= 0) { |
||||
PDFJS.disableCreateObjectURL = true; |
||||
} |
||||
})(); |
||||
|
||||
// Checks if navigator.language is supported
|
||||
(function checkNavigatorLanguage() { |
||||
if ('language' in navigator) |
||||
return; |
||||
Object.defineProperty(navigator, 'language', { |
||||
get: function navigatorLanguage() { |
||||
var language = navigator.userLanguage || 'en-US'; |
||||
return language.substring(0, 2).toLowerCase() + |
||||
language.substring(2).toUpperCase(); |
||||
}, |
||||
enumerable: true |
||||
}); |
||||
})(); |
||||
|
||||
(function checkRangeRequests() { |
||||
// Safari has issues with cached range requests see:
|
||||
// https://github.com/mozilla/pdf.js/issues/3260
|
||||
// Last tested with version 6.0.4.
|
||||
var isSafari = Object.prototype.toString.call( |
||||
window.HTMLElement).indexOf('Constructor') > 0; |
||||
|
||||
// Older versions of Android (pre 3.0) has issues with range requests, see:
|
||||
// https://github.com/mozilla/pdf.js/issues/3381.
|
||||
// Make sure that we only match webkit-based Android browsers,
|
||||
// since Firefox/Fennec works as expected.
|
||||
var regex = /Android\s[0-2][^\d]/; |
||||
var isOldAndroid = regex.test(navigator.userAgent); |
||||
|
||||
if (isSafari || isOldAndroid) { |
||||
PDFJS.disableRange = true; |
||||
} |
||||
})(); |
||||
|
||||
// Check if the browser supports manipulation of the history.
|
||||
(function checkHistoryManipulation() { |
||||
if (!window.history.pushState) { |
||||
PDFJS.disableHistory = true; |
||||
} |
||||
})(); |
||||
@ -0,0 +1,27 @@ |
||||
/* This is just a sample file with CSS rules. You should write your own @font-face declarations |
||||
* to add support for your desired fonts. |
||||
*/ |
||||
|
||||
@font-face { |
||||
font-family: 'Novecentowide Book'; |
||||
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot"); |
||||
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot?#iefix") format("embedded-opentype"), |
||||
url("/ViewerJS/fonts/Novecentowide-Bold-webfont.woff") format("woff"), |
||||
url("/fonts/Novecentowide-Bold-webfont.ttf") format("truetype"), |
||||
url("/fonts/Novecentowide-Bold-webfont.svg#NovecentowideBookBold") format("svg"); |
||||
font-weight: normal; |
||||
font-style: normal; |
||||
} |
||||
|
||||
@font-face { |
||||
font-family: 'exotica'; |
||||
src: url('/ViewerJS/fonts/Exotica-webfont.eot'); |
||||
src: url('/ViewerJS/fonts/Exotica-webfont.eot?#iefix') format('embedded-opentype'), |
||||
url('/ViewerJS/fonts/Exotica-webfont.woff') format('woff'), |
||||
url('/ViewerJS/fonts/Exotica-webfont.ttf') format('truetype'), |
||||
url('/ViewerJS/fonts/Exotica-webfont.svg#exoticamedium') format('svg'); |
||||
font-weight: normal; |
||||
font-style: normal; |
||||
|
||||
} |
||||
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 512 B |
|
After Width: | Height: | Size: 491 B |
|
After Width: | Height: | Size: 237 B |
|
After Width: | Height: | Size: 353 B |
|
After Width: | Height: | Size: 344 B |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 228 B |
|
After Width: | Height: | Size: 143 B |
@ -0,0 +1,129 @@ |
||||
<!DOCTYPE html> |
||||
|
||||
<!-- |
||||
Copyright (C) 2012-2014 KO GmbH <copyright@kogmbh.com> |
||||
|
||||
@licstart |
||||
This file is part of WebODF. |
||||
|
||||
WebODF is free software: you can redistribute it and/or modify it |
||||
under the terms of the GNU Affero General Public License (GNU AGPL) |
||||
as published by the Free Software Foundation, either version 3 of |
||||
the License, or (at your option) any later version. |
||||
|
||||
WebODF 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 Affero General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Affero General Public License |
||||
along with WebODF. If not, see <http://www.gnu.org/licenses/>. |
||||
@licend |
||||
|
||||
@source: http://www.webodf.org/ |
||||
@source: https://github.com/kogmbh/WebODF/ |
||||
--> |
||||
|
||||
<!-- |
||||
This file is a derivative from a part of Mozilla's PDF.js project. The |
||||
original license header follows. |
||||
--> |
||||
|
||||
<!-- |
||||
Copyright 2012 Mozilla Foundation |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
--> |
||||
|
||||
<html dir="ltr" lang="en-US"> |
||||
<head> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> |
||||
<title>ViewerJS</title> |
||||
<!-- If you want to use custom CSS (@font-face rules, for example) you should uncomment |
||||
the following reference and use a local.css file for that. See the example.local.css |
||||
file for a sample. |
||||
<link rel="stylesheet" type="text/css" href="local.css" media="screen"/> |
||||
--> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> |
||||
<link rel="stylesheet" type="text/css" href="viewer.css" media="screen"/> |
||||
<script src="viewer.js" type="text/javascript" charset="utf-8"></script> |
||||
<script src="PluginLoader.js" type="text/javascript" charset="utf-8"></script> |
||||
<script> |
||||
loadDocument(window.location.hash); |
||||
</script> |
||||
</head> |
||||
|
||||
<body> |
||||
<div id = "viewer"> |
||||
<div id = "titlebar"> |
||||
<div id = "documentName"></div> |
||||
<div id = "toolbarRight"> |
||||
<button id = "presentation" class = "toolbarButton presentation" title = "Presentation"></button> |
||||
<button id = "fullscreen" class = "toolbarButton fullscreen" title = "Fullscreen"></button> |
||||
<button id = "download" class = "toolbarButton download" title = "Download"></button> |
||||
</div> |
||||
</div> |
||||
<div id = "toolbarContainer"> |
||||
<div id = "toolbar"> |
||||
<div id = "toolbarLeft"> |
||||
<div id = "navButtons" class = "splitToolbarButton"> |
||||
<button id = "previous" class = "toolbarButton pageUp" title = "Previous Page"></button> |
||||
<div class="splitToolbarButtonSeparator"></div> |
||||
<button id = "next" class = "toolbarButton pageDown" title = "Next Page"></button> |
||||
</div> |
||||
<label id = "pageNumberLabel" class = "toolbarLabel" for = "pageNumber">Page:</label> |
||||
<input type = "number" id = "pageNumber" class = "toolbarField pageNumber"/> |
||||
<span id = "numPages" class = "toolbarLabel"></span> |
||||
</div> |
||||
<div id = "toolbarMiddleContainer" class = "outerCenter"> |
||||
<div id = "toolbarMiddle" class = "innerCenter"> |
||||
<div id = 'zoomButtons' class = "splitToolbarButton"> |
||||
<button id = "zoomOut" class = "toolbarButton zoomOut" title = "Zoom Out"></button> |
||||
<div class="splitToolbarButtonSeparator"></div> |
||||
<button id = "zoomIn" class = "toolbarButton zoomIn" title = "Zoom In"></button> |
||||
</div> |
||||
<span id="scaleSelectContainer" class="dropdownToolbarButton"> |
||||
<select id="scaleSelect" title="Zoom" oncontextmenu="return false;"> |
||||
<option id="pageAutoOption" value="auto" selected>Automatic</option> |
||||
<option id="pageActualOption" value="page-actual">Actual Size</option> |
||||
<option id="pageWidthOption" value="page-width">Full Width</option> |
||||
<option id="customScaleOption" value="custom"> </option> |
||||
<option value="0.5">50%</option> |
||||
<option value="0.75">75%</option> |
||||
<option value="1">100%</option> |
||||
<option value="1.25">125%</option> |
||||
<option value="1.5">150%</option> |
||||
<option value="2">200%</option> |
||||
</select> |
||||
</span> |
||||
<div id = "sliderContainer"> |
||||
<div id = "slider"></div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div id = "canvasContainer"> |
||||
<div id = "canvas"></div> |
||||
</div> |
||||
<div id = "overlayNavigator"> |
||||
<div id = "previousPage"></div> |
||||
<div id = "nextPage"></div> |
||||
</div> |
||||
<div id = "overlayCloseButton"> |
||||
✖ |
||||
</div> |
||||
<div id = "dialogOverlay"></div> |
||||
</div> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,175 @@ |
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
||||
/* Copyright 2012 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
'use strict'; |
||||
|
||||
/* globals PDFFindController, FindStates, mozL10n */ |
||||
|
||||
/** |
||||
* Creates a "search bar" given set of DOM elements |
||||
* that act as controls for searching, or for setting |
||||
* search preferences in the UI. This object also sets |
||||
* up the appropriate events for the controls. Actual |
||||
* searching is done by PDFFindController |
||||
*/ |
||||
var PDFFindBar = { |
||||
|
||||
opened: false, |
||||
bar: null, |
||||
toggleButton: null, |
||||
findField: null, |
||||
highlightAll: null, |
||||
caseSensitive: null, |
||||
findMsg: null, |
||||
findStatusIcon: null, |
||||
findPreviousButton: null, |
||||
findNextButton: null, |
||||
|
||||
initialize: function(options) { |
||||
if(typeof PDFFindController === 'undefined' || PDFFindController === null) { |
||||
throw 'PDFFindBar cannot be initialized ' + |
||||
'without a PDFFindController instance.'; |
||||
} |
||||
|
||||
this.bar = options.bar; |
||||
this.toggleButton = options.toggleButton; |
||||
this.findField = options.findField; |
||||
this.highlightAll = options.highlightAllCheckbox; |
||||
this.caseSensitive = options.caseSensitiveCheckbox; |
||||
this.findMsg = options.findMsg; |
||||
this.findStatusIcon = options.findStatusIcon; |
||||
this.findPreviousButton = options.findPreviousButton; |
||||
this.findNextButton = options.findNextButton; |
||||
|
||||
var self = this; |
||||
this.toggleButton.addEventListener('click', function() { |
||||
self.toggle(); |
||||
}); |
||||
|
||||
this.findField.addEventListener('input', function() { |
||||
self.dispatchEvent(''); |
||||
}); |
||||
|
||||
this.bar.addEventListener('keydown', function(evt) { |
||||
switch (evt.keyCode) { |
||||
case 13: // Enter
|
||||
if (evt.target === self.findField) { |
||||
self.dispatchEvent('again', evt.shiftKey); |
||||
} |
||||
break; |
||||
case 27: // Escape
|
||||
self.close(); |
||||
break; |
||||
} |
||||
}); |
||||
|
||||
this.findPreviousButton.addEventListener('click', |
||||
function() { self.dispatchEvent('again', true); } |
||||
); |
||||
|
||||
this.findNextButton.addEventListener('click', function() { |
||||
self.dispatchEvent('again', false); |
||||
}); |
||||
|
||||
this.highlightAll.addEventListener('click', function() { |
||||
self.dispatchEvent('highlightallchange'); |
||||
}); |
||||
|
||||
this.caseSensitive.addEventListener('click', function() { |
||||
self.dispatchEvent('casesensitivitychange'); |
||||
}); |
||||
}, |
||||
|
||||
dispatchEvent: function(aType, aFindPrevious) { |
||||
var event = document.createEvent('CustomEvent'); |
||||
event.initCustomEvent('find' + aType, true, true, { |
||||
query: this.findField.value, |
||||
caseSensitive: this.caseSensitive.checked, |
||||
highlightAll: this.highlightAll.checked, |
||||
findPrevious: aFindPrevious |
||||
}); |
||||
return window.dispatchEvent(event); |
||||
}, |
||||
|
||||
updateUIState: function(state, previous) { |
||||
var notFound = false; |
||||
var findMsg = ''; |
||||
var status = ''; |
||||
|
||||
switch (state) { |
||||
case FindStates.FIND_FOUND: |
||||
break; |
||||
|
||||
case FindStates.FIND_PENDING: |
||||
status = 'pending'; |
||||
break; |
||||
|
||||
case FindStates.FIND_NOTFOUND: |
||||
findMsg = mozL10n.get('find_not_found', null, 'Phrase not found'); |
||||
notFound = true; |
||||
break; |
||||
|
||||
case FindStates.FIND_WRAPPED: |
||||
if (previous) { |
||||
findMsg = mozL10n.get('find_reached_top', null, |
||||
'Reached top of document, continued from bottom'); |
||||
} else { |
||||
findMsg = mozL10n.get('find_reached_bottom', null, |
||||
'Reached end of document, continued from top'); |
||||
} |
||||
break; |
||||
} |
||||
|
||||
if (notFound) { |
||||
this.findField.classList.add('notFound'); |
||||
} else { |
||||
this.findField.classList.remove('notFound'); |
||||
} |
||||
|
||||
this.findField.setAttribute('data-status', status); |
||||
this.findMsg.textContent = findMsg; |
||||
}, |
||||
|
||||
open: function() { |
||||
if (!this.opened) { |
||||
this.opened = true; |
||||
this.toggleButton.classList.add('toggled'); |
||||
this.bar.classList.remove('hidden'); |
||||
} |
||||
|
||||
this.findField.select(); |
||||
this.findField.focus(); |
||||
}, |
||||
|
||||
close: function() { |
||||
if (!this.opened) return; |
||||
|
||||
this.opened = false; |
||||
this.toggleButton.classList.remove('toggled'); |
||||
this.bar.classList.add('hidden'); |
||||
|
||||
PDFFindController.active = false; |
||||
}, |
||||
|
||||
toggle: function() { |
||||
if (this.opened) { |
||||
this.close(); |
||||
} else { |
||||
this.open(); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
@ -0,0 +1,355 @@ |
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
||||
/* Copyright 2012 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
'use strict'; |
||||
|
||||
/* globals PDFFindBar, PDFJS, FindStates, FirefoxCom, Promise */ |
||||
|
||||
/** |
||||
* Provides a "search" or "find" functionality for the PDF. |
||||
* This object actually performs the search for a given string. |
||||
*/ |
||||
|
||||
var PDFFindController = { |
||||
startedTextExtraction: false, |
||||
|
||||
extractTextPromises: [], |
||||
|
||||
pendingFindMatches: {}, |
||||
|
||||
// If active, find results will be highlighted.
|
||||
active: false, |
||||
|
||||
// Stores the text for each page.
|
||||
pageContents: [], |
||||
|
||||
pageMatches: [], |
||||
|
||||
// Currently selected match.
|
||||
selected: { |
||||
pageIdx: -1, |
||||
matchIdx: -1 |
||||
}, |
||||
|
||||
// Where find algorithm currently is in the document.
|
||||
offset: { |
||||
pageIdx: null, |
||||
matchIdx: null |
||||
}, |
||||
|
||||
resumePageIdx: null, |
||||
|
||||
state: null, |
||||
|
||||
dirtyMatch: false, |
||||
|
||||
findTimeout: null, |
||||
|
||||
pdfPageSource: null, |
||||
|
||||
integratedFind: false, |
||||
|
||||
initialize: function(options) { |
||||
if(typeof PDFFindBar === 'undefined' || PDFFindBar === null) { |
||||
throw 'PDFFindController cannot be initialized ' + |
||||
'without a PDFFindController instance'; |
||||
} |
||||
|
||||
this.pdfPageSource = options.pdfPageSource; |
||||
this.integratedFind = options.integratedFind; |
||||
|
||||
var events = [ |
||||
'find', |
||||
'findagain', |
||||
'findhighlightallchange', |
||||
'findcasesensitivitychange' |
||||
]; |
||||
|
||||
this.firstPagePromise = new Promise(function (resolve) { |
||||
this.resolveFirstPage = resolve; |
||||
}.bind(this)); |
||||
this.handleEvent = this.handleEvent.bind(this); |
||||
|
||||
for (var i = 0; i < events.length; i++) { |
||||
window.addEventListener(events[i], this.handleEvent); |
||||
} |
||||
}, |
||||
|
||||
reset: function pdfFindControllerReset() { |
||||
this.startedTextExtraction = false; |
||||
this.extractTextPromises = []; |
||||
this.active = false; |
||||
}, |
||||
|
||||
calcFindMatch: function(pageIndex) { |
||||
var pageContent = this.pageContents[pageIndex]; |
||||
var query = this.state.query; |
||||
var caseSensitive = this.state.caseSensitive; |
||||
var queryLen = query.length; |
||||
|
||||
if (queryLen === 0) { |
||||
// Do nothing the matches should be wiped out already.
|
||||
return; |
||||
} |
||||
|
||||
if (!caseSensitive) { |
||||
pageContent = pageContent.toLowerCase(); |
||||
query = query.toLowerCase(); |
||||
} |
||||
|
||||
var matches = []; |
||||
|
||||
var matchIdx = -queryLen; |
||||
while (true) { |
||||
matchIdx = pageContent.indexOf(query, matchIdx + queryLen); |
||||
if (matchIdx === -1) { |
||||
break; |
||||
} |
||||
|
||||
matches.push(matchIdx); |
||||
} |
||||
this.pageMatches[pageIndex] = matches; |
||||
this.updatePage(pageIndex); |
||||
if (this.resumePageIdx === pageIndex) { |
||||
this.resumePageIdx = null; |
||||
this.nextPageMatch(); |
||||
} |
||||
}, |
||||
|
||||
extractText: function() { |
||||
if (this.startedTextExtraction) { |
||||
return; |
||||
} |
||||
this.startedTextExtraction = true; |
||||
|
||||
this.pageContents = []; |
||||
var extractTextPromisesResolves = []; |
||||
for (var i = 0, ii = this.pdfPageSource.pdfDocument.numPages; i < ii; i++) { |
||||
this.extractTextPromises.push(new Promise(function (resolve) { |
||||
extractTextPromisesResolves.push(resolve); |
||||
})); |
||||
} |
||||
|
||||
var self = this; |
||||
function extractPageText(pageIndex) { |
||||
self.pdfPageSource.pages[pageIndex].getTextContent().then( |
||||
function textContentResolved(bidiTexts) { |
||||
var str = ''; |
||||
|
||||
for (var i = 0; i < bidiTexts.length; i++) { |
||||
str += bidiTexts[i].str; |
||||
} |
||||
|
||||
// Store the pageContent as a string.
|
||||
self.pageContents.push(str); |
||||
|
||||
extractTextPromisesResolves[pageIndex](pageIndex); |
||||
if ((pageIndex + 1) < self.pdfPageSource.pages.length) |
||||
extractPageText(pageIndex + 1); |
||||
} |
||||
); |
||||
} |
||||
extractPageText(0); |
||||
}, |
||||
|
||||
handleEvent: function(e) { |
||||
if (this.state === null || e.type !== 'findagain') { |
||||
this.dirtyMatch = true; |
||||
} |
||||
this.state = e.detail; |
||||
this.updateUIState(FindStates.FIND_PENDING); |
||||
|
||||
this.firstPagePromise.then(function() { |
||||
this.extractText(); |
||||
|
||||
clearTimeout(this.findTimeout); |
||||
if (e.type === 'find') { |
||||
// Only trigger the find action after 250ms of silence.
|
||||
this.findTimeout = setTimeout(this.nextMatch.bind(this), 250); |
||||
} else { |
||||
this.nextMatch(); |
||||
} |
||||
}.bind(this)); |
||||
}, |
||||
|
||||
updatePage: function(idx) { |
||||
var page = this.pdfPageSource.pages[idx]; |
||||
|
||||
if (this.selected.pageIdx === idx) { |
||||
// If the page is selected, scroll the page into view, which triggers
|
||||
// rendering the page, which adds the textLayer. Once the textLayer is
|
||||
// build, it will scroll onto the selected match.
|
||||
page.scrollIntoView(); |
||||
} |
||||
|
||||
if (page.textLayer) { |
||||
page.textLayer.updateMatches(); |
||||
} |
||||
}, |
||||
|
||||
nextMatch: function() { |
||||
var previous = this.state.findPrevious; |
||||
var currentPageIndex = this.pdfPageSource.page - 1; |
||||
var numPages = this.pdfPageSource.pages.length; |
||||
|
||||
this.active = true; |
||||
|
||||
if (this.dirtyMatch) { |
||||
// Need to recalculate the matches, reset everything.
|
||||
this.dirtyMatch = false; |
||||
this.selected.pageIdx = this.selected.matchIdx = -1; |
||||
this.offset.pageIdx = currentPageIndex; |
||||
this.offset.matchIdx = null; |
||||
this.hadMatch = false; |
||||
this.resumePageIdx = null; |
||||
this.pageMatches = []; |
||||
var self = this; |
||||
|
||||
for (var i = 0; i < numPages; i++) { |
||||
// Wipe out any previous highlighted matches.
|
||||
this.updatePage(i); |
||||
|
||||
// As soon as the text is extracted start finding the matches.
|
||||
if (!(i in this.pendingFindMatches)) { |
||||
this.pendingFindMatches[i] = true; |
||||
this.extractTextPromises[i].then(function(pageIdx) { |
||||
delete self.pendingFindMatches[pageIdx]; |
||||
self.calcFindMatch(pageIdx); |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// If there's no query there's no point in searching.
|
||||
if (this.state.query === '') { |
||||
this.updateUIState(FindStates.FIND_FOUND); |
||||
return; |
||||
} |
||||
|
||||
// If we're waiting on a page, we return since we can't do anything else.
|
||||
if (this.resumePageIdx) { |
||||
return; |
||||
} |
||||
|
||||
var offset = this.offset; |
||||
// If there's already a matchIdx that means we are iterating through a
|
||||
// page's matches.
|
||||
if (offset.matchIdx !== null) { |
||||
var numPageMatches = this.pageMatches[offset.pageIdx].length; |
||||
if ((!previous && offset.matchIdx + 1 < numPageMatches) || |
||||
(previous && offset.matchIdx > 0)) { |
||||
// The simple case, we just have advance the matchIdx to select the next
|
||||
// match on the page.
|
||||
this.hadMatch = true; |
||||
offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1; |
||||
this.updateMatch(true); |
||||
return; |
||||
} |
||||
// We went beyond the current page's matches, so we advance to the next
|
||||
// page.
|
||||
this.advanceOffsetPage(previous); |
||||
} |
||||
// Start searching through the page.
|
||||
this.nextPageMatch(); |
||||
}, |
||||
|
||||
matchesReady: function(matches) { |
||||
var offset = this.offset; |
||||
var numMatches = matches.length; |
||||
var previous = this.state.findPrevious; |
||||
if (numMatches) { |
||||
// There were matches for the page, so initialize the matchIdx.
|
||||
this.hadMatch = true; |
||||
offset.matchIdx = previous ? numMatches - 1 : 0; |
||||
this.updateMatch(true); |
||||
// matches were found
|
||||
return true; |
||||
} else { |
||||
// No matches attempt to search the next page.
|
||||
this.advanceOffsetPage(previous); |
||||
if (offset.wrapped) { |
||||
offset.matchIdx = null; |
||||
if (!this.hadMatch) { |
||||
// No point in wrapping there were no matches.
|
||||
this.updateMatch(false); |
||||
// while matches were not found, searching for a page
|
||||
// with matches should nevertheless halt.
|
||||
return true; |
||||
} |
||||
} |
||||
// matches were not found (and searching is not done)
|
||||
return false; |
||||
} |
||||
}, |
||||
|
||||
nextPageMatch: function() { |
||||
if (this.resumePageIdx !== null) { |
||||
console.error('There can only be one pending page.'); |
||||
} |
||||
do { |
||||
var pageIdx = this.offset.pageIdx; |
||||
var matches = this.pageMatches[pageIdx]; |
||||
if (!matches) { |
||||
// The matches don't exist yet for processing by "matchesReady",
|
||||
// so set a resume point for when they do exist.
|
||||
this.resumePageIdx = pageIdx; |
||||
break; |
||||
} |
||||
} while (!this.matchesReady(matches)); |
||||
}, |
||||
|
||||
advanceOffsetPage: function(previous) { |
||||
var offset = this.offset; |
||||
var numPages = this.extractTextPromises.length; |
||||
offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1; |
||||
offset.matchIdx = null; |
||||
if (offset.pageIdx >= numPages || offset.pageIdx < 0) { |
||||
offset.pageIdx = previous ? numPages - 1 : 0; |
||||
offset.wrapped = true; |
||||
return; |
||||
} |
||||
}, |
||||
|
||||
updateMatch: function(found) { |
||||
var state = FindStates.FIND_NOTFOUND; |
||||
var wrapped = this.offset.wrapped; |
||||
this.offset.wrapped = false; |
||||
if (found) { |
||||
var previousPage = this.selected.pageIdx; |
||||
this.selected.pageIdx = this.offset.pageIdx; |
||||
this.selected.matchIdx = this.offset.matchIdx; |
||||
state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND; |
||||
// Update the currently selected page to wipe out any selected matches.
|
||||
if (previousPage !== -1 && previousPage !== this.selected.pageIdx) { |
||||
this.updatePage(previousPage); |
||||
} |
||||
} |
||||
this.updateUIState(state, this.state.findPrevious); |
||||
if (this.selected.pageIdx !== -1) { |
||||
this.updatePage(this.selected.pageIdx, true); |
||||
} |
||||
}, |
||||
|
||||
updateUIState: function(state, previous) { |
||||
if (this.integratedFind) { |
||||
FirefoxCom.request('updateFindControlState', |
||||
{result: state, findPrevious: previous}); |
||||
return; |
||||
} |
||||
PDFFindBar.updateUIState(state, previous); |
||||
} |
||||
}; |
||||
|
||||
@ -0,0 +1 @@ |
||||
var /**@const{!string}*/pdfjs_version = "d45d7bc"; |
||||
@ -0,0 +1,385 @@ |
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
||||
/* Copyright 2012 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/* globals CustomStyle, PDFFindController, scrollIntoView */ |
||||
|
||||
'use strict'; |
||||
|
||||
var FIND_SCROLL_OFFSET_TOP = -50; |
||||
var FIND_SCROLL_OFFSET_LEFT = -400; |
||||
|
||||
/** |
||||
* TextLayerBuilder provides text-selection |
||||
* functionality for the PDF. It does this |
||||
* by creating overlay divs over the PDF |
||||
* text. This divs contain text that matches |
||||
* the PDF text they are overlaying. This |
||||
* object also provides for a way to highlight |
||||
* text that is being searched for. |
||||
*/ |
||||
var TextLayerBuilder = function textLayerBuilder(options) { |
||||
var textLayerFrag = document.createDocumentFragment(); |
||||
|
||||
this.textLayerDiv = options.textLayerDiv; |
||||
this.layoutDone = false; |
||||
this.divContentDone = false; |
||||
this.pageIdx = options.pageIndex; |
||||
this.matches = []; |
||||
this.lastScrollSource = options.lastScrollSource; |
||||
this.viewport = options.viewport; |
||||
this.isViewerInPresentationMode = options.isViewerInPresentationMode; |
||||
|
||||
if(typeof PDFFindController === 'undefined') { |
||||
window.PDFFindController = null; |
||||
} |
||||
|
||||
if(typeof this.lastScrollSource === 'undefined') { |
||||
this.lastScrollSource = null; |
||||
} |
||||
|
||||
this.beginLayout = function textLayerBuilderBeginLayout() { |
||||
this.textDivs = []; |
||||
this.renderingDone = false; |
||||
}; |
||||
|
||||
this.endLayout = function textLayerBuilderEndLayout() { |
||||
this.layoutDone = true; |
||||
this.insertDivContent(); |
||||
}; |
||||
|
||||
this.renderLayer = function textLayerBuilderRenderLayer() { |
||||
var self = this; |
||||
var textDivs = this.textDivs; |
||||
var bidiTexts = this.textContent; |
||||
var textLayerDiv = this.textLayerDiv; |
||||
var canvas = document.createElement('canvas'); |
||||
var ctx = canvas.getContext('2d'); |
||||
|
||||
// No point in rendering so many divs as it'd make the browser unusable
|
||||
// even after the divs are rendered
|
||||
var MAX_TEXT_DIVS_TO_RENDER = 100000; |
||||
if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) |
||||
return; |
||||
|
||||
for (var i = 0, ii = textDivs.length; i < ii; i++) { |
||||
var textDiv = textDivs[i]; |
||||
if ('isWhitespace' in textDiv.dataset) { |
||||
continue; |
||||
} |
||||
|
||||
ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily; |
||||
var width = ctx.measureText(textDiv.textContent).width; |
||||
|
||||
if (width > 0) { |
||||
textLayerFrag.appendChild(textDiv); |
||||
var textScale = textDiv.dataset.canvasWidth / width; |
||||
var rotation = textDiv.dataset.angle; |
||||
var transform = 'scale(' + textScale + ', 1)'; |
||||
transform = 'rotate(' + rotation + 'deg) ' + transform; |
||||
CustomStyle.setProp('transform' , textDiv, transform); |
||||
CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%'); |
||||
} |
||||
} |
||||
|
||||
textLayerDiv.appendChild(textLayerFrag); |
||||
this.renderingDone = true; |
||||
this.updateMatches(); |
||||
}; |
||||
|
||||
this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() { |
||||
// Schedule renderLayout() if user has been scrolling, otherwise
|
||||
// run it right away
|
||||
var RENDER_DELAY = 200; // in ms
|
||||
var self = this; |
||||
var lastScroll = this.lastScrollSource === null ? |
||||
0 : this.lastScrollSource.lastScroll; |
||||
|
||||
if (Date.now() - lastScroll > RENDER_DELAY) { |
||||
// Render right away
|
||||
this.renderLayer(); |
||||
} else { |
||||
// Schedule
|
||||
if (this.renderTimer) |
||||
clearTimeout(this.renderTimer); |
||||
this.renderTimer = setTimeout(function() { |
||||
self.setupRenderLayoutTimer(); |
||||
}, RENDER_DELAY); |
||||
} |
||||
}; |
||||
|
||||
this.appendText = function textLayerBuilderAppendText(geom) { |
||||
var textDiv = document.createElement('div'); |
||||
|
||||
// vScale and hScale already contain the scaling to pixel units
|
||||
var fontHeight = geom.fontSize * Math.abs(geom.vScale); |
||||
textDiv.dataset.canvasWidth = geom.canvasWidth * Math.abs(geom.hScale); |
||||
textDiv.dataset.fontName = geom.fontName; |
||||
textDiv.dataset.angle = geom.angle * (180 / Math.PI); |
||||
|
||||
textDiv.style.fontSize = fontHeight + 'px'; |
||||
textDiv.style.fontFamily = geom.fontFamily; |
||||
var fontAscent = geom.ascent ? geom.ascent * fontHeight : |
||||
geom.descent ? (1 + geom.descent) * fontHeight : fontHeight; |
||||
textDiv.style.left = (geom.x + (fontAscent * Math.sin(geom.angle))) + 'px'; |
||||
textDiv.style.top = (geom.y - (fontAscent * Math.cos(geom.angle))) + 'px'; |
||||
|
||||
// The content of the div is set in the `setTextContent` function.
|
||||
|
||||
this.textDivs.push(textDiv); |
||||
}; |
||||
|
||||
this.insertDivContent = function textLayerUpdateTextContent() { |
||||
// Only set the content of the divs once layout has finished, the content
|
||||
// for the divs is available and content is not yet set on the divs.
|
||||
if (!this.layoutDone || this.divContentDone || !this.textContent) |
||||
return; |
||||
|
||||
this.divContentDone = true; |
||||
|
||||
var textDivs = this.textDivs; |
||||
var bidiTexts = this.textContent; |
||||
|
||||
for (var i = 0; i < bidiTexts.length; i++) { |
||||
var bidiText = bidiTexts[i]; |
||||
var textDiv = textDivs[i]; |
||||
if (!/\S/.test(bidiText.str)) { |
||||
textDiv.dataset.isWhitespace = true; |
||||
continue; |
||||
} |
||||
|
||||
textDiv.textContent = bidiText.str; |
||||
// TODO refactor text layer to use text content position
|
||||
/** |
||||
* var arr = this.viewport.convertToViewportPoint(bidiText.x, bidiText.y); |
||||
* textDiv.style.left = arr[0] + 'px'; |
||||
* textDiv.style.top = arr[1] + 'px'; |
||||
*/ |
||||
// bidiText.dir may be 'ttb' for vertical texts.
|
||||
textDiv.dir = bidiText.dir; |
||||
} |
||||
|
||||
this.setupRenderLayoutTimer(); |
||||
}; |
||||
|
||||
this.setTextContent = function textLayerBuilderSetTextContent(textContent) { |
||||
this.textContent = textContent; |
||||
this.insertDivContent(); |
||||
}; |
||||
|
||||
this.convertMatches = function textLayerBuilderConvertMatches(matches) { |
||||
var i = 0; |
||||
var iIndex = 0; |
||||
var bidiTexts = this.textContent; |
||||
var end = bidiTexts.length - 1; |
||||
var queryLen = PDFFindController === null ? |
||||
0 : PDFFindController.state.query.length; |
||||
|
||||
var lastDivIdx = -1; |
||||
var pos; |
||||
|
||||
var ret = []; |
||||
|
||||
// Loop over all the matches.
|
||||
for (var m = 0; m < matches.length; m++) { |
||||
var matchIdx = matches[m]; |
||||
// # Calculate the begin position.
|
||||
|
||||
// Loop over the divIdxs.
|
||||
while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) { |
||||
iIndex += bidiTexts[i].str.length; |
||||
i++; |
||||
} |
||||
|
||||
// TODO: Do proper handling here if something goes wrong.
|
||||
if (i == bidiTexts.length) { |
||||
console.error('Could not find matching mapping'); |
||||
} |
||||
|
||||
var match = { |
||||
begin: { |
||||
divIdx: i, |
||||
offset: matchIdx - iIndex |
||||
} |
||||
}; |
||||
|
||||
// # Calculate the end position.
|
||||
matchIdx += queryLen; |
||||
|
||||
// Somewhat same array as above, but use a > instead of >= to get the end
|
||||
// position right.
|
||||
while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) { |
||||
iIndex += bidiTexts[i].str.length; |
||||
i++; |
||||
} |
||||
|
||||
match.end = { |
||||
divIdx: i, |
||||
offset: matchIdx - iIndex |
||||
}; |
||||
ret.push(match); |
||||
} |
||||
|
||||
return ret; |
||||
}; |
||||
|
||||
this.renderMatches = function textLayerBuilder_renderMatches(matches) { |
||||
// Early exit if there is nothing to render.
|
||||
if (matches.length === 0) { |
||||
return; |
||||
} |
||||
|
||||
var bidiTexts = this.textContent; |
||||
var textDivs = this.textDivs; |
||||
var prevEnd = null; |
||||
var isSelectedPage = PDFFindController === null ? |
||||
false : (this.pageIdx === PDFFindController.selected.pageIdx); |
||||
|
||||
var selectedMatchIdx = PDFFindController === null ? |
||||
-1 : PDFFindController.selected.matchIdx; |
||||
|
||||
var highlightAll = PDFFindController === null ? |
||||
false : PDFFindController.state.highlightAll; |
||||
|
||||
var infty = { |
||||
divIdx: -1, |
||||
offset: undefined |
||||
}; |
||||
|
||||
function beginText(begin, className) { |
||||
var divIdx = begin.divIdx; |
||||
var div = textDivs[divIdx]; |
||||
div.textContent = ''; |
||||
|
||||
var content = bidiTexts[divIdx].str.substring(0, begin.offset); |
||||
var node = document.createTextNode(content); |
||||
if (className) { |
||||
var isSelected = isSelectedPage && |
||||
divIdx === selectedMatchIdx; |
||||
var span = document.createElement('span'); |
||||
span.className = className + (isSelected ? ' selected' : ''); |
||||
span.appendChild(node); |
||||
div.appendChild(span); |
||||
return; |
||||
} |
||||
div.appendChild(node); |
||||
} |
||||
|
||||
function appendText(from, to, className) { |
||||
var divIdx = from.divIdx; |
||||
var div = textDivs[divIdx]; |
||||
|
||||
var content = bidiTexts[divIdx].str.substring(from.offset, to.offset); |
||||
var node = document.createTextNode(content); |
||||
if (className) { |
||||
var span = document.createElement('span'); |
||||
span.className = className; |
||||
span.appendChild(node); |
||||
div.appendChild(span); |
||||
return; |
||||
} |
||||
div.appendChild(node); |
||||
} |
||||
|
||||
function highlightDiv(divIdx, className) { |
||||
textDivs[divIdx].className = className; |
||||
} |
||||
|
||||
var i0 = selectedMatchIdx, i1 = i0 + 1, i; |
||||
|
||||
if (highlightAll) { |
||||
i0 = 0; |
||||
i1 = matches.length; |
||||
} else if (!isSelectedPage) { |
||||
// Not highlighting all and this isn't the selected page, so do nothing.
|
||||
return; |
||||
} |
||||
|
||||
for (i = i0; i < i1; i++) { |
||||
var match = matches[i]; |
||||
var begin = match.begin; |
||||
var end = match.end; |
||||
|
||||
var isSelected = isSelectedPage && i === selectedMatchIdx; |
||||
var highlightSuffix = (isSelected ? ' selected' : ''); |
||||
if (isSelected && !this.isViewerInPresentationMode) { |
||||
scrollIntoView(textDivs[begin.divIdx], { top: FIND_SCROLL_OFFSET_TOP, |
||||
left: FIND_SCROLL_OFFSET_LEFT }); |
||||
} |
||||
|
||||
// Match inside new div.
|
||||
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) { |
||||
// If there was a previous div, then add the text at the end
|
||||
if (prevEnd !== null) { |
||||
appendText(prevEnd, infty); |
||||
} |
||||
// clears the divs and set the content until the begin point.
|
||||
beginText(begin); |
||||
} else { |
||||
appendText(prevEnd, begin); |
||||
} |
||||
|
||||
if (begin.divIdx === end.divIdx) { |
||||
appendText(begin, end, 'highlight' + highlightSuffix); |
||||
} else { |
||||
appendText(begin, infty, 'highlight begin' + highlightSuffix); |
||||
for (var n = begin.divIdx + 1; n < end.divIdx; n++) { |
||||
highlightDiv(n, 'highlight middle' + highlightSuffix); |
||||
} |
||||
beginText(end, 'highlight end' + highlightSuffix); |
||||
} |
||||
prevEnd = end; |
||||
} |
||||
|
||||
if (prevEnd) { |
||||
appendText(prevEnd, infty); |
||||
} |
||||
}; |
||||
|
||||
this.updateMatches = function textLayerUpdateMatches() { |
||||
// Only show matches, once all rendering is done.
|
||||
if (!this.renderingDone) |
||||
return; |
||||
|
||||
// Clear out all matches.
|
||||
var matches = this.matches; |
||||
var textDivs = this.textDivs; |
||||
var bidiTexts = this.textContent; |
||||
var clearedUntilDivIdx = -1; |
||||
|
||||
// Clear out all current matches.
|
||||
for (var i = 0; i < matches.length; i++) { |
||||
var match = matches[i]; |
||||
var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx); |
||||
for (var n = begin; n <= match.end.divIdx; n++) { |
||||
var div = textDivs[n]; |
||||
div.textContent = bidiTexts[n].str; |
||||
div.className = ''; |
||||
} |
||||
clearedUntilDivIdx = match.end.divIdx + 1; |
||||
} |
||||
|
||||
if (PDFFindController === null || !PDFFindController.active) |
||||
return; |
||||
|
||||
// Convert the matches on the page controller into the match format used
|
||||
// for the textLayer.
|
||||
this.matches = matches = |
||||
this.convertMatches(PDFFindController === null ? |
||||
[] : (PDFFindController.pageMatches[this.pageIdx] || [])); |
||||
|
||||
this.renderMatches(this.matches); |
||||
}; |
||||
}; |
||||
|
||||
@ -0,0 +1,270 @@ |
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
||||
/* Copyright 2012 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
'use strict'; |
||||
|
||||
// optimised CSS custom property getter/setter
|
||||
var CustomStyle = (function CustomStyleClosure() { |
||||
|
||||
// As noted on: http://www.zachstronaut.com/posts/2009/02/17/
|
||||
// animate-css-transforms-firefox-webkit.html
|
||||
// in some versions of IE9 it is critical that ms appear in this list
|
||||
// before Moz
|
||||
var prefixes = ['ms', 'Moz', 'Webkit', 'O']; |
||||
var _cache = { }; |
||||
|
||||
function CustomStyle() { |
||||
} |
||||
|
||||
CustomStyle.getProp = function get(propName, element) { |
||||
// check cache only when no element is given
|
||||
if (arguments.length == 1 && typeof _cache[propName] == 'string') { |
||||
return _cache[propName]; |
||||
} |
||||
|
||||
element = element || document.documentElement; |
||||
var style = element.style, prefixed, uPropName; |
||||
|
||||
// test standard property first
|
||||
if (typeof style[propName] == 'string') { |
||||
return (_cache[propName] = propName); |
||||
} |
||||
|
||||
// capitalize
|
||||
uPropName = propName.charAt(0).toUpperCase() + propName.slice(1); |
||||
|
||||
// test vendor specific properties
|
||||
for (var i = 0, l = prefixes.length; i < l; i++) { |
||||
prefixed = prefixes[i] + uPropName; |
||||
if (typeof style[prefixed] == 'string') { |
||||
return (_cache[propName] = prefixed); |
||||
} |
||||
} |
||||
|
||||
//if all fails then set to undefined
|
||||
return (_cache[propName] = 'undefined'); |
||||
}; |
||||
|
||||
CustomStyle.setProp = function set(propName, element, str) { |
||||
var prop = this.getProp(propName); |
||||
if (prop != 'undefined') |
||||
element.style[prop] = str; |
||||
}; |
||||
|
||||
return CustomStyle; |
||||
})(); |
||||
|
||||
function getFileName(url) { |
||||
var anchor = url.indexOf('#'); |
||||
var query = url.indexOf('?'); |
||||
var end = Math.min( |
||||
anchor > 0 ? anchor : url.length, |
||||
query > 0 ? query : url.length); |
||||
return url.substring(url.lastIndexOf('/', end) + 1, end); |
||||
} |
||||
|
||||
/** |
||||
* Returns scale factor for the canvas. It makes sense for the HiDPI displays. |
||||
* @return {Object} The object with horizontal (sx) and vertical (sy) |
||||
scales. The scaled property is set to false if scaling is |
||||
not required, true otherwise. |
||||
*/ |
||||
function getOutputScale(ctx) { |
||||
var devicePixelRatio = window.devicePixelRatio || 1; |
||||
var backingStoreRatio = ctx.webkitBackingStorePixelRatio || |
||||
ctx.mozBackingStorePixelRatio || |
||||
ctx.msBackingStorePixelRatio || |
||||
ctx.oBackingStorePixelRatio || |
||||
ctx.backingStorePixelRatio || 1; |
||||
var pixelRatio = devicePixelRatio / backingStoreRatio; |
||||
return { |
||||
sx: pixelRatio, |
||||
sy: pixelRatio, |
||||
scaled: pixelRatio != 1 |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Scrolls specified element into view of its parent. |
||||
* element {Object} The element to be visible. |
||||
* spot {Object} An object with optional top and left properties, |
||||
* specifying the offset from the top left edge. |
||||
*/ |
||||
function scrollIntoView(element, spot) { |
||||
// Assuming offsetParent is available (it's not available when viewer is in
|
||||
// hidden iframe or object). We have to scroll: if the offsetParent is not set
|
||||
// producing the error. See also animationStartedClosure.
|
||||
var parent = element.offsetParent; |
||||
var offsetY = element.offsetTop + element.clientTop; |
||||
var offsetX = element.offsetLeft + element.clientLeft; |
||||
if (!parent) { |
||||
console.error('offsetParent is not set -- cannot scroll'); |
||||
return; |
||||
} |
||||
while (parent.clientHeight === parent.scrollHeight) { |
||||
if (parent.dataset._scaleY) { |
||||
offsetY /= parent.dataset._scaleY; |
||||
offsetX /= parent.dataset._scaleX; |
||||
} |
||||
offsetY += parent.offsetTop; |
||||
offsetX += parent.offsetLeft; |
||||
parent = parent.offsetParent; |
||||
if (!parent) { |
||||
return; // no need to scroll
|
||||
} |
||||
} |
||||
if (spot) { |
||||
if (spot.top !== undefined) { |
||||
offsetY += spot.top; |
||||
} |
||||
if (spot.left !== undefined) { |
||||
offsetX += spot.left; |
||||
parent.scrollLeft = offsetX; |
||||
} |
||||
} |
||||
parent.scrollTop = offsetY; |
||||
} |
||||
|
||||
/** |
||||
* Event handler to suppress context menu. |
||||
*/ |
||||
function noContextMenuHandler(e) { |
||||
e.preventDefault(); |
||||
} |
||||
|
||||
/** |
||||
* Returns the filename or guessed filename from the url (see issue 3455). |
||||
* url {String} The original PDF location. |
||||
* @return {String} Guessed PDF file name. |
||||
*/ |
||||
function getPDFFileNameFromURL(url) { |
||||
var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/; |
||||
// SCHEME HOST 1.PATH 2.QUERY 3.REF
|
||||
// Pattern to get last matching NAME.pdf
|
||||
var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i; |
||||
var splitURI = reURI.exec(url); |
||||
var suggestedFilename = reFilename.exec(splitURI[1]) || |
||||
reFilename.exec(splitURI[2]) || |
||||
reFilename.exec(splitURI[3]); |
||||
if (suggestedFilename) { |
||||
suggestedFilename = suggestedFilename[0]; |
||||
if (suggestedFilename.indexOf('%') != -1) { |
||||
// URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
|
||||
try { |
||||
suggestedFilename = |
||||
reFilename.exec(decodeURIComponent(suggestedFilename))[0]; |
||||
} catch(e) { // Possible (extremely rare) errors:
|
||||
// URIError "Malformed URI", e.g. for "%AA.pdf"
|
||||
// TypeError "null has no properties", e.g. for "%2F.pdf"
|
||||
} |
||||
} |
||||
} |
||||
return suggestedFilename || 'document.pdf'; |
||||
} |
||||
|
||||
var ProgressBar = (function ProgressBarClosure() { |
||||
|
||||
function clamp(v, min, max) { |
||||
return Math.min(Math.max(v, min), max); |
||||
} |
||||
|
||||
function ProgressBar(id, opts) { |
||||
|
||||
// Fetch the sub-elements for later.
|
||||
this.div = document.querySelector(id + ' .progress'); |
||||
|
||||
// Get the loading bar element, so it can be resized to fit the viewer.
|
||||
this.bar = this.div.parentNode; |
||||
|
||||
// Get options, with sensible defaults.
|
||||
this.height = opts.height || 100; |
||||
this.width = opts.width || 100; |
||||
this.units = opts.units || '%'; |
||||
|
||||
// Initialize heights.
|
||||
this.div.style.height = this.height + this.units; |
||||
this.percent = 0; |
||||
} |
||||
|
||||
ProgressBar.prototype = { |
||||
|
||||
updateBar: function ProgressBar_updateBar() { |
||||
if (this._indeterminate) { |
||||
this.div.classList.add('indeterminate'); |
||||
this.div.style.width = this.width + this.units; |
||||
return; |
||||
} |
||||
|
||||
this.div.classList.remove('indeterminate'); |
||||
var progressSize = this.width * this._percent / 100; |
||||
this.div.style.width = progressSize + this.units; |
||||
}, |
||||
|
||||
get percent() { |
||||
return this._percent; |
||||
}, |
||||
|
||||
set percent(val) { |
||||
this._indeterminate = isNaN(val); |
||||
this._percent = clamp(val, 0, 100); |
||||
this.updateBar(); |
||||
}, |
||||
|
||||
setWidth: function ProgressBar_setWidth(viewer) { |
||||
if (viewer) { |
||||
var container = viewer.parentNode; |
||||
var scrollbarWidth = container.offsetWidth - viewer.offsetWidth; |
||||
if (scrollbarWidth > 0) { |
||||
this.bar.setAttribute('style', 'width: calc(100% - ' + |
||||
scrollbarWidth + 'px);'); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
hide: function ProgressBar_hide() { |
||||
this.bar.classList.add('hidden'); |
||||
this.bar.removeAttribute('style'); |
||||
} |
||||
}; |
||||
|
||||
return ProgressBar; |
||||
})(); |
||||
|
||||
var Cache = function cacheCache(size) { |
||||
var data = []; |
||||
this.push = function cachePush(view) { |
||||
var i = data.indexOf(view); |
||||
if (i >= 0) |
||||
data.splice(i); |
||||
data.push(view); |
||||
if (data.length > size) |
||||
data.shift().destroy(); |
||||
}; |
||||
}; |
||||
|
||||
//#if !(FIREFOX || MOZCENTRAL || B2G)
|
||||
var isLocalStorageEnabled = (function isLocalStorageEnabledClosure() { |
||||
// Feature test as per http://diveintohtml5.info/storage.html
|
||||
// The additional localStorage call is to get around a FF quirk, see
|
||||
// bug #495747 in bugzilla
|
||||
try { |
||||
return ('localStorage' in window && window['localStorage'] !== null && |
||||
localStorage); |
||||
} catch (e) { |
||||
return false; |
||||
} |
||||
})(); |
||||
//#endif
|
||||
@ -0,0 +1,801 @@ |
||||
/** |
||||
* Copyright (C) 2012-2014 KO GmbH <copyright@kogmbh.com> |
||||
* |
||||
* @licstart |
||||
* This file is part of WebODF. |
||||
* |
||||
* WebODF is free software: you can redistribute it and/or modify it |
||||
* under the terms of the GNU Affero General Public License (GNU AGPL) |
||||
* as published by the Free Software Foundation, either version 3 of |
||||
* the License, or (at your option) any later version. |
||||
* |
||||
* WebODF 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 Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with WebODF. If not, see <http://www.gnu.org/licenses/>. |
||||
* @licend |
||||
* |
||||
* @source: http://www.webodf.org/ |
||||
* @source: https://github.com/kogmbh/WebODF/ |
||||
*/ |
||||
|
||||
/* |
||||
* This file is a derivative from a part of Mozilla's PDF.js project. The |
||||
* original license header follows. |
||||
*/ |
||||
|
||||
/* Copyright 2012 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
* { |
||||
padding: 0; |
||||
margin: 0; |
||||
} |
||||
|
||||
html > body { |
||||
font-family: sans-serif; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.titlebar > span, |
||||
.toolbarLabel, |
||||
input, |
||||
button, |
||||
select { |
||||
font: message-box; |
||||
} |
||||
|
||||
#titlebar { |
||||
position: absolute; |
||||
z-index: 2; |
||||
top: 0px; |
||||
left: 0px; |
||||
height: 32px; |
||||
width: 100%; |
||||
overflow: hidden; |
||||
|
||||
-webkit-box-shadow: 0px 1px 3px rgba(50, 50, 50, 0.75); |
||||
-moz-box-shadow: 0px 1px 3px rgba(50, 50, 50, 0.75); |
||||
box-shadow: 0px 1px 3px rgba(50, 50, 50, 0.75); |
||||
|
||||
background-image: url(images/texture.png), linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); |
||||
background-image: url(images/texture.png), -webkit-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); |
||||
background-image: url(images/texture.png), -moz-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); |
||||
background-image: url(images/texture.png), -ms-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); |
||||
background-image: url(images/texture.png), -o-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); |
||||
} |
||||
|
||||
#titlebar a, #aboutDialog a, #titlebar a:visited, #aboutDialog a:visited { |
||||
color: #ccc; |
||||
} |
||||
|
||||
#documentName { |
||||
margin-right: 10px; |
||||
margin-left: 10px; |
||||
margin-top: 8px; |
||||
color: #F2F2F2; |
||||
line-height: 14px; |
||||
font-family: sans-serif; |
||||
} |
||||
#documentName { |
||||
font-size: 14px; |
||||
} |
||||
|
||||
#toolbarContainer { |
||||
position: absolute; |
||||
z-index: 2; |
||||
bottom: 0px; |
||||
left: 0px; |
||||
height: 32px; |
||||
width: 100%; |
||||
overflow: hidden; |
||||
|
||||
-webkit-box-shadow: 0px -1px 3px rgba(50, 50, 50, 0.75); |
||||
-moz-box-shadow: 0px -1px 3px rgba(50, 50, 50, 0.75); |
||||
box-shadow: 0px -1px 3px rgba(50, 50, 50, 0.75); |
||||
|
||||
background-image: url(images/texture.png), linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); |
||||
background-image: url(images/texture.png), -webkit-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); |
||||
background-image: url(images/texture.png), -moz-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); |
||||
background-image: url(images/texture.png), -ms-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); |
||||
background-image: url(images/texture.png), -o-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); |
||||
} |
||||
|
||||
#toolbar { |
||||
position: relative; |
||||
} |
||||
|
||||
#toolbarMiddleContainer, #toolbarLeft { |
||||
visibility: hidden; |
||||
} |
||||
|
||||
html[dir='ltr'] #toolbarLeft { |
||||
margin-left: -1px; |
||||
} |
||||
html[dir='rtl'] #toolbarRight { |
||||
margin-left: -1px; |
||||
} |
||||
html[dir='ltr'] #toolbarLeft, |
||||
html[dir='rtl'] #toolbarRight { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
} |
||||
html[dir='ltr'] #toolbarRight, |
||||
html[dir='rtl'] #toolbarLeft { |
||||
position: absolute; |
||||
top: 0; |
||||
right: 0; |
||||
} |
||||
html[dir='ltr'] #toolbarLeft > *, |
||||
html[dir='ltr'] #toolbarMiddle > *, |
||||
html[dir='ltr'] #toolbarRight > * { |
||||
float: left; |
||||
} |
||||
html[dir='rtl'] #toolbarLeft > *, |
||||
html[dir='rtl'] #toolbarMiddle > *, |
||||
html[dir='rtl'] #toolbarRight > * { |
||||
float: right; |
||||
} |
||||
|
||||
/* outer/inner center provides horizontal center */ |
||||
html[dir='ltr'] .outerCenter { |
||||
float: right; |
||||
position: relative; |
||||
right: 50%; |
||||
} |
||||
html[dir='rtl'] .outerCenter { |
||||
float: left; |
||||
position: relative; |
||||
left: 50%; |
||||
} |
||||
html[dir='ltr'] .innerCenter { |
||||
float: right; |
||||
position: relative; |
||||
right: -50%; |
||||
} |
||||
html[dir='rtl'] .innerCenter { |
||||
float: left; |
||||
position: relative; |
||||
left: -50%; |
||||
} |
||||
|
||||
html[dir='ltr'] .splitToolbarButton { |
||||
margin: 3px 2px 4px 0; |
||||
display: inline-block; |
||||
} |
||||
html[dir='rtl'] .splitToolbarButton { |
||||
margin: 3px 0 4px 2px; |
||||
display: inline-block; |
||||
} |
||||
html[dir='ltr'] .splitToolbarButton > .toolbarButton { |
||||
border-radius: 0; |
||||
float: left; |
||||
} |
||||
html[dir='rtl'] .splitToolbarButton > .toolbarButton { |
||||
border-radius: 0; |
||||
float: right; |
||||
} |
||||
|
||||
.splitToolbarButton.toggled .toolbarButton { |
||||
margin: 0; |
||||
} |
||||
|
||||
.toolbarButton { |
||||
border: 0 none; |
||||
background-color: rgba(0, 0, 0, 0); |
||||
min-width: 32px; |
||||
height: 25px; |
||||
border-radius: 2px; |
||||
background-image: none; |
||||
} |
||||
|
||||
html[dir='ltr'] .toolbarButton, |
||||
html[dir='ltr'] .dropdownToolbarButton { |
||||
margin: 3px 2px 4px 0; |
||||
} |
||||
html[dir='rtl'] .toolbarButton, |
||||
html[dir='rtl'] .dropdownToolbarButton { |
||||
margin: 3px 0 4px 2px; |
||||
} |
||||
|
||||
.toolbarButton:hover, |
||||
.toolbarButton:focus, |
||||
.dropdownToolbarButton { |
||||
background-color: hsla(0,0%,0%,.12); |
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-clip: padding-box; |
||||
border: 1px solid hsla(0,0%,0%,.35); |
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); |
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, |
||||
0 0 1px hsla(0,0%,100%,.15) inset, |
||||
0 1px 0 hsla(0,0%,100%,.05); |
||||
} |
||||
|
||||
.toolbarButton:hover:active, |
||||
.dropdownToolbarButton:hover:active { |
||||
background-color: hsla(0,0%,0%,.2); |
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45); |
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset, |
||||
0 0 1px hsla(0,0%,0%,.2) inset, |
||||
0 1px 0 hsla(0,0%,100%,.05); |
||||
} |
||||
|
||||
.splitToolbarButton:hover > .toolbarButton, |
||||
.splitToolbarButton:focus > .toolbarButton, |
||||
.splitToolbarButton.toggled > .toolbarButton, |
||||
.toolbarButton.textButton { |
||||
background-color: hsla(0,0%,0%,.12); |
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-clip: padding-box; |
||||
border: 1px solid hsla(0,0%,0%,.35); |
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); |
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, |
||||
0 0 1px hsla(0,0%,100%,.15) inset, |
||||
0 1px 0 hsla(0,0%,100%,.05); |
||||
-webkit-transition-property: background-color, border-color, box-shadow; |
||||
-webkit-transition-duration: 150ms; |
||||
-webkit-transition-timing-function: ease; |
||||
-moz-transition-property: background-color, border-color, box-shadow; |
||||
-moz-transition-duration: 150ms; |
||||
-moz-transition-timing-function: ease; |
||||
-ms-transition-property: background-color, border-color, box-shadow; |
||||
-ms-transition-duration: 150ms; |
||||
-ms-transition-timing-function: ease; |
||||
-o-transition-property: background-color, border-color, box-shadow; |
||||
-o-transition-duration: 150ms; |
||||
-o-transition-timing-function: ease; |
||||
transition-property: background-color, border-color, box-shadow; |
||||
transition-duration: 150ms; |
||||
transition-timing-function: ease; |
||||
|
||||
} |
||||
.splitToolbarButton > .toolbarButton:hover, |
||||
.splitToolbarButton > .toolbarButton:focus, |
||||
.dropdownToolbarButton:hover, |
||||
.toolbarButton.textButton:hover, |
||||
.toolbarButton.textButton:focus { |
||||
background-color: hsla(0,0%,0%,.2); |
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, |
||||
0 0 1px hsla(0,0%,100%,.15) inset, |
||||
0 0 1px hsla(0,0%,0%,.05); |
||||
z-index: 199; |
||||
} |
||||
|
||||
|
||||
.splitToolbarButton:hover > .toolbarButton, |
||||
.splitToolbarButton:focus > .toolbarButton, |
||||
.splitToolbarButton.toggled > .toolbarButton, |
||||
.toolbarButton.textButton { |
||||
background-color: hsla(0,0%,0%,.12); |
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-clip: padding-box; |
||||
border: 1px solid hsla(0,0%,0%,.35); |
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); |
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, |
||||
0 0 1px hsla(0,0%,100%,.15) inset, |
||||
0 1px 0 hsla(0,0%,100%,.05); |
||||
-webkit-transition-property: background-color, border-color, box-shadow; |
||||
-webkit-transition-duration: 150ms; |
||||
-webkit-transition-timing-function: ease; |
||||
-moz-transition-property: background-color, border-color, box-shadow; |
||||
-moz-transition-duration: 150ms; |
||||
-moz-transition-timing-function: ease; |
||||
-ms-transition-property: background-color, border-color, box-shadow; |
||||
-ms-transition-duration: 150ms; |
||||
-ms-transition-timing-function: ease; |
||||
-o-transition-property: background-color, border-color, box-shadow; |
||||
-o-transition-duration: 150ms; |
||||
-o-transition-timing-function: ease; |
||||
transition-property: background-color, border-color, box-shadow; |
||||
transition-duration: 150ms; |
||||
transition-timing-function: ease; |
||||
|
||||
} |
||||
.splitToolbarButton > .toolbarButton:hover, |
||||
.splitToolbarButton > .toolbarButton:focus, |
||||
.dropdownToolbarButton:hover, |
||||
.toolbarButton.textButton:hover, |
||||
.toolbarButton.textButton:focus { |
||||
background-color: hsla(0,0%,0%,.2); |
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, |
||||
0 0 1px hsla(0,0%,100%,.15) inset, |
||||
0 0 1px hsla(0,0%,0%,.05); |
||||
z-index: 199; |
||||
} |
||||
|
||||
.dropdownToolbarButton { |
||||
border: 1px solid #333 !important; |
||||
} |
||||
|
||||
.toolbarButton, |
||||
.dropdownToolbarButton { |
||||
min-width: 16px; |
||||
padding: 2px 6px 2px; |
||||
border: 1px solid transparent; |
||||
border-radius: 2px; |
||||
color: hsl(0,0%,95%); |
||||
font-size: 12px; |
||||
line-height: 14px; |
||||
-webkit-user-select:none; |
||||
-moz-user-select:none; |
||||
-ms-user-select:none; |
||||
/* Opera does not support user-select, use <... unselectable="on"> instead */ |
||||
cursor: default; |
||||
-webkit-transition-property: background-color, border-color, box-shadow; |
||||
-webkit-transition-duration: 150ms; |
||||
-webkit-transition-timing-function: ease; |
||||
-moz-transition-property: background-color, border-color, box-shadow; |
||||
-moz-transition-duration: 150ms; |
||||
-moz-transition-timing-function: ease; |
||||
-ms-transition-property: background-color, border-color, box-shadow; |
||||
-ms-transition-duration: 150ms; |
||||
-ms-transition-timing-function: ease; |
||||
-o-transition-property: background-color, border-color, box-shadow; |
||||
-o-transition-duration: 150ms; |
||||
-o-transition-timing-function: ease; |
||||
transition-property: background-color, border-color, box-shadow; |
||||
transition-duration: 150ms; |
||||
transition-timing-function: ease; |
||||
} |
||||
|
||||
html[dir='ltr'] .toolbarButton, |
||||
html[dir='ltr'] .dropdownToolbarButton { |
||||
margin: 3px 2px 4px 0; |
||||
} |
||||
html[dir='rtl'] .toolbarButton, |
||||
html[dir='rtl'] .dropdownToolbarButton { |
||||
margin: 3px 0 4px 2px; |
||||
} |
||||
|
||||
.splitToolbarButton:hover > .splitToolbarButtonSeparator, |
||||
.splitToolbarButton.toggled > .splitToolbarButtonSeparator { |
||||
padding: 12px 0; |
||||
margin: 0; |
||||
box-shadow: 0 0 0 1px hsla(0,0%,100%,.03); |
||||
-webkit-transition-property: padding; |
||||
-webkit-transition-duration: 10ms; |
||||
-webkit-transition-timing-function: ease; |
||||
-moz-transition-property: padding; |
||||
-moz-transition-duration: 10ms; |
||||
-moz-transition-timing-function: ease; |
||||
-ms-transition-property: padding; |
||||
-ms-transition-duration: 10ms; |
||||
-ms-transition-timing-function: ease; |
||||
-o-transition-property: padding; |
||||
-o-transition-duration: 10ms; |
||||
-o-transition-timing-function: ease; |
||||
transition-property: padding; |
||||
transition-duration: 10ms; |
||||
transition-timing-function: ease; |
||||
} |
||||
|
||||
.toolbarButton.toggled:hover:active, |
||||
.splitToolbarButton > .toolbarButton:hover:active { |
||||
background-color: hsla(0,0%,0%,.4); |
||||
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.5) hsla(0,0%,0%,.55); |
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset, |
||||
0 0 1px hsla(0,0%,0%,.3) inset, |
||||
0 1px 0 hsla(0,0%,100%,.05); |
||||
} |
||||
|
||||
html[dir='ltr'] .splitToolbarButton > .toolbarButton:first-child, |
||||
html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child { |
||||
position: relative; |
||||
margin: 0; |
||||
margin-left: 4px; |
||||
margin-right: -1px; |
||||
border-top-left-radius: 2px; |
||||
border-bottom-left-radius: 2px; |
||||
border-right-color: transparent; |
||||
} |
||||
html[dir='ltr'] .splitToolbarButton > .toolbarButton:last-child, |
||||
html[dir='rtl'] .splitToolbarButton > .toolbarButton:first-child { |
||||
position: relative; |
||||
margin: 0; |
||||
margin-left: -1px; |
||||
border-top-right-radius: 2px; |
||||
border-bottom-right-radius: 2px; |
||||
border-left-color: transparent; |
||||
} |
||||
.splitToolbarButtonSeparator { |
||||
padding: 8px 0; |
||||
width: 1px; |
||||
background-color: hsla(0,0%,00%,.5); |
||||
z-index: 99; |
||||
box-shadow: 0 0 0 1px hsla(0,0%,100%,.08); |
||||
display: inline-block; |
||||
margin: 5px 0; |
||||
} |
||||
html[dir='ltr'] .splitToolbarButtonSeparator { |
||||
float:left; |
||||
} |
||||
html[dir='rtl'] .splitToolbarButtonSeparator { |
||||
float:right; |
||||
} |
||||
|
||||
.dropdownToolbarButton { |
||||
min-width: 120px; |
||||
max-width: 120px; |
||||
padding: 4px 2px 4px; |
||||
overflow: hidden; |
||||
background: url(images/toolbarButton-menuArrows.png) no-repeat; |
||||
} |
||||
|
||||
.dropdownToolbarButton > select { |
||||
-webkit-appearance: none; |
||||
-moz-appearance: none; /* in the future this might matter, see bugzilla bug #649849 */ |
||||
min-width: 140px; |
||||
font-size: 12px; |
||||
color: hsl(0,0%,95%); |
||||
margin:0; |
||||
padding:0; |
||||
border:none; |
||||
background: rgba(0,0,0,0); /* Opera does not support 'transparent' <select> background */ |
||||
} |
||||
|
||||
.dropdownToolbarButton > select > option { |
||||
background: hsl(0,0%,24%); |
||||
} |
||||
|
||||
#pageWidthOption { |
||||
border-bottom: 1px rgba(255, 255, 255, .5) solid; |
||||
} |
||||
|
||||
|
||||
html[dir='ltr'] .dropdownToolbarButton { |
||||
background-position: 95%; |
||||
} |
||||
html[dir='rtl'] .dropdownToolbarButton { |
||||
background-position: 5%; |
||||
} |
||||
|
||||
|
||||
.toolbarButton.fullscreen::before { |
||||
display: inline-block; |
||||
content: url(images/toolbarButton-fullscreen.png); |
||||
} |
||||
|
||||
.toolbarButton.presentation::before { |
||||
display: inline-block; |
||||
content: url(images/toolbarButton-presentation.png); |
||||
} |
||||
|
||||
.toolbarButton.download::before { |
||||
display: inline-block; |
||||
content: url(images/toolbarButton-download.png); |
||||
} |
||||
|
||||
.toolbarButton.about { |
||||
color: #F2F2F2; |
||||
font-size: 14px; |
||||
font-weight: bold; |
||||
line-height: 14px; |
||||
font-family: sans-serif; |
||||
} |
||||
.toolbarButton.about::before { |
||||
display: inline-block; |
||||
} |
||||
|
||||
.toolbarButton.zoomOut::before { |
||||
display: inline-block; |
||||
content: url(images/toolbarButton-zoomOut.png); |
||||
} |
||||
|
||||
.toolbarButton.zoomIn::before { |
||||
display: inline-block; |
||||
content: url(images/toolbarButton-zoomIn.png); |
||||
} |
||||
|
||||
.toolbarButton.pageUp::before { |
||||
display: inline-block; |
||||
content: url(images/toolbarButton-pageUp.png); |
||||
} |
||||
|
||||
.toolbarButton.pageDown::before { |
||||
display: inline-block; |
||||
content: url(images/toolbarButton-pageDown.png); |
||||
} |
||||
|
||||
.toolbarField.pageNumber { |
||||
min-width: 16px; |
||||
text-align: right; |
||||
width: 40px; |
||||
} |
||||
|
||||
.toolbarField { |
||||
padding: 3px 6px; |
||||
margin: 4px 0 4px 0; |
||||
border: 1px solid transparent; |
||||
border-radius: 2px; |
||||
background-color: hsla(0,0%,100%,.09); |
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); |
||||
background-clip: padding-box; |
||||
border: 1px solid hsla(0,0%,0%,.35); |
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); |
||||
box-shadow: 0 1px 0 hsla(0,0%,0%,.05) inset, |
||||
0 1px 0 hsla(0,0%,100%,.05); |
||||
color: hsl(0,0%,95%); |
||||
font-size: 12px; |
||||
line-height: 14px; |
||||
outline-style: none; |
||||
-moz-transition-property: background-color, border-color, box-shadow; |
||||
-moz-transition-duration: 150ms; |
||||
-moz-transition-timing-function: ease; |
||||
} |
||||
|
||||
.toolbarField.pageNumber::-webkit-inner-spin-button, |
||||
.toolbarField.pageNumber::-webkit-outer-spin-button { |
||||
-webkit-appearance: none; |
||||
margin: 0; |
||||
} |
||||
|
||||
.toolbarField:hover { |
||||
background-color: hsla(0,0%,100%,.11); |
||||
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.43) hsla(0,0%,0%,.45); |
||||
} |
||||
|
||||
.toolbarField:focus { |
||||
background-color: hsla(0,0%,100%,.15); |
||||
border-color: hsla(204,100%,65%,.8) hsla(204,100%,65%,.85) hsla(204,100%,65%,.9); |
||||
} |
||||
|
||||
.toolbarLabel { |
||||
min-width: 16px; |
||||
padding: 3px 6px 3px 2px; |
||||
margin: 4px 2px 4px 0; |
||||
border: 1px solid transparent; |
||||
border-radius: 2px; |
||||
color: hsl(0,0%,85%); |
||||
font-size: 12px; |
||||
line-height: 14px; |
||||
text-align: left; |
||||
-webkit-user-select:none; |
||||
-moz-user-select:none; |
||||
cursor: default; |
||||
} |
||||
|
||||
#canvasContainer { |
||||
overflow: auto; |
||||
|
||||
padding-top: 6px; |
||||
padding-bottom: 6px; |
||||
position: absolute; |
||||
top: 32px; |
||||
right: 0; |
||||
bottom: 32px; |
||||
left: 0; |
||||
|
||||
text-align: center; |
||||
|
||||
background-color: #888; |
||||
background-image: url(images/texture.png); |
||||
} |
||||
|
||||
#canvasContainer.slideshow { |
||||
padding: 0; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
#canvasContainer.slideshow > * { |
||||
margin: auto; |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
} |
||||
|
||||
.presentationMode { |
||||
top: 0 !important; |
||||
bottom: 0 !important; |
||||
background-color: black !important; |
||||
cursor: default !important; |
||||
} |
||||
|
||||
#canvas { |
||||
box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
-webkit-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
-moz-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
-ms-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
-o-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); |
||||
|
||||
/* |
||||
* Hide the canvas overflow because otherwise the CSS-scaled 'sizer' child |
||||
* of the canvas will still advertise the original size in Firefox, causing |
||||
* strange scrollbar behavior. |
||||
*/ |
||||
overflow: hidden; |
||||
} |
||||
|
||||
#sliderContainer { |
||||
visibility: hidden; |
||||
} |
||||
|
||||
#overlayNavigator { |
||||
position: absolute; |
||||
width: 100%; |
||||
height: 0; |
||||
top: calc(50% - 50px); |
||||
background-color: rgba(0, 0, 0, 0); |
||||
z-index: 3; |
||||
opacity: 0; |
||||
-webkit-transition: opacity 1s ease-out; |
||||
-moz-transition: opacity 1s ease-out; |
||||
transition: opacity 1s ease-out; |
||||
} |
||||
#previousPage { |
||||
float: left; |
||||
margin-left: 10px; |
||||
|
||||
/* CSS triangle */ |
||||
border-top: 50px solid transparent; |
||||
border-bottom: 50px solid transparent; |
||||
border-right: 50px solid black; |
||||
|
||||
opacity: 0.5; |
||||
} |
||||
#nextPage { |
||||
float: right; |
||||
margin-right: 10px; |
||||
|
||||
/* CSS triangle */ |
||||
border-top: 50px solid transparent; |
||||
border-bottom: 50px solid transparent; |
||||
border-left: 50px solid black; |
||||
|
||||
opacity: 0.5; |
||||
} |
||||
#previousPage:active { |
||||
opacity: 0.8; |
||||
} |
||||
#nextPage:active { |
||||
opacity: 0.8; |
||||
} |
||||
#overlayCloseButton { |
||||
position: absolute; |
||||
top: 10px; |
||||
right: 10px; |
||||
z-index: 3; |
||||
font-size: 35px; |
||||
color: white; |
||||
background-color: black; |
||||
opacity: 0.5; |
||||
|
||||
width: 40px; |
||||
height: 40px; |
||||
-webkit-border-radius: 20px; |
||||
-moz-border-radius: 20px; |
||||
border-radius: 20px; |
||||
text-align: center; |
||||
cursor: pointer; |
||||
display: none; |
||||
} |
||||
#overlayCloseButton:active { |
||||
background-color: red; |
||||
} |
||||
|
||||
#aboutDialogCentererTable { |
||||
display: table; |
||||
width:100%; |
||||
height:100%; |
||||
} |
||||
|
||||
#aboutDialogCentererCell { |
||||
vertical-align: middle; |
||||
text-align:center; |
||||
display: table-cell; |
||||
} |
||||
|
||||
#aboutDialog { |
||||
width: 280px; |
||||
background-color: #666; |
||||
color: white; |
||||
text-align: center; |
||||
border-radius: 2px; |
||||
box-shadow: 0px 1px 6px black; |
||||
padding: 5px; |
||||
font-style: sans-serif; |
||||
display: inline-block; |
||||
} |
||||
|
||||
#aboutDialog h1 { |
||||
font-size: 25pt; |
||||
} |
||||
|
||||
#aboutDialog p { |
||||
font-size: 10pt; |
||||
} |
||||
|
||||
#aboutDialog > * { |
||||
margin: 10px; |
||||
} |
||||
|
||||
#dialogOverlay { |
||||
position: absolute; |
||||
left: 0px; |
||||
top: 0px; |
||||
width:100%; |
||||
height:100%; |
||||
z-index: 3; |
||||
background-color: rgba(0,0,0,.5); |
||||
overflow: auto; |
||||
display: none; |
||||
} |
||||
|
||||
@media only screen and (max-device-width: 800px) and (max-device-height: 800px) { |
||||
#canvasContainer { |
||||
top: 0; |
||||
bottom: 0; |
||||
} |
||||
|
||||
#overlayNavigator { |
||||
height: 100px; |
||||
pointer-events: none; |
||||
} |
||||
#nextPage, #previousPage { |
||||
pointer-events: all; |
||||
} |
||||
|
||||
#titlebar, #toolbarContainer { |
||||
background-color: rgba(0, 0, 0, 0.6); |
||||
background-image: none; |
||||
-webkit-transition: all 0.5s; |
||||
-moz-transition: all 0.5s; |
||||
transition: all 0.5s; |
||||
} |
||||
|
||||
#titlebar { |
||||
top: -32px; |
||||
} |
||||
#titlebar.viewer-touched { |
||||
top: 0px; |
||||
} |
||||
#toolbarContainer { |
||||
bottom: -32px; |
||||
} |
||||
#toolbarContainer.viewer-touched { |
||||
bottom: 0px; |
||||
} |
||||
|
||||
.viewer-touched { |
||||
display: block; |
||||
opacity: 1 !important; |
||||
} |
||||
|
||||
#next, #previous { |
||||
display: none; |
||||
} |
||||
} |
||||
@ -0,0 +1,16 @@ |
||||
function Viewer(c){function M(){var a,b,A,d,f;c&&(A=c.getPluginName(),d=c.getPluginVersion(),f=c.getPluginURL());a=document.createElement("div");a.id="aboutDialogCentererTable";b=document.createElement("div");b.id="aboutDialogCentererCell";n=document.createElement("div");n.id="aboutDialog";n.innerHTML='<h1>ViewerJS</h1><p>Open Source document viewer for webpages, built with HTML and JavaScript.</p><p>Learn more and get your own copy on the <a href="http://viewerjs.org/" target="_blank">ViewerJS website</a>.</p>'+ |
||||
(c?'<p>Using the <a href = "'+f+'" target="_blank">'+A+'</a> (<span id = "pluginVersion">'+d+"</span>) plugin to show you this document.</p>":"")+'<p>Supported by <a href="http://nlnet.nl" target="_blank"><br><img src="images/nlnet.png" width="160" height="60" alt="NLnet Foundation"></a></p><p>Made by <a href="http://kogmbh.com" target="_blank"><br><img src="images/kogmbh.png" width="172" height="40" alt="KO GmbH"></a></p><button id = "aboutDialogCloseButton" class = "toolbarButton textButton">Close</button>'; |
||||
u.appendChild(a);a.appendChild(b);b.appendChild(n);a=document.createElement("button");a.id="about";a.className="toolbarButton textButton about";a.title="About";a.innerHTML="ViewerJS";N.appendChild(a);a.addEventListener("click",function(){u.style.display="block"});document.getElementById("aboutDialogCloseButton").addEventListener("click",function(){u.style.display="none"})}function B(a){var b=O.options,c,d=!1,f;for(f=0;f<b.length;f+=1)c=b[f],c.value!==a?c.selected=!1:d=c.selected=!0;return d}function C(a, |
||||
c,d){a!==b.getZoomLevel()&&(b.setZoomLevel(a),d=document.createEvent("UIEvents"),d.initUIEvent("scalechange",!1,!1,window,0),d.scale=a,d.resetAutoSettings=c,window.dispatchEvent(d))}function D(){var a;if(c.onScroll)c.onScroll();c.getPageInView&&(a=c.getPageInView())&&(k=a,document.getElementById("pageNumber").value=a)}function E(a){window.clearTimeout(F);F=window.setTimeout(function(){D()},a)}function e(a,b,g){var e,f;if(e="custom"===a?parseFloat(document.getElementById("customScaleOption").textContent)/ |
||||
100:parseFloat(a))C(e,!0,g);else{e=d.clientWidth-p;f=d.clientHeight-p;switch(a){case "page-actual":C(1,b,g);break;case "page-width":c.fitToWidth(e);break;case "page-height":c.fitToHeight(f);break;case "page-fit":c.fitToPage(e,f);break;case "auto":c.isSlideshow()?c.fitToPage(e+p,f+p):c.fitSmart(e)}B(a)}E(300)}function q(){l=!l;r&&!l&&b.togglePresentationMode()}function v(){s&&(w.className="viewer-touched",window.clearTimeout(G),G=window.setTimeout(function(){w.className=""},5E3))}function x(){h.classList.add("viewer-touched"); |
||||
m.classList.add("viewer-touched");window.clearTimeout(H);H=window.setTimeout(function(){I()},5E3)}function I(){h.classList.remove("viewer-touched");m.classList.remove("viewer-touched")}function P(){h.classList.contains("viewer-touched")?I():x()}var b=this,p=40,r=!1,l=!1,J=!1,s=!1,y,g=document.getElementById("viewer"),d=document.getElementById("canvasContainer"),w=document.getElementById("overlayNavigator"),h=document.getElementById("titlebar"),m=document.getElementById("toolbarContainer"),K=document.getElementById("toolbarLeft"), |
||||
Q=document.getElementById("toolbarMiddleContainer"),O=document.getElementById("scaleSelect"),u=document.getElementById("dialogOverlay"),N=document.getElementById("toolbarRight"),n,L,t=[],k,F,G,H;this.initialize=function(){var a=String(document.location),z=a.indexOf("#"),a=a.substr(z+1);-1===z||0===a.length?console.log("Could not parse file path argument."):(y=a,L=y.replace(/^.*[\\\/]/,""),document.title=L,document.getElementById("documentName").innerHTML=document.title,c.onLoad=function(){document.getElementById("pluginVersion").innerHTML= |
||||
c.getPluginVersion();(s=c.isSlideshow())?(d.classList.add("slideshow"),K.style.visibility="visible"):(Q.style.visibility="visible",c.getPageInView&&(K.style.visibility="visible"));J=!0;t=c.getPages();document.getElementById("numPages").innerHTML="of "+t.length;b.showPage(1);e("auto");d.onscroll=D;E()},c.initialize(d,a))};this.showPage=function(a){0>=a?a=1:a>t.length&&(a=t.length);c.showPage(a);k=a;document.getElementById("pageNumber").value=k};this.showNextPage=function(){b.showPage(k+1)};this.showPreviousPage= |
||||
function(){b.showPage(k-1)};this.download=function(){var a=y.split("#")[0];window.open(a+"#viewer.action=download","_parent")};this.toggleFullScreen=function(){l?document.cancelFullScreen?document.cancelFullScreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.webkitCancelFullScreen?document.webkitCancelFullScreen():document.msExitFullscreen&&document.msExitFullscreen():g.requestFullScreen?g.requestFullScreen():g.mozRequestFullScreen?g.mozRequestFullScreen():g.webkitRequestFullScreen? |
||||
g.webkitRequestFullScreen():g.msRequestFullscreen&&g.msRequestFullscreen()};this.togglePresentationMode=function(){var a=document.getElementById("overlayCloseButton");r?(h.style.display=m.style.display="block",a.style.display="none",d.classList.remove("presentationMode"),d.onmouseup=function(){},d.oncontextmenu=function(){},d.onmousedown=function(){},e("auto"),s=c.isSlideshow()):(h.style.display=m.style.display="none",a.style.display="block",d.classList.add("presentationMode"),s=!0,d.onmousedown= |
||||
function(a){a.preventDefault()},d.oncontextmenu=function(a){a.preventDefault()},d.onmouseup=function(a){a.preventDefault();1===a.which?b.showNextPage():b.showPreviousPage()},e("page-fit"));r=!r};this.getZoomLevel=function(){return c.getZoomLevel()};this.setZoomLevel=function(a){c.setZoomLevel(a)};this.zoomOut=function(){var a=(b.getZoomLevel()/1.1).toFixed(2),a=Math.max(0.25,a);e(a,!0)};this.zoomIn=function(){var a=(1.1*b.getZoomLevel()).toFixed(2),a=Math.min(4,a);e(a,!0)};(function(){M();c&&(b.initialize(), |
||||
document.cancelFullScreen||document.mozCancelFullScreen||document.webkitCancelFullScreen||document.msExitFullscreen||(document.getElementById("fullscreen").style.visibility="hidden",document.getElementById("presentation").style.visibility="hidden"),document.getElementById("overlayCloseButton").addEventListener("click",b.toggleFullScreen),document.getElementById("fullscreen").addEventListener("click",b.toggleFullScreen),document.getElementById("presentation").addEventListener("click",function(){l|| |
||||
b.toggleFullScreen();b.togglePresentationMode()}),document.addEventListener("fullscreenchange",q),document.addEventListener("webkitfullscreenchange",q),document.addEventListener("mozfullscreenchange",q),document.addEventListener("MSFullscreenChange",q),document.getElementById("download").addEventListener("click",function(){b.download()}),document.getElementById("zoomOut").addEventListener("click",function(){b.zoomOut()}),document.getElementById("zoomIn").addEventListener("click",function(){b.zoomIn()}), |
||||
document.getElementById("previous").addEventListener("click",function(){b.showPreviousPage()}),document.getElementById("next").addEventListener("click",function(){b.showNextPage()}),document.getElementById("previousPage").addEventListener("click",function(){b.showPreviousPage()}),document.getElementById("nextPage").addEventListener("click",function(){b.showNextPage()}),document.getElementById("pageNumber").addEventListener("change",function(){b.showPage(this.value)}),document.getElementById("scaleSelect").addEventListener("change", |
||||
function(){e(this.value)}),d.addEventListener("click",v),w.addEventListener("click",v),d.addEventListener("click",P),h.addEventListener("click",x),m.addEventListener("click",x),window.addEventListener("scalechange",function(a){var b=document.getElementById("customScaleOption"),c=B(String(a.scale));b.selected=!1;c||(b.textContent=Math.round(1E4*a.scale)/100+"%",b.selected=!0)},!0),window.addEventListener("resize",function(a){J&&(document.getElementById("pageWidthOption").selected||document.getElementById("pageAutoOption").selected)&& |
||||
e(document.getElementById("scaleSelect").value);v()}),window.addEventListener("keydown",function(a){var c=a.shiftKey;switch(a.keyCode){case 33:case 38:case 37:b.showPreviousPage();break;case 34:case 40:case 39:b.showNextPage();break;case 32:c?b.showPreviousPage():b.showNextPage()}}))})()}; |
||||